com.codahale.metrics.graphite.GraphiteReporter Maven / Gradle / Ivy
Show all versions of metrics-graphite Show documentation
package com.codahale.metrics.graphite;
import com.codahale.metrics.Clock;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metered;
import com.codahale.metrics.MetricAttribute;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.ScheduledReporter;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.DoubleFunction;
import static com.codahale.metrics.MetricAttribute.COUNT;
import static com.codahale.metrics.MetricAttribute.M15_RATE;
import static com.codahale.metrics.MetricAttribute.M1_RATE;
import static com.codahale.metrics.MetricAttribute.M5_RATE;
import static com.codahale.metrics.MetricAttribute.MAX;
import static com.codahale.metrics.MetricAttribute.MEAN;
import static com.codahale.metrics.MetricAttribute.MEAN_RATE;
import static com.codahale.metrics.MetricAttribute.MIN;
import static com.codahale.metrics.MetricAttribute.P50;
import static com.codahale.metrics.MetricAttribute.P75;
import static com.codahale.metrics.MetricAttribute.P95;
import static com.codahale.metrics.MetricAttribute.P98;
import static com.codahale.metrics.MetricAttribute.P99;
import static com.codahale.metrics.MetricAttribute.P999;
import static com.codahale.metrics.MetricAttribute.STDDEV;
/**
* A reporter which publishes metric values to a Graphite server.
*
* @see Graphite - Scalable Realtime Graphing
*/
public class GraphiteReporter extends ScheduledReporter {
/**
* Returns a new {@link Builder} for {@link GraphiteReporter}.
*
* @param registry the registry to report
* @return a {@link Builder} instance for a {@link GraphiteReporter}
*/
public static Builder forRegistry(MetricRegistry registry) {
return new Builder(registry);
}
/**
* A builder for {@link GraphiteReporter} 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 String prefix;
private TimeUnit rateUnit;
private TimeUnit durationUnit;
private MetricFilter filter;
private ScheduledExecutorService executor;
private boolean shutdownExecutorOnStop;
private Set disabledMetricAttributes;
private boolean addMetricAttributesAsTags;
private DoubleFunction floatingPointFormatter;
private Builder(MetricRegistry registry) {
this.registry = registry;
this.clock = Clock.defaultClock();
this.prefix = null;
this.rateUnit = TimeUnit.SECONDS;
this.durationUnit = TimeUnit.MILLISECONDS;
this.filter = MetricFilter.ALL;
this.executor = null;
this.shutdownExecutorOnStop = true;
this.disabledMetricAttributes = Collections.emptySet();
this.addMetricAttributesAsTags = false;
this.floatingPointFormatter = DEFAULT_FP_FORMATTER;
}
/**
* Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
* Default value is true.
* Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
*
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @return {@code this}
*/
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
this.shutdownExecutorOnStop = shutdownExecutorOnStop;
return this;
}
/**
* Specifies the executor to use while scheduling reporting of metrics.
* Default value is null.
* Null value leads to executor will be auto created on start.
*
* @param executor the executor to use while scheduling reporting of metrics.
* @return {@code this}
*/
public Builder scheduleOn(ScheduledExecutorService executor) {
this.executor = executor;
return this;
}
/**
* 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;
}
/**
* Prefix all metric names with the given string.
*
* @param prefix the prefix for all metric names
* @return {@code this}
*/
public Builder prefixedWith(String prefix) {
this.prefix = prefix;
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;
}
/**
* Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
* See {@link MetricAttribute}.
*
* @param disabledMetricAttributes a set of {@link MetricAttribute}
* @return {@code this}
*/
public Builder disabledMetricAttributes(Set disabledMetricAttributes) {
this.disabledMetricAttributes = disabledMetricAttributes;
return this;
}
/**
* Specifies whether or not metric attributes (e.g. "p999", "stddev" or "m15") should be reported in the traditional dot delimited format or in the tag based format.
* Without tags (default): `my.metric.p99`
* With tags: `my.metric;metricattribute=p99`
*
* Note that this setting only modifies the metric attribute, and will not convert any other portion of the metric name to use tags.
* For mor information on Graphite tag support see https://graphite.readthedocs.io/en/latest/tags.html
* See {@link MetricAttribute}.
*
* @param addMetricAttributesAsTags if true, then metric attributes will be added as tags
* @return {@code this}
*/
public Builder addMetricAttributesAsTags(boolean addMetricAttributesAsTags) {
this.addMetricAttributesAsTags = addMetricAttributesAsTags;
return this;
}
/**
* Use custom floating point formatter.
*
* @param floatingPointFormatter a custom formatter for floating point values
* @return {@code this}
*/
public Builder withFloatingPointFormatter(DoubleFunction floatingPointFormatter) {
this.floatingPointFormatter = floatingPointFormatter;
return this;
}
/**
* Builds a {@link GraphiteReporter} with the given properties, sending metrics using the
* given {@link GraphiteSender}.
*
* Present for binary compatibility
*
* @param graphite a {@link Graphite}
* @return a {@link GraphiteReporter}
*/
public GraphiteReporter build(Graphite graphite) {
return build((GraphiteSender) graphite);
}
/**
* Builds a {@link GraphiteReporter} with the given properties, sending metrics using the
* given {@link GraphiteSender}.
*
* @param graphite a {@link GraphiteSender}
* @return a {@link GraphiteReporter}
*/
public GraphiteReporter build(GraphiteSender graphite) {
return new GraphiteReporter(registry,
graphite,
clock,
prefix,
rateUnit,
durationUnit,
filter,
executor,
shutdownExecutorOnStop,
disabledMetricAttributes,
addMetricAttributesAsTags,
floatingPointFormatter);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(GraphiteReporter.class);
// the Carbon plaintext format is pretty underspecified, but it seems like it just wants US-formatted digits
private static final DoubleFunction DEFAULT_FP_FORMATTER = fp -> String.format(Locale.US, "%2.2f", fp);
private final GraphiteSender graphite;
private final Clock clock;
private final String prefix;
private final boolean addMetricAttributesAsTags;
private final DoubleFunction floatingPointFormatter;
/**
* Creates a new {@link GraphiteReporter} instance.
*
* @param registry the {@link MetricRegistry} containing the metrics this
* reporter will report
* @param graphite the {@link GraphiteSender} which is responsible for sending metrics to a Carbon server
* via a transport protocol
* @param clock the instance of the time. Use {@link Clock#defaultClock()} for the default
* @param prefix the prefix of all metric names (may be null)
* @param rateUnit the time unit of in which rates will be converted
* @param durationUnit the time unit of in which durations will be converted
* @param filter the filter for which metrics to report
* @param executor the executor to use while scheduling reporting of metrics (may be null).
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @param disabledMetricAttributes do not report specific metric attributes
*/
protected GraphiteReporter(MetricRegistry registry,
GraphiteSender graphite,
Clock clock,
String prefix,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop,
Set disabledMetricAttributes) {
this(registry, graphite, clock, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop,
disabledMetricAttributes, false);
}
/**
* Creates a new {@link GraphiteReporter} instance.
*
* @param registry the {@link MetricRegistry} containing the metrics this
* reporter will report
* @param graphite the {@link GraphiteSender} which is responsible for sending metrics to a Carbon server
* via a transport protocol
* @param clock the instance of the time. Use {@link Clock#defaultClock()} for the default
* @param prefix the prefix of all metric names (may be null)
* @param rateUnit the time unit of in which rates will be converted
* @param durationUnit the time unit of in which durations will be converted
* @param filter the filter for which metrics to report
* @param executor the executor to use while scheduling reporting of metrics (may be null).
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @param disabledMetricAttributes do not report specific metric attributes
* @param addMetricAttributesAsTags if true, then add metric attributes as tags instead of suffixes
*/
protected GraphiteReporter(MetricRegistry registry,
GraphiteSender graphite,
Clock clock,
String prefix,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop,
Set disabledMetricAttributes,
boolean addMetricAttributesAsTags) {
this(registry, graphite, clock, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop,
disabledMetricAttributes, addMetricAttributesAsTags, DEFAULT_FP_FORMATTER);
}
/**
* Creates a new {@link GraphiteReporter} instance.
*
* @param registry the {@link MetricRegistry} containing the metrics this
* reporter will report
* @param graphite the {@link GraphiteSender} which is responsible for sending metrics to a Carbon server
* via a transport protocol
* @param clock the instance of the time. Use {@link Clock#defaultClock()} for the default
* @param prefix the prefix of all metric names (may be null)
* @param rateUnit the time unit of in which rates will be converted
* @param durationUnit the time unit of in which durations will be converted
* @param filter the filter for which metrics to report
* @param executor the executor to use while scheduling reporting of metrics (may be null).
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @param disabledMetricAttributes do not report specific metric attributes
* @param addMetricAttributesAsTags if true, then add metric attributes as tags instead of suffixes
* @param floatingPointFormatter custom floating point formatter
*/
protected GraphiteReporter(MetricRegistry registry,
GraphiteSender graphite,
Clock clock,
String prefix,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop,
Set disabledMetricAttributes,
boolean addMetricAttributesAsTags,
DoubleFunction floatingPointFormatter) {
super(registry, "graphite-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
disabledMetricAttributes);
this.graphite = graphite;
this.clock = clock;
this.prefix = prefix;
this.addMetricAttributesAsTags = addMetricAttributesAsTags;
this.floatingPointFormatter = floatingPointFormatter;
}
@Override
@SuppressWarnings("rawtypes")
public void report(SortedMap gauges,
SortedMap counters,
SortedMap histograms,
SortedMap meters,
SortedMap timers) {
final long timestamp = clock.getTime() / 1000;
// oh it'd be lovely to use Java 7 here
try {
graphite.connect();
for (Map.Entry entry : gauges.entrySet()) {
reportGauge(entry.getKey(), entry.getValue(), timestamp);
}
for (Map.Entry entry : counters.entrySet()) {
reportCounter(entry.getKey(), entry.getValue(), timestamp);
}
for (Map.Entry entry : histograms.entrySet()) {
reportHistogram(entry.getKey(), entry.getValue(), timestamp);
}
for (Map.Entry entry : meters.entrySet()) {
reportMetered(entry.getKey(), entry.getValue(), timestamp);
}
for (Map.Entry entry : timers.entrySet()) {
reportTimer(entry.getKey(), entry.getValue(), timestamp);
}
graphite.flush();
} catch (IOException e) {
LOGGER.warn("Unable to report to Graphite", graphite, e);
} finally {
try {
graphite.close();
} catch (IOException e1) {
LOGGER.warn("Error closing Graphite", graphite, e1);
}
}
}
@Override
public void stop() {
try {
super.stop();
} finally {
try {
graphite.close();
} catch (IOException e) {
LOGGER.debug("Error disconnecting from Graphite", graphite, e);
}
}
}
private void reportTimer(String name, Timer timer, long timestamp) throws IOException {
final Snapshot snapshot = timer.getSnapshot();
sendIfEnabled(MAX, name, convertDuration(snapshot.getMax()), timestamp);
sendIfEnabled(MEAN, name, convertDuration(snapshot.getMean()), timestamp);
sendIfEnabled(MIN, name, convertDuration(snapshot.getMin()), timestamp);
sendIfEnabled(STDDEV, name, convertDuration(snapshot.getStdDev()), timestamp);
sendIfEnabled(P50, name, convertDuration(snapshot.getMedian()), timestamp);
sendIfEnabled(P75, name, convertDuration(snapshot.get75thPercentile()), timestamp);
sendIfEnabled(P95, name, convertDuration(snapshot.get95thPercentile()), timestamp);
sendIfEnabled(P98, name, convertDuration(snapshot.get98thPercentile()), timestamp);
sendIfEnabled(P99, name, convertDuration(snapshot.get99thPercentile()), timestamp);
sendIfEnabled(P999, name, convertDuration(snapshot.get999thPercentile()), timestamp);
reportMetered(name, timer, timestamp);
}
private void reportMetered(String name, Metered meter, long timestamp) throws IOException {
sendIfEnabled(COUNT, name, meter.getCount(), timestamp);
sendIfEnabled(M1_RATE, name, convertRate(meter.getOneMinuteRate()), timestamp);
sendIfEnabled(M5_RATE, name, convertRate(meter.getFiveMinuteRate()), timestamp);
sendIfEnabled(M15_RATE, name, convertRate(meter.getFifteenMinuteRate()), timestamp);
sendIfEnabled(MEAN_RATE, name, convertRate(meter.getMeanRate()), timestamp);
}
private void reportHistogram(String name, Histogram histogram, long timestamp) throws IOException {
final Snapshot snapshot = histogram.getSnapshot();
sendIfEnabled(COUNT, name, histogram.getCount(), timestamp);
sendIfEnabled(MAX, name, snapshot.getMax(), timestamp);
sendIfEnabled(MEAN, name, snapshot.getMean(), timestamp);
sendIfEnabled(MIN, name, snapshot.getMin(), timestamp);
sendIfEnabled(STDDEV, name, snapshot.getStdDev(), timestamp);
sendIfEnabled(P50, name, snapshot.getMedian(), timestamp);
sendIfEnabled(P75, name, snapshot.get75thPercentile(), timestamp);
sendIfEnabled(P95, name, snapshot.get95thPercentile(), timestamp);
sendIfEnabled(P98, name, snapshot.get98thPercentile(), timestamp);
sendIfEnabled(P99, name, snapshot.get99thPercentile(), timestamp);
sendIfEnabled(P999, name, snapshot.get999thPercentile(), timestamp);
}
private void sendIfEnabled(MetricAttribute type, String name, double value, long timestamp) throws IOException {
if (getDisabledMetricAttributes().contains(type)) {
return;
}
graphite.send(prefix(appendMetricAttribute(name, type.getCode())), format(value), timestamp);
}
private void sendIfEnabled(MetricAttribute type, String name, long value, long timestamp) throws IOException {
if (getDisabledMetricAttributes().contains(type)) {
return;
}
graphite.send(prefix(appendMetricAttribute(name, type.getCode())), format(value), timestamp);
}
private void reportCounter(String name, Counter counter, long timestamp) throws IOException {
graphite.send(prefix(appendMetricAttribute(name, COUNT.getCode())), format(counter.getCount()), timestamp);
}
private void reportGauge(String name, Gauge> gauge, long timestamp) throws IOException {
final String value = format(gauge.getValue());
if (value != null) {
graphite.send(prefix(name), value, timestamp);
}
}
private String format(Object o) {
if (o instanceof Float) {
return format(((Float) o).doubleValue());
} else if (o instanceof Double) {
return format(((Double) o).doubleValue());
} else if (o instanceof Byte) {
return format(((Byte) o).longValue());
} else if (o instanceof Short) {
return format(((Short) o).longValue());
} else if (o instanceof Integer) {
return format(((Integer) o).longValue());
} else if (o instanceof Long) {
return format(((Long) o).longValue());
} else if (o instanceof Number) {
return format(((Number) o).doubleValue());
} else if (o instanceof Boolean) {
return format(((Boolean) o) ? 1 : 0);
}
return null;
}
private String prefix(String name) {
return MetricRegistry.name(prefix, name);
}
private String appendMetricAttribute(String name, String metricAttribute){
if (addMetricAttributesAsTags){
return name + ";metricattribute=" + metricAttribute;
}
return name + "." + metricAttribute;
}
private String format(long n) {
return Long.toString(n);
}
protected String format(double v) {
return floatingPointFormatter.apply(v);
}
}