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