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.

Open Liberty Documentation for the MicroProfile Metrics feature versions 2.0 and later is available on the Open Liberty website.

Architecture of MicroProfile metrics

The 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.

The following URL format example illustrates an address where you can view your metrics:
https://localhost:9443/metrics

In general, operations teams configure tools to monitor and record the metric values over time.

Adding metrics to your applications

To add metrics to your applications, you must create and register metrics with the application registry so that they are known to the system and can be reported on from the /metrics endpoint. You can create and register metrics in the following ways:
  • 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.

The application metric registry can be obtained by using CDI injection.
@Inject
MetricRegistry registry;

Registering metrics

The MetricRegistry class has methods that can retrieve or create counters, meters, histograms, and timers.

The registry checks if a metric with the same name exists. If the metric exists, it would be returned.
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:

Metadata can better describe the purpose of each metric. This information is useful when you access the metrics through the REST API.
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.
Global or application-wide tags can be set by passing a comma-separated list of name=value pairs in the environment variable 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.

Metadata cannot change over the lifetime of a process. For example, it cannot return units as seconds in one retrieval and as hours in another retrieval.
Note: If no DisplayName is given, the default name of the metric is the name of the object.

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.

The following example uses a counter to measure the number of hits to the /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();
}
The following result would be generated from the REST endpoints.
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.

The following example illustrates a gauge that is used to represent the current progress as a percentage toward reaching a goal.
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);
The following result would be generated from the REST endpoints.
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.

To use a meter, you must call the 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.

The following example uses a meter to determine that rate that 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;
}
The following result would be generated from the REST endpoints.
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.

A histogram provides the following information:
  • 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.

The following example illustrates a histogram that is used to store the value of donations. This example provides the administrator with an idea of the distribution of donation amounts.
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);
}
The following result is generated from the REST endpoints:
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.

A timer provides the following information:
  • 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.

The following example illustrates a timer that is used to measure the runtime for a linear search through donations:
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;
}
The following result is generated from the REST endpoints:
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

CDI injection provides an easy way for metrics to be created and registered to the MetricRegistry. Instead of creating a metadata object, the parameters of the @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 the @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;
    
    The 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 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;
    
The two examples illustrate how to inject a metric using different parameters.

Metrics annotations

MicroProfile metrics also have a number of interceptors to handle the @Counted, @Timed, @Metered, and @Gauge annotations.

Similar to the @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. If monotonic 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. The Gauge annotation is not compatible with beans that configured with a scope where multiple instances of the bean can be created. These beans encounter an IllegalArgumentException 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 setting reusable=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 illustrates someclass.setMetricsRegistry being automatically called by CDI.
@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) {
    }
}
The class is not called in the following example:
@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) {
    }
}