From 5e718aa600f48f356b02eb93db0ff45fd40b38de Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Wed, 7 Apr 2021 23:01:48 -0700 Subject: [PATCH 1/4] Introducing a skip predicate to the TimedAspect so that users can skip Timer creation based on the JoinPoint, fixes gh-780 --- .../io/micrometer/core/aop/TimedAspect.java | 53 +++++++++++++++++-- .../micrometer/core/aop/TimedAspectTest.java | 16 ++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java index 3eb7342e47..c0327c5c9c 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import java.util.function.Predicate; /** * AspectJ aspect for intercepting types or methods annotated with {@link Timed @Timed}. @@ -41,12 +42,14 @@ * @author Jon Schneider * @author Johnny Lim * @author Nejc Korasa + * @author Jonatan Ivanov * @since 1.0.0 */ @Aspect @NonNullApi @Incubating(since = "1.0.0") public class TimedAspect { + private static final Predicate DONT_SKIP_ANYTHING = pjp -> false; public static final String DEFAULT_METRIC_NAME = "method.timed"; public static final String DEFAULT_EXCEPTION_TAG_VALUE = "none"; @@ -59,9 +62,10 @@ public class TimedAspect { private final MeterRegistry registry; private final Function> tagsBasedOnJoinPoint; + private final Predicate shouldSkip; /** - * Create a {@code TimedAspect} instance with {@link Metrics#globalRegistry}. + * Creates a {@code TimedAspect} instance with {@link Metrics#globalRegistry}. * * @since 1.2.0 */ @@ -69,20 +73,59 @@ public TimedAspect() { this(Metrics.globalRegistry); } + /** + * Creates a {@code TimedAspect} instance with the given {@code meterRegistry}. + * + * @param registry Where we're going to register metrics. + */ public TimedAspect(MeterRegistry registry) { - this(registry, pjp -> - Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(), - "method", pjp.getStaticPart().getSignature().getName()) - ); + this(registry, DONT_SKIP_ANYTHING); } + /** + * Creates a {@code TimedAspect} instance with the given {@code meterRegistry} and tags provider function. + * + * @param registry Where we're going to register metrics. + * @param tagsBasedOnJoinPoint A function to generate tags given a join point. + */ public TimedAspect(MeterRegistry registry, Function> tagsBasedOnJoinPoint) { + this(registry, tagsBasedOnJoinPoint, DONT_SKIP_ANYTHING); + } + + /** + * Creates a {@code TimedAspect} instance with the given {@code meterRegistry} and skip predicate. + * + * @param registry Where we're going to register metrics. + * @param shouldSkip A predicate to decide if creating the timer should be skipped or not. + */ + public TimedAspect(MeterRegistry registry, Predicate shouldSkip) { + this( + registry, + pjp -> Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(), + "method", pjp.getStaticPart().getSignature().getName()), + shouldSkip + ); + } + + /** + * Creates a {@code TimedAspect} instance with the given {@code meterRegistry}, tags provider function and skip predicate. + * + * @param registry Where we're going to register metrics. + * @param tagsBasedOnJoinPoint A function to generate tags given a join point. + * @param shouldSkip A predicate to decide if creating the timer should be skipped or not. + */ + public TimedAspect(MeterRegistry registry, Function> tagsBasedOnJoinPoint, Predicate shouldSkip) { this.registry = registry; this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint; + this.shouldSkip = shouldSkip; } @Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))") public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable { + if (shouldSkip.test(pjp)) { + return pjp.proceed(); + } + Method method = ((MethodSignature) pjp.getSignature()).getMethod(); Timed timed = method.getAnnotation(Timed.class); if (timed == null) { diff --git a/micrometer-core/src/test/java/io/micrometer/core/aop/TimedAspectTest.java b/micrometer-core/src/test/java/io/micrometer/core/aop/TimedAspectTest.java index b0de70c64e..7d49167017 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/aop/TimedAspectTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/aop/TimedAspectTest.java @@ -25,12 +25,14 @@ import io.micrometer.core.instrument.search.MeterNotFoundException; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.lang.NonNull; +import org.aspectj.lang.ProceedingJoinPoint; import org.junit.jupiter.api.Test; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import javax.annotation.Nonnull; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.function.Predicate; import static java.util.concurrent.CompletableFuture.supplyAsync; import static org.assertj.core.api.Assertions.*; @@ -54,6 +56,20 @@ void timeMethod() { .timer().count()).isEqualTo(1); } + @Test + void timeMethodWithSkipPredicate() { + MeterRegistry registry = new SimpleMeterRegistry(); + + AspectJProxyFactory pf = new AspectJProxyFactory(new TimedService()); + pf.addAspect(new TimedAspect(registry, (Predicate) pjp -> true)); + + TimedService service = pf.getProxy(); + + service.call(); + + assertThat(registry.find("call").timer()).isNull(); + } + @Test void timeMethodWithLongTaskTimer() { MeterRegistry registry = new SimpleMeterRegistry(); From cdb36177b7e2ff2a84df9d87d5ac7909023cb275 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Thu, 8 Apr 2021 14:33:08 -0700 Subject: [PATCH 2/4] Adding javadoc for TimedAspect --- .../io/micrometer/core/aop/TimedAspect.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java index c0327c5c9c..9f476173e5 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java @@ -36,7 +36,37 @@ import java.util.function.Predicate; /** - * AspectJ aspect for intercepting types or methods annotated with {@link Timed @Timed}. + *

