/*
* (C) Copyright 2013-2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Delbosc Benoit
*/
package org.nuxeo.runtime.metrics;
import java.io.File;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.LogManager;
import org.nuxeo.common.Environment;
import org.nuxeo.common.xmap.annotation.XNode;
import org.nuxeo.common.xmap.annotation.XObject;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.management.ServerLocator;
import com.codahale.metrics.JmxAttributeGauge;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import com.codahale.metrics.jvm.BufferPoolMetricSet;
import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import com.codahale.metrics.log4j.InstrumentedAppender;
@XObject("metrics")
public class MetricsDescriptor implements Serializable {
private static final long serialVersionUID = 7833869486922092460L;
public MetricsDescriptor() {
super();
graphiteReporter = new GraphiteDescriptor();
csvReporter = new CsvDescriptor();
tomcatInstrumentation = new TomcatInstrumentationDescriptor();
log4jInstrumentation = new Log4jInstrumentationDescriptor();
}
@XObject(value = "graphiteReporter")
public static class GraphiteDescriptor {
public static final String ENABLED_PROPERTY = "metrics.graphite.enabled";
public static final String HOST_PROPERTY = "metrics.graphite.host";
public static final String PORT_PROPERTY = "metrics.graphite.port";
public static final String PERIOD_PROPERTY = "metrics.graphite.period";
public static final String PREFIX_PROPERTY = "metrics.graphite.prefix";
@XNode("@enabled")
protected Boolean enabled = Boolean.valueOf(Framework.getProperty(ENABLED_PROPERTY, "false"));
@XNode("@host")
public String host = Framework.getProperty(HOST_PROPERTY, "0.0.0.0");
@XNode("@port")
public Integer port = Integer.valueOf(Framework.getProperty(PORT_PROPERTY, "2030"));
@XNode("@periodInSecond")
public Integer period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10"));
@XNode("@prefix")
public String prefix = prefix();
public String prefix() {
if (prefix == null) {
prefix = Framework.getProperty(PREFIX_PROPERTY, "servers.${hostname}.nuxeo");
}
String hostname;
try {
hostname = InetAddress.getLocalHost().getHostName().split("\\.")[0];
} catch (UnknownHostException e) {
hostname = "unknown";
}
return prefix.replace("${hostname}", hostname);
}
@Override
public String toString() {
return String.format("graphiteReporter %s prefix: %s, host: %s, port: %d, period: %d", enabled ? "enabled"
: "disabled", prefix, host, port, period);
}
protected GraphiteReporter reporter;
public void enable(MetricRegistry registry) {
if (!enabled) {
return;
}
InetSocketAddress address = new InetSocketAddress(host, port);
Graphite graphite = new Graphite(address);
reporter = GraphiteReporter.forRegistry(registry).convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(
TimeUnit.MICROSECONDS).prefixedWith(prefix()).build(graphite);
reporter.start(period, TimeUnit.SECONDS);
}
public void disable(MetricRegistry registry) {
if (reporter == null) {
return;
}
try {
reporter.stop();
} finally {
reporter = null;
}
}
}
@XObject(value = "csvReporter")
public static class CsvDescriptor {
public static final String ENABLED_PROPERTY = "metrics.csv.enabled";
public static final String PERIOD_PROPERTY = "metrics.csv.period";
public static final String OUTPUT_PROPERTY = "metrics.csv.output";
@XNode("@output")
public File outputDir = outputDir();
@XNode("@periodInSecond")
public Integer period = 10;
@XNode("@enabled")
public boolean enabled = Framework.isBooleanPropertyTrue(ENABLED_PROPERTY);
public int getPeriod() {
if (period == null) {
period = Integer.valueOf(Framework.getProperty(PERIOD_PROPERTY, "10"));
}
return period;
}
protected File outputDir() {
String path = Framework.getProperty(OUTPUT_PROPERTY, Framework.getProperty(Environment.NUXEO_LOG_DIR));
DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
Date today = Calendar.getInstance().getTime();
outputDir = new File(path, "metrics-" + df.format(today));
return outputDir;
}
@Override
public String toString() {
return String.format("csvReporter %s, outputDir: %s, period: %d", enabled ? "enabled" : "disabled",
outputDir().toString(), getPeriod());
}
protected CsvReporter reporter;
public void enable(MetricRegistry registry) {
if (!enabled) {
return;
}
File parentDir = outputDir.getParentFile();
if (parentDir.exists() && parentDir.isDirectory()) {
outputDir.mkdir();
reporter = CsvReporter.forRegistry(registry).build(outputDir);
reporter.start(Long.valueOf(period), TimeUnit.SECONDS);
} else {
enabled = false;
LogFactory.getLog(MetricsServiceImpl.class).error("Invalid output directory, disabling: " + this);
}
}
public void disable(MetricRegistry registry) {
if (reporter == null) {
return;
}
try {
reporter.stop();
} finally {
reporter = null;
}
}
}
@XObject(value = "log4jInstrumentation")
public static class Log4jInstrumentationDescriptor {
public static final String ENABLED_PROPERTY = "metrics.log4j.enabled";
@XNode("@enabled")
protected boolean enabled = Boolean.getBoolean(Framework.getProperty(ENABLED_PROPERTY, "false"));
private InstrumentedAppender appender;
@Override
public String toString() {
return String.format("log4jInstrumentation %s", enabled ? "enabled" : "disabled");
}
public void enable(MetricRegistry registry) {
if (!enabled) {
return;
}
LogFactory.getLog(MetricsServiceImpl.class).info(this);
appender = new InstrumentedAppender(registry);
LogManager.getRootLogger().addAppender(appender);
}
public void disable(MetricRegistry registry) {
if (appender == null) {
return;
}
try {
LogManager.getRootLogger().removeAppender(appender);
} finally {
appender = null;
}
}
}
@XObject(value = "tomcatInstrumentation")
public static class TomcatInstrumentationDescriptor {
public static final String ENABLED_PROPERTY = "metrics.tomcat.enabled";
@XNode("@enabled")
protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "false"));
@Override
public String toString() {
return String.format("tomcatInstrumentation %s", enabled ? "enabled" : "disabled");
}
protected void registerTomcatGauge(String mbean, String attribute, MetricRegistry registry, String name) {
try {
registry.register(MetricRegistry.name("tomcat", name), new JmxAttributeGauge(new ObjectName(mbean),
attribute));
} catch (MalformedObjectNameException | IllegalArgumentException e) {
throw new UnsupportedOperationException("Cannot compute object name of " + mbean, e);
}
}
public void enable(MetricRegistry registry) {
if (!enabled) {
return;
}
LogFactory.getLog(MetricsServiceImpl.class).info(this);
// TODO: do not hard code the common datasource
// nameenable(registry)
String pool = "Catalina:type=DataSource,class=javax.sql.DataSource,name=\"jdbc/nuxeo\"";
String connector = String.format("Catalina:type=ThreadPool,name=\"http-bio-%s-%s\"",
Framework.getProperty("nuxeo.bind.address", "0.0.0.0"),
Framework.getProperty("nuxeo.bind.port", "8080"));
String requestProcessor = String.format("Catalina:type=GlobalRequestProcessor,name=\"http-bio-%s-%s\"",
Framework.getProperty("nuxeo.bind.address", "0.0.0.0"),
Framework.getProperty("nuxeo.bind.port", "8080"));
String manager = "Catalina:type=Manager,context=/nuxeo,host=localhost";
registerTomcatGauge(pool, "numActive", registry, "jdbc-numActive");
registerTomcatGauge(pool, "numIdle", registry, "jdbc-numIdle");
registerTomcatGauge(connector, "currentThreadCount", registry, "currentThreadCount");
registerTomcatGauge(connector, "currentThreadsBusy", registry, "currentThreadBusy");
registerTomcatGauge(requestProcessor, "errorCount", registry, "errorCount");
registerTomcatGauge(requestProcessor, "requestCount", registry, "requestCount");
registerTomcatGauge(requestProcessor, "processingTime", registry, "processingTime");
registerTomcatGauge(manager, "activeSessions", registry, "activeSessions");
}
public void disable(MetricRegistry registry) {
registry.remove("tomcat.jdbc-numActive");
registry.remove("tomcat.jdbc-numIdle");
registry.remove("tomcat.currentThreadCount");
registry.remove("tomcat.currentThreadBusy");
registry.remove("tomcat.errorCount");
registry.remove("tomcat.requestCount");
registry.remove("tomcat.processingTime");
registry.remove("tomcat.activeSessions");
}
}
@XObject(value = "jvmInstrumentation")
public static class JvmInstrumentationDescriptor {
public static final String ENABLED_PROPERTY = "metrics.jvm.enabled";
@XNode("@enabled")
protected boolean enabled = Boolean.parseBoolean(Framework.getProperty(ENABLED_PROPERTY, "true"));
public void enable(MetricRegistry registry) {
if (!enabled) {
return;
}
registry.register("jvm.memory", new MemoryUsageGaugeSet());
registry.register("jvm.garbage", new GarbageCollectorMetricSet());
registry.register("jvm.threads", new ThreadStatesGaugeSet());
registry.register("jvm.files", new FileDescriptorRatioGauge());
registry.register("jvm.buffers", new BufferPoolMetricSet(
Framework.getLocalService(ServerLocator.class).lookupServer()));
}
public void disable(MetricRegistry registry) {
if (!enabled) {
return;
}
registry.removeMatching(new MetricFilter() {
@Override
public boolean matches(String name, Metric metric) {
return name.startsWith("jvm.");
}
});
}
}
@XNode("graphiteReporter")
public GraphiteDescriptor graphiteReporter = new GraphiteDescriptor();
@XNode("csvReporter")
public CsvDescriptor csvReporter = new CsvDescriptor();
@XNode("log4jInstrumentation")
public Log4jInstrumentationDescriptor log4jInstrumentation = new Log4jInstrumentationDescriptor();
@XNode("tomcatInstrumentation")
public TomcatInstrumentationDescriptor tomcatInstrumentation = new TomcatInstrumentationDescriptor();
@XNode(value = "jvmInstrumentation")
public JvmInstrumentationDescriptor jvmInstrumentation = new JvmInstrumentationDescriptor();
protected JmxReporter jmxReporter;
public void enable(MetricRegistry registry) {
jmxReporter = JmxReporter.forRegistry(registry).build();
jmxReporter.start();
graphiteReporter.enable(registry);
csvReporter.enable(registry);
log4jInstrumentation.enable(registry);
tomcatInstrumentation.enable(registry);
jvmInstrumentation.enable(registry);
}
public void disable(MetricRegistry registry) {
try {
graphiteReporter.disable(registry);
csvReporter.disable(registry);
log4jInstrumentation.disable(registry);
tomcatInstrumentation.disable(registry);
jvmInstrumentation.disable(registry);
jmxReporter.stop();
} finally {
jmxReporter = null;
}
}
}