Skip to content

Commit 0ff1b25

Browse files
committed
Polish "Improve Micrometer histogram properties support"
Closes gh-14139
1 parent c1c79ab commit 0ff1b25

File tree

8 files changed

+225
-164
lines changed

8 files changed

+225
-164
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics;
18+
19+
import java.time.Duration;
20+
import java.util.concurrent.TimeUnit;
21+
22+
import io.micrometer.core.instrument.Meter.Type;
23+
24+
import org.springframework.boot.convert.DurationStyle;
25+
26+
/**
27+
* A meter value that is used when configuring micrometer. Can be a String representation
28+
* of either a {@link Long} (applicable to timers and distribution summaries) or a
29+
* {@link Duration} (applicable to only timers).
30+
*
31+
* @author Phillip Webb
32+
*/
33+
final class MeterValue {
34+
35+
private final Object value;
36+
37+
MeterValue(long value) {
38+
this.value = value;
39+
}
40+
41+
MeterValue(Duration value) {
42+
this.value = value;
43+
}
44+
45+
/**
46+
* Return the underlying value of the SLA in form suitable to apply to the given meter
47+
* type.
48+
* @param meterType the meter type
49+
* @return the value or {@code null} if the value cannot be applied
50+
*/
51+
public Long getValue(Type meterType) {
52+
if (meterType == Type.DISTRIBUTION_SUMMARY) {
53+
return getDistributionSummaryValue();
54+
}
55+
if (meterType == Type.TIMER) {
56+
return getTimerValue();
57+
}
58+
return null;
59+
}
60+
61+
private Long getDistributionSummaryValue() {
62+
if (this.value instanceof Long) {
63+
return (Long) this.value;
64+
}
65+
return null;
66+
}
67+
68+
private Long getTimerValue() {
69+
if (this.value instanceof Long) {
70+
return TimeUnit.MILLISECONDS.toNanos((long) this.value);
71+
}
72+
if (this.value instanceof Duration) {
73+
return ((Duration) this.value).toNanos();
74+
}
75+
return null;
76+
}
77+
78+
/**
79+
* Return a new {@link MeterValue} instance for the given String value. The value may
80+
* contain a simple number, or a {@link DurationStyle duration style string}.
81+
* @param value the source value
82+
* @return a {@link MeterValue} instance
83+
*/
84+
public static MeterValue valueOf(String value) {
85+
if (isNumber(value)) {
86+
return new MeterValue(Long.parseLong(value));
87+
}
88+
return new MeterValue(DurationStyle.detectAndParse(value));
89+
}
90+
91+
/**
92+
* Return a new {@link MeterValue} instance for the given long value.
93+
* @param value the source value
94+
* @return a {@link MeterValue} instance
95+
*/
96+
public static MeterValue valueOf(long value) {
97+
return new MeterValue(value);
98+
}
99+
100+
private static boolean isNumber(String value) {
101+
return value.chars().allMatch(Character::isDigit);
102+
}
103+
104+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -200,20 +200,18 @@ public static class Distribution {
200200
private final Map<String, ServiceLevelAgreementBoundary[]> sla = new LinkedHashMap<>();
201201

202202
/**
203-
* The minimum value that this distribution summary is expected to observe.
204-
* Controls the number of buckets shipped by percentilesHistogram. Can be
205-
* specified as a long or as a Duration value (for timer meters, defaulting to ms
206-
* if no unit specified).
203+
* Minimum value that meter IDs starting-with the specified name are expected to
204+
* observe. The longest match wins. Values can be specified as a long or as a
205+
* Duration value (for timer meters, defaulting to ms if no unit specified).
207206
*/
208-
private final Map<String, ServiceLevelAgreementBoundary> minimumExpectedValue = new LinkedHashMap<>();
207+
private final Map<String, String> minimumExpectedValue = new LinkedHashMap<>();
209208

210209
/**
211-
* The maximum value that this distribution summary is expected to observe.
212-
* Controls the number of buckets shipped by percentilesHistogram. Can be
213-
* specified as a long or as a Duration value (for timer meters, defaulting to ms
214-
* if no unit specified).
210+
* Maximum value that meter IDs starting-with the specified name are expected to
211+
* observe. The longest match wins. Values can be specified as a long or as a
212+
* Duration value (for timer meters, defaulting to ms if no unit specified).
215213
*/
216-
private final Map<String, ServiceLevelAgreementBoundary> maximumExpectedValue = new LinkedHashMap<>();
214+
private final Map<String, String> maximumExpectedValue = new LinkedHashMap<>();
217215

218216
public Map<String, Boolean> getPercentilesHistogram() {
219217
return this.percentilesHistogram;
@@ -227,11 +225,11 @@ public Map<String, ServiceLevelAgreementBoundary[]> getSla() {
227225
return this.sla;
228226
}
229227

230-
public Map<String, ServiceLevelAgreementBoundary> getMinimumExpectedValue() {
228+
public Map<String, String> getMinimumExpectedValue() {
231229
return this.minimumExpectedValue;
232230
}
233231

234-
public Map<String, ServiceLevelAgreementBoundary> getMaximumExpectedValue() {
232+
public Map<String, String> getMaximumExpectedValue() {
235233
return this.maximumExpectedValue;
236234
}
237235

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ public DistributionStatisticConfig configure(Meter.Id id,
8888
.percentiles(
8989
lookupWithFallbackToAll(distribution.getPercentiles(), id, null))
9090
.sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null)))
91-
.minimumExpectedValue(convertSla(id.getType(),
91+
.minimumExpectedValue(convertMeterValue(id.getType(),
9292
lookup(distribution.getMinimumExpectedValue(), id, null)))
93-
.maximumExpectedValue(convertSla(id.getType(),
93+
.maximumExpectedValue(convertMeterValue(id.getType(),
9494
lookup(distribution.getMaximumExpectedValue(), id, null)))
9595
.build().merge(config);
9696
}
@@ -105,8 +105,8 @@ private long[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[]
105105
return (converted.length != 0) ? converted : null;
106106
}
107107

108-
private Long convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary sla) {
109-
return (sla != null) ? sla.getValue(meterType) : null;
108+
private Long convertMeterValue(Meter.Type meterType, String value) {
109+
return (value != null) ? MeterValue.valueOf(value).getValue(meterType) : null;
110110
}
111111

112112
private <T> T lookup(Map<String, T> values, Id id, T defaultValue) {

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/ServiceLevelAgreementBoundary.java

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@
1717
package org.springframework.boot.actuate.autoconfigure.metrics;
1818

1919
import java.time.Duration;
20-
import java.util.concurrent.TimeUnit;
2120

2221
import io.micrometer.core.instrument.Meter;
23-
import io.micrometer.core.instrument.Meter.Type;
24-
25-
import org.springframework.boot.convert.DurationStyle;
2622

2723
/**
2824
* A service level agreement boundary for use when configuring micrometer. Can be
@@ -34,13 +30,9 @@
3430
*/
3531
public final class ServiceLevelAgreementBoundary {
3632

37-
private final Object value;
38-
39-
ServiceLevelAgreementBoundary(long value) {
40-
this.value = value;
41-
}
33+
private final MeterValue value;
4234

43-
ServiceLevelAgreementBoundary(Duration value) {
35+
ServiceLevelAgreementBoundary(MeterValue value) {
4436
this.value = value;
4537
}
4638

@@ -51,37 +43,7 @@ public final class ServiceLevelAgreementBoundary {
5143
* @return the value or {@code null} if the value cannot be applied
5244
*/
5345
public Long getValue(Meter.Type meterType) {
54-
if (meterType == Type.DISTRIBUTION_SUMMARY) {
55-
return getDistributionSummaryValue();
56-
}
57-
if (meterType == Type.TIMER) {
58-
return getTimerValue();
59-
}
60-
return null;
61-
}
62-
63-
private Long getDistributionSummaryValue() {
64-
if (this.value instanceof Long) {
65-
return (Long) this.value;
66-
}
67-
return null;
68-
}
69-
70-
private Long getTimerValue() {
71-
if (this.value instanceof Long) {
72-
return TimeUnit.MILLISECONDS.toNanos((long) this.value);
73-
}
74-
if (this.value instanceof Duration) {
75-
return ((Duration) this.value).toNanos();
76-
}
77-
return null;
78-
}
79-
80-
public static ServiceLevelAgreementBoundary valueOf(String value) {
81-
if (isNumber(value)) {
82-
return new ServiceLevelAgreementBoundary(Long.parseLong(value));
83-
}
84-
return new ServiceLevelAgreementBoundary(DurationStyle.detectAndParse(value));
46+
return this.value.getValue(meterType);
8547
}
8648

8749
/**
@@ -91,18 +53,17 @@ public static ServiceLevelAgreementBoundary valueOf(String value) {
9153
* @return a {@link ServiceLevelAgreementBoundary} instance
9254
*/
9355
public static ServiceLevelAgreementBoundary valueOf(long value) {
94-
return new ServiceLevelAgreementBoundary(value);
56+
return new ServiceLevelAgreementBoundary(MeterValue.valueOf(value));
9557
}
9658

9759
/**
98-
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given String
99-
* value. The value may contain a simple number, or a {@link DurationStyle duration
100-
* style string}.
60+
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given long
61+
* value.
10162
* @param value the source value
10263
* @return a {@link ServiceLevelAgreementBoundary} instance
10364
*/
104-
private static boolean isNumber(String value) {
105-
return value.chars().allMatch(Character::isDigit);
65+
public static ServiceLevelAgreementBoundary valueOf(String value) {
66+
return new ServiceLevelAgreementBoundary(MeterValue.valueOf(value));
10667
}
10768

10869
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics;
18+
19+
import io.micrometer.core.instrument.Meter.Type;
20+
import org.junit.Test;
21+
22+
import org.springframework.boot.context.properties.bind.Bindable;
23+
import org.springframework.boot.context.properties.bind.Binder;
24+
import org.springframework.boot.test.util.TestPropertyValues;
25+
import org.springframework.mock.env.MockEnvironment;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
/**
30+
* Tests for {@link MeterValue}.
31+
*
32+
* @author Phillip Webb
33+
*/
34+
public class MeterValueTests {
35+
36+
@Test
37+
public void getValueForDistributionSummaryWhenFromLongShouldReturnLongValue() {
38+
MeterValue meterValue = MeterValue.valueOf(123L);
39+
assertThat(meterValue.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123);
40+
}
41+
42+
@Test
43+
public void getValueForDistributionSummaryWhenFromNumberStringShouldReturnLongValue() {
44+
MeterValue meterValue = MeterValue.valueOf("123");
45+
assertThat(meterValue.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123);
46+
}
47+
48+
@Test
49+
public void getValueForDistributionSummaryWhenFromDurationStringShouldReturnNull() {
50+
MeterValue meterValue = MeterValue.valueOf("123ms");
51+
assertThat(meterValue.getValue(Type.DISTRIBUTION_SUMMARY)).isNull();
52+
}
53+
54+
@Test
55+
public void getValueForTimerWhenFromLongShouldReturnMsToNanosValue() {
56+
MeterValue meterValue = MeterValue.valueOf(123L);
57+
assertThat(meterValue.getValue(Type.TIMER)).isEqualTo(123000000);
58+
}
59+
60+
@Test
61+
public void getValueForTimerWhenFromNumberStringShouldMsToNanosValue() {
62+
MeterValue meterValue = MeterValue.valueOf("123");
63+
assertThat(meterValue.getValue(Type.TIMER)).isEqualTo(123000000);
64+
}
65+
66+
@Test
67+
public void getValueForTimerWhenFromDurationStringShouldReturnDurationNanos() {
68+
MeterValue meterValue = MeterValue.valueOf("123ms");
69+
assertThat(meterValue.getValue(Type.TIMER)).isEqualTo(123000000);
70+
}
71+
72+
@Test
73+
public void getValueForOthersShouldReturnNull() {
74+
MeterValue meterValue = MeterValue.valueOf("123");
75+
assertThat(meterValue.getValue(Type.COUNTER)).isNull();
76+
assertThat(meterValue.getValue(Type.GAUGE)).isNull();
77+
assertThat(meterValue.getValue(Type.LONG_TASK_TIMER)).isNull();
78+
assertThat(meterValue.getValue(Type.OTHER)).isNull();
79+
}
80+
81+
@Test
82+
public void valueOfShouldWorkInBinder() {
83+
MockEnvironment environment = new MockEnvironment();
84+
TestPropertyValues.of("duration=10ms", "long=20").applyTo(environment);
85+
assertThat(Binder.get(environment).bind("duration", Bindable.of(MeterValue.class))
86+
.get().getValue(Type.TIMER)).isEqualTo(10000000);
87+
assertThat(Binder.get(environment).bind("long", Bindable.of(MeterValue.class))
88+
.get().getValue(Type.TIMER)).isEqualTo(20000000);
89+
}
90+
91+
}

0 commit comments

Comments
 (0)