From 2f73d15e353912368787d2e57cc14d1fea4a5c01 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 15 Apr 2026 14:52:14 +0100 Subject: [PATCH] Update distribution properties to support long task timers Previously, LongTaskTimers were ignored when configuring a distribution's min and max expected values and SLO boundaries. This commit adds support, aligning them with the existing support for Timers. Fixes gh-49190 --- .../metrics/autoconfigure/MeterValue.java | 6 ++--- .../autoconfigure/MetricsProperties.java | 15 ++++++----- .../autoconfigure/MeterValueTests.java | 19 ++++++++++++- .../PropertiesMeterFilterTests.java | 27 +++++++++++++++++++ 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValue.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValue.java index 440a38f5b85..5afa8f4d39c 100644 --- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValue.java +++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValue.java @@ -27,8 +27,8 @@ import org.springframework.util.StringUtils; /** * A meter value that is used when configuring micrometer. Can be a String representation - * of either a {@link Double} (applicable to timers and distribution summaries) or a - * {@link Duration} (applicable to only timers). + * of either a {@link Double} (applicable to timers, long task timers, and distribution + * summaries) or a {@link Duration} (applicable to timers and long task timers). * * @author Phillip Webb * @author Stephane Nicoll @@ -55,7 +55,7 @@ public final class MeterValue { if (meterType == Type.DISTRIBUTION_SUMMARY) { return getDistributionSummaryValue(); } - if (meterType == Type.TIMER) { + if (meterType == Type.TIMER || meterType == Type.LONG_TASK_TIMER) { Long timerValue = getTimerValue(); if (timerValue != null) { return timerValue.doubleValue(); diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsProperties.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsProperties.java index ecf70096659..e5b4017723f 100644 --- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsProperties.java +++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsProperties.java @@ -203,22 +203,25 @@ public class MetricsProperties { /** * Specific service-level objective boundaries for meter IDs starting with the * specified name. The longest match wins. Counters will be published for each - * specified boundary. Values can be specified as a double or as a Duration value - * (for timer meters, defaulting to ms if no unit specified). + * specified boundary. Values can be specified as a double or, for timer and + * long-task timer meters, as a Duration value defaulting to ms if no unit + * specified. */ private final Map slo = new LinkedHashMap<>(); /** * Minimum value that meter IDs starting with the specified name are expected to - * observe. The longest match wins. Values can be specified as a double or as a - * Duration value (for timer meters, defaulting to ms if no unit specified). + * observe. The longest match wins. Values can be specified as a double or, for + * timer and long-task timer meters, as a Duration value defaulting to ms if no + * unit specified. */ private final Map minimumExpectedValue = new LinkedHashMap<>(); /** * Maximum value that meter IDs starting with the specified name are expected to - * observe. The longest match wins. Values can be specified as a double or as a - * Duration value (for timer meters, defaulting to ms if no unit specified). + * observe. The longest match wins. Values can be specified as a double or, for + * timer and long-task timer meters, as a Duration value defaulting to ms if no + * unit specified. */ private final Map maximumExpectedValue = new LinkedHashMap<>(); diff --git a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValueTests.java b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValueTests.java index c5d04c0ed48..b65b755577f 100644 --- a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValueTests.java +++ b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterValueTests.java @@ -82,12 +82,29 @@ class MeterValueTests { assertThat(meterValue.getValue(Type.TIMER)).isEqualTo(123000000); } + @Test + void getValueForLongTaskTimerWhenFromNumberShouldReturnMsToNanosValue() { + MeterValue meterValue = MeterValue.valueOf(123d); + assertThat(meterValue.getValue(Type.LONG_TASK_TIMER)).isEqualTo(123000000); + } + + @Test + void getValueForLongTaskTimerWhenFromNumberStringShouldReturnMsToNanosValue() { + MeterValue meterValue = MeterValue.valueOf("123"); + assertThat(meterValue.getValue(Type.LONG_TASK_TIMER)).isEqualTo(123000000); + } + + @Test + void getValueForLongTaskTimerWhenFromDurationStringShouldReturnDurationNanos() { + MeterValue meterValue = MeterValue.valueOf("123ms"); + assertThat(meterValue.getValue(Type.LONG_TASK_TIMER)).isEqualTo(123000000); + } + @Test void getValueForOthersShouldReturnNull() { MeterValue meterValue = MeterValue.valueOf("123"); assertThat(meterValue.getValue(Type.COUNTER)).isNull(); assertThat(meterValue.getValue(Type.GAUGE)).isNull(); - assertThat(meterValue.getValue(Type.LONG_TASK_TIMER)).isNull(); assertThat(meterValue.getValue(Type.OTHER)).isNull(); } diff --git a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/PropertiesMeterFilterTests.java b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/PropertiesMeterFilterTests.java index fcd71145e14..35bc86d8404 100644 --- a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/PropertiesMeterFilterTests.java +++ b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/PropertiesMeterFilterTests.java @@ -213,6 +213,15 @@ class PropertiesMeterFilterTests { .getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000); } + @Test + void configureWhenLongTaskTimerMeterHasSloShouldSetSloToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.slo.spring.boot=1,2,3")); + assertThat(filter + .configure(createMeterId("spring.boot", Meter.Type.LONG_TASK_TIMER), DistributionStatisticConfig.DEFAULT) + .getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000); + } + @Test void configureWhenDistributionSummaryMeterHasSloShouldSetSloToValue() { PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties("distribution.slo.example=1,2,3")); @@ -244,6 +253,15 @@ class PropertiesMeterFilterTests { .getMinimumExpectedValueAsDouble()).isEqualTo(Duration.ofMillis(10).toNanos()); } + @Test + void configureWhenLongTaskTimerMeterHasMinimumExpectedValueShouldSetMinimumExpectedToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.minimum-expected-value.spring.boot=10")); + assertThat(filter + .configure(createMeterId("spring.boot", Meter.Type.LONG_TASK_TIMER), DistributionStatisticConfig.DEFAULT) + .getMinimumExpectedValueAsDouble()).isEqualTo(Duration.ofMillis(10).toNanos()); + } + @Test void configureWhenDistributionSummaryMeterHasMinimumExpectedValueShouldSetMinimumExpectedToValue() { PropertiesMeterFilter filter = new PropertiesMeterFilter( @@ -278,6 +296,15 @@ class PropertiesMeterFilterTests { .getMaximumExpectedValueAsDouble()).isEqualTo(Duration.ofMillis(5000).toNanos()); } + @Test + void configureWhenLongTaskTimerMeterHasMaximumExpectedValueShouldSetMaximumExpectedToValue() { + PropertiesMeterFilter filter = new PropertiesMeterFilter( + createProperties("distribution.maximum-expected-value.spring.boot=5000")); + assertThat(filter + .configure(createMeterId("spring.boot", Meter.Type.LONG_TASK_TIMER), DistributionStatisticConfig.DEFAULT) + .getMaximumExpectedValueAsDouble()).isEqualTo(Duration.ofMillis(5000).toNanos()); + } + @Test void configureWhenDistributionSummaryMeterHasMaximumExpectedValueShouldSetMaximumExpectedToValue() { PropertiesMeterFilter filter = new PropertiesMeterFilter(