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
This commit is contained in:
Andy Wilkinson
2026-04-15 15:01:41 +01:00
parent 60c7922b20
commit 2f73d15e35
4 changed files with 57 additions and 10 deletions
@@ -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();
@@ -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<String, ServiceLevelObjectiveBoundary[]> 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<String, String> 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<String, String> maximumExpectedValue = new LinkedHashMap<>();
@@ -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();
}
@@ -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(