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.