/*
* 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.influxdb;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.concurrent.TimeUnit;
import org.influxdb.dto.Point;
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.google.common.collect.Lists;
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.*;
/**
* InfluxDB reporter for metrics
*
* @author Lorand Bendig
*
*/
public class InfluxDBReporter extends ConfiguredScheduledReporter {
private final InfluxDBPusher influxDBPusher;
private static final Logger LOGGER = LoggerFactory.getLogger(InfluxDBReporter.class);
public InfluxDBReporter(Builder<?> builder, Config config) {
super(builder, config);
if (builder.influxDBPusher.isPresent()) {
this.influxDBPusher = builder.influxDBPusher.get();
} else {
this.influxDBPusher =
new InfluxDBPusher.Builder(builder.url, builder.username, builder.password, builder.database,
builder.connectionType).build();
}
}
/**
* A static factory class for obtaining new {@link gobblin.metrics.influxdb.InfluxDBReporter.Builder}s
*
* @see gobblin.metrics.influxdb.InfluxDBReporter.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 InfluxDBReporter}. 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 url;
protected String username;
protected String password;
protected String database;
protected InfluxDBConnectionType connectionType;
protected Optional<InfluxDBPusher> influxDBPusher;
protected Builder() {
super();
this.name = "InfluxDBReporter";
this.influxDBPusher = Optional.absent();
this.filter = MetricFilter.ALL;
this.connectionType = InfluxDBConnectionType.TCP;
}
/**
* Set {@link gobblin.metrics.influxdb.InfluxDBPusher} to use.
*/
public T withInfluxDBPusher(InfluxDBPusher pusher) {
this.influxDBPusher = Optional.of(pusher);
return self();
}
/**
* Set connection parameters for the {@link gobblin.metrics.influxdb.InfluxDBPusher} creation
*/
public T withConnection(String url, String username, String password, String database) {
this.url = url;
this.username = username;
this.password = password;
this.database = database;
return self();
}
/**
* Set {@link gobblin.metrics.influxdb.InfluxDBConnectionType} to use.
*/
public T withConnectionType(InfluxDBConnectionType 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 InfluxDBReporter}.
*
* @return InfluxDBReporter
*/
public InfluxDBReporter build(Properties props) throws IOException {
return new InfluxDBReporter(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();
List<Point> points = Lists.newArrayList();
try {
for (Map.Entry<String, Gauge> gauge : gauges.entrySet()) {
reportGauge(points, prefix, gauge.getKey(), gauge.getValue(), timestamp);
}
for (Map.Entry<String, Counter> counter : counters.entrySet()) {
reportCounter(points, prefix, counter.getKey(), counter.getValue(), timestamp);
}
for (Map.Entry<String, Histogram> histogram : histograms.entrySet()) {
reportHistogram(points, prefix, histogram.getKey(), histogram.getValue(), timestamp);
}
for (Map.Entry<String, Meter> meter : meters.entrySet()) {
reportMetered(points, prefix, meter.getKey(), meter.getValue(), timestamp);
}
for (Map.Entry<String, Timer> timer : timers.entrySet()) {
reportTimer(points, prefix, timer.getKey(), timer.getValue(), timestamp);
}
influxDBPusher.push(points);
} catch (IOException ioe) {
LOGGER.error("Error sending metrics to InfluxDB", ioe);
}
}
private void reportGauge(List<Point> points, String prefix, String name, Gauge gauge, long timestamp)
throws IOException {
String metricName = getKey(prefix, name);
points.add(buildMetricAsPoint(metricName, gauge.getValue(), timestamp));
}
private void reportCounter(List<Point> points, String prefix, String name, Counting counter, long timestamp)
throws IOException {
String metricName = getKey(prefix, name, COUNT.getName());
points.add(buildMetricAsPoint(metricName, counter.getCount(), false, timestamp));
}
private void reportHistogram(List<Point> points, String prefix, String name, Histogram histogram, long timestamp)
throws IOException {
reportCounter(points, prefix, name, histogram, timestamp);
reportSnapshot(points, prefix, name, histogram.getSnapshot(), timestamp, false);
}
private void reportTimer(List<Point> points, String prefix, String name, Timer timer, long timestamp)
throws IOException {
reportSnapshot(points, prefix, name, timer.getSnapshot(), timestamp, true);
reportMetered(points, prefix, name, timer, timestamp);
}
private void reportSnapshot(List<Point> points, String prefix, String name, Snapshot snapshot, long timestamp,
boolean convertDuration) throws IOException {
String baseMetricName = getKey(prefix, name);
points.add(buildMetricAsPoint(getKey(baseMetricName, MIN), snapshot.getMin(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, MAX), snapshot.getMax(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, MEAN), snapshot.getMean(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, STDDEV), snapshot.getStdDev(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, MEDIAN), snapshot.getMedian(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, PERCENTILE_75TH), snapshot.get75thPercentile(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, PERCENTILE_95TH), snapshot.get95thPercentile(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, PERCENTILE_98TH), snapshot.get98thPercentile(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, PERCENTILE_99TH), snapshot.get99thPercentile(), convertDuration, timestamp));
points.add(buildMetricAsPoint(getKey(baseMetricName, PERCENTILE_999TH), snapshot.get999thPercentile(), convertDuration, timestamp));
}
private void reportMetered(List<Point> points, String prefix, String name, Metered metered, long timestamp)
throws IOException {
reportCounter(points,prefix, name, metered, timestamp);
String baseMetricName = getKey(prefix, name);
points.add(buildRateAsPoint(getKey(baseMetricName, RATE_1MIN), metered.getOneMinuteRate(), timestamp));
points.add(buildRateAsPoint(getKey(baseMetricName, RATE_5MIN), metered.getFiveMinuteRate(), timestamp));
points.add(buildRateAsPoint(getKey(baseMetricName, RATE_15MIN), metered.getFifteenMinuteRate(), timestamp));
points.add(buildRateAsPoint(getKey(baseMetricName, MEAN_RATE), metered.getMeanRate(), timestamp));
}
private Point buildMetricAsPoint(String metricName, Number value, boolean toDuration, long timestamp)
throws IOException {
Number metricValue = toDuration ? convertDuration(value.doubleValue()) : value;
return buildMetricAsPoint(metricName, metricValue, timestamp);
}
private Point buildRateAsPoint(String metricName, double value, long timestamp)
throws IOException {
return buildMetricAsPoint(metricName, convertRate(value), timestamp);
}
private Point buildMetricAsPoint(String name, Object value, long timestamp) throws IOException {
return Point.measurement(name).field("value", value).time(timestamp, TimeUnit.MILLISECONDS).build();
}
private String getKey(String baseName, Measurements measurements) {
return getKey(baseName, measurements.getName());
}
private String getKey(String... keys) {
return JOINER.join(keys);
}
}