MicroProfile metrics instrumentation API
You can use the MicroProfile metrics API to add metrics to your applications. The MicroProfile metrics API is similar to the Dropwizard metrics API.
Documentation for the MicroProfile Metrics feature versions 2.0 and later is available on the Open Liberty website.
Architecture of MicroProfile metrics
mpMetrics-1.x
and mpMetrics-2.x
features provide three
metric registries where metrics are stored.- Base
- The base registry is used for metrics from the server as necessary from the MicroProfile metrics specification.
- Vendor
- The vendor registry is used for metrics that are added by the product vendor, in this case Liberty.
- Application
- The application registry is used for metrics that are added by your applications. The application registry is the default MetricRegistry.
The mpMetrics-1.x
and mpMetrics-2.x
features also include a
/metrics endpoint, which provides the HTTP interface for browsers and
monitoring tools to interact with.
Checking values of metrics
MicroProfile metrics provide a /metrics endpoint that you can access by using HTTP. After the Liberty server is started with the proper configuration, you can view your metrics from any browser.
https://localhost:9443/metrics
In general, operations teams configure tools to monitor and record the metric values over time.
Adding metrics to your applications
- Using the MetricRegistry directly. By using this method, your application code explicitly creates the metrics and registers them.
- Using CDI to inject metrics. By using this method, metrics are implicitly created by CDI and registered with the application MetricRegistry.
- Using metrics annotations. By using this method, metrics are implicitly created by CDI and registered with the application MetricRegistry.
Directly administering the MetricRegistry
The MetricRegistry stores all metrics and their respective metadata. Metrics and metadata can be registered and retrieved by using the methods that are provided. By default, all application-related metrics are added to a single application scoped MetricRegistry.
@Inject
MetricRegistry registry;
Registering metrics
The MetricRegistry class has methods that can retrieve or create counters, meters, histograms, and timers.
Metadata counterMetadata = new Metadata(…);
…
// Create or retrieve the metric by Metadata.getName()
Counter counterMetric = registry.counter(counterMetadata);
Timer timerMetric = registry.timer(timerMetadata);
Histogram histogramMetric = registry.histogram(histogramMetadata);
Meter meterMetric = registry.meter(meterMetadata);
// Register a gauge metric by Metadata.getName()
registry.register("gaugeMetric", gaugeMetric, gaugeMetadata);
Describing metrics with metadata
Metadata is a set of data that describes and summarizes information about metrics and can make finding and working with different types of metrics easier. Metadata can be granular enough to define information at the attribute level.
Metric metadata consists of the following attributes:
- Unit
- A fixed set of string units.
- Type
- Gauge, counter, meter, histogram, or timer.
- Description
- A human readable description of the metric.
- DisplayName
- A human readable name of the metric for display purposes if the metric name is not human readable, for example, when the metric name is a UUID.
- Tags
- A list of key and value (key=value) pairs, which are separated by commas.
- Reusable
- In MicroProfile Metrics version 1.1 and higher, reusable is a Boolean to indicate that the metric name can be reused. For example, when you want to reuse a single metric while you annotate multiple methods.
MP_METRICS_TAGS
.export MP_METRICS_TAGS=app=shop,node=2932
You can also use MicroProfile config to configure
MP_METRICS_TAGS
. For more information about how to configure
MP_METRICS_TAGS
, see Valid configuration value locations in the Liberty runtime.
Counters
Counters are a metric that is used to keep an incremental or a decremental count. The initial
value of the counter is set to 0 and can be incremented by using inc()
or
inc(long n)
and decremented by using dec()
or dec(long
n)
.
You can use a counter to count total number of requests that are received or total number of concurrently active HTTP sessions.
/donations
REST
endpoints.Metadata statsHitsCounterMetadata = new Metadata(
"statsHits", // name
"Stats Hits", // display name
"Number of hits on the /stats endpoint", // description
MetricType.COUNTER, // type
MetricUnits.NONE); // units
Counter statsHitsCounter = registry.counter(statsHitsCounterMetadata);
@GET
@Path("/donations")
public String getTotalDonations() {
statsHitsCounter.inc();
return "$" + donationManager.getTotalDonations();
}
curl -k -u user:password https://localhost:9443/metrics/application/statsHits
# TYPE application:stats_hits counter
# HELP application:stats_hits Number of hits on the /stats endpoint
application:stats_hits 213
curl -k -u user:password -H "Accept: application/json" https://localhost:9443/metrics/application/statsHits
{"statsHits":213}
Gauges
Gauges represent metrics that are sampled to obtain their value.
A gauge is an interface that needs to be implemented by the developer. Since the implementation
of a gauge is not defined, they must be manually registered with the MetricRegistry by using the
MetricRegistry .register()
method. Ensure that register()
is used
only one time per metric. You cannot register two metrics with the same name. You would use a gauge
to measure CPU temperature or to measure disk usage.
Gauge<Double> progressGauge = new Gauge<Double>() {
public Double getValue() {
return (double) getTotalDonations()
/ (double) getGoal() * 100.0;
}
};
// Gauge
Metadata progressMetadata = new Metadata(
"progress", // name
"Donation Progress", // display name
"The percentage of the goal achieved so far", // description
MetricType.GAUGE, // type
MetricUnits.PERCENT); // units
registry.register(progressMetadata.getName(), progressGauge, progressMetadata);
curl -k -u user:password https://localhost:9443/metrics/application/progress
# TYPE application:progress_percent gauge
# HELP application:progress_percent The percentage of the goal achieved so far
application:progress_percent 4.472
curl -k -u user:password -H "Accept: application/json" https://localhost:9443/metrics/application/progress
{"progress":4.472}
Meters
Meters are used to track throughput.
meter.mark()
method to mark an event. For
multiple events, you can also use mark(long n)
to mark multiple occurrences of
events at the same time. A meter provides the following information:- Mean throughput.
- One/five/fifteen minute exponentially weighted moving average throughput.
- A count of the number of measurements.
You can use a meter to calculate the rate of transactions that are being processed by an application.
getProcess()
is
called. A high rate might indicate that the result needs to be cached or a rate limit needs to be
imposed.Metadata getProgressMeterMetadata = new Metadata(
"getProgressMeter", // name
"getProgress Call Rate", // display name
"The rate of getProgress calls", // description
MetricType.METERED, // type
MetricUnits.SECONDS); // units
Meter getProgressMeter = registry.meter(getProgressMeterMetadata);
public Double getProgress() {
getProgressMeter.mark();
return (double) getTotalDonations()/(double) getGoal() * 100.0;
}
curl -k -u user:password https://localhost:9443/metrics/application/getProgressMeter1
# TYPE application:get_progress_meter_total counter
# HELP application:get_progress_meter_total The rate of getProgress calls
application:get_progress_meter_total 78
# TYPE application:get_progress_meter_rate_per_second gauge
application:get_progress_meter_rate_per_second 0.6584919803150174
# TYPE application:get_progress_meter_one_min_rate_per_second gauge
application:get_progress_meter_one_min_rate_per_second 0.5851884005757912
# TYPE application:get_progress_meter_five_min_rate_per_second gauge
application:get_progress_meter_five_min_rate_per_second 1.4218610926179416
# TYPE application:get_progress_meter_fifteen_min_rate_per_second gauge
application:get_progress_meter_fifteen_min_rate_per_second 1.6627979138032118
curl -k -u user:password -H "Accept: application/json" https://localhost:9443/metrics/application/getProgressMeter
{
"getProgressMeter": {
"count": 78,
"fifteenMinRate": 1.6627979138032118,
"fiveMinRate": 1.4218610926179416,
"meanRate": 0.6584919803150174,
"oneMinRate": 0.5851884005757912
}
}
Histograms
Histograms are used to store the distribution of values.
To record a value in the histogram, you must call histogram.update(long value)
with the value that you want to record. The current state (or snapshot) of the histogram can be
retrieved by using getSnapshot()
. Histograms in MicroProfile Metrics only support
integer or long values.
- Max/Min/Mean values
- The value at the 50th, 75th, 95th, 98th, 99th, 99.9th percentile
- A count of the number of values
Examples of histograms include the distribution of payload sizes that are retrieved or for an onboarding survey that collects the distribution of household income.
Metadata donationDistributionMetadata = new Metadata(
"donationDistribution", // name
"Donation Distribution", // display name
"The distribution of the donation amounts ", // description
MetricType.HISTOGRAM, // type
"dollars"); // units
Histogram donationDistribution = registry.histogram(donationDistributionMetadata);
public void addDonation(Long amount) {
totalDonations += amount;
donations.add(amount);
donationDistribution.update(amount);
}
curl -k -u user:password https://localhost:9443/metrics/application/com.example.samples.donationapp.DonationManager.donationDistribution
# TYPE application:com_example_samples_donationapp_donation_manager_donation_distribution_mean_dollars gauge
application:com_example_samples_donationapp_donation_manager_donation_distribution_mean_dollars 19.300015535407777
# TYPE application:com_example_samples_donationapp_donation_manager_donation_distribution_max_dollars gauge
application:com_example_samples_donationapp_donation_manager_donation_distribution_max_dollars 102.0
# TYPE application:com_example_samples_donationapp_donation_manager_donation_distribution_min_dollars gauge
application:com_example_samples_donationapp_donation_manager_donation_distribution_min_dollars 3.0
# TYPE application:com_example_samples_donationapp_donation_manager_donation_distribution_stddev_dollars gauge
application:com_example_samples_donationapp_donation_manager_donation_distribution_stddev_dollars 26.35464238355834
# TYPE application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars summary
# HELP application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars The distribution of the donation amounts
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars_count 203
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars{quantile="0.5"} 5.0
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars{quantile="0.75"} 24.0
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars{quantile="0.95"} 83.0
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars{quantile="0.98"} 93.0
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars{quantile="0.99"} 101.0
application:com_example_samples_donationapp_donation_manager_donation_distribution_dollars{quantile="0.999"} 102.0
curl -k -u user:password -H "Accept: application/json" https://localhost:9443/metrics/application/com.example.samples.donationapp.DonationManager.donationDistribution
{
"com.example.samples.donationapp.DonationManager.donationDistribution": {
"count": 203,
"max": 102,
"mean": 19.300015535407777,
"min": 3,
"p50": 5.0,
"p75": 24.0,
"p95": 83.0,
"p98": 93.0,
"p99": 101.0,
"p999": 102.0,
"stddev": 26.35464238355834
}
}
Timers
Timers are used to aggregate timing durations, in nanoseconds, and provide duration and throughput statistics.
To time a portion of the code, you can call timer.time()
, which returns a
timer.context
object. This context is used to stop the timer by calling
context.close()
. The information that is retrieved from the timer is a combination
of a meter and a histogram of timed durations.
- Max/Min/Mean times.
- The time value at the 50th, 75th, 95th, 98th, 99th, 99.9th percentile.
- Mean throughput.
- One/five/fifteen minute exponentially weighted moving average throughput.
- A count of the number of timed events.
Examples of what you can use a timer for include measuring response times and measuring processing time for complex logic.
Metadata topDonationCalcTimerMetadata = new Metadata(
"topDonationCalcTimer", // name
"Top Donation Calculation Time", // display name
"Processing time to find the highest donation", // description
MetricType.TIMER, // type
MetricUnits.NANOSECONDS); // units
Timer topDonationCalcTimer = registry.timer(topDonationCalcTimerMetadata);
public Long getTopDonation() {
// Start timing here
Timer.Context context = topDonationCalcTimer.time();
Long max = 0L;
for (Long amount : donationManager.getDonationsList()) {
max = amount > max ? amount : max;
}
// Stop timing
context.close();
return max;
}
curl -k -u user:password https://localhost:9443/metrics/application/topDonationCalcTimer
# TYPE application:top_donation_calc_timer_rate_per_second gauge
application:top_donation_calc_timer_rate_per_second 0.19107302754898328
# TYPE application:top_donation_calc_timer_one_min_rate_per_second gauge
application:top_donation_calc_timer_one_min_rate_per_second 0.013233974568205872
# TYPE application:top_donation_calc_timer_five_min_rate_per_second gauge
application:top_donation_calc_timer_five_min_rate_per_second 0.4845914744130395
# TYPE application:top_donation_calc_timer_fifteen_min_rate_per_second gauge
application:top_donation_calc_timer_fifteen_min_rate_per_second 0.8866728789348088
# TYPE application:top_donation_calc_timer_mean_seconds gauge
application:top_donation_calc_timer_mean_seconds 9.37780684853573E-5
# TYPE application:top_donation_calc_timer_max_seconds gauge
application:top_donation_calc_timer_max_seconds 1.97197E-4
# TYPE application:top_donation_calc_timer_min_seconds gauge
application:top_donation_calc_timer_min_seconds 4.9630000000000004E-5
# TYPE application:top_donation_calc_timer_stddev_seconds gauge
application:top_donation_calc_timer_stddev_seconds 3.082659934664267E-5
# TYPE application:top_donation_calc_timer_seconds summary
# HELP application:top_donation_calc_timer_seconds Processing time to find the highest donation
application:top_donation_calc_timer_seconds_count 63
application:top_donation_calc_timer_seconds{quantile="0.5"} 8.6069E-5
application:top_donation_calc_timer_seconds{quantile="0.75"} 1.0372E-4
application:top_donation_calc_timer_seconds{quantile="0.95"} 1.53694E-4
application:top_donation_calc_timer_seconds{quantile="0.98"} 1.96615E-4
application:top_donation_calc_timer_seconds{quantile="0.99"} 1.97197E-4
application:top_donation_calc_timer_seconds{quantile="0.999"} 1.97197E-4
curl -k -u user:password -H "Accept: application/json" https://localhost:9443/metrics/application/topDonationCalcTimer
{
"topDonationCalcTimer": {
"count": 63,
"fifteenMinRate": 0.8866728789348088,
"fiveMinRate": 0.4845914744130395,
"max": 197197,
"mean": 93778.06848535729,
"meanRate": 0.19107302754898328,
"min": 49630,
"oneMinRate": 0.013233974568205872,
"p50": 86069.0,
"p75": 103720.0,
"p95": 153694.0,
"p98": 196615.0,
"p99": 197197.0,
"p999": 197197.0,
"stddev": 30826.599346642666
}
}
Injecting metrics by using CDI
@Metric
annotation can be used to assign a metrics metadata.@Metric
: An annotation that describes the metric that is injected. This annotation can be used on the fields of type Meter, Timer, Counter, and Histogram.@Metric
Parameters: name, displayname, units, tags, description, absolute.The parameters for theThe server creates and registers a counter in the MetricRegistry and provides it for the application to use. The counter example is equivalent to the statsHitsCounter example. The absolute=true means that the provided name is used as is. The units parameter is unused and defaults to@Metric
annotation are similar to the corresponding metadata field with the following differences.- tags: An array of
key=value
tag strings - absolute: If true, set the metric name to the exact name that is specified in the name parameter. If false, use the fully qualified name by adding a prefix to the package class and name.
@Inject @Metric(name="statsHits", displayName="Stats Hits", description="Number of hits on the /stats endpoint", absolute=true) Counter statsHitsCounter;
MetricUnits.NONE
.@Inject @Metric(name= "topDonationCalcTimer", unit = MetricUnits.NANOSECONDS, description="Processing time to find the highest donation") Timer topDonationCalcTimer; @Inject @Metric(absolute = true, tags=["appName=DonationApp"]) Counter counted;
- tags: An array of
Metrics annotations
MicroProfile metrics also have a number of interceptors to handle the @Counted
,
@Timed
, @Metered
, and @Gauge
annotations.
@Metric
annotation, you can set the metadata parameters for each
of these annotations by using the annotation directly. Each annotation can be applied to classes,
methods, or types. If the name is not provided, the name is obtained from the method or constructor name.@Counted
- An annotation for marking a method, constructor, or type as a counter. Along with metadata
fields, counter also has a monotonic field. If
monotonic
is set to false, the counter is incremented when the annotated method is called and decremented when the annotated method returns, counting current invocations of the annotated method. Ifmonotonic
is set to true, the counter increases monotonically, counting total invocations of the annotated method.Note: By default, monotonic is set to false.@GET @Path("/no") @Counted(name="no", displayName="No donation count", description="Number of people that declined to donate.", monotonic=true) public String noDonation() { return "Maybe next time!"; }
@Timed
- An annotation for marking a constructor or method of an annotated object as timed. The timer
metric tracks how frequently the annotated object is started and tracks how long the invocations
take to
complete.
@POST @Path("/creditcard") @Timed( name="donateAmountViaCreditCard.timer", displayName="Donations Via Credit Cards", description = "Donations that were made using a credit card") public String donateAmountViaCreditCard(@FormParam("amount") Long amount, @FormParam("card") String card) { if (processCard(card, amount)) return "Thanks for donating!"; return "Sorry, please try again."; }
@Metered
- An annotation for marking a constructor or method as metered. The meter counts the invocations
of the constructor or method and tracks how frequently they are
called.
@Metered(displayName="Rate of donations", description="Rate of incoming donations (the instances not the amount)") public void addDonation(Long amount) { totalDonations += amount; donations.add(amount); donationDistribution.update(amount); }
@Gauge
- An annotation for marking a method as a
gauge.
@Gauge( name="donations", displayName="Total Donations", description="Total amount of money raised for charity!", unit = "dollars", absolute=true) public Long getTotalDonations(){ return totalDonations; }
Avoid trouble: The gauge annotation is only compatible with CDI beans that are application-scoped. An application-scoped bean exists as one instance throughout the lifecycle of the application. TheGauge
annotation is not compatible with beans that configured with a scope where multiple instances of the bean can be created. These beans encounter anIllegalArgumentException
error. This restriction does not apply to other metric annotations.
- Reusing metrics
- When you use MicroProfile metrics version 1.1 and higher, you can reuse metrics. The
@Counted
,@Metered
, and@Timed
annotations have a flag to indicate that the metric can be reused by other annotations. For example, your application might have multiple endpoints for different payment methods. To track the number of payments across all payment methods, you can use the same metric name by settingreusable=true
. The following examples illustrate sample usage.@POST @Path("/creditcard") @Counted( name="donation.counter", displayName="Number of donations", description = "Donations that were made using any method", monotonic=true, reusable=true) public String donateAmountViaCreditCard(@FormParam("amount") Long amount, @FormParam("card") String card) { if (processCard(card, amount)) return "Thanks for donating!"; return "Sorry, please try again."; }
@POST @Path("/debitcard") @Counted( name="donations.counter", displayName="Number of donations", description = "Donations that were made using any method", monotonic=true, reusable=true) public String donateAmountViaDebitCard(@FormParam("amount") Long amount, @FormParam("card") String card) { if (processDebitCard(card, amount)) return "Thanks for donating!"; return "Sorry, please try again."; }
CDI tips
The mpMetrics-1.0
feature relies on CDI. CDI injection is used whenever you want
to inject a MetricRegistry or to inject one of the metric types into a field. CDI interceptors are
used to apply the @Timed
, @Counted
, @Metered
, and
@Gauge
metrics around types, constructors, or methods.
- Using explicit bean archives
- For application startup efficiency, ensure that your bean archives have a beans.xml file. If you are creating a web application, your beans.xml file must be in the WEB-INF directory. If you are creating an EJB module or a JAR file, your beans.xml file must be in the META-INF directory. By avoiding CDIs implicit bean archive capability in your applications, you can tune your Liberty server to avoid scanning for bean archives at startup.
- Limitations for Interceptors
@Timed
,@Counted
,@Metered
,@Gauge
- CDI relies on Java interceptors for metrics annotations that are implemented in Liberty as interceptors. The
@Timed
,@Counted
,@Metered
, and@Gauge
annotations are implemented by using interceptors. Interceptors rely on being able to create a proxy that can be started on the code path before it can call constructors or methods with interceptor annotations. The interceptor code is where metrics are updated. Limits exist on what kind of code can be proxied and where the interceptor annotations can be used. Only managed beans, as defined by CDI, that can be proxied can be used with interceptors. Proxies are implemented as subclasses of the bean that they are proxying. To ensure that a class can be proxied, the class must not be marked final and must a non-private, no-args constructor. - Limitations for injection
- To
@Inject
types such as a MetricRegistry into a class, that class must be managed by CDI. The following JAX-RS example illustratessomeclass.setMetricsRegistry
being automatically called by CDI.
The class is not called in the following example:@Path("/myPath") public class MyService { // lifecyle of Someclass will be managed by CDI @Inject Someclass someclass; ... } public class Someclass { // Someclass was injected and is managed by CDI and therefore can use @Inject // setMetricRegistry will be called before the first method call to Someclass @Inject public void setMetricRegistry(final MetricRegistry metrics) { } }
@Path("/myPath") public class MyService { // CDI doesn't manage lifecycle of Someclass Someclass someclass = new Someclass(); ... } public class Someclass { // This method will never be called by CDI since Someclass was not created with CDI @Inject public void setMetricRegistry(MetricRegistry metrics) { } }