TLDR: MicroProfile metrics are awesome and useful, here is the documentation.
Those who have already had the pleasure of developing an application with Quarkus are probably aware of its benefits when collecting metrics. The MicroProfile specification offers a range of possibilities for this, which I will briefly introduce here.
However, this article by no means covers all possibilities and aspects concerning MicroProfile metrics and serves rather as an introduction with useful examples. While it is intended for Quarkus, it should apply to other MicroProfile implementations as well. I will not go into the storage and display of metrics with e.g. Prometheus or Grafana here.
Quarkus (since version 1.3) now also supports the MicroProfile 3.3 specification, which includes MicroProfile metrics in version 2.3.
The metrics in the MicroProfile – like many specifications in the MicroProfile – are designed for simple applications. Metrics are divided into three areas (scopes): " base ", "vendor" and "application" metrics.
"Base" metrics must be created for every MicroProfile implementation. "Vendor" metrics are intended for additional metrics of each implementation and "application" for the application itself.
Since I intend to discuss the possibilities within a Quarkus project here, I will limit myself to the "application" metrics.
More information about the specification can be found here for the current version 2.3 supported by Quarkus:
See a brief introduction with examples of use in Quarkus:
Let’s begin with a minimal code example. This provides a REST interface that sorts a passed list of integers:
The easiest way to add metrics to the desired classes, methods or fields is to use annotation. Let’s have a look at some simple metrics.
If we want to know how often our resource is called, all we need is the following:
These and all other metrics can be conveniently queried via REST interface in OpenMetrics or Json format under "/metrics/application" for all application metrics.
As soon as we have called our interface once, we get the following output in OpenMetrics format for our example:
Not so fancy, is it? Okay, let’s try again:
With this we get the following:
We can set the name of the metric (name) and specify that it is displayed as an absolute name (absolute) instead of the full class name.
With a timer we obtain execution times in addition to the number:
A SimpleTimer is a simplified timer that only considers the number and elapsed time.
This is particularly useful if you evaluate the metrics with Prometheus. Since Prometheus can determine many values by itself, they do not have to be provided by the metric. See also the specification and this discussion on GitHub.
A meter, on the other hand, indicates the number of calls in certain time periods:
With a gauge it is possible to use return values of methods to generate metrics. The value itself can thus be adjusted programmatically and at runtime. For a good example, see the Quarkus tutorial.
If this is not sufficient, you may need to create metrics dynamically.
With the presented metrics per annotation, one can already cover a multitude of the scenarios that are relevant in a microservice. However, it is sometimes necessary to collect metrics dynamically and programmatically. Especially metrics for specific values of the business logic like numbers of objects, or metrics that should only be registered dynamically at runtime can thus be used flexibly.
Metrics are stored in a MetricRegistry. A MetricRegistry manages all metrics including metadata, such as name and description. The unique assignment is done by the Metric-ID, which consists of the name and all tags of the metric. There are registries for each of the three areas Base, Vendor and Application. Later we will take a closer look at the use of tags.
The corresponding registry can be easily injected:
Since the Application Registry is the default, it can also be injected without @RegistryType .
If you have integrated a registry, you can register metrics directly in it. For our example we want to introduce additional counters for the status codes 200 and 400 in addition to an annotated counter.
To do this, we specify the following:
These counters can now be called anywhere in the code. In our example it looks like this:
If only one number is passed, an HTTP response code 400 should be returned and the corresponding metric is incremented. Accordingly, if successful, the metric for the response code 200 is incremented.
When calling the interface, once with one number and once with three numbers, the following results when retrieving the metrics:
This is only a simple example. A metadata object can be used to add additional data such as name and description. For this see also the specification.
If you want to add a new metric to the application registry anyway, you can also do this more conveniently using @Metric Annotation. In our example, I would like to show this using a metric that is only available dynamically – the histogram.
We register our histogram as follows:
In the code we use it to measure the amount of submitted numbers as frequencies:
A call with four times two numbers, three times three numbers, twice four numbers and once five numbers results in the following metrics:
In addition to the name of the metric, tags can be defined as key, value pairs to better describe and distinguish the metric. Tags can be added to both annotated and dynamic metrics.
Here is an annotated metric with tags including the retrieval of the metric interface:
For annotated metrics, reuse under the same name and tags (Metrik-ID) is not possible by default to avoid hard-to-find errors. However, you can enable this explicitly, just set the field "reusable" to true. For dynamic metrics on the other hand, reusable is enabled by default to be able to address a metric at different places in the code.
Regarding the use of names, tags, metadata and types let me say the following:
If a metric is to be reused, the name, tags and type (e.g. counter) must match. More about this here.
In addition to the self-generated metrics, there are also metrics that are added when other parts of the MicroProfile are used.
An example of this is the Fault Tolerance specification, which automatically registers corresponding metrics when using e.g. @Timeout or @Retry. This combination of the individual specifications allows you to benefit from automatic metrics without having to create them yourself.
Another example comes from Quarkus itself. There, you can activate metrics when your service communicates with databases, for instance. These are even delivered "for free". Not only is this practical, but it saves implementation effort as well and prevents every developer from having to come up with his or her own solution for the same metrics.
With the new MicroProfile version 4.0 the metrics will also be available in a new version 3.0.
These changes are more extensive than between 2.2 and 2.3. In addition to adjustments to metrics such as SimpleTimer and Timer, the reusability of metrics has been expanded. Thus, this is now also possible by default for annotated metrics (except gauge). Further changes can be found in the changelog.
All in all, the metric specification of the MicroProfile offers a great and easy way to spruce up your own microservice with metrics.
So far we have already been able to use them successfully in several microservices in various projects.
Naturally, the metrics and possibilities presented above are only a selection. The specification of the metrics and the link mentioned at the beginning gives a deeper insight for interested readers.
I hope this article is helpful for some of you! And respect to all who have made it this far 😉