Rename Service Level Agreement to Service Level Objective
This commit harmonizes our integration of Micrometer's Service Level objectives. Closes gh-21076
This commit is contained in:
parent
489f9b0e58
commit
fe90b2a251
|
@ -20,6 +20,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,12 +233,12 @@ public class MetricsProperties {
|
||||||
private final Map<String, double[]> percentiles = new LinkedHashMap<>();
|
private final Map<String, double[]> percentiles = new LinkedHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific SLA boundaries for meter IDs starting-with the specified name. The
|
* Specific Service Level Objectives boundaries for meter IDs starting-with the
|
||||||
* longest match wins. Counters will be published for each specified boundary.
|
* specified name. The longest match wins. Counters will be published for each
|
||||||
* Values can be specified as a long or as a Duration value (for timer meters,
|
* specified boundary. Values can be specified as a long or as a Duration value
|
||||||
* defaulting to ms if no unit specified).
|
* (for timer meters, defaulting to ms if no unit specified).
|
||||||
*/
|
*/
|
||||||
private final Map<String, ServiceLevelAgreementBoundary[]> sla = new LinkedHashMap<>();
|
private final Map<String, ServiceLevelObjectiveBoundary[]> slo = new LinkedHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum value that meter IDs starting-with the specified name are expected to
|
* Minimum value that meter IDs starting-with the specified name are expected to
|
||||||
|
@ -261,8 +262,14 @@ public class MetricsProperties {
|
||||||
return this.percentiles;
|
return this.percentiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, ServiceLevelAgreementBoundary[]> getSla() {
|
@Deprecated
|
||||||
return this.sla;
|
@DeprecatedConfigurationProperty(replacement = "management.metrics.distribution.slo")
|
||||||
|
public Map<String, ServiceLevelObjectiveBoundary[]> getSla() {
|
||||||
|
return this.slo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ServiceLevelObjectiveBoundary[]> getSlo() {
|
||||||
|
return this.slo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getMinimumExpectedValue() {
|
public Map<String, String> getMinimumExpectedValue() {
|
||||||
|
|
|
@ -83,7 +83,8 @@ public class PropertiesMeterFilter implements MeterFilter {
|
||||||
return DistributionStatisticConfig.builder()
|
return DistributionStatisticConfig.builder()
|
||||||
.percentilesHistogram(lookupWithFallbackToAll(distribution.getPercentilesHistogram(), id, null))
|
.percentilesHistogram(lookupWithFallbackToAll(distribution.getPercentilesHistogram(), id, null))
|
||||||
.percentiles(lookupWithFallbackToAll(distribution.getPercentiles(), id, null))
|
.percentiles(lookupWithFallbackToAll(distribution.getPercentiles(), id, null))
|
||||||
.sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null)))
|
.serviceLevelObjectives(
|
||||||
|
convertServiceLevelObjectives(id.getType(), lookup(distribution.getSlo(), id, null)))
|
||||||
.minimumExpectedValue(
|
.minimumExpectedValue(
|
||||||
convertMeterValue(id.getType(), lookup(distribution.getMinimumExpectedValue(), id, null)))
|
convertMeterValue(id.getType(), lookup(distribution.getMinimumExpectedValue(), id, null)))
|
||||||
.maximumExpectedValue(
|
.maximumExpectedValue(
|
||||||
|
@ -91,11 +92,11 @@ public class PropertiesMeterFilter implements MeterFilter {
|
||||||
.build().merge(config);
|
.build().merge(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[] sla) {
|
private double[] convertServiceLevelObjectives(Meter.Type meterType, ServiceLevelObjectiveBoundary[] slo) {
|
||||||
if (sla == null) {
|
if (slo == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
double[] converted = Arrays.stream(sla).map((candidate) -> candidate.getValue(meterType))
|
double[] converted = Arrays.stream(slo).map((candidate) -> candidate.getValue(meterType))
|
||||||
.filter(Objects::nonNull).mapToDouble(Double::doubleValue).toArray();
|
.filter(Objects::nonNull).mapToDouble(Double::doubleValue).toArray();
|
||||||
return (converted.length != 0) ? converted : null;
|
return (converted.length != 0) ? converted : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,14 @@ import io.micrometer.core.instrument.Meter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service level agreement boundary for use when configuring Micrometer. Can be
|
* A service level agreement boundary for use when configuring Micrometer. Can be
|
||||||
* specified as either a {@link Double} (applicable to timers and distribution summaries)
|
* specified as either a {@link Long} (applicable to timers and distribution summaries) or
|
||||||
* or a {@link Duration} (applicable to only timers).
|
* a {@link Duration} (applicable to only timers).
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
|
* @deprecated as of 2.3.0 in favor of {@link ServiceLevelObjectiveBoundary}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class ServiceLevelAgreementBoundary {
|
public final class ServiceLevelAgreementBoundary {
|
||||||
|
|
||||||
private final MeterValue value;
|
private final MeterValue value;
|
||||||
|
@ -42,17 +44,18 @@ public final class ServiceLevelAgreementBoundary {
|
||||||
* @param meterType the meter type
|
* @param meterType the meter type
|
||||||
* @return the value or {@code null} if the value cannot be applied
|
* @return the value or {@code null} if the value cannot be applied
|
||||||
*/
|
*/
|
||||||
public Double getValue(Meter.Type meterType) {
|
public Long getValue(Meter.Type meterType) {
|
||||||
return this.value.getValue(meterType);
|
Double value = this.value.getValue(meterType);
|
||||||
|
return (value != null) ? value.longValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given double
|
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given long
|
||||||
* value.
|
* value.
|
||||||
* @param value the source value
|
* @param value the source value
|
||||||
* @return a {@link ServiceLevelAgreementBoundary} instance
|
* @return a {@link ServiceLevelAgreementBoundary} instance
|
||||||
*/
|
*/
|
||||||
public static ServiceLevelAgreementBoundary valueOf(double value) {
|
public static ServiceLevelAgreementBoundary valueOf(long value) {
|
||||||
return new ServiceLevelAgreementBoundary(MeterValue.valueOf(value));
|
return new ServiceLevelAgreementBoundary(MeterValue.valueOf(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2020 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Meter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service level objective boundary for use when configuring Micrometer. Can be
|
||||||
|
* specified as either a {@link Double} (applicable to timers and distribution summaries)
|
||||||
|
* or a {@link Duration} (applicable to only timers).
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 2.3.0
|
||||||
|
*/
|
||||||
|
public final class ServiceLevelObjectiveBoundary {
|
||||||
|
|
||||||
|
private final MeterValue value;
|
||||||
|
|
||||||
|
ServiceLevelObjectiveBoundary(MeterValue value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the underlying value of the SLO in form suitable to apply to the given meter
|
||||||
|
* type.
|
||||||
|
* @param meterType the meter type
|
||||||
|
* @return the value or {@code null} if the value cannot be applied
|
||||||
|
*/
|
||||||
|
public Double getValue(Meter.Type meterType) {
|
||||||
|
return this.value.getValue(meterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link ServiceLevelObjectiveBoundary} instance for the given double
|
||||||
|
* value.
|
||||||
|
* @param value the source value
|
||||||
|
* @return a {@link ServiceLevelObjectiveBoundary} instance
|
||||||
|
*/
|
||||||
|
public static ServiceLevelObjectiveBoundary valueOf(double value) {
|
||||||
|
return new ServiceLevelObjectiveBoundary(MeterValue.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link ServiceLevelObjectiveBoundary} instance for the given String
|
||||||
|
* value.
|
||||||
|
* @param value the source value
|
||||||
|
* @return a {@link ServiceLevelObjectiveBoundary} instance
|
||||||
|
*/
|
||||||
|
public static ServiceLevelObjectiveBoundary valueOf(String value) {
|
||||||
|
return new ServiceLevelObjectiveBoundary(MeterValue.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -204,29 +204,35 @@ class PropertiesMeterFilterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void configureWhenHasSlaShouldSetSlaToValue() {
|
@Deprecated
|
||||||
|
void configureWhenHasDeprecatedSlaShouldSetSlaToValue() {
|
||||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||||
createProperties("distribution.sla.spring.boot=1,2,3"));
|
createProperties("distribution.sla.spring.boot=1,2,3"));
|
||||||
assertThat(
|
assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT)
|
||||||
filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getSlaBoundaries())
|
.getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000);
|
||||||
.containsExactly(1000000, 2000000, 3000000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void configureWhenHasHigherSlaShouldSetPercentilesToValue() {
|
void configureWhenHasSloShouldSetSloToValue() {
|
||||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties("distribution.sla.spring=1,2,3"));
|
|
||||||
assertThat(
|
|
||||||
filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getSlaBoundaries())
|
|
||||||
.containsExactly(1000000, 2000000, 3000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void configureWhenHasHigherSlaAndLowerShouldSetSlaToHigher() {
|
|
||||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||||
createProperties("distribution.sla.spring=1,2,3", "distribution.sla.spring.boot=4,5,6"));
|
createProperties("distribution.slo.spring.boot=1,2,3"));
|
||||||
assertThat(
|
assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT)
|
||||||
filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getSlaBoundaries())
|
.getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000);
|
||||||
.containsExactly(4000000, 5000000, 6000000);
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configureWhenHasHigherSloShouldSetPercentilesToValue() {
|
||||||
|
PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties("distribution.slo.spring=1,2,3"));
|
||||||
|
assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT)
|
||||||
|
.getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configureWhenHasHigherSloAndLowerShouldSetSloToHigher() {
|
||||||
|
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||||
|
createProperties("distribution.slo.spring=1,2,3", "distribution.slo.spring.boot=4,5,6"));
|
||||||
|
assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT)
|
||||||
|
.getServiceLevelObjectiveBoundaries()).containsExactly(4000000, 5000000, 6000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -22,47 +22,47 @@ import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ServiceLevelAgreementBoundary}.
|
* Tests for {@link ServiceLevelObjectiveBoundary}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
class ServiceLevelAgreementBoundaryTests {
|
class ServiceLevelObjectiveBoundaryTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getValueForTimerWhenFromLongShouldReturnMsToNanosValue() {
|
void getValueForTimerWhenFromLongShouldReturnMsToNanosValue() {
|
||||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf(123L);
|
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf(123L);
|
||||||
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
|
assertThat(slo.getValue(Type.TIMER)).isEqualTo(123000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getValueForTimerWhenFromNumberStringShouldMsToNanosValue() {
|
void getValueForTimerWhenFromNumberStringShouldMsToNanosValue() {
|
||||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123");
|
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123");
|
||||||
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
|
assertThat(slo.getValue(Type.TIMER)).isEqualTo(123000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getValueForTimerWhenFromDurationStringShouldReturnDurationNanos() {
|
void getValueForTimerWhenFromDurationStringShouldReturnDurationNanos() {
|
||||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123ms");
|
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123ms");
|
||||||
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
|
assertThat(slo.getValue(Type.TIMER)).isEqualTo(123000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getValueForDistributionSummaryWhenFromDoubleShouldReturnDoubleValue() {
|
void getValueForDistributionSummaryWhenFromDoubleShouldReturnDoubleValue() {
|
||||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf(123.42);
|
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf(123.42);
|
||||||
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
|
assertThat(slo.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getValueForDistributionSummaryWhenFromStringShouldReturnDoubleValue() {
|
void getValueForDistributionSummaryWhenFromStringShouldReturnDoubleValue() {
|
||||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123.42");
|
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123.42");
|
||||||
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
|
assertThat(slo.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getValueForDistributionSummaryWhenFromDurationShouldReturnNull() {
|
void getValueForDistributionSummaryWhenFromDurationShouldReturnNull() {
|
||||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123ms");
|
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123ms");
|
||||||
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isNull();
|
assertThat(slo.getValue(Type.DISTRIBUTION_SUMMARY)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -2131,8 +2131,8 @@ The following properties allow per-meter customization:
|
||||||
| configprop:management.metrics.distribution.percentiles[]
|
| configprop:management.metrics.distribution.percentiles[]
|
||||||
| Publish percentile values computed in your application
|
| Publish percentile values computed in your application
|
||||||
|
|
||||||
| configprop:management.metrics.distribution.sla[]
|
| configprop:management.metrics.distribution.slo[]
|
||||||
| Publish a cumulative histogram with buckets defined by your SLAs.
|
| Publish a cumulative histogram with buckets defined by your Service Level Objectives.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
For more details on concepts behind `percentiles-histogram`, `percentiles` and `sla` refer to the {micrometer-concepts-docs}#_histograms_and_percentiles["Histograms and percentiles" section] of the micrometer documentation.
|
For more details on concepts behind `percentiles-histogram`, `percentiles` and `sla` refer to the {micrometer-concepts-docs}#_histograms_and_percentiles["Histograms and percentiles" section] of the micrometer documentation.
|
||||||
|
|
Loading…
Reference in New Issue