Skip to main content

In the world of Microservices, knowing how your server, application and container are doing is crucial for success. In production, every system administrator wants to be able to proactively check the container memory, the disk, the network, and the JVM. They need to know how many times a service is being called, how long this service took to execute, and several other metrics that help to manage services before they become unavailable. The MicroProfile Metrics is a specification that provides a standard way for application servers to expose metrics and, also, an API for developers to build their own application metrics. This article will show you how to make the best use of MicroProfile Metrics. In our coverage we will include the following classes and annotations.

  • org.eclipse.microprofile.metrics.annotation.Counted
  • org.eclipse.microprofile.metrics.annotation.Gauge
  • org.eclipse.microprofile.metrics.Metered
  • org.eclipse.microprofile.metrics.annotation.Timed
  • org.eclipse.microprofile.metrics.Histogram

Servers that implement MicroProfile Metrics have their metrics located at a resource called /metrics. Under this resource there are 3 different areas called scopes. The available scopes are /metrics/base, /metrics/vendor, and /metrics/application.

Base Scope

The Base Scope has all the core information of the server; the metrics that are REQUIRED by all application servers to implement. You will also find some that are OPTIONAL within the list. This scope will provide data on, among other things, heap memory, thread count, and available processors.

HTTP Verbs: GET, OPTIONS

Resource Path: /metrics/base

Vendor Scope

Vendor Scope exposes vendor-specific information. Each application server may have different implementations or internal components that can be monitored. This data does not need to be portable between different implementations.

HTTP Verbs: GET, OPTIONS

Resource Path: /metrics/vendor

Application Scope

Application Scope exposes the application-specific information. For this scope a Java API is provided to monitor different parts of the application. We will be going through the API later in the article.

HTTP Verbs: GET, OPTIONS

Resource Path: /metrics/application

HTTP Verbs

For each Resource URL there are two HTTP verbs that can be used to call the endpoint: GET and OPTIONS. The GET verb is used to retrieve the metric value(s) like the following example:

GET /metrics/application/login

Response in Prometheus format:

# TYPE application:login counter
application:login 3.0

The format retrieved will be Prometheus by default, however, for this article, we will add application/json in the HTTP Accept header so the payload retrieved is JSON. Learn more about the Prometheus format here.

Response in application/json format:

{
   "login": 3
}

The OPTIONS HTTP verb is used to retrieve the metadata for the metric.

Metadata

Metadata is pieces of data that describe a metric. It will have attributes/fields, such as, the metric type (counter, gauge, meter, histogram or timer), description, displayName and so on.

This data is exposed via the HTTP Verb OPTIONS and Resource URL /metrics/{scope}/{metric_name}. See following example:

OPTIONS /metrics/application/login.

Response in application/json:

{
 "login": {
   "unit": "none",
   "type": "counter",
   "description": "Metrics to show how many times login method was called.",
   "displayName": "Login"
 }
}

The Application Scope REST endpoint will have everything you decide to track through the Java API. Using annotations defined by the MicroProfile Metrics, the Java API allows you to declare the metric metadata supported by a Java application. If you don’t want to use Java annotations you can use the non-annotations API, but this is not the focus of this article. Let’s check the main Java annotations that are used to monitor metrics.

@Counted

A counter is a simple incrementing and decrementing long.

@Counted(unit = MetricUnits.NONE,
       name = "itemsCheckedOut",
       absolute = true,
       monotonic = true,
       displayName = "Checkout items",
       description = "Metrics to show how many times checkoutItems method was called.",
       tags = {"checkout=items"})
@POST
@Path("/checkout")
public Response checkoutItems() {
   return Response.ok().build();
}

The absolute flag, as true, means the name of the package and class will not be prepended to the metric name.

The monotonic flag, as true, means it will count total invocations of the annotated method. If the flag is false (default) it will count concurrent invocations.

Metric Payload for GET /metrics/application/itemsCheckedOut

{
 "itemsCheckedOut": 5
}

@Gauge

A gauge is the simplest metric type that just returns a value.

@GET
@Path("/price")
public Response getItemsPrice() {
   return Response.ok(getPrice()).build();
}

@Gauge(unit = "USD", name = "itemsPrice", absolute = true)
public long getPrice() {
   return 4;
}

Metric Payload for GET /metrics/application/itemsPrice

{
 "itemsPrice": 4
}

@Metered

A meter measures the rate at which a set of events occur.

@Metered(name = "itemsSold", unit = MetricUnits.MINUTES, description = "Metrics to monitor sold method.", absolute = true)
@GET
@Path("/sold")
public Response itemSold() {
   return Response.ok().build();
}

Metric Payload for GET /metrics/application/itemsSold

"itemsSold":{
   "count":2,
   "meanRate":0.06512797383709411,
   "oneMinRate":0.02942507589548365,
   "fiveMinRate":0.0065021413358446346,
   "fifteenMinRate":0.0022037834843897696
}

@Timed

It is a timer that tracks the duration of an event.

@Timed(name = "itemsProcessed",
       description = "Metrics to monitor the times of processItem method.",
       unit = MetricUnits.MINUTES,
       absolute = true)
@POST
@Path("/process")
public Response proccessItem() {
   return Response.ok().build();
}

Metric Payload for GET /metrics/application/itemsProcessed

"itemsProcessed":{
   "p50":22481,
   "p75":61186,
   "p95":61186,
   "p98":61186,
   "p99":61186,
   "p999":61186,
   "min":17035,
   "mean":29478.75,
   "max":61186,
   "stddev":18436.470274635,
   "count":4,
   "meanRate":0.13026652736864805,
   "oneMinRate":0.0588501517909673,
   "fiveMinRate":0.013004282671689269,
   "fifteenMinRate":0.004407566968779539
}

Histogram

There is also another metric, which does not have an annotation, that is called Histogram. It measures the distribution of values in a stream of data and allows you to measure, not just natural things like the min, mean, max, and standard deviation of values, but also quantiles like the median or 95th percentile.

@POST
@Path("/add/{numberOfItems}")
public Response addItems(@PathParam("numberOfItems") String numberOfItems) {
   Metadata metadata = new Metadata("itemsAdded", MetricType.HISTOGRAM);
   Histogram histogram = registry.histogram(metadata);
   histogram.update(Long.valueOf(numberOfItems));
   return Response.ok().build();
}

Metric Payload for GET /metrics/application/itemsAdded

"itemsAdded": {
   "count": 5,
   "p50": 6,
   "p75": 6,
   "p95": 6,
   "p98": 6,
   "p99": 6,
   "p999": 6,
   "min": 6,
   "mean": 5.999999999999999,
   "max": 6,
   "stddev": 8.881784197001251e-16
}

Summary

There are other features like @Metric annotation, the reusable flag, and MetricRegistry that won’t be covered in this article. If you want more detailed information take a look in the latest microprofile specification for metrics.

Using MicroProfile Metrics will increase transparency of your production system resulting in higher reliability. The MicroProfile Metrics specification is critical to implementing microservices in Java and will be supported by Apache TomEE and other providers.

Ivan Junckes Filho

Ivan Junckes Filho

Ivan joins the Tomitribe Product Team with 8+ years of software development experience, including several years at IBM. He is a passionate developer contributing to open-source projects: Eclipse JNoSQL, MicroProfile, and TomEE. He likes sharing interesting technical content with the Community through speaking engagements and blogging.
ivanjunckes

Leave a Reply