Search code examples
javaquarkusmicroprofilesmallrye

Microprofile (SmallRye) @Gauge but not invoked by the metrics infrastructure


I'm using SmallRye implementation for Eclipse Microprofile Metrics in a project that uses Quarkus at version 1.7.3. I need to collect a random Integer value returned by a method in an ApplicationScoped class as a metric, but when I use @Gauge the method is invoked by the metrics infrastructure. It doesn't work for me because I need to provide a parameter to that method so it can make sense. Is there a way to collect this metric when the method is invoked by my own application and not by the Microprofile implementation?

In my scenario I send an Item to an external API and wait for them to respond using my API. This is asynchronous, should take at least 2 minutes and an avarege of 5 to 6 minutes, but it can sometimes take several minutes. So when I receive the response I first retrieve the Item from my database and before I do whatever I need to do with it I call the method timeWaitingForResponseMinutes passing the Item I just retrieved from the database as a parameter:

    @Gauge(name = "timeWaitingForResponseMinutes", description = "Time waiting for a response for the Item in minutes", unit = MetricUnits.NONE)
    public Integer timeWaitingForResponseMinutes(Item item) {
        Timestamp nowTimestamp = Timestamp.from(Instant.now());
        long nowMilliseconds = nowTimestamp.getTime();
        long itemMilliseconds = item.getTimestampItemSent().getTime();
        Integer minutesWaiting = (int)((nowMilliseconds - itemMilliseconds) / (60 * 1000));
        return minutesWaiting;
    }

This Item has a java.sql.Timestamp field called timestampItemSent that I use to compare with current time. The @Gauge is not working like that since the method should have no parameters. I'm taking the error message: Unable to export metric company_controller_ItemController_timeWaitingForResponseMinutes: java.lang.IllegalArgumentException

Any help will be very much appreciated.


Solution

  • Essentially what you need is to time a span that is defined by two different methods in your code, one for starting the span and one for ending it. I do not think it is doable with annotations, it is doable however with a bit of programming. You can inject a metric in a CDI bean, so I would modify the timeWaitingForResponseMinutes as follows:

    import org.eclipse.microprofile.metrics.Timer;
    import java.util.concurrent.TimeUnit;
    
    @ApplicationScoped
    public class MyResponseTimer {
        @Inject
        @Metric(name = "timeWaitingForResponseMinutes", description = "Time waiting for a response for the Item in minutes", unit = MetricUnits.MINUTES)
        private Timer timer;
    
        public void timeWaitingForResponseMinutes(Item item) {
            Timestamp nowTimestamp = Timestamp.from(Instant.now());
            long nowMilliseconds = nowTimestamp.getTime();
            long itemMilliseconds = item.getTimestampItemSent().getTime();
            long minutesWaiting = ((nowMilliseconds - itemMilliseconds) / (60 * 1000));
            timer.update(minutesWaiting, TimeUnit.MINUTES);
        }
    }
    

    Now inject this bean and just call timeWaitingForResponseMinutes as you wanted, passing your Item. (You will see many pieces of your own code, note however that I changed the unit of the metric to MINUTES to make sense)