diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java index 2eeabbdf4d3e..4129e53439c0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java @@ -25,6 +25,7 @@ * {@link ConfigurationProperties} for configuring Micrometer-based metrics. * * @author Jon Schneider + * @author Alexander Abramov * @since 2.0.0 */ @ConfigurationProperties("management.metrics") @@ -199,6 +200,22 @@ public static class Distribution { */ private final Map sla = new LinkedHashMap<>(); + /** + * The minimum value that this distribution summary is expected to observe. + * Controls the number of buckets shipped by percentilesHistogram. Can be + * specified as a long or as a Duration value (for timer meters, defaulting to ms + * if no unit specified). + */ + private final Map minimumExpectedValue = new LinkedHashMap<>(); + + /** + * The maximum value that this distribution summary is expected to observe. + * Controls the number of buckets shipped by percentilesHistogram. Can be + * specified as a long or as a Duration value (for timer meters, defaulting to ms + * if no unit specified). + */ + private final Map maximumExpectedValue = new LinkedHashMap<>(); + public Map getPercentilesHistogram() { return this.percentilesHistogram; } @@ -211,6 +228,14 @@ public Map getSla() { return this.sla; } + public Map getMinimumExpectedValue() { + return this.minimumExpectedValue; + } + + public Map getMaximumExpectedValue() { + return this.maximumExpectedValue; + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilter.java index eacbe66e893f..7009a5395f5b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilter.java @@ -39,6 +39,7 @@ * @author Jon Schneider * @author Phillip Webb * @author Stephane Nicoll + * @author Alexander Abramov * @since 2.0.0 */ public class PropertiesMeterFilter implements MeterFilter { @@ -84,6 +85,10 @@ public DistributionStatisticConfig configure(Meter.Id id, lookup(distribution.getPercentilesHistogram(), id, null)) .percentiles(lookup(distribution.getPercentiles(), id, null)) .sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null))) + .minimumExpectedValue(convertSla(id.getType(), + lookup(distribution.getMinimumExpectedValue(), id, null))) + .maximumExpectedValue(convertSla(id.getType(), + lookup(distribution.getMaximumExpectedValue(), id, null))) .build().merge(config); } @@ -97,6 +102,10 @@ private long[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[] return (converted.length != 0) ? converted : null; } + private Long convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary sla) { + return (sla != null) ? sla.getValue(meterType) : null; + } + private T lookup(Map values, Id id, T defaultValue) { if (values.isEmpty()) { return defaultValue; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java index 0b8459a8334a..4defae0bbe11 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics; +import java.time.Duration; import java.util.Collections; import io.micrometer.core.instrument.Meter; @@ -296,6 +297,110 @@ public void configureWhenSlaLongShouldOnlyApplyToTimerAndDistributionSummary() { .getSlaBoundaries()).isNullOrEmpty(); } + @Test + public void configureWhenHasMinimumExpectedValueShouldSetMinimumExpectedToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.minimum-expected-value.[spring.boot]=10")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMinimumExpectedValue()) + .isEqualTo(Duration.ofMillis(10).toNanos()); + } + + @Test + public void configureWhenHasHigherMinimumExpectedValueShouldSetMinimumExpectedValueToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.minimum-expected-value.spring=10")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMinimumExpectedValue()) + .isEqualTo(Duration.ofMillis(10).toNanos()); + } + + @Test + public void configureWhenHasHigherMinimumExpectedValueAndLowerShouldSetMinimumExpectedValueToHigher() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.minimum-expected-value.spring=10", + "distribution.minimum-expected-value.[spring.boot]=50")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMinimumExpectedValue()) + .isEqualTo(Duration.ofMillis(50).toNanos()); + } + + @Test + public void configureWhenAllMinimumExpectedValueSetShouldSetMinimumExpectedValueToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.minimum-expected-value.all=10")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMinimumExpectedValue()) + .isEqualTo(Duration.ofMillis(10).toNanos()); + } + + @Test + public void configureWhenMinimumExpectedValueDurationShouldOnlyApplyToTimer() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.minimum-expected-value.all=10ms")); + Meter.Id timer = createMeterId("spring.boot", Meter.Type.TIMER); + Meter.Id summary = createMeterId("spring.boot", Meter.Type.DISTRIBUTION_SUMMARY); + Meter.Id counter = createMeterId("spring.boot", Meter.Type.COUNTER); + assertThat(filter.configure(timer, DistributionStatisticConfig.DEFAULT) + .getMinimumExpectedValue()).isEqualTo(Duration.ofMillis(10).toNanos()); + assertThat(filter.configure(summary, DistributionStatisticConfig.DEFAULT) + .getMinimumExpectedValue()).isEqualTo(1L); + assertThat(filter.configure(counter, DistributionStatisticConfig.DEFAULT) + .getMinimumExpectedValue()).isEqualTo(1L); + } + + @Test + public void configureWhenHasMaximumExpectedValueShouldSetMaximumExpectedToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties( + "distribution.maximum-expected-value.[spring.boot]=5000")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMaximumExpectedValue()) + .isEqualTo(Duration.ofMillis(5000).toNanos()); + } + + @Test + public void configureWhenHasHigherMaximumExpectedValueShouldSetMaximumExpectedValueToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.maximum-expected-value.spring=5000")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMaximumExpectedValue()) + .isEqualTo(Duration.ofMillis(5000).toNanos()); + } + + @Test + public void configureWhenHasHigherMaximumExpectedValueAndLowerShouldSetMaximumExpectedValueToHigher() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.maximum-expected-value.spring=5000", + "distribution.maximum-expected-value.[spring.boot]=10000")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMaximumExpectedValue()) + .isEqualTo(Duration.ofMillis(10000).toNanos()); + } + + @Test + public void configureWhenAllMaximumExpectedValueSetShouldSetMaximumExpectedValueToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.maximum-expected-value.all=5000")); + assertThat(filter.configure(createMeterId("spring.boot"), + DistributionStatisticConfig.DEFAULT).getMaximumExpectedValue()) + .isEqualTo(Duration.ofMillis(5000).toNanos()); + } + + @Test + public void configureWhenMaximumExpectedValueDurationShouldOnlyApplyToTimer() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.maximum-expected-value.all=15s")); + Meter.Id timer = createMeterId("spring.boot", Meter.Type.TIMER); + Meter.Id summary = createMeterId("spring.boot", Meter.Type.DISTRIBUTION_SUMMARY); + Meter.Id counter = createMeterId("spring.boot", Meter.Type.COUNTER); + assertThat(filter.configure(timer, DistributionStatisticConfig.DEFAULT) + .getMaximumExpectedValue()).isEqualTo(Duration.ofMillis(15000).toNanos()); + assertThat(filter.configure(summary, DistributionStatisticConfig.DEFAULT) + .getMaximumExpectedValue()).isEqualTo(Long.MAX_VALUE); + assertThat(filter.configure(counter, DistributionStatisticConfig.DEFAULT) + .getMaximumExpectedValue()).isEqualTo(Long.MAX_VALUE); + } + private Id createMeterId(String name) { Meter.Type meterType = Type.TIMER; return createMeterId(name, meterType); diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index ad6cdfe920bd..5669386e01ea 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1378,6 +1378,8 @@ content into your application. Rather, pick only the properties that you need. management.metrics.binders.processor.enabled=true # Whether to enable processor metrics. management.metrics.binders.uptime.enabled=true # Whether to enable uptime metrics. management.metrics.distribution.percentiles-histogram.*= # Whether meter IDs starting with the specified name should publish percentile histograms. + management.metrics.distribution.minimum-expected-value.*= # Minimum limit on the histogram buckets for IDs starting with the specified name. The longest match wins, the key `all` can also be used to configure all meters. + management.metrics.distribution.maximum-expected-value.*= # Maximum limit on the histogram buckets for IDs starting with the specified name. The longest match wins, the key `all` can also be used to configure all meters. management.metrics.distribution.percentiles.*= # Specific computed non-aggregable percentiles to ship to the backend for meter IDs starting-with the specified name. management.metrics.distribution.sla.*= # Specific SLA boundaries for meter IDs starting-with the specified name. The longest match wins, the key `all` can also be used to configure all meters. management.metrics.enable.*= # Whether meter IDs starting-with the specified name should be enabled. The longest match wins, the key `all` can also be used to configure all meters. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index b11ef639d6a9..1837c37a389e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -1930,6 +1930,10 @@ The following properties allow per-meter customization: | Whether to publish a histogram suitable for computing aggregable (across dimension) percentile approximations. +| `management.metrics.distribution.minimum-expected-value` +| `management.metrics.distribution.maximum-expected-value` +| Publish less histogram buckets by clamping the range of expected values. + | `management.metrics.distribution.percentiles` | Publish percentile values computed in your application