From 4a598db0e4b73af3dc88c794e87ac2038e38837f Mon Sep 17 00:00:00 2001 From: Jon Schneider Date: Wed, 4 Oct 2017 21:16:13 -0500 Subject: [PATCH] StatsD support (fixes #143) --- .../io/micrometer/datadog/DatadogConfig.java | 1 + .../datadog/DatadogNamingConvention.java | 11 +- ...DatadogMeterRegistryCompatibilityTest.java | 2 +- .../datadog/DatadogNamingConventionTest.java | 6 +- ...GangliaMeterRegistryCompatibilityTest.java | 2 +- ...raphiteMeterRegistryCompatibilityTest.java | 2 +- .../influx/InfluxNamingConvention.java | 2 +- .../io/micrometer/influx/InfluxRegistry.java | 19 +- .../influx/InfluxNamingConventionTest.java | 1 - .../prometheus/PrometheusConfig.java | 5 + .../prometheus/PrometheusMeterRegistry.java | 13 +- ...metheusMeterRegistryCompatibilityTest.java | 2 +- .../micrometer-registry-statsd/build.gradle | 11 + .../io/micrometer/statsd/StatsdConfig.java | 108 ++++++++ .../io/micrometer/statsd/StatsdCounter.java | 62 +++++ .../statsd/StatsdDistributionSummary.java | 82 ++++++ .../io/micrometer/statsd/StatsdFlavor.java | 36 +++ .../io/micrometer/statsd/StatsdGauge.java | 68 +++++ .../micrometer/statsd/StatsdLineBuilder.java | 144 ++++++++++ .../statsd/StatsdLongTaskTimer.java | 80 ++++++ .../statsd/StatsdMeterRegistry.java | 234 +++++++++++++++++ .../io/micrometer/statsd/StatsdPollable.java | 20 ++ .../io/micrometer/statsd/StatsdTimer.java | 73 ++++++ .../statsd/internal/MemoizingSupplier.java | 61 +++++ .../io/micrometer/statsd/CounterSample.java | 50 ++++ .../StatsdMeterRegistryCompatibilityTest.java | 36 +++ .../statsd/StatsdMeterRegistryTest.java | 245 ++++++++++++++++++ micrometer-core/build.gradle | 6 +- .../micrometer/core/annotation/TimedSet.java | 5 +- .../instrument/AbstractMeterRegistry.java | 22 +- .../core/instrument/FunctionCounter.java | 4 - .../core/instrument/LongTaskTimer.java | 4 +- .../io/micrometer/core/instrument/Meter.java | 9 + .../core/instrument/MeterRegistry.java | 1 - .../core/instrument/MeterRegistryConfig.java | 4 +- .../core/instrument/NamingConvention.java | 5 + .../micrometer/core/instrument/Statistic.java | 11 +- .../io/micrometer/core/instrument/Timer.java | 2 +- .../binder/cache/EhCache2Metrics.java | 2 - .../binder/cache/HazelcastCacheMetrics.java | 2 - .../binder/jvm/ExecutorServiceMetrics.java | 1 - .../binder/system/ProcessorMetrics.java | 1 - .../binder/system/UptimeMetrics.java | 10 +- .../composite/CompositeLongTaskTimer.java | 1 - .../composite/CompositeMeterRegistry.java | 5 + .../dropwizard/DropwizardMeterRegistry.java | 28 +- .../internal/DefaultFunctionTimer.java | 3 - .../core/instrument/internal/MeterId.java | 7 + .../instrument/internal/TimedExecutor.java | 4 +- .../core/instrument/noop/NoopMeter.java | 5 + .../simple/SimpleMeterRegistry.java | 17 +- .../spectator/SpectatorLongTaskTimer.java | 2 - .../spectator/SpectatorMeterRegistry.java | 18 +- .../spectator/step/AbstractStepRegistry.java | 2 +- .../step/StepSpectatorMeterRegistry.java | 1 - .../util/HierarchicalNameMapper.java | 21 +- .../io/micrometer/core/instrument/IdTest.java | 30 +++ .../cache/CaffeineCacheMetricsTest.java | 2 - .../binder/jvm/ClassLoaderMetricsTest.java | 1 - .../jvm/ExecutorServiceMetricsTest.java | 8 +- .../binder/jvm/JvmThreadMetricsTest.java | 1 - .../binder/logging/LogbackMetricsTest.java | 1 - .../binder/system/ProcessorMetricsTest.java | 1 - .../binder/system/UptimeMetricsTest.java | 1 - .../util/HierarchicalNameMapperTest.java | 92 ++++--- micrometer-samples/build.gradle | 2 +- .../core/samples/utils/SampleRegistries.java | 19 +- micrometer-spring-legacy/build.gradle | 3 +- .../MeterBindersConfiguration.java | 2 +- .../MetricsAutoConfiguration.java | 8 +- .../atlas/AtlasExportConfiguration.java | 1 - .../export/atlas/AtlasProperties.java | 5 - .../statsd/StatsdExportConfiguration.java | 111 ++++++++ .../export/statsd/StatsdProperties.java | 149 +++++++++++ .../spring/jdbc/DataSourceMetrics.java | 1 - .../spring/samples/StatsdDatadogSample.java | 28 ++ .../spring/samples/StatsdTelegrafSample.java | 28 ++ .../resources/application-statsd-datadog.yml | 19 ++ .../resources/application-statsd-telegraf.yml | 19 ++ .../src/samples/resources/application.yml | 2 + .../MetricsAutoConfigurationTest.java | 2 +- .../simple/SimpleExportConfigurationTest.java | 3 +- .../jdbc/DataSourceMetricsHikariTest.java | 1 - .../ScheduledMethodMetricsTest.java | 4 - ...etricsHandlerInterceptorAutoTimedTest.java | 1 - .../servlet/WebMvcMetricsIntegrationTest.java | 1 - .../tck/MeterRegistryCompatibilityKit.java | 7 +- .../SimpleMeterRegistryCompatibilityTest.java | 4 +- scripts/.gitignore | 3 +- scripts/grafana.sh | 2 + scripts/statsd-telegraf.sh | 9 + settings.gradle | 2 +- 92 files changed, 1934 insertions(+), 218 deletions(-) create mode 100644 implementations/micrometer-registry-statsd/build.gradle create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdConfig.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdCounter.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdDistributionSummary.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdFlavor.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdGauge.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLineBuilder.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLongTaskTimer.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdMeterRegistry.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdPollable.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdTimer.java create mode 100644 implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/internal/MemoizingSupplier.java create mode 100644 implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/CounterSample.java create mode 100644 implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryCompatibilityTest.java create mode 100644 implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java create mode 100644 micrometer-core/src/test/java/io/micrometer/core/instrument/IdTest.java create mode 100644 micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdExportConfiguration.java create mode 100644 micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdProperties.java create mode 100644 micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdDatadogSample.java create mode 100644 micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdTelegrafSample.java create mode 100644 micrometer-spring-legacy/src/samples/resources/application-statsd-datadog.yml create mode 100644 micrometer-spring-legacy/src/samples/resources/application-statsd-telegraf.yml create mode 100755 scripts/grafana.sh create mode 100755 scripts/statsd-telegraf.sh diff --git a/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogConfig.java b/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogConfig.java index 144819d456..1bf86834d9 100644 --- a/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogConfig.java +++ b/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogConfig.java @@ -23,6 +23,7 @@ * @author Jon Schneider */ public interface DatadogConfig extends StepRegistryConfig { + @Override default String prefix() { return "datadog"; } diff --git a/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogNamingConvention.java b/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogNamingConvention.java index aa177f934c..a8dda3705b 100644 --- a/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogNamingConvention.java +++ b/implementations/micrometer-registry-datadog/src/main/java/io/micrometer/datadog/DatadogNamingConvention.java @@ -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; @@ -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); } /** diff --git a/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogMeterRegistryCompatibilityTest.java b/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogMeterRegistryCompatibilityTest.java index 5db83a5938..bfe2b24bdc 100644 --- a/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogMeterRegistryCompatibilityTest.java +++ b/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogMeterRegistryCompatibilityTest.java @@ -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; diff --git a/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogNamingConventionTest.java b/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogNamingConventionTest.java index 780b3b841e..0446f372a0 100644 --- a/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogNamingConventionTest.java +++ b/implementations/micrometer-registry-datadog/src/test/java/io/micrometer/datadog/DatadogNamingConventionTest.java @@ -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"); } } diff --git a/implementations/micrometer-registry-ganglia/src/test/java/io/micrometer/ganglia/GangliaMeterRegistryCompatibilityTest.java b/implementations/micrometer-registry-ganglia/src/test/java/io/micrometer/ganglia/GangliaMeterRegistryCompatibilityTest.java index 85e421ebec..5d82109f3f 100644 --- a/implementations/micrometer-registry-ganglia/src/test/java/io/micrometer/ganglia/GangliaMeterRegistryCompatibilityTest.java +++ b/implementations/micrometer-registry-ganglia/src/test/java/io/micrometer/ganglia/GangliaMeterRegistryCompatibilityTest.java @@ -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 diff --git a/implementations/micrometer-registry-graphite/src/test/java/io/micrometer/graphite/GraphiteMeterRegistryCompatibilityTest.java b/implementations/micrometer-registry-graphite/src/test/java/io/micrometer/graphite/GraphiteMeterRegistryCompatibilityTest.java index 31952a4847..3bd3be778c 100644 --- a/implementations/micrometer-registry-graphite/src/test/java/io/micrometer/graphite/GraphiteMeterRegistryCompatibilityTest.java +++ b/implementations/micrometer-registry-graphite/src/test/java/io/micrometer/graphite/GraphiteMeterRegistryCompatibilityTest.java @@ -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 diff --git a/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxNamingConvention.java b/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxNamingConvention.java index 0a9ca23c36..0e24946812 100644 --- a/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxNamingConvention.java +++ b/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxNamingConvention.java @@ -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("=", "\\=") diff --git a/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxRegistry.java b/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxRegistry.java index d86f7db123..8a09853ee4 100644 --- a/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxRegistry.java +++ b/implementations/micrometer-registry-influx/src/main/java/io/micrometer/influx/InfluxRegistry.java @@ -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 @@ -81,7 +82,7 @@ protected void pushMetrics() { con.setRequestProperty("Authorization", "Basic " + encoded); } - String body = batch.stream() + List bodyLines = batch.stream() .filter(m -> !Double.isNaN(m.value())) .map(m -> { String field = StreamSupport.stream(m.id().tags().spliterator(), false) @@ -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()); @@ -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"))); } diff --git a/implementations/micrometer-registry-influx/src/test/java/io/micrometer/influx/InfluxNamingConventionTest.java b/implementations/micrometer-registry-influx/src/test/java/io/micrometer/influx/InfluxNamingConventionTest.java index 9c78cccd40..1827f9f298 100644 --- a/implementations/micrometer-registry-influx/src/test/java/io/micrometer/influx/InfluxNamingConventionTest.java +++ b/implementations/micrometer-registry-influx/src/test/java/io/micrometer/influx/InfluxNamingConventionTest.java @@ -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(); diff --git a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusConfig.java b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusConfig.java index 2d36b9ef45..7e03f13a15 100644 --- a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusConfig.java +++ b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusConfig.java @@ -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. diff --git a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusMeterRegistry.java b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusMeterRegistry.java index f3e0e8a467..184183036d 100644 --- a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusMeterRegistry.java +++ b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheus/PrometheusMeterRegistry.java @@ -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; @@ -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()); @@ -235,15 +233,14 @@ private C collectorByName(Class collectorType, String n return (C) collector; } - @Override - protected io.micrometer.core.instrument.Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction 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; + } } diff --git a/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheus/PrometheusMeterRegistryCompatibilityTest.java b/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheus/PrometheusMeterRegistryCompatibilityTest.java index 2a2316364d..2256079a07 100644 --- a/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheus/PrometheusMeterRegistryCompatibilityTest.java +++ b/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheus/PrometheusMeterRegistryCompatibilityTest.java @@ -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 { diff --git a/implementations/micrometer-registry-statsd/build.gradle b/implementations/micrometer-registry-statsd/build.gradle new file mode 100644 index 0000000000..128b7c83cf --- /dev/null +++ b/implementations/micrometer-registry-statsd/build.gradle @@ -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' +} \ No newline at end of file diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdConfig.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdConfig.java new file mode 100644 index 0000000000..a102e58eef --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdConfig.java @@ -0,0 +1,108 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 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); + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdCounter.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdCounter.java new file mode 100644 index 0000000000..d7698d61b7 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdCounter.java @@ -0,0 +1,62 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.util.MeterEquivalence; +import org.reactivestreams.Subscriber; + +import java.util.concurrent.atomic.DoubleAdder; + +/** + * @author Jon Schneider + */ +public class StatsdCounter extends AbstractMeter implements Counter { + private DoubleAdder count = new DoubleAdder(); + private final StatsdLineBuilder lineBuilder; + private final Subscriber publisher; + + StatsdCounter(Id id, StatsdLineBuilder lineBuilder, Subscriber publisher) { + super(id); + this.lineBuilder = lineBuilder; + this.publisher = publisher; + } + + @Override + public void increment(double amount) { + if(amount > 0) { + count.add(amount); + publisher.onNext(lineBuilder.count((long) amount)); + } + } + + @Override + public double count() { + return count.doubleValue(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return MeterEquivalence.equals(this, o); + } + + @Override + public int hashCode() { + return MeterEquivalence.hashCode(this); + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdDistributionSummary.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdDistributionSummary.java new file mode 100644 index 0000000000..6b82a6dc86 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdDistributionSummary.java @@ -0,0 +1,82 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.stats.hist.Histogram; +import io.micrometer.core.instrument.stats.quantile.Quantiles; +import io.micrometer.core.instrument.util.MeterEquivalence; +import org.reactivestreams.Subscriber; + +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; + +public class StatsdDistributionSummary extends AbstractMeter implements DistributionSummary { + private LongAdder count = new LongAdder(); + private DoubleAdder amount = new DoubleAdder(); + + private final StatsdLineBuilder lineBuilder; + private final Subscriber publisher; + private final Quantiles quantiles; + private final Histogram histogram; + + StatsdDistributionSummary(Meter.Id id, StatsdLineBuilder lineBuilder, Subscriber publisher, + Quantiles quantiles, Histogram histogram) { + super(id); + this.lineBuilder = lineBuilder; + this.publisher = publisher; + this.quantiles = quantiles; + this.histogram = histogram; + } + + @Override + public void record(double amount) { + if (amount >= 0) { + count.increment(); + this.amount.add(amount); + + publisher.onNext(lineBuilder.histogram(amount)); + + if (quantiles != null) + quantiles.observe(amount); + if (histogram != null) + histogram.observe(amount); + } + } + + @Override + public long count() { + return count.longValue(); + } + + @Override + public double totalAmount() { + return amount.doubleValue(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return MeterEquivalence.equals(this, o); + } + + @Override + public int hashCode() { + return MeterEquivalence.hashCode(this); + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdFlavor.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdFlavor.java new file mode 100644 index 0000000000..3c232892dc --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdFlavor.java @@ -0,0 +1,36 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +public enum StatsdFlavor { + /** + * https://github.com/etsy/statsd/blob/master/docs/metric_types.md + */ + Etsy, + + /** + * https://docs.datadoghq.com/guides/dogstatsd/#datagram-format + */ + Datadog, + + /** + * https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/ + * + * For gauges to work as expected, you should set `delete_gauges = false` in your input options as documented here: + * https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd + */ + Telegraf +} \ No newline at end of file diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdGauge.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdGauge.java new file mode 100644 index 0000000000..a3ea53e8ad --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdGauge.java @@ -0,0 +1,68 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.util.MeterEquivalence; +import org.reactivestreams.Subscriber; + +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.ToDoubleFunction; + +public class StatsdGauge extends AbstractMeter implements Gauge, StatsdPollable { + private final StatsdLineBuilder lineBuilder; + private final Subscriber publisher; + + private final WeakReference ref; + private final ToDoubleFunction value; + private final AtomicReference lastValue = new AtomicReference<>(Double.NEGATIVE_INFINITY); + + StatsdGauge(Meter.Id id, StatsdLineBuilder lineBuilder, Subscriber publisher, T obj, ToDoubleFunction value) { + super(id); + this.lineBuilder = lineBuilder; + this.publisher = publisher; + this.ref = new WeakReference<>(obj); + this.value = value; + } + + @Override + public double value() { + T obj = ref.get(); + return obj != null ? value.applyAsDouble(ref.get()) : 0; + } + + @Override + public void poll() { + double val = value(); +// if(lastValue.getAndSet(val) != val) { + publisher.onNext(lineBuilder.gauge(val)); +// } + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return MeterEquivalence.equals(this, o); + } + + @Override + public int hashCode() { + return MeterEquivalence.hashCode(this); + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLineBuilder.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLineBuilder.java new file mode 100644 index 0000000000..a1b8f5c593 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLineBuilder.java @@ -0,0 +1,144 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.NamingConvention; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.util.HierarchicalNameMapper; + +import java.beans.Introspector; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.micrometer.statsd.internal.MemoizingSupplier.memoize; +import static java.beans.Introspector.decapitalize; +import static java.util.stream.Stream.*; + +class StatsdLineBuilder { + private final Meter.Id id; + private final StatsdFlavor flavor; + private final NamingConvention convention; + + private final Supplier datadogTagString; + private final Supplier telegrafTagString; + + /** + * Because NumberFormat is not thread-safe we cannot share instances across threads. + */ + private static final ThreadLocal NUMBER_FORMATTERS = ThreadLocal.withInitial(() -> { + // Always create the formatter for the US locale in order to avoid this bug: + // https://github.com/indeedeng/java-dogstatsd-client/issues/3 + final NumberFormat numberFormatter = NumberFormat.getInstance(Locale.US); + numberFormatter.setGroupingUsed(false); + numberFormatter.setMaximumFractionDigits(6); + + // We need to specify a value for Double.NaN that is recognizable + final DecimalFormat decimalFormat = (DecimalFormat) numberFormatter; + final DecimalFormatSymbols symbols = decimalFormat.getDecimalFormatSymbols(); + symbols.setNaN("NaN"); + decimalFormat.setDecimalFormatSymbols(symbols); + + return numberFormatter; + }); + + StatsdLineBuilder(Meter.Id id, StatsdFlavor flavor, NamingConvention convention) { + this.id = id; + this.flavor = flavor; + this.convention = convention; + + // service:payroll,region:us-west + this.datadogTagString = memoize(() -> + id.getTags().iterator().hasNext() ? + id.getConventionTags(convention).stream() + .map(t -> t.getKey() + ":" + t.getValue()) + .collect(Collectors.joining(",")) + : null + ); + + // service=payroll,region=us-west + this.telegrafTagString = memoize(() -> + id.getTags().iterator().hasNext() ? + id.getConventionTags(convention).stream() + .map(t -> t.getKey() + "=" + t.getValue()) + .collect(Collectors.joining(",")) + : null + ); + } + + String count(long amount) { + return count(amount, Statistic.Count); + } + + String count(long amount, Statistic stat) { + return line(Long.toString(amount), stat, "c"); + } + + String gauge(double amount) { + return gauge(amount, Statistic.Value); + } + + String gauge(double amount, Statistic stat) { + return line(NUMBER_FORMATTERS.get().format(amount), stat, "g"); + } + + String histogram(double amount) { + return line(NUMBER_FORMATTERS.get().format(amount), null, "h"); + } + + String timing(double timeMs) { + return line(NUMBER_FORMATTERS.get().format(timeMs), null, "ms"); + } + + private String line(String amount, Statistic stat, String type) { + switch (flavor) { + case Etsy: + return metricName(stat) + ":" + amount + "|" + type; + case Datadog: + return metricName(stat) + ":" + amount + "|" + type + tags(stat, datadogTagString.get(),":", "|#"); + case Telegraf: + default: + return metricName(stat) + tags(stat, telegrafTagString.get(),"=", ",") + ":" + amount + "|" + type; + } + } + + private String tags(Statistic stat, String otherTags, String keyValueSeparator, String preamble) { + String tags = of(stat == null ? null : "statistic" + keyValueSeparator + decapitalize(stat.toString()), otherTags) + .filter(Objects::nonNull) + .collect(Collectors.joining(",")); + + if(!tags.isEmpty()) + tags = preamble + tags; + return tags; + } + + private String metricName(Statistic stat) { + switch (flavor) { + case Etsy: + return HierarchicalNameMapper.DEFAULT.toHierarchicalName(id.withTag(stat), convention); + case Datadog: + case Telegraf: + default: + return convention.name(id.getName(), Meter.Type.Counter, id.getBaseUnit()); + } + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLongTaskTimer.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLongTaskTimer.java new file mode 100644 index 0000000000..e978fd9870 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdLongTaskTimer.java @@ -0,0 +1,80 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.simple.SimpleLongTaskTimer; +import org.reactivestreams.Subscriber; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class StatsdLongTaskTimer extends AbstractMeter implements LongTaskTimer, StatsdPollable { + private final LongTaskTimer delegate; + private final StatsdLineBuilder lineBuilder; + private final Subscriber publisher; + + private final AtomicReference lastActive = new AtomicReference<>(Long.MIN_VALUE); + private final AtomicReference lastDuration = new AtomicReference<>(Double.NEGATIVE_INFINITY); + + StatsdLongTaskTimer(Id id, StatsdLineBuilder lineBuilder, Subscriber publisher, Clock clock) { + super(id); + this.delegate = new SimpleLongTaskTimer(id, clock); + this.lineBuilder = lineBuilder; + this.publisher = publisher; + } + + @Override + public long start() { + return delegate.start(); + } + + @Override + public long stop(long task) { + return delegate.stop(task); + } + + @Override + public double duration(long task, TimeUnit unit) { + return delegate.duration(task, unit); + } + + @Override + public double duration(TimeUnit unit) { + return delegate.duration(unit); + } + + @Override + public int activeTasks() { + return delegate.activeTasks(); + } + + @Override + public void poll() { + long active = activeTasks(); + if(lastActive.getAndSet(active) != active) { + publisher.onNext(lineBuilder.gauge(active, Statistic.ActiveTasks)); + } + + double duration = duration(TimeUnit.NANOSECONDS); + if(lastDuration.getAndSet(duration) != duration) { + publisher.onNext(lineBuilder.gauge(duration, Statistic.Duration)); + } + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdMeterRegistry.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdMeterRegistry.java new file mode 100644 index 0000000000..6c050432fe --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdMeterRegistry.java @@ -0,0 +1,234 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.*; +import io.micrometer.core.instrument.stats.hist.Bucket; +import io.micrometer.core.instrument.stats.hist.BucketFilter; +import io.micrometer.core.instrument.stats.hist.Histogram; +import io.micrometer.core.instrument.stats.hist.PercentileTimeHistogram; +import io.micrometer.core.instrument.stats.quantile.Quantiles; +import io.netty.bootstrap.Bootstrap; +import io.netty.handler.logging.LoggingHandler; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.UnicastProcessor; +import reactor.ipc.netty.NettyContext; +import reactor.ipc.netty.NettyPipeline; +import reactor.ipc.netty.channel.ChannelOperations; +import reactor.ipc.netty.channel.ContextHandler; +import reactor.ipc.netty.options.ClientOptions; +import reactor.ipc.netty.tcp.TcpResources; +import reactor.util.concurrent.Queues; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; +import java.util.function.ToDoubleFunction; + +/** + * @author Jon Schneider + */ +public class StatsdMeterRegistry extends AbstractMeterRegistry { + private final StatsdConfig statsdConfig; + + private volatile UnicastProcessor publisher; + + private Disposable.Swap udpClient = Disposables.swap(); + + private final Collection pollableMeters = Collections.synchronizedCollection(new LinkedList<>()); + + // VisibleForTesting + Disposable.Swap meterPoller = Disposables.swap(); + + public StatsdMeterRegistry(StatsdConfig config, Clock clock) { + super(clock); + + this.statsdConfig = config; + + switch (statsdConfig.flavor()) { + case Datadog: + config().namingConvention(NamingConvention.dot); + break; + default: + config().namingConvention(NamingConvention.camelCase); + } + + this.publisher = UnicastProcessor.create(Queues.get(statsdConfig.queueSize()).get()); + gauge("statsd.queue.size", this.publisher, UnicastProcessor::size); + gauge("statsd.queue.capacity", this.publisher, UnicastProcessor::getBufferSize); + + if (config.enabled()) + start(); + } + + public void start() { + // TODO this will get simpler when this issue is addressed: + // https://github.com/reactor/reactor-netty/issues/174 + Mono + .create(sink -> { + ClientOptions options = new ClientOptions(ClientOptions.builder() + .loopResources(TcpResources.get()) + .poolResources(TcpResources.get())) { + @Override + protected boolean useDatagramChannel() { + return true; + } + }; + + Bootstrap b = options.get(); + + SocketAddress adr = new InetSocketAddress(statsdConfig.host(), statsdConfig.port()); + b.remoteAddress(adr); + + ContextHandler h = ContextHandler.newClientContext(sink, + options, + new LoggingHandler(StatsdMeterRegistry.class), + false, + adr, + (ch, c, msg) -> ChannelOperations.bind(ch, (in, out) -> { + out.options(NettyPipeline.SendOptions::flushOnEach) + .sendString(publisher) + .then().subscribe(); + return Flux.never(); + }, c)); + + b.handler(h); + h.setFuture(b.connect()); + }) + .subscribe(client -> { + this.udpClient.replace(client); + + // now that we're connected, start polling gauges + meterPoller.replace(Flux.interval(statsdConfig.pollingFrequency()) + .doOnEach(n -> { + synchronized (pollableMeters) { + pollableMeters.forEach(StatsdPollable::poll); + } + }) + .subscribe()); + }); + } + + public void stop() { + udpClient.dispose(); + meterPoller.dispose(); + } + + @Override + protected Gauge newGauge(Meter.Id id, T obj, ToDoubleFunction f) { + StatsdGauge gauge = new StatsdGauge<>(id, lineBuilder(id), publisher, obj, f); + pollableMeters.add(gauge); + return gauge; + } + + @Override + protected Counter newCounter(Meter.Id id) { + return new StatsdCounter(id, lineBuilder(id), publisher); + } + + @Override + protected LongTaskTimer newLongTaskTimer(Meter.Id id) { + StatsdLongTaskTimer ltt = new StatsdLongTaskTimer(id, lineBuilder(id), publisher, clock); + pollableMeters.add(ltt); + return ltt; + } + + @Override + protected Timer newTimer(Meter.Id id, Histogram.Builder histogram, Quantiles quantiles) { + registerQuantilesGaugeIfNecessary(id, quantiles); + return new StatsdTimer(id, lineBuilder(id), publisher, clock, quantiles, + histogram == null ? null : registerHistogramCounterIfNecessary(id, histogram)); + } + + @Override + protected DistributionSummary newDistributionSummary(Meter.Id id, Histogram.Builder histogram, Quantiles quantiles) { + registerQuantilesGaugeIfNecessary(id, quantiles); + return new StatsdDistributionSummary(id, lineBuilder(id), publisher, quantiles, registerHistogramCounterIfNecessary(id, histogram)); + } + + @Override + protected void newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { + measurements.forEach(ms -> { + StatsdLineBuilder line = lineBuilder(id); + switch (ms.getStatistic()) { + case Count: + case Total: + case TotalTime: + case SumOfSquares: + pollableMeters.add(() -> publisher.onNext(line.count((long) ms.getValue(), ms.getStatistic()))); + break; + case Value: + case ActiveTasks: + case Duration: + case Unknown: + pollableMeters.add(() -> publisher.onNext(line.gauge(ms.getValue(), ms.getStatistic()))); + break; + } + }); + } + + @Override + protected TimeUnit getBaseTimeUnit() { + return TimeUnit.MILLISECONDS; + } + + private void registerQuantilesGaugeIfNecessary(Meter.Id id, Quantiles quantiles) { + if (quantiles != null) { + for (Double q : quantiles.monitored()) { + if (!Double.isNaN(q)) { + gauge(id.getName(), Tags.concat(id.getTags(), "quantile", Double.toString(q)), q, quantiles::get); + } + } + } + } + + private Histogram registerHistogramCounterIfNecessary(Meter.Id id, Histogram.Builder builder) { + if (builder != null) { + if (builder instanceof PercentileTimeHistogram.Builder) { + PercentileTimeHistogram.Builder percentileHistBuilder = (PercentileTimeHistogram.Builder) builder; + + if (statsdConfig.timerPercentilesMax() != null) { + double max = (double) statsdConfig.timerPercentilesMax().toMillis(); + percentileHistBuilder.filterBuckets(BucketFilter.clampMax(max)); + } + + if (statsdConfig.timerPercentilesMin() != null) { + double min = (double) statsdConfig.timerPercentilesMin().toMillis(); + percentileHistBuilder.filterBuckets(BucketFilter.clampMin(min)); + } + } + + Histogram hist = builder.create(Histogram.Summation.Normal); + + for (Bucket bucket : hist.getBuckets()) { + more().counter(createId(id.getName(), Tags.concat(id.getTags(), "bucket", bucket.getTagString()), null), + bucket, Bucket::getValue); + } + return hist; + } + return null; + } + + private StatsdLineBuilder lineBuilder(Meter.Id id) { + return new StatsdLineBuilder(id, statsdConfig.flavor(), config().namingConvention()); + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdPollable.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdPollable.java new file mode 100644 index 0000000000..9cce93987e --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdPollable.java @@ -0,0 +1,20 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +interface StatsdPollable { + void poll(); +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdTimer.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdTimer.java new file mode 100644 index 0000000000..00b4ba0b6a --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/StatsdTimer.java @@ -0,0 +1,73 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import io.micrometer.core.instrument.AbstractTimer; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.stats.hist.Histogram; +import io.micrometer.core.instrument.stats.quantile.Quantiles; +import io.micrometer.core.instrument.util.TimeUtils; +import org.reactivestreams.Processor; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; + +public class StatsdTimer extends AbstractTimer implements Timer { + private LongAdder count = new LongAdder(); + private DoubleAdder totalTime = new DoubleAdder(); + + private final StatsdLineBuilder lineBuilder; + private final Processor publisher; + private final Quantiles quantiles; + private final Histogram histogram; + + StatsdTimer(Id id, StatsdLineBuilder lineBuilder, Processor publisher, Clock clock, Quantiles quantiles, Histogram histogram) { + super(id, clock); + this.lineBuilder = lineBuilder; + this.publisher = publisher; + this.quantiles = quantiles; + this.histogram = histogram; + } + + @Override + public void record(long amount, TimeUnit unit) { + if (amount >= 0) { + count.increment(); + + double msAmount = TimeUtils.convert(amount, unit, TimeUnit.MILLISECONDS); + totalTime.add(msAmount); + + publisher.onNext(lineBuilder.timing(msAmount)); + + if (quantiles != null) + quantiles.observe(msAmount); + if (histogram != null) + histogram.observe(msAmount); + } + } + + @Override + public long count() { + return count.longValue(); + } + + @Override + public double totalTime(TimeUnit unit) { + return TimeUtils.millisToUnit(totalTime.doubleValue(), unit); + } +} diff --git a/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/internal/MemoizingSupplier.java b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/internal/MemoizingSupplier.java new file mode 100644 index 0000000000..b34a5d8889 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/main/java/io/micrometer/statsd/internal/MemoizingSupplier.java @@ -0,0 +1,61 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd.internal; + +import java.util.function.Supplier; + +/** + * Modified from Guava's MemoizingSupplier + * @param + */ +public class MemoizingSupplier implements Supplier { + + final Supplier delegate; + transient volatile boolean initialized; + + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + transient T value; + + public MemoizingSupplier(Supplier delegate) { + this.delegate = delegate; + } + + public static MemoizingSupplier memoize(Supplier delegate) { + return new MemoizingSupplier<>(delegate); + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + T t = delegate.get(); + value = t; + initialized = true; + return t; + } + } + } + return value; + } + + @Override + public String toString() { + return "Suppliers.memoize(" + delegate + ")"; + } +} \ No newline at end of file diff --git a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/CounterSample.java b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/CounterSample.java new file mode 100644 index 0000000000..b9398866d0 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/CounterSample.java @@ -0,0 +1,50 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Counter; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; + +import java.time.Duration; + +public class CounterSample { + public static void main(String[] args) { + Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.DEBUG); + + StatsdMeterRegistry registry = new StatsdMeterRegistry(new StatsdConfig() { + @Override + public String get(String k) { + return null; + } + + @Override + public StatsdFlavor flavor() { + return StatsdFlavor.Telegraf; + } + }, Clock.SYSTEM); + + Counter counter = registry.counter("my.counter", "k", "v"); + + Flux.interval(Duration.ofSeconds(1)) + .doOnEach(s -> counter.increment()) + .blockLast(); + } +} diff --git a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryCompatibilityTest.java b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryCompatibilityTest.java new file mode 100644 index 0000000000..c25e05fdd8 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryCompatibilityTest.java @@ -0,0 +1,36 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import io.micrometer.core.MockClock; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.tck.MeterRegistryCompatibilityKit; +import org.junit.jupiter.api.BeforeAll; +import org.slf4j.LoggerFactory; + +public class StatsdMeterRegistryCompatibilityTest extends MeterRegistryCompatibilityKit { + @BeforeAll + static void before() { + ((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.INFO); + } + + @Override + public MeterRegistry registry() { + return new StatsdMeterRegistry(k -> null, new MockClock()); + } +} diff --git a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java new file mode 100644 index 0000000000..61bf314a04 --- /dev/null +++ b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java @@ -0,0 +1,245 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.statsd; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import io.micrometer.core.MockClock; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.netty.channel.ChannelOption; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.slf4j.LoggerFactory; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.publisher.Flux; +import reactor.ipc.netty.options.ClientOptions; +import reactor.ipc.netty.udp.UdpClient; +import reactor.test.StepVerifier; + +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Jon Schneider + */ +class StatsdMeterRegistryTest { + /** + * A port that is NOT the default for DogStatsD or Telegraf, so these unit tests + * do not fail if one of those agents happens to be running on the same box. + */ + private static final int PORT = 8126; + private MockClock mockClock = new MockClock(); + + @BeforeAll + static void before() { + ((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.INFO); + } + + private void assertLines(Consumer registryAction, StatsdFlavor flavor, String... expected) { + final CountDownLatch bindLatch = new CountDownLatch(1); + final CountDownLatch receiveLatch = new CountDownLatch(1); + + final AtomicReference result = new AtomicReference<>(); + final Disposable.Swap server = Disposables.swap(); + + final StatsdMeterRegistry registry = registry(flavor); + + Consumer> opts = builder -> builder.option(ChannelOption.SO_REUSEADDR, true) + .connectAddress(() -> new InetSocketAddress(PORT)); + + UdpClient.create(opts) + .newHandler((in, out) -> { + in.receive() + .asString() + .subscribe(line -> { + // ignore gauges monitoring the registry itself + if(line.startsWith("statsd")) + return; + + result.set(line); + receiveLatch.countDown(); + }); + return Flux.never(); + }) + .doOnSuccess(v -> bindLatch.countDown()) + .subscribe(server::replace); + + try { + assertTrue(bindLatch.await(1, TimeUnit.SECONDS)); + + try { + registryAction.accept(registry); + } catch (Throwable t) { + fail("Failed to perform registry action", t); + } + + assertTrue(receiveLatch.await(10, TimeUnit.SECONDS)); + assertThat(result.get().split("\n")).contains(expected); + } catch (InterruptedException e) { + fail("Failed to wait for line", e); + } finally { + server.dispose(); + registry.stop(); + } + } + + private StatsdMeterRegistry registry(StatsdFlavor flavor) { + return new StatsdMeterRegistry(new StatsdConfig() { + @Override + public String get(String k) { + return null; + } + + @Override + public int port() { + return PORT; + } + + @Override + public StatsdFlavor flavor() { + return flavor; + } + + @Override + public Duration pollingFrequency() { + return Duration.ofMillis(1); + } + }, mockClock); + } + + @ParameterizedTest + @EnumSource(StatsdFlavor.class) + void counterLineProtocol(StatsdFlavor flavor) { + String line = null; + switch (flavor) { + case Etsy: + line = "myCounter.myTag.val.statistic.count:2|c"; + break; + case Datadog: + line = "my.counter:2|c|#statistic:count,my.tag:val"; + break; + case Telegraf: + line = "myCounter,statistic=count,myTag=val:2|c"; + } + + assertLines(r -> r.counter("my.counter", "my.tag", "val").increment(2.1), flavor, line); + } + + @ParameterizedTest + @EnumSource(StatsdFlavor.class) + void gaugeLineProtocol(StatsdFlavor flavor) { + String line = null; + switch (flavor) { + case Etsy: + line = "myGauge.myTag.val.statistic.value:2|g"; + break; + case Datadog: + line = "my.gauge:2|g|#statistic:value,my.tag:val"; + break; + case Telegraf: + line = "myGauge,statistic=value,myTag=val:2|g"; + break; + } + + Integer n = 2; + assertLines(r -> r.gauge("my.gauge", Tags.zip("my.tag", "val"), n), flavor, line); + } + + @ParameterizedTest + @EnumSource(StatsdFlavor.class) + void timerLineProtocol(StatsdFlavor flavor) { + String line = null; + switch (flavor) { + case Etsy: + line = "myTimer.myTag.val:1|ms"; + break; + case Datadog: + line = "my.timer:1|ms|#my.tag:val"; + break; + case Telegraf: + line = "myTimer,myTag=val:1|ms"; + } + + assertLines(r -> r.timer("my.timer", "my.tag", "val").record(1, TimeUnit.MILLISECONDS), + flavor, line); + } + + @ParameterizedTest + @EnumSource(StatsdFlavor.class) + void summaryLineProtocol(StatsdFlavor flavor) { + String line = null; + switch (flavor) { + case Etsy: + line = "mySummary.myTag.val:1|h"; + break; + case Datadog: + line = "my.summary:1|h|#my.tag:val"; + break; + case Telegraf: + line = "mySummary,myTag=val:1|h"; + } + + assertLines(r -> r.summary("my.summary", "my.tag", "val").record(1), flavor, line); + } + + @ParameterizedTest + @EnumSource(StatsdFlavor.class) + void longTaskTimerLineProtocol(StatsdFlavor flavor) { + final Function ltt = r -> r.more().longTaskTimer(r.createId("my.long.task", Tags.zip("my.tag", "val"), "")); + + StepVerifier + .withVirtualTime(() -> { + String[] lines = null; + switch (flavor) { + case Etsy: + lines = new String[]{ + "myLongTask.myTag.val.statistic.activetasks:1|c", + "myLongTaskDuration.myTag.val.statistic.value:1|c", + }; + break; + case Datadog: + lines = new String[]{ + "my.long.task:1|c|#statistic:activetasks,myTag:val", + "my.long.task:1|c|#statistic:duration,myTag:val", + }; + break; + case Telegraf: + lines = new String[]{ + "myLongTask,statistic=activetasks,myTag=val:1|c", + "myLongTask,statistic=duration,myTag=val:1|c", + }; + } + + assertLines(r -> ltt.apply(r).start(), flavor, lines); + return null; + }) + .then(() -> mockClock.addAndGet(10, TimeUnit.MILLISECONDS)) + .thenAwait(Duration.ofMillis(10)); + } +} \ No newline at end of file diff --git a/micrometer-core/build.gradle b/micrometer-core/build.gradle index f32570614a..b77a51e12a 100644 --- a/micrometer-core/build.gradle +++ b/micrometer-core/build.gradle @@ -23,10 +23,10 @@ dependencies { compile 'ch.qos.logback:logback-classic:latest.release', optional // reactor - compile 'io.projectreactor:reactor-core:3.1.0.RC1', optional + compile 'io.projectreactor:reactor-core:3.1.0.RELEASE', optional - testCompile 'io.projectreactor:reactor-test:3.1.0.RC1' - testCompile 'io.projectreactor.ipc:reactor-netty:latest.release' + testCompile 'io.projectreactor:reactor-test:3.1.0.RELEASE' + testCompile 'io.projectreactor.ipc:reactor-netty:0.7.0.RELEASE' // JUnit 5 testCompile 'org.junit.jupiter:junit-jupiter-api:5.0.0' diff --git a/micrometer-core/src/main/java/io/micrometer/core/annotation/TimedSet.java b/micrometer-core/src/main/java/io/micrometer/core/annotation/TimedSet.java index 7455db968c..8d46281639 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/annotation/TimedSet.java +++ b/micrometer-core/src/main/java/io/micrometer/core/annotation/TimedSet.java @@ -15,7 +15,10 @@ */ package io.micrometer.core.annotation; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/AbstractMeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/AbstractMeterRegistry.java index c7db8a8283..e3443b9806 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/AbstractMeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/AbstractMeterRegistry.java @@ -19,6 +19,7 @@ import io.micrometer.core.instrument.internal.MeterId; import io.micrometer.core.instrument.stats.hist.Histogram; import io.micrometer.core.instrument.stats.quantile.Quantiles; +import io.micrometer.core.instrument.util.TimeUtils; import java.lang.ref.WeakReference; import java.util.*; @@ -101,9 +102,17 @@ public AbstractMeterRegistry(Clock clock) { protected abstract void newMeter(Meter.Id id, Meter.Type type, Iterable measurements); - protected abstract Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction f); + protected Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction f) { + id.setBaseUnit(getBaseTimeUnitStr()); + return newGauge(id, obj, obj2 -> TimeUtils.convert(f.applyAsDouble(obj2), fUnit, getBaseTimeUnit())); + } - protected abstract Meter newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnits); + protected Meter newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnits) { + id.setBaseUnit(getBaseTimeUnitStr()); + FunctionTimer ft = new DefaultFunctionTimer<>(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnits, getBaseTimeUnit()); + newMeter(id, Meter.Type.Timer, ft.measure()); + return ft; + } protected List getConventionTags(Meter.Id id) { return id.getConventionTags(config().namingConvention()); @@ -113,6 +122,14 @@ protected String getConventionName(Meter.Id id) { return id.getConventionName(config().namingConvention()); } + protected abstract TimeUnit getBaseTimeUnit(); + + private String getBaseTimeUnitStr() { + if(getBaseTimeUnit() == null) + return null; + return getBaseTimeUnit().toString().toLowerCase(); + } + @Override public Meter register(Meter.Id id, Meter.Type type, Iterable measurements) { return registerMeterIfNecessary(Meter.class, id, id2 -> { @@ -174,6 +191,7 @@ public DistributionSummary summary(Meter.Id id, Histogram.Builder histogram, public LongTaskTimer longTaskTimer(Meter.Id id) { return registerMeterIfNecessary(LongTaskTimer.class, id, id2 -> { id2.setType(Meter.Type.LongTaskTimer); + id2.setBaseUnit(getBaseTimeUnitStr()); return newLongTaskTimer(id2); }); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/FunctionCounter.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/FunctionCounter.java index 94647d76dd..3b3363ab57 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/FunctionCounter.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/FunctionCounter.java @@ -15,10 +15,6 @@ */ package io.micrometer.core.instrument; -import io.micrometer.core.instrument.Measurement; -import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.Statistic; - import java.util.Collections; /** diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/LongTaskTimer.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/LongTaskTimer.java index aee4d9e863..d49fa47867 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/LongTaskTimer.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/LongTaskTimer.java @@ -120,8 +120,8 @@ default void record(Runnable f) { @Override default Iterable measure() { return Arrays.asList( - new Measurement(() -> (double) activeTasks(), Statistic.Count), - new Measurement(() -> duration(TimeUnit.NANOSECONDS), Statistic.Total) + new Measurement(() -> (double) activeTasks(), Statistic.ActiveTasks), + new Measurement(() -> duration(TimeUnit.NANOSECONDS), Statistic.Duration) ); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java index 3b25221a03..661b96ab60 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java @@ -15,6 +15,7 @@ */ package io.micrometer.core.instrument; +import java.beans.Introspector; import java.util.List; /** @@ -61,6 +62,14 @@ interface Id { String getConventionName(NamingConvention convention); List getConventionTags(NamingConvention convention); + Id withTag(Tag tag); + + default Id withTag(Statistic statistic) { + if(statistic == null) + return this; + return withTag(Tag.of("statistic", Introspector.decapitalize(statistic.toString()))); + } + /** * Associate this id with a specific type, sometimes used in the determination of a * convention name. This association is 1-1 since an id can only be used once per registry diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java index 1150270c03..1c7d4e8207 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java @@ -15,7 +15,6 @@ */ package io.micrometer.core.instrument; -import io.micrometer.core.instrument.internal.DefaultFunctionTimer; import io.micrometer.core.instrument.stats.hist.Histogram; import io.micrometer.core.instrument.stats.quantile.Quantiles; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistryConfig.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistryConfig.java index aae77c35bc..84069efc3c 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistryConfig.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistryConfig.java @@ -16,9 +16,7 @@ package io.micrometer.core.instrument; public interface MeterRegistryConfig { - default String prefix() { - return "prometheus"; - } + String prefix(); /** * Get the value associated with a key. diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/NamingConvention.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/NamingConvention.java index 814eb741d7..9bea1cf583 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/NamingConvention.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/NamingConvention.java @@ -34,6 +34,11 @@ public interface NamingConvention { NamingConvention identity = (name, type, baseUnit) -> name; + /** + * This maps to identity because we suggest using dot notation everywhere in Micrometer-instrumented code + */ + NamingConvention dot = identity; + NamingConvention snakeCase = new NamingConvention() { @Override public String name(String name, Meter.Type type, String baseUnit) { diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Statistic.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Statistic.java index 37be2ee1ec..9f0e1df429 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Statistic.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Statistic.java @@ -19,6 +19,9 @@ public enum Statistic { /** The sum of the amounts recorded. */ Total, + /** The sum of the times recorded. */ + TotalTime, + /** Rate per second for calls. */ Count, @@ -31,5 +34,11 @@ public enum Statistic { /** Instantaneous value, such as those reported by gauges **/ Value, - Unknown + Unknown, + + /** Number of currently active tasks for a long task timer. */ + ActiveTasks, + + /** Duration of a running task in a long task timer. */ + Duration, } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java index 95d2d17bde..a7932beba3 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java @@ -95,7 +95,7 @@ default Runnable wrap(Runnable f) { default Iterable measure() { return Arrays.asList( new Measurement(() -> (double) count(), Statistic.Count), - new Measurement(() -> totalTime(TimeUnit.NANOSECONDS), Statistic.Total) + new Measurement(() -> totalTime(TimeUnit.NANOSECONDS), Statistic.TotalTime) ); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/EhCache2Metrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/EhCache2Metrics.java index 9e01e8345c..a36d7b3123 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/EhCache2Metrics.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/EhCache2Metrics.java @@ -22,8 +22,6 @@ import net.sf.ehcache.Ehcache; import net.sf.ehcache.statistics.StatisticsGateway; -import javax.cache.Cache; - /** * @author Jon Schneider */ diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/HazelcastCacheMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/HazelcastCacheMetrics.java index f81a47e08b..4035594e02 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/HazelcastCacheMetrics.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/cache/HazelcastCacheMetrics.java @@ -15,8 +15,6 @@ */ package io.micrometer.core.instrument.binder.cache; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.hazelcast.core.IMap; import com.hazelcast.monitor.LocalMapStats; import io.micrometer.core.instrument.MeterRegistry; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetrics.java index b3066f6c96..b463e24719 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetrics.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetrics.java @@ -25,7 +25,6 @@ import java.util.concurrent.*; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; /** * Monitors the status of executor service pools. Does not record timings on operations executed in the {@link ExecutorService}, diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/ProcessorMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/ProcessorMetrics.java index 6d6c8d591d..3a6e1ea8ac 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/ProcessorMetrics.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/ProcessorMetrics.java @@ -19,7 +19,6 @@ import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.binder.MeterBinder; -import javax.annotation.processing.Processor; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/UptimeMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/UptimeMetrics.java index 9e9db984b2..7ca57bfe4e 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/UptimeMetrics.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/UptimeMetrics.java @@ -15,16 +15,16 @@ */ package io.micrometer.core.instrument.binder.system; -import static java.util.Collections.emptyList; -import static java.util.Objects.requireNonNull; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.MeterBinder; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.util.concurrent.TimeUnit; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.binder.MeterBinder; +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; /** * Uptime metrics. diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeLongTaskTimer.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeLongTaskTimer.java index 33374b15e3..ee44f31e42 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeLongTaskTimer.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeLongTaskTimer.java @@ -20,7 +20,6 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.noop.NoopLongTaskTimer; -import io.micrometer.core.instrument.util.TimeUtils; import java.util.Collections; import java.util.LinkedHashMap; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeMeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeMeterRegistry.java index b144edec98..88940c4f94 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeMeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/composite/CompositeMeterRegistry.java @@ -103,6 +103,11 @@ protected Meter newFunctionTimer(Meter.Id id, T obj, ToLongFunction count return ft; } + @Override + protected TimeUnit getBaseTimeUnit() { + return null; + } + @Override protected void newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { CompositeMeter meter = new CompositeCustomMeter(id, type, measurements); diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/dropwizard/DropwizardMeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/dropwizard/DropwizardMeterRegistry.java index 2432097621..94318d865e 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/dropwizard/DropwizardMeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/dropwizard/DropwizardMeterRegistry.java @@ -18,18 +18,15 @@ import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; import io.micrometer.core.instrument.*; -import io.micrometer.core.instrument.internal.DefaultFunctionTimer; import io.micrometer.core.instrument.simple.SimpleLongTaskTimer; import io.micrometer.core.instrument.stats.hist.Bucket; import io.micrometer.core.instrument.stats.hist.Histogram; import io.micrometer.core.instrument.stats.quantile.Quantiles; import io.micrometer.core.instrument.util.HierarchicalNameMapper; -import io.micrometer.core.instrument.util.TimeUtils; import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; import java.util.function.ToDoubleFunction; -import java.util.function.ToLongFunction; /** * @author Jon Schneider @@ -67,7 +64,6 @@ protected io.micrometer.core.instrument.Gauge newGauge(Meter.Id id, T obj, T @Override protected Timer newTimer(Meter.Id id, Histogram.Builder histogram, Quantiles quantiles) { - id.setBaseUnit("nanoseconds"); registerQuantilesGaugeIfNecessary(id, quantiles); return new DropwizardTimer(id, registry.timer(hierarchicalName(id)), clock, quantiles, registerHistogramCounterIfNecessary(id, histogram)); @@ -105,33 +101,19 @@ private Histogram registerHistogramCounterIfNecessary(Meter.Id id, Histogram. protected LongTaskTimer newLongTaskTimer(Meter.Id id) { LongTaskTimer ltt = new SimpleLongTaskTimer(id, clock); - registry.register(hierarchicalName(id) + ".active", (Gauge) ltt::activeTasks); - registry.register(hierarchicalName(id) + ".duration", (Gauge) () -> ltt.duration(TimeUnit.NANOSECONDS)); + registry.register(hierarchicalName(id.withTag(Statistic.ActiveTasks)), (Gauge) ltt::activeTasks); + registry.register(hierarchicalName(id.withTag(Statistic.Duration)), (Gauge) () -> ltt.duration(TimeUnit.NANOSECONDS)); return ltt; } - @Override - protected Meter newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnits) { - id.setBaseUnit("nanoseconds"); - FunctionTimer ft = new DefaultFunctionTimer<>(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnits, - TimeUnit.NANOSECONDS); - newMeter(id, Meter.Type.Timer, ft.measure()); - return ft; - } - @Override protected void newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { - measurements.forEach(ms -> { - Meter.Id idWithStatTag = createId(id.getName(), Tags.concat(id.getTags(), "statistic", ms.getStatistic().toString().toLowerCase()), - id.getDescription(), id.getBaseUnit()); - registry.register(hierarchicalName(idWithStatTag), (Gauge) ms::getValue); - }); + measurements.forEach(ms -> registry.register(hierarchicalName(id.withTag(ms.getStatistic())), (Gauge) ms::getValue)); } @Override - protected io.micrometer.core.instrument.Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction f) { - id.setBaseUnit("nanoseconds"); - return newGauge(id, obj, obj2 -> TimeUtils.convert(f.applyAsDouble(obj2), fUnit, TimeUnit.NANOSECONDS)); + protected TimeUnit getBaseTimeUnit() { + return TimeUnit.NANOSECONDS; } private String hierarchicalName(Meter.Id id) { diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/DefaultFunctionTimer.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/DefaultFunctionTimer.java index 065fbb97c3..2c7346a35a 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/DefaultFunctionTimer.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/DefaultFunctionTimer.java @@ -16,13 +16,10 @@ package io.micrometer.core.instrument.internal; import io.micrometer.core.instrument.FunctionTimer; -import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.Statistic; import io.micrometer.core.instrument.util.TimeUtils; import java.lang.ref.WeakReference; -import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.function.ToDoubleFunction; import java.util.function.ToLongFunction; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/MeterId.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/MeterId.java index 164291e8ce..648054abb3 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/MeterId.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/MeterId.java @@ -18,7 +18,9 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.NamingConvention; import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -45,6 +47,11 @@ public MeterId(String name, Iterable tags, String baseUnit, String descript this.description = description; } + @Override + public Meter.Id withTag(Tag tag) { + return new MeterId(name, Tags.concat(tags, Collections.singletonList(tag)), baseUnit, description); + } + @Override public String getName() { return name; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/TimedExecutor.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/TimedExecutor.java index f905870b5c..e000e1b12f 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/TimedExecutor.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/internal/TimedExecutor.java @@ -15,12 +15,12 @@ */ package io.micrometer.core.instrument.internal; -import java.util.concurrent.Executor; - import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Timer; +import java.util.concurrent.Executor; + /** * An {@link Executor} that is timed */ diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/noop/NoopMeter.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/noop/NoopMeter.java index e2d60cb4e5..742913b45b 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/noop/NoopMeter.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/noop/NoopMeter.java @@ -57,6 +57,11 @@ public List getConventionTags(NamingConvention convention) { return Collections.emptyList(); } + @Override + public Id withTag(Tag tag) { + return null; + } + @Override public void setType(Type type) { } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/simple/SimpleMeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/simple/SimpleMeterRegistry.java index 09c5a2b84d..37d9bd7391 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/simple/SimpleMeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/simple/SimpleMeterRegistry.java @@ -16,15 +16,12 @@ package io.micrometer.core.instrument.simple; import io.micrometer.core.instrument.*; -import io.micrometer.core.instrument.internal.DefaultFunctionTimer; import io.micrometer.core.instrument.stats.hist.Bucket; import io.micrometer.core.instrument.stats.hist.Histogram; import io.micrometer.core.instrument.stats.quantile.Quantiles; -import io.micrometer.core.instrument.util.TimeUtils; import java.util.concurrent.TimeUnit; import java.util.function.ToDoubleFunction; -import java.util.function.ToLongFunction; /** * A minimal meter registry implementation primarily used for tests. @@ -53,7 +50,6 @@ protected DistributionSummary newDistributionSummary(Meter.Id id, Histogram.Buil @Override protected io.micrometer.core.instrument.Timer newTimer(Meter.Id id, Histogram.Builder histogram, Quantiles quantiles) { - id.setBaseUnit("nanoseconds"); registerQuantilesGaugeIfNecessary(id, quantiles); return new SimpleTimer(id, config().clock(), quantiles, registerHistogramCounterIfNecessary(id, histogram)); } @@ -65,7 +61,6 @@ protected Gauge newGauge(Meter.Id id, T obj, ToDoubleFunction f) { @Override protected LongTaskTimer newLongTaskTimer(Meter.Id id) { - id.setBaseUnit("nanoseconds"); return new SimpleLongTaskTimer(id, config().clock()); } @@ -92,21 +87,13 @@ private Histogram registerHistogramCounterIfNecessary(Meter.Id id, Histogram. return null; } - @Override - protected Meter newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnits) { - id.setBaseUnit("nanoseconds"); - return new DefaultFunctionTimer<>(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnits, - TimeUnit.NANOSECONDS); - } - @Override protected void newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { // do nothing, the meter is already registered } @Override - protected io.micrometer.core.instrument.Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction f) { - id.setBaseUnit("nanoseconds"); - return newGauge(id, obj, obj2 -> TimeUtils.convert(f.applyAsDouble(obj2), fUnit, TimeUnit.NANOSECONDS)); + protected TimeUnit getBaseTimeUnit() { + return TimeUnit.NANOSECONDS; } } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorLongTaskTimer.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorLongTaskTimer.java index 4812e6175b..8587ba07a6 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorLongTaskTimer.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorLongTaskTimer.java @@ -15,14 +15,12 @@ */ package io.micrometer.core.instrument.spectator; -import ch.qos.logback.core.util.TimeUtil; import io.micrometer.core.instrument.AbstractMeter; import io.micrometer.core.instrument.LongTaskTimer; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.util.MeterEquivalence; import io.micrometer.core.instrument.util.TimeUtils; -import java.sql.Time; import java.util.concurrent.TimeUnit; public class SpectatorLongTaskTimer extends AbstractMeter implements LongTaskTimer { diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorMeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorMeterRegistry.java index 6670049ce6..06b85a0a47 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorMeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/SpectatorMeterRegistry.java @@ -20,18 +20,15 @@ import com.netflix.spectator.api.Measurement; import com.netflix.spectator.api.Registry; import io.micrometer.core.instrument.*; -import io.micrometer.core.instrument.internal.DefaultFunctionTimer; import io.micrometer.core.instrument.stats.hist.Bucket; import io.micrometer.core.instrument.stats.hist.BucketFilter; import io.micrometer.core.instrument.stats.hist.Histogram; import io.micrometer.core.instrument.stats.hist.PercentileTimeHistogram; import io.micrometer.core.instrument.stats.quantile.Quantiles; -import io.micrometer.core.instrument.util.TimeUtils; import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.function.ToDoubleFunction; -import java.util.function.ToLongFunction; import java.util.function.UnaryOperator; import java.util.stream.Stream; @@ -77,22 +74,12 @@ protected io.micrometer.core.instrument.DistributionSummary newDistributionSumma @Override protected io.micrometer.core.instrument.Timer newTimer(Meter.Id id, Histogram.Builder histogram, Quantiles quantiles) { // scale nanosecond precise quantile values to seconds - id.setBaseUnit("nanoseconds"); registerQuantilesGaugeIfNecessary(id, quantiles, t -> t / 1.0e6); com.netflix.spectator.api.Timer timer = registry.timer(getConventionName(id), toSpectatorTags(getConventionTags(id))); return new SpectatorTimer(id, timer, clock, quantiles, registerHistogramCounterIfNecessary(id, histogram)); } - @Override - protected Meter newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnits) { - id.setBaseUnit("nanoseconds"); - FunctionTimer ft = new DefaultFunctionTimer<>(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnits, - TimeUnit.NANOSECONDS); - newMeter(id, Meter.Type.Timer, ft.measure()); - return ft; - } - @Override protected io.micrometer.core.instrument.Gauge newGauge(Meter.Id id, T obj, ToDoubleFunction f) { Id gaugeId = registry.createId(getConventionName(id), toSpectatorTags(getConventionTags(id))); @@ -173,8 +160,7 @@ private static Id spectatorId(Registry registry, String name, Iterable tags } @Override - protected io.micrometer.core.instrument.Gauge newTimeGauge(Meter.Id id, T obj, TimeUnit fUnit, ToDoubleFunction f) { - id.setBaseUnit("nanoseconds"); - return newGauge(id, obj, obj2 -> TimeUtils.convert(f.applyAsDouble(obj2), fUnit, TimeUnit.NANOSECONDS)); + protected TimeUnit getBaseTimeUnit() { + return TimeUnit.NANOSECONDS; } } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/AbstractStepRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/AbstractStepRegistry.java index f454932dd1..2cb878a1c8 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/AbstractStepRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/AbstractStepRegistry.java @@ -71,7 +71,7 @@ public void start() { .withFrequency(Scheduler.Policy.FIXED_RATE_SKIP_IF_LONG, step) .withInitialDelay(Duration.ofMillis(getInitialDelay(stepMillis))) .withStopOnFailure(false); - scheduler = new Scheduler(this, "spring-metrics-publisher", numThreads); + scheduler = new Scheduler(this, "micrometer-publisher", numThreads); scheduler.schedule(options, this::pushMetrics); logger.info("started collecting metrics every {}", step); } else { diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/StepSpectatorMeterRegistry.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/StepSpectatorMeterRegistry.java index 848c4e22fb..5f1c228dd9 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/StepSpectatorMeterRegistry.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/spectator/step/StepSpectatorMeterRegistry.java @@ -19,7 +19,6 @@ import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.spectator.SpectatorMeterRegistry; import java.util.List; diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/util/HierarchicalNameMapper.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/util/HierarchicalNameMapper.java index c337136a78..57cb1882c7 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/util/HierarchicalNameMapper.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/util/HierarchicalNameMapper.java @@ -17,11 +17,7 @@ import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.NamingConvention; -import io.micrometer.core.instrument.Tag; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; import java.util.stream.Collectors; /** @@ -35,13 +31,16 @@ public interface HierarchicalNameMapper { * {@code http_server_requests.response.200.method.GET} */ HierarchicalNameMapper DEFAULT = (id, convention) -> { - List tagsCopy = new ArrayList<>(); - tagsCopy.addAll(id.getConventionTags(convention)); - tagsCopy.sort(Comparator.comparing(Tag::getKey)); - return id.getConventionName(convention) + "." + tagsCopy.stream() - .map(t -> t.getKey() + "." + t.getValue()) - .map(nameSegment -> nameSegment.replace(" ", "_")) - .collect(Collectors.joining(".")); + String tags = ""; + + if(id.getTags().iterator().hasNext()) { + tags = "." + id.getConventionTags(convention).stream() + .map(t -> t.getKey() + "." + t.getValue()) + .map(nameSegment -> nameSegment.replace(" ", "_")) + .collect(Collectors.joining(".")); + } + + return id.getConventionName(convention) + tags; }; String toHierarchicalName(Meter.Id id, NamingConvention convention); diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/IdTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/IdTest.java new file mode 100644 index 0000000000..d0aa55e1be --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/IdTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.core.instrument; + +import io.micrometer.core.instrument.internal.MeterId; +import org.junit.jupiter.api.Test; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; + +class IdTest { + @Test + void withStatistic() { + MeterId id = new MeterId("my.id", emptyList(), null, null); + assertThat(id.withTag(Statistic.TotalTime).getTags()).contains(Tag.of("statistic", "totalTime")); + } +} \ No newline at end of file diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineCacheMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineCacheMetricsTest.java index 1a29517496..77afec8b6e 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineCacheMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineCacheMetricsTest.java @@ -18,10 +18,8 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ClassLoaderMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ClassLoaderMetricsTest.java index 1429aabf61..c19d3570a6 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ClassLoaderMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ClassLoaderMetricsTest.java @@ -16,7 +16,6 @@ package io.micrometer.core.instrument.binder.jvm; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetricsTest.java index 51dd7e8ccd..20f0b20b35 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/ExecutorServiceMetricsTest.java @@ -16,21 +16,15 @@ package io.micrometer.core.instrument.binder.jvm; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.MeterRegistry.Search; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import static io.micrometer.core.instrument.Statistic.Count; import static io.micrometer.core.instrument.Statistic.Value; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/JvmThreadMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/JvmThreadMetricsTest.java index 950eb3eea5..1657a66b32 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/JvmThreadMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jvm/JvmThreadMetricsTest.java @@ -16,7 +16,6 @@ package io.micrometer.core.instrument.binder.jvm; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/logging/LogbackMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/logging/LogbackMetricsTest.java index a590a6ddc2..43b49a0d63 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/logging/LogbackMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/logging/LogbackMetricsTest.java @@ -18,7 +18,6 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.binder.logging.LogbackMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/ProcessorMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/ProcessorMetricsTest.java index 6125e1dc6e..23fe0f10fa 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/ProcessorMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/ProcessorMetricsTest.java @@ -16,7 +16,6 @@ package io.micrometer.core.instrument.binder.system; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.binder.system.ProcessorMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/UptimeMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/UptimeMetricsTest.java index 9615ff799b..7e1ae603e2 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/UptimeMetricsTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/system/UptimeMetricsTest.java @@ -17,7 +17,6 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Statistic; -import io.micrometer.core.instrument.binder.system.UptimeMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/util/HierarchicalNameMapperTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/util/HierarchicalNameMapperTest.java index c32c5fc4a2..a212b05b9e 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/util/HierarchicalNameMapperTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/util/HierarchicalNameMapperTest.java @@ -23,6 +23,7 @@ import java.util.List; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -30,55 +31,68 @@ * @author Jon Schneider */ class HierarchicalNameMapperTest { + private HierarchicalNameMapper mapper = HierarchicalNameMapper.DEFAULT; @Test void buildHierarchicalNameFromDimensionalId() { - HierarchicalNameMapper mapper = HierarchicalNameMapper.DEFAULT; String name = mapper.toHierarchicalName( - new Meter.Id() { - @Override - public String getName() { - fail("should not be used"); - return null; - } + createId("httpRequests", Tags.zip("method", "GET", "other", "With Spaces", "status", "200")), + NamingConvention.camelCase + ); + assertThat(name).isEqualTo("httpRequests.method.GET.other.With_Spaces.status.200"); + } - @Override - public Iterable getTags() { - fail("should not be used"); - return null; - } + private Meter.Id createId(String conventionName, List conventionTags) { + return new Meter.Id() { + @Override + public String getName() { + fail("should not be used"); + return null; + } - @Override - public String getBaseUnit() { - return null; - } + @Override + public Iterable getTags() { + return conventionTags; + } - @Override - public String getDescription() { - return null; - } + @Override + public String getBaseUnit() { + return null; + } - @Override - public String getConventionName(NamingConvention convention) { - return "httpRequests"; - } + @Override + public String getDescription() { + return null; + } - @Override - public List getConventionTags(NamingConvention convention) { - return Tags.zip("status", "200", "method", "GET", - "other", "With Spaces"); - } + @Override + public String getConventionName(NamingConvention convention) { + return conventionName; + } - @Override - public void setType(Meter.Type type) { - } + @Override + public List getConventionTags(NamingConvention convention) { + return conventionTags; + } - @Override - public void setBaseUnit(String baseUnit) { - } - }, - NamingConvention.snakeCase - ); - assertThat(name).isEqualTo("httpRequests.method.GET.other.With_Spaces.status.200"); + @Override + public Meter.Id withTag(Tag tag) { + return null; + } + + @Override + public void setType(Meter.Type type) { + } + + @Override + public void setBaseUnit(String baseUnit) { + } + }; + } + + @Test + void noTags() { + assertThat(mapper.toHierarchicalName(createId("httpRequests", emptyList()), NamingConvention.camelCase)) + .isEqualTo("httpRequests"); } } diff --git a/micrometer-samples/build.gradle b/micrometer-samples/build.gradle index 19d3f9f12c..950433d57e 100644 --- a/micrometer-samples/build.gradle +++ b/micrometer-samples/build.gradle @@ -4,7 +4,7 @@ dependencies { compile project(':micrometer-core') compile 'colt:colt:1.2.0' - ['atlas', 'prometheus', 'datadog', 'ganglia', 'graphite', 'jmx', 'influx'].each { sys -> + ['atlas', 'prometheus', 'datadog', 'ganglia', 'graphite', 'jmx', 'influx', 'statsd'].each { sys -> compile project(":micrometer-registry-$sys") } } \ No newline at end of file diff --git a/micrometer-samples/src/main/java/io/micrometer/core/samples/utils/SampleRegistries.java b/micrometer-samples/src/main/java/io/micrometer/core/samples/utils/SampleRegistries.java index 2ca4f96adc..fcb330edc0 100644 --- a/micrometer-samples/src/main/java/io/micrometer/core/samples/utils/SampleRegistries.java +++ b/micrometer-samples/src/main/java/io/micrometer/core/samples/utils/SampleRegistries.java @@ -17,8 +17,8 @@ import com.netflix.spectator.atlas.AtlasConfig; import com.sun.net.httpserver.HttpServer; -import io.micrometer.core.instrument.Clock; import io.micrometer.atlas.AtlasMeterRegistry; +import io.micrometer.core.instrument.Clock; import io.micrometer.datadog.DatadogConfig; import io.micrometer.datadog.DatadogMeterRegistry; import io.micrometer.ganglia.GangliaMeterRegistry; @@ -27,6 +27,9 @@ import io.micrometer.influx.InfluxMeterRegistry; import io.micrometer.jmx.JmxMeterRegistry; import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.statsd.StatsdConfig; +import io.micrometer.statsd.StatsdFlavor; +import io.micrometer.statsd.StatsdMeterRegistry; import java.io.IOException; import java.io.OutputStream; @@ -91,6 +94,20 @@ public String get(String k) { return new DatadogMeterRegistry(config); } + public static StatsdMeterRegistry datadogStatsd() { + return new StatsdMeterRegistry(new StatsdConfig() { + @Override + public String get(String k) { + return null; + } + + @Override + public StatsdFlavor flavor() { + return StatsdFlavor.Datadog; + } + }, Clock.SYSTEM); + } + public static GangliaMeterRegistry ganglia() { return new GangliaMeterRegistry(); } diff --git a/micrometer-spring-legacy/build.gradle b/micrometer-spring-legacy/build.gradle index 0a543152fe..e6d17ee0de 100644 --- a/micrometer-spring-legacy/build.gradle +++ b/micrometer-spring-legacy/build.gradle @@ -11,6 +11,7 @@ dependencyManagement { } dependencies { dependency 'org.assertj:assertj-core:3.8.0' + dependency 'io.projectreactor:reactor-core:3.1.0.RELEASE' } } @@ -35,7 +36,7 @@ dependencies { compile 'javax.servlet:javax.servlet-api:3.1.0', optional compile 'org.aspectj:aspectjweaver:1.8.+', optional - ['atlas', 'prometheus', 'datadog', 'ganglia', 'graphite', 'jmx', 'influx'].each { sys -> + ['atlas', 'prometheus', 'datadog', 'ganglia', 'graphite', 'jmx', 'influx', 'statsd'].each { sys -> compile project(":micrometer-registry-$sys"), optional } diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MeterBindersConfiguration.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MeterBindersConfiguration.java index aa5f05a25f..d7f1fcb0a9 100644 --- a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MeterBindersConfiguration.java +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MeterBindersConfiguration.java @@ -15,9 +15,9 @@ */ package io.micrometer.spring.autoconfigure; +import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; import io.micrometer.core.instrument.binder.logging.LogbackMetrics; -import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.system.UptimeMetrics; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MetricsAutoConfiguration.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MetricsAutoConfiguration.java index c81dcd50d2..8301fc86b5 100644 --- a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MetricsAutoConfiguration.java +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/MetricsAutoConfiguration.java @@ -29,6 +29,7 @@ import io.micrometer.spring.autoconfigure.export.jmx.JmxExportConfiguration; import io.micrometer.spring.autoconfigure.export.prometheus.PrometheusExportConfiguration; import io.micrometer.spring.autoconfigure.export.simple.SimpleExportConfiguration; +import io.micrometer.spring.autoconfigure.export.statsd.StatsdExportConfiguration; import io.micrometer.spring.autoconfigure.web.client.RestTemplateMetricsConfiguration; import io.micrometer.spring.autoconfigure.web.servlet.WebMvcMetricsConfiguration; import io.micrometer.spring.integration.SpringIntegrationMetrics; @@ -57,9 +58,10 @@ @ConditionalOnClass(Timed.class) @EnableConfigurationProperties(MetricsProperties.class) @Import({MeterBindersConfiguration.class, WebMvcMetricsConfiguration.class, - RestTemplateMetricsConfiguration.class, AtlasExportConfiguration.class, DatadogExportConfiguration.class, - GangliaExportConfiguration.class, GraphiteExportConfiguration.class, - InfluxExportConfiguration.class, JmxExportConfiguration.class, + RestTemplateMetricsConfiguration.class, AtlasExportConfiguration.class, + DatadogExportConfiguration.class, GangliaExportConfiguration.class, + GraphiteExportConfiguration.class, InfluxExportConfiguration.class, + JmxExportConfiguration.class, StatsdExportConfiguration.class, PrometheusExportConfiguration.class, SimpleExportConfiguration.class}) public class MetricsAutoConfiguration { diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasExportConfiguration.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasExportConfiguration.java index 3f1a1d114a..bfd826ed9b 100644 --- a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasExportConfiguration.java +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasExportConfiguration.java @@ -20,7 +20,6 @@ import io.micrometer.core.instrument.Clock; import io.micrometer.spring.autoconfigure.export.DefaultStepRegistryConfig; import io.micrometer.spring.autoconfigure.export.MetricsExporter; -import io.micrometer.spring.autoconfigure.export.StepRegistryProperties; import io.micrometer.spring.autoconfigure.export.StringToDurationConverter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasProperties.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasProperties.java index 7338a1789e..e3193b4a33 100644 --- a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasProperties.java +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/atlas/AtlasProperties.java @@ -15,15 +15,10 @@ */ package io.micrometer.spring.autoconfigure.export.atlas; -import com.netflix.spectator.atlas.AtlasConfig; import io.micrometer.spring.autoconfigure.export.StepRegistryProperties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; import java.time.Duration; -import java.util.HashMap; -import java.util.Map; /** * {@link ConfigurationProperties} for configuring Atlas metrics export. diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdExportConfiguration.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdExportConfiguration.java new file mode 100644 index 0000000000..3b3a37544e --- /dev/null +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdExportConfiguration.java @@ -0,0 +1,111 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.spring.autoconfigure.export.statsd; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.spring.autoconfigure.export.MetricsExporter; +import io.micrometer.spring.autoconfigure.export.StringToDurationConverter; +import io.micrometer.statsd.StatsdConfig; +import io.micrometer.statsd.StatsdFlavor; +import io.micrometer.statsd.StatsdMeterRegistry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.time.Duration; + +/** + * Configuration for exporting metrics to a StatsD agent. + * + * @author Jon Schneider + */ +@Configuration +@ConditionalOnClass(StatsdMeterRegistry.class) +@Import(StringToDurationConverter.class) +@EnableConfigurationProperties(StatsdProperties.class) +public class StatsdExportConfiguration { + + private class DefaultStatsdConfig implements StatsdConfig { + private final StatsdProperties props; + private final StatsdConfig defaults = k -> null; + + private DefaultStatsdConfig(StatsdProperties props) { + this.props = props; + } + + @Override + public String get(String k) { + return null; + } + + @Override + public StatsdFlavor flavor() { + return props.getFlavor() == null ? defaults.flavor() : props.getFlavor(); + } + + @Override + public boolean enabled() { + return props.getEnabled(); + } + + @Override + public String host() { + return props.getHost() == null ? defaults.host() : props.getHost(); + } + + @Override + public int port() { + return props.getPort() == null ? defaults.port() : props.getPort(); + } + + @Override + public int maxPacketLength() { + return props.getMaxPacketLength() == null ? defaults.maxPacketLength() : props.getMaxPacketLength(); + } + + @Override + public Duration pollingFrequency() { + return props.getPollingFrequency() == null ? defaults.pollingFrequency() : props.getPollingFrequency(); + } + + @Override + public int queueSize() { + return props.getQueueSize() == null ? defaults.queueSize() : props.getQueueSize(); + } + } + + @Bean + @ConditionalOnMissingBean(StatsdConfig.class) + public StatsdConfig statsdConfig(StatsdProperties props) { + return new DefaultStatsdConfig(props); + } + + @Bean + @ConditionalOnProperty(value = "spring.metrics.statsd.enabled", matchIfMissing = true) + public MetricsExporter statsdExporter(StatsdConfig config, Clock clock) { + return () -> new StatsdMeterRegistry(config, clock); + } + + @Bean + @ConditionalOnMissingBean + public Clock clock() { + return Clock.SYSTEM; + } +} diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdProperties.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdProperties.java new file mode 100644 index 0000000000..fb8bd0da84 --- /dev/null +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/autoconfigure/export/statsd/StatsdProperties.java @@ -0,0 +1,149 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.spring.autoconfigure.export.statsd; + +import io.micrometer.statsd.StatsdFlavor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.time.Duration; + +/** + * {@link ConfigurationProperties} for configuring Influx metrics export. + * + * @author Jon Schneider + */ +@ConfigurationProperties(prefix = "spring.metrics.statsd") +public class StatsdProperties { + /** + * Enable publishing to the backend. + */ + private Boolean enabled = true; + + /** + * Choose which variant of the StatsD line protocol to use. + */ + private StatsdFlavor flavor = StatsdFlavor.Datadog; + + /** + * The host name of the StatsD agent. + */ + private String host = "localhost"; + + /** + * The UDP port of the StatsD agent. + */ + private Integer port = 8125; + + /** + * The total length of a single payload should be kept within your network's MTU. + */ + private Integer maxPacketLength = 1400; + + /** + * 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. + */ + private Duration pollingFrequency = Duration.ofSeconds(10); + + /** + * Governs the maximum size of the queue of items waiting to be sent to a StatsD agent over UDP. + */ + private Integer queueSize = Integer.MAX_VALUE; + + /** + * Used to create a bucket filter clamping the bucket domain of timer percentiles histograms to some max value. + * This is used to limit the number of buckets shipped to Prometheus to save on storage. + */ + private Duration timerPercentilesMax = Duration.ofMinutes(2); + + /** + * Used to create a bucket filter clamping the bucket domain of timer percentiles histograms to some min value. + * This is used to limit the number of buckets shipped to Prometheus to save on storage. + */ + private Duration timerPercentilesMin = Duration.ofMillis(10); + + public Duration getTimerPercentilesMax() { + return timerPercentilesMax; + } + + public void setTimerPercentilesMax(Duration timerPercentilesMax) { + this.timerPercentilesMax = timerPercentilesMax; + } + + public Duration getTimerPercentilesMin() { + return timerPercentilesMin; + } + + public void setTimerPercentilesMin(Duration timerPercentilesMin) { + this.timerPercentilesMin = timerPercentilesMin; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public StatsdFlavor getFlavor() { + return flavor; + } + + public void setFlavor(StatsdFlavor flavor) { + this.flavor = flavor; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Integer getMaxPacketLength() { + return maxPacketLength; + } + + public void setMaxPacketLength(Integer maxPacketLength) { + this.maxPacketLength = maxPacketLength; + } + + public Duration getPollingFrequency() { + return pollingFrequency; + } + + public void setPollingFrequency(Duration pollingFrequency) { + this.pollingFrequency = pollingFrequency; + } + + public Integer getQueueSize() { + return queueSize; + } + + public void setQueueSize(Integer queueSize) { + this.queueSize = queueSize; + } +} diff --git a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/jdbc/DataSourceMetrics.java b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/jdbc/DataSourceMetrics.java index 735b04229e..2464a8db0c 100644 --- a/micrometer-spring-legacy/src/main/java/io/micrometer/spring/jdbc/DataSourceMetrics.java +++ b/micrometer-spring-legacy/src/main/java/io/micrometer/spring/jdbc/DataSourceMetrics.java @@ -23,7 +23,6 @@ import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProviders; import javax.sql.DataSource; -import java.util.ArrayList; import java.util.Collection; /** diff --git a/micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdDatadogSample.java b/micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdDatadogSample.java new file mode 100644 index 0000000000..8d0b8e8dd8 --- /dev/null +++ b/micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdDatadogSample.java @@ -0,0 +1,28 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.spring.samples; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication(scanBasePackages = "io.micrometer.spring.samples.components") +@EnableScheduling +public class StatsdDatadogSample { + public static void main(String[] args) { + new SpringApplicationBuilder(AtlasSample.class).profiles("statsd-datadog").run(args); + } +} diff --git a/micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdTelegrafSample.java b/micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdTelegrafSample.java new file mode 100644 index 0000000000..c05b19a9fe --- /dev/null +++ b/micrometer-spring-legacy/src/samples/java/io/micrometer/spring/samples/StatsdTelegrafSample.java @@ -0,0 +1,28 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * 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 + *

+ * 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 io.micrometer.spring.samples; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication(scanBasePackages = "io.micrometer.spring.samples.components") +@EnableScheduling +public class StatsdTelegrafSample { + public static void main(String[] args) { + new SpringApplicationBuilder(AtlasSample.class).profiles("statsd-telegraf").run(args); + } +} diff --git a/micrometer-spring-legacy/src/samples/resources/application-statsd-datadog.yml b/micrometer-spring-legacy/src/samples/resources/application-statsd-datadog.yml new file mode 100644 index 0000000000..8b4c0495bd --- /dev/null +++ b/micrometer-spring-legacy/src/samples/resources/application-statsd-datadog.yml @@ -0,0 +1,19 @@ +# +# Copyright 2017 Pivotal Software, Inc. +#

+# 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 +#

+# 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. +# + +spring.metrics.statsd: + enabled: true + flavor: datadog \ No newline at end of file diff --git a/micrometer-spring-legacy/src/samples/resources/application-statsd-telegraf.yml b/micrometer-spring-legacy/src/samples/resources/application-statsd-telegraf.yml new file mode 100644 index 0000000000..8ddb382961 --- /dev/null +++ b/micrometer-spring-legacy/src/samples/resources/application-statsd-telegraf.yml @@ -0,0 +1,19 @@ +# +# Copyright 2017 Pivotal Software, Inc. +#

+# 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 +#

+# 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. +# + +spring.metrics.statsd: + enabled: true + flavor: telegraf \ No newline at end of file diff --git a/micrometer-spring-legacy/src/samples/resources/application.yml b/micrometer-spring-legacy/src/samples/resources/application.yml index e4f39e7815..b2deaa3058 100644 --- a/micrometer-spring-legacy/src/samples/resources/application.yml +++ b/micrometer-spring-legacy/src/samples/resources/application.yml @@ -19,6 +19,7 @@ management.security.enabled: false # see https://stackoverflow.com/questions/44474160/spring-integration-display-warning-that-header-is-ignored-for-population-because logging.level.org.springframework.integration.support.MessageBuilder: WARN +logging.level.org.springframework.boot: INFO spring.metrics: atlas.enabled: false @@ -28,6 +29,7 @@ spring.metrics: influx.enabled: false jmx.enabled: false prometheus.enabled: false + statsd.enabled: false # % of the time, blow up on the Roulette endpoint roulette.frequency: 0.8 \ No newline at end of file diff --git a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/MetricsAutoConfigurationTest.java b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/MetricsAutoConfigurationTest.java index 6d2ed913d2..34cb66d54b 100644 --- a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/MetricsAutoConfigurationTest.java +++ b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/MetricsAutoConfigurationTest.java @@ -17,9 +17,9 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; import io.micrometer.core.instrument.binder.logging.LogbackMetrics; -import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/export/simple/SimpleExportConfigurationTest.java b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/export/simple/SimpleExportConfigurationTest.java index 198effac78..ec1a1ad1f9 100644 --- a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/export/simple/SimpleExportConfigurationTest.java +++ b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/autoconfigure/export/simple/SimpleExportConfigurationTest.java @@ -39,7 +39,8 @@ "spring.metrics.ganglia.enabled=false", "spring.metrics.graphite.enabled=false", "spring.metrics.influx.enabled=false", - "spring.metrics.jmx.enabled=false" + "spring.metrics.jmx.enabled=false", + "spring.metrics.statsd.enabled=false", }) public class SimpleExportConfigurationTest { @Autowired diff --git a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/jdbc/DataSourceMetricsHikariTest.java b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/jdbc/DataSourceMetricsHikariTest.java index 8ccd7336d0..81bb4228c2 100644 --- a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/jdbc/DataSourceMetricsHikariTest.java +++ b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/jdbc/DataSourceMetricsHikariTest.java @@ -30,7 +30,6 @@ import org.springframework.test.context.junit4.SpringRunner; import javax.sql.DataSource; - import java.sql.SQLException; import java.util.Collection; diff --git a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/scheduling/ScheduledMethodMetricsTest.java b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/scheduling/ScheduledMethodMetricsTest.java index 8cf7b50de4..a366ee5275 100644 --- a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/scheduling/ScheduledMethodMetricsTest.java +++ b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/scheduling/ScheduledMethodMetricsTest.java @@ -25,19 +25,15 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.context.web.WebAppConfiguration; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import static io.micrometer.core.instrument.Statistic.Count; -import static io.micrometer.core.instrument.Statistic.Total; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) diff --git a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/MetricsHandlerInterceptorAutoTimedTest.java b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/MetricsHandlerInterceptorAutoTimedTest.java index 318c7c369a..b5fea308e3 100644 --- a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/MetricsHandlerInterceptorAutoTimedTest.java +++ b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/MetricsHandlerInterceptorAutoTimedTest.java @@ -35,7 +35,6 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import static org.assertj.core.api.Assertions.assertThat; diff --git a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/WebMvcMetricsIntegrationTest.java b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/WebMvcMetricsIntegrationTest.java index d6c24295a0..a2e71de9e9 100644 --- a/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/WebMvcMetricsIntegrationTest.java +++ b/micrometer-spring-legacy/src/test/java/io/micrometer/spring/web/servlet/WebMvcMetricsIntegrationTest.java @@ -35,7 +35,6 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import static org.assertj.core.api.Assertions.assertThat; diff --git a/micrometer-test/src/main/java/io/micrometer/core/tck/MeterRegistryCompatibilityKit.java b/micrometer-test/src/main/java/io/micrometer/core/tck/MeterRegistryCompatibilityKit.java index 5e300644e5..041718861c 100644 --- a/micrometer-test/src/main/java/io/micrometer/core/tck/MeterRegistryCompatibilityKit.java +++ b/micrometer-test/src/main/java/io/micrometer/core/tck/MeterRegistryCompatibilityKit.java @@ -20,10 +20,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.extension.ExtendWith; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Collections; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -34,7 +32,6 @@ import static io.micrometer.core.instrument.Statistic.Total; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; /** * @author Jon Schneider @@ -55,7 +52,7 @@ void uniqueMeters(MeterRegistry registry) { registry.counter("foo"); registry.counter("foo"); - assertThat(registry.getMeters().size()).isEqualTo(1); + assertThat(registry.find("foo").meters().size()).isEqualTo(1); } @Test diff --git a/micrometer-test/src/test/java/io/micrometer/core/instrument/simple/SimpleMeterRegistryCompatibilityTest.java b/micrometer-test/src/test/java/io/micrometer/core/instrument/simple/SimpleMeterRegistryCompatibilityTest.java index 8defa17ba4..820acb0b6e 100644 --- a/micrometer-test/src/test/java/io/micrometer/core/instrument/simple/SimpleMeterRegistryCompatibilityTest.java +++ b/micrometer-test/src/test/java/io/micrometer/core/instrument/simple/SimpleMeterRegistryCompatibilityTest.java @@ -15,11 +15,9 @@ */ package io.micrometer.core.instrument.simple; +import io.micrometer.core.MockClock; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.tck.MeterRegistryCompatibilityKit; -import io.micrometer.core.MockClock; -import io.micrometer.core.tck.RegistryResolver; -import org.junit.jupiter.api.extension.ExtendWith; public class SimpleMeterRegistryCompatibilityTest extends MeterRegistryCompatibilityKit { @Override diff --git a/scripts/.gitignore b/scripts/.gitignore index 7300ac4eb9..83bc7d9f9e 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,2 +1,3 @@ .atlas/ -.ganglia/ \ No newline at end of file +.ganglia/ +.telegraf/ \ No newline at end of file diff --git a/scripts/grafana.sh b/scripts/grafana.sh new file mode 100755 index 0000000000..2d77f0ebbd --- /dev/null +++ b/scripts/grafana.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +`docker run -i -p 3000:3000 grafana/grafana` \ No newline at end of file diff --git a/scripts/statsd-telegraf.sh b/scripts/statsd-telegraf.sh new file mode 100755 index 0000000000..ac347fc364 --- /dev/null +++ b/scripts/statsd-telegraf.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ ! -f .telegraf/tele.conf ]; then + echo "Initializing the telegraf config. If you need to point to an influx instance not on localhost, modify ./telegraf/tele.conf" + mkdir .telegraf + telegraf --input-filter statsd --output-filter influxdb config > ./.telegraf/tele.conf +fi + +telegraf -config ./.telegraf/tele.conf \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7f2734376a..1db9ca98c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ include 'micrometer-spring-legacy' include 'micrometer-samples' include 'micrometer-test' -['atlas', 'prometheus', 'datadog', 'ganglia', 'graphite', 'jmx', 'influx'].each { sys -> +['atlas', 'prometheus', 'datadog', 'ganglia', 'graphite', 'jmx', 'influx', 'statsd'].each { sys -> include "micrometer-registry-$sys" project(":micrometer-registry-$sys").projectDir = new File(rootProject.projectDir, "implementations/micrometer-registry-$sys") }