3

In my application, I have a use case where I have to monitor a method by the argument value it is supplied. I have to expose the metrics to Prometheus endpoint. However, the function is a common function and is used by many different classes. I am trying to get the value passed in the method parameter to @Timed, so as to distinguish between different behaviors this function would exhibit based on the parameter value passed.

I tried using @Timed annotation but could not get the @Timed annotation expose the function parameter as a metric to Prometheus.

@Timed("getFooContent")
public void getFooContent(Arg1 arg1, Arg2 arg2) {
    //some code.... 
}
user1184527
  • 113
  • 1
  • 12

4 Answers4

4

I was able to figure this out by creating an annotation @Foo and then adding this annotation to the parameter of my function:

@Timed("getFooContent")
public void getFooContent(@Foo Arg1 arg1, Arg2 arg2) {
 //some code.... 
}

Following is my Timed Configuration class:

@Configuration
@SuppressWarnings("unchecked")
public class TimedConfiguration {
 public static final String NOT_AVAILABLE = "N/A";
  Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;
  @Bean
  public TimedAspect timedAspect(MeterRegistry registry) {
    tagsBasedOnJoinPoint = pjp ->
        Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
            "method", pjp.getStaticPart().getSignature().getName(),
            "parameter_1", getArguments(pjp));
    return new TimedAspect(registry, tagsBasedOnJoinPoint);
  }


private String getArguments(ProceedingJoinPoint pjp) {
    Object[] args = pjp.getArgs();
    String className = pjp.getStaticPart().getSignature().getDeclaringTypeName();
    if(className.contains("com.example.foo")) { //Resstricting to only certain packages starting with com.example.foo
      MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
      Method method = methodSignature.getMethod();
      Annotation[][] annotations = method.getParameterAnnotations();
      int index = -1;
      for(int i = 0; i < annotations.length; i++) {
        Annotation[] annotationsArr = annotations[i];
        for(Annotation annotation: annotationsArr) {
          if(annotation.annotationType().getName().equals(Foo.class.getName())) {
            index = i;
            break;
          }
        }
      }
      if(index >= 0) {
        List parameterValues = new ArrayList((List)args[index]);
        if(CollectionUtils.isNotEmpty(parameterValues) && parameterValues.get(0) instanceof Byte) {
          Collections.sort(parameterValues); //Sorting the paratemer values as per my use case
          return String.valueOf(parameterValues.stream().collect(Collectors.toSet()));
        }
      }  
    }
    return NOT_AVAILABLE;
  }
rsm
  • 2,530
  • 4
  • 26
  • 33
user1184527
  • 113
  • 1
  • 12
  • 2
    be very careful when adding custom tags/labes, you might end up in the "in-memory" limit exception for holding the metrics. An examples is the "uriVaraibles" limit/exception when doing rest-calls with webclient – rfelgent Aug 30 '21 at 09:22
3

I solved it with this TimedAspect configuration that I found in a PoC in a micrometer github issue: https://github.com/jonatan-ivanov/micrometer-tags/blob/master/src/main/java/com/example/micrometertags/MetricsConfig.java

@Configuration
public class MetricsConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry meterRegistry) {
    return new TimedAspect(meterRegistry, this::tagFactory);
}

private Iterable<Tag> tagFactory(ProceedingJoinPoint pjp) {
    return Tags.of(
        "class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
        "method", pjp.getStaticPart().getSignature().getName()
    )
    .and(getParameterTags(pjp))
    .and(ExtraTagsPropagation.getTagsAndReset());
}

private Iterable<Tag> getParameterTags(ProceedingJoinPoint pjp) {
    Set<Tag> tags = new HashSet<>();

    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    Parameter[] parameters = method.getParameters();
    for (int i = 0; i < parameters.length; i++) {
        for (Annotation annotation : parameters[i].getAnnotations()) {
            if (annotation instanceof ExtraTag) {
                ExtraTag extraTag = (ExtraTag) annotation;
                tags.add(Tag.of(extraTag.value(), String.valueOf(pjp.getArgs()[i])));
            }
        }
    }

    return tags;
}

}

2

There isn't a way to include the parameters in the timer's tags using just the annotation. Micrometer provides the annotation for simple use cases, and recommends using the programmatic approach when you need something more complex.

You should use the record method on the timer and wrap your code in that.

registry.timer("myclass.getFooContent", Tags.of("arg1", arg1)).record(() -> {
  //some code...
})
checketts
  • 14,167
  • 10
  • 53
  • 82
0

Add this below bean in your Configuration class and then try.

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

Annotate the configuration class with @EnableAspectJAutoProxy

Please read thru this link http://micrometer.io/docs/concepts#_the_timed_annotation