/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ /*************************************************************************************** * Attribution Notice * * This file is imported from Metrics (https://github.com/codahale/metrics subproject metrics-core). * Metrics is Copyright (c) 2010-2012 Coda Hale, Yammer.com * Metrics is Published under Apache Software License 2.0, see LICENSE in root folder. * * Thank you for the Metrics developers efforts in making their library available under an Apache license. * EsperTech incorporates Metrics version 0.2.2 in source code form since Metrics depends on SLF4J * and this dependency is not possible to introduce for Esper. * ************************************************************************************* */ package com.espertech.esper.metrics.codahale_metrics.metrics.reporting; import com.espertech.esper.metrics.codahale_metrics.metrics.core.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.*; import java.lang.management.ManagementFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** * A reporter which exposes application metric as JMX MBeans. */ public class JmxReporter extends AbstractReporter implements MetricsRegistryListener, MetricProcessor<JmxReporter.Context> { private static final Logger log = LoggerFactory.getLogger(JmxReporter.class); private final Map<MetricName, ObjectName> registeredBeans; private final MBeanServer server; // CHECKSTYLE:OFF @SuppressWarnings("UnusedDeclaration") public interface MetricMBean { ObjectName objectName(); } // CHECKSTYLE:ON private abstract static class AbstractBean implements MetricMBean { private final ObjectName objectName; protected AbstractBean(ObjectName objectName) { this.objectName = objectName; } @Override public ObjectName objectName() { return objectName; } } // CHECKSTYLE:OFF @SuppressWarnings("UnusedDeclaration") public interface GaugeMBean extends MetricMBean { Object getValue(); } // CHECKSTYLE:ON private static class Gauge extends AbstractBean implements GaugeMBean { private final com.espertech.esper.metrics.codahale_metrics.metrics.core.Gauge<?> metric; private Gauge(com.espertech.esper.metrics.codahale_metrics.metrics.core.Gauge<?> metric, ObjectName objectName) { super(objectName); this.metric = metric; } @Override public Object getValue() { return metric.value(); } } // CHECKSTYLE:OFF @SuppressWarnings("UnusedDeclaration") public interface CounterMBean extends MetricMBean { long getCount(); } // CHECKSTYLE:ON private static class Counter extends AbstractBean implements CounterMBean { private final com.espertech.esper.metrics.codahale_metrics.metrics.core.Counter metric; private Counter(com.espertech.esper.metrics.codahale_metrics.metrics.core.Counter metric, ObjectName objectName) { super(objectName); this.metric = metric; } @Override public long getCount() { return metric.count(); } } //CHECKSTYLE:OFF @SuppressWarnings("UnusedDeclaration") public interface MeterMBean extends MetricMBean { long getCount(); String getEventType(); TimeUnit getRateUnit(); double getMeanRate(); double getOneMinuteRate(); double getFiveMinuteRate(); double getFifteenMinuteRate(); } //CHECKSTYLE:ON private static class Meter extends AbstractBean implements MeterMBean { private final Metered metric; private Meter(Metered metric, ObjectName objectName) { super(objectName); this.metric = metric; } @Override public long getCount() { return metric.count(); } @Override public String getEventType() { return metric.eventType(); } @Override public TimeUnit getRateUnit() { return metric.rateUnit(); } @Override public double getMeanRate() { return metric.meanRate(); } @Override public double getOneMinuteRate() { return metric.oneMinuteRate(); } @Override public double getFiveMinuteRate() { return metric.fiveMinuteRate(); } @Override public double getFifteenMinuteRate() { return metric.fifteenMinuteRate(); } } // CHECKSTYLE:OFF @SuppressWarnings("UnusedDeclaration") public interface HistogramMBean extends MetricMBean { long getCount(); double getMin(); double getMax(); double getMean(); double getStdDev(); double get50thPercentile(); double get75thPercentile(); double get95thPercentile(); double get98thPercentile(); double get99thPercentile(); double get999thPercentile(); double[] values(); } // CHECKSTYLE:ON private static class Histogram implements HistogramMBean { private final ObjectName objectName; private final com.espertech.esper.metrics.codahale_metrics.metrics.core.Histogram metric; private Histogram(com.espertech.esper.metrics.codahale_metrics.metrics.core.Histogram metric, ObjectName objectName) { this.metric = metric; this.objectName = objectName; } @Override public ObjectName objectName() { return objectName; } @Override public double get50thPercentile() { return metric.getSnapshot().getMedian(); } @Override public long getCount() { return metric.count(); } @Override public double getMin() { return metric.min(); } @Override public double getMax() { return metric.max(); } @Override public double getMean() { return metric.mean(); } @Override public double getStdDev() { return metric.stdDev(); } @Override public double get75thPercentile() { return metric.getSnapshot().get75thPercentile(); } @Override public double get95thPercentile() { return metric.getSnapshot().get95thPercentile(); } @Override public double get98thPercentile() { return metric.getSnapshot().get98thPercentile(); } @Override public double get99thPercentile() { return metric.getSnapshot().get99thPercentile(); } @Override public double get999thPercentile() { return metric.getSnapshot().get999thPercentile(); } @Override public double[] values() { return metric.getSnapshot().getValues(); } } // CHECKSTYLE:OFF @SuppressWarnings("UnusedDeclaration") public interface TimerMBean extends MeterMBean, HistogramMBean { TimeUnit getLatencyUnit(); } // CHECKSTYLE:ON static class TimerImpl extends Meter implements TimerMBean { private final Timer metric; private TimerImpl(Timer metric, ObjectName objectName) { super(metric, objectName); this.metric = metric; } @Override public double get50thPercentile() { return metric.getSnapshot().getMedian(); } @Override public TimeUnit getLatencyUnit() { return metric.durationUnit(); } @Override public double getMin() { return metric.min(); } @Override public double getMax() { return metric.max(); } @Override public double getMean() { return metric.mean(); } @Override public double getStdDev() { return metric.stdDev(); } @Override public double get75thPercentile() { return metric.getSnapshot().get75thPercentile(); } @Override public double get95thPercentile() { return metric.getSnapshot().get95thPercentile(); } @Override public double get98thPercentile() { return metric.getSnapshot().get98thPercentile(); } @Override public double get99thPercentile() { return metric.getSnapshot().get99thPercentile(); } @Override public double get999thPercentile() { return metric.getSnapshot().get999thPercentile(); } @Override public double[] values() { return metric.getSnapshot().getValues(); } } private static JmxReporter instance; /** * Starts the default instance of {@link JmxReporter}. * * @param registry the {@link MetricsRegistry} to report from */ public static void startDefault(MetricsRegistry registry) { instance = new JmxReporter(registry); instance.start(); } /** * Returns the default instance of {@link JmxReporter} if it has been started. * * @return The default instance or null if the default is not used */ public static JmxReporter getDefault() { return instance; } /** * Stops the default instance of {@link JmxReporter}. */ public static void shutdownDefault() { if (instance != null) { instance.shutdown(); } } static final class Context { private final MetricName metricName; private final ObjectName objectName; public Context(final MetricName metricName, final ObjectName objectName) { this.metricName = metricName; this.objectName = objectName; } MetricName getMetricName() { return metricName; } ObjectName getObjectName() { return objectName; } } /** * Creates a new {@link JmxReporter} for the given registry. * * @param registry a {@link MetricsRegistry} */ public JmxReporter(MetricsRegistry registry) { super(registry); this.registeredBeans = new ConcurrentHashMap<MetricName, ObjectName>(100); this.server = ManagementFactory.getPlatformMBeanServer(); } @Override public void onMetricAdded(MetricName name, Metric metric) { if (metric != null) { try { metric.processWith(this, name, new Context(name, new ObjectName(name.getMBeanName()))); } catch (Exception e) { log.warn("Error processing '" + name + "': " + e.getMessage(), e); } } } @Override public void onMetricRemoved(MetricName name) { final ObjectName objectName = registeredBeans.remove(name); if (objectName != null) { unregisterBean(objectName); } } @Override public void processMeter(MetricName name, Metered meter, Context context) throws Exception { registerBean(context.getMetricName(), new Meter(meter, context.getObjectName()), context.getObjectName()); } @Override public void processCounter(MetricName name, com.espertech.esper.metrics.codahale_metrics.metrics.core.Counter counter, Context context) throws Exception { registerBean(context.getMetricName(), new Counter(counter, context.getObjectName()), context.getObjectName()); } @Override public void processHistogram(MetricName name, com.espertech.esper.metrics.codahale_metrics.metrics.core.Histogram histogram, Context context) throws Exception { registerBean(context.getMetricName(), new Histogram(histogram, context.getObjectName()), context.getObjectName()); } @Override public void processTimer(MetricName name, Timer timer, Context context) throws Exception { registerBean(context.getMetricName(), new TimerImpl(timer, context.getObjectName()), context.getObjectName()); } @Override public void processGauge(MetricName name, com.espertech.esper.metrics.codahale_metrics.metrics.core.Gauge gauge, Context context) throws Exception { registerBean(context.getMetricName(), new Gauge(gauge, context.getObjectName()), context.getObjectName()); } @Override public void shutdown() { getMetricsRegistry().removeListener(this); for (ObjectName name : registeredBeans.values()) { unregisterBean(name); } registeredBeans.clear(); } /** * Starts the reporter. */ public final void start() { getMetricsRegistry().addListener(this); } private void registerBean(MetricName name, MetricMBean bean, ObjectName objectName) throws MBeanRegistrationException, OperationsException { if (server.isRegistered(objectName)) { server.unregisterMBean(objectName); } server.registerMBean(bean, objectName); registeredBeans.put(name, objectName); } private void unregisterBean(ObjectName name) { try { server.unregisterMBean(name); } catch (InstanceNotFoundException e) { // This is often thrown when the process is shutting down. An application with lots of // metrics will often begin unregistering metrics *after* JMX itself has cleared, // resulting in a huge dump of exceptions as the process is exiting. log.trace("Error unregistering '" + name + "': " + e.getMessage(), e); } catch (MBeanRegistrationException e) { log.trace("Error unregistering '" + name + "': " + e.getMessage(), e); } } }