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:
Stephane Nicoll 2020-04-23 10:13:17 +02:00
parent 489f9b0e58
commit fe90b2a251
7 changed files with 137 additions and 50 deletions

View File

@ -20,6 +20,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
@ -232,12 +233,12 @@ public class MetricsProperties {
private final Map<String, double[]> percentiles = new LinkedHashMap<>();
/**
* Specific SLA 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 long or as a Duration value (for timer meters,
* defaulting to ms if no unit specified).
* Specific Service Level Objectives 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 long or as a Duration value
* (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
@ -261,8 +262,14 @@ public class MetricsProperties {
return this.percentiles;
}
public Map<String, ServiceLevelAgreementBoundary[]> getSla() {
return this.sla;
@Deprecated
@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() {

View File

@ -83,7 +83,8 @@ public class PropertiesMeterFilter implements MeterFilter {
return DistributionStatisticConfig.builder()
.percentilesHistogram(lookupWithFallbackToAll(distribution.getPercentilesHistogram(), 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(
convertMeterValue(id.getType(), lookup(distribution.getMinimumExpectedValue(), id, null)))
.maximumExpectedValue(
@ -91,11 +92,11 @@ public class PropertiesMeterFilter implements MeterFilter {
.build().merge(config);
}
private double[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[] sla) {
if (sla == null) {
private double[] convertServiceLevelObjectives(Meter.Type meterType, ServiceLevelObjectiveBoundary[] slo) {
if (slo == 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();
return (converted.length != 0) ? converted : null;
}

View File

@ -22,12 +22,14 @@ import io.micrometer.core.instrument.Meter;
/**
* A service level agreement 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).
* specified as either a {@link Long} (applicable to timers and distribution summaries) or
* a {@link Duration} (applicable to only timers).
*
* @author Phillip Webb
* @since 2.0.0
* @deprecated as of 2.3.0 in favor of {@link ServiceLevelObjectiveBoundary}
*/
@Deprecated
public final class ServiceLevelAgreementBoundary {
private final MeterValue value;
@ -42,17 +44,18 @@ public final class ServiceLevelAgreementBoundary {
* @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);
public Long getValue(Meter.Type 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.
* @param value the source value
* @return a {@link ServiceLevelAgreementBoundary} instance
*/
public static ServiceLevelAgreementBoundary valueOf(double value) {
public static ServiceLevelAgreementBoundary valueOf(long value) {
return new ServiceLevelAgreementBoundary(MeterValue.valueOf(value));
}

View File

@ -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));
}
}

View File

@ -204,29 +204,35 @@ class PropertiesMeterFilterTests {
}
@Test
void configureWhenHasSlaShouldSetSlaToValue() {
@Deprecated
void configureWhenHasDeprecatedSlaShouldSetSlaToValue() {
PropertiesMeterFilter filter = new PropertiesMeterFilter(
createProperties("distribution.sla.spring.boot=1,2,3"));
assertThat(
filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getSlaBoundaries())
.containsExactly(1000000, 2000000, 3000000);
assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT)
.getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000);
}
@Test
void configureWhenHasHigherSlaShouldSetPercentilesToValue() {
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() {
void configureWhenHasSloShouldSetSloToValue() {
PropertiesMeterFilter filter = new PropertiesMeterFilter(
createProperties("distribution.sla.spring=1,2,3", "distribution.sla.spring.boot=4,5,6"));
assertThat(
filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getSlaBoundaries())
.containsExactly(4000000, 5000000, 6000000);
createProperties("distribution.slo.spring.boot=1,2,3"));
assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT)
.getServiceLevelObjectiveBoundaries()).containsExactly(1000000, 2000000, 3000000);
}
@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

View File

@ -22,47 +22,47 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ServiceLevelAgreementBoundary}.
* Tests for {@link ServiceLevelObjectiveBoundary}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class ServiceLevelAgreementBoundaryTests {
class ServiceLevelObjectiveBoundaryTests {
@Test
void getValueForTimerWhenFromLongShouldReturnMsToNanosValue() {
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf(123L);
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf(123L);
assertThat(slo.getValue(Type.TIMER)).isEqualTo(123000000);
}
@Test
void getValueForTimerWhenFromNumberStringShouldMsToNanosValue() {
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123");
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123");
assertThat(slo.getValue(Type.TIMER)).isEqualTo(123000000);
}
@Test
void getValueForTimerWhenFromDurationStringShouldReturnDurationNanos() {
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123ms");
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123ms");
assertThat(slo.getValue(Type.TIMER)).isEqualTo(123000000);
}
@Test
void getValueForDistributionSummaryWhenFromDoubleShouldReturnDoubleValue() {
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf(123.42);
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf(123.42);
assertThat(slo.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
}
@Test
void getValueForDistributionSummaryWhenFromStringShouldReturnDoubleValue() {
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123.42");
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123.42");
assertThat(slo.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123.42);
}
@Test
void getValueForDistributionSummaryWhenFromDurationShouldReturnNull() {
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123ms");
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isNull();
ServiceLevelObjectiveBoundary slo = ServiceLevelObjectiveBoundary.valueOf("123ms");
assertThat(slo.getValue(Type.DISTRIBUTION_SUMMARY)).isNull();
}
}

View File

@ -2131,8 +2131,8 @@ The following properties allow per-meter customization:
| configprop:management.metrics.distribution.percentiles[]
| Publish percentile values computed in your application
| configprop:management.metrics.distribution.sla[]
| Publish a cumulative histogram with buckets defined by your SLAs.
| configprop:management.metrics.distribution.slo[]
| 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.