This page walks through the MP Metrics 5.1.1 instrumentation patterns that come up in real applications. Every example relies strictly on the standard annotations org.eclipse.microprofile.metrics.annotation.*; no Dirac-proprietary import is ever needed in application code.
Maven coordinates
Only the MP Metrics spec is compiled by the application:
<dependency>
<groupId>org.eclipse.microprofile.metrics</groupId>
<artifactId>microprofile-metrics-api</artifactId>
<version>5.1.1</version>
<scope>provided</scope>
</dependency>
Add dirac-cdi-vauban for the interceptors and the BCE, and dirac-rest for the GET /metrics endpoint.
Counter — @Counted
Monotonic counter, incremented on every invocation. Internal implementation: LongAdder — no contention under heavy concurrency.
@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 (default false) suppresses the class-name prefix.
Timer — @Timed
Per-call duration measurement, aggregated in a HistogramImpl. Measured via System.nanoTime() around proceed().
@ApplicationScoped
public class CheckoutService {
@Timed(name = "order_checkout_seconds",
description = "Duration of checkout flow",
unit = MetricUnits.SECONDS)
public Receipt checkout(Cart cart) {
// ...
}
}
unit is free (the formatter does no automatic conversion) — by convention, express durations as MetricUnits.SECONDS for Prometheus.
Gauge — @Gauge
Instantaneous value returned by a bean method. Resolved by MethodHandle at startup by DiracExtension — never via 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();
}
}
|
The gauge method must return a numeric type ( |
Programmatic metrics
For cases that do not fit an annotation (counter incremented from a non-interceptable callback, batch-fed histogram, 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(…) is idempotent on MetricID: a second call returns the already-registered instance.
Annotation tags
The spec format is tags = { "key1=value1", "key2=value2" }. A metric’s uniqueness is computed on the union (name, annotation tags, global mp.metrics.tags).
@Counted(name = "http_requests_total",
tags = { "method=GET", "endpoint=orders" })
public List<Order> list() {
// ...
}
For dynamic tags (per request, per user), use the programmatic API:
registry.counter("http_requests_total",
new Tag("method", request.method()),
new Tag("status", String.valueOf(response.status())))
.inc();
Percentiles and buckets — distribution
Spec §4 exposes three mp.metrics.distribution.* keys read by DistributionConfig. They apply globally or per named metric.
# 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
An empty value (mp.metrics.distribution.percentiles=) disables percentiles globally and only emits _count and _sum.
Global application tags
mp.metrics.tags=app=orders,env=prod adds two tags to every metric, in every exposition, without touching application code.
mp.metrics.appName=orders-service
mp.metrics.tags=app=orders-service,env=prod,region=eu-west-3
These keys are resolved through Ravel, which implements MicroProfile Config 3.1.
HTTP exposition
With dirac-rest on the classpath, the endpoint follows MP Metrics §3:
# OpenMetrics / Prometheus text (default)
curl http://localhost:8080/metrics
# JSON (MP Metrics §3.2)
curl -H 'Accept: application/json' http://localhost:8080/metrics
# Single scope
curl http://localhost:8080/metrics/application
curl http://localhost:8080/metrics/base
curl http://localhost:8080/metrics/vendor
# Single family
curl http://localhost:8080/metrics/application/orders_placed_total
Negotiation runs on Accept. The OpenMetrics output looks like:
# 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