/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gobblin.metrics.graphite;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Counting;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metered;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import com.google.common.base.Optional;
import com.typesafe.config.Config;
import gobblin.configuration.ConfigurationKeys;
import gobblin.metrics.Measurements;
import gobblin.metrics.reporter.ConfiguredScheduledReporter;
import gobblin.util.ConfigUtils;
import static gobblin.metrics.Measurements.*;
/**
* Graphite reporter for metrics
*
* @author Lorand Bendig
*
*/
public class GraphiteReporter extends ConfiguredScheduledReporter {
private final GraphitePusher graphitePusher;
private static final Logger LOGGER = LoggerFactory.getLogger(GraphiteReporter.class);
public GraphiteReporter(Builder<?> builder, Config config) throws IOException {
super(builder, config);
if (builder.graphitePusher.isPresent()) {
this.graphitePusher = builder.graphitePusher.get();
} else {
this.graphitePusher = this.closer.register(new GraphitePusher(builder.hostname, builder.port, builder.connectionType));
}
}
/**
* A static factory class for obtaining new {@link gobblin.metrics.graphite.GraphiteReporter.Builder}s
*
* @see gobblin.metrics.graphite.GraphiteReporter.Builder
*/
public static class Factory {
public static BuilderImpl newBuilder() {
return new BuilderImpl();
}
}
public static class BuilderImpl extends Builder<BuilderImpl> {
@Override
protected BuilderImpl self() {
return this;
}
}
/**
* Builder for {@link GraphiteReporter}. Defaults to no filter, reporting rates in seconds and times in
* milliseconds using TCP sending type
*/
public static abstract class Builder<T extends ConfiguredScheduledReporter.Builder<T>> extends
ConfiguredScheduledReporter.Builder<T> {
protected MetricFilter filter;
protected String hostname;
protected int port;
protected GraphiteConnectionType connectionType;
protected Optional<GraphitePusher> graphitePusher;
protected Builder() {
super();
this.name = "GraphiteReporter";
this.graphitePusher = Optional.absent();
this.filter = MetricFilter.ALL;
this.connectionType = GraphiteConnectionType.TCP;
}
/**
* Set {@link gobblin.metrics.graphite.GraphitePusher} to use.
*/
public T withGraphitePusher(GraphitePusher pusher) {
this.graphitePusher = Optional.of(pusher);
return self();
}
/**
* Set connection parameters for the {@link gobblin.metrics.graphite.GraphitePusher} creation
*/
public T withConnection(String hostname, int port) {
this.hostname = hostname;
this.port = port;
return self();
}
/**
* Set {@link gobblin.metrics.graphite.GraphiteConnectionType} to use.
*/
public T withConnectionType(GraphiteConnectionType connectionType) {
this.connectionType = connectionType;
return self();
}
/**
* Only report metrics which match the given filter.
*
* @param filter a {@link MetricFilter}
* @return {@code this}
*/
public T filter(MetricFilter filter) {
this.filter = filter;
return self();
}
/**
* Builds and returns {@link GraphiteReporter}.
*
* @param props metrics properties
* @return GraphiteReporter
*/
public GraphiteReporter build(Properties props) throws IOException {
return new GraphiteReporter(this, ConfigUtils.propertiesToConfig(props,
Optional.of(ConfigurationKeys.METRICS_CONFIGURATIONS_PREFIX)));
}
}
@Override
protected void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers,
Map<String, Object> tags) {
String prefix = getMetricNamePrefix(tags);
long timestamp = System.currentTimeMillis() / 1000l;
try {
for (Map.Entry<String, Gauge> gauge : gauges.entrySet()) {
reportGauge(prefix, gauge.getKey(), gauge.getValue(), timestamp);
}
for (Map.Entry<String, Counter> counter : counters.entrySet()) {
reportCounter(prefix, counter.getKey(), counter.getValue(), timestamp);
}
for (Map.Entry<String, Histogram> histogram : histograms.entrySet()) {
reportHistogram(prefix, histogram.getKey(), histogram.getValue(), timestamp);
}
for (Map.Entry<String, Meter> meter : meters.entrySet()) {
reportMetered(prefix, meter.getKey(), meter.getValue(), timestamp);
}
for (Map.Entry<String, Timer> timer : timers.entrySet()) {
reportTimer(prefix, timer.getKey(), timer.getValue(), timestamp);
}
this.graphitePusher.flush();
} catch (IOException ioe) {
LOGGER.error("Error sending metrics to Graphite", ioe);
try {
this.graphitePusher.close();
} catch (IOException innerIoe) {
LOGGER.error("Error closing the Graphite sender", innerIoe);
}
}
}
private void reportGauge(String prefix, String name, Gauge gauge, long timestamp) throws IOException {
String metricName = getKey(prefix, name);
pushMetric(metricName, gauge.getValue().toString(), timestamp);
}
private void reportCounter(String prefix, String name, Counting counter, long timestamp) throws IOException {
String metricName = getKey(prefix, name, COUNT.getName());
pushMetric(metricName, counter.getCount(), false, timestamp);
}
private void reportHistogram(String prefix, String name, Histogram histogram, long timestamp) throws IOException {
reportCounter(prefix, name, histogram, timestamp);
reportSnapshot(prefix, name, histogram.getSnapshot(), timestamp, false);
}
private void reportTimer(String prefix, String name, Timer timer, long timestamp) throws IOException {
reportSnapshot(prefix, name, timer.getSnapshot(), timestamp, true);
reportMetered(prefix, name, timer, timestamp);
}
private void reportSnapshot(String prefix, String name, Snapshot snapshot, long timestamp, boolean convertDuration)
throws IOException {
String baseMetricName = getKey(prefix, name);
pushMetric(getKey(baseMetricName, MIN), snapshot.getMin(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, MAX), snapshot.getMax(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, MEAN), snapshot.getMean(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, STDDEV), snapshot.getStdDev(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, MEDIAN), snapshot.getMedian(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, PERCENTILE_75TH), snapshot.get75thPercentile(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, PERCENTILE_95TH), snapshot.get95thPercentile(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, PERCENTILE_98TH), snapshot.get98thPercentile(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, PERCENTILE_99TH), snapshot.get99thPercentile(), convertDuration, timestamp);
pushMetric(getKey(baseMetricName, PERCENTILE_999TH), snapshot.get999thPercentile(), convertDuration, timestamp);
}
private void reportMetered(String prefix, String name, Metered metered, long timestamp) throws IOException {
reportCounter(prefix, name, metered, timestamp);
String baseMetricName = getKey(prefix, name);
pushMetricRate(getKey(baseMetricName, RATE_1MIN), metered.getOneMinuteRate(), timestamp);
pushMetricRate(getKey(baseMetricName, RATE_5MIN), metered.getFiveMinuteRate(), timestamp);
pushMetricRate(getKey(baseMetricName, RATE_15MIN), metered.getFifteenMinuteRate(), timestamp);
pushMetricRate(getKey(baseMetricName, MEAN_RATE), metered.getMeanRate(), timestamp);
}
private void pushMetric(String metricName, Number value, boolean toDuration, long timestamp) throws IOException {
String metricValue = toDuration ? getValue(convertDuration(value.doubleValue())) : getValue(value);
pushMetric(metricName, metricValue, timestamp);
}
private void pushMetricRate(String metricName, double value, long timestamp)
throws IOException {
pushMetric(metricName, getValue(convertRate(value)), timestamp);
}
private void pushMetric(String name, String value, long timestamp) throws IOException {
this.graphitePusher.push(name, value, timestamp);
}
private String getValue(Number value) {
return value.toString();
}
private String getKey(String baseName, Measurements measurements) {
return getKey(baseName, measurements.getName());
}
private String getKey(String... keys) {
return JOINER.join(keys);
}
}