Skip to content

Commit e37145a

Browse files
committed
Merge pull request #14139 from alexanderabramov
* pr/14139: Polish "Improve Micrometer histogram properties support" Improve Micrometer histogram properties support
2 parents 5607fca + 0ff1b25 commit e37145a

File tree

9 files changed

+299
-93
lines changed

9 files changed

+299
-93
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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* {@link ConfigurationProperties} for configuring Micrometer-based metrics.
2626
*
2727
* @author Jon Schneider
28+
* @author Alexander Abramov
2829
* @since 2.0.0
2930
*/
3031
@ConfigurationProperties("management.metrics")
@@ -198,6 +199,20 @@ public static class Distribution {
198199
*/
199200
private final Map<String, ServiceLevelAgreementBoundary[]> sla = new LinkedHashMap<>();
200201

202+
/**
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).
206+
*/
207+
private final Map<String, String> minimumExpectedValue = new LinkedHashMap<>();
208+
209+
/**
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).
213+
*/
214+
private final Map<String, String> maximumExpectedValue = new LinkedHashMap<>();
215+
201216
public Map<String, Boolean> getPercentilesHistogram() {
202217
return this.percentilesHistogram;
203218
}
@@ -210,6 +225,14 @@ public Map<String, ServiceLevelAgreementBoundary[]> getSla() {
210225
return this.sla;
211226
}
212227

228+
public Map<String, String> getMinimumExpectedValue() {
229+
return this.minimumExpectedValue;
230+
}
231+
232+
public Map<String, String> getMaximumExpectedValue() {
233+
return this.maximumExpectedValue;
234+
}
235+
213236
}
214237

215238
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* @author Phillip Webb
4242
* @author Stephane Nicoll
4343
* @author Artsiom Yudovin
44+
* @author Alexander Abramov
4445
* @since 2.0.0
4546
*/
4647
public class PropertiesMeterFilter implements MeterFilter {
@@ -87,6 +88,10 @@ public DistributionStatisticConfig configure(Meter.Id id,
8788
.percentiles(
8889
lookupWithFallbackToAll(distribution.getPercentiles(), id, null))
8990
.sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null)))
91+
.minimumExpectedValue(convertMeterValue(id.getType(),
92+
lookup(distribution.getMinimumExpectedValue(), id, null)))
93+
.maximumExpectedValue(convertMeterValue(id.getType(),
94+
lookup(distribution.getMaximumExpectedValue(), id, null)))
9095
.build().merge(config);
9196
}
9297

@@ -100,6 +105,10 @@ private long[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[]
100105
return (converted.length != 0) ? converted : null;
101106
}
102107

108+
private Long convertMeterValue(Meter.Type meterType, String value) {
109+
return (value != null) ? MeterValue.valueOf(value).getValue(meterType) : null;
110+
}
111+
103112
private <T> T lookup(Map<String, T> values, Id id, T defaultValue) {
104113
if (values.isEmpty()) {
105114
return 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)