Skip to content

Commit

Permalink
Service level objectives support on @Timed annotation (#5145)
Browse files Browse the repository at this point in the history
Adding the ability to specify, on @timed annotations, the service level objectives.
Using seconds as the unit of time for these values.
  • Loading branch information
lcavadas committed Jul 9, 2024
1 parent 557d997 commit 1061499
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@
*/
boolean histogram() default false;

/**
* List of service level objectives to calculate client-side for the
* {@link io.micrometer.core.instrument.Timer} in seconds. For example, for a 100ms
* should be passed as {@code 0.1}.
* @return service level objectives to calculate
* @see io.micrometer.core.instrument.Timer.Builder#serviceLevelObjectives(java.time.Duration...)
*/
double[] serviceLevelObjectives() default {};

/**
* Description of the {@link io.micrometer.core.instrument.Timer}.
* @return meter description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
import io.micrometer.core.annotation.Incubating;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.util.TimeUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;

Expand Down Expand Up @@ -254,7 +258,11 @@ private Timer.Builder recordBuilder(ProceedingJoinPoint pjp, Timed timed, String
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());
.serviceLevelObjectives(
timed.serviceLevelObjectives().length > 0 ? Arrays.stream(timed.serviceLevelObjectives())
.mapToObj(s -> Duration.ofNanos((long) TimeUtils.secondsToUnit(s, TimeUnit.NANOSECONDS)))
.toArray(Duration[]::new) : null);

if (meterTagAnnotationHandler != null) {
meterTagAnnotationHandler.addAnnotatedParameters(builder, pjp);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.micrometer.core.instrument.distribution.HistogramSupport;
import io.micrometer.core.instrument.distribution.ValueAtPercentile;
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
import io.micrometer.core.instrument.util.TimeUtils;

import java.time.Duration;
import java.util.Arrays;
Expand Down Expand Up @@ -100,7 +101,11 @@ static Builder builder(Timed timed, String defaultName) {
return new Builder(timed.value().isEmpty() ? defaultName : timed.value()).tags(timed.extraTags())
.description(timed.description().isEmpty() ? null : timed.description())
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length > 0 ? timed.percentiles() : null);
.publishPercentiles(timed.percentiles().length > 0 ? timed.percentiles() : null)
.serviceLevelObjectives(
timed.serviceLevelObjectives().length > 0 ? Arrays.stream(timed.serviceLevelObjectives())
.mapToObj(s -> Duration.ofNanos((long) TimeUtils.secondsToUnit(s, TimeUnit.NANOSECONDS)))
.toArray(Duration[]::new) : null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.micrometer.core.instrument.Meter.Id;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.distribution.CountAtBucket;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
import io.micrometer.core.instrument.search.MeterNotFoundException;
Expand All @@ -34,6 +35,7 @@
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Predicate;
Expand Down Expand Up @@ -95,6 +97,29 @@ void timeMethodWithLongTaskTimer() {
.size()).isEqualTo(1);
}

@Test
void timeMethodWithSloTimer() {
MeterRegistry registry = new SimpleMeterRegistry();

AspectJProxyFactory pf = new AspectJProxyFactory(new TimedService());
pf.addAspect(new TimedAspect(registry));

TimedService service = pf.getProxy();

service.sloCall();

assertThat(Arrays
.stream(registry.get("sloCall")
.tag("class", getClass().getName() + "$TimedService")
.tag("method", "sloCall")
.tag("extra", "tag")
.timer()
.takeSnapshot()
.histogramCounts())
.mapToDouble(CountAtBucket::bucket)
.toArray()).isEqualTo(new double[] { Math.pow(10, 9) * 0.1, Math.pow(10, 9) * 0.5 });
}

@Test
void timeMethodFailure() {
MeterRegistry failingRegistry = new FailingMeterRegistry();
Expand Down Expand Up @@ -621,6 +646,11 @@ void call() {
void longCall() {
}

@Timed(value = "sloCall", extraTags = { "extra", "tag" }, histogram = true,
serviceLevelObjectives = { 0.1, 0.5 })
void sloCall() {
}

}

static class AsyncTimedService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.jersey.server.resources.TimedOnClassResource;
import io.micrometer.core.instrument.binder.jersey.server.resources.TimedResource;
import io.micrometer.core.instrument.distribution.CountAtBucket;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
Expand Down Expand Up @@ -93,6 +94,20 @@ void resourcesWithAnnotationAreTimed() {
assertThat(registry.get("multi2").tags(tagsFrom("/multi-timed", 200)).timer().count()).isEqualTo(1);
}

@Test
void sloTaskTimerSupported() throws InterruptedException, ExecutionException, TimeoutException {
target("timed-slo").request().get();

CountAtBucket[] slos = registry.get("timedSlo")
.tags(tagsFrom("/timed-slo", 200))
.timer()
.takeSnapshot()
.histogramCounts();
assertThat(slos.length).isEqualTo(2);
assertThat(slos[0].bucket()).isEqualTo(Math.pow(10, 9) * 0.1);
assertThat(slos[1].bucket()).isEqualTo(Math.pow(10, 9) * 0.5);
}

@Test
void longTaskTimerSupported() throws InterruptedException, ExecutionException, TimeoutException {
final Future<Response> future = target("long-timed").request().async().get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ public String timed() {
return "timed";
}

@GET
@Path("timed-slo")
@Timed(value = "timedSlo", histogram = true, serviceLevelObjectives = { 0.1, 0.5 })
public String timedSlo() {
return "timed";
}

@GET
@Path("multi-timed")
@Timed("multi1")
Expand Down

0 comments on commit 1061499

Please sign in to comment.