001/* 002 * (C) Copyright 2013-2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Delbosc Benoit 018 */ 019package org.nuxeo.runtime.metrics; 020 021import java.io.File; 022import java.io.Serializable; 023import java.net.InetAddress; 024import java.net.InetSocketAddress; 025import java.net.UnknownHostException; 026import java.text.DateFormat; 027import java.text.SimpleDateFormat; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Calendar; 031import java.util.Collections; 032import java.util.Date; 033import java.util.List; 034import java.util.concurrent.TimeUnit; 035import java.util.stream.Stream; 036 037import javax.management.MalformedObjectNameException; 038import javax.management.ObjectName; 039 040import org.apache.commons.logging.LogFactory; 041import org.apache.log4j.LogManager; 042 043import org.nuxeo.common.Environment; 044import org.nuxeo.common.xmap.annotation.XNode; 045import org.nuxeo.common.xmap.annotation.XNodeList; 046import org.nuxeo.common.xmap.annotation.XObject; 047import org.nuxeo.runtime.api.Framework; 048import org.nuxeo.runtime.management.ServerLocator; 049 050import com.codahale.metrics.JmxAttributeGauge; 051import com.codahale.metrics.JmxReporter; 052import com.codahale.metrics.Metric; 053import com.codahale.metrics.MetricFilter; 054import com.codahale.metrics.MetricRegistry; 055import com.codahale.metrics.graphite.Graphite; 056import com.codahale.metrics.graphite.GraphiteReporter; 057import com.codahale.metrics.jvm.BufferPoolMetricSet; 058import com.codahale.metrics.jvm.FileDescriptorRatioGauge; 059import com.codahale.metrics.jvm.GarbageCollectorMetricSet; 060import com.codahale.metrics.jvm.MemoryUsageGaugeSet; 061import com.codahale.metrics.jvm.ThreadStatesGaugeSet; 062import com.codahale.metrics.log4j.InstrumentedAppender; 063 064@XObject("metrics") 065public class MetricsDescriptor implements Serializable { 066 067 private static final long serialVersionUID = 7833869486922092460L; 068 069 public MetricsDescriptor() { 070 super(); 071 graphiteReporter = new GraphiteDescriptor(); 072 csvReporter = new CsvDescriptor(); 073 tomcatInstrumentation = new TomcatInstrumentationDescriptor(); 074 log4jInstrumentation = new Log4jInstrumentationDescriptor(); 075 } 076 077 @XObject(value = "graphiteReporter") 078 public static class GraphiteDescriptor { 079 080 public static final String ENABLED_PROPERTY = "metrics.graphite.enabled"; 081 082 public static final String HOST_PROPERTY = "metrics.graphite.host"; 083 084 public static final String PORT_PROPERTY = "metrics.graphite.port"; 085 086 public static final String PERIOD_PROPERTY = "metrics.graphite.period"; 087 088 public static final String PREFIX_PROPERTY = "metrics.graphite.prefix"; 089 090 /** 091 * A list of metric prefixes that if defined should be kept reported, separated by commas 092 * 093 * @since 9.3 094 */ 095 public static final String ALLOWED_METRICS_PROPERTY = "metrics.graphite.allowedMetrics"; 096 097 /** 098 * A list of metric prefixes that if defined should not be reported, separated by commas 099 * 100 * @since 9.3 101 */ 102 public static final String DENIED_METRICS_PROPERTY = "metrics.graphite.deniedMetrics"; 103 104 /** 105 * @since 9.3 106 */ 107 public static final String DEFAULT_ALLOWED_METRICS = "nuxeo.cache.user-entry-cache,nuxeo.cache.group-entry-cache,nuxeo.directories.userDirectory,nuxeo.directories.groupDirectory"; 108 109 /** 110 * @since 9.3 111 */ 112 public static final String DEFAULT_DENIED_METRICS = "nuxeo.cache,nuxeo.directories"; 113 114 /** 115 * @since 9.3 116 */ 117 public static final String ALL_METRICS = "ALL"; 118 119 @XNode("@enabled") 120 protected Boolean enabled = Boolean.valueOf(Framework.getProperty(ENABLED_PROPERTY, "false")); 121 122 @XNode("@host") 123 public String host = Framework.getProperty(HOST_PROPERTY, "0.0.0.0"); 124 125 @XNode("@port") 126 public Integer port = Integer.valueOf(Framework.getProperty(PORT_PROPERTY, "2030")); 127 128 @XNode("@periodInSecond") 129 public Integer period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10")); 130 131 @XNode("@prefix") 132 public String prefix = prefix(); 133 134 /** 135 * A list of metric prefixes that if defined should be kept reported 136 * 137 * @since 9.3 138 */ 139 @XNodeList(value = "allowedMetrics/metric", type = ArrayList.class, componentType = String.class) 140 public List<String> allowedMetrics = Arrays.asList( 141 Framework.getProperty(ALLOWED_METRICS_PROPERTY, DEFAULT_ALLOWED_METRICS).split(",")); 142 143 /** 144 * A list of metric prefixes that if defined should not be reported 145 * 146 * @since 9.3 147 */ 148 @XNodeList(value = "deniedMetrics/metric", type = ArrayList.class, componentType = String.class) 149 public List<String> deniedMetrics = Arrays.asList( 150 Framework.getProperty(DENIED_METRICS_PROPERTY, DEFAULT_DENIED_METRICS).split(",")); 151 152 public String prefix() { 153 if (prefix == null) { 154 prefix = Framework.getProperty(PREFIX_PROPERTY, "servers.${hostname}.nuxeo"); 155 } 156 String hostname; 157 try { 158 hostname = InetAddress.getLocalHost().getHostName().split("\\.")[0]; 159 } catch (UnknownHostException e) { 160 hostname = "unknown"; 161 } 162 return prefix.replace("${hostname}", hostname); 163 } 164 165 public boolean filter(String name) { 166 return allowedMetrics.stream().anyMatch(f -> ALL_METRICS.equals(f) || name.startsWith(f)) 167 || deniedMetrics.stream().noneMatch(f -> ALL_METRICS.equals(f) || name.startsWith(f)); 168 } 169 170 @Override 171 public String toString() { 172 return String.format("graphiteReporter %s prefix: %s, host: %s, port: %d, period: %d", enabled ? "enabled" 173 : "disabled", prefix, host, port, period); 174 } 175 176 protected GraphiteReporter reporter; 177 178 public void enable(MetricRegistry registry) { 179 if (!enabled) { 180 return; 181 } 182 183 InetSocketAddress address = new InetSocketAddress(host, port); 184 Graphite graphite = new Graphite(address); 185 reporter = GraphiteReporter.forRegistry(registry) 186 .convertRatesTo(TimeUnit.SECONDS) 187 .convertDurationsTo(TimeUnit.MICROSECONDS) 188 .prefixedWith(prefix()) 189 .filter((name, metric) -> filter(name)) 190 .build(graphite); 191 reporter.start(period, TimeUnit.SECONDS); 192 } 193 194 public void disable(MetricRegistry registry) { 195 if (reporter == null) { 196 return; 197 } 198 try { 199 reporter.stop(); 200 } finally { 201 reporter = null; 202 } 203 } 204 } 205 206 @XObject(value = "csvReporter") 207 public static class CsvDescriptor { 208 209 public static final String ENABLED_PROPERTY = "metrics.csv.enabled"; 210 211 public static final String PERIOD_PROPERTY = "metrics.csv.period"; 212 213 public static final String OUTPUT_PROPERTY = "metrics.csv.output"; 214 215 @XNode("@output") 216 public File outputDir = outputDir(); 217 218 @XNode("@periodInSecond") 219 public Integer period = 10; 220 221 @XNode("@enabled") 222 public boolean enabled = Framework.isBooleanPropertyTrue(ENABLED_PROPERTY); 223 224 public int getPeriod() { 225 if (period == null) { 226 period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10")); 227 } 228 return period; 229 } 230 231 protected File outputDir() { 232 String path = Framework.getProperty(OUTPUT_PROPERTY, Framework.getProperty(Environment.NUXEO_LOG_DIR)); 233 DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss"); 234 Date today = Calendar.getInstance().getTime(); 235 outputDir = new File(path, "metrics-" + df.format(today)); 236 return outputDir; 237 } 238 239 @Override 240 public String toString() { 241 return String.format("csvReporter %s, outputDir: %s, period: %d", enabled ? "enabled" : "disabled", 242 outputDir().toString(), getPeriod()); 243 } 244 245 protected CsvReporter reporter; 246 247 public void enable(MetricRegistry registry) { 248 if (!enabled) { 249 return; 250 } 251 File parentDir = outputDir.getParentFile(); 252 if (parentDir.exists() && parentDir.isDirectory()) { 253 outputDir.mkdir(); 254 reporter = CsvReporter.forRegistry(registry).build(outputDir); 255 reporter.start(Long.valueOf(period), TimeUnit.SECONDS); 256 } else { 257 enabled = false; 258 LogFactory.getLog(MetricsServiceImpl.class).error("Invalid output directory, disabling: " + this); 259 } 260 } 261 262 public void disable(MetricRegistry registry) { 263 if (reporter == null) { 264 return; 265 } 266 try { 267 reporter.stop(); 268 } finally { 269 reporter = null; 270 } 271 } 272 273 } 274 275 @XObject(value = "log4jInstrumentation") 276 public static class Log4jInstrumentationDescriptor { 277 278 public static final String ENABLED_PROPERTY = "metrics.log4j.enabled"; 279 280 @XNode("@enabled") 281 protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "false")); 282 283 private InstrumentedAppender appender; 284 285 @Override 286 public String toString() { 287 return String.format("log4jInstrumentation %s", enabled ? "enabled" : "disabled"); 288 } 289 290 public void enable(MetricRegistry registry) { 291 if (!enabled) { 292 return; 293 } 294 LogFactory.getLog(MetricsServiceImpl.class).info(this); 295 appender = new InstrumentedAppender(registry); 296 appender.activateOptions(); 297 LogManager.getRootLogger().addAppender(appender); 298 } 299 300 public void disable(MetricRegistry registry) { 301 if (appender == null) { 302 return; 303 } 304 try { 305 LogManager.getRootLogger().removeAppender(appender); 306 } finally { 307 appender = null; 308 } 309 } 310 311 } 312 313 @XObject(value = "tomcatInstrumentation") 314 public static class TomcatInstrumentationDescriptor { 315 316 public static final String ENABLED_PROPERTY = "metrics.tomcat.enabled"; 317 318 @XNode("@enabled") 319 protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "false")); 320 321 @Override 322 public String toString() { 323 return String.format("tomcatInstrumentation %s", enabled ? "enabled" : "disabled"); 324 } 325 326 protected void registerTomcatGauge(String mbean, String attribute, MetricRegistry registry, String name) { 327 try { 328 registry.register(MetricRegistry.name("tomcat", name), new JmxAttributeGauge(new ObjectName(mbean), 329 attribute)); 330 } catch (MalformedObjectNameException | IllegalArgumentException e) { 331 throw new UnsupportedOperationException("Cannot compute object name of " + mbean, e); 332 } 333 } 334 335 public void enable(MetricRegistry registry) { 336 if (!enabled) { 337 return; 338 } 339 LogFactory.getLog(MetricsServiceImpl.class).info(this); 340 // TODO: do not hard code the common datasource 341 // nameenable(registry) 342 String pool = "Catalina:type=DataSource,class=javax.sql.DataSource,name=\"jdbc/nuxeo\""; 343 String connector = String.format("Catalina:type=ThreadPool,name=\"http-bio-%s-%s\"", 344 Framework.getProperty("nuxeo.bind.address", "0.0.0.0"), 345 Framework.getProperty("nuxeo.bind.port", "8080")); 346 String requestProcessor = String.format("Catalina:type=GlobalRequestProcessor,name=\"http-bio-%s-%s\"", 347 Framework.getProperty("nuxeo.bind.address", "0.0.0.0"), 348 Framework.getProperty("nuxeo.bind.port", "8080")); 349 String manager = "Catalina:type=Manager,context=/nuxeo,host=localhost"; 350 registerTomcatGauge(pool, "numActive", registry, "jdbc-numActive"); 351 registerTomcatGauge(pool, "numIdle", registry, "jdbc-numIdle"); 352 registerTomcatGauge(connector, "currentThreadCount", registry, "currentThreadCount"); 353 registerTomcatGauge(connector, "currentThreadsBusy", registry, "currentThreadBusy"); 354 registerTomcatGauge(requestProcessor, "errorCount", registry, "errorCount"); 355 registerTomcatGauge(requestProcessor, "requestCount", registry, "requestCount"); 356 registerTomcatGauge(requestProcessor, "processingTime", registry, "processingTime"); 357 registerTomcatGauge(manager, "activeSessions", registry, "activeSessions"); 358 } 359 360 public void disable(MetricRegistry registry) { 361 registry.remove("tomcat.jdbc-numActive"); 362 registry.remove("tomcat.jdbc-numIdle"); 363 registry.remove("tomcat.currentThreadCount"); 364 registry.remove("tomcat.currentThreadBusy"); 365 registry.remove("tomcat.errorCount"); 366 registry.remove("tomcat.requestCount"); 367 registry.remove("tomcat.processingTime"); 368 registry.remove("tomcat.activeSessions"); 369 } 370 } 371 372 @XObject(value = "jvmInstrumentation") 373 public static class JvmInstrumentationDescriptor { 374 375 public static final String ENABLED_PROPERTY = "metrics.jvm.enabled"; 376 377 @XNode("@enabled") 378 protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "true")); 379 380 public void enable(MetricRegistry registry) { 381 if (!enabled) { 382 return; 383 } 384 registry.register("jvm.memory", new MemoryUsageGaugeSet()); 385 registry.register("jvm.garbage", new GarbageCollectorMetricSet()); 386 registry.register("jvm.threads", new ThreadStatesGaugeSet()); 387 registry.register("jvm.files", new FileDescriptorRatioGauge()); 388 registry.register("jvm.buffers", new BufferPoolMetricSet( 389 Framework.getService(ServerLocator.class).lookupServer())); 390 } 391 392 public void disable(MetricRegistry registry) { 393 if (!enabled) { 394 return; 395 } 396 registry.removeMatching(new MetricFilter() { 397 398 @Override 399 public boolean matches(String name, Metric metric) { 400 return name.startsWith("jvm."); 401 } 402 }); 403 404 } 405 } 406 407 @XNode("graphiteReporter") 408 public GraphiteDescriptor graphiteReporter = new GraphiteDescriptor(); 409 410 @XNode("csvReporter") 411 public CsvDescriptor csvReporter = new CsvDescriptor(); 412 413 @XNode("log4jInstrumentation") 414 public Log4jInstrumentationDescriptor log4jInstrumentation = new Log4jInstrumentationDescriptor(); 415 416 @XNode("tomcatInstrumentation") 417 public TomcatInstrumentationDescriptor tomcatInstrumentation = new TomcatInstrumentationDescriptor(); 418 419 @XNode(value = "jvmInstrumentation") 420 public JvmInstrumentationDescriptor jvmInstrumentation = new JvmInstrumentationDescriptor(); 421 422 protected JmxReporter jmxReporter; 423 424 public void enable(MetricRegistry registry) { 425 jmxReporter = JmxReporter.forRegistry(registry).build(); 426 jmxReporter.start(); 427 graphiteReporter.enable(registry); 428 csvReporter.enable(registry); 429 log4jInstrumentation.enable(registry); 430 tomcatInstrumentation.enable(registry); 431 jvmInstrumentation.enable(registry); 432 } 433 434 public void disable(MetricRegistry registry) { 435 try { 436 graphiteReporter.disable(registry); 437 csvReporter.disable(registry); 438 log4jInstrumentation.disable(registry); 439 tomcatInstrumentation.disable(registry); 440 jvmInstrumentation.disable(registry); 441 jmxReporter.stop(); 442 } finally { 443 jmxReporter = null; 444 } 445 } 446 447}