package io.dropwizard.metrics.influxdb;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.dropwizard.metrics.Counter;
import io.dropwizard.metrics.Counting;
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 io.dropwizard.metrics.influxdb.data.InfluxDbPoint;
public final class InfluxDbReporter extends ScheduledReporter {
public static final class Builder {
private final MetricRegistry registry;
private Map<String, String> tags;
private TimeUnit rateUnit;
private TimeUnit durationUnit;
private MetricFilter filter;
private boolean skipIdleMetrics;
private Builder(MetricRegistry registry) {
this.registry = registry;
this.tags = null;
this.rateUnit = TimeUnit.SECONDS;
this.durationUnit = TimeUnit.MILLISECONDS;
this.filter = MetricFilter.ALL;
}
/**
* Add these tags to all metrics.
*
* @param tags a map containing tags common to all metrics
* @return {@code this}
*/
public Builder withTags(Map<String, String> tags) {
this.tags = Collections.unmodifiableMap(tags);
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;
}
/**
* Only report metrics that have changed.
*
* @param skipIdleMetrics true/false for skipping metrics not reported
* @return {@code this}
*/
public Builder skipIdleMetrics(boolean skipIdleMetrics) {
this.skipIdleMetrics = skipIdleMetrics;
return this;
}
public InfluxDbReporter build(final InfluxDbSender influxDb) {
return new InfluxDbReporter(registry, influxDb, tags, rateUnit, durationUnit, filter, skipIdleMetrics);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(InfluxDbReporter.class);
private final InfluxDbSender influxDb;
private final boolean skipIdleMetrics;
private final Map<MetricName, Long> previousValues;
private InfluxDbReporter(final MetricRegistry registry, final InfluxDbSender influxDb, final Map<String, String> tags,
final TimeUnit rateUnit, final TimeUnit durationUnit, final MetricFilter filter, final boolean skipIdleMetrics) {
super(registry, "influxDb-reporter", filter, rateUnit, durationUnit);
this.influxDb = influxDb;
influxDb.setTags(tags);
this.skipIdleMetrics = skipIdleMetrics;
this.previousValues = new TreeMap<>();
}
public static Builder forRegistry(MetricRegistry registry) {
return new Builder(registry);
}
@Override
public void report(final SortedMap<MetricName, Gauge> gauges, final SortedMap<MetricName, Counter> counters,
final SortedMap<MetricName, Histogram> histograms, final SortedMap<MetricName, Meter> meters, final SortedMap<MetricName, Timer> timers) {
final long now = System.currentTimeMillis();
try {
influxDb.flush();
for (Map.Entry<MetricName, Gauge> entry : gauges.entrySet()) {
reportGauge(entry.getKey(), entry.getValue(), now);
}
for (Map.Entry<MetricName, Counter> entry : counters.entrySet()) {
reportCounter(entry.getKey(), entry.getValue(), now);
}
for (Map.Entry<MetricName, Histogram> entry : histograms.entrySet()) {
reportHistogram(entry.getKey(), entry.getValue(), now);
}
for (Map.Entry<MetricName, Meter> entry : meters.entrySet()) {
reportMeter(entry.getKey(), entry.getValue(), now);
}
for (Map.Entry<MetricName, Timer> entry : timers.entrySet()) {
reportTimer(entry.getKey(), entry.getValue(), now);
}
if (influxDb.hasSeriesData()) {
influxDb.writeData();
}
} catch (Exception e) {
LOGGER.warn("Unable to report to InfluxDB. Discarding data.", e);
}
}
private void reportTimer(MetricName name, Timer timer, long now) {
if (canSkipMetric(name, timer)) {
return;
}
final Snapshot snapshot = timer.getSnapshot();
Map<String, Object> fields = new HashMap<>();
fields.put("count", timer.getCount());
fields.put("min", convertDuration(snapshot.getMin()));
fields.put("max", convertDuration(snapshot.getMax()));
fields.put("mean", convertDuration(snapshot.getMean()));
fields.put("std-dev", convertDuration(snapshot.getStdDev()));
fields.put("median", convertDuration(snapshot.getMedian()));
fields.put("50-percentile", convertDuration(snapshot.getMedian()));
fields.put("75-percentile", convertDuration(snapshot.get75thPercentile()));
fields.put("95-percentile", convertDuration(snapshot.get95thPercentile()));
fields.put("98-percentile", convertDuration(snapshot.get98thPercentile()));
fields.put("99-percentile", convertDuration(snapshot.get99thPercentile()));
fields.put("999-percentile", convertDuration(snapshot.get999thPercentile()));
fields.put("one-minute", convertRate(timer.getOneMinuteRate()));
fields.put("five-minute", convertRate(timer.getFiveMinuteRate()));
fields.put("fifteen-minute", convertRate(timer.getFifteenMinuteRate()));
fields.put("mean-rate", convertRate(timer.getMeanRate()));
fields.put("run-count", timer.getCount());
influxDb.appendPoints(new InfluxDbPoint(
name.getKey(),
name.getTags(),
String.valueOf(now),
fields));
}
private void reportHistogram(MetricName name, Histogram histogram, long now) {
if (canSkipMetric(name, histogram)) {
return;
}
final Snapshot snapshot = histogram.getSnapshot();
Map<String, Object> fields = new HashMap<>();
fields.put("count", histogram.getCount());
fields.put("min", snapshot.getMin());
fields.put("max", snapshot.getMax());
fields.put("mean", snapshot.getMean());
fields.put("median", snapshot.getMedian());
fields.put("std-dev", snapshot.getStdDev());
fields.put("50-percentile", snapshot.getMedian());
fields.put("75-percentile", snapshot.get75thPercentile());
fields.put("95-percentile", snapshot.get95thPercentile());
fields.put("98-percentile", snapshot.get98thPercentile());
fields.put("99-percentile", snapshot.get99thPercentile());
fields.put("999-percentile", snapshot.get999thPercentile());
fields.put("run-count", histogram.getCount());
influxDb.appendPoints(new InfluxDbPoint(
name.getKey(),
name.getTags(),
String.valueOf(now),
fields));
}
private void reportCounter(MetricName name, Counter counter, long now) {
Map<String, Object> fields = new HashMap<>();
fields.put("count", counter.getCount());
influxDb.appendPoints(new InfluxDbPoint(
name.getKey(),
name.getTags(),
String.valueOf(now),
fields));
}
private void reportGauge(MetricName name, Gauge<?> gauge, long now) {
Map<String, Object> fields = new HashMap<>();
fields.put("value", gauge.getValue());
influxDb.appendPoints(new InfluxDbPoint(
name.getKey(),
name.getTags(),
String.valueOf(now),
fields));
}
private void reportMeter(MetricName name, Metered meter, long now) {
if (canSkipMetric(name, meter)) {
return;
}
Map<String, Object> fields = new HashMap<>();
fields.put("count", meter.getCount());
fields.put("one-minute", convertRate(meter.getOneMinuteRate()));
fields.put("five-minute", convertRate(meter.getFiveMinuteRate()));
fields.put("fifteen-minute", convertRate(meter.getFifteenMinuteRate()));
fields.put("mean-rate", convertRate(meter.getMeanRate()));
influxDb.appendPoints(new InfluxDbPoint(
name.getKey(),
name.getTags(),
String.valueOf(now),
fields));
}
private boolean canSkipMetric(MetricName name, Counting counting) {
boolean isIdle = (calculateDelta(name, counting.getCount()) == 0);
if (skipIdleMetrics && !isIdle) {
previousValues.put(name, counting.getCount());
}
return skipIdleMetrics && isIdle;
}
private long calculateDelta(MetricName name, long count) {
Long previous = previousValues.get(name);
if (previous == null) {
return -1;
}
if (count < previous) {
LOGGER.warn("Saw a non-monotonically increasing value for metric '{}'", name);
return 0;
}
return count - previous;
}
}