All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.spredfast.kafka.connect.s3.metrics.DatadogMetrics Maven / Gradle / Ivy

There is a newer version: 0.5.0
Show newest version
package com.spredfast.kafka.connect.s3.metrics;

import static java.util.Optional.ofNullable;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.coursera.metrics.datadog.DatadogReporter;
import org.coursera.metrics.datadog.transport.HttpTransport;
import org.coursera.metrics.datadog.transport.Transport;
import org.coursera.metrics.datadog.transport.UdpTransport;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.spredfast.kafka.connect.s3.Configure;
import com.spredfast.kafka.connect.s3.Metrics;

/**
 * Reports metrics via https://github.com/coursera/metrics-datadog
 *
 * Config: https://github.com/spredfast/kafka-connect-s3/wiki/Metrics
 *
 */
public class DatadogMetrics implements Metrics {

	private final MetricRegistry registry;

	private final Map, String>> nameCache = new HashMap<>();

	public DatadogMetrics(Map config) {
		registry = new MetricRegistry();

		// don't try to report to DD in tests
		if ("true".equals(config.get("test.noreport"))) {
			return;
		}

		DatadogReporter.Builder builder = DatadogReporter.forRegistry(registry);

		parseTags(config).ifPresent(builder::withTags);

		if (Boolean.valueOf(config.getOrDefault("ec2", "true"))) {
			try {
				builder.withEC2Host();
			} catch (IOException e) {
				throw new IllegalArgumentException(e);
			}
		}

		ofNullable(config.get("prefix"))
			.ifPresent(builder::withPrefix);

		if ("udp".equalsIgnoreCase(config.getOrDefault("transport", "udp"))) {
			builder.withTransport(buildUdp(Configure.subKeys("transport", config)));
		} else {
			builder.withTransport(buildHttp(Configure.subKeys("transport", config)));
		}

		// don't worry about closing as it only creates a daemon thread
		builder.build().start(Integer.parseInt(config.getOrDefault("frequency", "10")), TimeUnit.SECONDS);
	}

	@VisibleForTesting
	static Optional> parseTags(Map config) {
		return ofNullable(config.get("tags"))
			.filter(s -> s.length() > 0)
			.map(s -> s.split(","))
			.map(ImmutableList::copyOf);
	}

	private Transport buildUdp(Map transport) {
		UdpTransport.Builder builder = new UdpTransport.Builder();
		ofNullable(transport.get("host"))
			.map(Object::toString)
			.ifPresent(builder::withStatsdHost);
		ofNullable(transport.get("prefix"))
			.map(Object::toString)
			.ifPresent(builder::withPrefix);
		ofNullable(transport.get("port"))
			.map(Object::toString)
			.map(Integer::parseInt)
			.ifPresent(builder::withPort);
		return builder.build();
	}

	private Transport buildHttp(Map transport) {
		HttpTransport.Builder builder = new HttpTransport.Builder();

		builder.withApiKey(ofNullable(transport.get("key"))
			.orElseThrow(() -> new IllegalArgumentException("Missing API Key!")).toString());

		ofNullable(transport.get("connect.timeout.ms"))
			.map(Object::toString)
			.map(Integer::parseInt)
			.ifPresent(builder::withConnectTimeout);

		ofNullable(transport.get("socket.timeout.ms"))
			.map(Object::toString)
			.map(Integer::parseInt)
			.ifPresent(builder::withSocketTimeout);

		// only setup proxy if at least a port is specified
		ofNullable(transport.get("proxy.port"))
			.map(Object::toString)
			.map(Integer::parseInt)
			.ifPresent(port -> builder.withProxy(transport.getOrDefault("proxy.host", "localhost").toString(), port));

		return builder.build();
	}

	@Override
	public void meter(int count, String name, Map tags) {
		registry.meter(name(name, tags)).mark(count);
	}

	private String name(String name, Map tags) {
		// cache all this string concatenation. hopefully the hashing is a lot cheaper
		return nameCache.computeIfAbsent(name, n -> new HashMap<>())
			.computeIfAbsent(tags, ts -> name + '[' + tags.entrySet().stream()
				.sorted(Map.Entry.comparingByKey())
				.reduce(new StringBuilder(), (sb, tag) -> (sb.length() > 0 ? sb.append(',') : sb)
					.append(tag.getKey()).append(':').append(tag.getValue()), StringBuilder::append)
				.toString() + ']');
	}

	@Override
	public void hist(long value, String name, Map tags) {
		registry.histogram(name(name, tags)).update(value);
	}

	@Override
	public void gauge(String name, Map tags, Supplier getValue) {
		String key = name(name, tags);
		// only add the gauge if it doesn't already exist
		if (!registry.getGauges().containsKey(key)) {
			synchronized (registry) {
				if (!registry.getGauges().containsKey(key)) {
					registry.register(key, (Gauge) getValue::get);
				}
			}
		}
	}

	public MetricRegistry getRegistry() {
		return registry;
	}

	@Override
	public StopTimer time(String name, Map tags) {
		return registry.timer(name(name, tags)).time()::stop;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy