
com.wavefront.integrations.metrics.WavefrontReporter Maven / Gradle / Ivy
package com.wavefront.integrations.metrics;
import com.google.common.base.Preconditions;
import com.codahale.metrics.Clock;
import com.codahale.metrics.Counter;
import com.codahale.metrics.DeltaCounter;
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 com.codahale.metrics.jvm.BufferPoolMetricSet;
import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.SafeFileDescriptorRatioGauge;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import com.wavefront.common.MetricConstants;
import com.wavefront.integrations.Wavefront;
import com.wavefront.integrations.WavefrontDirectSender;
import com.wavefront.integrations.WavefrontSender;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.validation.constraints.NotNull;
/**
* A reporter which publishes metric values to a Wavefront Proxy from a Dropwizard {@link MetricRegistry}.
*/
public class WavefrontReporter extends ScheduledReporter {
private static final Logger LOGGER = LoggerFactory.getLogger(WavefrontReporter.class);
/**
* Returns a new {@link Builder} for {@link WavefrontReporter}.
*
* @param registry the registry to report
* @return a {@link Builder} instance for a {@link WavefrontReporter}
*/
public static Builder forRegistry(MetricRegistry registry) {
return new Builder(registry);
}
/**
* A builder for {@link WavefrontReporter} instances. Defaults to not using a prefix, using the
* default clock, converting rates to events/second, converting durations to milliseconds, a host
* named "unknown", no point Tags, and not filtering any 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 String source;
private Map pointTags;
private boolean includeJvmMetrics;
private Set disabledMetricAttributes;
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.source = "dropwizard-metrics";
this.pointTags = new HashMap<>();
this.includeJvmMetrics = false;
this.disabledMetricAttributes = Collections.emptySet();
}
/**
* Use the given {@link Clock} instance for the time. Defaults to Clock.defaultClock()
*
* @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. Defaults to null.
*
* @param prefix the prefix for all metric names
* @return {@code this}
*/
public Builder prefixedWith(String prefix) {
this.prefix = prefix;
return this;
}
/**
* Set the host for this reporter. This is equivalent to withSource.
*
* @param host the host for all metrics
* @return {@code this}
*/
public Builder withHost(String host) {
this.source = host;
return this;
}
/**
* Set the source for this reporter. This is equivalent to withHost.
*
* @param source the host for all metrics
* @return {@code this}
*/
public Builder withSource(String source) {
this.source = source;
return this;
}
/**
* Set the Point Tags for this reporter.
*
* @param pointTags the pointTags Map for all metrics
* @return {@code this}
*/
public Builder withPointTags(Map pointTags) {
this.pointTags.putAll(pointTags);
return this;
}
/**
* Set a point tag for this reporter.
*
* @param ptagK the key of the Point Tag
* @param ptagV the value of the Point Tag
* @return {@code this}
*/
public Builder withPointTag(String ptagK, String ptagV) {
this.pointTags.put(ptagK, ptagV);
return this;
}
/**
* Convert rates to the given time unit. Defaults to Seconds.
*
* @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. Defaults to Milliseconds.
*
* @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. Defaults to MetricFilter.ALL
*
* @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;
}
/**
* Include JVM Metrics from this Reporter.
*
* @return {@code this}
*/
public Builder withJvmMetrics() {
this.includeJvmMetrics = true;
return this;
}
/**
* Builds a {@link WavefrontReporter} from the VCAP_SERVICES env variable, sending metrics
* using the given {@link WavefrontSender}. This should be used in PCF environment only. It
* uses 'wavefront-proxy' as the name to fetch the proxy details from VCAP_SERVICES.
*
* @return a {@link WavefrontReporter}
*/
public WavefrontReporter bindToCloudFoundryService() {
return bindToCloudFoundryService("wavefront-proxy", false);
}
/**
* Builds a {@link WavefrontReporter} from the VCAP_SERVICES env variable, sending metrics
* using the given {@link WavefrontSender}. This should be used in PCF environment only. It
* assumes failOnError to be false.
*
* @return a {@link WavefrontReporter}
*/
public WavefrontReporter bindToCloudFoundryService(@NotNull String proxyServiceName) {
return bindToCloudFoundryService(proxyServiceName, false);
}
/**
* Builds a {@link WavefrontReporter} from the VCAP_SERVICES env variable, sending metrics
* using the given {@link WavefrontSender}. This should be used in PCF environment only.
*
* @param proxyServiceName The name of the wavefront proxy service. If wavefront-tile is used to
* deploy the proxy, then the service name will be 'wavefront-proxy'.
* @param failOnError A flag to determine what to do if the service parameters are not
* available. If 'true' then the method will throw RuntimeException else
* it will log an error message and continue.
* @return a {@link WavefrontReporter}
*/
public WavefrontReporter bindToCloudFoundryService(@NotNull String proxyServiceName,
boolean failOnError) {
Preconditions.checkNotNull(proxyServiceName, "proxyServiceName arg should not be null");
String proxyHostname;
int proxyPort;
// read the env variable VCAP_SERVICES
String services = System.getenv("VCAP_SERVICES");
if (services == null || services.length() == 0) {
if (failOnError) {
throw new RuntimeException("VCAP_SERVICES environment variable is unavailable.");
} else {
LOGGER.error("Environment variable VCAP_SERVICES is empty. No metrics will be reported " +
"to wavefront proxy.");
// since the wavefront-proxy is not tied to the app, use dummy hostname and port.
proxyHostname = "";
proxyPort = 2878;
}
} else {
// parse the json to read the hostname and port
JSONObject json = new JSONObject(services);
// When wavefront tile is installed on PCF, it will be automatically named wavefront-proxy
JSONArray jsonArray = json.getJSONArray(proxyServiceName);
if (jsonArray == null || jsonArray.isNull(0)) {
if (failOnError) {
throw new RuntimeException(proxyServiceName + " is not present in the VCAP_SERVICES " +
"env variable. Please verify and provide the wavefront proxy service name.");
} else {
LOGGER.error(proxyServiceName + " is not present in VCAP_SERVICES env variable. No " +
"metrics will be reported to wavefront proxy.");
// since the wavefront-proxy is not tied to the app, use dummy hostname and port.
proxyHostname = "";
proxyPort = 2878;
}
} else {
JSONObject details = jsonArray.getJSONObject(0);
JSONObject credentials = details.getJSONObject("credentials");
proxyHostname = credentials.getString("hostname");
proxyPort = credentials.getInt("port");
}
}
return new WavefrontReporter(registry,
proxyHostname,
proxyPort,
clock,
prefix,
source,
pointTags,
rateUnit,
durationUnit,
filter,
includeJvmMetrics,
disabledMetricAttributes);
}
/**
* Builds a {@link WavefrontReporter} with the given properties, sending metrics directly
* to a given Wavefront server using direct ingestion APIs.
*
* @param server Wavefront server hostname of the form "https://serverName.wavefront.com"
* @param token Wavefront API token with direct ingestion permission
* @return a {@link WavefrontReporter}
*/
public WavefrontReporter buildDirect(String server, String token) {
WavefrontSender wavefrontSender = new WavefrontDirectSender(server, token);
return new WavefrontReporter(registry, wavefrontSender, clock, prefix, source, pointTags, rateUnit,
durationUnit, filter, includeJvmMetrics, disabledMetricAttributes);
}
/**
* Builds a {@link WavefrontReporter} with the given properties, sending metrics using the given
* {@link WavefrontSender}.
*
* @param proxyHostname Wavefront Proxy hostname.
* @param proxyPort Wavefront Proxy port.
* @return a {@link WavefrontReporter}
*/
public WavefrontReporter build(String proxyHostname, int proxyPort) {
return new WavefrontReporter(registry,
proxyHostname,
proxyPort,
clock,
prefix,
source,
pointTags,
rateUnit,
durationUnit,
filter,
includeJvmMetrics,
disabledMetricAttributes);
}
/**
* Builds a {@link WavefrontReporter} with the given properties, sending metrics using the given
* {@link WavefrontSender}.
*
* @param Wavefront a {@link WavefrontSender}.
* @return a {@link WavefrontReporter}
*/
public WavefrontReporter build(WavefrontSender wavefrontSender) {
return new WavefrontReporter(registry,
wavefrontSender,
clock,
prefix,
source,
pointTags,
rateUnit,
durationUnit,
filter,
includeJvmMetrics,
disabledMetricAttributes);
}
}
private final WavefrontSender wavefront;
private final Clock clock;
private final String prefix;
private final String source;
private final Map pointTags;
private WavefrontReporter(MetricRegistry registry,
WavefrontSender wavefrontSender,
final Clock clock,
String prefix,
String source,
Map pointTags,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter,
boolean includeJvmMetrics,
Set disabledMetricAttributes) {
super(registry, "wavefront-reporter", filter, rateUnit, durationUnit, Executors.newSingleThreadScheduledExecutor(),
true, disabledMetricAttributes == null ? Collections.emptySet() : disabledMetricAttributes);
this.wavefront = wavefrontSender;
this.clock = clock;
this.prefix = prefix;
this.source = source;
this.pointTags = pointTags;
if (includeJvmMetrics) {
registry.register("jvm.uptime", (Gauge) () -> ManagementFactory.getRuntimeMXBean().getUptime());
registry.register("jvm.current_time", (Gauge) clock::getTime);
registry.register("jvm.classes", new ClassLoadingGaugeSet());
registry.register("jvm.fd_usage", new SafeFileDescriptorRatioGauge());
registry.register("jvm.buffers", new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
registry.register("jvm.gc", new GarbageCollectorMetricSet());
registry.register("jvm.memory", new MemoryUsageGaugeSet());
registry.register("jvm.thread-states", new ThreadStatesGaugeSet());
}
}
private WavefrontReporter(MetricRegistry registry,
String proxyHostname,
int proxyPort,
final Clock clock,
String prefix,
String source,
Map pointTags,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter,
boolean includeJvmMetrics,
Set disabledMetricAttributes) {
this(registry, new Wavefront(proxyHostname, proxyPort), clock, prefix, source, pointTags, rateUnit,
durationUnit, filter, includeJvmMetrics, disabledMetricAttributes);
}
@Override
public void report(SortedMap gauges,
SortedMap counters,
SortedMap histograms,
SortedMap meters,
SortedMap timers) {
try {
if (!wavefront.isConnected()) {
wavefront.connect();
}
for (Map.Entry entry : gauges.entrySet()) {
if (entry.getValue().getValue() instanceof Number) {
reportGauge(entry.getKey(), entry.getValue());
}
}
for (Map.Entry entry : counters.entrySet()) {
reportCounter(entry.getKey(), entry.getValue());
}
for (Map.Entry entry : histograms.entrySet()) {
reportHistogram(entry.getKey(), entry.getValue());
}
for (Map.Entry entry : meters.entrySet()) {
reportMetered(entry.getKey(), entry.getValue());
}
for (Map.Entry entry : timers.entrySet()) {
reportTimer(entry.getKey(), entry.getValue());
}
wavefront.flush();
} catch (IOException e) {
LOGGER.warn("Unable to report to Wavefront", wavefront, e);
try {
wavefront.close();
} catch (IOException e1) {
LOGGER.warn("Error closing Wavefront", wavefront, e);
}
}
}
@Override
public void stop() {
try {
super.stop();
} finally {
try {
wavefront.close();
} catch (IOException e) {
LOGGER.debug("Error disconnecting from Wavefront", wavefront, e);
}
}
}
private void reportTimer(String name, Timer timer) throws IOException {
final Snapshot snapshot = timer.getSnapshot();
final long time = clock.getTime() / 1000;
sendIfEnabled(MetricAttribute.MAX, name, convertDuration(snapshot.getMax()), time);
sendIfEnabled(MetricAttribute.MEAN, name, convertDuration(snapshot.getMean()), time);
sendIfEnabled(MetricAttribute.MIN, name, convertDuration(snapshot.getMin()), time);
sendIfEnabled(MetricAttribute.STDDEV, name, convertDuration(snapshot.getStdDev()), time);
sendIfEnabled(MetricAttribute.P50, name, convertDuration(snapshot.getMedian()), time);
sendIfEnabled(MetricAttribute.P75, name, convertDuration(snapshot.get75thPercentile()), time);
sendIfEnabled(MetricAttribute.P95, name, convertDuration(snapshot.get95thPercentile()), time);
sendIfEnabled(MetricAttribute.P98, name, convertDuration(snapshot.get98thPercentile()), time);
sendIfEnabled(MetricAttribute.P99, name, convertDuration(snapshot.get99thPercentile()), time);
sendIfEnabled(MetricAttribute.P999, name, convertDuration(snapshot.get999thPercentile()), time);
reportMetered(name, timer);
}
private void reportMetered(String name, Metered meter) throws IOException {
final long time = clock.getTime() / 1000;
sendIfEnabled(MetricAttribute.COUNT, name, meter.getCount(), time);
sendIfEnabled(MetricAttribute.M1_RATE, name, convertRate(meter.getOneMinuteRate()), time);
sendIfEnabled(MetricAttribute.M5_RATE, name, convertRate(meter.getFiveMinuteRate()), time);
sendIfEnabled(MetricAttribute.M15_RATE, name, convertRate(meter.getFifteenMinuteRate()), time);
sendIfEnabled(MetricAttribute.MEAN_RATE, name, convertRate(meter.getMeanRate()), time);
}
private void reportHistogram(String name, Histogram histogram) throws IOException {
final Snapshot snapshot = histogram.getSnapshot();
final long time = clock.getTime() / 1000;
sendIfEnabled(MetricAttribute.COUNT, name, histogram.getCount(), time);
sendIfEnabled(MetricAttribute.MAX, name, snapshot.getMax(), time);
sendIfEnabled(MetricAttribute.MEAN, name, snapshot.getMean(), time);
sendIfEnabled(MetricAttribute.MIN, name, snapshot.getMin(), time);
sendIfEnabled(MetricAttribute.STDDEV, name, snapshot.getStdDev(), time);
sendIfEnabled(MetricAttribute.P50, name, snapshot.getMedian(), time);
sendIfEnabled(MetricAttribute.P75, name, snapshot.get75thPercentile(), time);
sendIfEnabled(MetricAttribute.P95, name, snapshot.get95thPercentile(), time);
sendIfEnabled(MetricAttribute.P98, name, snapshot.get98thPercentile(), time);
sendIfEnabled(MetricAttribute.P99, name, snapshot.get99thPercentile(), time);
sendIfEnabled(MetricAttribute.P999, name, snapshot.get999thPercentile(), time);
}
private void reportCounter(String name, Counter counter) throws IOException {
if (counter instanceof DeltaCounter) {
long count = counter.getCount();
name = MetricConstants.DELTA_PREFIX + prefixAndSanitize(name.substring(1), "count");
wavefront.send(name, count,clock.getTime() / 1000, source, pointTags);
counter.dec(count);
} else {
wavefront.send(prefixAndSanitize(name, "count"), counter.getCount(), clock.getTime() / 1000, source, pointTags);
}
}
private void reportGauge(String name, Gauge gauge) throws IOException {
wavefront.send(prefixAndSanitize(name), gauge.getValue().doubleValue(), clock.getTime() / 1000, source, pointTags);
}
private void sendIfEnabled(MetricAttribute type, String name, double value, long timestamp) throws IOException {
if (!getDisabledMetricAttributes().contains(type)) {
wavefront.send(prefixAndSanitize(name, type.getCode()), value, timestamp, source, pointTags);
}
}
private String prefixAndSanitize(String... components) {
return sanitize(MetricRegistry.name(prefix, components));
}
private static String sanitize(String name) {
return SIMPLE_NAMES.matcher(name).replaceAll("_");
}
private static final Pattern SIMPLE_NAMES = Pattern.compile("[^a-zA-Z0-9_.\\-~]");
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy