Search code examples
javaspringspring-bootmicrometerspring-micrometer

Java Update Micrometer Gauge with Dynamic Labels


I'm using Java Micrometer gauge to measure a metric that has a range of labels. The label values are dynamically retrieved but bounded.

For example: I have withdraw.cycle metric with label currency.

Because Micrometer doesn't hold the reference to the gauge itself, it makes the code look quite cumbersome.

I've got two ways to do it:

private void updateGauge(String currency, int minutes) {
    Tags tags = Tags.of("currency", currency);
    Gauge.builder("withdraw.cycle",() -> minutes)
         .tags(tags)
         .strongReference(true)
         .register(meterRegistry);
}

OR

private final Map<Tags, AtomicLong> cycleGaugeMap = new ConcurrentHashMap<>();

// Use cycleGaugeMap to hold the reference to the gauge
private void updateGauge(String currency, int minutes) {
    Tags tags = Tags.of("currency", currency);
    if (cycleGaugeMap.containsKey(tags)) {
        cycleGaugeMap.get(tags).set(minutes);
    } else {
        AtomicLong old = cycleGaugeMap.putIfAbsent(tags, meterRegistry.gauge("withdraw.cycle", tags,
                                                                                     new AtomicLong(minutes)));
        if (old != null) {
            old.set(minutes);
        }
    }
 }

Question: Are they the right ways to do it or does Micrometer have an API that I'm not aware of for such cases? I'd really want to know a "standard" and elegant way to do it.


Solution

  • Kindly follow this github issue here.

    To Summarize:

    • First approach to update gauge is via MultiGauge:

    By definition, Micrometer supports one special type of Gauge, called a MultiGauge, to help manage gauging a growing or shrinking list of criteria. This feature lets you select a set of well-bounded but slightly changing set of criteria from something like an SQL query and report some metric for each row as a Gauge.

    Working example:

    public class Example {
    
        private static final SimpleMeterRegistry registry = new SimpleMeterRegistry();
        private static final MultiGauge gauge = MultiGauge.builder("withdraw.cycle").register(registry);
    
        private static final Map<String, Number> values = new ConcurrentHashMap<>(); 
    
        private static void updateGauge(String currency, int minutes) {
            values.put(currency, minutes);
            List<MultiGauge.Row<?>> rows = values.entrySet()
                .stream()
                .map(row -> MultiGauge.Row.of(Tags.of("currency", row.getKey()), row.getValue()))
                .collect(Collectors.toList());
            gauge.register(rows, true);
        }
    
        public static void main(String[] args) throws InterruptedException {
           updateGauge("Rupee", 1);
           System.out.println(registry.getMetersAsString());
           System.out.println("--------");
           updateGauge("Dollar", 2);
           System.out.println(registry.getMetersAsString());
           System.out.println("--------");
           updateGauge("Rupee", 3);
           System.out.println(registry.getMetersAsString());
        }
    

    Output:

    withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
    --------
    withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
    withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
    --------
    withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
    withdraw.cycle(GAUGE)[currency='Rupee']; value=3.0
    
    • Second Approach to update gauge via Gauge itself.

    Working example:

    public class Example {
    
        private static final SimpleMeterRegistry registry = new SimpleMeterRegistry();
    
        private static final Map<String, Number> values = new ConcurrentHashMap<>();
    
        private static void updateGauge(String currency, int minutes) {
            Tags tags = Tags.of("currency", currency);
            values.put(currency, minutes);
            if (registry.find("withdraw.cycle").tags(tags).gauge() == null) {
                Gauge.builder("withdraw.cycle", () -> values.get(currency))
                        .tags(tags)
                        .register(registry);
            }
        }
    
    
        public static void main(String[] args) throws InterruptedException {
            updateGauge("Rupee", 1);
            System.out.println(registry.getMetersAsString());
            System.out.println("--------");
            updateGauge("Dollar", 2);
            System.out.println(registry.getMetersAsString());
            System.out.println("--------");
            updateGauge("Rupee", 3);
            System.out.println(registry.getMetersAsString());
        }
    }
    

    Output:

    withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
    --------
    withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
    withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
    --------
    withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
    withdraw.cycle(GAUGE)[currency='Rupee']; value=3.0
    

    I guess this is the optimal approach that you were looking for.