package io.dropwizard.metrics.collectd; import io.dropwizard.metrics.Clock; import io.dropwizard.metrics.Counter; import io.dropwizard.metrics.Gauge; import io.dropwizard.metrics.Histogram; import io.dropwizard.metrics.Meter; import io.dropwizard.metrics.Metered; import io.dropwizard.metrics.MetricFilter; import io.dropwizard.metrics.MetricName; import io.dropwizard.metrics.MetricRegistry; import io.dropwizard.metrics.ScheduledReporter; import io.dropwizard.metrics.Snapshot; import io.dropwizard.metrics.Timer; import java.util.Map; import java.util.SortedMap; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A reporter which publishes metric values to a Collectd server. * * @see <a href="https://collectd.org">collectd – The system statistics * collection daemon</a> */ public class CollectdReporter extends ScheduledReporter { /** * Returns a new {@link Builder} for {@link CollectdReporter}. * * @param registry * the registry to report * @return a {@link Builder} instance for a {@link CollectdReporter} */ public static Builder forRegistry(MetricRegistry registry) { return new Builder(registry); } /** * A builder for {@link CollectdReporter} instances. Defaults to not using a * prefix, using the default clock, converting rates to events/second, * converting durations to milliseconds, and not filtering metrics. */ public static class Builder { private final MetricRegistry registry; private Clock clock; private TimeUnit rateUnit; private TimeUnit durationUnit; private MetricFilter filter; private Builder(MetricRegistry registry) { this.registry = registry; this.clock = Clock.defaultClock(); this.rateUnit = TimeUnit.SECONDS; this.durationUnit = TimeUnit.MILLISECONDS; this.filter = MetricFilter.ALL; } /** * Use the given {@link Clock} instance for the time. * * @param clock * a {@link Clock} instance * @return {@code this} */ public Builder withClock(Clock clock) { this.clock = clock; 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; } /** * 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; } /** * Builds a {@link CollectdReporter} with the given properties, sending * metrics using the given {@link Collectd}. * * @param collectd * a {@link Collectd} * @return a {@link CollectdReporter} */ public CollectdReporter build(Collectd collectd) { return new CollectdReporter(registry, collectd, clock, rateUnit, durationUnit, filter); } } private static final Logger LOGGER = LoggerFactory.getLogger(CollectdReporter.class); private final Collectd collectd; private final Clock clock; private long period; private CollectdReporter(MetricRegistry registry, Collectd collectd, Clock clock, TimeUnit rateUnit, TimeUnit durationUnit, MetricFilter filter) { super(registry, "collectd-reporter", filter, rateUnit, durationUnit); this.collectd = collectd; this.clock = clock; } @Override public void start(long period, TimeUnit unit) { this.period = period; super.start(period, unit); } @SuppressWarnings("rawtypes") @Override public void report(SortedMap<MetricName, Gauge> gauges, SortedMap<MetricName, Counter> counters, SortedMap<MetricName, Histogram> histograms, SortedMap<MetricName, Meter> meters, SortedMap<MetricName, Timer> timers) { final long timestamp = clock.getTime() / 1000; // oh it'd be lovely to use Java 7 here try { if (!collectd.isConnected()) { collectd.connect(); } for (Map.Entry<MetricName, Gauge> entry : gauges.entrySet()) { reportGauge(entry.getKey(), entry.getValue(), timestamp); } for (Map.Entry<MetricName, Counter> entry : counters.entrySet()) { reportCounter(entry.getKey(), entry.getValue(), timestamp); } for (Map.Entry<MetricName, Histogram> entry : histograms.entrySet()) { reportHistogram(entry.getKey(), entry.getValue(), timestamp); } for (Map.Entry<MetricName, Meter> entry : meters.entrySet()) { reportMetered(entry.getKey(), entry.getValue(), timestamp); } for (Map.Entry<MetricName, Timer> entry : timers.entrySet()) { reportTimer(entry.getKey(), entry.getValue(), timestamp); } } catch (Throwable t) { LOGGER.warn("Unable to report to Collectd", collectd, t); } } private void reportTimer(MetricName name, Timer timer, long timestamp) { final Snapshot snapshot = timer.getSnapshot(); collectd.send(name, "max", convertDuration(snapshot.getMax()), timestamp, DataType.GAUGE, period); collectd.send(name, "mean", convertDuration(snapshot.getMean()), timestamp, DataType.GAUGE, period); collectd.send(name, "min", convertDuration(snapshot.getMin()), timestamp, DataType.GAUGE, period); collectd.send(name, "stddev", convertDuration(snapshot.getStdDev()), timestamp, DataType.GAUGE, period); collectd.send(name, "p50", convertDuration(snapshot.getMedian()), timestamp, DataType.GAUGE, period); collectd.send(name, "p75", convertDuration(snapshot.get75thPercentile()), timestamp, DataType.GAUGE, period); collectd.send(name, "p95", convertDuration(snapshot.get95thPercentile()), timestamp, DataType.GAUGE, period); collectd.send(name, "p98", convertDuration(snapshot.get98thPercentile()), timestamp, DataType.GAUGE, period); collectd.send(name, "p99", convertDuration(snapshot.get99thPercentile()), timestamp, DataType.GAUGE, period); collectd.send(name, "p999", convertDuration(snapshot.get999thPercentile()), timestamp, DataType.GAUGE, period); reportMetered(name, timer, timestamp); } private void reportMetered(MetricName name, Metered meter, long timestamp) { collectd.send(name, "count", meter.getCount(), timestamp, DataType.GAUGE, period); collectd.send(name, "m1_rate", convertRate(meter.getOneMinuteRate()), timestamp, DataType.GAUGE, period); collectd.send(name, "m5_rate", convertRate(meter.getFiveMinuteRate()), timestamp, DataType.GAUGE, period); collectd.send(name, "m15_rate", convertRate(meter.getFifteenMinuteRate()), timestamp, DataType.GAUGE, period); collectd.send(name, "mean_rate", convertRate(meter.getMeanRate()), timestamp, DataType.GAUGE, period); } private void reportHistogram(MetricName name, Histogram histogram, long timestamp) { final Snapshot snapshot = histogram.getSnapshot(); collectd.send(name, "count", histogram.getCount(), timestamp, DataType.GAUGE, period); collectd.send(name, "max", snapshot.getMax(), timestamp, DataType.GAUGE, period); collectd.send(name, "mean", snapshot.getMean(), timestamp, DataType.GAUGE, period); collectd.send(name, "min", snapshot.getMin(), timestamp, DataType.GAUGE, period); collectd.send(name, "stddev", snapshot.getStdDev(), timestamp, DataType.GAUGE, period); collectd.send(name, "p50", snapshot.getMedian(), timestamp, DataType.GAUGE, period); collectd.send(name, "p75", snapshot.get75thPercentile(), timestamp, DataType.GAUGE, period); collectd.send(name, "p95", snapshot.get95thPercentile(), timestamp, DataType.GAUGE, period); collectd.send(name, "p98", snapshot.get98thPercentile(), timestamp, DataType.GAUGE, period); collectd.send(name, "p99", snapshot.get99thPercentile(), timestamp, DataType.GAUGE, period); collectd.send(name, "p999", snapshot.get999thPercentile(), timestamp, DataType.GAUGE, period); } private void reportCounter(MetricName key, Counter value, long timestamp) { collectd.send(key, value.getCount(), timestamp, DataType.COUNTER, period); } private void reportGauge(MetricName key, Gauge<?> value, long timestamp) { collectd.send(key, (Number) value.getValue(), timestamp, DataType.GAUGE, period); } }