From 293fbfe91caed38c89dafe87e0b5debedb359d4d Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Mon, 22 May 2023 18:15:19 -0400 Subject: [PATCH] Update micrometer docs (tutorial / reference) --- docs/src/main/asciidoc/micrometer.adoc | 633 ----------------- .../telemetry-micrometer-tutorial.adoc | 72 +- .../main/asciidoc/telemetry-micrometer.adoc | 669 ++++++++++++++++++ .../doc/micrometer/ExampleResource.java | 71 +- 4 files changed, 763 insertions(+), 682 deletions(-) delete mode 100644 docs/src/main/asciidoc/micrometer.adoc create mode 100644 docs/src/main/asciidoc/telemetry-micrometer.adoc diff --git a/docs/src/main/asciidoc/micrometer.adoc b/docs/src/main/asciidoc/micrometer.adoc deleted file mode 100644 index b264eb2f62da7b..00000000000000 --- a/docs/src/main/asciidoc/micrometer.adoc +++ /dev/null @@ -1,633 +0,0 @@ -//// -This guide is maintained in the main Quarkus repository -and pull requests should be submitted there: -https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc -//// -= Micrometer Metrics -include::_attributes.adoc[] -:categories: observability -:summary: This guide demonstrates how your Quarkus application can collect metrics using the Micrometer extension. - -This guide demonstrates how your Quarkus application can utilize the Micrometer metrics library for runtime and -application metrics. - -Apart from application-specific metrics, which are described in this guide, you may also utilize built-in metrics -exposed by various Quarkus extensions. These are described in the guide for each particular extension that supports -built-in metrics. - -IMPORTANT: Micrometer is the recommended approach to metrics for Quarkus. - -== Prerequisites - -include::{includes}/prerequisites.adoc[] - -== Architecture - -Micrometer defines a core library providing a registration mechanism for Metrics, and core metric types (Counters, -Gauges, Timers, Distribution Summaries, etc.). These core types provide an abstraction layer that can be adapted to -different backend monitoring systems. In essence, your application (or a library) can `register` a `Counter`, -`Gauge`, `Timer`, or `DistributionSummary` with a `MeterRegistry`. Micrometer will then delegate that registration to -one or more implementations, where each implementation handles the unique considerations for the associated -monitoring stack. - -Micrometer uses naming conventions to translate between registered Meters and the conventions used by various backend -registries. Meter names, for example, should be created and named using dots to separate segments, `a.name.like.this`. -Micrometer then translates that name into the format that the selected registry prefers. Prometheus -uses underscores, which means the previous name will appear as `a_name_like_this` in Prometheus-formatted metrics -output. - -== Solution - -We recommend that you follow the instructions in the next sections and create the application step by step. -You can skip right to the solution if you prefer. Either: - -* Clone the git repository: `git clone {quickstarts-clone-url}`, or -* Download an {quickstarts-archive-url}[archive]. - -The solution is located in the `micrometer-quickstart` {quickstarts-tree-url}/micrometer-quickstart[directory]. - -== Creating the Maven Project - -Quarkus Micrometer extensions are structured similarly to Micrometer itself: `quarkus-micrometer` provides core -micrometer support and runtime integration and other Quarkus and Quarkiverse extensions bring in additional -dependencies and requirements to support specific monitoring systems. - -For this example, we'll use the Prometheus registry. - -First, we need a new project. Create a new project with the following command: - -:create-app-artifact-id: micrometer-quickstart -:create-app-extensions: resteasy-reactive,micrometer-registry-prometheus -include::{includes}/devtools/create-app.adoc[] - -This command generates a Maven project, that imports the `micrometer-registry-prometheus` extension as a dependency. -This extension will load the core `micrometer` extension as well as additional library dependencies required to support -prometheus. - -If you already have your Quarkus project configured, you can add the `micrometer-registry-prometheus` extension -to your project by running the following command in your project base directory: - -:add-extension-extensions: micrometer-registry-prometheus -include::{includes}/devtools/extension-add.adoc[] - -This will add the following to your build file: - -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - io.quarkus - quarkus-micrometer-registry-prometheus - ----- - -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle ----- -implementation("io.quarkus:quarkus-micrometer-registry-prometheus") ----- - -== Writing the application - -Micrometer provides an API that allows you to construct your own custom metrics. The most common types of -meters supported by monitoring systems are gauges, counters, and summaries. The following sections build -an example endpoint, and observes endpoint behavior using these basic meter types. - -To register meters, you need a reference to a `MeterRegistry`, which is configured and maintained by the Micrometer -extension. The `MeterRegistry` can be injected into your application as follows: - -[source,java] ----- -package org.acme.micrometer; - -import io.micrometer.core.instrument.MeterRegistry; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; - -@Path("/example") -@Produces("text/plain") -public class ExampleResource { - - private final MeterRegistry registry; - - ExampleResource(MeterRegistry registry) { - this.registry = registry; - } -} ----- - -Micrometer maintains an internal mapping between unique metric identifier and tag combinations and specific meter -instances. Using `register`, `counter`, or other methods to increment counters or record values does not create -a new instance of a meter unless that combination of identifier and tag/label values hasn't been seen before. - -=== Gauges - -Gauges measure a value that can increase or decrease over time, like the speedometer on a car. Gauges can be -useful when monitoring the statistics for a cache or collection. Consider the following simple example that -observes the size of a list: - -[source,java] ----- - LinkedList list = new LinkedList<>(); - - // Update the constructor to create the gauge - ExampleResource(MeterRegistry registry) { - this.registry = registry; - registry.gaugeCollectionSize("example.list.size", Tags.empty(), list); - } - - @GET - @Path("gauge/{number}") - public Long checkListSize(long number) { - if (number == 2 || number % 2 == 0) { - // add even numbers to the list - list.add(number); - } else { - // remove items from the list for odd numbers - try { - number = list.removeFirst(); - } catch (NoSuchElementException nse) { - number = 0; - } - } - return number; - } ----- - -Note that even numbers are added to the list, and odd numbers remove an element from the list. - -Start your application in dev mode: - -include::{includes}/devtools/dev.adoc[] - -Then try the following sequence and look for `example_list_size` in the plain text output: - -[source,shell] ----- -curl http://localhost:8080/example/gauge/1 -curl http://localhost:8080/example/gauge/2 -curl http://localhost:8080/example/gauge/4 -curl http://localhost:8080/q/metrics -curl http://localhost:8080/example/gauge/6 -curl http://localhost:8080/example/gauge/5 -curl http://localhost:8080/example/gauge/7 -curl http://localhost:8080/q/metrics ----- - -It is important to note that gauges are sampled rather than set; there is no record of how the value associated with a -gauge might have changed between measurements. In this example, the size of the list is observed when the Prometheus -endpoint is visited. - -Micrometer provides a few additional mechanisms for creating gauges. Note that Micrometer does not create strong -references to the objects it observes by default. Depending on the registry, Micrometer either omits gauges that observe -objects that have been garbage-collected entirely or uses `NaN` (not a number) as the observed value. - -When should you use a Gauge? Only if you can't use something else. Never gauge something you can count. Gauges can be -less straight-forward to use than counters. If what you are measuring can be counted (because the value always -increments), use a counter instead. - -[NOTE] -.Management interface -==== -By default, the metrics are exposed on the main HTTP server. -You can expose them on a separate network interface and port by enabling the management interface with the -`quarkus.management.enabled=true` property. -Refer to the xref:./management-interface-reference.adoc[management interface reference] for more information. -==== - -=== Counters - -Counters are used to measure values that only increase. In the example below, you will count the number of times you -test a number to see if it is prime: - -[source,java] ----- - @GET - @Path("prime/{number}") - public String checkIfPrime(long number) { - if (number < 1) { - return "Only natural numbers can be prime numbers."; - } - if (number == 1 || number == 2 || number % 2 == 0) { - return number + " is not prime."; - } - - if ( testPrimeNumber(number) ) { - return number + " is prime."; - } else { - return number + " is not prime."; - } - } - - protected boolean testPrimeNumber(long number) { - // Count the number of times we test for a prime number - registry.counter("example.prime.number").increment(); - for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { - if (number % i == 0) { - return false; - } - } - return true; - } ----- - -It might be tempting to add a label or tag to the counter indicating what value was checked, but remember that each -unique combination of metric name (`example.prime.number`) and label value produces a unique time series. Using an -unbounded set of data as label values can lead to a "cardinality explosion", an exponential increase in the creation -of new time series. - -[NOTE] -==== -Label and tag can be used interchangeably. You may also see "attribute" used in this context in some documentation. -The gist is each that each label or tag or attribute defines an additional bit of information associated with the -single numerical measurement that helps you classify, group, or aggregate the measured value later. The Micrometer API -uses `Tag` as the mechanism for specifying this additional data. -==== - -It is possible to add a tag that would convey a little more information, however. Let's adjust our code, and move -the counter to add some tags to convey additional information. - -[source,java] ----- - @GET - @Path("prime/{number}") - public String checkIfPrime(long number) { - if (number < 1) { - registry.counter("example.prime.number", "type", "not-natural").increment(); - return "Only natural numbers can be prime numbers."; - } - if (number == 1 ) { - registry.counter("example.prime.number", "type", "one").increment(); - return number + " is not prime."; - } - if (number == 2 || number % 2 == 0) { - registry.counter("example.prime.number", "type", "even").increment(); - return number + " is not prime."; - } - - if ( testPrimeNumber(number) ) { - registry.counter("example.prime.number", "type", "prime").increment(); - return number + " is prime."; - } else { - registry.counter("example.prime.number", "type", "not-prime").increment(); - return number + " is not prime."; - } - } - - protected boolean testPrimeNumber(long number) { - for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { - if (number % i == 0) { - return false; - } - } - return true; - } ----- - -Looking at the data produced by this counter, you can tell how often a negative number was checked, or the number one, -or an even number, and so on. Try the following sequence and look for `example_prime_number_total` in the plain text -output. Note that the `_total` suffix is added when Micrometer applies Prometheus naming conventions to -`example.prime.number`, the originally specified counter name. - -If you did not leave Quarkus running in dev mode, start it again: - -include::{includes}/devtools/dev.adoc[] - -Then execute the following sequence: - -[source,shell] ----- -curl http://localhost:8080/example/prime/-1 -curl http://localhost:8080/example/prime/0 -curl http://localhost:8080/example/prime/1 -curl http://localhost:8080/example/prime/2 -curl http://localhost:8080/example/prime/3 -curl http://localhost:8080/example/prime/15 -curl http://localhost:8080/q/metrics ----- - -When should you use a counter? Only if you are doing something that can not be either timed (or summarized). -Counters only record a count, which may be all that is needed. However, if you want to understand more about how a -value is changing, a timer (when the base unit of measurement is time) or a distribution summary might be -more appropriate. - -=== Summaries and Timers - -Timers and distribution summaries in Micrometer are very similar. Both allow you to record an observed value, which -will be aggregated with other recorded values and stored as a sum. Micrometer also increments a counter to indicate the -number of measurements that have been recorded and tracks the maximum observed value (within a decaying interval). - -Distribution summaries are populated by calling the `record` method to record observed values, while timers provide -additional capabilities specific to working with time and measuring durations. For example, we can use a timer to -measure how long it takes to calculate prime numbers using one of the `record` methods that wraps the invocation of a -Supplier function: - -[source,java] ----- - protected boolean testPrimeNumber(long number) { - Timer timer = registry.timer("example.prime.number.test"); - return timer.record(() -> { - for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { - if (number % i == 0) { - return false; - } - } - return true; - }); - } ----- - -Micrometer will apply Prometheus conventions when emitting metrics for this timer. Prometheus measures time in seconds. -Micrometer converts measured durations into seconds and includes the unit in the metric name, per convention. After -visiting the prime endpoint a few more times, look in the plain text output for the following three entries: -`example_prime_number_test_seconds_count`, `example_prime_number_test_seconds_sum`, and -`example_prime_number_test_seconds_max`. - -If you did not leave Quarkus running in dev mode, start it again: - -include::{includes}/devtools/dev.adoc[] - -Then execute the following sequence: - -[source,shell] ----- -curl http://localhost:8080/example/prime/256 -curl http://localhost:8080/q/metrics -curl http://localhost:8080/example/prime/7919 -curl http://localhost:8080/q/metrics ----- - -Both timers and distribution summaries can be configured to emit additional statistics, like histogram data, -precomputed percentiles, or service level objective (SLO) boundaries. Note that the count, sum, and histogram data -can be re-aggregated across dimensions (or across a series of instances), while precomputed percentile values cannot. - -=== Review automatically generated metrics - -To view metrics, execute `curl localhost:8080/q/metrics/` - -The Micrometer extension automatically times HTTP server requests. Following Prometheus naming conventions for -timers, look for `http_server_requests_seconds_count`, `http_server_requests_seconds_sum`, and -`http_server_requests_seconds_max`. Dimensional labels have been added for the requested uri, the HTTP method -(GET, POST, etc.), the status code (200, 302, 404, etc.), and a more general outcome field. - -[source,text] ----- -# HELP http_server_requests_seconds -# TYPE http_server_requests_seconds summary -http_server_requests_seconds_count{method="GET",outcome="SUCCESS",status="200",uri="/example/prime/{number}"} 1.0 -http_server_requests_seconds_sum{method="GET",outcome="SUCCESS",status="200",uri="/example/prime/{number}"} 0.017385896 -# HELP http_server_requests_seconds_max -# TYPE http_server_requests_seconds_max gauge -http_server_requests_seconds_max{method="GET",outcome="SUCCESS",status="200",uri="/example/prime/{number}"} 0.017385896 -# ----- - -Note that metrics appear lazily, you often won't see any data for your endpoint until -something tries to access it, etc. - -.Ignoring endpoints - -You can disable measurement of HTTP endpoints using the `quarkus.micrometer.binder.http-server.ignore-patterns` -property. This property accepts a comma-separated list of simple regex match patterns identifying URI paths that should -be ignored. For example, setting `quarkus.micrometer.binder.http-server.ignore-patterns=/example/prime/[0-9]+` will -ignore a request to `http://localhost:8080/example/prime/7919`. A request to `http://localhost:8080/example/gauge/7919` -would still be measured. - -.URI templates - -The micrometer extension will make a best effort at representing URIs containing path parameters in templated form. -Using examples from above, a request to `http://localhost:8080/example/prime/7919` should appear as an attribute of -`http_server_requests_seconds_*` metrics with a value of `uri=/example/prime/{number}`. - -Use the `quarkus.micrometer.binder.http-server.match-patterns` property if the correct URL can not be determined. This -property accepts a comma-separated list defining an association between a simple regex match pattern and a replacement -string. For example, setting -`quarkus.micrometer.binder.http-server.match-patterns=/example/prime/[0-9]+=/example/{jellybeans}` would use the value -`/example/{jellybeans}` for the uri attribute any time the requested uri matches `/example/prime/[0-9]+`. - -.Exported metrics format - -By default, the metrics are exported using the Prometheus format `application/openmetrics-text`, -you can revert to the former format by specifying the `Accept` request header to `text/plain` (`curl -H "Accept: text/plain" localhost:8080/q/metrics/`). - -== Using MeterFilter to configure metrics - -Micrometer uses `MeterFilter` instances to customize the metrics emitted by `MeterRegistry` instances. -The Micrometer extension will detect `MeterFilter` CDI beans and use them when initializing `MeterRegistry` -instances. - -[source,java] ----- -@Singleton -public class CustomConfiguration { - - @ConfigProperty(name = "deployment.env") - String deploymentEnv; - - /** Define common tags that apply only to a Prometheus Registry */ - @Produces - @Singleton - @MeterFilterConstraint(applyTo = PrometheusMeterRegistry.class) - public MeterFilter configurePrometheusRegistries() { - return MeterFilter.commonTags(Arrays.asList( - Tag.of("registry", "prometheus"))); - } - - /** Define common tags that apply globally */ - @Produces - @Singleton - public MeterFilter configureAllRegistries() { - return MeterFilter.commonTags(Arrays.asList( - Tag.of("env", deploymentEnv))); - } - - /** Enable histogram buckets for a specific timer */ - @Produces - @Singleton - public MeterFilter enableHistogram() { - return new MeterFilter() { - @Override - public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) { - if(id.getName().startsWith("myservice")) { - return DistributionStatisticConfig.builder() - .percentiles(0.5, 0.95) // median and 95th percentile, not aggregable - .percentilesHistogram(true) // histogram buckets (e.g. prometheus histogram_quantile) - .build() - .merge(config); - } - return config; - } - }; - } -} ----- - -In this example, a singleton CDI bean will produce two different `MeterFilter` beans. One will be applied only to -Prometheus `MeterRegistry` instances (using the `@MeterFilterConstraint` qualifier), and another will be applied -to all `MeterRegistry` instances. An application configuration property is also injected and used as a tag value. -Additional examples of MeterFilters can be found in the -link:https://micrometer.io/docs/concepts[official documentation]. - -== Does Micrometer support annotations? - -Micrometer does define two annotations, `@Counted` and `@Timed`, that can be added to methods. The `@Timed` annotation -will wrap the execution of a method and will emit the following tags in addition to any tags defined on the -annotation itself: class, method, and exception (either "none" or the simple class name of a detected exception). - -Using annotations is limited, as you can't dynamically assign meaningful tag values. Also note that many methods, e.g. -REST endpoint methods or Vert.x Routes, are counted and timed by the micrometer extension out of the box. - -== Using other Registry implementations - -If you aren't using Prometheus, you have a few options. Some Micrometer registry implementations -have been wrapped in -https://github.com/quarkiverse/quarkiverse-micrometer-registry[Quarkiverse extensions]. -To use the Micrometer StackDriver MeterRegistry, for example, you would use the -`quarkus-micrometer-registry-stackdriver` extension: - -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - io.quarkus - quarkus-micrometer-registry-stackdriver - ----- - -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle ----- -implementation("io.quarkus:quarkus-micrometer-registry-stackdriver") ----- - -If the Micrometer registry you would like to use does not yet have an associated extension, -use the `quarkus-micrometer` extension and bring in the packaged MeterRegistry dependency directly: - -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - io.quarkus - quarkus-micrometer - - - com.acme - custom-micrometer-registry - ----- - -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle ----- -implementation("io.quarkus:quarkus-micrometer") -implementation("com.acme:custom-micrometer-registry") ----- - -You will then need to specify your own provider to configure and initialize the -MeterRegistry, as discussed in the next section. - -== Creating a customized MeterRegistry - -Use a custom `@Produces` method to create and configure a customized `MeterRegistry` if you need to. - -The following example customizes the line format used for StatsD: - -[source,java] ----- -@Produces -@Singleton -public StatsdMeterRegistry createStatsdMeterRegistry(StatsdConfig statsdConfig, Clock clock) { - // define what to do with lines - Consumer lineLogger = line -> logger.info(line); - - // inject a configuration object, and then customize the line builder - return StatsdMeterRegistry.builder(statsdConfig) - .clock(clock) - .lineSink(lineLogger) - .build(); -} ----- - -This example corresponds to the following instructions in the Micrometer documentation: -https://micrometer.io/docs/registry/statsD#_customizing_the_metrics_sink - -Note that the method returns the specific type of `MeterRegistry` as a `@Singleton`. Use MicroProfile Config -to inject any configuration attributes you need to configure the registry. Most Micrometer registry extensions, -like `quarkus-micrometer-registry-statsd`, define a producer for registry-specific configuration objects -that are integrated with the Quarkus configuration model. - -== Support for the MicroProfile Metrics API - -If you use the MicroProfile Metrics API in your application, the Micrometer extension will create an adaptive -layer to map those metrics into the Micrometer registry. Note that naming conventions between the two -systems is different, so the metrics that are emitted when using MP Metrics with Micrometer will change. -You can use a `MeterFilter` to remap names or tags according to your conventions. - -[source,java] ----- -@Produces -@Singleton -public MeterFilter renameApplicationMeters() { - final String targetMetric = MPResourceClass.class.getName() + ".mpAnnotatedMethodName"; - - return MeterFilter() { - @Override - public Meter.Id map(Meter.Id id) { - if (id.getName().equals(targetMetric)) { - // Drop the scope tag (MP Registry type: application, vendor, base) - List tags = id.getTags().stream().filter(x -> !"scope".equals(x.getKey())) - .collect(Collectors.toList()); - // rename the metric - return id.withName("my.metric.name").replaceTags(tags); - } - return id; - } - }; -} ----- - -Ensure the following dependency is present in your build file if you require the MicroProfile Metrics API: - -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - org.eclipse.microprofile.metrics - microprofile-metrics-api - ----- - -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle ----- -implementation("org.eclipse.microprofile.metrics:microprofile-metrics-api") ----- - -NOTE: The MP Metrics API compatibility layer will be moved to a different extension in the future. - -== Management interface - -By default, the metrics are exposed on the main HTTP server. -You can expose them on a separate network interface and port by setting `quarkus.management.enabled=true` in your application configuration. -Note that this property is a build-time property. -The value cannot be overridden at runtime. - -If you enable the management interface without customizing the management network interface and port, the metrics are exposed under: `http://0.0.0.0:9000/q/metrics`. -You can configure the path of each exposed format using: -[source, properties] ----- -quarkus.micrometer.export.json.enabled=true # Enable json metrics -quarkus.micrometer.export.json.path=metrics/json -quarkus.micrometer.export.prometheus.path=metrics/prometheus ----- - -With such a configuration, the json metrics will be available from `http://0.0.0.0:9000/q/metrics/json`. -The prometheus metrics will be available from `http://0.0.0.0:9000/q/metrics/prometheus`. - -Refer to the xref:./management-interface-reference.adoc[management interface reference] for more information. - -== Configuration Reference - -include::{generated-dir}/config/quarkus-micrometer.adoc[opts=optional, leveloffset=+1] diff --git a/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc b/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc index 386b2e24aa51d4..58cb1e7168ca3f 100644 --- a/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc +++ b/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc @@ -3,8 +3,9 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// -[id="tutorial-micrometer"] +[id="telemetry-micrometer-tutorial"] = Collect metrics using Micrometer +:categories: observability include::_attributes.adoc[] Create an application that uses the Micrometer metrics library to collect runtime, extension and application metrics and expose them as a Prometheus (OpenMetrics) endpoint. @@ -15,7 +16,8 @@ include::{includes}/prerequisites.adoc[] == Solution -We recommend that you follow the instructions to create the application step by step, but you can skip right to the solution if you prefer. Either: +We recommend that you follow the instructions to create the application step by step, but you can skip right to the solution if you prefer. +Either: * Clone the git repository: `git clone {quickstarts-clone-url}`, or * Download an {quickstarts-archive-url}[archive]. @@ -24,7 +26,7 @@ The solution is located in the `micrometer-quickstart` {quickstarts-tree-url}/mi :sectnums: :sectnumlevels: 3 -== Creating the Maven Project +== Create the Maven project Create a new project with the following command: @@ -35,13 +37,13 @@ include::{includes}/devtools/create-app.adoc[] This command generates a Maven project, that imports the `micrometer-registry-prometheus` extension as a dependency. This extension will load the core `micrometer` extension as well as additional library dependencies required to support prometheus. -== Writing the application +== Create a REST endpoint Let's first add a simple endpoint that calculates prime numbers. [source,java] ---- -include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=example;!ignore;!registry;!gauge;!counter;!timer] +include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=example;!ignore;!registry;!gauge;!counted;!timed] ---- Start your application in dev mode: @@ -60,7 +62,8 @@ curl http://localhost:8080/example/prime/256 curl http://localhost:8080/example/prime/7919 ---- -The Micrometer Prometheus MeterRegistry extension creates an endpoint we can use to observe collected metrics. Let's take a look at the metrics that have been collected: +The Micrometer Prometheus MeterRegistry extension creates an endpoint we can use to observe collected metrics. +Let's take a look at the metrics that have been collected: [source,shell] ---- @@ -71,7 +74,8 @@ Look for `http_server_requests_seconds_count`, `http_server_requests_seconds_sum `http_server_requests_seconds_max` in the output. Dimensional labels are added for the request uri, the HTTP method -(GET, POST, etc.), the status code (200, 302, 404, etc.), and a more general outcome field. You should find something like this: +(GET, POST, etc.), the status code (200, 302, 404, etc.), and a more general outcome field. +You should find something like this: [source,text] ---- @@ -112,7 +116,7 @@ We'll add a dimensional label (also called an attribute or a tag) that will allo [source,java] ---- -include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=counted;!ignore;!timer] +include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=primeMethod;counted;!ignore;!timed] ---- <1> Find or create a counter called `example.prime.number` that has a `type` label with the specified value. @@ -155,11 +159,12 @@ You can also count how often a number was checked (generally) by aggregating all == Add a Timer -Timers are a specialized abstraction for measuring duration. Let's add a timer to measure how long it takes to determine if a number is prime. +Timers are a specialized abstraction for measuring duration. +Let's add a timer to measure how long it takes to determine if a number is prime. [source,java] ---- -include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=timed;!ignore;!default] +include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=primeMethod;counted;timed;!ignore;!default] ---- <1> Find or create a counter called `example.prime.number` that has a `type` label with the specified value. @@ -182,7 +187,8 @@ Try the following sequence and look for the following entries in the plain text - `example_prime_number_test_seconds_count` -- how many times the method was called - `example_prime_number_test_seconds_sum` -- the total duration of all method calls -- `example_prime_number_test_seconds_max` -- the maximum observed duration within a decaying interval. This value will return to 0 if the method is not invoked frequently. +- `example_prime_number_test_seconds_max` -- the maximum observed duration within a decaying interval. +This value will return to 0 if the method is not invoked frequently. [source,shell] ---- @@ -192,14 +198,54 @@ curl http://localhost:8080/example/prime/7919 curl http://localhost:8080/q/metrics ---- -Looking at the dimensional data produced by this counter, you can use the sum and the count to calculate how long (on average) it takes to determine if a number is prime. Using the dimensional label, you might be able to understand if there is a significant difference in duration for numbers that are prime when compared with numbers that are not. +Looking at the dimensional data produced by this counter, you can use the sum and the count to calculate how long (on average) it takes to determine if a number is prime. +Using the dimensional label, you might be able to understand if there is a significant difference in duration for numbers that are prime when compared with numbers that are not. + +== Add a Gauge + +Gauges measure a value that can increase or decrease over time, like the speedometer on a car. +The value of a gauge is not accumulated, it is observed at collection time. +Use a gauge to observe the size of a collection, or the value returned from a function. + +[source,java] +---- +include::{code-examples}/telemetry-micrometer-tutorial-example-resource.java[tags=ctor;gauge] +---- + +<1> Define list that will hold arbitrary numbers. +<2> Register a gauge that will track the size of the list. +<3> Create a REST endpoint to populate the list. +Even numbers are added to the list, and odd numbers remove an element from the list. + +=== Review collected metrics + +If you did not leave Quarkus running in dev mode, start it again: + +include::{includes}/devtools/dev.adoc[] + +Then try the following sequence and look for `example_list_size` in the plain text output: + +[source,shell] +---- +curl http://localhost:8080/example/gauge/1 +curl http://localhost:8080/example/gauge/2 +curl http://localhost:8080/example/gauge/4 +curl http://localhost:8080/q/metrics +curl http://localhost:8080/example/gauge/6 +curl http://localhost:8080/example/gauge/5 +curl http://localhost:8080/example/gauge/7 +curl http://localhost:8080/q/metrics +---- + :sectnums!: == Summary Congratulations! -You have created a project that uses the Micrometer and Prometheus Meter Registry extensions to collect metrics. You've observed some of the metrics that Quarkus captures automatically, and have added a `Counter` and `Timer` that are unique to the application. You've also added dimensional labels to metrics, and have observed how those labels shape the data emitted by the prometheus endpoint. +You have created a project that uses the Micrometer and Prometheus Meter Registry extensions to collect metrics. +You've observed some of the metrics that Quarkus captures automatically, and have added a `Counter` and `Timer` that are unique to the application. +You've also added dimensional labels to metrics, and have observed how those labels shape the data emitted by the prometheus endpoint. diff --git a/docs/src/main/asciidoc/telemetry-micrometer.adoc b/docs/src/main/asciidoc/telemetry-micrometer.adoc new file mode 100644 index 00000000000000..b6d6e004b0a365 --- /dev/null +++ b/docs/src/main/asciidoc/telemetry-micrometer.adoc @@ -0,0 +1,669 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// +[id=telemetry-micrometer] += Micrometer Metrics +include::_attributes.adoc[] +:categories: observability +:summary: Use Micrometer to collect metrics produced by Quarkus, its extensions, and your application. +:base-units: https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/BaseUnits.java +:concepts: https://micrometer.io/docs/concepts + +Micrometer provides an abstraction layer for metrics collection. +It defines an API for basic meter types, like counters, gauges, timers, and distribution summaries, along with a `MeterRegistry` API that generalizes metrics collection and propagation for different backend monitoring systems. + +IMPORTANT: Micrometer is the recommended approach to metrics for Quarkus. + +NOTE: By default, the metrics are exposed on the main HTTP server. +If you would like to surface metrics from a separate management port, see the <> section. + +== Micrometer and monitoring system extensions + +Quarkus Micrometer extensions are structured in the same way as the Micrometer project. +The `quarkus-micrometer` extension provides core Micrometer support and runtime integration. +Other Quarkus and Quarkiverse extensions use the Quarkus Micrometer extension to provide support for other monitoring systems. + +Quarkus extensions: + +- micrometer +- micrometer-registry-prometheus + +link:https://github.com/quarkiverse/quarkus-micrometer-registry[Quarkiverse extensions] (may be incomplete): + +- micrometer-registry-azure-monitor +- micrometer-registry-datadog +- micrometer-registry-graphite +- micrometer-registry-influx +- micrometer-registry-jmx +- micrometer-registry-newrelic-telemetry +- micrometer-registry-otlp +- micrometer-registry-signalfx +- micrometer-registry-stackdriver +- micrometer-registry-statsd + +To add support for Prometheus metrics to your application, for example, use the `micrometer-registry-prometheus` extension. +It will bring in the Quarkus Micrometer extension and Micrometer core libraries as dependencies. + +Add the extension to your project using following command (from your project directory): + +:add-extension-extensions: micrometer-registry-prometheus +include::{includes}/devtools/extension-add.adoc[] + +This will add the following to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-micrometer-registry-prometheus + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-micrometer-registry-prometheus") +---- + +And you're all set! + +A similar process applies for other meter registry extensions. +To use the Micrometer StackDriver MeterRegistry, for example, you would use the +`quarkus-micrometer-registry-stackdriver` extension from the Quarkiverse: + +:add-extension-extensions: io.quarkiverse.micrometer.registry:quarkus-micrometer-registry-stackdriver +include::{includes}/devtools/extension-add.adoc[] + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkiverse.micrometer.registry + quarkus-micrometer-registry-stackdriver + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkiverse.micrometer.registry:quarkus-micrometer-registry-stackdriver") +---- + +=== Other registry implementations + +If the Micrometer registry you would like to use does not yet have an associated extension, +use the `quarkus-micrometer` extension and bring in the Micrometer meter registry dependency directly: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-micrometer + + + com.acme + custom-micrometer-registry + ... + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-micrometer") +implementation("com.acme:custom-micrometer-registry") +---- + +You will then need to specify your own provider to configure and initialize the +MeterRegistry, as discussed in the next section. + +=== Create a customized MeterRegistry + +Use a custom `@Produces` method to create and configure a your own `MeterRegistry` if you need to. + +The following example customizes the line format used for StatsD: + +[source,java] +---- +@Produces +@Singleton // <1> +public StatsdMeterRegistry createStatsdMeterRegistry(StatsdConfig statsdConfig, Clock clock) { // <2> + // define what to do with lines + Consumer lineLogger = line -> logger.info(line); + + // inject a configuration object, and then customize the line builder + return StatsdMeterRegistry.builder(statsdConfig) + .clock(clock) + .lineSink(lineLogger) + .build(); +} +---- + +<1> The method returns a `@Singleton`. +<2> The method returns the specific type of `MeterRegistry` + +This example corresponds to the following instructions in the Micrometer documentation: link:https://micrometer.io/docs/registry/statsD#_customizing_the_metrics_sink[Micrometer StatsD: Customizing the Metrics Sink] + +Use MicroProfile Config to inject any configuration attributes you need to configure the registry. +Most Micrometer registry extensions, like `quarkus-micrometer-registry-statsd`, provide registry-specific configuration objects that are integrated with the Quarkus configuration model. +The link:https://github.com/quarkiverse/quarkus-micrometer-registry[Quarkiverse GitHub Repository] can be a useful implementation reference. + +== Create your own metrics + +Metrics data is used in the aggregate to observe how data changes over time. +This data is used for trend analysis, anomaly detection, and alerting. +Data is stored by backend monitoring systems in time series databases, with new values appended to the end of the series. + +NOTE: Metrics are constructed lazily. You may not see any data for the metric you're looking for until you've performed an action that will create it, like visiting an endpoint. + +=== Naming conventions + +Meter names should use dots to separate segments, `a.name.like.this`. +Micrometer applies naming conventions to convert registered meter names to match the expectations of backend monitoring systems. + +Given the following declaration of a timer: `registry.timer("http.server.requests")`, applied naming conventions will emit the following metrics for different monitoring systems: + +- Prometheus: `http_server_requests_duration_seconds` +- Atlas: `httpServerRequests` +- Graphite: `http.server.requests` +- InfluxDB: `http_server_requests` + +[[define-tags]] +=== Define dimensions for aggregation + +Metrics, single numerical measurements, often have additional data captured with them. This ancillary data is used to group or aggregate metrics for analysis. +The Micrometer API refers to this dimensional data as tags, but you may it referred to as "labels" or "attributes" in other documentation sources. + +Micrometer is built primariliy for backend monitoring systems that support dimensional data (metric names that are enchriched with key/value pairs). +For heirarchical systems that only support a flat metric name, Micrometer will flatten the set of key/value pairs (sorted by key) and add them to the name. + +Tags can be specified when a meter is registered with a `MeterRegistry` or using a <>. + +See the Micrometer documentation for additional advice on link:{concepts}#_tag_naming[tag naming]. + +IMPORTANT: Each unique combination of metric name and dimension produces a unique time series. +Using an unbounded set of dimensional data can lead to a "cardinality explosion", an exponential increase in the creation of new time series. + +=== Obtain a reference to a MeterRegistry + +To register meters, you need a reference to a `MeterRegistry`, which is configured and maintained by the Micrometer extension. + +Use one of the following methods to obtain a reference to a `MeterRegistry`: + +1. Use CDI Constructor injection: ++ +[source,java] +---- +package org.acme.micrometer; + +import io.micrometer.core.instrument.MeterRegistry; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +@Path("/example") +@Produces("text/plain") +public class ExampleResource { + + private final MeterRegistry registry; + + ExampleResource(MeterRegistry registry) { + this.registry = registry; + } +} +---- + +2. Use a `MeterRegistry` member variable and use `@Inject`: ++ +[source,java] +---- + @Inject + MeterRegistry registry; +---- + +3. Use the global `MeterRegistry`: ++ +[source,java] +---- + MeterRegistry registry = Metrics.globalRegistry; +---- + +=== Gauges + +Gauges measure a value that can increase or decrease over time, like the speedometer on a car. +Gauges can be useful when monitoring the statistics for a cache or collection. + +Gauge values are sampled rather than set; +there is no record of how the value associated with a gauge may have changed between measurements. + +Micrometer provides a few mechanisms for creating gauges: + +1. Wrap construction of a collection to monitor its size: ++ +[source,java] +---- +List list = registry.gaugeCollectionSize("fantastic.list", // <1> + Tags.of("key", "value") // optional <2> + new ArrayList<>()); // <3> +---- ++ +<1> Create a new gauge, `list.size`, using the dot-separated convention. +<2> Associate <> with the gauge. +Gauge tag values are constant, and must be assigned at construction time. +<3> Construct the array list whose size should be observed. + +2. Use a builder to create a Gauge that will call a function: ++ +[source,java] +---- +Gauge.builder("jvm.threads.peak", threadBean, ThreadMXBean::getPeakThreadCount) // <1> + .baseUnit(BaseUnits.THREADS) // optional <2> + .description("The peak live thread count...") // optional <3> + .tags("key", "value") // optional <4> + .register(registry); // <5> +---- ++ +<1> Create a new gauge called `jvm.threads.peak` that will call `getPeakThreadCount` on `threadBean`, an instance of `ThreadMXBean` +<2> Define the base unit, see link:{base-units}[BaseUnits.java] for predefined values. +<3> Provide a description of the Gauge +<4> Associate <> with the gauge +<5> Register the Gauge with the MeterRegistry + +See link:{concepts}#_gauges[Gauges] in the Micrometer documentation for more information and examples. +Of note are two special cases: `TimeGauge` for measuring time, and a `MultiGauge` for reporting several criteria together. + +NOTE: Micrometer does not create strong references to the objects it observes by default. +Depending on the registry, Micrometer either omits gauges that observe +objects that have been garbage-collected entirely or uses `NaN` (not a number) as the observed value. + +When should you use a gauge? +Only use a gauge when you can't use something else. +Gauges can be less straight-forward to use than other meters. +If what you are measuring can be counted (because the value always increments), use a counter instead. + +=== Counters + +Counters measure values that only increase. +Use one of the methods below to create a counter. + +1. Use a convenience method on the `MeterRegistry`: ++ +[source,java] +---- +registry.counter("example.prime.number", "type", "prime"); // <1> <2> +---- ++ +<1> `example.prime.number` is the counter name. +<2> `type` is a dimensional tag with value `prime`. + + +2. Use `Counter.builder` to provide a description and units: ++ +[source,java] +---- +Counter.builder("count.me") // <1> + .baseUnit("beans") // optional <2> + .description("a description") // optional <3> + .tags("region", "test") // optional <4> + .register(registry); +---- ++ +<1> Create a new counter called `count.me` +<2> Define a custom base unit. See link:{base-units}[BaseUnits.java] for predefined values. +<3> Provide a description for the counter +<4> Associate <> with the counter + +3. <> a method ++ +[source,java] +---- +@Counted(value = "counted.method", extraTags = { "extra", "annotated" }) // <1> <2> +void countThisMethod(){ + ... +} +---- ++ +<1> A CDI interceptor will create and register a counter called `counted.method` +<2> The interceptor-created counter will have the "extra" dimension tag with value "annotated" + +See link:{concepts}#_counters[Counters] in the Micrometer documentation for more information and examples, including the less common `FunctionCounter` that can be used to measure the result returned by an always increasing function. + +When should you use a counter? +Use a counter if you are doing something that can not be either timed or summarized. +If you want to understand more about how a value is changing, +a timer (when the base unit of measurement is time) or a distribution summary might be +more appropriate. + +=== Summaries and Timers + +Timers and distribution summaries in Micrometer are very similar. Both meters record data, and can capture additional histogram or percentile data. While distribution summaries can be use for arbitrary types of data, timers are optimized for measuring time and durations. + +Timers and distribution summaries store at least three values internally: + +- the aggregation of all recorded values as a sum +- the number of values that have been recorded (a counter) +- the highest value seen within a decaying time window (a gauge). + +==== Create a distribution summary + +Use a distribution summary to record a value, not time. +Use one of the following methods to create a distribution summary. + +1. Use a convenience method on the `MeterRegistry`: ++ +[source,java] +---- +registry.summary("bytes.written", "protocol", "http"); // <1> <2> +---- ++ +<1> `bytes.written` is the summary name +<2> `protocol` is a dimensional tag with value `http`. + + +2. Use `DistributionSummary.builder` to provide a description and units: ++ +[source,java] +---- +DistributionSummary.builder("response.size") // <1> + .baseUnit("bytes") // optional <2> + .description("a description") // optional <3> + .tags("protocol", "http") // optional <4> + .register(registry); +---- ++ +<1> Create a new distribution summary called `response.size` +<2> Use `bytes` as a base unit. See link:{base-units}[BaseUnits.java] for predefined values. +<3> Provide a description for the distribution summary +<4> Associate <> with the distribution summary + +==== Create a timer + +Timers measure short-duration latencies and how often they occur. Negative values are not supported, and longer durations could cause an overflow of the total time (Long.MAX_VALUE nanoseconds (292.3 years)). + +Use one of the following methods to construct a timer. + +1. Use a convenience method on the `MeterRegistry`: ++ +[source,java] +---- +registry.timer("fabric.selection", "primary", "blue"); // <1> <2> +---- ++ +<1> `fabric.selection` is the summary name +<2> `primary` is a dimensional tag with value `blue`. + + +2. Use `Timer.builder` to provide a description and units: ++ +[source,java] +---- +Timer.builder("my.timer") // <1> <2> + .description("description ") // optional <3> + .tags("region", "test") // optional <4> + .register(registry); +---- ++ +<1> Create a new timer called `my.timer` +<2> Timers measure time, and will convert it into the units required by the monitoring backend +<3> Provide a description for the distribution summary +<4> Associate <> with the timer + +3. <> a method ++ +[source,java] +---- +@Timed(value = "call", extraTags = {"region", "test"}) // <1> <2> +---- ++ +<1> A CDI interceptor will create and register a timer called `call` +<2> The interceptor-created timer will have the "region" dimension tag with value "test" + +==== Measure durations with Timers + +Micrometer provides the following convenience mechanisms for recording durations. + +1. Wrap the invocation of a `Runnable`: ++ +[source,java] +---- +timer.record(() -> noReturnValue()); +---- + +2. Wrap the invocation of a `Callable`: ++ +[source,java] +---- +timer.recordCallable(() -> returnValue()); +---- + +3. Create a wrapped `Runnable` for repeated invocation: ++ +[source,java] +---- +Runnable r = timer.wrap(() -> noReturnValue()); +---- + +4. Create a wrapped `Callable` for repeated invocation: ++ +[source,java] +---- +Callable c = timer.wrap(() -> returnValue()); +---- + +5. Use a `Sample` for more complex code paths: ++ +[source,java] +---- +Sample sample = Timer.start(registry); // <1> + +doStuff; // <2> + +sample.stop(registry.timer("my.timer", "response", response.status())); // <3> +---- ++ +<1> We create a sample, which records the start of the timer. +<2> The sample can be passed along as context +<3> We can choose the timer when the sample is stopped. This example uses a response status as a tag identifying the timer, which won't be known until processing is complete. + +==== Histograms and percentiles + +Both timers and distribution summaries can be configured to emit additional statistics, like histogram data, precomputed percentiles, or service level objective (SLO) boundaries. +See link:{concepts}#_timers[Timers] and link:{concepts}#_distribution_summaries[Distribution Summaries] in the Micrometer documentation for more information and examples, including memory footprint estimation for both types. + +[IMPORTANT] +==== +The count, sum, and histogram data associated with timers and distribution summaries can be re-aggregated across dimensions (or across a series of instances). + +Precomputed percentile values can not. Percentiles are unique to each dataset (the 90th percentile of this collection of measurements). +==== + +== Automatically generated metrics + +The Micrometer extension automatically times HTTP server requests. Following Prometheus naming conventions for +timers, look for `http_server_requests_seconds_count`, `http_server_requests_seconds_sum`, and +`http_server_requests_seconds_max`. Dimensional labels have been added for the requested uri, the HTTP method +(GET, POST, etc.), the status code (200, 302, 404, etc.), and a more general outcome field. + +.Ignoring endpoints + +You can disable measurement of HTTP endpoints using the `quarkus.micrometer.binder.http-server.ignore-patterns` +property. +This property accepts a comma-separated list of simple regex match patterns identifying URI paths that should +be ignored. +For example, setting `quarkus.micrometer.binder.http-server.ignore-patterns=/example/prime/[0-9]+` will +ignore a request to `http://localhost:8080/example/prime/7919`. +A request to `http://localhost:8080/example/gauge/7919` +would still be measured. + +.URI templates + +The micrometer extension will make a best effort at representing URIs containing path parameters in templated form. +Using examples from above, a request to `http://localhost:8080/example/prime/7919` should appear as an attribute of +`http_server_requests_seconds_*` metrics with a value of `uri=/example/prime/{number}`. + +Use the `quarkus.micrometer.binder.http-server.match-patterns` property if the correct URL can not be determined. +This property accepts a comma-separated list defining an association between a simple regex match pattern and a replacement +string. +For example, setting +`quarkus.micrometer.binder.http-server.match-patterns=/example/prime/[0-9]+=/example/{jellybeans}` would use the value +`/example/{jellybeans}` for the uri attribute any time the requested uri matches `/example/prime/[0-9]+`. + +.Exported metrics format + +By default, the metrics are exported using the Prometheus format `application/openmetrics-text`, +you can revert to the former format by specifying the `Accept` request header to `text/plain` (`curl -H "Accept: text/plain" localhost:8080/q/metrics/`). + +[[meter-filter]] +== Use `MeterFilter` to customize emitted tags and metrics + +Micrometer uses `MeterFilter` instances to customize the metrics emitted by `MeterRegistry` instances. +The Micrometer extension will detect `MeterFilter` CDI beans and use them when initializing `MeterRegistry` +instances. + +[source,java] +---- +@Singleton +public class CustomConfiguration { + + @ConfigProperty(name = "deployment.env") + String deploymentEnv; + + /** Define common tags that apply only to a Prometheus Registry */ + @Produces + @Singleton + @MeterFilterConstraint(applyTo = PrometheusMeterRegistry.class) + public MeterFilter configurePrometheusRegistries() { + return MeterFilter.commonTags(Arrays.asList( + Tag.of("registry", "prometheus"))); + } + + /** Define common tags that apply globally */ + @Produces + @Singleton + public MeterFilter configureAllRegistries() { + return MeterFilter.commonTags(Arrays.asList( + Tag.of("env", deploymentEnv))); + } + + /** Enable histogram buckets for a specific timer */ + @Produces + @Singleton + public MeterFilter enableHistogram() { + return new MeterFilter() { + @Override + public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) { + if(id.getName().startsWith("myservice")) { + return DistributionStatisticConfig.builder() + .percentiles(0.5, 0.95) // median and 95th percentile, not aggregable + .percentilesHistogram(true) // histogram buckets (e.g. prometheus histogram_quantile) + .build() + .merge(config); + } + return config; + } + }; + } +} +---- + +In this example, a singleton CDI bean will produce two different `MeterFilter` beans. +One will be applied only to +Prometheus `MeterRegistry` instances (using the `@MeterFilterConstraint` qualifier), and another will be applied +to all `MeterRegistry` instances. +An application configuration property is also injected and used as a tag value. +Additional examples of MeterFilters can be found in the +link:https://micrometer.io/docs/concepts[official documentation]. + +[[annotations]] +== Does Micrometer support annotations? + +Micrometer does define two annotations, `@Counted` and `@Timed`, that can be added to methods. +The `@Timed` annotation will wrap the execution of a method and will emit the following tags +in addition to any tags defined on the annotation itself: +class, method, and exception (either "none" or the simple class name of a detected exception). + +Using annotations is limited, as you can't dynamically assign meaningful tag values. +Also note that many methods, e.g. REST endpoint methods or Vert.x Routes, are counted and timed by the micrometer extension out of the box. + +== Support for the MicroProfile Metrics API + +If you use the MicroProfile Metrics API in your application, +the Micrometer extension will create an adaptive layer to map those metrics into the Micrometer registry. +Note that naming conventions between the two systems is different, so the metrics that are emitted when using MP Metrics with Micrometer will change. + +Use a `MeterFilter` to remap names or tags according to your conventions. + +[source,java] +---- +@Produces +@Singleton +public MeterFilter renameApplicationMeters() { + final String targetMetric = MPResourceClass.class.getName() + ".mpAnnotatedMethodName"; + + return MeterFilter() { + @Override + public Meter.Id map(Meter.Id id) { + if (id.getName().equals(targetMetric)) { + // Drop the scope tag (MP Registry type: application, vendor, base) + List tags = id.getTags().stream().filter(x -> !"scope".equals(x.getKey())) + .collect(Collectors.toList()); + // rename the metric + return id.withName("my.metric.name").replaceTags(tags); + } + return id; + } + }; +} +---- + +Ensure the following dependency is present if you require the MicroProfile Metrics API: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + org.eclipse.microprofile.metrics + microprofile-metrics-api + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("org.eclipse.microprofile.metrics:microprofile-metrics-api") +---- + +NOTE: The MP Metrics API compatibility layer may be moved to a different extension in the future. + +[[management-interface]] +== Management interface + +By default, the metrics are exposed on the main HTTP server. + +You can expose them on a separate network interface and port by setting `quarkus.management.enabled=true` in your application configuration. +Note that this property is a build-time property. +The value cannot be overridden at runtime. + +If you enable the management interface without customizing the management network interface and port, the metrics are exposed under: `http://0.0.0.0:9000/q/metrics`. + +You can configure the path of each exposed format using: +[source, properties] +---- +quarkus.micrometer.export.json.enabled=true # Enable json metrics +quarkus.micrometer.export.json.path=metrics/json +quarkus.micrometer.export.prometheus.path=metrics/prometheus +---- + +With such a configuration, the json metrics will be available from `http://0.0.0.0:9000/q/metrics/json`. +The prometheus metrics will be available from `http://0.0.0.0:9000/q/metrics/prometheus`. + +Refer to the xref:./management-interface-reference.adoc[management interface reference] for more information. + +== Configuration Reference + +include::{generated-dir}/config/quarkus-micrometer.adoc[opts=optional, leveloffset=+1] diff --git a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/doc/micrometer/ExampleResource.java b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/doc/micrometer/ExampleResource.java index d17ec50ddbdbbd..46e817a54cf4f5 100644 --- a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/doc/micrometer/ExampleResource.java +++ b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/doc/micrometer/ExampleResource.java @@ -1,14 +1,12 @@ // tag::example[] -// tag::ignore[] -// Source: {{source}} -package io.quarkus.doc.micrometer; - -/* -// end::ignore[] +/*- package org.acme.micrometer; // tag::ignore[] */ +// Source: integration-tests/micrometer-prometheus/src/main/java/io/quarkus/doc/micrometer/ExampleResource.java +package io.quarkus.doc.micrometer; + // end::ignore[] import java.util.LinkedList; import java.util.NoSuchElementException; @@ -25,22 +23,27 @@ @Path("/example") @Produces("text/plain") public class ExampleResource { - private final LinkedList list = new LinkedList<>(); + // tag::gauge[] + private final LinkedList list = new LinkedList<>(); // <1> + + // end::gauge[] // tag::registry[] private final MeterRegistry registry; + // tag::ctor[] ExampleResource(MeterRegistry registry) { this.registry = registry; // tag::gauge[] - registry.gaugeCollectionSize("example.list.size", Tags.empty(), list); + registry.gaugeCollectionSize("example.list.size", Tags.empty(), list); // <2> // end::gauge[] } + // end::ctor[] // end::registry[] // tag::gauge[] @GET @Path("gauge/{number}") - public Long checkListSize(@PathParam("number") long number) { + public Long checkListSize(@PathParam("number") long number) { // <3> if (number == 2 || number % 2 == 0) { // add even numbers to the list list.add(number); @@ -56,61 +59,58 @@ public Long checkListSize(@PathParam("number") long number) { } // end::gauge[] - // tag::timed[] - // tag::counted[] + // tag::primeMethod[] @GET @Path("prime/{number}") public String checkIfPrime(@PathParam("number") long number) { if (number < 1) { - // tag::counter[] + // tag::counted[] registry.counter("example.prime.number", "type", "not-natural") // <1> .increment(); // <2> - // end::counter[] + // end::counted[] return "Only natural numbers can be prime numbers."; } if (number == 1) { - // tag::counter[] + // tag::counted[] registry.counter("example.prime.number", "type", "one") // <1> .increment(); // <2> - // end::counter[] + // end::counted[] return number + " is not prime."; } if (number == 2 || number % 2 == 0) { - // tag::counter[] + // tag::counted[] registry.counter("example.prime.number", "type", "even") // <1> .increment(); // <2> - // end::counter[] + // end::counted[] return number + " is not prime."; } - - // tag::timer[] + // tag::timed[] if (timedTestPrimeNumber(number)) { // <3> - // end::timer[] + // end::timed[] // tag::ignore[] - /* - * } - * // end::ignore[] - * // tag::default[] - * if (testPrimeNumber(number)) { - * // end::default[] - * // tag::ignore[] - */ - // end::ignore[] - // tag::counter[] + registry.counter("example.prime.number", "type", "prime") // <1> + .increment(); + return number + " is prime."; + } else + // end::ignore[] + // tag::default[] + if (testPrimeNumber(number)) { + // end::default[] + // tag::counted[] registry.counter("example.prime.number", "type", "prime") // <1> .increment(); // <2> - // end::counter[] + // end::counted[] return number + " is prime."; } else { - // tag::counter[] + // tag::counted[] registry.counter("example.prime.number", "type", "not-prime") // <1> .increment(); // <2> - // end::counter[] + // end::counted[] return number + " is not prime."; } } - // end::counted[] - // tag::timer[] + // end::primeMethod[] + // tag::timed[] protected boolean timedTestPrimeNumber(long number) { Timer.Sample sample = Timer.start(registry); // <4> @@ -118,7 +118,6 @@ protected boolean timedTestPrimeNumber(long number) { sample.stop(registry.timer("example.prime.number.test", "prime", result + "")); // <6> return result; } - // end::timer[] // end::timed[] protected boolean testPrimeNumber(long number) {