Skip to content

Instantly share code, notes, and snippets.

@pavelfomin
Last active January 16, 2024 23:12
Show Gist options
  • Save pavelfomin/c6034d012de7f71fdad8d5b5c35670ea to your computer and use it in GitHub Desktop.
Save pavelfomin/c6034d012de7f71fdad8d5b5c35670ea to your computer and use it in GitHub Desktop.
How to use @timed annotation

Using @Timed and Observation API

https://micrometer.io/docs/concepts#_the_timed_annotation

@Configuration
public class TimedConfiguration {
   @Bean
   public TimedAspect timedAspect(MeterRegistry registry) {
      return new TimedAspect(registry);
   }
}

Usage:

import io.micrometer.core.annotation.Timed;
@Timed(value = "tps.consumer", extraTags = {"consumer", "NotificationConsumer"})
public process() {
}

Using @MeterTag

@Configuration
public class TimedConfiguration {

    @Bean
    TimedAspect timedAspect(MeterRegistry registry, ValueResolverProvider valueResolverProvider, ValueExpressionResolverProvider valueExpressionResolverProvider) {

        TimedAspect timedAspect = new TimedAspect(registry);

        timedAspect.setMeterTagAnnotationHandler(
            new MeterTagAnnotationHandler(valueResolverProvider, valueExpressionResolverProvider));

        return timedAspect;
    }

    /**
     * Value expressions are not supported.
     */
    @Component
    @RequiredArgsConstructor
    static class ValueExpressionResolverProvider implements Function<Class<? extends ValueExpressionResolver>, ValueExpressionResolver> {

        @Override
        public ValueExpressionResolver apply(Class<? extends ValueExpressionResolver> aClass) {

            throw new IllegalArgumentException("Value expressions are not supported");
        }
    }
    
    /**
     * All custom ValueResolvers should be registered as singleton Beans for performance reason
     * to avoid their instantiation per @Timed call.
     */
    @Component
    @RequiredArgsConstructor
    static class ValueResolverProvider implements Function<Class<? extends ValueResolver>, ValueResolver> {

        private final NotificationMethodValueResolver notificationMethodValueResolver;

        private Map<Class, ValueResolver> valueResolvers = new HashMap<>();

        @PostConstruct
        public void postConstruct() {

            valueResolvers.put(notificationMethodValueResolver.getClass(), notificationMethodValueResolver);
        }

        @Override
        public ValueResolver apply(Class<? extends ValueResolver> aClass) {

            ValueResolver valueResolver = valueResolvers.get(aClass);

            if (valueResolver != null) {
                return valueResolver;
            } else {
                throw new IllegalArgumentException("Custom ValueResolver class " + aClass + " must be registered");
            }
        }
    }

    @Component
    public static class NotificationMethodValueResolver implements ValueResolver {

        @Override
        public String resolve(Object parameter) {
            return ((Notification) parameter).getNotificationMethod().toString();
        }
    }
}

Usage:

    @Timed(value = "tps.processor", extraTags = {"processor", "NotificationProcessor"})
    public NotificationEntity process(
        @MeterTag(value = "notificationMethod", resolver = TimedConfiguration.NotificationMethodValueResolver.class) Notification notification,
        // MeterTagAnnotationHandler will use .toString() by default 
        @MeterTag("metadata") KafkaEventMetadata metadata) {

Using programmatic API

public class ObservationConfiguration {

    static final String OBSERVATION = "foobar";
    static final String OBSERVATION_QUERY = OBSERVATION + ".query";
    static final String OBSERVATION_PUBLISH = OBSERVATION + ".publish";

    @Bean
    Observation queryObservation(ObservationRegistry observationRegistry) {

        return Observation.createNotStarted(OBSERVATION_QUERY, observationRegistry);
    }

    @Bean
    Observation publishObservation(ObservationRegistry observationRegistry) {

        return Observation.createNotStarted(OBSERVATION_PUBLISH, observationRegistry);
    }
}

Then use Observation beans to either call observe:

        Observation observation = publishObservation.lowCardinalityKeyValues(createPublishTags(payload));
        return observation.observe(() -> producer.sendMessage(payload));

or observeChecked:

        Observation observation = queryObservation.highCardinalityKeyValues(createQueryTags(offset));
        List<Data> objects = observation.observeChecked(() -> service.queryData(offset));

Limitations of @MeterTag:

  • only a single tag can be derived from a complex parameter
  • lack of low vs high cardinality support
  • not sure about additional per call performance implications, especially with the expression usage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment