diff --git a/docs/includes/guides/metrics.adoc b/docs/includes/guides/metrics.adoc index 5092a4ceca6..a75b8966c48 100644 --- a/docs/includes/guides/metrics.adoc +++ b/docs/includes/guides/metrics.adoc @@ -21,7 +21,7 @@ ifndef::rootdir[:rootdir: {docdir}/../..] // tag::intro[] This guide describes how to create a sample Helidon {intro-project-name} project -that can be used to run some basic examples using both built-in and custom metrics with Helidon. +that can be used to run some basic examples using both built-in and custom {metrics} with Helidon. == What You Need @@ -51,25 +51,27 @@ mvn -U archetype:generate -DinteractiveMode=false \ // end::create-sample-project[] // tag::using-built-in-metrics-intro[] -=== Using the Built-In Metrics +=== Using the Built-In {metrics_uc} -Helidon provides three scopes of metrics: base, vendor, and application. Here are the metric endpoints: +Helidon provides three built-in scopes of metrics: base, vendor, and application. Here are the metric endpoints: -1. `/metrics/base` - Base metrics data as specified by the MicroProfile Metrics specification. -2. `/metrics/vendor` - Helidon-specific metrics data. -3. `/metrics/application` - Application-specific metrics data. +1. `{metrics-endpoint}?scope=base` - Base {metrics} +ifdef::mp-flavor[as specified by the MicroProfile Metrics specification] +2. `{metrics-endpoint}?scope=vendor` - Helidon-specific {metrics} +3. `{metrics-endpoint}?scope=application` - Application-specific metrics data. -NOTE: The `/metrics` endpoint will return data for all scopes. +Applications can add their own custom scopes as well simply by specifying a custom scope name when registering a {metric}. -The built-in metrics fall into three categories: +NOTE: The `{metrics-endpoint}` endpoint returns data for all scopes. -. JVM behavior (in the base registry), -. basic key performance indicators for request handling (in the vendor registry), and -. thread pool utilization (also in the vendor registry). +The built-in {metrics} fall into these categories: -A later section describes the <> in detail. +. JVM behavior (in the base scope), and +. basic key performance indicators for request handling (in the vendor scope). -The following example demonstrates how to use the other built-in metrics. All examples are executed +A later section describes the <> in detail. + +The following example demonstrates how to use the other built-in {metrics}. All examples are executed from the root directory of your project (helidon-quickstart-{flavor-lc}). // end::using-built-in-metrics-intro[] @@ -85,19 +87,19 @@ from the root directory of your project (helidon-quickstart-{flavor-lc}). // tag::build-and-run-intro[] [source,bash,subs="attributes+"] -.Build the application, skipping unit tests, then run it: +.Build the application and then run it: ---- -mvn package -DskipTests=true +mvn package java -jar target/helidon-quickstart-{flavor-lc}.jar ---- -NOTE: Metrics can be returned in either text format (the default), or JSON. The text format uses OpenMetrics (Prometheus) Text Format, +NOTE: Metrics output can be returned in either text format (the default), or JSON. The text format uses OpenMetrics (Prometheus) Text Format, see https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-details. -[source,bash] +[source,bash,subs="attributes+"] .Verify the metrics endpoint in a new terminal window: ---- -curl http://localhost:8080/metrics +curl http://localhost:8080{metrics-endpoint} ---- // end::build-and-run-intro[] @@ -115,91 +117,58 @@ base:classloader_total_loaded_class_count 7512 // tag::curl-metrics-json[] You can get the same data in JSON format. -[source,bash] +[source,bash,subs="attributes+"] .Verify the metrics endpoint with an HTTP accept header: ---- -curl -H "Accept: application/json" http://localhost:8080/metrics +curl -H "Accept: application/json" http://localhost:8080{metrics-endpoint} ---- // end::curl-metrics-json[] // tag::base-metrics-json-output[] - "classloader.currentLoadedClass.count": 7534, - "classloader.totalLoadedClass.count": 7538, - "classloader.totalUnloadedClass.count": 1, - "cpu.availableProcessors": 4, - "cpu.systemLoadAverage": 2.83349609375, - "gc.PS MarkSweep.count": 2, - "gc.PS MarkSweep.time": 77, - "gc.PS Scavenge.count": 5, - "gc.PS Scavenge.time": 37, - "jvm.uptime": 727588, - "memory.committedHeap": 284164096, - "memory.maxHeap": 3817865216, - "memory.usedHeap": 53283088, - "thread.count": 44, - "thread.daemon.count": 35, - "thread.max.count": 44 + "gc.total;name=G1 Young Generation": 1, + "cpu.systemLoadAverage": 4.451171875, + "classloader.loadedClasses.count": 3582, + "thread.count": 18, + "classloader.unloadedClasses.total": 0, + "jvm.uptime": 369478, + "gc.time;name=G1 Young Generation": 0, + "memory.committedHeap": 541065216, + "thread.max.count": 19, + "cpu.availableProcessors": 8, + "classloader.loadedClasses.total": 3582, + "thread.daemon.count": 16, + "memory.maxHeap": 8589934592, + "memory.usedHeap": 20491248 // end::base-metrics-json-output[] // tag::vendor-metrics-json-output[] "vendor": { - "executor-service.active-count;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 0, - "executor-service.completed-task-count;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 0, - "executor-service.largest-pool-size;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 5, - "executor-service.pool-size;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 5, - "executor-service.queue.remaining-capacity;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 10000, - "executor-service.queue.size;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 0, - "executor-service.task-count;poolIndex=0;supplierCategory=helidon-thread-pool-2;supplierIndex=0": 0, - "requests.count": 6, - "requests.meter": { - "count": 6, - "meanRate": 0.008275992296704147, - "oneMinRate": 0.01576418632772332, - "fiveMinRate": 0.006695060022357365, - "fifteenMinRate": 0.0036382699664488415 - } + "requests.count": 3 } // end::vendor-metrics-json-output[] // tag::get-single-metric[] -You can get a single metric by specifying the name in the URL path. +You can get a single metric by specifying the scope and name as query parameters in the URL. -[source,bash] -.Get the Helidon `requests.meter` metric: +[source,bash,subs="attributes+"] +.Get the Helidon `requests.count` {metric}: ---- -curl -H "Accept: application/json" http://localhost:8080/metrics/vendor/requests.meter +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=vendor&name=requests.count' ---- [source,json] .JSON response: ---- { - "requests.meter": { - "count": 6, - "meanRate": 0.008275992296704147, - "oneMinRate": 0.01576418632772332, - "fiveMinRate": 0.006695060022357365, - "fifteenMinRate": 0.0036382699664488415 - } + "requests.count": 6 } ---- -NOTE: You cannot get the individual fields of a metric. For example, you cannot target http://localhost:8080/metrics/vendor/requests.meter.count. // end::get-single-metric[] // tag::built-in-metrics-discussion[] -The `base` metrics illustrated above provide some insight into the behavior of the JVM in which the server runs. +The `base` {metrics} illustrated above provide some insight into the behavior of the JVM in which the server runs. -The `vendor` metrics shown above appear in two groups: - -- Helidon thread pools + -+ -Helidon uses these thread pools for its own internal work, and your application can also use Helidon-managed thread pools if it needs to do work asynchronously. (See link:{helidon-github-tree-url}/examples/webserver/threadpool[this example].) -The metrics in this group show information about the thread pools which can help you assess how efficiently they are utilized. -Helidon uses tags to distinguish the metrics which describe different thread pools. -In some cases the specific metrics exposed depend on the particular type of thread pool. -- basic key performance indicators + -+ -These metrics give an idea of the request traffic the server is handling. See the <> for more information on the basic and extended key performance indicator metrics. +The `vendor` {metric} shown above gives an idea of the request traffic the server is handling. See the <> for more information on the basic and extended key performance indicator {metrics}. // end::built-in-metrics-discussion[] @@ -212,10 +181,7 @@ By adding a `metrics` section to your application configuration you can control * <>. // end::controlling-intro-part-1[] // tag::controlling-intro-part-2[] -* Identify groups of metrics to control: -** <>, and -** <> (application, vendor, and base) and within a registry by metric names which match patterns you provide. -* Select whether to collect <>. +* Select whether to collect <>. // end::controlling-intro-part-2[] // end::controlling-intro[] @@ -223,17 +189,30 @@ By adding a `metrics` section to your application configuration you can control // tag::disabling-whole-intro[] [[disabling-entirely]] ==== Disabling Metrics Subsystem Entirely -By default, if your application depends on the `helidon-metrics` Maven module then full-featured metrics are enabled. You can disable the metrics subsystem entirely using configuration: +ifdef::mp-flavor[] [source,properties] .Configuration properties file disabling metrics ---- metrics.enabled=false ---- +endif::mp-flavor[] +ifdef::se-flavor[] +[source,yaml] +.Configuration properties file disabling metrics +---- +server: + features: + observe: + observers: + metrics: + enabled: false +---- +endif::se-flavor[] // end::disabling-whole-intro[] // tag::disabling-whole-summary[] -With metrics processing disabled, Helidon never updates any metrics and the `/metrics` endpoints respond with `404` plus a message that the metrics subsystem is disabled. +With metrics processing disabled, Helidon never updates any {metrics} and the `{metrics-endpoint}` endpoints respond with `404`. // end::disabling-whole-summary[] // end::disabling-whole[] @@ -259,7 +238,7 @@ Note that if you disable metrics processing entirely, no component updates its m // tag::controlling-by-registry[] // tag::controlling-by-registry-intro[] -[[enabling-disabling-by-registry]] +[[enabling-disabling-by-scope]] ==== Controlling Metrics By Registry Type and Metric Name You can control the collection and reporting of metrics by registry type and metric name within registry type. @@ -357,43 +336,56 @@ metrics.registries.0.filter.exclude = myapp\..*/deletes [[basic-and-extended-kpi]] ==== Collecting Basic and Extended Key Performance Indicator (KPI) Metrics -Any time you include the Helidon metrics module in your application, Helidon tracks two basic performance indicator metrics: - -* a `Counter` of all requests received (`requests.count`), and -* a `Meter` of all requests received (`requests.meter`). +Any time you include the Helidon metrics module in your application, Helidon tracks a basic performance indicator {metric}: a `Counter` of all requests received (`requests.count`). Helidon {h1-prefix} also includes additional, extended KPI metrics which are disabled by default: -* current number of requests in-flight - a `ConcurrentGauge` (`requests.inFlight`) of requests currently being processed -* long-running requests - a `Meter` (`requests.longRunning`) measuring the rate at which Helidon processes requests which take at least a given amount of time to complete; configurable, defaults to 10000 milliseconds (10 seconds) -* load - a `Meter` (`requests.load`) measuring the rate at which requests are worked on (as opposed to received) -* deferred - a `Meter` (`requests.deferred`) measuring the rate at which a request's processing is delayed after Helidon receives the request +* current number of requests in-flight - a `Gauge` (`requests.inFlight`) of requests currently being processed +* long-running requests - a `Counter` (`requests.longRunning`) measuring the total number of requests which take at least a given amount of time to complete; configurable, defaults to 10000 milliseconds (10 seconds) +* load - a `Counter` (`requests.load`) measuring the number of requests worked on (as opposed to received) +* deferred - a `Gauge` (`requests.deferred`) measuring delayed request processing (work on a request was delayed after Helidon received the request) -You can enable and control these metrics using configuration: +You can enable and control these {metrics} using configuration: +ifdef::mp-flavor[] [source,properties] -.Configuration properties file controlling extended KPI metrics +.Configuration properties file controlling extended KPI {metrics} ---- metrics.key-performance-indicators.extended = true metrics.key-performance-indicators.long-running.threshold-ms = 2000 ---- +endif::mp-flavor[] +ifdef::se-flavor[] +[source,yaml] +---- +server: + features: + observe: + observers: + metrics: + key-performance-indicators: + extended: true + long-running: + threshold-ms: 2000 +---- +endif::se-flavor[] // end::KPI[] // end::controlling[] // tag::metrics-metadata[] === Metrics Metadata -Each metric has associated metadata that describes: +Each {metric} has associated metadata that includes: -1. name: The name of the metric. -2. units: The unit of the metric such as time (seconds, millisecond), size (bytes, megabytes), etc. -3. type: The type of metric: `Counter`, `Timer`, `Meter`, `Histogram`, `SimpleTimer`, or `Gauge`. +1. name: The name of the {metric}. +2. units: The unit of the {metric} such as time (seconds, millisecond), size (bytes, megabytes), etc. +3. a description of the {metric}. You can get the metadata for any scope, such as `/metrics/base`, as shown below: [source,bash] .Get the metrics metadata using HTTP OPTIONS method: ---- - curl -X OPTIONS -H "Accept: application/json" http://localhost:8080/metrics/base + curl -X OPTIONS -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=base' ---- [source,json] @@ -403,8 +395,7 @@ You can get the metadata for any scope, such as `/metrics/base`, as shown below: "classloader.currentLoadedClass.count": { "unit": "none", "type": "counter", - "description": "Displays the number of classes that are currently loaded in the Java virtual machine.", - "displayName": "Current Loaded Class Count" + "description": "Displays the number of classes that are currently loaded in the Java virtual machine." }, "jvm.uptime": { "unit": "milliseconds", diff --git a/docs/includes/metrics/metrics-config.adoc b/docs/includes/metrics/metrics-config.adoc index 7a7627bd160..d389bed4dee 100644 --- a/docs/includes/metrics/metrics-config.adoc +++ b/docs/includes/metrics/metrics-config.adoc @@ -41,12 +41,12 @@ Metrics configuration is quite extensive and powerful and, therefore, a bit comp The rest of this section illustrates some of the most common scenarios: * <> -* <> * <> ifdef::mp-flavor[] * <> endif::[] + [#config-disable] ==== Disable Metrics Subsystem @@ -60,76 +60,31 @@ endif::[] ifdef::se-flavor[] [source,yaml] ---- -metrics: - enabled: false +server: + features: + observe: + observers: + metrics: + enabled: false ---- endif::[] -Helidon does not update metrics, and the `/metrics` endpoints respond with `404` plus a message that the metrics subsystem is disabled. - -[#config-selective] -==== Disable Selected Metrics - -You can be even more selective. Within a registry type you can configure up to two regular expression patterns: - -* one matching metric names to _exclude_, and -* one matching metric names to _include_. - -Helidon updates and reports a metric only if two conditions hold: - -* the metric name _does not_ match the `exclude` regex pattern (if you define one), and -* either -** there is no `include` regex pattern, or -** the metric name matches the `include` pattern. - -[NOTE] -==== -Make sure any `include` regex pattern you specify matches _all_ the metric names you want to capture. -==== -Suppose your application creates and updates a group of metrics with names such as `myapp.xxx.queries`, `myapp.xxx.creates`, `myapp.xxx.updates`, and `myapp.xxx.deletes` where `xxx` can be either `supplier` or `customer`. - -The following example gathers all metrics _except_ those from your application regarding suppliers _although_ supplier updates are _included_: - -.Disabling and enabling metrics by name -ifdef::mp-flavor[] -[source,properties] ----- -metrics.registries.0.type=application -metrics.registries.0.application.filter.exclude=myapp\.supplier\..* -metrics.registries.0.application.filter.include=myapp\.supplier\.updates ----- -endif::[] -ifdef::se-flavor[] -[source,yaml] ----- -metrics: - registries: - - type: application - filter: - exclude: "myapp\.supplier\..*" - include: "myapp\.supplier\.updates" ----- -endif::[] - -This setting excludes metrics with names starting with `myapp.supplier` _except_ for the metric `myapp.supplier.updates`. The `exclude` and `include` values are regular expressions. +Helidon does not update metrics, and the `{metrics-endpoint}` endpoints respond with `404`.. [#config-kpi] -==== Collecting Basic and Extended Key Performance Indicator (KPI) Metrics - -Any time you include the Helidon metrics module in your application, Helidon tracks two basic performance indicator metrics: +==== Collecting Basic and Extended Key Performance Indicator (KPI) {metrics_uc} -* a `Counter` of all requests received (`requests.count`), and -* a `Meter` of all requests received (`requests.meter`). +Any time you include the Helidon metrics module in your application, Helidon tracks a basic performance indicator {metric}: a `Counter` of all requests received (`requests.count`) -Helidon {h1-prefix} also includes additional, extended KPI metrics which are disabled by default: +Helidon {h1-prefix} also includes additional, extended KPI {metrics} which are disabled by default: -* current number of requests in-flight - a `ConcurrentGauge` (`requests.inFlight`) of requests currently being processed -* long-running requests - a `Meter` (`requests.longRunning`) measuring the rate at which Helidon processes requests which take at least a given amount of time to complete; configurable, defaults to 10000 milliseconds (10 seconds) -* load - a `Meter` (`requests.load`) measuring the rate at which requests are worked on (as opposed to received) -* deferred - a `Meter` (`requests.deferred`) measuring the rate at which a request's processing is delayed after Helidon receives the request +* current number of requests in-flight - a `Gauge` (`requests.inFlight`) of requests currently being processed +* long-running requests - a `Counter` (`requests.longRunning`) measuring the total number of requests which take at least a given amount of time to complete; configurable, defaults to 10000 milliseconds (10 seconds) +* load - a `Counter` (`requests.load`) measuring the number of requests worked on (as opposed to received) +* deferred - a `Gauge` (`requests.deferred`) measuring delayed request processing (work on a request was delayed after Helidon received the request) -You can enable and control these metrics using configuration: +You can enable and control these {metrics} using configuration: -.Controlling extended KPI metrics +.Controlling extended KPI {metrics} ifdef::mp-flavor[] [source,properties] ---- @@ -140,24 +95,28 @@ endif::[] ifdef::se-flavor[] [source,yaml] ---- -metrics: - key-performance-indicators: - extended: true - long-running: - threshold-ms: 2000 +server: + features: + observe: + observers: + metrics: + key-performance-indicators: + extended: true + long-running: + threshold-ms: 2000 ---- endif::[] [#config-rest-request] ifdef::mp-flavor[] -==== Enable `REST.request` Metrics +==== Enable `REST.request` {metrics_uc} -.Controlling REST request metrics +.Controlling REST request {metrics} [source,properties] ---- metrics.rest-request-enabled=true ---- -Helidon automatically registers and updates `SimpleTimer` metrics for every REST endpoint in your service. +Helidon automatically registers and updates `Timer` {metrics} for every REST endpoint in your service. endif::[] // end::config-examples[] \ No newline at end of file diff --git a/docs/includes/metrics/metrics-shared.adoc b/docs/includes/metrics/metrics-shared.adoc index b2dbc81c439..e0da52aef04 100644 --- a/docs/includes/metrics/metrics-shared.adoc +++ b/docs/includes/metrics/metrics-shared.adoc @@ -22,66 +22,81 @@ ifndef::rootdir[:rootdir: {docdir}/../..] ifndef::flavor-lc[:flavor-lc: se] :description: Helidon metrics :keywords: helidon, metrics -:writing-code-content: code which explicitly invokes the metrics API to register metrics, retrieve previously-registered metrics, and update metric values. +:writing-code-content: code which explicitly invokes the metrics API to register {metrics}, retrieve previously-registered {metrics}, and update {metric} values. * a unified way for ifdef::mp-flavor[MicroProfile] ifdef::se-flavor[Helidon] servers to export monitoring data--telemetry--to management agents, and -* a unified Java API which all application programmers can use to register and update metrics to expose telemetry data from their services. +* a unified Java API which all application programmers can use to register and update {metrics} to expose telemetry data from their services. ifdef::mp-flavor[] * support for metrics-related annotations. -endif::[] Learn more about the https://github.com/eclipse/microprofile-metrics/releases/tag/{version-lib-microprofile-metrics-api}[MicroProfile Metrics specification]. +endif::[] + +Metrics is one of the Helidon observability features. // end::overview[] // tag::usage-body[] === Instrumenting Your Service -You add metrics to your service +You add {metrics} to your service ifdef::se-flavor[] by writing {writing-code-content} endif::[] ifdef::mp-flavor[] in these ways: -* Annotate bean methods--typically your REST resource endpoint methods (the Java code that receives incoming REST requests); Helidon automatically registers these metrics and updates them when the annotated methods are invoked via CDI. +* Annotate bean methods--typically your REST resource endpoint methods (the Java code that receives incoming REST requests); Helidon automatically registers these {metrics} and updates them when the annotated methods are invoked via CDI. * Write {writing-code-content} -* Configure some simple `REST.request` metrics which Helidon automatically registers and updates for all REST resource endpoints. +* Configure some simple `REST.request` {metrics} which Helidon automatically registers and updates for all REST resource endpoints. endif::[] Later sections of this document describe how to do ifdef::mp-flavor[each of these.] ifdef::se-flavor[this.] -=== Categorizing Types of Metrics -Helidon distinguishes among three general _types_, or scopes, of metrics, as described in the link:{microprofile-metrics-spec-url}[MP metrics specification]. +=== Categorizing Types of {Metrics_uc} +Helidon distinguishes among _scopes_, or types, of +ifdef::se-flavor[{metrics}.] +ifdef::mp-flavor[{metrics} as described in the link:{microprofile-metrics-spec-url}[MP metrics specification].] -.Types (scopes) of metrics +Helidon includes {metrics} in the built-in scopes described below. +Applications often register their own {metrics} in the `application` scope but can create their own scopes and register {metrics} within them. + +.Built-in {metric} scopes [%autowidth] |==== -| Type/scope | Typical Usage - -| base | Mandated by the MP metrics specification, such as OS or Java runtime measurements (available heap, disk space, etc.). -| vendor | Implemented by vendors, including the `REST.request` metrics and other key performance indicator measurements (described in later sections). -| application | Declared via annotations or programmatically registered by your service code. +| Built-in Scope | Typical Usage + +| `base` +| OS or Java runtime measurements (available heap, disk space, etc.). +ifdef::mp-flavor[Mandated by the MP metrics specification] +| `vendor` +| Implemented by vendors, including the `REST.request` metrics and other key performance indicator measurements (described in later sections). +| `application` +| Declared via annotations or programmatically registered by your service code. |==== -When you add metrics annotations to your service code, Helidon registers the resulting metrics as type `application`. - -=== Metric Registries -A _metric registry_ collects registered metrics of a given type. Helidon supports three registries, one for each of the three metrics types. +ifdef::mp-flavor[When you add metrics annotations to your service code, Helidon registers the resulting metrics in the `application` scope.] +ifdef::se-flavor[] +When an application creates a new {meter} it can specify which scope the {meter} belongs to. If the application does not specify a scope for a new {meter}, the default scope is `application`. +endif::se-flavor[] -When you add code to your service to create a metric programmatically, the code first locates the appropriate registry and then registers the metric with that registry. +// end::usage-body[] +// tag::usage-retrieving[] === Retrieving Metrics Reports from your Service -When you add the metrics dependency to your project, Helidon automatically provides a built-in REST endpoint `/metrics` which responds with a report of the registered metrics and their values. +When you add the +ifdef::mp-flavor[metrics dependency] +ifdef::se-flavor[`helidon-webserver-observe-metrics` dependency] +to your project, Helidon automatically provides a built-in REST endpoint `{metrics-endpoint}` which responds with a report of the registered {metrics} and their values. Clients can request a particular output format. -.Formats for `/metrics` output +.Formats for `{metrics-endpoint}` output [%autowidth] |==== | Format | Requested by @@ -90,18 +105,18 @@ Clients can request a particular output format. | JSON | Header `Accept: application/json` |==== -Clients can also limit the report by appending the metric type to the path: +Clients can also limit the report by specifying the scope as a query parameter in the request URL: -* `/metrics/base` -* `/metrics/vendor` -* `/metrics/application` +* `{metrics-endpoint}?scope=base` +* `{metrics-endpoint}?scope=vendor` +* `{metrics-endpoint}?scope=application` -Further, clients can narrow down to a specific metric name by adding the name as a subpath such as `/metrics/application/myCount`. +Further, clients can narrow down to a specific metric name by adding the name as another query parameter, such as `{metrics-endpoint}?scope=application&name=myCount`. -[source,bash] +[source,bash,subs="attributes+"] .Example Reporting: Prometheus format ---- -curl -s -H 'Accept: text/plain' -X GET http://localhost:8080/metrics/ +curl -s -H 'Accept: text/plain' -X GET http://localhost:8080{metrics-endpoint} ---- [listing] @@ -113,9 +128,9 @@ base:classloader_total_loaded_class_count 3157 .Example Reporting: JSON format -[source,bash] +[source,bash,subs="attributes+"] ---- -curl -s -H 'Accept: application/json' -X GET http://localhost:8080/metrics/ +curl -s -H 'Accept: application/json' -X GET http://localhost:8080{metrics-endpoint} ---- [listing] @@ -128,20 +143,20 @@ curl -s -H 'Accept: application/json' -X GET http://localhost:8080/metrics/ } ---- -In addition to your application metrics, the reports contain other -metrics of interest such as system and VM information. +In addition to your application {metrics}, the reports contain other +{metrics} of interest such as system and VM information. -// end::usage-body[] +// end::usage-retrieving[] // tag::metric-registry-api[] === The `MetricRegistry` API -To register or look up metrics programmatically, your service code uses one of the three link:{microprofile-metrics-javadoc-url}/org/eclipse/microprofile/metrics/MetricRegistry.html[`MetricRegistry`] instances (base, vendor, and application) which Helidon furnishes automatically. +To register or look up {metrics} programmatically, your service code uses the link:{microprofile-metrics-javadoc-url}/org/eclipse/microprofile/metrics/MetricRegistry.html[`MetricRegistry`] instance for the scope of interest: `base`, `vendor`, `application`, or a custom scope. ifdef::mp-flavor[] To get a `MetricRegistry` reference -* `@Inject` the metric registry you want, perhaps also using the link:{microprofile-metrics-javadoc-annotation-url}/RegistryType.html[`@RegistryType`] annotation to select the registry type, or -* Get a Helidon link:{metrics-javadoc-base-url}/RegistryFactory.html[`RegistryFactory`]; either +* `@Inject` the metric registry you want, perhaps also using the link:{microprofile-metrics-javadoc-annotation-url}/RegistryScope.html[`@RegistryScope`] annotation to select the registry type, or +* Get a Helidon link:{metrics-mp-javadoc-base-url}/RegistryFactory.html[`RegistryFactory`]; either + -- ** `@Inject` `RegistryFactory` or @@ -161,8 +176,8 @@ The `MetricRegistry` allows your code to register new metrics, look up previousl // tag::example-apps[] Helidon {flavor-uc} includes several prewritten example applications illustrating aspects of metrics: -* link:{helidon-github-tree-url}/examples/metrics/filtering/{flavor-lc}[Enabling/disabling metrics] using -ifdef::se-flavor[`MetricsSettings`] +* link:{helidon-github-tree-url}/examples/metrics/filtering/{flavor-lc}[Enabling/disabling {metrics}] using +ifdef::se-flavor[`MetricsObserver` and `MetricsConfig`] ifdef::mp-flavor[configuration] ifdef::se-flavor[] * link:{helidon-github-tree-url}/examples/metrics/kpi[Controlling key performance indicator metrics] using configuration and `KeyPerformanceIndicatorMetricsSettings`. diff --git a/docs/includes/metrics/prometheus-exemplar-support.adoc b/docs/includes/metrics/prometheus-exemplar-support.adoc index a948011f193..035beb3e62c 100644 --- a/docs/includes/metrics/prometheus-exemplar-support.adoc +++ b/docs/includes/metrics/prometheus-exemplar-support.adoc @@ -41,7 +41,7 @@ A value such as the total time consumed by a given REST endpoint which can be in Tracing, on the other hand, captures the usage of _multiple_ parts of your code as your service responds to a _single_ request. -Metrics and tracing come together in Helidon's support for examplars. +Metrics and tracing come together in Helidon's support for exemplars. [NOTE] -- diff --git a/docs/mp/guides/metrics.adoc b/docs/mp/guides/metrics.adoc index f4d8c50e42d..f8d842e63a9 100644 --- a/docs/mp/guides/metrics.adoc +++ b/docs/mp/guides/metrics.adoc @@ -23,6 +23,11 @@ :intro-project-name: MicroProfile (MP) include::{rootdir}/includes/mp.adoc[] +:metric: metric +:metrics: metrics +:metric_uc: Metric +:metrics_uc: Metrics +:metrics-endpoint: /metrics include::{rootdir}/includes/guides/metrics.adoc[tag=intro] include::{rootdir}/includes/guides/metrics.adoc[tag=create-sample-project] @@ -31,51 +36,45 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=build-and-run-intro] [source,text] -.Text response: ----- -# TYPE base_REST_request_total counter -# HELP base_REST_request_total The number of invocations and total response time of this RESTful resource method since the start of the server. The metric will not record the elapsed time nor count of a REST request if it resulted in an unmapped exception. Also tracks the highest recorded time duration within the previous completed full minute and lowest recorded time duration within the previous completed full minute. -base_REST_request_total{class="io.helidon.examples.quickstart.mp.GreetResource",method="getDefaultMessage"} 0 -# TYPE base_REST_request_elapsedTime_seconds gauge -base_REST_request_elapsedTime_seconds{class="io.helidon.examples.quickstart.mp.GreetResource",method="getDefaultMessage"} 0.0 -# TYPE base_REST_request_maxTimeDuration_seconds gauge -base_REST_request_maxTimeDuration_seconds{class="io.helidon.examples.quickstart.mp.GreetResource",method="getDefaultMessage"} NaN -# TYPE base_REST_request_minTimeDuration_seconds gauge -base_REST_request_minTimeDuration_seconds{class="io.helidon.examples.quickstart.mp.GreetResource",method="getDefaultMessage"} NaN -base_REST_request_total{class="io.helidon.examples.quickstart.mp.GreetResource",method="getMessage_java.lang.String"} 0 -base_REST_request_elapsedTime_seconds{class="io.helidon.examples.quickstart.mp.GreetResource",method="getMessage_java.lang.String"} 0.0 -base_REST_request_maxTimeDuration_seconds{class="io.helidon.examples.quickstart.mp.GreetResource",method="getMessage_java.lang.String"} NaN -base_REST_request_minTimeDuration_seconds{class="io.helidon.examples.quickstart.mp.GreetResource",method="getMessage_java.lang.String"} NaN - -# TYPE base_REST_request_unmappedException_total counter -# HELP base_REST_request_unmappedException_total The total number of unmapped exceptions that occur from this RESTful resouce method since the start of the server. -base_REST_request_unmappedException_total{class="io.helidon.examples.quickstart.mp.GreetResource",method="getDefaultMessage"} 0 -base_REST_request_unmappedException_total{class="io.helidon.examples.quickstart.mp.GreetResource",method="getMessage_java.lang.String"} 0 - -include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-prometheus-output] +.Text response: (partial) +---- +# HELP classloader_loadedClasses_total Displays the total number of classes that have been loaded since the Java virtual machine has started execution. +# TYPE classloader_loadedClasses_total counter +classloader_loadedClasses_total{mp_scope="base",} 8146.0 +# HELP requests_count_total Each request (regardless of HTTP method) will increase this counter +# TYPE requests_count_total counter +requests_count_total{mp_scope="vendor",} 1.0 +# HELP jvm_uptime_seconds Displays the start time of the Java virtual machine in milliseconds. This attribute displays the approximate time when the Java virtual machine started. +# TYPE jvm_uptime_seconds gauge +jvm_uptime_seconds{mp_scope="base",} 7377.0 ---- include::{rootdir}/includes/guides/metrics.adoc[tag=curl-metrics-json] [source,json] -.JSON response: +.JSON response (partial): ---- { - "base": { - "REST.request": - { - "count;class=io.helidon.examples.quickstart.mp.GreetResource;method=getDefaultMessage": 0, - "elapsedTime;class=io.helidon.examples.quickstart.mp.GreetResource;method=getDefaultMessage": 0, - "maxTimeDuration;class=io.helidon.examples.quickstart.mp.GreetResource;method=getDefaultMessage": null, - "minTimeDuration;class=io.helidon.examples.quickstart.mp.GreetResource;method=getDefaultMessage": null, - "count;class=io.helidon.examples.quickstart.mp.GreetResource;method=getMessage_java.lang.String": 0, - "elapsedTime;class=io.helidon.examples.quickstart.mp.GreetResource;method=getMessage_java.lang.String": 0, - "maxTimeDuration;class=io.helidon.examples.quickstart.mp.GreetResource;method=getMessage_java.lang.String": null, - "minTimeDuration;class=io.helidon.examples.quickstart.mp.GreetResource;method=getMessage_java.lang.String": null, - }, -include::{rootdir}/includes/guides/metrics.adoc[tag=base-metrics-json-output] + "application": { + "personalizedGets": 0, + "allGets": { + "count": 0, + "elapsedTime": 0, + "max": 0, + "mean": 0 + } + }, + "vendor": { + "requests.count": 2 }, -include::{rootdir}/includes/guides/metrics.adoc[tag=vendor-metrics-json-output] + "base": { + "gc.total;name=G1 Concurrent GC": 2, + "cpu.systemLoadAverage": 10.3388671875, + "classloader.loadedClasses.count": 8224, + "thread.count": 19, + "classloader.unloadedClasses.total": 0, + "jvm.uptime": 368224 + } } ---- include::{rootdir}/includes/guides/metrics.adoc[tag=get-single-metric] @@ -93,9 +92,9 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=KPI] [[controlling-rest-request-metrics]] ==== Controlling `REST.request` Metrics -Helidon implements the optional family of metrics, all with the name `REST.request`, as described in the +Helidon MP implements the optional family of metrics, all with the name `REST.request`, as described in the link:{microprofile-metrics-spec-url}#_optional_rest[MicroProfile Metrics specification]. -Each instance is a `SimpleTimer` with tags `class` and `method` identifying exactly which REST endpoint Java +Each instance is a `Timer` with tags `class` and `method` identifying exactly which REST endpoint Java method that instance measures. By default, Helidon MP does _not_ enable this feature. @@ -105,9 +104,6 @@ Note that the applications you generate using the full Helidon archetype _do_ en generated config file. You can see the results in the sample output shown in earlier example runs. -include::{rootdir}/includes/guides/metrics.adoc[tags=controlling-by-component;controlling-by-registry] - -include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-metadata] === Application-Specific Metrics Data @@ -121,12 +117,10 @@ Each metric annotation has mandatory and optional fields. The name field, for ex ==== Method Level Metrics -There are four metrics that you can use by annotating a method: +There are two metrics that you can use by annotating a method: 1. `@Counted` - Register a `Counter` metric 2. `@Timed` - Register a `Timer` metric -3. `@Metered` - Register a `Meter` metric -4. `@SimplyTimed` - Register a `SimpleTimer` metric The following example will demonstrate how to use the `@Counted` annotation to track the number of times the `/cards` endpoint is called. @@ -174,21 +168,16 @@ invocation. <3> The annotation `@Counted` will register a `Counter` metric for this method, creating it if needed. The counter is incremented each time the anyCards method is called. The `name` attribute is optional. -NOTE: For Metrics 1.1, you must set `monotonic` field to `true` to force the count to increment when entering the method. -The default behavior is to decrement when exiting the method. Here is an example: -`@Counted(name = "any-card", monotonic = true)`. - - -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the application endpoints below: ---- curl http://localhost:8080/cards curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- [source,json] -.JSON response: +.JSON response (partial): ---- { "io.helidon.examples.quickstart.mp.GreetingCards.any-card":2 // <1> @@ -201,16 +190,14 @@ You must use `absolute=false` for class-level annotations. ==== Additional Method Level Metrics -The `@Timed`, `@Metered`, and `@SimplyTimed` annotations can also be used with a method. For the following example. -you can just annotate the same method with `@Metered` and `@Timed`. These metrics collect significant +The `@Timed` annotation can also be used with a method. For the following example. +you can just annotate the same method with `@Timed`. These metrics collect significant information about the measured methods, but at a cost of some overhead and more complicated output. -Use `@SimplyTimed` in cases where capturing the invocation count and the total elapsed time -spent in a block of code is sufficient. Note that when using multiple annotations on a method, you *must* give the metrics different names as shown below. [source,java] -.Update the `GreetingCards` class with the following code: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.mp; @@ -225,7 +212,6 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.metrics.MetricUnits; import org.eclipse.microprofile.metrics.annotation.Counted; -import org.eclipse.microprofile.metrics.annotation.Metered; import org.eclipse.microprofile.metrics.annotation.Timed; @Path("/cards") @@ -237,8 +223,7 @@ public class GreetingCards { @GET @Produces(MediaType.APPLICATION_JSON) @Counted(name = "cardCount", absolute = true) //<1> - @Metered(name = "cardMeter", absolute = true, unit = MetricUnits.MILLISECONDS) //<2> - @Timed(name = "cardTimer", absolute = true, unit = MetricUnits.MILLISECONDS) //<3> + @Timed(name = "cardTimer", absolute = true, unit = MetricUnits.MILLISECONDS) //<2> public JsonObject anyCard() { return createResponse("Here are some random cards ..."); } @@ -250,53 +235,36 @@ public class GreetingCards { ---- <1> Specify a custom name for the `Counter` metric and set `absolute=true` to remove the path prefix from the name. -<2> Add the `@Metered` annotation to get a `Meter` metric. -<3> Add the `@Timed` annotation to get a `Timer` metric. +<2> Add the `@Timed` annotation to get a `Timer` metric. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the application endpoints below: ---- curl http://localhost:8080/cards curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- [source,json] -.JSON response: +.JSON response (partial): ---- { - "cardCount": 2, - "cardMeter": { // <1> + "cardTimer": { "count": 2, - "meanRate": 0.15653506570241812, - "oneMinRate": 0, - "fiveMinRate": 0, - "fifteenMinRate": 0 - }, - "cardTimer": { // <2> - "count": 2, - "elapsedTime": 2, - "meanRate": 0.15651866263362785, - "oneMinRate": 0, - "fiveMinRate": 0, - "fifteenMinRate": 0, - "min": 0, - "max": 2, - "mean": 1.0506565, - "stddev": 1.0405735, - "p50": 2.09123, - "p75": 2.09123, - "p95": 2.09123, - "p98": 2.09123, - "p99": 2.09123, - "p999": 2.09123 + "max": 0.002921992, + "mean": 0.0014682555, + "elapsedTime": 0.002936511, + "p0.5": 1.4336e-05, + "p0.75": 0.003014144, + "p0.95": 0.003014144, + "p0.98": 0.003014144, + "p0.99": 0.003014144, + "p0.999": 0.003014144 } + "cardCount": 2 } ---- -<1> The `Meter` metric includes the count field (it is a superset of `Counter`). -<2> The `Timer` metric includes the `Meter` fields (it is a superset of `Meter`). - ==== Reusing Metrics @@ -304,7 +272,7 @@ You can share a metric across multiple endpoints simply by specifying the same m demonstrated below. [source,java] -.Update the `GreetingCards` class with the following code: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.mp; @@ -357,21 +325,21 @@ public class GreetingCards { <2> The `/wedding` endpoint uses the same `Counter` metric, named `specialEventCard`. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the following endpoints: ---- curl http://localhost:8080/cards/wedding curl http://localhost:8080/cards/birthday curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- [source,json] -.JSON response from `/metrics/application`: +.JSON response (partial)`: ---- { -"anyCard": 1, -"specialEventCard": 2 // <1> + "anyCard": 1, + "specialEventCard": 2 // <1> } ---- <1> Notice that `specialEventCard` count is two, since you accessed `/cards/wedding` and `/cards/birthday`. @@ -383,7 +351,7 @@ The following example introduces a metric to count all card queries. In the fol needed to aggregate the counts, but they are left in the example to demonstrate the combined output of all three metrics. [source,java] -.Update the `GreetingCards` class with the following code: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.mp; @@ -429,16 +397,16 @@ public class GreetingCards { <2> Use `absolute=true` to remove path prefix for method-level annotations. <3> Add a method with a `Counter` metric to get birthday cards. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the following endpoints: ---- curl http://localhost:8080/cards curl http://localhost:8080/cards/birthday -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- [source,json] -.JSON response from `/metrics/application`: +.JSON response (partial): ---- { "anyCard": 1, @@ -453,12 +421,12 @@ fully qualified. ==== Field Level Metrics Field level metrics can be injected into managed objects, but they need to be updated by the application code. -This annotation can be used on fields of type `Meter`, `Timer`, `Counter`, and `Histogram`. +This annotation can be used on fields of type `Timer`, `Counter`, and `Histogram`. The following example shows how to use a field-level `Counter` metric to track cache hits. [source,java] -.Update the `GreetingCards` class with the following code: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.mp; @@ -521,7 +489,7 @@ public class GreetingCards { <3> Call `updateStats()` to update the cache hits. <4> Randomly increment the `cacheHits` counter. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the following endpoints: ---- curl http://localhost:8080/cards @@ -529,11 +497,11 @@ curl http://localhost:8080/cards curl http://localhost:8080/cards/birthday curl http://localhost:8080/cards/birthday curl http://localhost:8080/cards/birthday -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- [source,json] -.JSON response from `/metrics/application`: +.JSON response (partial): ---- { "anyCard": 2, @@ -546,13 +514,8 @@ curl -H "Accept: application/json" http://localhost:8080/metrics/application ==== Gauge Metric -The metrics you have tested so far are updated in response to an application REST request, i.e GET `/cards`. These -metrics can be declared in a request scoped class and Helidon will store the metric in the `MetricRegistry`, so the value persists -across requests. When GET `/metrics/application` is invoked, Helidon will return the current value of the metric stored in the `MetricRegistry`. -The `Gauge` metric is different from all the other metrics. The application must provide a getter to return the gauge value in an -application scoped class. When GET `/metrics/application` is invoked, Helidon will call the `Gauge` getter, store that value -in the `MetricsRegistry`, and return it as part of the metrics response payload. So, the `Gauge` metric value is updated real-time, in response to the -get metrics request. +The `Gauge` {metric} measures a value that is maintained by code outside the metrics subsystem. As with other {metrics}, the application explicitly registers a gauge. When the `{metrics-endpoint}` endpoint +is invoked, Helidon retrieves the value of each registered `Gauge`. The following example demonstrates how to use a `Gauge` to track application up-time. diff --git a/docs/mp/metrics/metrics.adoc b/docs/mp/metrics/metrics.adoc index b37aca89df2..a3a693208d6 100644 --- a/docs/mp/metrics/metrics.adoc +++ b/docs/mp/metrics/metrics.adoc @@ -26,6 +26,11 @@ :rootdir: {docdir}/../.. include::{rootdir}/includes/mp.adoc[] +:metric: metric +:metrics: metrics +:metric_uc: Metric +:metrics_uc: Metrics +:metrics-endpoint: /metrics == Contents @@ -54,13 +59,16 @@ include::{rootdir}/includes/dependencies.adoc[] Adding this dependency packages the full-featured metrics implementation with your service. -=== Other packaging options -Helidon gives you flexibility in how you make metrics available to your service. xref:{rootdir}/mp/metrics/metrics-capable-components.adoc[This document] explains your options. - == Usage include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=usage-body] +=== {Metric_uc} Registries +A _{metric} registry_ collects registered {metrics} of a given scope. Helidon supports one metrics registry for each scope. + +When you add code to your service to create a metric programmatically, the code first locates the appropriate registry and then registers the metric with that registry. + +include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=usage-retrieving] == API @@ -80,18 +88,9 @@ The MicroProfile Metrics specification describes several metric types you can cr | link:{microprofile-metrics-javadoc-annotation-url}/Counted.html[`@Counted`] | Monotonically increasing count of events. -| link:{microprofile-metrics-javadoc-annotation-url}ConcurrentGauge.html[`@ConcurrentGauge`] -| Increasing and decreasing measurement of currently-executing blocks of code. - | link:{microprofile-metrics-javadoc-annotation-url}/Gauge.html[`@Gauge`] | Access to a value managed by other code in the service. -| link:{microprofile-metrics-javadoc-annotation-url}/Metered.html[`@Metered`] -| Count of invocations and how frequently invocations have occurred. - -| link:{microprofile-metrics-javadoc-annotation-url}/SimplyTimed.html[`@SimplyTimed`] -| Count of invocations and the total duration consumed by those invocations. - | link:{microprofile-metrics-javadoc-annotation-url}/Timed.html[`@Timed`] | Frequency of invocations and the distribution of how long the invocations take. @@ -190,7 +189,7 @@ java -jar target/helidon-quickstart-mp.jar ---- curl http://localhost:8080/cards curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080/metrics?scope=application' ---- // end::access-cards-example[] @@ -200,21 +199,26 @@ curl -H "Accept: application/json" http://localhost:8080/metrics/application .JSON response: ---- { - "io.helidon.examples.quickstart.mp.GreetingCards.any-card":2 // <1> + "io.helidon.examples.quickstart.mp.GreetingCards.any-card": 2, // <1> + "personalizedGets": 0, + "allGets": { + "count": 0, + "elapsedTime": 0, + "max": 0, + "mean": 0 + } } ---- -<1> The any-card count is two, since you invoked the endpoint twice. +<1> The any-card count is two, since you invoked the endpoint twice. The other metrics are from the `SimpleGreetResource` class. NOTE: Notice the counter name is fully qualified with the class and method names. You can remove the prefix by using the `absolute=true` field in the `@Counted` annotation. You must use `absolute=false` (the default) for class-level annotations. ==== Additional Method-level Metrics -The `@ConcurrentGauge`, @Timed`, `@Metered`, and `@SimplyTimed` annotations can also be used with a method. For the following example. -you can just annotate the same method with `@Metered` and `@Timed`. These metrics collect significant +You can also use the @Timed` annotation with a method. For the following example. +you can just annotate the same method with `@Timed`. Timers significant information about the measured methods, but at a cost of some overhead and more complicated output. -Use `@SimplyTimed` in cases where capturing the invocation count and the total elapsed time -spent in a block of code is sufficient. Note that when using multiple annotations on a method, you *must* give the metrics different names as shown below (although they do not have to be absolute). @@ -245,9 +249,8 @@ public class GreetingCards { @GET @Produces(MediaType.APPLICATION_JSON) - @Counted(name = "cardCount", absolute = true) //<1> - @Metered(name = "cardMeter", absolute = true, unit = MetricUnits.MILLISECONDS) //<2> - @Timed(name = "cardTimer", absolute = true, unit = MetricUnits.MILLISECONDS) //<3> + @Counted(name = "cardCount", absolute = true) // <1> + @Timed(name = "cardTimer", absolute = true, unit = MetricUnits.MILLISECONDS) // <2> public JsonObject anyCard() { return createResponse("Here are some random cards ..."); } @@ -259,8 +262,7 @@ public class GreetingCards { ---- <1> Specify a custom name for the `Counter` metric and set `absolute=true` to remove the path prefix from the name. -<2> Add the `@Metered` annotation to get a `Meter` metric. -<3> Add the `@Timed` annotation to get a `Timer` metric. +<2>Add the `@Timed` annotation to get a `Timer` metric. include::metrics.adoc[tag=build-and-access-cards-example] @@ -268,41 +270,26 @@ include::metrics.adoc[tag=build-and-access-cards-example] .JSON response: ---- { - "cardCount": 2, - "cardMeter": { // <1> + "cardTimer": { "count": 2, - "meanRate": 0.15653506570241812, - "oneMinRate": 0, - "fiveMinRate": 0, - "fifteenMinRate": 0 + "elapsedTime": 0.002941925, + "max": 0.002919973, + "mean": 0.0014709625 }, - "cardTimer": { // <2> - "count": 2, - "elapsedTime": 2, - "meanRate": 0.15651866263362785, - "oneMinRate": 0, - "fiveMinRate": 0, - "fifteenMinRate": 0, - "min": 0, - "max": 2, - "mean": 1.0506565, - "stddev": 1.0405735, - "p50": 2.09123, - "p75": 2.09123, - "p95": 2.09123, - "p98": 2.09123, - "p99": 2.09123, - "p999": 2.09123 - } + "personalizedGets": 0, + "allGets": { + "count": 0, + "elapsedTime": 0, + "max": 0, + "mean": 0 + }, + "cardCount": 2 } ---- -<1> The `Meter` metric includes the count field (it is a superset of `Counter`). -<2> The `Timer` metric includes the `Meter` fields (it is a superset of `Meter`). - ==== Class-level Metrics -You can collect metrics at the class-level to aggregate data from all methods in that class using the same metric. +You can collect metrics at the class level to aggregate data from all methods in that class using the same metric. The following example introduces a metric to count all card queries. In the following example, the method-level metrics are not needed to aggregate the counts, but they are left in the example to demonstrate the combined output of all three metrics. @@ -349,7 +336,7 @@ public class GreetingCards { } } ---- -<1> This class is annotated with `@Counted`, which aggregates count data from all the method that have a `Count` annotation. +<1> This class is now annotated with `@Counted`, which aggregates count data from all the method that have a `Count` annotation. <2> Use `absolute=true` to remove path prefix for method-level annotations. <3> Add a method with a `Counter` metric to get birthday cards. @@ -359,26 +346,34 @@ include::metrics.adoc[tag=build-cards-example] ---- curl http://localhost:8080/cards curl http://localhost:8080/cards/birthday -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080/metrics?scope=application' ---- [source,json] -.JSON response from `/metrics/application`: +.JSON response from `/metrics?scope=application`: ---- { - "anyCard": 1, "birthdayCard": 1, - "io.helidon.examples.quickstart.mp.totalCards.GreetingCards": 2 // <1> + "personalizedGets": 0, + "allGets": { + "count": 0, + "elapsedTime": 0, + "max": 0, + "mean": 0 + }, + "anyCard": 1, + "io.helidon.examples.quickstart.mp.totalCards.GreetingCards": 2 // <1> } + ---- -<1> The `totalCards` count is a total of all the method-level `Counter` metrics. Class level metric names are always +<1> The `totalCards.GreetingCards` count is a total of all the method-level `Counter` metrics. Class level metric names are always fully qualified. ==== Field Level Metrics Field level metrics can be injected into managed objects, but they need to be updated by the application code. -This annotation can be used on fields of type `Meter`, `Timer`, `Counter`, and `Histogram`. +This annotation can be used on fields of type `Timer`, `Counter`, and `Histogram`. The following example shows how to use a field-level `Counter` metric to track cache hits. @@ -454,16 +449,23 @@ curl http://localhost:8080/cards curl http://localhost:8080/cards/birthday curl http://localhost:8080/cards/birthday curl http://localhost:8080/cards/birthday -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080/metrics?scope=application' ---- [source,json] .JSON response from `/metrics/application`: ---- { - "anyCard": 2, "birthdayCard": 3, - "cacheHits": 2, // <1> + "personalizedGets": 0, + "allGets": { + "count": 0, + "elapsedTime": 0, + "max": 0, + "mean": 0 + }, + "anyCard": 2, + "cacheHits": 2, // <1> "io.helidon.examples.quickstart.mp.totalCards.GreetingCards": 5 } ---- @@ -473,11 +475,10 @@ curl -H "Accept: application/json" http://localhost:8080/metrics/application The metrics you have tested so far are updated in response to an application REST request, i.e GET `/cards`. These metrics can be declared in a request scoped class and Helidon will store the metric in the `MetricRegistry`, so the value persists -across requests. When GET `/metrics/application` is invoked, Helidon will return the current value of the metric stored in the `MetricRegistry`. -The `Gauge` metric is different from all the other metrics. The application must provide a getter to return the gauge value in an -application scoped class. When GET `/metrics/application` is invoked, Helidon will call the `Gauge` getter, store that value -in the `MetricsRegistry`, and return it as part of the metrics response payload. So, the `Gauge` metric value is updated real-time, in response to the -get metrics request. +across requests. When GET `/metrics?scope=application` is invoked, Helidon will return the current value of the metric stored in the `MetricRegistry`. + +The `Gauge` annotation is different from the other metric annotations. The application must provide a method to return the gauge value in an +application-scoped class. When GET `/metrics?scope=application` is invoked, Helidon will call the `Gauge` method, using the returned value as the value of the gauge as part of the metrics response. The following example demonstrates how to use a `Gauge` to track application up-time. @@ -508,7 +509,7 @@ public class GreetingCardsAppMetrics { } } ---- -<1> This managed object must be application scoped to properly register and use the `Gauge` metric. +<1> This managed object must be application scoped to properly register and use the annotated `Gauge` metric. <2> Declare an `AtomicLong` field to hold the start time of the application. <3> Initialize the application start time. <4> Return the application `appUpTimeSeconds` metric, which will be included in the application metrics. @@ -552,18 +553,26 @@ public class GreetingCards { [source,bash] .Build and run the application, then invoke the application metrics endpoint: ---- -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080/metrics?scope=application' ---- [source,json] .JSON response from `/metrics/application`: ---- { - "cardCount": 0, - "io.helidon.examples.quickstart.mp.GreetingCardsAppMetrics.appUpTimeSeconds": 6 // <1> + "personalizedGets": 0, + "allGets": { + "count": 0, + "elapsedTime": 0, + "max": 0, + "mean": 0 + }, + "io.helidon.examples.quickstart.mp.GreetingCardsAppMetrics.appUpTimeSeconds": 23, // <1> + "cardCount": 0 } + ---- -<1> The application has been running for 6 seconds. +<1> The application has been running for 23 seconds. // Config examples include::{rootdir}/includes/metrics/metrics-config.adoc[tag=config-examples] diff --git a/docs/se/guides/metrics.adoc b/docs/se/guides/metrics.adoc index 093e52963c9..33f83dc8a65 100644 --- a/docs/se/guides/metrics.adoc +++ b/docs/se/guides/metrics.adoc @@ -23,6 +23,15 @@ :rootdir: {docdir}/../.. include::{rootdir}/includes/se.adoc[] +:metric: meter +:metrics: meters +:metric_uc: Meter +:metrics_uc: Meters +:meter: meter +:meters: meters +:meter_uc: Meter +:meters_uc: Meters +:metrics-endpoint: /observe/metrics include::{rootdir}/includes/guides/metrics.adoc[tag=intro] include::{rootdir}/includes/guides/metrics.adoc[tag=create-sample-project] @@ -31,23 +40,23 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=using-built-in-metrics-intro The generated source code is already configured for both metrics and health checks, but the following example removes health checks. [source,xml] -.Notice that the metrics dependency is already in the project's pom.xml file: -include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-dependency] - -[source,java] -.Replace the `Main.createRouting` method with the following code: +.Metrics dependencies in the generated `pom.xml`: ---- - private static Routing createRouting(Config config) { - - GreetService greetService = new GreetService(config); - - return Routing.builder() - .register(MetricsSupport.create()) // <1> - .register("/greet", greetService) - .build(); - } + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + + + io.helidon.metrics + helidon-metrics-system-meters + runtime + ---- -<1> Register the built-in base and vendor metrics. +<1> Includes the Helidon observability component for metrics and, as transitive dependencies, the Helidon neutral metrics API and a full-featured implementation of the API. +<2> Includes the built-in meters. + +With these dependencies in your project, Helidon's auto-discovery of webserver features automatically finds and runs the metrics subsystem. +You do not need to change any of the generated source code. include::{rootdir}/includes/guides/metrics.adoc[tag=build-and-run-intro] @@ -59,10 +68,10 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-prometheus-output] You can get the same data in JSON format. -[source,bash] +[source,bash,subs="attributes+"] .Verify the metrics endpoint with an HTTP accept header: ---- -curl -H "Accept: application/json" http://localhost:8080/metrics +curl -H "Accept: application/json" http://localhost:8080{metrics-endpoint} ---- [source,json] @@ -96,82 +105,30 @@ A Helidon SE application can disable metrics processing programmatically. [source,java] .Disable all metrics behavior ---- -import io.helidon.metrics.api.MetricsSettings; -import io.helidon.metrics.serviceapi.MetricsSupport; -import io.helidon.metrics.api.RegistryFactory; - - MetricsSettings metricsSettings = MetricsSettings.builder() - .enabled(false) - .build(); // <1> - - MetricsSupport metricsSupport = MetricsSupport.create(metricsSettings); // <2> - - RegistryFactory registryFactory = RegistryFactory.getInstance(metricsSettings); // <3> ----- -<1> Create a link:{metrics-javadoc-base-url}/io/helidon/metrics/api/MetricsSettings.html[`MetricsSettings]` instance (via its link:{metrics-javadoc-base-url}/io/helidon/metrics/api/MetricsSettings.Builder.html[`Builder`]) with the metrics subsystem disabled. -<2> Get a link:{metrics-serviceapi-javadoc-base-url}/io/helidon/metrics/serviceapi/MetricsSupport.html[`MetricsSupport]` service (usable in setting routing rules) that responds -to the `/metrics` endpoint with `404` and an explanatory message. -<3> Get a link:{metrics-javadoc-base-url}/io/helidon/metrics/api/RegistryFactory.html[`RegistryFactory]` instance that provides `MetricRegistry` instances which register - no-op metric objects (counters, timers, etc.). + ObserveFeature observe = ObserveFeature.builder() // <1> + .addObserver(metricsObserer.builder() // <2> + .enabled(false) // <3> + .build()) // <4> + .build(); // <5> + + WebServer server = WebServer.builder() // <6> + .config(Config.global().get("server")) + .addFeature(observe) + .routing(Main::routing) + .build() + .start(); +---- +<1> Begin preparing the `ObserveFeature`. +<2> Begin preparing the `MetricsObserver`. +<3> Disable metrics. +<4> Complete the `MetricsObserver`. +<5> Complete the `ObserveFeature`. +<6> Create and start the `WebServer` with the `ObserveFeature` (and other settings). These builders and interfaces also have methods which accept `Config` objects representing the `metrics` node from the application configuration. include::{rootdir}/includes/guides/metrics.adoc[tag=disabling-whole-summary] -include::{rootdir}/includes/guides/metrics.adoc[tag=controlling-by-component-intro] - -Your Helidon SE application can disable a metrics-capable component's use of metrics programmatically. - -[source,java] -.Disable metrics use in a metrics-capable component ----- -import io.helidon.metrics.api.ComponentMetricsSettings; - - ComponentMetricsSettings.Builder componentMetricsSettingsBuilder = ComponentMetricsSettings.builder() - .enabled(false); // <1> - - SomeService someService = SomeService.builder() - .componentMetricsSettings(componentMetricsSettingsBuilder) - .build(); // <2> - ----- -<1> Create a link:{metrics-javadoc-base-url}/io/helidon/metrics/api/ComponentMetricsSettings.html[`ComponentMetricsSettings]` instance (via its link:{metrics-javadoc-base-url}/io/helidon/metrics/api/ComponentMetricsSettings.Builder.html[`Builder`]) indicating that metrics usage should be disabled. -<2> Create an instance of the service with its metrics usage disabled. - -include::{rootdir}/includes/guides/metrics.adoc[tag=controlling-by-component-summary] - -include::{rootdir}/includes/guides/metrics.adoc[tag=controlling-by-registry-intro] - -Your Helidon SE application can control the collection and reporting of metrics programmatically as well by preparing these settings objects: - -* link:{metrics-javadoc-base-url}/io/helidon/metrics/api/RegistryFilterSettings.html[`RegistryFilterSettings`] -* link:{metrics-javadoc-base-url}/io/helidon/metrics/api/RegistrySettings.html[`RegistrySettings`] -* link:{metrics-javadoc-base-url}/io/helidon/metrics/api/MetricsSettings.html[`MetricsSettings`] - -and using the resulting `MetricsSettings` to retrieve a suitable link:{metrics-javadoc-base-url}/io/helidon/metrics/api/RegistryFactory.html[`RegistryFactory`]. -[source,java] -.Control metrics by registry type and name ----- -import io.helidon.metrics.api.RegistryFilterSettings; -import org.eclipse.microprofile.metrics.MetricRegistry; -... - RegistryFilterSettings appFilterSettings = RegistryFilterSettings.builder() // <1> - .include("myapp\..*\.updates") - .build(); - RegistrySettings registrySettings = RegistrySettings.builder() // <2> - .filterSettings(appFilterSettings) - .build(); - MetricsSettings metricsSettings = MetricsSettings.builder() // <3> - .registrySettings(MetricRegistry.Type.APPLICATION, appFilterSettings) - .build(); - RegistryFactory rf = RegistryFactory.getInstance(metricsSettings); // <4> - MetricRegistry registry = rf.getRegistry(MetricRegistry.Type.APPLICATION); // <5> ----- -<1> Create the registry filter settings to include only those metrics with names indicating updates. -<2> Create the registry settings with that filter. -<3> Create the metrics settings, associating the registry settings with the `APPLICATION` metric registry. -<4> Set the overall metrics settings and retrieve a registry factory suitably initialized. -<5> Obtain a reference to the `APPLICATION` registry which is set up to create and report on only metrics with names starting with `myapp.updates.`. include::{rootdir}/includes/guides/metrics.adoc[tag=KPI] @@ -179,123 +136,125 @@ Your Helidon SE application can also control the KPI settings programmatically. [source,java] .Assign KPI metrics behavior from code ---- -import io.helidon.metrics.api.KeyPerformanceIndicatorMetricsSettings; -import io.helidon.metrics.api.MetricsSettings; -import io.helidon.metrics.serviceapi.MetricsSupport; -import io.helidon.metrics.api.RegistryFactory; -... + KeyPerformanceIndicatorMetricsConfig kpiConfig = + KeyPerformanceIndicatorMetricsConfig.builder() // <1> + .extended(true) // <2> + .longRunningRequestThreshold(Duration.ofSeconds(4)) // <3> + .build(); - KeyPerformanceIndicatorMetricsSettings.Builder kpiSettingsBuilder = - KeyPerformanceIndicatorMetricsSettings.builder() - .extended(true) - .longRunningThresholdMs(2000); <1> + MetricsObserver metrics = MetricsObserver.builder() + .metricsConfig(MetricsConfig.builder() // <4> + .keyPerformanceIndicatorMetricsConfig(kpiConfig)) // <5> + .build(); - MetricsSettings metricsSettings = MetricsSettings.builder() - .keyPerformanceIndicatorSettings(kpiSettingsBuilder) - .build(); // <2> ----- -<1> Create a link:{metrics-javadoc-base-url}/io/helidon/metrics/api/KeyPerformanceIndicatorMetricsSettings.html[`KeyPerformanceIndicatorMetricsSettings]` instance (via its link:{metrics-javadoc-base-url}/io/helidon/metrics/api/KeyPerformanceIndicatorMetricsSettings.Builder.html[`Builder`]) with non-default values. -<2> Create a link:{metrics-javadoc-base-url}/io/helidon/metrics/api/MetricsSettings.html[`MetricsSettings]` instance reflecting the KPI settings. + ObserveFeature observe = ObserveFeature.builder() + .config(config.get("server.features.observe")) + .addObserver(metrics) // <6> + .build(); -// end of Controlling Metrics section + WebServer server = WebServer.builder() // <7> + .config(Config.global().get("server")) + .addFeature(observe) + .routing(Main::routing) + .build() + .start(); +---- +<1> Create a link:{metrics-javadoc-base-url}/io/helidon/metrics/api/KeyPerformanceIndicatorMetricsConfig.html[`KeyPerformanceIndicatorMetricsConfig]` instance (via its link:{metrics-javadoc-base-url}/io/helidon/metrics/api/KeyPerformanceIndicatorMetricsConfig.Builder.html[`Builder`]) with non-default values. +<2> Enabled extended KPI {metrics}. +<3> Set the long-running request threshold. +<4> Prepare the metrics observer's builder. +<5> Update the metrics observer's builder using the just-prepared KPI metrics config. +<6> Add the metrics observer to the `ObserveFeature`. +<7> Add the `ObserveFeature` to the `WebServer`. -include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-metadata] +// end of Controlling Metrics section === Application-Specific Metrics Data -This section demonstrates how to use application-specific metrics and integrate them with Helidon. -It is the application's responsibility to create and update the metrics at runtime. The application has -complete control over when and how each metric is used. For example, an application may use the -same counter for multiple methods, or one counter per method. Helidon maintains an application -`MetricRegistry` which is used to manage all of the application metrics. -Helidon returns these metrics in response to a `/metrics/application` REST request. +This section demonstrates how to use application-specific {metrics} and integrate them with Helidon, starting from a Helidon SE QuickStart application. + +It is the application's responsibility to create and update the {metrics} at runtime. The application has +complete control over when and how each {metric} is used. For example, an application may use the +same counter for multiple methods, or one counter per method. Helidon maintains a single meter registry which holds all {metrics}. -In all of these examples, the scope and lifetime of the metric is at the application-level. -Each metric, except `Gauge`, is updated in response to a REST request and the contents of the -metric is cumulative. +In all of these examples, the code uses a {metric} builder specific to the type of {metric} needed to register a new {metric} or locate a previous-registered {metric}. -==== Counter Metric +==== Counter {metric_uc} -The `Counter` metric is a monotonically increasing or decreasing number. The following example -will demonstrate how to use a `Counter` to track the number of times the `/cards` endpoint is called. +The `Counter` {metric} is a monotonically increasing number. The following example demonstrates how to use a `Counter` to track the number of times the `/cards` endpoint is called. [source,java] .Create a new class named `GreetingCards` with the following code: ---- package io.helidon.examples.quickstart.se; -import io.helidon.metrics.RegistryFactory; -import io.helidon.webserver.HttpRules; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; import java.util.Collections; + +import io.helidon.metrics.api.Counter; // <1> +import io.helidon.metrics.api.Metrics; +import io.helidon.webserver.http.HttpRules; +import io.helidon.webserver.http.HttpService; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; + import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; -import org.eclipse.microprofile.metrics.Counter; // <1> -import org.eclipse.microprofile.metrics.MetricRegistry; // <1> public class GreetingCards implements HttpService { - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - private final Counter cardCounter; // <2> + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - GreetingCards() { - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - cardCounter = appRegistry.counter("cardCount"); // <3> - } + private final Counter cardCounter; // <2> - @Override - public void routing(HttpRules rules) { - rules.get("/", this::getDefaultMessageHandler); - } + GreetingCards() { + cardCounter = Metrics.globalRegistry() + .getOrCreate(Counter.builder("cardCount") + .description("Counts card retrievals")); // <3> + } - private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { - cardCounter.inc(); // <4> - sendResponse(response, "Here are some cards ..."); - } + @Override + public void routing(HttpRules rules) { + rules.get("/", this::getDefaultMessageHandler); + } - private void sendResponse(ServerResponse response, String msg) { - JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); - response.send(returnObject); - } + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + cardCounter.increment(); // <4> + sendResponse(response, "Here are some cards ..."); + } + + private void sendResponse(ServerResponse response, String msg) { + JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); + response.send(returnObject); + } } ---- -<1> Import metrics classes. -<2> Declare a `Counter` member variable. -<3> Create and register the `Counter` metric in the `MetricRegistry`. This `Counter` will exist for the lifetime of +<1> Import metrics types. +<2> Declare a `Counter` member field. +<3> Create and register the `Counter` {metric} in the global meter registry`. This `Counter` will exist for the lifetime of the application. <4> Increment the count. [source,java] -.Update the `Main.createRouting` method as follows: +.Update the `Main.routing` method as follows: ---- - private static Routing createRouting(Config config) { - - MetricsSupport metrics = MetricsSupport.create(); - GreetService greetService = new GreetService(config); - HealthSupport health = HealthSupport.builder() - .add(HealthChecks.healthChecks()) // Adds a convenient set of checks - .build(); + static void routing(HttpRouting.Builder routing) { + Config config = Config.global(); - return Routing.builder() - .register(health) // Health at "/health" - .register(metrics) // Metrics at "/metrics" - .register("/greet", greetService) - .register("/cards", new GreetingCards()) // <1> - .build(); - } + routing + .register("/greet", new GreetService()) + .register("/cards", new GreetingCards()) // <1> + .get("/simple-greet", (req, res) -> res.send("Hello World!")); + } ---- -<1> Add the `GreetingCards` service to the `Routing.builder`. Helidon will route any REST requests with +<1> Add the `GreetingCards` service to the routing. Helidon routes any REST requests with the `/cards` root path to the `GreetingCards` service. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the endpoints below: ---- curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- [source,json] @@ -308,157 +267,73 @@ curl -H "Accept: application/json" http://localhost:8080/metrics/application <1> The count value is one since the method was called once. -==== Meter Metric +==== Timer {metric_uc} -The `Meter` metric is used to measure throughput, the number of times an event occurs within a certain time period. -When a `Meter` object is created, its internal clock starts running. That clock is used to calculate the various rates -stored this metric. The `Meter` also includes the `count` field from the `Counter` metric. When you mark an event, -the count is incremented. +The `Timer` {metric} aggregates durations. -The following example marks an event each time the `/cards` endpoint is called. +In the following example, +a `Timer` {metric} measures the duration of a method's execution. Whenever the REST `/cards` +endpoint is called, the code updates the `Timer` with additional timing information. [source,java] -.Update the `GreetingCards` class with the following code: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.se; -import io.helidon.webserver.HttpRules; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; import java.util.Collections; -import jakarta.json.Json; -import jakarta.json.JsonBuilderFactory; -import jakarta.json.JsonObject; -import org.eclipse.microprofile.metrics.Meter; // <1> -import org.eclipse.microprofile.metrics.MetricRegistry; // <1> - -public class GreetingCards implements HttpService { - - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - private final Meter cardMeter; // <2> - - GreetingCards() { - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - cardMeter = appRegistry.meter("cardMeter"); // <3> - } - - @Override - public void routing(HttpRules rules) { - rules.get("/", this::getDefaultMessageHandler); - } - - private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { - cardMeter.mark(); // <4> - sendResponse(response, "Here are some cards ..."); - } - - private void sendResponse(ServerResponse response, String msg) { - JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); - response.send(returnObject); - } -} ----- -<1> Import metrics classes. -<2> Declare a `Meter` member variable. -<3> Create and register the `Meter` metric in the `MetricRegistry`. -<4> Mark the occurrence of an event. - -TIP: Note: you can specify a count parameter such as `mark(100)` to mark multiple events. - -[source,bash] -.Build and run the application, then invoke the endpoints below: ----- -curl http://localhost:8080/cards -curl http://localhost:8080/cards -curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application ----- - -[source,json] -.JSON response: ----- -{ - "cardMeter": { // <1> - "count": 3, // <2> - "meanRate": 0.17566568722974535, - "oneMinRate": 0.04413761384322548, - "fiveMinRate": 0.009753212003766951, - "fifteenMinRate": 0.0033056752265846544 - } -} ----- -<1> The `Meter` metric has a set of fields to show various rates, along with the count. -<2> The `/cards` endpoint was called three times. +import io.helidon.metrics.api.Metrics; // <1> +import io.helidon.metrics.api.Timer; +import io.helidon.webserver.http.HttpRules; +import io.helidon.webserver.http.HttpService; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; -==== Timer Metric - -(See also <<#simple_timer_metric,Simple timer metric>>.) - -The `Timer` metric aggregates durations, provides timing statistics, and includes throughput statistics -using an internal `Meter` metric. The `Timer` measures duration in nanoseconds. In the following example, -a `Timer` metric is used to measure the duration of a method's execution. Whenever the REST `/cards` -endpoint is called, the `Timer` will be updated with additional timing information. - -[source,java] -.Update the `GreetingCards` class with the following code: ----- -package io.helidon.examples.quickstart.se; - -import io.helidon.metrics.RegistryFactory; -import io.helidon.webserver.Routing; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; -import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; -import org.eclipse.microprofile.metrics.MetricRegistry; // <1> -import org.eclipse.microprofile.metrics.Timer; // <1> public class GreetingCards implements HttpService { - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - private final Timer cardTimer; // <2> + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + private final Timer cardTimer; // <2> - GreetingCards() { - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - cardTimer = appRegistry.timer("cardTimer"); // <3> - } + GreetingCards() { + cardTimer = Metrics.globalRegistry() + .getOrCreate(Timer.builder("cardTimer") // <3> + .description("Times card retrievals")); + } - @Override - public void routing(HttpRules rules) { - rules.get("/", this::getDefaultMessageHandler); - } + @Override + public void routing(HttpRules rules) { + rules.get("/", this::getDefaultMessageHandler); + } - private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { - Timer.Context timerContext = cardTimer.time(); // <4> - sendResponse(response, "Here are some cards ..."); - response.whenSent().thenAccept(res -> timerContext.stop()); // <5> - } + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + Timer.Sample timerSample = Timer.start(); // <4> + sendResponse(response, "Here are some cards ..."); + response.whenSent(() -> timerSample.stop(cardTimer)); // <5> + } - private void sendResponse(ServerResponse response, String msg) { - JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); - response.send(returnObject); - } + private void sendResponse(ServerResponse response, String msg) { + JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); + response.send(returnObject); + } } ---- -<1> Import metrics classes. -<2> Declare a `Timer` member variable. -<3> Create and register the `Timer` metric in the `MetricRegistry`. -<4> Start the timer. -<5> Stop the timer. +<1> Import relevant metrics classes. +<2> Declare a `Timer` member field. +<3> Create and register the `Timer` metric in the global meter registry. +<4> Create a timer sample which, among other things, automatically records the starting time. +<5> Arrange for the timer sample to be stopped and applied to the `cardTimer` once Helidon sends the response to the client. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the endpoints below: ---- curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl http://localhost:8080/cards +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- @@ -467,99 +342,89 @@ curl -H "Accept: application/json" http://localhost:8080/metrics/application ---- { "cardTimer": { - "count": 1, // <1> - "elapsedTime": 26683406, // <2> - "meanRate": 0.05669258258076838, - "oneMinRate": 0, - "fiveMinRate": 0, - "fifteenMinRate": 0, - "min": 26683406, // <2> - "max": 26683406, - "mean": 26683406, - "stddev": 0, - "p50": 26683406, - "p75": 26683406, - "p95": 26683406, - "p98": 26683406, - "p99": 26683406, - "p999": 26683406 + "count": 2, + "max": 0.01439681, + "mean": 0.0073397075, + "elapsedTime": 0.014679415, + "p0.5": 0.000278528, + "p0.75": 0.01466368, + "p0.95": 0.01466368, + "p0.98": 0.01466368, + "p0.99": 0.01466368, + "p0.999": 0.01466368 } } - ---- -<1> The first several fields (except for `elapsedTime`) are the same ones used by `Meter`. -<2> The `elapsedTime` field and the rest starting with `min` are the `Timer` fields that measure the duration of the `getDefaultMessageHandler` method. Some of these values -will change each time you invoke the `/cards` endpoint. +Helidon updated the timer statistics for each of the two accesses to the `/cards` endpoint. -==== Histogram Metric +==== Distribution Summary {metrics_uc} -The `Histogram` metric calculates the distribution of a set of values within ranges. This metric does -not relate to time at all. The following example will record a set of random numbers in a `Histogram` metric when +The `DistributionSummary` {metric} calculates the distribution of a set of values within ranges. This {metric} does +not relate to time at all. The following example records a set of random numbers in a `DistributionSummary` {metric} when the `/cards` endpoint is invoked. [source,java] -.Update the `GreetingCards` class with the following code: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.se; -import io.helidon.metrics.RegistryFactory; -import io.helidon.webserver.HttpRules; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; import java.util.Collections; import java.util.Random; + +import io.helidon.metrics.api.DistributionSummary; // <1> +import io.helidon.metrics.api.Metrics; +import io.helidon.webserver.http.HttpRules; +import io.helidon.webserver.http.HttpService; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; + import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; -import org.eclipse.microprofile.metrics.Histogram; // <1> -import org.eclipse.microprofile.metrics.MetricRegistry; // <1> public class GreetingCards implements HttpService { - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - private final Histogram cardHistogram; // <2> + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + private final DistributionSummary cardSummary; // <2> - GreetingCards() { - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - cardHistogram = appRegistry.histogram("cardHistogram"); // <3> - } + GreetingCards() { + cardSummary = Metrics.globalRegistry() + .getOrCreate(DistributionSummary.builder("cardDist") + .description("random card distribution")); // <3> + } - @Override - public void routing(HttpRules rules) { - rules.get("/", this::getDefaultMessageHandler); - } + @Override + public void routing(HttpRules rules) { + rules.get("/", this::getDefaultMessageHandler); + } - private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + Random r = new Random(); // <4> + for (int i = 0; i < 1000; i++) { + cardSummary.record(1 + r.nextDouble()); + } + sendResponse(response, "Here are some cards ..."); - Random r = new Random(); - for (int i = 0; i < 1000; i++) { // <4> - cardHistogram.update(1 + r.nextInt(25)); // <5> } - sendResponse(response, "Here are some cards ..."); - } - private void sendResponse(ServerResponse response, String msg) { - JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); - response.send(returnObject); - } + private void sendResponse(ServerResponse response, String msg) { + JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); + response.send(returnObject); + } } - ---- -<1> Import metrics classes. -<2> Declare a `Histogram` member variable. -<3> Create and register the `Histogram` metric in the `MetricRegistry`. -<4> Update the `Histogram` metric with a random number. -<5> Loop, loading the histogram with numbers. +<1> Import relevant metrics classes. +<2> Declare a `DistributionSummary` member field. +<3> Create and register the `DistributionSummary` {metric} in the global meter registry +<4> Update the distribution summary with a random number multiple times for each request. -[source,bash] +[source,bash,subs="attributes+"] .Build and run the application, then invoke the endpoints below: ---- curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application' ---- @@ -567,234 +432,107 @@ curl -H "Accept: application/json" http://localhost:8080/metrics/application .JSON response: ---- { - "cardHistogram": { //<1> + "cardDist": { "count": 1000, - "min": 1, - "max": 25, - "mean": 12.743999999999915, - "stddev": 7.308793607702962, - "p50": 13.0, - "p75": 19.0, - "p95": 24.0, - "p98": 25.0, - "p99": 25.0, - "p999": 25.0 + "max": 1.999805150914427, + "mean": 1.4971440362723523, + "total": 1497.1440362723522, + "p0.5": 1.4375, + "p0.75": 1.6875, + "p0.95": 1.9375, + "p0.98": 1.9375, + "p0.99": 1.9375, + "p0.999": 1.9375 } } ---- -<1> This is the histogram data. Some of these values will change each time you invoke the `/cards` endpoint. - +The `DistributionSummary.Builder` allows your code to configure other aspects of the summary, such as bucket boundaries and percentiles to track. ==== Gauge Metric -The `Gauge` metric measures a discreet value at a point in time, such as a temperature. The metric is not normally -tied to a REST endpoint, rather it should be registered during application startup. When the `/metrics/application` endpoint -is invoked, Helidon will call the `getValue` method of each registered `Gauge`. The following example demonstrates +The `Gauge` {metric} measures a value that is maintained by code outside the metrics subsystem. As with other {metrics}, the application explicitly registers a gauge. When the `{metrics-endpoint}` endpoint +is invoked, Helidon retrieves the value of each registered `Gauge`. The following example demonstrates how a `Gauge` is used to get the current temperature. [source,java] -.Add new imports to `Main.java` and replace the `Main.createRouting` method with the following code: ----- - -import io.helidon.metrics.RegistryFactory; -import org.eclipse.microprofile.metrics.Gauge; -import org.eclipse.microprofile.metrics.MetricRegistry; - -... - - private static Routing createRouting(Config config) { - - MetricsSupport metrics = MetricsSupport.create(); - - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - appRegistry.register("temperature", (Gauge)() -> new Random().nextInt(100)); //<1> - - GreetService greetService = new GreetService(config); - return Routing.builder() - .register(JsonSupport.create()) - .register(metrics) // Metrics at "/metrics" - .register("/greet", greetService) - .register("/cards", new GreetingCards()) - .build(); - } ----- -<1> Register the `Gauge`, providing a lambda function that will return a random temperature. - -[source,java] -.Update the `GreetingCards` class with the following code to use the `Counter` metric which will simplify the JSON output: +.Replace the `GreetingCards` class with the following code: ---- package io.helidon.examples.quickstart.se; -import io.helidon.metrics.RegistryFactory; -import io.helidon.webserver.HttpRules; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; import java.util.Collections; -import jakarta.json.Json; -import jakarta.json.JsonBuilderFactory; -import jakarta.json.JsonObject; -import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.MetricRegistry; - -public class GreetingCards implements HttpService { - - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - private final Counter cardCounter; - - GreetingCards() { - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - cardCounter = appRegistry.counter("cardCount"); - } - - @Override - public void routing(HttpRules rules) { - rules.get("/", this::getDefaultMessageHandler); - } - - private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { - cardCounter.inc(); // <4> - sendResponse(response, "Here are some cards ..."); - } - - private void sendResponse(ServerResponse response, String msg) { - JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); - response.send(returnObject); - } -} ----- - -[source,bash] -.Build and run the application, then invoke the endpoints below: ----- -curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application ----- - -[source,json] -.JSON response from `/metrics/application`: ----- -{ - "cardCount": 1, - "temperature": 11 // <1> -} ----- -<1> The current temperature is returned. Invoke the `/metrics/application` endpoint again and you should get a different value. - -[[simple_timer_metric]] -==== Simple Timer Metric - -The `SimpleTimer` metric counts invocations and accumulates duration (in seconds). -It also records the minimum and maximum values sampled during the previous complete minute. -In the following example, -a `SimpleTimer` metric is used to count and measure the duration of a method's execution. Whenever the REST `/cards` -endpoint is called, the `SimpleTimer` updates its count and total elapsed time. +import java.util.Random; -[source,java] -.Update the `GreetingCards` class with the following code: ----- -package io.helidon.examples.quickstart.se; +import io.helidon.metrics.api.Gauge; +import io.helidon.metrics.api.Metrics; +import io.helidon.webserver.http.HttpRules; +import io.helidon.webserver.http.HttpService; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; -import io.helidon.metrics.RegistryFactory; -import io.helidon.webserver.HttpRules; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; -import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; -import org.eclipse.microprofile.metrics.MetricRegistry; // <1> -import org.eclipse.microprofile.metrics.SimpleTimer; // public class GreetingCards implements HttpService { - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - private final SimpleTimer cardTimer; // <2> + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - GreetingCards() { - RegistryFactory metricsRegistry = RegistryFactory.getInstance(); - MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION); - cardTimer = appRegistry.simpleTimer("cardSimpleTimer"); // <3> - } + GreetingCards() { + Random r = new Random(); + Metrics.globalRegistry() + .getOrCreate(Gauge.builder("temperature", + () -> r.nextDouble(100.0)) + .description("Ambient temperature")); // <1> + } - @Override - public void routing(HttpRules rules) { - rules.get("/", this::getDefaultMessageHandler); - } + @Override + public void routing(HttpRules rules) { + rules.get("/", this::getDefaultMessageHandler); + } - private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { - cardTimer.time(() -> sendResponse(response, "Here are some cards ...")); // <4> - } + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + sendResponse(response, "Here are some cards ..."); + } - private void sendResponse(ServerResponse response, String msg) { - JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); - response.send(returnObject); - } + private void sendResponse(ServerResponse response, String msg) { + JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build(); + response.send(returnObject); + } } ---- -<1> Import metrics classes, particularly the `SimpleTimer` interface for this example. -<2> Declare a `SimpleTimer` member variable. -<3> Create and register the `SimpleTimer` metric in the `MetricRegistry`. -<4> Wrap the business logic in the simple timer's `time` method which updates the count and the total elapsed time. +<1> Register the `Gauge`, passing a `Supplier` which furnishes a random temperature from 0 to 100.0 each time the metrics system interrogates the gauge. -[source,bash] -.Build and run the application, then invoke the endpoints below: +[source,bash,subs="attributes+"] +.Build and run the application, then invoke the endpoint below: ---- -curl http://localhost:8080/cards -curl -H "Accept: application/json" http://localhost:8080/metrics/application +curl -H "Accept: application/json" 'http://localhost:8080{metrics-endpoint}?scope=application ---- - [source,json] .JSON response: ---- { - "cardSimpleTimer": { - "count": 1, // <1> - "elapsedTime": 13455720, // <2> - "maxTimeDuration": null, // <3> - "minTimeDuration": null - } + "temperature": 46.582132737739066 // <1> } ----- -<1> How many times the `getDefaultMessageHandler` method ran. -<2> Cumulative time spent in the `getDefaultMessageHandler` method during its executions. -<3> Simple timers report the minimum and maximum durations recorded over the preceding complete minute. The JSON output contains `null` for these values if the simple timer recorded no observations during the preceding complete minute. -If you retrieve the metrics again shortly after the current minute has completed, the simple timer reports useful values. -[source,json] -.JSON response after the current minute completes: ---- -{ - "cardSimpleTimer": { - "count": 1, - "elapsedTime": 13455720, - "maxTimeDuration": 13455720, - "minTimeDuration": 13455720 - } -} ----- -If you again wait until the _next_ complete minute without accessing the greeting card endpoint again and rerun the metrics query, the simple timer reports null values again because there was no activity in the preceding complete minute. +<1> The current (random) temperature. Accessing the endpoint again returns a different value. + include::{rootdir}/includes/guides/metrics.adoc[tag=k8s-and-prometheus-integration] === Summary This guide demonstrated how to use metrics in a Helidon SE application using various combinations of -metrics and scopes. +{metrics} and scopes. -* Access metrics for all three scopes: base, vendor, and application -* Configure metrics that are updated by the application when an application REST endpoint is invoked -* Configure a `Gauge` metric +* Access {metrics} for all three built-in scopes: base, vendor, and application +* Configure {metrics} that are updated by the application when an application REST endpoint is invoked +* Configure a `Gauge` {metric} * Integrate Helidon metrics with Kubernetes and Prometheus Refer to the following references for additional information: -* link:{microprofile-metrics-spec-url}[MicroProfile Metrics specification] -* link:{microprofile-metrics-javadoc-url}[MicroProfile Metrics Javadoc] * link:{javadoc-base-url}/index.html?overview-summary.html[Helidon Javadoc] diff --git a/docs/se/metrics/metrics.adoc b/docs/se/metrics/metrics.adoc index c08a28aada8..cea1a4074e3 100644 --- a/docs/se/metrics/metrics.adoc +++ b/docs/se/metrics/metrics.adoc @@ -23,14 +23,21 @@ :rootdir: {docdir}/../.. include::{rootdir}/includes/se.adoc[] +:metric: meter +:metrics: meters +:metric_uc: Meter +:metrics_uc: Meters +:meter: meter +:meters: meters +:meter_uc: Meter +:meters_uc: Meters +:metrics-endpoint: /observe/metrics == Contents - <> - <> -** <> - <> -** <> - <> ** <> - <> @@ -41,97 +48,151 @@ include::{rootdir}/includes/se.adoc[] ** <> == Overview -Helidon SE metrics is inspired by--but does not fully implement--the MicroProfile Metrics specification. -In particular, the Helidon metrics subsystem furnishes +Helidon SE metrics is a neutral metrics API which provides include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=overview] +=== A Word about Terminology +Helidon {flavor-uc} uses the term "metrics" to refer to the subsystem in Helidon which manages the registration of, updates to, and reporting of aggregate statistical measurements about the service. +The term "{meter}" refers to an entity which collects these measurements, such as a counter or a timer. + // Maven coordinates comes next include::{rootdir}/includes/dependencies.adoc[] -.Packaging full-featured metrics +.Packaging the metrics API [source,xml] ---- io.helidon.metrics - helidon-metrics + helidon-metrics-api + +---- +This dependency adds the metrics API and a no-op implementation of that API to your project. +The no-op implementation: + +* does not register {metrics} in a registry +* does not update {metric} values +* does not expose the metrics endpoint for reporting {metric} values. + +To include the full-featured metrics implementation, add the following dependency to your project: + +.Packaging a full-featured metrics implementation +[source,xml] +---- + + io.helidon.webserver.observe + helidon-webserver-observe-metrics ---- +Adding this dependency packages the full-featured metrics implementation and support for the metrics endpoint with your service. -Adding this dependency packages the full-featured metrics implementation with your service. +You might notice the transitive dependency `io.helidon.metrics.providers:helidon-metrics-providers-micrometer` in your project. +This component contains an implementation of the Helidon metrics API that uses Micrometer as the underlying metrics technology. -=== Other Packaging Options -Helidon gives you flexibility in how you make metrics available to your service. xref:{rootdir}/se/metrics/metrics-capable-components.adoc[This document] explains your options. +Helidon provides several built-in {meters} in a separate artifact. +To include the build-in {meters}, add the following dependency to your project: + +.Packaging the built-in {meters} +[source,xml] +---- + + io.helidon.metrics + helidon-metrics-system-meters + runtime + +---- == Usage include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=usage-body] -=== Enabling the MetricsSupport REST Service -To enable the metrics REST endpoint: +=== Meter Registry +Helidon stores all meters in a _meter registry_. Typically, applications use the global meter registry which is the registry where Helidon stores built-in meters. +Application code refers to the global registry using `Metrics.globalRegistry()`. + + +include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=usage-retrieving] -. Create an instance of link:{metrics-serviceapi-javadoc-base-url}/MetricsSupport.html[`MetricsSupport`], either directly as shown below or using its link:{metrics-serviceapi-javadoc-base-url}/MetricsSupport.Builder.html[`Builder`]. -. Include the `MetricsSupport` instance in your application's routing. + +=== Enabling the Metrics REST Service +If you add the dependencies described above, your service automatically supports the metrics REST endpoint as long as the `WebServer` is configured to discover features automatically. + +If you disable auto-discovery, you can add the metrics observer explicitly. + +. Create an instance of `MetricsObserver`, either directly as shown below or using its builder. +. Include the `MetricsObserver` instance in your application's `ObserveFeature`. +. Register your `ObserveFeature` with your `WebServer`. [source,java] ---- -import io.helidon.metrics.serviceapi.MetricsSupport; +Config config = Config.create(); +Config.global(config); -Routing.builder() - .register(MetricsSupport.create()) - .register("/myapp", new MyService()) +ObserveFeature observe = ObserveFeature.builder() + .config(config.get("server.features.observe")) + .addObserver(MetricsObserver.create()) .build(); + +WebServer server = WebServer.builder() + .config(Config.global().get("server")) + .featuresDiscoverServices(false) + .addFeature(observe) + .routing(Main::routing) + .build() + .start(); + ---- == API To work with Helidon Metrics in your code, follow these steps: -. Use a static method on the link:{metrics-javadoc-base-url}/RegistryFactory.html[`RegistryFactory`] to get a reference to the link:{microprofile-metrics-javadoc-url}/org/eclipse/microprofile/metrics/MetricRegistry.html[`MetricRegistry`] instance you want to use. -. Use the `MetricRegistry` instance to register new metrics and look up previously-registered metrics. -. Use the metric reference returned from the `MetricRegistry` to update the metric or get its value. +. Use the static `globalRegistry` method on the link:{metrics-javadoc-base-url}/Metrics.html[`Metrics`] interface to get a reference to the global link:{metrics-javadoc-base-url}/MeterRegistry.html[`MeterRegistry`] instance. +. Use the `MeterRegistry` instance to register new {metrics} and look up previously-registered {metrics}. +. Use the {metric} reference returned from the `MeterRegistry` to update the {metric} or get its value. -You can also use the `MetricRegistry` to remove an existing metric. +You can also use the `MeterRegistry` to remove an existing {metric}. === Helidon Metrics API -The Helidon Metrics API is aligned with the link:{microprofile-metrics-javadoc-url}[MicroProfile Metrics API]. -That API defines the classes and interfaces for metric types, metric registries, and other related items. Helidon Metrics reuses that API for classes and interfaces (but not for annotations which Helidon SE does not support). +The Helidon Metrics API defines the classes and interfaces for {metric} types and other related items. -The following table summarizes the metric types. +The following table summarizes the {metric} types. -.Metric Types +.{Metric_uc} Types [%autowidth] |==== -| Metric Type | Usage +| {Metric_uc} Type | Usage -| link:{microprofile-metrics-javadoc-metric-url}/Counter.html[`Counter`] +| link:{metrics-javadoc-base-url}/Counter.html[`Counter`] | Monotonically increasing count of events. -| link:{microprofile-metrics-javadoc-metric-url}ConcurrentGauge.html[`ConcurrentGauge`] -| Increasing and decreasing measurement of currently-executing blocks of code. - -| link:{microprofile-metrics-javadoc-metric-url}/Gauge.html[`Gauge`] +| link:{metrics-javadoc-base-url}/Gauge.html[`Gauge`] | Access to a value managed by other code in the service. -|link:{microprofile-metrics-javadoc-metric-url}/Histogram.html[`Histogram`] +|link:{metrics-javadoc-base-url}/DistributionSummary.html[`DistributionSummary`] |Calculates the distribution of a value. -| link:{microprofile-metrics-javadoc-metric-url}/Meter.html[`Meter`] -| Count of invocations and how frequently invocations have occurred. - -| link:{microprofile-metrics-javadoc-metric-url}/SimpleTimer.html[`SimpleTimer`] -| Count of invocations and the total duration consumed by those invocations. - -| link:{microprofile-metrics-javadoc-metric-url}/Timer.html[`Timer`] +| link:{metrics-javadoc-base-url}/Timer.html[`Timer`] | Frequency of invocations and the distribution of how long the invocations take. |==== -Each metric type has its own set of methods for updating and retrieving the metric's value. +Each {metric} type has its own set of methods for updating and retrieving the metric's value. + +=== The `MeterRegistry` API +To register or look up {meters} programmatically, your service code uses the global `MeterRegistry`. +Simply invoke `Metrics.globalRegistry()` to get a reference to the global meter registry. + +To locate an existing meter or register a new one, your code: + +. Creates a builder of the appropriate type of meter, setting the name and possibly other characteristics of the meter. +. Invokes the `MeterRegistry.getOrCreate` method, passing the builder. +The meter registry returns a reference to a previously-registered meter with the specified name and tags or, if none exists, a newly-registered meter. +Your code can then operate on the returned meter as needed to record new measurements or retrieve existing data. -include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=metric-registry-api] +The example code in the <<_examples,Examples>> section below illustrates how to register, retrieve, and update meters. // Here's Configuration. include::{rootdir}/includes/metrics/metrics-config.adoc[tag=config-intro] @@ -140,45 +201,84 @@ include::{rootdir}/includes/metrics/metrics-config.adoc[tag=config-intro] include::{rootdir}/includes/metrics/metrics-shared.adoc[tag=example-apps] -The rest of this section shows how to add a metric to your code and how to configure the Helidon metrics subsystem. +The rest of this section shows how to add a custom meter to your code and how to configure the Helidon metrics subsystem. === Example Application Code -The following example illustrates registering and updating a new `Counter` in application code. +The following example, based on the Helidon SE QuickStart application, shows how to register and update a new `Counter` in application code. The counter tracks the number of times any of the service endpoints is accessed. .Define and use a `Counter` [source,java] ---- -import io.helidon.metrics.api.RegistryFactory; -import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.MetricRegistry; +import io.helidon.metrics.api.Counter; //... -public class MyService implements HttpService { +public class GreetService implements HttpService { - private final MetricRegistry registry = RegistryFactory.getInstance() - .getRegistry(MetricRegistry.Type.APPLICATION); // <1> - private final Counter accessCtr = registry.counter("accessctr"); // <2> + private final Counter accessCtr = Metrics.globalRegistry() // <1> + .getOrCreate(Counter.builder("accessctr")); // <2> @Override public void routing(HttpRules rules) { rules - .any(this::countAccess) - .get("/", this::myGet); + .any(this::countAccess) // <3> + .get("/", this::getDefaultMessageHandler) + .get("/{name}", this::getMessageHandler) + .put("/greeting", this::updateGreetingHandler) + } - private void countAccess(ServerRequest request, ServerResponse response) { - accessCtr.inc(); // <3> + private void countAccess(ServerRequest request, + ServerResponse response) { + accessCtr.inc(); // <4> request.next(); } } ---- -<1> Get the application metric registry. -<2> Create a counter in that registry. -<3> Increment the counter for every request. +<1> Get the global meter registry. +<2> Create (or find) a counter named "accessctr" in the global registry. +<3> Route every request to the `countAccess` method. +<4> Increment the access counter for every request. + +Perform the following steps to see the new counter in action. + +.Build and run the application +[source,bash] +---- +mvn package +java -jar target/helidon-quickstart-se.jar +---- -NOTE: Helidon-provided endpoints for `/metrics` do their work synchronously, using the same thread on which the request arrived via Netty. To prevent performance degradation, avoid including long-running code that can be invoked by these handlers while Helidon is responding to the metric. -For example, if you implement your own application-specific metric types, you will write logic to format the JSON and OpenMetrics output for those metric types. Helidon invokes this formatting logic whenever a client accesses the /metrics endpoints, so make that formatting code as efficient as possible. +.Retrieve `application` metrics +[source.bash] +---- +curl 'http://localhost:8080/observe/metrics?scope=application' # <1> + +# HELP accessctr_total +# TYPE accessctr_total counter +accessctr_total{scope="application",} 0.0 // <2> +---- +<1> Access the metrics endpoint, selecting only application meters. +<2> Note the counter is zero; we have not accessed a service endpoint yet. + +.Access a service endpoint to retrieve a greeting +[source,bash] +---- +curl http://localhost:8080/greet + +{"message":"Hello World"} +---- + +.Retrieve `application` metrics again +[source,bash] +---- +curl 'http://localhost:8080/observe/metrics?scope=application' + +# HELP accessctr_total +# TYPE accessctr_total counter +accessctr_total{scope="application",} 1.0 // <1> +---- +<1> The counter now reports 1, reflecting our earlier access to the `/greet` endpoint. // example configuration include::{rootdir}/includes/metrics/metrics-config.adoc[tag=config-examples] @@ -223,10 +323,9 @@ Your code creates a link:{prometheus-javadoc-base-url}/PrometheusSupport.html[`P ---- import io.helidon.metrics.prometheus.PrometheusSupport; -Routing.builder() - .register(PrometheusSupport.create()) - .register("/myapp", new MyService()) - .build(); +routing + .register(PrometheusSupport.create()) + .register("/myapp", new MyService()); ---- This example uses the default Prometheus `CollectorRegistry`. By default, the `PrometheusSupport` and exposes its REST endpoint at the path diff --git a/docs/sitegen.yaml b/docs/sitegen.yaml index 753184228ed..8f74d88ccb4 100644 --- a/docs/sitegen.yaml +++ b/docs/sitegen.yaml @@ -194,9 +194,7 @@ backend: value: "av_timer" sources: - "metrics.adoc" - - "micrometer.adoc" - "prometheus-exemplar-support.adoc" - - "metrics-capable-components.adoc" - type: "MENU" title: "OpenAPI" dir: "openapi" @@ -401,9 +399,7 @@ backend: value: "av_timer" sources: - "metrics.adoc" - - "micrometer.adoc" - "prometheus-exemplar-support.adoc" - - "metrics-capable-components.adoc" - type: "MENU" title: "OpenAPI" dir: "openapi"