Cette page parcourt les patrons d’instrumentation MP Metrics 5.1.1 couramment rencontrés. Tous les exemples reposent strictement sur les annotations standard org.eclipse.microprofile.metrics.annotation.* ; aucun import propriétaire Dirac n’est jamais requis dans le code applicatif.

Coordonnées Maven

Seule la spec MP Metrics est compilée par l’application :

<dependency>
  <groupId>org.eclipse.microprofile.metrics</groupId>
  <artifactId>microprofile-metrics-api</artifactId>
  <version>5.1.1</version>
  <scope>provided</scope>
</dependency>

Ajoutez dirac-cdi-vauban pour les intercepteurs et la BCE, et dirac-rest pour l’endpoint GET /metrics.

Compteur — @Counted

Compteur monotone, incrémenté à chaque invocation. Implémentation interne : LongAdder — sans contention sur la concurrence forte.

@ApplicationScoped
public class OrderService {

    @Counted(name = "orders_placed_total",
             description = "Total number of orders placed",
             tags = "endpoint=orders")
    public void placeOrder(String id) {
        // ...
    }
}

absolute=true (par défaut false) supprime le préfixage par le nom de la classe.

Minuterie — @Timed

Mesure de durée par appel, agrégée dans un HistogramImpl. Mesure via System.nanoTime() autour de proceed().

@ApplicationScoped
public class CheckoutService {

    @Timed(name = "order_checkout_seconds",
           description = "Duration of checkout flow",
           unit = MetricUnits.SECONDS)
    public Receipt checkout(Cart cart) {
        // ...
    }
}

unit est libre (le formatter ne fait pas de conversion automatique) — par convention, exprimer la durée en MetricUnits.SECONDS pour Prometheus.

Jauge — @Gauge

Valeur instantanée renvoyée par une méthode du bean. Résolution par MethodHandle au démarrage par DiracExtension — jamais par java.lang.reflect.Proxy.

@ApplicationScoped
public class QueueService {

    private final BlockingQueue<Order> backlog = new LinkedBlockingQueue<>();

    @Gauge(name = "backlog_size",
           unit = "none",
           description = "Pending orders in backlog")
    public int backlogSize() {
        return backlog.size();
    }
}

La méthode jauge doit retourner un type numérique (int, long, double, Number). Une signature invalide fait échouer la BCE au démarrage avec un message explicite — pas de surprise silencieuse au premier appel à /metrics.

Métriques programmatiques

Pour les cas qui ne s’expriment pas via annotation (compteur incrémenté depuis un callback non interceptable, histogramme alimenté en lot, etc.) :

@ApplicationScoped
public class RateLimiter {

    @Inject
    MetricRegistry registry;

    private Counter rejections;

    @PostConstruct
    void init() {
        rejections = registry.counter(
            Metadata.builder()
                .withName("rate_limited_total")
                .withDescription("Requests rejected by rate limit")
                .build(),
            new Tag("policy", "burst"));
    }

    public void reject() {
        rejections.inc();
    }
}

MetricRegistry.counter/histogram/timer/gauge(…​) est idempotent sur MetricID : un second appel renvoie l’instance déjà enregistrée.

Tags d’annotation

Le format spec est tags = { "clef1=valeur1", "clef2=valeur2" }. L’unicité d’une métrique se calcule sur l’union (name, tags d’annotation, tags globaux mp.metrics.tags).

@Counted(name = "http_requests_total",
         tags = { "method=GET", "endpoint=orders" })
public List<Order> list() {
    // ...
}

Pour des tags dynamiques (par requête, par utilisateur), utiliser l’API programmatique :

registry.counter("http_requests_total",
    new Tag("method", request.method()),
    new Tag("status", String.valueOf(response.status())))
    .inc();

Percentiles et buckets — distribution

La spec §4 expose trois clés mp.metrics.distribution.* lues via DistributionConfig. Elles s’appliquent globalement ou par métrique nommée.

# microprofile-config.properties
mp.metrics.distribution.percentiles=0.5,0.75,0.95,0.99
mp.metrics.distribution.percentiles=order_checkout_seconds=0.5,0.95,0.99
mp.metrics.distribution.timer.buckets=order_checkout_seconds=10ms,50ms,250ms,1s,5s

Une valeur vide (mp.metrics.distribution.percentiles=) désactive les percentiles globalement et n’émet plus que _count et _sum.

Tags globaux d’application

mp.metrics.tags=app=orders,env=prod ajoute deux tags à toutes les métriques, à toutes les expositions, sans modifier le code applicatif.

mp.metrics.appName=orders-service
mp.metrics.tags=app=orders-service,env=prod,region=eu-west-3

Ces clés sont résolues via Ravel qui implémente MicroProfile Config 3.1.

Exposition HTTP

Avec dirac-rest sur le classpath, l’endpoint suit la spec MP Metrics §3 :

# OpenMetrics / Prometheus text (défaut)
curl http://localhost:8080/metrics

# JSON (MP Metrics §3.2)
curl -H 'Accept: application/json' http://localhost:8080/metrics

# Un seul scope
curl http://localhost:8080/metrics/application
curl http://localhost:8080/metrics/base
curl http://localhost:8080/metrics/vendor

# Une famille précise
curl http://localhost:8080/metrics/application/orders_placed_total

La négociation se fait sur Accept. La sortie OpenMetrics ressemble à :

# HELP order_checkout_seconds Duration of checkout flow
# TYPE order_checkout_seconds summary
order_checkout_seconds{mp_scope="application",quantile="0.5"} 0.024
order_checkout_seconds{mp_scope="application",quantile="0.95"} 0.072
order_checkout_seconds_count{mp_scope="application"} 1542
order_checkout_seconds_sum{mp_scope="application"} 38.71

Métriques JVM (BASE)

Pas de code à écrire : BaseMetricsRegistrar peuple le registre BASE au démarrage, via DiracExtension. Les métriques listées par la spec §5 (mémoire, GC, threads, classes, CPU, uptime) sont exposées automatiquement sur GET /metrics/base.

curl http://localhost:8080/metrics/base

Pour aller plus loin