2

I'm working on a huge legacy project with tons of legacy and new endpoints.

We are planning on deleting old endpoints that are unused. We use Grafana to graph counts on how much an endpoint is being used.

Problem is Prometheus only lists endpoints that have at least been called once. Therefore the unused ones are not being shown in Grafana.

# HELP http_server_requests_seconds  
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{application="foo",...,uri="/api/internal/v5/foos/{..}/...",} 822.0
http_server_requests_seconds_sum{application="foo",...,uri="/api/internal/v5/foos/{..}/...",} 13.827411282
http_server_requests_seconds_count{application="foo",...,uri="/api/internal/v4/foos/...",} 475.0
http_server_requests_seconds_sum{application="foo",...,uri="/api/internal/v4/foos/...",} 4.885103028
http_server_requests_seconds_count{application="foo",...,uri="/api/ui/...",} 1496.0
http_server_requests_seconds_sum{application="foo",...,uri="/api/ui/...",} 1.638166633
http_server_requests_seconds_count{application="foo",...,uri="/...",} 30589.0
http_server_requests_seconds_sum{application="foo",...,uri="/...",} 23.555870127
http_server_requests_seconds_count{application="foo",...,uri="/api/internal/v5/foos/{..}/...",} 4.0
http_server_requests_seconds_sum{application="foo",...,uri="/api/internal/v5/foos/{..}/...",} 2.474261611
http_server_requests_seconds_count{application="foo",...,uri="/prometheus",} 165998.0
http_server_requests_seconds_sum{application="foo",...,uri="/prometheus",} 1652.047452397

Is there a way I can include unused endpoints, so that they are shown as:

http_server_requests_seconds_count{application="foo",...,uri="/api/internal/v5/foos/{..}/...",} 0.0
http_server_requests_seconds_count{application="foo",...,uri="/api/internal/v4/foos/...",} 0.0

Every endpoint would have been called once in it's history, but we only added prometheus metrics recently, so we can't go back to the beginnings of time.

Thanks in advance!

Edito
  • 3,030
  • 13
  • 35
  • 67
  • I know this doesnt answer your question, but I solved this using Aspect to wrap all my RestControllers. Then, I added a counter.increment() (to a dedicated prometheus metric that I created) everytime a call occured. This way I had full control over my metric, so I just initiated all like: my_super_metric { endpoint ="/foo/bar" } with 1. After a while you can query the metric and check the least used (or the ones that have a value of 1 indicating no usage at all). – Jodee Sep 30 '22 at 09:25
  • @Jodee You can add this as a standalone answer with added code examples if you'd like. I considered doing something similar by calling each existing endpoint listed under `/mappings` once. during app startup Then you could treat every endpoint having value `1.0` as never been used. Problem is that we must now treat a `1.0` as some sort of false-positive, we can never tell for sure if the endpoint has been actually called once or if it was part the initial call. – Edito Oct 03 '22 at 11:24
  • Maybe a Gauge metric would be more suitable for that, I went with the Counter solution – Jodee Oct 04 '22 at 12:39

1 Answers1

0

I managed to solve this by using an Aspect class that is incrementing a Prometheus counter every time a call is made to one of my GetMapping/PostMapping/PutMapping/DeleteMapping/RequestMapping methods.

I used org.reflections library to find all my annotated methods

@PostConstruct //after bean initialization I initialize the Prometheus counter for each annotated method
public void postConstruct(){
    for (Method method : getAnnotatedMethods()) {
        Counter.builder(METER_NAME)
                .tag("package", method.getDeclaringClass().getPackageName())
                .tag("name" , method.getName())
                .register(meterRegistry);
    }
}

//used to find all methods with the given annotations
private HashSet<Method> getAnnotatedMethods(){
    Reflections reflections = new Reflections("my.super.package", Scanners.MethodsAnnotated);
    HashSet<Method> methods = new HashSet<>();
    methods.addAll(reflections.getMethodsAnnotatedWith(GetMapping.class));
    methods.addAll(reflections.getMethodsAnnotatedWith(PostMapping.class));
    methods.addAll(reflections.getMethodsAnnotatedWith(PutMapping.class));
    methods.addAll(reflections.getMethodsAnnotatedWith(PatchMapping.class));
    methods.addAll(reflections.getMethodsAnnotatedWith(DeleteMapping.class));
    methods.addAll(reflections.getMethodsAnnotatedWith(RequestMapping.class));
    return methods;
}

And then I wrapped all the listed annotations with a Pointcut aspect as described here https://stackoverflow.com/questions/42642681/spring-aspectj-before-all-rest-method[enter link description here]1

@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping) " +
        "|| @annotation(org.springframework.web.bind.annotation.GetMapping)" +
        "|| @annotation(org.springframework.web.bind.annotation.PostMapping)" +
        "|| @annotation(org.springframework.web.bind.annotation.PatchMapping)" +
        "|| @annotation(org.springframework.web.bind.annotation.PutMapping)" +
        "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)"
)
public void mappingAnnotations() {}

@AfterReturning("mappingAnnotations()")
public void onExecute(JoinPoint jp) {
    addMetric(jp.getSignature().getDeclaringType().getPackageName(), jp.getSignature().getName());
}


private void addMetric(String packageName, String methodName){
    LOGGER.trace("Incrementing [{}] with [{}] and [{}]", METER_NAME, packageName, methodName);
    Counter.builder(METER_NAME)
            .tag("package", packageName)
            .tag("name" , methodName)
            .register(meterRegistry).increment();
}

I am not a Prometheus expert so I guess there will be more sophisticated ways/counter types to use is such scenario. This implementation was more than enough for what I wanted so I sticked with it.

Jodee
  • 156
  • 1
  • 10