+ * AspectJ aspect for intercepting types or methods annotated with {@link Timed @Timed}.
+ * The aspect supports programmatic customizations through constructor-injectable custom logic. + *

+ * + *

+ * You might want to add tags programmatically to the {@link Timer}.
+ * In this case, the tags provider function (Function<ProceedingJoinPoint, Iterable<Tag>>) can help. + * It receives a {@link ProceedingJoinPoint} and returns the {@link Tag}s that will be attached to the {@link Timer}. + *

+ *

+ * You might also want to skip the {@link Timer} creation programmatically.
+ * One use-case can be having another component in your application that already processes the {@link Timed @Timed} annotation + * in some cases so that {@code TimedAspect} should not intercept these methods. E.g.: Spring Boot does this for its controllers. + * By using the skip predicate (Predicate<ProceedingJoinPoint>) + * you can tell the {@code TimedAspect} when not to create a {@link Timer}. + * + * Here's an example to disable {@link Timer} creation for Spring controllers: + * + *

+ * @Bean
+ * public TimedAspect timedAspect(MeterRegistry meterRegistry) {
+ *     return new TimedAspect(meterRegistry, this::skipControllers);
+ * }
+ *
+ * private boolean skipControllers(ProceedingJoinPoint pjp) {
+ *     Class targetClass = pjp.getTarget().getClass();
+ *     return targetClass.isAnnotationPresent(RestController.class) || targetClass.isAnnotationPresent(Controller.class);
+ * }
+ * 
+ *

