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 3bcbd3d0f0..dcd309a0ad 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 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * {@link ConfigurationProperties} for configuring Micrometer-based metrics. * * @author Jon Schneider + * @author Alexander Abramov * @since 2.0.0 */ @ConfigurationProperties("management.metrics") @@ -198,6 +199,22 @@ public class MetricsProperties { */ 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; } @@ -210,6 +227,14 @@ public class MetricsProperties { 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 e514c36ffb..d29a57b1c7 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 @@ -41,6 +41,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Stephane Nicoll * @author Artsiom Yudovin + * @author Alexander Abramov * @since 2.0.0 */ public class PropertiesMeterFilter implements MeterFilter { @@ -87,6 +88,10 @@ public class PropertiesMeterFilter implements MeterFilter { .percentiles( lookupWithFallbackToAll(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); } @@ -100,6 +105,10 @@ public class PropertiesMeterFilter implements MeterFilter { 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 9be02cc6ac..5fd2d2f3f5 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; @@ -251,6 +252,110 @@ public class PropertiesMeterFilterTests { .containsExactly(4000000, 5000000, 6000000); } + @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 0e690de9d0..ef142175b6 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 @@ -1380,6 +1380,8 @@ content into your application. Rather, pick only the properties that you need. # 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. 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 fbef7f0642..343a9181ff 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 @@ -2051,6 +2051,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