Skip to content

Commit

Permalink
Micrometer - rework interceptors
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
mkouba committed Jul 28, 2021
1 parent b4b1105 commit f86fa57
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;

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;
Expand All @@ -23,9 +26,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;
Expand All @@ -45,7 +51,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;
Expand All @@ -62,7 +67,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 {
Expand Down Expand Up @@ -97,7 +101,8 @@ MetricsCapabilityBuildItem metricsCapabilityPrometheusBuildItem(
UnremovableBeanBuildItem registerAdditionalBeans(CombinedIndexBuildItem indexBuildItem,
BuildProducer<MicrometerRegistryProviderBuildItem> providerClasses,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<InterceptorBindingRegistrarBuildItem> interceptorBindings) {

// Create and keep some basic Providers
additionalBeans.produce(AdditionalBeanBuildItem.builder()
Expand All @@ -111,13 +116,22 @@ 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() {
// TODO replace with the new API from https://github.com/quarkusio/quarkus/pull/19064
@Override
public Map<DotName, Set<String>> registerAdditionalBindings() {
return Map.of(DotName.createSimple(Timed.class.getName()),
Set.of("value", "extraTags", "longTask", "percentiles", "histogram", "description"));
}
}));

IndexView index = indexBuildItem.getIndex();

// Find classes that define MeterRegistries, MeterBinders, and MeterFilters
Expand Down Expand Up @@ -182,9 +196,27 @@ void collectNames(Collection<ClassInfo> classes, Collection<String> names) {
}

@BuildStep(onlyIf = MicrometerEnabled.class)
void processAnnotatedMetrics(BuildProducer<AnnotationsTransformerBuildItem> annotationsTransformers) {
annotationsTransformers.produce(createAnnotationTransformer(COUNTED_ANNOTATION, COUNTED_BINDING));
annotationsTransformers.produce(createAnnotationTransformer(TIMED_ANNOTATION, TIMED_BINDING));
AnnotationsTransformerBuildItem processAnnotatedMetrics(
BuildProducer<AnnotationsTransformerBuildItem> 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<AnnotationInstance> 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)
Expand Down Expand Up @@ -238,15 +270,6 @@ void configureRegistry(MicrometerRecorder recorder,
}
}

public static AnnotationInstance findAnnotation(Collection<AnnotationInstance> annotations, DotName annotationClass) {
for (AnnotationInstance a : annotations) {
if (annotationClass.equals(a.name())) {
return a;
}
}
return null;
}

ReflectiveClassBuildItem createReflectiveBuildItem(DotName sourceAnnotation, IndexView index) {
Set<String> classes = new HashSet<>();

Expand All @@ -268,17 +291,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<AnnotationInstance> annotations = ctx.getAnnotations();
AnnotationInstance annotation = MicrometerProcessor.findAnnotation(annotations, sourceAnnotation);
if (annotation == null) {
return;
}
ctx.transform().add(bindingAnnotation).done();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -28,7 +28,7 @@ static AnnotationsTransformerBuildItem transformAnnotations(final IndexView inde
@Override
public void transform(TransformationContext ctx) {
final Collection<AnnotationInstance> annotations = ctx.getAnnotations();
AnnotationInstance annotation = MicrometerProcessor.findAnnotation(annotations, sourceAnnotation);
AnnotationInstance annotation = Annotations.find(annotations, sourceAnnotation);
if (annotation == null) {
return;
}
Expand Down Expand Up @@ -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 " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 "";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.quarkus.micrometer.runtime;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.CompletionStage;

import javax.annotation.Priority;
Expand All @@ -12,6 +14,7 @@
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
Expand Down Expand Up @@ -52,12 +55,11 @@ public MicrometerCountedInterceptor(MeterRegistry meterRegistry) {
*/
@AroundInvoke
Object countedMethod(InvocationContext context) throws Exception {
Method method = context.getMethod();
Counted counted = method.getAnnotation(Counted.class);
MicrometerCounted counted = getCounted(context);
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
Expand All @@ -84,15 +86,15 @@ 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()) {
record(counted, commonTags, null);
}
}

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())
Expand All @@ -108,4 +110,16 @@ private void record(Counted counted, Tags commonTags, Throwable throwable) {
private Tags getCommonTags(String className, String methodName) {
return Tags.of("class", className, "method", methodName);
}

private MicrometerCounted getCounted(InvocationContext context) {
@SuppressWarnings("unchecked")
Set<Annotation> bindings = (Set<Annotation>) context.getContextData()
.get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS);
for (Annotation annotation : bindings) {
if (annotation.annotationType().equals(MicrometerCounted.class)) {
return (MicrometerCounted) annotation;
}
}
return null;
}
}

This file was deleted.

Loading

0 comments on commit f86fa57

Please sign in to comment.