/*
* 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.Queue;
import java.util.concurrent.TimeUnit;
import org.influxdb.dto.Point;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.primitives.Doubles;
import gobblin.metrics.GobblinTrackingEvent;
import gobblin.metrics.MetricContext;
import gobblin.metrics.event.MultiPartEvent;
import gobblin.metrics.event.EventSubmitter;
import gobblin.metrics.reporter.EventReporter;
import static gobblin.metrics.event.TimingEvent.METADATA_DURATION;
/**
*
* {@link gobblin.metrics.reporter.EventReporter} that emits {@link gobblin.metrics.GobblinTrackingEvent} events
* as timestamped name - value pairs to InfluxDB
*
* @author Lorand Bendig
*
*/
public class InfluxDBEventReporter extends EventReporter {
private final InfluxDBPusher influxDBPusher;
private static final double EMTPY_VALUE = 0d;
private static final Logger LOGGER = LoggerFactory.getLogger(InfluxDBEventReporter.class);
public InfluxDBEventReporter(Builder<?> builder) throws IOException {
super(builder);
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();
}
}
@Override
public void reportEventQueue(Queue<GobblinTrackingEvent> queue) {
GobblinTrackingEvent nextEvent;
try {
while (null != (nextEvent = queue.poll())) {
pushEvent(nextEvent);
}
} catch (IOException e) {
LOGGER.error("Error sending event to InfluxDB", e);
}
}
/**
* Extracts the event and its metadata from {@link GobblinTrackingEvent} and creates
* timestamped name value pairs
*
* @param event {@link GobblinTrackingEvent} to be reported
* @throws IOException
*/
private void pushEvent(GobblinTrackingEvent event) throws IOException {
Map<String, String> metadata = event.getMetadata();
String name = getMetricName(metadata, event.getName());
long timestamp = event.getTimestamp();
MultiPartEvent multiPartEvent = MultiPartEvent.getEvent(metadata.get(EventSubmitter.EVENT_TYPE));
if (multiPartEvent == null) {
influxDBPusher.push(buildEventAsPoint(name, EMTPY_VALUE, timestamp));
}
else {
List<Point> points = Lists.newArrayList();
for (String field : multiPartEvent.getMetadataFields()) {
Point point = buildEventAsPoint(JOINER.join(name, field), convertValue(field, metadata.get(field)), timestamp);
points.add(point);
}
influxDBPusher.push(points);
}
}
/**
* Convert the event value taken from the metadata to double (default type).
* It falls back to string type if the value is missing or it is non-numeric
* is of string or missing
* Metadata entries are emitted as distinct events (see {@link MultiPartEvent})
*
* @param field {@link GobblinTrackingEvent} metadata key
* @param value {@link GobblinTrackingEvent} metadata value
* @return The converted event value
*/
private Object convertValue(String field, String value) {
if (value == null) return EMTPY_VALUE;
if (METADATA_DURATION.equals(field)) {
return convertDuration(TimeUnit.MILLISECONDS.toNanos(Long.parseLong(value)));
}
else {
Double doubleValue = Doubles.tryParse(value);
return (doubleValue == null) ? value : doubleValue;
}
}
/**
* Returns a new {@link InfluxDBEventReporter.Builder} for {@link InfluxDBEventReporter}.
* Will automatically add all Context tags to the reporter.
*
* @param context the {@link gobblin.metrics.MetricContext} to report
* @return InfluxDBEventReporter builder
* @deprecated this method is bugged. Use {@link InfluxDBEventReporter.Factory#forContext} instead.
*/
@Deprecated
public static Builder<? extends Builder> forContext(MetricContext context) {
return new BuilderImpl(context);
}
public static class BuilderImpl extends Builder<BuilderImpl> {
private BuilderImpl(MetricContext context) {
super(context);
}
@Override
protected BuilderImpl self() {
return this;
}
}
public static class Factory {
/**
* Returns a new {@link InfluxDBEventReporter.Builder} for {@link InfluxDBEventReporter}.
* Will automatically add all Context tags to the reporter.
*
* @param context the {@link gobblin.metrics.MetricContext} to report
* @return InfluxDBEventReporter builder
*/
public static BuilderImpl forContext(MetricContext context) {
return new BuilderImpl(context);
}
}
/**
* Builder for {@link InfluxDBEventReporter}.
* Defaults to no filter, reporting rates in seconds and times in milliseconds using TCP connection
*/
public static abstract class Builder<T extends EventReporter.Builder<T>>
extends EventReporter.Builder<T> {
protected String url;
protected String username;
protected String password;
protected String database;
protected InfluxDBConnectionType connectionType;
protected Optional<InfluxDBPusher> influxDBPusher;
protected Builder(MetricContext context) {
super(context);
this.influxDBPusher = Optional.absent();
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();
}
/**
* Builds and returns {@link InfluxDBEventReporter}.
*
* @return InfluxDBEventReporter
*/
public InfluxDBEventReporter build() throws IOException {
return new InfluxDBEventReporter(this);
}
}
private Point buildEventAsPoint(String name, Object value, long timestamp) throws IOException {
return Point.measurement(name).field("value", value).time(timestamp, TimeUnit.MILLISECONDS).build();
}
}