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 c4c0b2c2f4..61583ff424 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 @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics; +import java.time.Duration; import java.util.LinkedHashMap; import java.util.Map; @@ -296,6 +297,21 @@ public class MetricsProperties { */ private final Map maximumExpectedValue = new LinkedHashMap<>(); + /** + * Maximum amount of time that samples for meter IDs starting with the specified + * name are accumulated to decaying distribution statistics before they are reset + * and rotated. The longest match wins, the key `all` can also be used to + * configure all meters. + */ + private final Map expiry = new LinkedHashMap<>(); + + /** + * Number of histograms for meter IDs starting with the specified name to keep in + * the ring buffer. The longest match wins, the key `all` can also be used to + * configure all meters. + */ + private final Map bufferLength = new LinkedHashMap<>(); + public Map getPercentilesHistogram() { return this.percentilesHistogram; } @@ -316,6 +332,14 @@ public class MetricsProperties { return this.maximumExpectedValue; } + public Map getExpiry() { + return this.expiry; + } + + public Map getBufferLength() { + return this.bufferLength; + } + } } 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 7689480c7e..88e7d8dece 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -89,7 +89,8 @@ public class PropertiesMeterFilter implements MeterFilter { convertMeterValue(id.getType(), lookup(distribution.getMinimumExpectedValue(), id, null))) .maximumExpectedValue( convertMeterValue(id.getType(), lookup(distribution.getMaximumExpectedValue(), id, null))) - .build().merge(config); + .expiry(lookupWithFallbackToAll(distribution.getExpiry(), id, null)) + .bufferLength(lookupWithFallbackToAll(distribution.getBufferLength(), id, null)).build().merge(config); } private double[] convertServiceLevelObjectives(Meter.Type meterType, ServiceLevelObjectiveBoundary[] slo) { 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 7d6611df10..b8a748b966 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 @@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * @author Phillip Webb * @author Jon Schneider * @author Artsiom Yudovin + * @author Leo Li */ class PropertiesMeterFilterTests { @@ -275,6 +276,71 @@ class PropertiesMeterFilterTests { .getMaximumExpectedValueAsDouble()).isEqualTo(Duration.ofMillis(10000).toNanos()); } + @Test + void configureWhenHasExpiryShouldSetExpiryToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.expiry[spring.boot]=5ms")); + assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getExpiry()) + .isEqualTo(Duration.ofMillis(5)); + } + + @Test + void configureWhenHasHigherExpiryShouldSetExpiryToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties("distribution.expiry.spring=5ms")); + assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getExpiry()) + .isEqualTo(Duration.ofMillis(5)); + } + + @Test + void configureWhenHasHigherExpiryAndLowerShouldSetExpiryToHigher() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.expiry.spring=5ms", "distribution.expiry[spring.boot]=10ms")); + assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getExpiry()) + .isEqualTo(Duration.ofMillis(10)); + } + + @Test + void configureWhenAllExpirySetShouldSetExpiryToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties("distribution.expiry.all=5ms")); + assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getExpiry()) + .isEqualTo(Duration.ofMillis(5)); + } + + @Test + void configureWhenHasBufferLengthShouldSetBufferLengthToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.buffer-length.spring.boot=3")); + assertThat( + filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getBufferLength()) + .isEqualTo(3); + } + + @Test + void configureWhenHasHigherBufferLengthShouldSetBufferLengthToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.buffer-length.spring=3")); + assertThat( + filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getBufferLength()) + .isEqualTo(3); + } + + @Test + void configureWhenHasHigherBufferLengthAndLowerShouldSetBufferLengthToHigher() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.buffer-length.spring=2", "distribution.buffer-length.spring.boot=3")); + assertThat( + filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getBufferLength()) + .isEqualTo(3); + } + + @Test + void configureWhenAllBufferLengthSetShouldSetBufferLengthToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties("distribution.buffer-length.all=3")); + assertThat( + filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getBufferLength()) + .isEqualTo(3); + } + private Id createMeterId(String name) { Meter.Type meterType = Type.TIMER; return createMeterId(name, meterType); diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc index a9e6da0e3a..bac1fbd914 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc @@ -1153,6 +1153,10 @@ The following properties allow per-meter customization: | configprop:management.metrics.distribution.percentiles[] | Publish percentile values computed in your application +| configprop:management.metrics.distribution.expiry[], configprop:management.metrics.distribution.buffer-length[] +| Give greater weight to recent samples by accumulating them in ring buffers which rotate after a configurable expiry, with a +configurable buffer length. + | configprop:management.metrics.distribution.slo[] | Publish a cumulative histogram with buckets defined by your service-level objectives. |===