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