diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterBindersConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterBindersConfiguration.java index 7617ead8811..95842d4b797 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterBindersConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterBindersConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics; 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.system.ProcessorMetrics; import io.micrometer.core.instrument.binder.system.UptimeMetrics; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -53,4 +54,9 @@ class MeterBindersConfiguration { return new UptimeMetrics(); } + @Bean + @ConditionalOnMissingBean(ProcessorMetrics.class) + public ProcessorMetrics processorMetrics() { + return new ProcessorMetrics(); + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryProperties.java index 2a56b1fe5a1..b3b83a7be90 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryProperties.java @@ -67,7 +67,7 @@ public abstract class StepRegistryProperties { this.step = step; } - public Boolean getEnabled() { + public Boolean isEnabled() { return this.enabled; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryPropertiesConfigAdapter.java index cf2fde0bdac..aede43227ba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/StepRegistryPropertiesConfigAdapter.java @@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export; import java.time.Duration; -import io.micrometer.core.instrument.spectator.step.StepRegistryConfig; +import io.micrometer.core.instrument.step.StepRegistryConfig; /** * Base class for {@link StepRegistryProperties} to {@link StepRegistryConfig} adapters. @@ -53,7 +53,7 @@ public abstract class StepRegistryPropertiesConfigAdapter { - - @Override - public Duration convert(String source) { - return Duration.parse(source); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasExportConfiguration.java index ccdce6adefc..8530caa3491 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasExportConfiguration.java @@ -21,14 +21,12 @@ import io.micrometer.atlas.AtlasMeterRegistry; import io.micrometer.core.instrument.Clock; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter; 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; /** * Configuration for exporting metrics to Atlas. @@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import; */ @Configuration @ConditionalOnClass(AtlasMeterRegistry.class) -@Import(StringToDurationConverter.class) @EnableConfigurationProperties(AtlasProperties.class) public class AtlasExportConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasPropertiesConfigAdapter.java index 4e1c6d0c25f..b065d6b12a7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasPropertiesConfigAdapter.java @@ -49,7 +49,7 @@ class AtlasPropertiesConfigAdapter extends @Override public boolean enabled() { - return get(AtlasProperties::getEnabled, AtlasConfig::enabled); + return get(AtlasProperties::isEnabled, AtlasConfig::enabled); } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogExportConfiguration.java index b4e9ab2adb2..04b07c19764 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogExportConfiguration.java @@ -21,14 +21,12 @@ import io.micrometer.datadog.DatadogConfig; import io.micrometer.datadog.DatadogMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter; 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; /** * Configuration for exporting metrics to Datadog. @@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import; @Configuration @ConditionalOnClass(DatadogMeterRegistry.class) @ConditionalOnProperty("spring.metrics.datadog.api-key") -@Import(StringToDurationConverter.class) @EnableConfigurationProperties(DatadogProperties.class) public class DatadogExportConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java index 626304bb379..4228086b0fd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.datadog; -import java.time.Duration; - import org.springframework.boot.actuate.autoconfigure.metrics.export.StepRegistryProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -43,18 +41,10 @@ public class DatadogProperties extends StepRegistryProperties { private String hostTag; /** - * The 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); - - /** - * The 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. + * The URI to ship metrics to. If you need to publish metrics to an internal proxy en route to + * datadoghq, you can define the location of the proxy with this. */ - private Duration timerPercentilesMin = Duration.ofMillis(10); + private String uri; public String getApiKey() { return this.apiKey; @@ -72,19 +62,11 @@ public class DatadogProperties extends StepRegistryProperties { this.hostTag = hostKey; } - public Duration getTimerPercentilesMax() { - return this.timerPercentilesMax; - } - - public void setTimerPercentilesMax(Duration timerPercentilesMax) { - this.timerPercentilesMax = timerPercentilesMax; - } - - public Duration getTimerPercentilesMin() { - return this.timerPercentilesMin; + public String getUri() { + return this.uri; } - public void setTimerPercentilesMin(Duration timerPercentilesMin) { - this.timerPercentilesMin = timerPercentilesMin; + public void setUri(String uri) { + this.uri = uri; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java index 6066810d981..a7079f6e0ca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.datadog; -import java.time.Duration; - import io.micrometer.datadog.DatadogConfig; import org.springframework.boot.actuate.autoconfigure.metrics.export.StepRegistryPropertiesConfigAdapter; @@ -49,15 +47,7 @@ class DatadogPropertiesConfigAdapter } @Override - public Duration timerPercentilesMax() { - return get(DatadogProperties::getTimerPercentilesMax, - DatadogConfig::timerPercentilesMax); + public String uri() { + return get(DatadogProperties::getUri, DatadogConfig::uri); } - - @Override - public Duration timerPercentilesMin() { - return get(DatadogProperties::getTimerPercentilesMin, - DatadogConfig::timerPercentilesMin); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaExportConfiguration.java index d0897c84d72..db6b10bfa7f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaExportConfiguration.java @@ -22,14 +22,12 @@ import io.micrometer.ganglia.GangliaConfig; import io.micrometer.ganglia.GangliaMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter; 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; /** * Configuration for exporting metrics to Ganglia. @@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import; */ @Configuration @ConditionalOnClass(GangliaMeterRegistry.class) -@Import(StringToDurationConverter.class) @EnableConfigurationProperties(GangliaProperties.class) public class GangliaExportConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaProperties.java index 1ca2a0f959f..664ac081502 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaProperties.java @@ -77,7 +77,7 @@ public class GangliaProperties { */ private Integer port; - public Boolean getEnabled() { + public Boolean isEnabled() { return this.enabled; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaPropertiesConfigAdapter.java index 6499905b4ff..8746a888a0a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/ganglia/GangliaPropertiesConfigAdapter.java @@ -47,7 +47,7 @@ class GangliaPropertiesConfigAdapter @Override public boolean enabled() { - return get(GangliaProperties::getEnabled, GangliaConfig::enabled); + return get(GangliaProperties::isEnabled, GangliaConfig::enabled); } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteExportConfiguration.java index a4a264cc9e7..0e6832b3ce6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteExportConfiguration.java @@ -22,14 +22,12 @@ import io.micrometer.graphite.GraphiteConfig; import io.micrometer.graphite.GraphiteMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter; 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; /** * Configuration for exporting metrics to Graphite. @@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import; */ @Configuration @ConditionalOnClass(GraphiteMeterRegistry.class) -@Import(StringToDurationConverter.class) @EnableConfigurationProperties(GraphiteProperties.class) public class GraphiteExportConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteProperties.java index c210e0d3c02..e1e09292435 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphiteProperties.java @@ -67,7 +67,7 @@ public class GraphiteProperties { */ private GraphiteProtocol protocol = GraphiteProtocol.Pickled; - public Boolean getEnabled() { + public Boolean isEnabled() { return this.enabled; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphitePropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphitePropertiesConfigAdapter.java index d1e4fac0457..d44fd6f6f68 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphitePropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/graphite/GraphitePropertiesConfigAdapter.java @@ -47,7 +47,7 @@ class GraphitePropertiesConfigAdapter @Override public boolean enabled() { - return get(GraphiteProperties::getEnabled, GraphiteConfig::enabled); + return get(GraphiteProperties::isEnabled, GraphiteConfig::enabled); } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxExportConfiguration.java index a9d645e9265..8b31b410fb5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxExportConfiguration.java @@ -21,14 +21,12 @@ import io.micrometer.influx.InfluxConfig; import io.micrometer.influx.InfluxMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter; 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; /** * Configuration for exporting metrics to Influx. @@ -38,7 +36,6 @@ import org.springframework.context.annotation.Import; */ @Configuration @ConditionalOnClass(InfluxMeterRegistry.class) -@Import(StringToDurationConverter.class) @EnableConfigurationProperties(InfluxProperties.class) public class InfluxExportConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxProperties.java index dff4983c3df..e7178ae0f8e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxProperties.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.influx; -import java.time.Duration; - import io.micrometer.influx.InfluxConsistency; import org.springframework.boot.actuate.autoconfigure.metrics.export.StepRegistryProperties; @@ -68,20 +66,6 @@ public class InfluxProperties extends StepRegistryProperties { */ private Boolean compressed; - /** - * The 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); - - /** - * The 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 String getDb() { return this.db; } @@ -137,20 +121,4 @@ public class InfluxProperties extends StepRegistryProperties { public void setCompressed(Boolean compressed) { this.compressed = compressed; } - - public Duration getTimerPercentilesMax() { - return this.timerPercentilesMax; - } - - public void setTimerPercentilesMax(Duration timerPercentilesMax) { - this.timerPercentilesMax = timerPercentilesMax; - } - - public Duration getTimerPercentilesMin() { - return this.timerPercentilesMin; - } - - public void setTimerPercentilesMin(Duration timerPercentilesMin) { - this.timerPercentilesMin = timerPercentilesMin; - } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxPropertiesConfigAdapter.java index eb68b172965..07c2bc634a1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/influx/InfluxPropertiesConfigAdapter.java @@ -73,17 +73,4 @@ class InfluxPropertiesConfigAdapter public boolean compressed() { return get(InfluxProperties::getCompressed, InfluxConfig::compressed); } - - @Override - public Duration timerPercentilesMax() { - return get(InfluxProperties::getTimerPercentilesMax, - InfluxConfig::timerPercentilesMax); - } - - @Override - public Duration timerPercentilesMin() { - return get(InfluxProperties::getTimerPercentilesMin, - InfluxConfig::timerPercentilesMin); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java index d655daad4e7..b8d5ce37d4d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java @@ -41,20 +41,11 @@ public class PrometheusProperties { private Boolean descriptions = true; /** - * The 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. + * The step size (reporting frequency) to use. */ - private Duration timerPercentilesMax = Duration.ofMinutes(2); + private Duration step = Duration.ofMinutes(1); - /** - * The 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 Boolean getEnabled() { + public Boolean isEnabled() { return this.enabled; } @@ -70,20 +61,11 @@ public class PrometheusProperties { this.descriptions = descriptions; } - public Duration getTimerPercentilesMax() { - return this.timerPercentilesMax; - } - - public void setTimerPercentilesMax(Duration timerPercentilesMax) { - this.timerPercentilesMax = timerPercentilesMax; - } - - public Duration getTimerPercentilesMin() { - return this.timerPercentilesMin; + public Duration getStep() { + return this.step; } - public void setTimerPercentilesMin(Duration timerPercentilesMin) { - this.timerPercentilesMin = timerPercentilesMin; + public void setStep(Duration step) { + this.step = step; } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java index d153c3be157..05f2fb42350 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusPropertiesConfigAdapter.java @@ -49,15 +49,7 @@ class PrometheusPropertiesConfigAdapter } @Override - public Duration timerPercentilesMin() { - return get(PrometheusProperties::getTimerPercentilesMin, - PrometheusConfig::timerPercentilesMin); + public Duration step() { + return get(PrometheusProperties::getStep, PrometheusConfig::step); } - - @Override - public Duration timerPercentilesMax() { - return get(PrometheusProperties::getTimerPercentilesMax, - PrometheusConfig::timerPercentilesMax); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfiguration.java index 0f66417cd20..8e7d5f8c4ed 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.simple; import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; @@ -39,8 +40,8 @@ public class SimpleExportConfiguration { @Bean @ConditionalOnProperty(value = "spring.metrics.export.simple.enabled", matchIfMissing = true) @ConditionalOnMissingBean(MetricsExporter.class) - public MetricsExporter simpleExporter(Clock clock) { - return () -> new SimpleMeterRegistry(clock); + public MetricsExporter simpleExporter(SimpleConfig config, Clock clock) { + return () -> new SimpleMeterRegistry(config, clock); } @Bean @@ -49,4 +50,9 @@ public class SimpleExportConfiguration { return Clock.SYSTEM; } + @Bean + @ConditionalOnMissingBean(SimpleConfig.class) + public SimpleConfig simpleConfig(SimpleProperties simpleProperties) { + return new SimplePropertiesConfigAdapter(simpleProperties); + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleProperties.java index efe07257a5b..93c568f3c51 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleProperties.java @@ -16,10 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.simple; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import java.time.Duration; import org.springframework.boot.context.properties.ConfigurationProperties; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; + /** * {@link ConfigurationProperties} for configuring metrics export to a * {@link SimpleMeterRegistry}. @@ -30,8 +32,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.metrics.export.simple") public class SimpleProperties { + /** + * Enable publishing to the backend. + */ private boolean enabled = true; + /** + * The step size (reporting frequency) to use. + */ + private Duration step = Duration.ofSeconds(10); + public boolean isEnabled() { return this.enabled; } @@ -40,4 +50,11 @@ public class SimpleProperties { this.enabled = enabled; } + public Duration getStep() { + return this.step; + } + + public void setStep(Duration step) { + this.step = step; + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimplePropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimplePropertiesConfigAdapter.java new file mode 100644 index 00000000000..c21b3c91db5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimplePropertiesConfigAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * 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 org.springframework.boot.actuate.autoconfigure.metrics.export.simple; + +import java.time.Duration; + +import io.micrometer.core.instrument.simple.SimpleConfig; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter; + + +/** + * Adapter to convert {@link SimpleProperties} to a {@link SimpleConfig}. + * + * @author Jon Schneider + * @since 2.0.0 + */ +public class SimplePropertiesConfigAdapter extends + PropertiesConfigAdapter implements SimpleConfig { + private static final SimpleConfig DEFAULTS = (key) -> null; + + public SimplePropertiesConfigAdapter(SimpleProperties properties) { + super(properties, DEFAULTS); + } + + @Override + public String get(String k) { + return null; + } + + @Override + public boolean enabled() { + return get(SimpleProperties::isEnabled, SimpleConfig::enabled); + } + + @Override + public Duration step() { + return get(SimpleProperties::getStep, SimpleConfig::step); + } +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdExportConfiguration.java index 6048d9fa215..d1de35d5e60 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdExportConfiguration.java @@ -17,18 +17,17 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.statsd; import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.util.HierarchicalNameMapper; import io.micrometer.statsd.StatsdConfig; import io.micrometer.statsd.StatsdMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; -import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter; 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; /** * Configuration for exporting metrics to StatsD. @@ -38,7 +37,6 @@ import org.springframework.context.annotation.Import; */ @Configuration @ConditionalOnClass(StatsdMeterRegistry.class) -@Import(StringToDurationConverter.class) @EnableConfigurationProperties(StatsdProperties.class) public class StatsdExportConfiguration { @@ -50,8 +48,8 @@ public class StatsdExportConfiguration { @Bean @ConditionalOnProperty(value = "spring.metrics.export.statsd.enabled", matchIfMissing = true) - public MetricsExporter statsdExporter(StatsdConfig statsdConfig, Clock clock) { - return () -> new StatsdMeterRegistry(statsdConfig, clock); + public MetricsExporter statsdExporter(StatsdConfig statsdConfig, HierarchicalNameMapper hierarchicalNameMapper, Clock clock) { + return () -> new StatsdMeterRegistry(statsdConfig, hierarchicalNameMapper, clock); } @Bean @@ -60,4 +58,9 @@ public class StatsdExportConfiguration { return Clock.SYSTEM; } + @Bean + @ConditionalOnMissingBean + public HierarchicalNameMapper hierarchicalNameMapper() { + return HierarchicalNameMapper.DEFAULT; + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdProperties.java index 0d7519f0980..949b79e7f07 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdProperties.java @@ -68,37 +68,7 @@ public class StatsdProperties { */ 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 StatsD 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 StatsD to save on storage. - */ - private Duration timerPercentilesMin = Duration.ofMillis(10); - - public Duration getTimerPercentilesMax() { - return this.timerPercentilesMax; - } - - public void setTimerPercentilesMax(Duration timerPercentilesMax) { - this.timerPercentilesMax = timerPercentilesMax; - } - - public Duration getTimerPercentilesMin() { - return this.timerPercentilesMin; - } - - public void setTimerPercentilesMin(Duration timerPercentilesMin) { - this.timerPercentilesMin = timerPercentilesMin; - } - - public Boolean getEnabled() { + public Boolean isEnabled() { return this.enabled; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdPropertiesConfigAdapter.java index 840be63de53..4014a9be7ae 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/statsd/StatsdPropertiesConfigAdapter.java @@ -50,7 +50,7 @@ public class StatsdPropertiesConfigAdapter extends @Override public boolean enabled() { - return get(StatsdProperties::getEnabled, StatsdConfig::enabled); + return get(StatsdProperties::isEnabled, StatsdConfig::enabled); } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java index 1965a53c60f..3928ff7f063 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java @@ -20,7 +20,7 @@ import io.micrometer.core.instrument.MeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; -import org.springframework.boot.actuate.metrics.web.servlet.MetricsHandlerInterceptor; +import org.springframework.boot.actuate.metrics.web.servlet.MetricsFilter; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetrics; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -30,8 +30,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; /** * Configures instrumentation of Spring Web MVC servlet-based request mappings. @@ -61,27 +60,7 @@ public class WebMvcMetricsConfiguration { } @Bean - public MetricsHandlerInterceptor webMetricsInterceptor( - WebMvcMetrics controllerMetrics) { - return new MetricsHandlerInterceptor(controllerMetrics); + public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { + return new MetricsFilter(controllerMetrics, introspector); } - - @Configuration - public class MetricsServletRequestInterceptorConfiguration - implements WebMvcConfigurer { - - private final MetricsHandlerInterceptor handlerInterceptor; - - public MetricsServletRequestInterceptorConfiguration( - MetricsHandlerInterceptor handlerInterceptor) { - this.handlerInterceptor = handlerInterceptor; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(this.handlerInterceptor); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/TimedUtils.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/TimedUtils.java new file mode 100644 index 00000000000..b54ff2f2352 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/TimedUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * 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 org.springframework.boot.actuate.metrics; + +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.stream.Stream; + +import io.micrometer.core.annotation.Timed; +import io.micrometer.core.annotation.TimedSet; + +import org.springframework.core.annotation.AnnotationUtils; + +/** + * A utility class for finding {@link Timed} annotations. + * + * @author Jon Schneider + * @since 2.0.0 + */ +public final class TimedUtils { + private TimedUtils() { + } + + public static Stream findTimedAnnotations(AnnotatedElement element) { + Timed t = AnnotationUtils.findAnnotation(element, Timed.class); + if (t != null) + return Stream.of(t); + + TimedSet ts = AnnotationUtils.findAnnotation(element, TimedSet.class); + if (ts != null) { + return Arrays.stream(ts.value()); + } + + return Stream.empty(); + } +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/integration/SpringIntegrationMetrics.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/integration/SpringIntegrationMetrics.java index 41f96320b65..bafb3462ce5 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/integration/SpringIntegrationMetrics.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/integration/SpringIntegrationMetrics.java @@ -21,18 +21,11 @@ import java.util.Collection; import java.util.Collections; import java.util.concurrent.TimeUnit; -import io.micrometer.core.instrument.Meter.Id; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.binder.MeterBinder; - import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.integration.support.management.IntegrationManagementConfigurer; -import org.springframework.integration.support.management.MessageChannelMetrics; -import org.springframework.integration.support.management.MessageHandlerMetrics; -import org.springframework.integration.support.management.MessageSourceMetrics; -import org.springframework.integration.support.management.PollableChannelManagement; +import org.springframework.integration.support.management.*; + +import io.micrometer.core.instrument.*; +import io.micrometer.core.instrument.binder.MeterBinder; /** * A {@link MeterBinder} for Spring Integration metrics. @@ -60,133 +53,104 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS @Override public void bindTo(MeterRegistry registry) { - bindChannelNames(registry); - bindHandlerNames(registry); - bindSourceNames(registry); - this.registries.add(registry); - } + Gauge.builder("spring.integration.channelNames", this.configurer, + c -> c.getChannelNames().length).tags(tags) + .description("The number of spring integration channels") + .register(registry); - private void bindChannelNames(MeterRegistry registry) { - Id id = registry.createId("spring.integration.channelNames", this.tags, - "The number of spring integration channels"); - registry.gauge(id, this.configurer, (c) -> c.getChannelNames().length); - } + Gauge.builder("spring.integration.handlerNames", configurer, + c -> c.getHandlerNames().length).tags(this.tags) + .description("The number of spring integration handlers") + .register(registry); - private void bindHandlerNames(MeterRegistry registry) { - Id id = registry.createId("spring.integration.handlerNames", this.tags, - "The number of spring integration handlers"); - registry.gauge(id, this.configurer, (c) -> c.getHandlerNames().length); - } + Gauge.builder("spring.integration.sourceNames", configurer, + c -> c.getSourceNames().length).tags(this.tags) + .description("The number of spring integration sources") + .register(registry); - private void bindSourceNames(MeterRegistry registry) { - Id id = registry.createId("spring.integration.sourceNames", this.tags, - "The number of spring integration sources"); - registry.gauge(id, this.configurer, (c) -> c.getSourceNames().length); + this.registries.add(registry); } private void addSourceMetrics(MeterRegistry registry) { - for (String sourceName : this.configurer.getSourceNames()) { - addSourceMetrics(registry, sourceName); + for (String source : this.configurer.getSourceNames()) { + MessageSourceMetrics sourceMetrics = this.configurer.getSourceMetrics(source); + Iterable tagsWithSource = Tags.concat(this.tags, "source", source); + + FunctionCounter + .builder("spring.integration.source.messages", sourceMetrics, + MessageSourceMetrics::getMessageCount) + .tags(tagsWithSource) + .description("The number of successful handler calls") + .register(registry); } } - private void addSourceMetrics(MeterRegistry registry, String sourceName) { - MessageSourceMetrics sourceMetrics = this.configurer.getSourceMetrics(sourceName); - Iterable tags = Tags.concat(this.tags, "source", sourceName); - Id id = registry.createId("spring.integration.source.messages", tags, - "The number of successful handler calls"); - registry.more().counter(id, sourceMetrics, MessageSourceMetrics::getMessageCount); - } - private void addHandlerMetrics(MeterRegistry registry) { for (String handler : this.configurer.getHandlerNames()) { - addHandlerMetrics(registry, handler); + MessageHandlerMetrics handlerMetrics = this.configurer + .getHandlerMetrics(handler); + + // TODO could use improvement to dynamically commute the handler name with its + // ID, which can change after creation as shown in the + // SpringIntegrationApplication sample. + Iterable tagsWithHandler = Tags.concat(this.tags, "handler", handler); + + TimeGauge + .builder("spring.integration.handler.duration.max", handlerMetrics, + TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMaxDuration) + .tags(tagsWithHandler).description("The maximum handler duration") + .register(registry); + + TimeGauge + .builder("spring.integration.handler.duration.min", handlerMetrics, + TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMinDuration) + .tags(tagsWithHandler).description("The minimum handler duration") + .register(registry); + + TimeGauge + .builder("spring.integration.handler.duration.mean", handlerMetrics, + TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMeanDuration) + .tags(tagsWithHandler).description("The mean handler duration") + .register(registry); + + Gauge.builder("spring.integration.handler.activeCount", handlerMetrics, + MessageHandlerMetrics::getActiveCount).tags(tagsWithHandler) + .description("The number of active handlers").register(registry); } } - private void addHandlerMetrics(MeterRegistry registry, String handler) { - MessageHandlerMetrics handlerMetrics = this.configurer.getHandlerMetrics(handler); - // FIXME could use improvement to dynamically compute the handler name with its - // ID, which can change after - // creation as shown in the SpringIntegrationApplication sample. - Iterable tags = Tags.concat(this.tags, "handler", handler); - addDurationMax(registry, handlerMetrics, tags); - addDurationMin(registry, handlerMetrics, tags); - addDurationMean(registry, handlerMetrics, tags); - addActiveCount(registry, handlerMetrics, tags); - } - - private void addDurationMax(MeterRegistry registry, - MessageHandlerMetrics handlerMetrics, Iterable tags) { - Id id = registry.createId("spring.integration.handler.duration.max", tags, - "The maximum handler duration"); - registry.more().timeGauge(id, handlerMetrics, TimeUnit.MILLISECONDS, - MessageHandlerMetrics::getMaxDuration); - } - - private void addDurationMin(MeterRegistry registry, - MessageHandlerMetrics handlerMetrics, Iterable tags) { - Id id = registry.createId("spring.integration.handler.duration.min", tags, - "The minimum handler duration"); - registry.more().timeGauge(id, handlerMetrics, TimeUnit.MILLISECONDS, - MessageHandlerMetrics::getMinDuration); - } - - private void addDurationMean(MeterRegistry registry, - MessageHandlerMetrics handlerMetrics, Iterable tags) { - Id id = registry.createId("spring.integration.handler.duration.mean", tags, - "The mean handler duration"); - registry.more().timeGauge(id, handlerMetrics, TimeUnit.MILLISECONDS, - MessageHandlerMetrics::getMeanDuration); - } - - private void addActiveCount(MeterRegistry registry, - MessageHandlerMetrics handlerMetrics, Iterable tags) { - Id id = registry.createId("spring.integration.handler.activeCount", tags, - "The number of active handlers"); - registry.gauge(id, handlerMetrics, MessageHandlerMetrics::getActiveCount); - } - private void addChannelMetrics(MeterRegistry registry) { for (String channel : this.configurer.getChannelNames()) { - addChannelMetrics(registry, channel); + MessageChannelMetrics channelMetrics = this.configurer + .getChannelMetrics(channel); + Iterable tagsWithChannel = Tags.concat(this.tags, "channel", channel); + + FunctionCounter + .builder("spring.integration.channel.sendErrors", channelMetrics, + MessageChannelMetrics::getSendErrorCount) + .tags(tagsWithChannel) + .description( + "The number of failed sends (either throwing an exception or rejected by the channel)") + .register(registry); + + FunctionCounter + .builder("spring.integration.channel.sends", channelMetrics, + MessageChannelMetrics::getSendCount) + .tags(tagsWithChannel).description("The number of successful sends") + .register(registry); + + if (channelMetrics instanceof PollableChannelManagement) { + FunctionCounter + .builder("spring.integration.receives", + (PollableChannelManagement) channelMetrics, + PollableChannelManagement::getReceiveCount) + .tags(tagsWithChannel) + .description("The number of messages received") + .register(registry); + } } } - private void addChannelMetrics(MeterRegistry registry, String channel) { - Iterable tags = Tags.concat(this.tags, "channel", channel); - MessageChannelMetrics channelMetrics = this.configurer.getChannelMetrics(channel); - addSendErrors(registry, tags, channelMetrics); - addChannelSends(registry, tags, channelMetrics); - if (channelMetrics instanceof PollableChannelManagement) { - addReceives(registry, tags, channelMetrics); - } - } - - private void addSendErrors(MeterRegistry registry, Iterable tags, - MessageChannelMetrics channelMetrics) { - Id id = registry.createId("spring.integration.channel.sendErrors", tags, - "The number of failed sends (either throwing an exception or " - + "rejected by the channel)"); - registry.more().counter(id, channelMetrics, - MessageChannelMetrics::getSendErrorCount); - } - - private void addReceives(MeterRegistry registry, Iterable tags, - MessageChannelMetrics channelMetrics) { - Id id = registry.createId("spring.integration.channel.receives", tags, - "The number of messages received"); - registry.more().counter(id, (PollableChannelManagement) channelMetrics, - PollableChannelManagement::getReceiveCount); - } - - private void addChannelSends(MeterRegistry registry, Iterable tags, - MessageChannelMetrics channelMetrics) { - Id id = registry.createId("spring.integration.channel.sends", tags, - "The number of successful sends"); - registry.more().counter(id, channelMetrics, MessageChannelMetrics::getSendCount); - } - @Override public void afterSingletonsInstantiated() { this.registries.forEach((registry) -> { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java index 148884584c0..5984c341258 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java @@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.stats.hist.Histogram; import org.springframework.core.NamedThreadLocal; import org.springframework.http.HttpRequest; @@ -98,13 +97,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse response) { - Timer.Builder builder = Timer.builder(this.metricName) + return Timer.builder(this.metricName) .tags(this.tagProvider.getTags(urlTemplate.get(), request, response)) - .description("Timer of RestTemplate operation"); - if (this.recordPercentiles) { - builder = builder.histogram(Histogram.percentilesTime()); - } - return builder; + .description("Timer of RestTemplate operation") + .publishPercentileHistogram(this.recordPercentiles); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilter.java new file mode 100644 index 00000000000..9c548865fc5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * 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 org.springframework.boot.actuate.metrics.web.servlet; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; +import org.springframework.web.servlet.handler.MatchableHandlerMapping; +import org.springframework.web.util.NestedServletException; + +/** + * Intercepts incoming HTTP requests and records metrics about execution time and results. + * + * @author Jon Schneider + * @since 2.0.0 + */ +@Order(Ordered.HIGHEST_PRECEDENCE) +public class MetricsFilter extends OncePerRequestFilter { + private final WebMvcMetrics webMvcMetrics; + private final HandlerMappingIntrospector mappingIntrospector; + private final Logger logger = LoggerFactory.getLogger(MetricsFilter.class); + + public MetricsFilter(WebMvcMetrics webMvcMetrics, + HandlerMappingIntrospector mappingIntrospector) { + this.webMvcMetrics = webMvcMetrics; + this.mappingIntrospector = mappingIntrospector; + } + + @Override + protected boolean shouldNotFilterAsyncDispatch() { + return false; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + HandlerExecutionChain handler; + try { + MatchableHandlerMapping matchableHandlerMapping = this.mappingIntrospector + .getMatchableHandlerMapping(request); + handler = matchableHandlerMapping.getHandler(request); + } + catch (Exception e) { + this.logger.debug("Unable to time request", e); + return; + } + + if (handler != null) { + Object handlerObject = handler.getHandler(); + this.webMvcMetrics.preHandle(request, handlerObject); + try { + filterChain.doFilter(request, response); + + // when an async operation is complete, the whole filter gets called + // again with isAsyncStarted = false + if (!request.isAsyncStarted()) { + this.webMvcMetrics.record(request, response, null); + } + } + catch (NestedServletException e) { + this.webMvcMetrics.record(request, response, e.getCause()); + throw e; + } + } + else { + filterChain.doFilter(request, response); + } + } +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptor.java deleted file mode 100644 index 00b7a41d9c9..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptor.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * 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 org.springframework.boot.actuate.metrics.web.servlet; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -/** - * Intercepts incoming HTTP requests and records metrics about execution time and results. - * - * @author Jon Schneider - * @since 2.0.0 - */ -public class MetricsHandlerInterceptor extends HandlerInterceptorAdapter { - - private final WebMvcMetrics webMvcMetrics; - - public MetricsHandlerInterceptor(WebMvcMetrics webMvcMetrics) { - this.webMvcMetrics = webMvcMetrics; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, - Object handler) throws Exception { - this.webMvcMetrics.preHandle(request, handler); - return super.preHandle(request, response, handler); - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, - Object handler, Exception ex) throws Exception { - this.webMvcMetrics.record(request, response, ex); - super.afterCompletion(request, response, handler, ex); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java index 7d74712ea9c..d7bf324c321 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.metrics.web.servlet; import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Map; @@ -25,28 +24,21 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import io.micrometer.core.annotation.Timed; -import io.micrometer.core.instrument.LongTaskTimer; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.stats.hist.Histogram; -import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles; -import io.micrometer.core.instrument.util.AnnotationUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.springframework.boot.actuate.metrics.TimedUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.method.HandlerMethod; +import io.micrometer.core.annotation.Timed; +import io.micrometer.core.instrument.*; + /** * Support class for Spring MVC metrics. * @@ -154,25 +146,21 @@ public class WebMvcMetrics { HttpServletResponse response, Throwable thrown, TimerConfig config) { Timer.Builder builder = Timer.builder(config.getName()) .tags(this.tagsProvider.httpRequestTags(request, response, thrown)) - .tags(config.getExtraTags()).description("Timer of servlet request"); - if (config.getQuantiles().length > 0) { - WindowSketchQuantiles quantiles = WindowSketchQuantiles - .quantiles(config.getQuantiles()).create(); - builder = builder.quantiles(quantiles); - } - if (config.isPercentiles()) { - builder = builder.histogram(Histogram.percentilesTime()); + .tags(config.getExtraTags()).description("Timer of servlet request") + .publishPercentileHistogram(config.histogram); + if (config.getPercentiles().length > 0) { + builder = builder.publishPercentiles(config.getPercentiles()); } return builder; } private LongTaskTimer longTaskTimer(TimerConfig config, HttpServletRequest request, - Object handler) { - Iterable tags = Tags.concat( - this.tagsProvider.httpLongRequestTags(request, handler), - config.getExtraTags()); - return this.registry.more().longTaskTimer(this.registry.createId(config.getName(), - tags, "Timer of long servlet request")); + Object handler) { + return LongTaskTimer.builder(config.getName()) + .tags(this.tagsProvider.httpLongRequestTags(request, handler)) + .tags(config.getExtraTags()) + .description("Timer of long servlet request") + .register(this.registry); } private Set longTaskTimed(Object handler) { @@ -210,22 +198,15 @@ public class WebMvcMetrics { } private Set getNonLongTaskAnnotationConfig(AnnotatedElement element) { - return findTimedAnnotations(element).filter((t) -> !t.longTask()) + return TimedUtils.findTimedAnnotations(element).filter((t) -> !t.longTask()) .map(this::fromAnnotation).collect(Collectors.toSet()); } private Set getLongTaskAnnotationConfig(AnnotatedElement element) { - return findTimedAnnotations(element).filter(Timed::longTask) + return TimedUtils.findTimedAnnotations(element).filter(Timed::longTask) .map(this::fromAnnotation).collect(Collectors.toSet()); } - private Stream findTimedAnnotations(AnnotatedElement element) { - if (element instanceof Class) { - return AnnotationUtils.findTimed((Class) element); - } - return AnnotationUtils.findTimed((Method) element); - } - private TimerConfig fromAnnotation(Timed timed) { return new TimerConfig(timed, this::getServerRequestName); } @@ -240,22 +221,22 @@ public class WebMvcMetrics { private final Iterable extraTags; - private final double[] quantiles; + private final double[] percentiles; - private final boolean percentiles; + private final boolean histogram; - TimerConfig(String name, boolean percentiles) { + TimerConfig(String name, boolean histogram) { this.name = name; this.extraTags = Collections.emptyList(); - this.quantiles = new double[0]; - this.percentiles = percentiles; + this.percentiles = new double[0]; + this.histogram = histogram; } TimerConfig(Timed timed, Supplier name) { this.name = buildName(timed, name); this.extraTags = Tags.zip(timed.extraTags()); - this.quantiles = timed.quantiles(); this.percentiles = timed.percentiles(); + this.histogram = timed.histogram(); } private String buildName(Timed timed, Supplier name) { @@ -271,16 +252,16 @@ public class WebMvcMetrics { return this.name; } - public Iterable getExtraTags() { + Iterable getExtraTags() { return this.extraTags; } - public double[] getQuantiles() { - return this.quantiles; + double[] getPercentiles() { + return this.percentiles; } - public boolean isPercentiles() { - return this.percentiles; + boolean isHistogram() { + return this.histogram; } @Override @@ -301,5 +282,4 @@ public class WebMvcMetrics { } } - } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java index 8b36d0cf119..7a7021e2bf3 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java @@ -21,11 +21,14 @@ import java.util.Optional; import java.util.stream.Stream; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Statistic; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; +import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Test; +import static io.micrometer.core.instrument.MockClock.clock; import static org.assertj.core.api.Assertions.assertThat; /** @@ -36,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class MetricsEndpointTests { - private final MeterRegistry registry = new SimpleMeterRegistry(); + private final MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); private final MetricsEndpoint endpoint = new MetricsEndpoint(this.registry); @@ -74,6 +77,8 @@ public class MetricsEndpointTests { this.registry.counter("cache", "result", "hit", "host", "1").increment(2); this.registry.counter("cache", "result", "miss", "host", "1").increment(2); this.registry.counter("cache", "result", "hit", "host", "2").increment(2); + clock(registry).add(SimpleConfig.DEFAULT_STEP); + MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache", Collections.emptyList()); assertThat(response.getName()).isEqualTo("cache"); @@ -87,6 +92,8 @@ public class MetricsEndpointTests { @Test public void metricWithSpaceInTagValue() { this.registry.counter("counter", "key", "a space").increment(2); + clock(registry).add(SimpleConfig.DEFAULT_STEP); + MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter", Collections.singletonList("key:a space")); assertThat(response.getName()).isEqualTo("counter"); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java index 0ba352a0700..bd3433d8314 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java @@ -22,16 +22,20 @@ import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; +import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointRunners; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.web.reactive.server.WebTestClient; +import static io.micrometer.core.instrument.MockClock.clock; import static org.assertj.core.api.Assertions.assertThat; /** @@ -42,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ @RunWith(WebEndpointRunners.class) public class MetricsEndpointWebIntegrationTests { - + private static MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); private static WebTestClient client; private final ObjectMapper mapper = new ObjectMapper(); @@ -50,7 +54,7 @@ public class MetricsEndpointWebIntegrationTests { @SuppressWarnings("unchecked") @Test public void listNames() throws IOException { - String responseBody = MetricsEndpointWebIntegrationTests.client.get() + String responseBody = client.get() .uri("/application/metrics").exchange().expectStatus().isOk() .expectBody(String.class).returnResult().getResponseBody(); Map> names = this.mapper.readValue(responseBody, Map.class); @@ -59,14 +63,16 @@ public class MetricsEndpointWebIntegrationTests { @Test public void selectByName() throws IOException { - MetricsEndpointWebIntegrationTests.client.get() + clock(registry).add(SimpleConfig.DEFAULT_STEP); + client.get() .uri("/application/metrics/jvm.memory.used").exchange().expectStatus() .isOk().expectBody().jsonPath("$.name").isEqualTo("jvm.memory.used"); } @Test public void selectByTag() { - MetricsEndpointWebIntegrationTests.client.get() + clock(registry).add(SimpleConfig.DEFAULT_STEP); + client.get() .uri("/application/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space") .exchange().expectStatus().isOk().expectBody().jsonPath("$.name") .isEqualTo("jvm.memory.used"); @@ -77,7 +83,7 @@ public class MetricsEndpointWebIntegrationTests { @Bean public MeterRegistry registry() { - return new SimpleMeterRegistry(); + return registry; } @Bean diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java index b9ddf038e83..894bb4329a2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java @@ -19,8 +19,10 @@ package org.springframework.boot.actuate.metrics.web.client; import java.util.stream.StreamSupport; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Statistic; import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Test; @@ -31,6 +33,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; import org.springframework.web.client.RestTemplate; +import static io.micrometer.core.instrument.MockClock.clock; +import static java.util.stream.StreamSupport.stream; import static org.assertj.core.api.Assertions.assertThat; /** @@ -42,7 +46,7 @@ public class MetricsRestTemplateCustomizerTests { @Test public void interceptRestTemplate() { - MeterRegistry registry = new SimpleMeterRegistry(); + MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); RestTemplate restTemplate = new RestTemplate(); MetricsRestTemplateCustomizer customizer = new MetricsRestTemplateCustomizer( registry, new DefaultRestTemplateExchangeTagsProvider(), @@ -55,13 +59,12 @@ public class MetricsRestTemplateCustomizerTests { .andRespond(MockRestResponseCreators.withSuccess("OK", MediaType.APPLICATION_JSON)); String result = restTemplate.getForObject("/test/{id}", String.class, 123); + clock(registry).add(SimpleConfig.DEFAULT_STEP); + assertThat(registry.find("http.client.requests").meters()) + .anySatisfy(m -> assertThat(stream(m.getId().getTags().spliterator(), false).map(Tag::getKey)).doesNotContain("bucket")); assertThat(registry.find("http.client.requests") .tags("method", "GET", "uri", "/test/{id}", "status", "200") .value(Statistic.Count, 1.0).timer()).isPresent(); - assertThat(registry.find("http.client.requests").meters() - .stream().flatMap((m) -> StreamSupport - .stream(m.getId().getTags().spliterator(), false)) - .map(Tag::getKey)).contains("bucket"); assertThat(result).isEqualTo("OK"); mockServer.verify(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptorAutoTimedTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilterAutoTimedTests.java similarity index 51% rename from spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptorAutoTimedTests.java rename to spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilterAutoTimedTests.java index ea1584376bb..8b212a134f6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptorAutoTimedTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilterAutoTimedTests.java @@ -16,12 +16,14 @@ package org.springframework.boot.actuate.metrics.web.servlet; +import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.MockClock; +import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,87 +38,81 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; 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.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** - * Tests for {@link MetricsHandlerInterceptor} with auto-timed server requests. - * * @author Jon Schneider */ @RunWith(SpringRunner.class) @WebAppConfiguration -public class MetricsHandlerInterceptorAutoTimedTests { +public class MetricsFilterAutoTimedTests { - @Autowired - private MeterRegistry registry; + @Autowired + private MeterRegistry registry; - @Autowired - private WebApplicationContext context; + @Autowired + private MockClock clock; - private MockMvc mvc; + @Autowired + private WebApplicationContext context; - @Before - public void setupMockMvc() { - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } + private MockMvc mvc; - @Test - public void metricsCanBeAutoTimed() throws Exception { - this.mvc.perform(get("/api/10")).andExpect(status().isOk()); - assertThat( - this.registry.find("http.server.requests").tags("status", "200").timer()) - .hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1)); - } + @Autowired + private MetricsFilter filter; - @Configuration - @EnableWebMvc - @Import(Controller.class) - static class TestConfiguration { - - @Bean - MeterRegistry meterRegistry() { - return new SimpleMeterRegistry(); - } - - @Bean - WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) { - return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(), - "http.server.requests", true, true); - } + @Before + public void setupMockMvc() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context) + .addFilters(filter) + .build(); + } - @Configuration - static class HandlerInterceptorConfiguration implements WebMvcConfigurer { + @Test + public void metricsCanBeAutoTimed() throws Exception { + this.mvc.perform(get("/api/10")).andExpect(status().isOk()); - private final WebMvcMetrics webMvcMetrics; + clock.add(SimpleConfig.DEFAULT_STEP); + assertThat(this.registry.find("http.server.requests").tags("status", "200").timer()) + .hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1)); + } - HandlerInterceptorConfiguration(WebMvcMetrics webMvcMetrics) { - this.webMvcMetrics = webMvcMetrics; - } + @Configuration + @EnableWebMvc + @Import({Controller.class}) + static class TestConfiguration { + @Bean + MockClock clock() { + return new MockClock(); + } - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor( - new MetricsHandlerInterceptor(this.webMvcMetrics)); - } + @Bean + MeterRegistry meterRegistry(Clock clock) { + return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock); + } + @Bean + public WebMvcMetrics controllerMetrics(MeterRegistry registry) { + return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true, + false); } - } - - @RestController - @RequestMapping("/api") - static class Controller { - - @GetMapping("/{id}") - public String successful(@PathVariable Long id) { - return id.toString(); + @Bean + public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { + return new MetricsFilter(controllerMetrics, introspector); } - - } - + } + + @RestController + @RequestMapping("/api") + static class Controller { + @GetMapping("/{id}") + public String successful(@PathVariable Long id) { + return id.toString(); + } + } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilterTests.java similarity index 58% rename from spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptorTests.java rename to spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilterTests.java index c108ebe1b73..34d80d4ab79 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsHandlerInterceptorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/MetricsFilterTests.java @@ -16,26 +16,39 @@ package org.springframework.boot.actuate.metrics.web.servlet; +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.stream.StreamSupport; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Statistic; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.prometheus.PrometheusConfig; +import io.micrometer.prometheus.PrometheusMeterRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +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.context.annotation.Import; import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -48,10 +61,12 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.servlet.ModelAndView; 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.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -61,40 +76,57 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** - * Tests for {@link MetricsHandlerInterceptor}. + * Tests for {@link MetricsFilter} * * @author Jon Schneider */ @RunWith(SpringRunner.class) @WebAppConfiguration -public class MetricsHandlerInterceptorTests { - - private static final CountDownLatch longRequestCountDown = new CountDownLatch(1); - +public class MetricsFilterTests { @Autowired - private MeterRegistry registry; + private PrometheusMeterRegistry registry; @Autowired private WebApplicationContext context; + @Autowired + private MetricsFilter filter; + private MockMvc mvc; + @Autowired + private CountDownLatch asyncLatch; + @Before public void setupMockMvc() { - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.mvc = MockMvcBuilders + .webAppContextSetup(this.context) + .addFilters(filter, new RedirectAndNotFoundFilter()) + .build(); } @Test public void timedMethod() throws Exception { this.mvc.perform(get("/api/c1/10")).andExpect(status().isOk()); + assertThat(this.registry.find("http.server.requests") .tags("status", "200", "uri", "/api/c1/{id}", "public", "true") .value(Statistic.Count, 1.0).timer()).isPresent(); } + @Test + public void subclassedTimedMethod() throws Exception { + this.mvc.perform(get("/api/c1/metaTimed/10")).andExpect(status().isOk()); + + assertThat(this.registry.find("http.server.requests") + .tags("status", "200", "uri", "/api/c1/metaTimed/{id}") + .value(Statistic.Count, 1.0).timer()).isPresent(); + } + @Test public void untimedMethod() throws Exception { this.mvc.perform(get("/api/c1/untimed/10")).andExpect(status().isOk()); + assertThat(this.registry.find("http.server.requests") .tags("uri", "/api/c1/untimed/10").timer()).isEmpty(); } @@ -102,23 +134,48 @@ public class MetricsHandlerInterceptorTests { @Test public void timedControllerClass() throws Exception { this.mvc.perform(get("/api/c2/10")).andExpect(status().isOk()); - assertThat( - this.registry.find("http.server.requests").tags("status", "200").timer()) - .hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1)); + + assertThat(this.registry.find("http.server.requests").tags("status", "200") + .value(Statistic.Count, 1.0) + .timer()).isPresent(); } @Test public void badClientRequest() throws Exception { this.mvc.perform(get("/api/c1/oops")).andExpect(status().is4xxClientError()); - assertThat( - this.registry.find("http.server.requests").tags("status", "400").timer()) - .hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1)); + + assertThat(this.registry.find("http.server.requests").tags("status", "400") + .value(Statistic.Count, 1.0) + .timer()).isPresent(); + } + + + @Test + public void redirectRequest() throws Exception { + this.mvc.perform(get("/api/redirect") + .header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "302")).andExpect(status().is3xxRedirection()); + + assertThat(this.registry.find("http.server.requests") + .tags("uri", "REDIRECTION") + .tags("status", "302").timer()).isPresent(); + } + + @Test + public void notFoundRequest() throws Exception { + this.mvc.perform(get("/api/not/found") + .header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "404")).andExpect(status().is4xxClientError()); + + assertThat(this.registry.find("http.server.requests") + .tags("uri", "NOT_FOUND") + .tags("status", "404").timer()).isPresent(); } @Test public void unhandledError() throws Exception { assertThatCode(() -> this.mvc.perform(get("/api/c1/unhandledError/10")) - .andExpect(status().isOk())).hasCauseInstanceOf(RuntimeException.class); + .andExpect(status().isOk())) + .hasRootCauseInstanceOf(RuntimeException.class); + assertThat(this.registry.find("http.server.requests") .tags("exception", "RuntimeException").value(Statistic.Count, 1.0) .timer()).isPresent(); @@ -127,14 +184,19 @@ public class MetricsHandlerInterceptorTests { @Test public void longRunningRequest() throws Exception { MvcResult result = this.mvc.perform(get("/api/c1/long/10")) - .andExpect(request().asyncStarted()).andReturn(); + .andExpect(request().asyncStarted()) + .andReturn(); + + // the request is not prematurely recorded as complete + assertThat(this.registry.find("http.server.requests") + .tags("uri", "/api/c1/async").timer()).isNotPresent(); // while the mapping is running, it contributes to the activeTasks count assertThat(this.registry.find("my.long.request").tags("region", "test") .value(Statistic.Count, 1.0).longTaskTimer()).isPresent(); // once the mapping completes, we can gather information about status, etc. - longRequestCountDown.countDown(); + asyncLatch.countDown(); this.mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); @@ -145,6 +207,7 @@ public class MetricsHandlerInterceptorTests { @Test public void endpointThrowsError() throws Exception { this.mvc.perform(get("/api/c1/error/10")).andExpect(status().is4xxClientError()); + assertThat(this.registry.find("http.server.requests").tags("status", "422") .value(Statistic.Count, 1.0).timer()).isPresent(); } @@ -152,6 +215,7 @@ public class MetricsHandlerInterceptorTests { @Test public void regexBasedRequestMapping() throws Exception { this.mvc.perform(get("/api/c1/regex/.abc")).andExpect(status().isOk()); + assertThat(this.registry.find("http.server.requests") .tags("uri", "/api/c1/regex/{id:\\.[a-z]+}").value(Statistic.Count, 1.0) .timer()).isPresent(); @@ -159,80 +223,76 @@ public class MetricsHandlerInterceptorTests { @Test public void recordQuantiles() throws Exception { - this.mvc.perform(get("/api/c1/quantiles/10")).andExpect(status().isOk()); + this.mvc.perform(get("/api/c1/percentiles/10")).andExpect(status().isOk()); - assertThat(this.registry.find("http.server.requests").tags("quantile", "0.5") - .gauge()).isNotEmpty(); - assertThat(this.registry.find("http.server.requests").tags("quantile", "0.95") - .gauge()).isNotEmpty(); + assertThat(this.registry.scrape()).contains("quantile=\"0.5\""); + assertThat(this.registry.scrape()).contains("quantile=\"0.95\""); } @Test - public void recordPercentiles() throws Exception { - this.mvc.perform(get("/api/c1/percentiles/10")).andExpect(status().isOk()); + public void recordHistogram() throws Exception { + this.mvc.perform(get("/api/c1/histogram/10")).andExpect(status().isOk()); - assertThat(this.registry.find("http.server.requests").meters() - .stream().flatMap((m) -> StreamSupport - .stream(m.getId().getTags().spliterator(), false)) - .map(Tag::getKey)).contains("bucket"); + assertThat(this.registry.scrape()).contains("le=\"0.001\""); + assertThat(this.registry.scrape()).contains("le=\"30.0\""); + } + + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Timed(percentiles = 0.95) + public @interface Timed95 { } @Configuration @EnableWebMvc - @Import({ Controller1.class, Controller2.class }) - static class TestConfiguration { - + @Import({Controller1.class, Controller2.class}) + static class MetricsFilterApp { @Bean MeterRegistry meterRegistry() { - return new SimpleMeterRegistry(); + // one of the few registries that support aggregable percentiles + return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); } @Bean - WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) { - return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(), - "http.server.requests", true, true); + CountDownLatch asyncLatch() { + return new CountDownLatch(1); } - @Configuration - static class HandlerInterceptorConfiguration implements WebMvcConfigurer { - - private final WebMvcMetrics webMvcMetrics; - - HandlerInterceptorConfiguration(WebMvcMetrics webMvcMetrics) { - this.webMvcMetrics = webMvcMetrics; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor( - new MetricsHandlerInterceptor(this.webMvcMetrics)); - } - + @Bean + public WebMvcMetrics controllerMetrics(MeterRegistry registry) { + return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true, + false); } + @Bean + public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { + return new MetricsFilter(controllerMetrics, introspector); + } } @RestController @RequestMapping("/api/c1") static class Controller1 { + private final CountDownLatch asyncLatch; + + public Controller1(CountDownLatch asyncLatch) { + this.asyncLatch = asyncLatch; + } - @Timed(extraTags = { "public", "true" }) + @Timed(extraTags = {"public", "true"}) @GetMapping("/{id}") public String successfulWithExtraTags(@PathVariable Long id) { return id.toString(); } - @Timed // contains dimensions for status, etc. that can't be known until after the - // response is sent - @Timed(value = "my.long.request", extraTags = { "region", - "test" }, longTask = true) // in progress metric + @Timed + @Timed(value = "my.long.request", extraTags = {"region", "test"}, longTask = true) @GetMapping("/long/{id}") public Callable takesLongTimeToSatisfy(@PathVariable Long id) { return () -> { try { - longRequestCountDown.await(); - } - catch (InterruptedException e) { + asyncLatch.await(); + } catch (InterruptedException e) { throw new RuntimeException(e); } return id.toString(); @@ -247,13 +307,13 @@ public class MetricsHandlerInterceptorTests { @Timed @GetMapping("/error/{id}") public String alwaysThrowsException(@PathVariable Long id) { - throw new IllegalStateException("Boom on $id!"); + throw new IllegalStateException("Boom on " + id + "!"); } @Timed @GetMapping("/unhandledError/{id}") public String alwaysThrowsUnhandledException(@PathVariable Long id) { - throw new RuntimeException("Boom on $id!"); + throw new RuntimeException("Boom on " + id + "!"); } @Timed @@ -262,15 +322,21 @@ public class MetricsHandlerInterceptorTests { return id; } - @Timed(quantiles = { 0.5, 0.95 }) - @GetMapping("/quantiles/{id}") - public String quantiles(@PathVariable String id) { + @Timed(percentiles = {0.50, 0.95}) + @GetMapping("/percentiles/{id}") + public String percentiles(@PathVariable String id) { return id; } - @Timed(percentiles = true) - @GetMapping("/percentiles/{id}") - public String percentiles(@PathVariable String id) { + @Timed(histogram = true) + @GetMapping("/histogram/{id}") + public String histogram(@PathVariable String id) { + return id; + } + + @Timed95 + @GetMapping("/metaTimed/{id}") + public String meta(@PathVariable String id) { return id; } @@ -279,19 +345,31 @@ public class MetricsHandlerInterceptorTests { ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) { return new ModelAndView("myerror"); } - } @RestController @Timed @RequestMapping("/api/c2") static class Controller2 { - @GetMapping("/{id}") public String successful(@PathVariable Long id) { return id.toString(); } - } + static class RedirectAndNotFoundFilter extends OncePerRequestFilter { + + static final String TEST_MISBEHAVE_HEADER = "x-test-misbehave-status"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER); + if (misbehave != null) { + response.setStatus(Integer.parseInt(misbehave)); + } else { + filterChain.doFilter(request, response); + } + } + } } + diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java index 9e86287ad68..ff3d7970e73 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java @@ -17,8 +17,11 @@ package org.springframework.boot.actuate.metrics.web.servlet; import io.micrometer.core.annotation.Timed; +import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Before; import org.junit.Test; @@ -33,15 +36,10 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; 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.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -63,42 +61,64 @@ public class WebMvcMetricsIntegrationTests { @Autowired private SimpleMeterRegistry registry; + @Autowired + private MockClock clock; + private MockMvc mvc; + @Autowired + private MetricsFilter filter; + @Before public void setupMockMvc() { - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context) + .addFilters(filter) + .build(); } @Test public void handledExceptionIsRecordedInMetricTag() throws Exception { this.mvc.perform(get("/api/handledError")).andExpect(status().is5xxServerError()); + + clock.add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.server.requests") .tags("exception", "Exception1").value(Statistic.Count, 1.0).timer()) - .isPresent(); + .isPresent(); } @Test public void rethrownExceptionIsRecordedInMetricTag() throws Exception { assertThatCode(() -> this.mvc.perform(get("/api/rethrownError")) .andExpect(status().is5xxServerError())); + + clock.add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.server.requests") .tags("exception", "Exception2").value(Statistic.Count, 1.0).timer()) - .isPresent(); + .isPresent(); } @Configuration + @EnableWebMvc static class TestConfiguration { + @Bean + MockClock clock() { + return new MockClock(); + } + + @Bean + MeterRegistry meterRegistry(Clock clock) { + return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock); + } @Bean - WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) { - return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(), - "http.server.requests", true, true); + public WebMvcMetrics controllerMetrics(MeterRegistry registry) { + return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true, + false); } @Bean - MeterRegistry registry() { - return new SimpleMeterRegistry(); + public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { + return new MetricsFilter(controllerMetrics, introspector); } @RestController @@ -122,33 +142,12 @@ public class WebMvcMetricsIntegrationTests { } } - - @Configuration - @EnableWebMvc - static class HandlerInterceptorConfiguration implements WebMvcConfigurer { - - private final WebMvcMetrics webMvcMetrics; - - HandlerInterceptorConfiguration(WebMvcMetrics webMvcMetrics) { - this.webMvcMetrics = webMvcMetrics; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor( - new MetricsHandlerInterceptor(this.webMvcMetrics)); - } - - } - } static class Exception1 extends RuntimeException { - } static class Exception2 extends RuntimeException { - } @ControllerAdvice @@ -168,7 +167,5 @@ public class WebMvcMetricsIntegrationTests { ResponseEntity rethrowError(Exception2 ex) throws Throwable { throw ex; } - } - -} +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 2b175c974f1..42d88f02512 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -114,7 +114,7 @@ 1.2.3 1.16.18 2.1.2 - 1.0.0-rc.2 + 1.0.0-SNAPSHOT 6.2.2.jre8 2.11.0 1.6.0