io.dropwizard.metrics5.jmx.JmxReporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metrics-jmx Show documentation
Show all versions of metrics-jmx Show documentation
A set of classes which allow you to report metrics via JMX.
package io.dropwizard.metrics5.jmx;
import io.dropwizard.metrics5.Counter;
import io.dropwizard.metrics5.Gauge;
import io.dropwizard.metrics5.Histogram;
import io.dropwizard.metrics5.Meter;
import io.dropwizard.metrics5.Metered;
import io.dropwizard.metrics5.MetricFilter;
import io.dropwizard.metrics5.MetricName;
import io.dropwizard.metrics5.MetricRegistry;
import io.dropwizard.metrics5.MetricRegistryListener;
import io.dropwizard.metrics5.Reporter;
import io.dropwizard.metrics5.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import java.io.Closeable;
import java.lang.management.ManagementFactory;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* A reporter which listens for new metrics and exposes them as namespaced MBeans.
*/
public class JmxReporter implements Reporter, Closeable {
/**
* Returns a new {@link Builder} for {@link JmxReporter}.
*
* @param registry the registry to report
* @return a {@link Builder} instance for a {@link JmxReporter}
*/
public static Builder forRegistry(MetricRegistry registry) {
return new Builder(registry);
}
/**
* A builder for {@link JmxReporter} instances. Defaults to using the default MBean server and
* not filtering metrics.
*/
public static class Builder {
private final MetricRegistry registry;
private MBeanServer mBeanServer;
private TimeUnit rateUnit;
private TimeUnit durationUnit;
private ObjectNameFactory objectNameFactory;
private MetricFilter filter = MetricFilter.ALL;
private String domain;
private Map specificDurationUnits;
private Map specificRateUnits;
private Builder(MetricRegistry registry) {
this.registry = registry;
this.rateUnit = TimeUnit.SECONDS;
this.durationUnit = TimeUnit.MILLISECONDS;
this.domain = "metrics";
this.objectNameFactory = new DefaultObjectNameFactory();
this.specificDurationUnits = Collections.emptyMap();
this.specificRateUnits = Collections.emptyMap();
}
/**
* Register MBeans with the given {@link MBeanServer}.
*
* @param mBeanServer an {@link MBeanServer}
* @return {@code this}
*/
public Builder registerWith(MBeanServer mBeanServer) {
this.mBeanServer = mBeanServer;
return this;
}
/**
* Convert rates to the given time unit.
*
* @param rateUnit a unit of time
* @return {@code this}
*/
public Builder convertRatesTo(TimeUnit rateUnit) {
this.rateUnit = rateUnit;
return this;
}
public Builder createsObjectNamesWith(ObjectNameFactory onFactory) {
if (onFactory == null) {
throw new IllegalArgumentException("null objectNameFactory");
}
this.objectNameFactory = onFactory;
return this;
}
/**
* Convert durations to the given time unit.
*
* @param durationUnit a unit of time
* @return {@code this}
*/
public Builder convertDurationsTo(TimeUnit durationUnit) {
this.durationUnit = durationUnit;
return this;
}
/**
* Only report metrics which match the given filter.
*
* @param filter a {@link MetricFilter}
* @return {@code this}
*/
public Builder filter(MetricFilter filter) {
this.filter = filter;
return this;
}
public Builder inDomain(String domain) {
this.domain = domain;
return this;
}
/**
* Use specific {@link TimeUnit}s for the duration of the metrics with these names.
*
* @param specificDurationUnits a map of metric names and specific {@link TimeUnit}s
* @return {@code this}
*/
public Builder specificDurationUnits(Map specificDurationUnits) {
this.specificDurationUnits = Collections.unmodifiableMap(specificDurationUnits);
return this;
}
/**
* Use specific {@link TimeUnit}s for the rate of the metrics with these names.
*
* @param specificRateUnits a map of metric names and specific {@link TimeUnit}s
* @return {@code this}
*/
public Builder specificRateUnits(Map specificRateUnits) {
this.specificRateUnits = Collections.unmodifiableMap(specificRateUnits);
return this;
}
/**
* Builds a {@link JmxReporter} with the given properties.
*
* @return a {@link JmxReporter}
*/
public JmxReporter build() {
final MetricTimeUnits timeUnits = new MetricTimeUnits(rateUnit, durationUnit, specificRateUnits, specificDurationUnits);
if (mBeanServer == null) {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}
return new JmxReporter(mBeanServer, domain, registry, filter, timeUnits, objectNameFactory);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(JmxReporter.class);
@SuppressWarnings("UnusedDeclaration")
public interface MetricMBean {
ObjectName objectName();
}
private abstract static class AbstractBean implements MetricMBean {
private final ObjectName objectName;
AbstractBean(ObjectName objectName) {
this.objectName = objectName;
}
@Override
public ObjectName objectName() {
return objectName;
}
}
@SuppressWarnings("UnusedDeclaration")
public interface JmxGaugeMBean extends MetricMBean {
Object getValue();
Number getNumber();
}
private static class JmxGauge extends AbstractBean implements JmxGaugeMBean {
private final Gauge> metric;
private JmxGauge(Gauge> metric, ObjectName objectName) {
super(objectName);
this.metric = metric;
}
@Override
public Object getValue() {
return metric.getValue();
}
@Override
public Number getNumber() {
Object value = metric.getValue();
return value instanceof Number ? (Number) value : 0;
}
}
@SuppressWarnings("UnusedDeclaration")
public interface JmxCounterMBean extends MetricMBean {
long getCount();
}
private static class JmxCounter extends AbstractBean implements JmxCounterMBean {
private final Counter metric;
private JmxCounter(Counter metric, ObjectName objectName) {
super(objectName);
this.metric = metric;
}
@Override
public long getCount() {
return metric.getCount();
}
}
@SuppressWarnings("UnusedDeclaration")
public interface JmxHistogramMBean extends MetricMBean {
long getCount();
long getSum();
long getMin();
long getMax();
double getMean();
double getStdDev();
double get50thPercentile();
double get75thPercentile();
double get95thPercentile();
double get98thPercentile();
double get99thPercentile();
double get999thPercentile();
long[] values();
long getSnapshotSize();
}
private static class JmxHistogram implements JmxHistogramMBean {
private final ObjectName objectName;
private final Histogram metric;
private JmxHistogram(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.getCount();
}
@Override
public long getSum() {
return metric.getSum();
}
@Override
public long getMin() {
return metric.getSnapshot().getMin();
}
@Override
public long getMax() {
return metric.getSnapshot().getMax();
}
@Override
public double getMean() {
return metric.getSnapshot().getMean();
}
@Override
public double getStdDev() {
return metric.getSnapshot().getStdDev();
}
@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 long[] values() {
return metric.getSnapshot().getValues();
}
@Override
public long getSnapshotSize() {
return metric.getSnapshot().size();
}
}
@SuppressWarnings("UnusedDeclaration")
public interface JmxMeterMBean extends MetricMBean {
long getCount();
double getSum();
double getMeanRate();
double getOneMinuteRate();
double getFiveMinuteRate();
double getFifteenMinuteRate();
String getRateUnit();
}
private static class JmxMeter extends AbstractBean implements JmxMeterMBean {
private final Metered metric;
private final double rateFactor;
private final String rateUnit;
private JmxMeter(Metered metric, ObjectName objectName, TimeUnit rateUnit) {
super(objectName);
this.metric = metric;
this.rateFactor = rateUnit.toSeconds(1);
this.rateUnit = ("events/" + calculateRateUnit(rateUnit)).intern();
}
@Override
public long getCount() {
return metric.getCount();
}
@Override
public double getSum() {
return metric.getSum();
}
@Override
public double getMeanRate() {
return metric.getMeanRate() * rateFactor;
}
@Override
public double getOneMinuteRate() {
return metric.getOneMinuteRate() * rateFactor;
}
@Override
public double getFiveMinuteRate() {
return metric.getFiveMinuteRate() * rateFactor;
}
@Override
public double getFifteenMinuteRate() {
return metric.getFifteenMinuteRate() * rateFactor;
}
@Override
public String getRateUnit() {
return rateUnit;
}
private String calculateRateUnit(TimeUnit unit) {
final String s = unit.toString().toLowerCase(Locale.US);
return s.substring(0, s.length() - 1);
}
}
@SuppressWarnings("UnusedDeclaration")
public interface JmxTimerMBean extends JmxMeterMBean {
double getMin();
double getMax();
double getMean();
double getStdDev();
double get50thPercentile();
double get75thPercentile();
double get95thPercentile();
double get98thPercentile();
double get99thPercentile();
double get999thPercentile();
long[] values();
String getDurationUnit();
}
static class JmxTimer extends JmxMeter implements JmxTimerMBean {
private final Timer metric;
private final double durationFactor;
private final String durationUnit;
private JmxTimer(Timer metric,
ObjectName objectName,
TimeUnit rateUnit,
TimeUnit durationUnit) {
super(metric, objectName, rateUnit);
this.metric = metric;
this.durationFactor = 1.0 / durationUnit.toNanos(1);
this.durationUnit = durationUnit.toString().toLowerCase(Locale.US);
}
@Override
public double get50thPercentile() {
return metric.getSnapshot().getMedian() * durationFactor;
}
@Override
public double getMin() {
return metric.getSnapshot().getMin() * durationFactor;
}
@Override
public double getMax() {
return metric.getSnapshot().getMax() * durationFactor;
}
@Override
public double getMean() {
return metric.getSnapshot().getMean() * durationFactor;
}
@Override
public double getStdDev() {
return metric.getSnapshot().getStdDev() * durationFactor;
}
@Override
public double get75thPercentile() {
return metric.getSnapshot().get75thPercentile() * durationFactor;
}
@Override
public double get95thPercentile() {
return metric.getSnapshot().get95thPercentile() * durationFactor;
}
@Override
public double get98thPercentile() {
return metric.getSnapshot().get98thPercentile() * durationFactor;
}
@Override
public double get99thPercentile() {
return metric.getSnapshot().get99thPercentile() * durationFactor;
}
@Override
public double get999thPercentile() {
return metric.getSnapshot().get999thPercentile() * durationFactor;
}
@Override
public double getSum() {
return super.getSum() * durationFactor;
}
@Override
public long[] values() {
return metric.getSnapshot().getValues();
}
@Override
public String getDurationUnit() {
return durationUnit;
}
}
private static class JmxListener implements MetricRegistryListener {
private final String name;
private final MBeanServer mBeanServer;
private final MetricFilter filter;
private final MetricTimeUnits timeUnits;
private final Map registered;
private final ObjectNameFactory objectNameFactory;
private JmxListener(MBeanServer mBeanServer, String name, MetricFilter filter, MetricTimeUnits timeUnits, ObjectNameFactory objectNameFactory) {
this.mBeanServer = mBeanServer;
this.name = name;
this.filter = filter;
this.timeUnits = timeUnits;
this.registered = new ConcurrentHashMap<>();
this.objectNameFactory = objectNameFactory;
}
private void registerMBean(Object mBean, ObjectName objectName) throws InstanceAlreadyExistsException, JMException {
ObjectInstance objectInstance = mBeanServer.registerMBean(mBean, objectName);
if (objectInstance != null) {
// the websphere mbeanserver rewrites the objectname to include
// cell, node & server info
// make sure we capture the new objectName for unregistration
registered.put(objectName, objectInstance.getObjectName());
} else {
registered.put(objectName, objectName);
}
}
private void unregisterMBean(ObjectName originalObjectName) throws InstanceNotFoundException, MBeanRegistrationException {
ObjectName storedObjectName = registered.remove(originalObjectName);
if (storedObjectName != null) {
mBeanServer.unregisterMBean(storedObjectName);
} else {
mBeanServer.unregisterMBean(originalObjectName);
}
}
@Override
public void onGaugeAdded(MetricName name, Gauge> gauge) {
try {
if (filter.matches(name, gauge)) {
final ObjectName objectName = createName("gauges", name);
registerMBean(new JmxGauge(gauge, objectName), objectName);
}
} catch (InstanceAlreadyExistsException e) {
LOGGER.debug("Unable to register gauge", e);
} catch (JMException e) {
LOGGER.warn("Unable to register gauge", e);
}
}
@Override
public void onGaugeRemoved(MetricName name) {
try {
final ObjectName objectName = createName("gauges", name);
unregisterMBean(objectName);
} catch (InstanceNotFoundException e) {
LOGGER.debug("Unable to unregister gauge", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Unable to unregister gauge", e);
}
}
@Override
public void onCounterAdded(MetricName name, Counter counter) {
try {
if (filter.matches(name, counter)) {
final ObjectName objectName = createName("counters", name);
registerMBean(new JmxCounter(counter, objectName), objectName);
}
} catch (InstanceAlreadyExistsException e) {
LOGGER.debug("Unable to register counter", e);
} catch (JMException e) {
LOGGER.warn("Unable to register counter", e);
}
}
@Override
public void onCounterRemoved(MetricName name) {
try {
final ObjectName objectName = createName("counters", name);
unregisterMBean(objectName);
} catch (InstanceNotFoundException e) {
LOGGER.debug("Unable to unregister counter", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Unable to unregister counter", e);
}
}
@Override
public void onHistogramAdded(MetricName name, Histogram histogram) {
try {
if (filter.matches(name, histogram)) {
final ObjectName objectName = createName("histograms", name);
registerMBean(new JmxHistogram(histogram, objectName), objectName);
}
} catch (InstanceAlreadyExistsException e) {
LOGGER.debug("Unable to register histogram", e);
} catch (JMException e) {
LOGGER.warn("Unable to register histogram", e);
}
}
@Override
public void onHistogramRemoved(MetricName name) {
try {
final ObjectName objectName = createName("histograms", name);
unregisterMBean(objectName);
} catch (InstanceNotFoundException e) {
LOGGER.debug("Unable to unregister histogram", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Unable to unregister histogram", e);
}
}
@Override
public void onMeterAdded(MetricName name, Meter meter) {
try {
if (filter.matches(name, meter)) {
final ObjectName objectName = createName("meters", name);
registerMBean(new JmxMeter(meter, objectName, timeUnits.rateFor(name.getKey())), objectName);
}
} catch (InstanceAlreadyExistsException e) {
LOGGER.debug("Unable to register meter", e);
} catch (JMException e) {
LOGGER.warn("Unable to register meter", e);
}
}
@Override
public void onMeterRemoved(MetricName name) {
try {
final ObjectName objectName = createName("meters", name);
unregisterMBean(objectName);
} catch (InstanceNotFoundException e) {
LOGGER.debug("Unable to unregister meter", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Unable to unregister meter", e);
}
}
@Override
public void onTimerAdded(MetricName name, Timer timer) {
try {
if (filter.matches(name, timer)) {
final ObjectName objectName = createName("timers", name);
registerMBean(new JmxTimer(timer, objectName, timeUnits.rateFor(name.getKey()), timeUnits.durationFor(name.getKey())), objectName);
}
} catch (InstanceAlreadyExistsException e) {
LOGGER.debug("Unable to register timer", e);
} catch (JMException e) {
LOGGER.warn("Unable to register timer", e);
}
}
@Override
public void onTimerRemoved(MetricName name) {
try {
final ObjectName objectName = createName("timers", name);
unregisterMBean(objectName);
} catch (InstanceNotFoundException e) {
LOGGER.debug("Unable to unregister timer", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Unable to unregister timer", e);
}
}
private ObjectName createName(String type, MetricName name) {
return objectNameFactory.createName(type, this.name, name);
}
void unregisterAll() {
for (ObjectName name : registered.keySet()) {
try {
unregisterMBean(name);
} catch (InstanceNotFoundException e) {
LOGGER.debug("Unable to unregister metric", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Unable to unregister metric", e);
}
}
registered.clear();
}
}
private static class MetricTimeUnits {
private final TimeUnit defaultRate;
private final TimeUnit defaultDuration;
private final Map rateOverrides;
private final Map durationOverrides;
MetricTimeUnits(TimeUnit defaultRate,
TimeUnit defaultDuration,
Map rateOverrides,
Map durationOverrides) {
this.defaultRate = defaultRate;
this.defaultDuration = defaultDuration;
this.rateOverrides = rateOverrides;
this.durationOverrides = durationOverrides;
}
public TimeUnit durationFor(String name) {
return durationOverrides.getOrDefault(name, defaultDuration);
}
public TimeUnit rateFor(String name) {
return rateOverrides.getOrDefault(name, defaultRate);
}
}
private final MetricRegistry registry;
private final JmxListener listener;
private JmxReporter(MBeanServer mBeanServer,
String domain,
MetricRegistry registry,
MetricFilter filter,
MetricTimeUnits timeUnits,
ObjectNameFactory objectNameFactory) {
this.registry = registry;
this.listener = new JmxListener(mBeanServer, domain, filter, timeUnits, objectNameFactory);
}
/**
* Starts the reporter.
*/
public void start() {
registry.addListener(listener);
}
/**
* Stops the reporter.
*/
public void stop() {
registry.removeListener(listener);
listener.unregisterAll();
}
/**
* Stops the reporter.
*/
@Override
public void close() {
stop();
}
/**
* Visible for testing
*/
ObjectNameFactory getObjectNameFactory() {
return listener.objectNameFactory;
}
}