From cd49175085755105ae33e22c57e98cc030a77e6f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 28 Jul 2021 15:59:56 +0200 Subject: [PATCH] Micrometer - rework interceptors - interceptors obtain the metadata from the interceptor bindings instead of InvocationContext.getMethod() and reflection; this makes it possible to reflect "synthetic" annotations added by extensions via annotation transformers - Timed is registered as an additional interceptor binding; MicrometerTimed is no longer necessary - repeatable Timed annotations are now supported - Counted cannot be registered as an additional interceptor binding because it's only applicable to methods - also enhanced the ArcInvocationContext to easily find interceptor binding annotations of a given type --- .../deployment/MicrometerProcessor.java | 65 ++++---- .../binder/mpmetrics/AnnotationHandler.java | 9 +- .../MicrometerTimedInterceptorTest.java | 19 +++ .../micrometer/test/TimedResource.java | 8 + .../micrometer/runtime/MicrometerCounted.java | 27 ++++ .../runtime/MicrometerCountedInterceptor.java | 14 +- .../micrometer/runtime/MicrometerTimed.java | 16 -- .../runtime/MicrometerTimedInterceptor.java | 150 +++++++++++------- .../mpmetrics/ConcurrentGaugeInterceptor.java | 17 +- .../binder/mpmetrics/CountedInterceptor.java | 17 +- .../mpmetrics/MpMetricsRegistryProducer.java | 14 -- .../binder/mpmetrics/TimedInterceptor.java | 16 +- .../io/quarkus/arc/ArcInvocationContext.java | 44 ++++- .../arc/impl/AbstractInvocationContext.java | 28 +++- .../arcInvContext/ArcContextInterceptor.java | 6 + 15 files changed, 285 insertions(+), 165 deletions(-) delete mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimed.java diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java index 1eb7a1939c498..cc01bcb212cc4 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java @@ -8,6 +8,8 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; @@ -23,9 +25,12 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.InterceptorBindingRegistrar; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -45,7 +50,6 @@ import io.quarkus.micrometer.runtime.MicrometerCounted; import io.quarkus.micrometer.runtime.MicrometerCountedInterceptor; import io.quarkus.micrometer.runtime.MicrometerRecorder; -import io.quarkus.micrometer.runtime.MicrometerTimed; import io.quarkus.micrometer.runtime.MicrometerTimedInterceptor; import io.quarkus.micrometer.runtime.config.MicrometerConfig; import io.quarkus.runtime.RuntimeValue; @@ -62,7 +66,6 @@ public class MicrometerProcessor { private static final DotName COUNTED_BINDING = DotName.createSimple(MicrometerCounted.class.getName()); private static final DotName COUNTED_INTERCEPTOR = DotName.createSimple(MicrometerCountedInterceptor.class.getName()); private static final DotName TIMED_ANNOTATION = DotName.createSimple(Timed.class.getName()); - private static final DotName TIMED_BINDING = DotName.createSimple(MicrometerTimed.class.getName()); private static final DotName TIMED_INTERCEPTOR = DotName.createSimple(MicrometerTimedInterceptor.class.getName()); public static class MicrometerEnabled implements BooleanSupplier { @@ -97,7 +100,8 @@ MetricsCapabilityBuildItem metricsCapabilityPrometheusBuildItem( UnremovableBeanBuildItem registerAdditionalBeans(CombinedIndexBuildItem indexBuildItem, BuildProducer providerClasses, BuildProducer reflectiveClasses, - BuildProducer additionalBeans) { + BuildProducer additionalBeans, + BuildProducer interceptorBindings) { // Create and keep some basic Providers additionalBeans.produce(AdditionalBeanBuildItem.builder() @@ -111,13 +115,20 @@ UnremovableBeanBuildItem registerAdditionalBeans(CombinedIndexBuildItem indexBui .addBeanClass(MeterFilterConstraint.class) .addBeanClass(MeterFilterConstraints.class) .addBeanClass(TIMED_ANNOTATION.toString()) - .addBeanClass(TIMED_BINDING.toString()) .addBeanClass(TIMED_INTERCEPTOR.toString()) .addBeanClass(COUNTED_ANNOTATION.toString()) .addBeanClass(COUNTED_BINDING.toString()) .addBeanClass(COUNTED_INTERCEPTOR.toString()) .build()); + // @Timed is registered as an additional interceptor binding + interceptorBindings.produce(new InterceptorBindingRegistrarBuildItem(new InterceptorBindingRegistrar() { + @Override + public List getAdditionalBindings() { + return List.of(InterceptorBinding.of(Timed.class, m -> true)); + } + })); + IndexView index = indexBuildItem.getIndex(); // Find classes that define MeterRegistries, MeterBinders, and MeterFilters @@ -182,9 +193,27 @@ void collectNames(Collection classes, Collection names) { } @BuildStep(onlyIf = MicrometerEnabled.class) - void processAnnotatedMetrics(BuildProducer annotationsTransformers) { - annotationsTransformers.produce(createAnnotationTransformer(COUNTED_ANNOTATION, COUNTED_BINDING)); - annotationsTransformers.produce(createAnnotationTransformer(TIMED_ANNOTATION, TIMED_BINDING)); + AnnotationsTransformerBuildItem processAnnotatedMetrics( + BuildProducer annotationsTransformers) { + return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { + + @Override + public boolean appliesTo(Kind kind) { + // @Counted is only applicable to a method + return kind == Kind.METHOD; + } + + @Override + public void transform(TransformationContext ctx) { + final Collection annotations = ctx.getAnnotations(); + AnnotationInstance counted = Annotations.find(annotations, COUNTED_ANNOTATION); + if (counted == null) { + return; + } + // Copy all the values so that the interceptor can use the binding annotation instead of java.lang.reflect.Method + ctx.transform().add(COUNTED_BINDING, counted.values().toArray(new AnnotationValue[] {})).done(); + } + }); } @BuildStep(onlyIf = MicrometerEnabled.class) @@ -238,15 +267,6 @@ void configureRegistry(MicrometerRecorder recorder, } } - public static AnnotationInstance findAnnotation(Collection annotations, DotName annotationClass) { - for (AnnotationInstance a : annotations) { - if (annotationClass.equals(a.name())) { - return a; - } - } - return null; - } - ReflectiveClassBuildItem createReflectiveBuildItem(DotName sourceAnnotation, IndexView index) { Set classes = new HashSet<>(); @@ -268,17 +288,4 @@ ReflectiveClassBuildItem createReflectiveBuildItem(DotName sourceAnnotation, Ind return ReflectiveClassBuildItem.builder(classes.toArray(new String[0])).build(); } - AnnotationsTransformerBuildItem createAnnotationTransformer(DotName sourceAnnotation, DotName bindingAnnotation) { - return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { - @Override - public void transform(TransformationContext ctx) { - final Collection annotations = ctx.getAnnotations(); - AnnotationInstance annotation = MicrometerProcessor.findAnnotation(annotations, sourceAnnotation); - if (annotation == null) { - return; - } - ctx.transform().add(bindingAnnotation).done(); - } - }); - } } diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java index bd4490c904b2d..47326df5a8e0a 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java @@ -6,9 +6,9 @@ import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.DotNames; -import io.quarkus.micrometer.deployment.MicrometerProcessor; /** * Create beans to handle MP Metrics API annotations. @@ -28,7 +28,7 @@ static AnnotationsTransformerBuildItem transformAnnotations(final IndexView inde @Override public void transform(TransformationContext ctx) { final Collection annotations = ctx.getAnnotations(); - AnnotationInstance annotation = MicrometerProcessor.findAnnotation(annotations, sourceAnnotation); + AnnotationInstance annotation = Annotations.find(annotations, sourceAnnotation); if (annotation == null) { return; } @@ -78,9 +78,8 @@ static boolean removeCountedWhenTimed(DotName sourceAnnotation, AnnotationTarget MethodInfo methodInfo) { if (MetricDotNames.COUNTED_ANNOTATION.equals(sourceAnnotation)) { if (methodInfo == null) { - if (MicrometerProcessor.findAnnotation(classInfo.classAnnotations(), MetricDotNames.TIMED_ANNOTATION) == null && - MicrometerProcessor.findAnnotation(classInfo.classAnnotations(), - MetricDotNames.SIMPLY_TIMED_ANNOTATION) == null) { + if (!Annotations.contains(classInfo.classAnnotations(), MetricDotNames.TIMED_ANNOTATION) && + !Annotations.contains(classInfo.classAnnotations(), MetricDotNames.SIMPLY_TIMED_ANNOTATION)) { return false; } log.warnf("Bean %s is both counted and timed. The @Counted annotation " + diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java index 02d4d84684815..090c2060be30b 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java @@ -154,4 +154,23 @@ void testTimeMethod_LongTaskTimer_AsyncFailed() { Assertions.assertEquals(0, timer.activeTasks()); } + @Test + void testTimeMethod_repeatable() { + timed.repeatableCall(false); + Timer alphaTimer = registry.get("alpha") + .tag("method", "repeatableCall") + .tag("class", "io.quarkus.micrometer.test.TimedResource") + .tag("exception", "none") + .tag("extra", "tag").timer(); + Assertions.assertNotNull(alphaTimer); + Assertions.assertEquals(1, alphaTimer.count()); + Timer bravoTimer = registry.get("bravo") + .tag("method", "repeatableCall") + .tag("class", "io.quarkus.micrometer.test.TimedResource") + .tag("exception", "none") + .tag("extra", "tag").timer(); + Assertions.assertNotNull(bravoTimer); + Assertions.assertEquals(1, bravoTimer.count()); + } + } diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/TimedResource.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/TimedResource.java index d4856e8bc600b..f40895f1e8b73 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/TimedResource.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/TimedResource.java @@ -42,4 +42,12 @@ public CompletableFuture longAsyncCall(GuardedResult guardedResult) { } return supplyAsync(guardedResult::get); } + + @Timed(value = "alpha", extraTags = { "extra", "tag" }) + @Timed(value = "bravo", extraTags = { "extra", "tag" }) + public void repeatableCall(boolean fail) { + if (fail) { + throw new NullPointerException("Failed on purpose"); + } + } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCounted.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCounted.java index 7cad4a9914812..985410fc6d3fc 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCounted.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCounted.java @@ -6,11 +6,38 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.enterprise.util.Nonbinding; import javax.interceptor.InterceptorBinding; +import io.micrometer.core.annotation.Counted; + @Inherited @InterceptorBinding @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface MicrometerCounted { + + /** + * @see Counted#value() + */ + @Nonbinding + String value() default "method.counted"; + + /** + * @see Counted#recordFailuresOnly() + */ + @Nonbinding + boolean recordFailuresOnly() default false; + + /** + * @see Counted#extraTags() + */ + @Nonbinding + String[] extraTags() default {}; + + /** + * @see Counted#description() + */ + @Nonbinding + String description() default ""; } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java index 82968c20efe16..c22cf26e6f661 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java @@ -6,12 +6,12 @@ import javax.annotation.Priority; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import io.micrometer.core.annotation.Counted; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; +import io.quarkus.arc.ArcInvocationContext; /** * Quarkus declared interceptor responsible for intercepting all methods @@ -51,13 +51,12 @@ public MicrometerCountedInterceptor(MeterRegistry meterRegistry) { * @throws Throwable When the intercepted method throws one. */ @AroundInvoke - Object countedMethod(InvocationContext context) throws Exception { - Method method = context.getMethod(); - Counted counted = method.getAnnotation(Counted.class); + Object countedMethod(ArcInvocationContext context) throws Exception { + MicrometerCounted counted = context.findIterceptorBinding(MicrometerCounted.class); if (counted == null) { return context.proceed(); } - + Method method = context.getMethod(); Tags commonTags = getCommonTags(method.getDeclaringClass().getName(), method.getName()); // If we're working with a CompletionStage @@ -84,7 +83,7 @@ Object countedMethod(InvocationContext context) throws Exception { } } - private void recordCompletionResult(Counted counted, Tags commonTags, Throwable throwable) { + private void recordCompletionResult(MicrometerCounted counted, Tags commonTags, Throwable throwable) { if (throwable != null) { record(counted, commonTags, throwable); } else if (!counted.recordFailuresOnly()) { @@ -92,7 +91,7 @@ private void recordCompletionResult(Counted counted, Tags commonTags, Throwable } } - private void record(Counted counted, Tags commonTags, Throwable throwable) { + private void record(MicrometerCounted counted, Tags commonTags, Throwable throwable) { Counter.Builder builder = Counter.builder(counted.value()) .tags(commonTags) .tags(counted.extraTags()) @@ -108,4 +107,5 @@ private void record(Counted counted, Tags commonTags, Throwable throwable) { private Tags getCommonTags(String className, String methodName) { return Tags.of("class", className, "method", methodName); } + } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimed.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimed.java deleted file mode 100644 index 2e90821cea451..0000000000000 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimed.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.quarkus.micrometer.runtime; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import javax.interceptor.InterceptorBinding; - -@Inherited -@InterceptorBinding -@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface MicrometerTimed { -} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java index 7f843fc6aa804..507a7c3cd68bb 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java @@ -1,12 +1,14 @@ package io.quarkus.micrometer.runtime; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CompletionStage; import javax.annotation.Priority; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.jboss.logging.Logger; @@ -15,16 +17,18 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; +import io.quarkus.arc.ArcInvocationContext; /** * Quarkus defined interceptor for types or methods annotated with {@link Timed @Timed}. - * - * @see Timed */ @Interceptor -@MicrometerTimed +// For some unknown reason, the default value for Timed#value() is not always available in the Jandex index when the annotation literal is being processed by ArC +// As a workaround we do specify the value explicitly - note that is has no effect on resolution because it's non-binding +@Timed("") @Priority(Interceptor.Priority.LIBRARY_BEFORE + 10) public class MicrometerTimedInterceptor { + private static final Logger log = Logger.getLogger(MicrometerTimedInterceptor.class); public static final String DEFAULT_METRIC_NAME = "method.timed"; @@ -35,42 +39,26 @@ public MicrometerTimedInterceptor(MeterRegistry meterRegistry) { } @AroundInvoke - Object timedMethod(InvocationContext context) throws Exception { - Method method = context.getMethod(); - Timed timed = method.getAnnotation(Timed.class); - if (timed == null) { - return context.proceed(); - } - - Tags commonTags = getCommonTags(method.getDeclaringClass().getName(), method.getName()); - final boolean stopWhenCompleted = CompletionStage.class.isAssignableFrom(method.getReturnType()); - - return time(context, timed, commonTags, stopWhenCompleted); - } + Object timedMethod(ArcInvocationContext context) throws Exception { + final boolean stopWhenCompleted = CompletionStage.class.isAssignableFrom(context.getMethod().getReturnType()); + final List samples = getSamples(context); - Object time(InvocationContext context, Timed timed, Tags commonTags, boolean stopWhenCompleted) throws Exception { - final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value(); - - if (timed.longTask()) { - return processWithLongTaskTimer(context, timed, commonTags, metricName, stopWhenCompleted); - } else { - return processWithTimer(context, timed, commonTags, metricName, stopWhenCompleted); + if (samples.isEmpty()) { + // This should never happen - at least one @Timed binding must be present + return context.proceed(); } - } - - private Object processWithTimer(InvocationContext context, Timed timed, Tags commonTags, String metricName, - boolean stopWhenCompleted) throws Exception { - - Timer.Sample sample = Timer.start(meterRegistry); - Tags timerTags = Tags.concat(commonTags, timed.extraTags()); if (stopWhenCompleted) { try { return ((CompletionStage) context.proceed()).whenComplete((result, throwable) -> { - record(timed, metricName, sample, MicrometerRecorder.getExceptionTag(throwable), timerTags); + for (Sample sample : samples) { + sample.stop(MicrometerRecorder.getExceptionTag(throwable)); + } }); } catch (Exception ex) { - record(timed, metricName, sample, MicrometerRecorder.getExceptionTag(ex), timerTags); + for (Sample sample : samples) { + sample.stop(MicrometerRecorder.getExceptionTag(ex)); + } throw ex; } } @@ -82,11 +70,32 @@ private Object processWithTimer(InvocationContext context, Timed timed, Tags com exceptionClass = MicrometerRecorder.getExceptionTag(ex); throw ex; } finally { - record(timed, metricName, sample, exceptionClass, timerTags); + for (Sample sample : samples) { + sample.stop(exceptionClass); + } + } + } + + private List getSamples(ArcInvocationContext context) { + Method method = context.getMethod(); + Tags commonTags = getCommonTags(method.getDeclaringClass().getName(), method.getName()); + List timed = context.findIterceptorBindings(Timed.class); + if (timed.isEmpty()) { + return Collections.emptyList(); } + List samples = new ArrayList<>(timed.size()); + for (Timed t : timed) { + if (t.longTask()) { + samples.add(new LongTimerSample(t, commonTags)); + } else { + samples.add(new TimerSample(t, commonTags)); + } + } + return samples; } - private void record(Timed timed, String metricName, Timer.Sample sample, String exceptionClass, Tags timerTags) { + private void record(Timed timed, Timer.Sample sample, String exceptionClass, Tags timerTags) { + final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value(); try { Timer.Builder builder = Timer.builder(metricName) .description(timed.description().isEmpty() ? null : timed.description()) @@ -103,30 +112,6 @@ private void record(Timed timed, String metricName, Timer.Sample sample, String } } - private Object processWithLongTaskTimer(InvocationContext context, Timed timed, Tags commonTags, String metricName, - boolean stopWhenCompleted) throws Exception { - LongTaskTimer.Sample sample = startLongTaskTimer(timed, commonTags, metricName); - if (sample == null) { - return context.proceed(); - } - - if (stopWhenCompleted) { - try { - return ((CompletionStage) context.proceed()) - .whenComplete((result, throwable) -> stopLongTaskTimer(metricName, sample)); - } catch (Exception ex) { - stopLongTaskTimer(metricName, sample); - throw ex; - } - } - - try { - return context.proceed(); - } finally { - stopLongTaskTimer(metricName, sample); - } - } - LongTaskTimer.Sample startLongTaskTimer(Timed timed, Tags commonTags, String metricName) { try { // This will throw if the annotation is incorrect. @@ -156,4 +141,53 @@ private void stopLongTaskTimer(String metricName, LongTaskTimer.Sample sample) { private Tags getCommonTags(String className, String methodName) { return Tags.of("class", className, "method", methodName); } + + abstract class Sample { + + protected final Timed timed; + protected final Tags commonTags; + + public Sample(Timed timed, Tags commonTags) { + this.timed = timed; + this.commonTags = commonTags; + } + + String metricName() { + return timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value(); + } + + abstract void stop(String exceptionClass); + } + + final class TimerSample extends Sample { + + private final Timer.Sample sample; + + public TimerSample(Timed timed, Tags commonTags) { + super(timed, commonTags); + this.sample = Timer.start(meterRegistry); + } + + @Override + void stop(String exceptionClass) { + record(timed, sample, exceptionClass, Tags.concat(commonTags, timed.extraTags())); + } + + } + + final class LongTimerSample extends Sample { + + private final LongTaskTimer.Sample sample; + + public LongTimerSample(Timed timed, Tags commonTags) { + super(timed, commonTags); + this.sample = startLongTaskTimer(timed, commonTags, metricName()); + } + + @Override + void stop(String exceptionClass) { + stopLongTaskTimer(metricName(), sample); + } + + } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/ConcurrentGaugeInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/ConcurrentGaugeInterceptor.java index 0234514388fa0..1155e3aea6925 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/ConcurrentGaugeInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/ConcurrentGaugeInterceptor.java @@ -3,13 +3,13 @@ import javax.annotation.Priority; import javax.interceptor.AroundConstruct; import javax.interceptor.AroundInvoke; -import javax.interceptor.AroundTimeout; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.annotation.ConcurrentGauge; +import io.quarkus.arc.ArcInvocationContext; + @ConcurrentGauge @Interceptor @Priority(Interceptor.Priority.LIBRARY_BEFORE + 10) @@ -23,22 +23,17 @@ class ConcurrentGaugeInterceptor { } @AroundConstruct - Object cGaugeConstructor(InvocationContext context) throws Exception { + Object cGaugeConstructor(ArcInvocationContext context) throws Exception { return cGauge(context, context.getConstructor().getDeclaringClass().getSimpleName()); } @AroundInvoke - Object cGaugeMethod(InvocationContext context) throws Exception { - return cGauge(context, context.getMethod().getName()); - } - - @AroundTimeout - Object cGaugeTimeout(InvocationContext context) throws Exception { + Object cGaugeMethod(ArcInvocationContext context) throws Exception { return cGauge(context, context.getMethod().getName()); } - Object cGauge(InvocationContext context, String methodName) throws Exception { - ConcurrentGauge annotation = MpMetricsRegistryProducer.getAnnotation(context, ConcurrentGauge.class); + Object cGauge(ArcInvocationContext context, String methodName) throws Exception { + ConcurrentGauge annotation = context.findIterceptorBinding(ConcurrentGauge.class); if (annotation != null) { MpMetadata metadata = new MpMetadata(annotation.name().replace("", methodName), annotation.description().replace("", methodName), diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/CountedInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/CountedInterceptor.java index 96e456bacd5fe..9d331037fd381 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/CountedInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/CountedInterceptor.java @@ -3,13 +3,13 @@ import javax.annotation.Priority; import javax.interceptor.AroundConstruct; import javax.interceptor.AroundInvoke; -import javax.interceptor.AroundTimeout; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.annotation.Counted; +import io.quarkus.arc.ArcInvocationContext; + @SuppressWarnings("unused") @Counted @Interceptor @@ -24,22 +24,17 @@ class CountedInterceptor { } @AroundConstruct - Object countedConstructor(InvocationContext context) throws Exception { + Object countedConstructor(ArcInvocationContext context) throws Exception { return increment(context, context.getConstructor().getDeclaringClass().getSimpleName()); } @AroundInvoke - Object countedMethod(InvocationContext context) throws Exception { - return increment(context, context.getMethod().getName()); - } - - @AroundTimeout - Object countedTimeout(InvocationContext context) throws Exception { + Object countedMethod(ArcInvocationContext context) throws Exception { return increment(context, context.getMethod().getName()); } - Object increment(InvocationContext context, String methodName) throws Exception { - Counted annotation = MpMetricsRegistryProducer.getAnnotation(context, Counted.class); + Object increment(ArcInvocationContext context, String methodName) throws Exception { + Counted annotation = context.findIterceptorBinding(Counted.class); if (annotation != null) { MpMetadata metadata = new MpMetadata(annotation.name().replace("", methodName), annotation.description().replace("", methodName), diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricsRegistryProducer.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricsRegistryProducer.java index 32cc2ddfeac6e..c3585efd16fcd 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricsRegistryProducer.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricsRegistryProducer.java @@ -1,17 +1,14 @@ package io.quarkus.micrometer.runtime.binder.mpmetrics; -import java.lang.annotation.Annotation; import java.util.*; import javax.enterprise.inject.Produces; import javax.inject.Singleton; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.metrics.*; import org.eclipse.microprofile.metrics.annotation.RegistryType; import io.micrometer.core.instrument.MeterRegistry; -import io.quarkus.arc.ArcInvocationContext; @Singleton @SuppressWarnings("unused") @@ -43,15 +40,4 @@ public MetricRegistry produceVendorRegistry(MeterRegistry registry) { return MpMetricsRecorder.getRegistry(MetricRegistry.Type.VENDOR); } - public static T getAnnotation(InvocationContext context, Class annotationClass) { - Set annotations = (Set) context.getContextData() - .get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); - - for (Annotation a : annotations) { - if (annotationClass.isInstance(a)) { - return annotationClass.cast(a); - } - } - return null; - } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/TimedInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/TimedInterceptor.java index c838455ef15c6..6124c087c4018 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/TimedInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/TimedInterceptor.java @@ -3,14 +3,13 @@ import javax.annotation.Priority; import javax.interceptor.AroundConstruct; import javax.interceptor.AroundInvoke; -import javax.interceptor.AroundTimeout; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.annotation.Timed; import io.micrometer.core.instrument.Timer; +import io.quarkus.arc.ArcInvocationContext; @SuppressWarnings("unused") @Timed @@ -26,22 +25,17 @@ class TimedInterceptor { } @AroundConstruct - Object timedConstructor(InvocationContext context) throws Exception { + Object timedConstructor(ArcInvocationContext context) throws Exception { return time(context, context.getConstructor().getDeclaringClass().getSimpleName()); } @AroundInvoke - Object timedMethod(InvocationContext context) throws Exception { + Object timedMethod(ArcInvocationContext context) throws Exception { return time(context, context.getMethod().getName()); } - @AroundTimeout - Object timedTimeout(InvocationContext context) throws Exception { - return time(context, context.getMethod().getName()); - } - - Object time(InvocationContext context, String methodName) throws Exception { - Timed annotation = MpMetricsRegistryProducer.getAnnotation(context, Timed.class); + Object time(ArcInvocationContext context, String methodName) throws Exception { + Timed annotation = context.findIterceptorBinding(Timed.class); if (annotation != null) { MpMetadata metadata = new MpMetadata(annotation.name().replace("", methodName), annotation.description().replace("", methodName), diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java index b365f7c517857..6f08c830db5fb 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java @@ -1,11 +1,13 @@ package io.quarkus.arc; import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; import java.util.Set; import javax.interceptor.InvocationContext; /** - * + * Enhanced version of {@link InvocationContext}. */ public interface ArcInvocationContext extends InvocationContext { @@ -20,4 +22,44 @@ public interface ArcInvocationContext extends InvocationContext { */ Set getInterceptorBindings(); + /** + * + * @param annotationType + * @return the first interceptor binding found, or {@code null} + */ + T findIterceptorBinding(Class annotationType); + + /** + * + * @param annotationType + * @return the list of interceptor bindings of the given annotation type + */ + List findIterceptorBindings(Class annotationType); + + /** + * + * @param context + * @param annotationType + * @return the first interceptor binding found, or {@code null} + */ + static T findIterceptorBinding(InvocationContext context, Class annotationType) { + if (context instanceof ArcInvocationContext) { + return ((ArcInvocationContext) context).findIterceptorBinding(annotationType); + } + return null; + } + + /** + * + * @param context + * @param annotationType + * @return the list of interceptor bindings of the given annotation type + */ + static List findIterceptorBindings(InvocationContext context, Class annotationType) { + if (context instanceof ArcInvocationContext) { + return ((ArcInvocationContext) context).findIterceptorBindings(annotationType); + } + return Collections.emptyList(); + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java index 0516b9e1b31e8..49108c32f80ae 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java @@ -4,6 +4,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -35,7 +36,7 @@ protected AbstractInvocationContext(Object target, Method method, this.constructor = constructor; this.parameters = parameters != null ? parameters : EMPTY_PARAMS; this.contextData = contextData != null ? contextData : new LazyValue<>(this); - this.interceptorBindings = interceptorBindings; + this.interceptorBindings = Collections.unmodifiableSet(interceptorBindings); this.chain = chain; } @@ -46,7 +47,30 @@ public Map getContextData() { @Override public Set getInterceptorBindings() { - return Collections.unmodifiableSet(interceptorBindings); + return interceptorBindings; + } + + @SuppressWarnings("unchecked") + @Override + public T findIterceptorBinding(Class annotationType) { + for (Annotation annotation : interceptorBindings) { + if (annotation.annotationType().equals(annotationType)) { + return (T) annotation; + } + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public List findIterceptorBindings(Class annotationType) { + List found = new ArrayList<>(); + for (Annotation annotation : (Set) interceptorBindings) { + if (annotation.annotationType().equals(annotationType)) { + found.add((T) annotation); + } + } + return found; } @Override diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/arcInvContext/ArcContextInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/arcInvContext/ArcContextInterceptor.java index 0ee0025b53209..6e9d7fa17de9e 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/arcInvContext/ArcContextInterceptor.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/arcInvContext/ArcContextInterceptor.java @@ -22,6 +22,12 @@ Object aroundInvoke(ArcInvocationContext ctx) throws Exception { if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } + if (ctx.findIterceptorBinding(SomeBinding.class) == null) { + throw new IllegalArgumentException("No bindings found"); + } + if (ctx.findIterceptorBindings(SomeBinding.class).isEmpty()) { + throw new IllegalArgumentException("No bindings found"); + } return "" + ctx.proceed() + ArcContextInterceptor.class.getSimpleName(); } }