* * @author David J. M. Karlsen * @author Jon Schneider From bb7f6e7a6a4aa5ac1b3d436e3bc48b75dcc98d17 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Thu, 8 Apr 2021 15:20:40 -0700 Subject: [PATCH 3/4] Introducing a skip predicate to the CountedAspect so that users can skip Counter creation based on the JoinPoint, see the same thing for TimedAspect: gh-780 --- .../io/micrometer/core/aop/CountedAspect.java | 61 ++++++++++++++++--- .../core/aop/CountedAspectTest.java | 20 +++++- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java index c98ad60d0f..de3694b089 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java @@ -18,6 +18,7 @@ import io.micrometer.core.annotation.Counted; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.lang.NonNullApi; @@ -29,6 +30,7 @@ import java.lang.reflect.Method; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import java.util.function.Predicate; /** * Aspect responsible for intercepting all methods annotated with the {@link Counted} @@ -41,6 +43,7 @@ @Aspect @NonNullApi public class CountedAspect { + private static final Predicate DONT_SKIP_ANYTHING = pjp -> false; public final String DEFAULT_EXCEPTION_TAG_VALUE = "none"; public final String RESULT_TAG_FAILURE_VALUE = "failure"; public final String RESULT_TAG_SUCCESS_VALUE = "success"; @@ -66,26 +69,63 @@ public class CountedAspect { private final Function> tagsBasedOnJoinPoint; /** - * Construct a new aspect with the given {@code meterRegistry} along with a default - * tags provider. + * A predicate that decides if Timer creation should be skipped for the given join point. + */ + private final Predicate shouldSkip; + + /** + * Creates a {@code CountedAspect} instance with {@link Metrics#globalRegistry}. + * + */ + public CountedAspect() { + this(Metrics.globalRegistry); + } + + /** + * Creates a {@code CountedAspect} instance with the given {@code meterRegistry}. * - * @param meterRegistry Where we're going register metrics. + * @param registry Where we're going to register metrics. */ - public CountedAspect(MeterRegistry meterRegistry) { - this(meterRegistry, pjp -> - Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(), - "method", pjp.getStaticPart().getSignature().getName())); + public CountedAspect(MeterRegistry registry) { + this(registry, DONT_SKIP_ANYTHING); } /** - * Constructs a new aspect with the given {@code meterRegistry} and tags provider function. + * Creates a {@code CountedAspect} instance with the given {@code meterRegistry} and tags provider function. * - * @param meterRegistry Where we're going register metrics. + * @param meterRegistry Where we're going to register metrics. * @param tagsBasedOnJoinPoint A function to generate tags given a join point. */ public CountedAspect(MeterRegistry meterRegistry, Function> tagsBasedOnJoinPoint) { + this(meterRegistry, tagsBasedOnJoinPoint, DONT_SKIP_ANYTHING); + } + + /** + * Creates a {@code CountedAspect} instance with the given {@code meterRegistry} and skip predicate. + * + * @param meterRegistry Where we're going to register metrics. + * @param shouldSkip A predicate to decide if creating the timer should be skipped or not. + */ + public CountedAspect(MeterRegistry meterRegistry, Predicate shouldSkip) { + this( + meterRegistry, + pjp -> Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(), + "method", pjp.getStaticPart().getSignature().getName()), + shouldSkip + ); + } + + /** + * Creates a {@code CountedAspect} instance with the given {@code meterRegistry}, tags provider function and skip predicate. + * + * @param meterRegistry Where we're going to register metrics. + * @param tagsBasedOnJoinPoint A function to generate tags given a join point. + * @param shouldSkip A predicate to decide if creating the timer should be skipped or not. + */ + public CountedAspect(MeterRegistry meterRegistry, Function> tagsBasedOnJoinPoint, Predicate shouldSkip) { this.meterRegistry = meterRegistry; this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint; + this.shouldSkip = shouldSkip; } /** @@ -106,6 +146,9 @@ public CountedAspect(MeterRegistry meterRegistry, Function) proceedingJoinPoint -> true) + ); + + countedService.succeedWithMetrics(); + + assertThat(meterRegistry.find("metric.success").counter()).isNull(); + } + @Test void countedWithFailure() { try { @@ -204,8 +218,12 @@ void emptyMetricNameWithException() { } private T getAdvisedService(T countedService) { + return getAdvisedService(countedService, new CountedAspect(meterRegistry)); + } + + private T getAdvisedService(T countedService, CountedAspect countedAspect) { AspectJProxyFactory proxyFactory = new AspectJProxyFactory(countedService); - proxyFactory.addAspect(new CountedAspect(meterRegistry)); + proxyFactory.addAspect(countedAspect); return proxyFactory.getProxy(); } From 9bc284c9e77c277d5ccb3ca0b0bc0ff64ced0844 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Thu, 8 Apr 2021 15:21:09 -0700 Subject: [PATCH 4/4] Adding javadoc for CountedAspect --- .../io/micrometer/core/aop/CountedAspect.java | 34 +++++++++++++++++-- .../io/micrometer/core/aop/TimedAspect.java | 1 - 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java index de3694b089..429d2b87fd 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java @@ -33,10 +33,40 @@ import java.util.function.Predicate; /** - * Aspect responsible for intercepting all methods annotated with the {@link Counted} - * annotation and record a few counter metrics about their execution status. + *

+ * Aspect responsible for intercepting all methods annotated with the {@link Counted @Counted} + * annotation and record a few counter metrics about their execution status.
+ * The aspect supports programmatic customizations through constructor-injectable custom logic. + *

+ *

+ * You might want to add tags programmatically to the {@link Counter}.
+ * In this case, the tags provider function (Function<ProceedingJoinPoint, Iterable<Tag>>) can help. + * It receives a {@link ProceedingJoinPoint} and returns the {@link Tag}s that will be attached to the {@link Counter}. + *

+ *

+ * You might also want to skip the {@link Counter} creation programmatically.
+ * One use-case can be having another component in your application that already processes the {@link Counted @Counted} annotation + * in some cases so that {@code CountedAspect} should not intercept these methods. + * By using the skip predicate (Predicate<ProceedingJoinPoint>) + * you can tell the {@code CountedAspect} when not to create a {@link Counter}. + * + * Here's a theoretic example to disable {@link Counter} creation for Spring controllers: + * + *

+ * @Bean
+ * public CountedAspect countedAspect(MeterRegistry meterRegistry) {
+ *     return new CountedAspect(meterRegistry, this::skipControllers);
+ * }
+ *
+ * private boolean skipControllers(ProceedingJoinPoint pjp) {
+ *     Class targetClass = pjp.getTarget().getClass();
+ *     return targetClass.isAnnotationPresent(RestController.class) || targetClass.isAnnotationPresent(Controller.class);
+ * }
+ * 
+ *

* * @author Ali Dehghani + * @author Jonatan Ivanov * @since 1.2.0 * @see Counted */ diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java index 9f476173e5..4b98be6748 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java @@ -40,7 +40,6 @@ * AspectJ aspect for intercepting types or methods annotated with {@link Timed @Timed}.
* The aspect supports programmatic customizations through constructor-injectable custom logic. *

- * *

* You might want to add tags programmatically to the {@link Timer}.
* In this case, the tags provider function (Function<ProceedingJoinPoint, Iterable<Tag>>) can help.