001/* 002 * (C) Copyright 2013-2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Delbosc Benoit 016 */ 017package org.nuxeo.runtime.metrics; 018 019import java.io.File; 020import java.io.Serializable; 021import java.net.InetAddress; 022import java.net.InetSocketAddress; 023import java.net.UnknownHostException; 024import java.text.DateFormat; 025import java.text.SimpleDateFormat; 026import java.util.Calendar; 027import java.util.Date; 028import java.util.concurrent.TimeUnit; 029 030import javax.management.MalformedObjectNameException; 031import javax.management.ObjectName; 032 033import org.apache.commons.logging.LogFactory; 034import org.apache.log4j.LogManager; 035 036import org.nuxeo.common.Environment; 037import org.nuxeo.common.xmap.annotation.XNode; 038import org.nuxeo.common.xmap.annotation.XObject; 039import org.nuxeo.runtime.api.Framework; 040import org.nuxeo.runtime.management.ServerLocator; 041 042import com.codahale.metrics.CsvReporter; 043import com.codahale.metrics.JmxAttributeGauge; 044import com.codahale.metrics.JmxReporter; 045import com.codahale.metrics.Metric; 046import com.codahale.metrics.MetricFilter; 047import com.codahale.metrics.MetricRegistry; 048import com.codahale.metrics.graphite.Graphite; 049import com.codahale.metrics.graphite.GraphiteReporter; 050import com.codahale.metrics.jvm.BufferPoolMetricSet; 051import com.codahale.metrics.jvm.FileDescriptorRatioGauge; 052import com.codahale.metrics.jvm.GarbageCollectorMetricSet; 053import com.codahale.metrics.jvm.MemoryUsageGaugeSet; 054import com.codahale.metrics.jvm.ThreadStatesGaugeSet; 055import com.codahale.metrics.log4j.InstrumentedAppender; 056 057@XObject("metrics") 058public class MetricsDescriptor implements Serializable { 059 060 private static final long serialVersionUID = 7833869486922092460L; 061 062 public MetricsDescriptor() { 063 super(); 064 graphiteReporter = new GraphiteDescriptor(); 065 csvReporter = new CsvDescriptor(); 066 tomcatInstrumentation = new TomcatInstrumentationDescriptor(); 067 log4jInstrumentation = new Log4jInstrumentationDescriptor(); 068 } 069 070 @XObject(value = "graphiteReporter") 071 public static class GraphiteDescriptor { 072 073 public static final String ENABLED_PROPERTY = "metrics.graphite.enabled"; 074 075 public static final String HOST_PROPERTY = "metrics.graphite.host"; 076 077 public static final String PORT_PROPERTY = "metrics.graphite.port"; 078 079 public static final String PERIOD_PROPERTY = "metrics.graphite.period"; 080 081 public static final String PREFIX_PROPERTY = "metrics.graphite.prefix"; 082 083 @XNode("@enabled") 084 protected Boolean enabled = Boolean.valueOf(Framework.getProperty(ENABLED_PROPERTY, "false")); 085 086 @XNode("@host") 087 public String host = Framework.getProperty(HOST_PROPERTY, "0.0.0.0"); 088 089 @XNode("@port") 090 public Integer port = Integer.valueOf(Framework.getProperty(PORT_PROPERTY, "2030")); 091 092 @XNode("@periodInSecond") 093 public Integer period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10")); 094 095 @XNode("@prefix") 096 public String prefix = prefix(); 097 098 public String prefix() { 099 if (prefix == null) { 100 prefix = Framework.getProperty(PREFIX_PROPERTY, "servers.${hostname}.nuxeo"); 101 } 102 String hostname; 103 try { 104 hostname = InetAddress.getLocalHost().getHostName().split("\\.")[0]; 105 } catch (UnknownHostException e) { 106 hostname = "unknown"; 107 } 108 return prefix.replace("${hostname}", hostname); 109 } 110 111 @Override 112 public String toString() { 113 return String.format("graphiteReporter %s prefix: %s, host: %s, port: %d, period: %d", enabled ? "enabled" 114 : "disabled", prefix, host, port, period); 115 } 116 117 protected GraphiteReporter reporter; 118 119 public void enable(MetricRegistry registry) { 120 if (!enabled) { 121 return; 122 } 123 InetSocketAddress address = new InetSocketAddress(host, port); 124 Graphite graphite = new Graphite(address); 125 reporter = GraphiteReporter.forRegistry(registry).convertRatesTo(TimeUnit.SECONDS).convertDurationsTo( 126 TimeUnit.MICROSECONDS).prefixedWith(prefix()).build(graphite); 127 reporter.start(period, TimeUnit.SECONDS); 128 } 129 130 public void disable(MetricRegistry registry) { 131 if (reporter == null) { 132 return; 133 } 134 try { 135 reporter.stop(); 136 } finally { 137 reporter = null; 138 } 139 } 140 } 141 142 @XObject(value = "csvReporter") 143 public static class CsvDescriptor { 144 145 public static final String ENABLED_PROPERTY = "metrics.csv.enabled"; 146 147 public static final String PERIOD_PROPERTY = "metrics.csv.period"; 148 149 public static final String OUTPUT_PROPERTY = "metrics.csv.output"; 150 151 @XNode("@output") 152 public File outputDir = outputDir(); 153 154 @XNode("@periodInSecond") 155 public Integer period = 10; 156 157 @XNode("@enabled") 158 public boolean enabled = Framework.isBooleanPropertyTrue(ENABLED_PROPERTY); 159 160 public int getPeriod() { 161 if (period == null) { 162 period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10")); 163 } 164 return period; 165 } 166 167 protected File outputDir() { 168 String path = Framework.getProperty(OUTPUT_PROPERTY, Framework.getProperty(Environment.NUXEO_LOG_DIR)); 169 DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss"); 170 Date today = Calendar.getInstance().getTime(); 171 outputDir = new File(path, "metrics-" + df.format(today)); 172 return outputDir; 173 } 174 175 @Override 176 public String toString() { 177 return String.format("csvReporter %s, outputDir: %s, period: %d", enabled ? "enabled" : "disabled", 178 outputDir().toString(), getPeriod()); 179 } 180 181 protected CsvReporter reporter; 182 183 public void enable(MetricRegistry registry) { 184 if (!enabled) { 185 return; 186 } 187 File parentDir = outputDir.getParentFile(); 188 if (parentDir.exists() && parentDir.isDirectory()) { 189 outputDir.mkdir(); 190 reporter = CsvReporter.forRegistry(registry).build(outputDir); 191 reporter.start(Long.valueOf(period), TimeUnit.SECONDS); 192 } else { 193 enabled = false; 194 LogFactory.getLog(MetricsServiceImpl.class).error("Invalid output directory, disabling: " + this); 195 } 196 } 197 198 public void disable(MetricRegistry registry) { 199 if (reporter == null) { 200 return; 201 } 202 try { 203 reporter.stop(); 204 } finally { 205 reporter = null; 206 } 207 } 208 209 } 210 211 @XObject(value = "log4jInstrumentation") 212 public static class Log4jInstrumentationDescriptor { 213 214 public static final String ENABLED_PROPERTY = "metrics.log4j.enabled"; 215 216 @XNode("@enabled") 217 protected boolean enabled = Boolean.getBoolean(Framework.getProperty(ENABLED_PROPERTY, "false")); 218 219 private InstrumentedAppender appender; 220 221 @Override 222 public String toString() { 223 return String.format("log4jInstrumentation %s", enabled ? "enabled" : "disabled"); 224 } 225 226 public void enable(MetricRegistry registry) { 227 if (!enabled) { 228 return; 229 } 230 LogFactory.getLog(MetricsServiceImpl.class).info(this); 231 appender = new InstrumentedAppender(registry); 232 LogManager.getRootLogger().addAppender(appender); 233 } 234 235 public void disable(MetricRegistry registry) { 236 if (appender == null) { 237 return; 238 } 239 try { 240 LogManager.getRootLogger().removeAppender(appender); 241 } finally { 242 appender = null; 243 } 244 } 245 246 } 247 248 @XObject(value = "tomcatInstrumentation") 249 public static class TomcatInstrumentationDescriptor { 250 251 public static final String ENABLED_PROPERTY = "metrics.tomcat.enabled"; 252 253 @XNode("@enabled") 254 protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "false")); 255 256 @Override 257 public String toString() { 258 return String.format("tomcatInstrumentation %s", enabled ? "enabled" : "disabled"); 259 } 260 261 protected void registerTomcatGauge(String mbean, String attribute, MetricRegistry registry, String name) { 262 try { 263 registry.register(MetricRegistry.name("tomcat", name), new JmxAttributeGauge(new ObjectName(mbean), 264 attribute)); 265 } catch (MalformedObjectNameException | IllegalArgumentException e) { 266 throw new UnsupportedOperationException("Cannot compute object name of " + mbean, e); 267 } 268 } 269 270 public void enable(MetricRegistry registry) { 271 if (!enabled) { 272 return; 273 } 274 LogFactory.getLog(MetricsServiceImpl.class).info(this); 275 // TODO: do not hard code the common datasource 276 // nameenable(registry) 277 String pool = "Catalina:type=DataSource,class=javax.sql.DataSource,name=\"jdbc/nuxeo\""; 278 String connector = String.format("Catalina:type=ThreadPool,name=\"http-bio-%s-%s\"", 279 Framework.getProperty("nuxeo.bind.address", "0.0.0.0"), 280 Framework.getProperty("nuxeo.bind.port", "8080")); 281 String requestProcessor = String.format("Catalina:type=GlobalRequestProcessor,name=\"http-bio-%s-%s\"", 282 Framework.getProperty("nuxeo.bind.address", "0.0.0.0"), 283 Framework.getProperty("nuxeo.bind.port", "8080")); 284 String manager = "Catalina:type=Manager,context=/nuxeo,host=localhost"; 285 registerTomcatGauge(pool, "numActive", registry, "jdbc-numActive"); 286 registerTomcatGauge(pool, "numIdle", registry, "jdbc-numIdle"); 287 registerTomcatGauge(connector, "currentThreadCount", registry, "currentThreadCount"); 288 registerTomcatGauge(connector, "currentThreadsBusy", registry, "currentThreadBusy"); 289 registerTomcatGauge(requestProcessor, "errorCount", registry, "errorCount"); 290 registerTomcatGauge(requestProcessor, "requestCount", registry, "requestCount"); 291 registerTomcatGauge(requestProcessor, "processingTime", registry, "processingTime"); 292 registerTomcatGauge(manager, "activeSessions", registry, "activeSessions"); 293 } 294 295 public void disable(MetricRegistry registry) { 296 registry.remove("tomcat.jdbc-numActive"); 297 registry.remove("tomcat.jdbc-numIdle"); 298 registry.remove("tomcat.currentThreadCount"); 299 registry.remove("tomcat.currentThreadBusy"); 300 registry.remove("tomcat.errorCount"); 301 registry.remove("tomcat.requestCount"); 302 registry.remove("tomcat.processingTime"); 303 registry.remove("tomcat.activeSessions"); 304 } 305 } 306 307 @XObject(value = "jvmInstrumentation") 308 public static class JvmInstrumentationDescriptor { 309 310 public static final String ENABLED_PROPERTY = "metrics.jvm.enabled"; 311 312 @XNode("@enabled") 313 protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "true")); 314 315 public void enable(MetricRegistry registry) { 316 if (!enabled) { 317 return; 318 } 319 registry.register("jvm.memory", new MemoryUsageGaugeSet()); 320 registry.register("jvm.garbage", new GarbageCollectorMetricSet()); 321 registry.register("jvm.threads", new ThreadStatesGaugeSet()); 322 registry.register("jvm.files", new FileDescriptorRatioGauge()); 323 registry.register("jvm.buffers", new BufferPoolMetricSet( 324 Framework.getLocalService(ServerLocator.class).lookupServer())); 325 } 326 327 public void disable(MetricRegistry registry) { 328 if (!enabled) { 329 return; 330 } 331 registry.removeMatching(new MetricFilter() { 332 333 @Override 334 public boolean matches(String name, Metric metric) { 335 return name.startsWith("jvm."); 336 } 337 }); 338 339 } 340 } 341 342 @XNode("graphiteReporter") 343 public GraphiteDescriptor graphiteReporter = new GraphiteDescriptor(); 344 345 @XNode("csvReporter") 346 public CsvDescriptor csvReporter = new CsvDescriptor(); 347 348 @XNode("log4jInstrumentation") 349 public Log4jInstrumentationDescriptor log4jInstrumentation = new Log4jInstrumentationDescriptor(); 350 351 @XNode("tomcatInstrumentation") 352 public TomcatInstrumentationDescriptor tomcatInstrumentation = new TomcatInstrumentationDescriptor(); 353 354 @XNode(value = "jvmInstrumentation") 355 public JvmInstrumentationDescriptor jvmInstrumentation = new JvmInstrumentationDescriptor(); 356 357 protected JmxReporter jmxReporter; 358 359 public void enable(MetricRegistry registry) { 360 jmxReporter = JmxReporter.forRegistry(registry).build(); 361 jmxReporter.start(); 362 graphiteReporter.enable(registry); 363 csvReporter.enable(registry); 364 log4jInstrumentation.enable(registry); 365 tomcatInstrumentation.enable(registry); 366 jvmInstrumentation.enable(registry); 367 } 368 369 public void disable(MetricRegistry registry) { 370 try { 371 graphiteReporter.disable(registry); 372 csvReporter.disable(registry); 373 log4jInstrumentation.disable(registry); 374 tomcatInstrumentation.disable(registry); 375 jvmInstrumentation.disable(registry); 376 jmxReporter.stop(); 377 } finally { 378 jmxReporter = null; 379 } 380 } 381 382}