Skip to content

Commit

Permalink
StatsD support (fixes micrometer-metrics#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkschneider authored Oct 5, 2017
1 parent 9e24b2e commit 4a598db
Show file tree
Hide file tree
Showing 92 changed files with 1,934 additions and 218 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* @author Jon Schneider
*/
public interface DatadogConfig extends StepRegistryConfig {
@Override
default String prefix() {
return "datadog";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ public class DatadogNamingConvention implements NamingConvention {
*/
@Override
public String name(String name, Meter.Type type, String baseUnit) {
String sanitized = NamingConvention.camelCase.name(name, type, baseUnit);
String sanitized = name;

// Metrics that don't start with a letter get dropped on the floor by the Datadog publish API,
// so we will prepend them with 'm_'.
if(!Character.isLetter(sanitized.charAt(0))) {
sanitized = "m_" + sanitized;
sanitized = "m." + sanitized;
}

sanitized = NamingConvention.dot.name(sanitized, type, baseUnit);

if(sanitized.length() > 200)
return sanitized.substring(0, 200);
return sanitized;
Expand All @@ -49,10 +51,11 @@ public String name(String name, Meter.Type type, String baseUnit) {
*/
@Override
public String tagKey(String key) {
String sanitized = key;
if(Character.isDigit(key.charAt(0))) {
return "m_" + key;
sanitized = "m." + key;
}
return NamingConvention.camelCase.tagKey(key);
return NamingConvention.dot.tagKey(sanitized);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package io.micrometer.datadog;

import io.micrometer.core.MockClock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.tck.MeterRegistryCompatibilityKit;
import io.micrometer.core.MockClock;

import java.time.Duration;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ class DatadogNamingConventionTest {

@Test
void nameStartsWithLetter() {
assertThat(convention.name("123", Meter.Type.Gauge, null)).isEqualTo("m_123");
assertThat(convention.name("123", Meter.Type.Gauge, null)).isEqualTo("m.123");
}

@Test
void tagKeyStartsWithLetter() {
assertThat(convention.tagKey("123")).isEqualTo("m_123");
assertThat(convention.tagKey("123")).isEqualTo("m.123");
}

@Test
void dotNotationIsConvertedToCamelCase() {
assertThat(convention.name("gauge.size", Meter.Type.Gauge, null)).isEqualTo("gaugeSize");
assertThat(convention.name("gauge.size", Meter.Type.Gauge, null)).isEqualTo("gauge.size");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package io.micrometer.ganglia;

import io.micrometer.core.MockClock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
import io.micrometer.core.tck.MeterRegistryCompatibilityKit;
import io.micrometer.core.MockClock;

class GangliaMeterRegistryCompatibilityTest extends MeterRegistryCompatibilityKit {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package io.micrometer.graphite;

import io.micrometer.core.MockClock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
import io.micrometer.core.tck.MeterRegistryCompatibilityKit;
import io.micrometer.core.MockClock;

class GraphiteMeterRegistryCompatibilityTest extends MeterRegistryCompatibilityKit {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public String tagValue(String value) {

private String format(String name) {
// https://docs.influxdata.com/influxdb/v1.3/write_protocols/line_protocol_reference/#special-characters
return NamingConvention.snakeCase.tagKey(name)
return NamingConvention.camelCase.tagKey(name)
.replace(",", "\\,")
.replace(" ", "\\ ")
.replace("=", "\\=")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.zip.GZIPOutputStream;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

/**
* @author Jon Schneider
Expand Down Expand Up @@ -81,7 +82,7 @@ protected void pushMetrics() {
con.setRequestProperty("Authorization", "Basic " + encoded);
}

String body = batch.stream()
List<String> bodyLines = batch.stream()
.filter(m -> !Double.isNaN(m.value()))
.map(m -> {
String field = StreamSupport.stream(m.id().tags().spliterator(), false)
Expand All @@ -96,16 +97,20 @@ protected void pushMetrics() {
.collect(joining(""));

return m.id().name() + tags + " " + field + "=" + m.value() + " " + time;
}).collect(joining("\n"));
})
.collect(toList());

String body = String.join("\n", bodyLines);

if(compressed)
con.setRequestProperty("Content-Encoding", "gzip");

try (OutputStream os = con.getOutputStream();
GZIPOutputStream gz = new GZIPOutputStream(os)) {
try (OutputStream os = con.getOutputStream()) {
if(compressed) {
gz.write(body.getBytes());
gz.flush();
try(GZIPOutputStream gz = new GZIPOutputStream(os)) {
gz.write(body.getBytes());
gz.flush();
}
}
else {
os.write(body.getBytes());
Expand All @@ -118,7 +123,7 @@ protected void pushMetrics() {
if (status >= 200 && status < 300) {
logger.info("successfully sent " + batch.size() + " metrics to influx");
} else if (status >= 400) {
try (InputStream in = (status >= 400) ? con.getErrorStream() : con.getInputStream()) {
try (InputStream in = con.getErrorStream()) {
logger.error("failed to send metrics: " + new BufferedReader(new InputStreamReader(in))
.lines().collect(joining("\n")));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assertions.fail;

class InfluxNamingConventionTest {
private InfluxNamingConvention convention = new InfluxNamingConvention();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
import io.micrometer.core.instrument.stats.hist.HistogramConfig;

public interface PrometheusConfig extends HistogramConfig {
@Override
default String prefix() {
return "prometheus";
}

/**
* {@code true} if meter descriptions should be sent to Prometheus.
* Turn this off to minimize the amount of data sent on each scrape.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import io.micrometer.core.instrument.stats.hist.PercentileTimeHistogram;
import io.micrometer.core.instrument.stats.hist.TimeHistogram;
import io.micrometer.core.instrument.stats.quantile.Quantiles;
import io.micrometer.core.instrument.util.TimeUtils;
import io.micrometer.prometheus.internal.*;
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
Expand Down Expand Up @@ -100,7 +99,6 @@ public DistributionSummary newDistributionSummary(Meter.Id id, Histogram.Builder

@Override
protected io.micrometer.core.instrument.Timer newTimer(Meter.Id id, Histogram.Builder<?> histogram, Quantiles quantiles) {
id.setBaseUnit("seconds");
final CustomPrometheusSummary summary = collectorByName(CustomPrometheusSummary.class, getConventionName(id),
n -> new CustomPrometheusSummary(collectorId(id)).register(registry));
return new PrometheusTimer(id, summary.child(getConventionTags(id), quantiles, buildHistogramIfNecessary(histogram)), config().clock());
Expand Down Expand Up @@ -235,15 +233,14 @@ private <C extends Collector> C collectorByName(Class<C> collectorType, String n
return (C) collector;
}

@Override
protected <T> io.micrometer.core.instrument.Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction<T> f) {
id.setBaseUnit("seconds");
return newGauge(id, obj, obj2 -> TimeUtils.convert(f.applyAsDouble(obj2), fUnit, TimeUnit.SECONDS));
}

private PrometheusCollectorId collectorId(Meter.Id id) {
return new PrometheusCollectorId(getConventionName(id),
getConventionTags(id).stream().map(Tag::getKey).collect(toList()),
id.getDescription());
}

@Override
protected TimeUnit getBaseTimeUnit() {
return TimeUnit.SECONDS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package io.micrometer.prometheus;

import io.micrometer.core.MockClock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.tck.MeterRegistryCompatibilityKit;
import io.micrometer.core.MockClock;
import io.prometheus.client.CollectorRegistry;

class PrometheusMeterRegistryCompatibilityTest extends MeterRegistryCompatibilityKit {
Expand Down
11 changes: 11 additions & 0 deletions implementations/micrometer-registry-statsd/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apply plugin: 'org.junit.platform.gradle.plugin'

dependencies {
compile project(':micrometer-core')
compile 'io.projectreactor:reactor-core:3.1.0.RELEASE'
compile 'io.projectreactor.ipc:reactor-netty:0.7.0.RELEASE'

testCompile project(':micrometer-test')
testCompile 'io.projectreactor:reactor-test:3.1.0.RELEASE'
testCompile 'org.junit.jupiter:junit-jupiter-params:5.0.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2017 Pivotal Software, Inc.
* <p>
* Licensed 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 io.micrometer.statsd;

import io.micrometer.core.instrument.MeterRegistryConfig;
import io.micrometer.core.instrument.stats.hist.HistogramConfig;

import java.time.Duration;

/**
* @author Jon Schneider
*/
public interface StatsdConfig extends MeterRegistryConfig, HistogramConfig {
@Override
default String prefix() {
return "statsd";
}

/**
* Choose which variant of the StatsD line protocol to use.
*/
default StatsdFlavor flavor() {
String v = get(prefix() + ".flavor");

// Datadog is the default because it is more frequently requested than
// vanilla StatsD (Etsy), and Telegraf supports Datadog's format with a configuration
// option.
if(v == null)
return StatsdFlavor.Datadog;

for (StatsdFlavor flavor : StatsdFlavor.values()) {
if(flavor.toString().equalsIgnoreCase(v))
return flavor;
}

throw new IllegalArgumentException("Unrecognized statsd flavor '" + v + "' (check property " + prefix() + ".flavor)");
}

/**
* Returns true if publishing is enabled. Default is {@code true}.
*/
default boolean enabled() {
String v = get(prefix() + ".enabled");
return v == null || Boolean.valueOf(v);
}

/**
* The host name of the StatsD agent.
*/
default String host() {
String v = get(prefix() + ".host");
return (v == null) ? "localhost" : v;
}

/**
* The UDP port of the StatsD agent.
*/
default int port() {
String v = get(prefix() + ".port");
return (v == null) ? 8125 : Integer.parseInt(v);
}

/**
* Keep the total length of the payload within your network's MTU. There is no single good value to use, but here are some guidelines for common network scenarios:
* 1. Fast Ethernet (1432) - This is most likely for Intranets.
* 2. Gigabit Ethernet (8932) - Jumbo frames can make use of this feature much more efficient.
* 3. Commodity Internet (512) - If you are routing over the internet a value in this range will be reasonable. You might be able to go higher, but you are at the mercy of all the hops in your route.
*
* FIXME implement packet-limiting the StatsD publisher
*/
default int maxPacketLength() {
String v = get(prefix() + ".maxPacketLength");

// 1400 is the value that Datadog has chosen in their client. Seems to work well
// for most cases.
return (v == null) ? 1400 : Integer.parseInt(v);
}

/**
* Determines how often gauges will be polled. When a gauge is polled, its value is recalculated. If the value has changed,
* it is sent to the StatsD server.
*/
default Duration pollingFrequency() {
String v = get(prefix() + ".pollingFrequency");
return v == null ? Duration.ofSeconds(10) : Duration.parse(v);
}

/**
* Governs the maximum size of the queue of items waiting to be sent to a StatsD agent over UDP.
*/
default int queueSize() {
String v = get(prefix() + ".queueSize");
return v == null ? Integer.MAX_VALUE : Integer.parseInt(v);
}
}
Loading

0 comments on commit 4a598db

Please sign in to comment.