Helidon SE Metrics Guide

This guide describes how to create a sample Helidon SE project that can be used to run some basic examples using both built-in and custom metrics with Helidon.

What You Need

For this 30 minute tutorial, you will need the following:

A Helidon SE ApplicationYou can use your own application or use the Helidon SE Quickstart to create a sample application.
Java SE 11 (Open JDK 11)Helidon requires Java 11+.
Maven 3.6.1+Helidon requires Maven 3.6.1+.
Docker 18.09+You need Docker if you want to build and deploy Docker containers.
Kubectl 1.16.5+If you want to deploy to Kubernetes, you need kubectl and a Kubernetes cluster (you can install one on your desktop).
HelmTo manage Kubernetes applications.
Verify Prerequisites
java -version
mvn --version
docker --version
kubectl version --short
Copied
Setting JAVA_HOME
# On Mac
export JAVA_HOME=`/usr/libexec/java_home -v 11`

# On Linux
# Use the appropriate path to your JDK
export JAVA_HOME=/usr/lib/jvm/jdk-11
Copied

Create a Sample Helidon SE Project

Use the Helidon SE Maven archetype to create a simple project that can be used for the examples in this guide.

Run the Maven archetype
mvn -U archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion=2.6.14 \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-se \
    -Dpackage=io.helidon.examples.quickstart.se
Copied

Using the Built-In Metrics

Helidon provides three 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.

The /metrics endpoint will return data for all scopes.

The built-in metrics fall into three categories:

  1. JVM behavior (in the base registry),
  2. basic key performance indicators for request handling (in the vendor registry), and
  3. thread pool utilization (also in the vendor registry).

A later section describes the key performance indicator metrics 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-se).

The generated source code is already configured for both metrics and health checks, but the following example removes health checks.

Notice that the metrics dependency is already in the project’s pom.xml file:
<dependency>
    <groupId>io.helidon.metrics</groupId>
    <artifactId>helidon-metrics</artifactId>
</dependency>
Copied
Replace the Main.createRouting method with the following code:
    private static Routing createRouting(Config config) {

      GreetService greetService = new GreetService(config);

      return Routing.builder()
          .register(MetricsSupport.create()) 
          .register("/greet", greetService)
          .build();
    }
Copied
  • Register the built-in base and vendor metrics.
Build the application, skipping unit tests, then run it:
mvn package -DskipTests=true
java -jar target/helidon-quickstart-se.jar
Copied

Metrics 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.

Verify the metrics endpoint in a new terminal window:
curl http://localhost:8080/metrics
Copied
Text response:
# TYPE base:classloader_current_loaded_class_count counter
# HELP base:classloader_current_loaded_class_count Displays the number of classes that are currently loaded in the Java virtual machine.
base:classloader_current_loaded_class_count 7511
# TYPE base:classloader_total_loaded_class_count counter
# HELP base:classloader_total_loaded_class_count Displays the total number of classes that have been loaded since the Java virtual machine has started execution.
base:classloader_total_loaded_class_count 7512
...
Copied

You can get the same data in JSON format.

Verify the metrics endpoint with an HTTP accept header:
curl -H "Accept: application/json"  http://localhost:8080/metrics
Copied
JSON response:
{
  "base": {
    "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
  },
  "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
    }
  }
}
Copied

You can get a single metric by specifying the name in the URL path.

Get the Helidon requests.meter metric:
curl -H "Accept: application/json"  http://localhost:8080/metrics/vendor/requests.meter
Copied
JSON response:
{
  "requests.meter": {
    "count": 6,
    "meanRate": 0.008275992296704147,
    "oneMinRate": 0.01576418632772332,
    "fiveMinRate": 0.006695060022357365,
    "fifteenMinRate": 0.0036382699664488415
  }
}
Copied

You cannot get the individual fields of a metric. For example, you cannot target http://localhost:8080/metrics/vendor/requests.meter.count.

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 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 later section for more information on the basic and extended key performance indicator metrics.

Controlling Metrics Behavior

By adding a metrics section to your application configuration you can control how the Helidon metrics subsystem behaves in any of several ways.

Your Helidon SE application can also control metrics processing programmatically as described in the following sections.

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:

Configuration properties file disabling metrics
metrics.enabled=false
Copied

A Helidon SE application can disable metrics processing programmatically.

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(); 

    MetricsSupport metricsSupport = MetricsSupport.create(metricsSettings); 

    RegistryFactory registryFactory = RegistryFactory.getInstance(metricsSettings); 
Copied
  • Create a MetricsSettings instance (via its Builder) with the metrics subsystem disabled.
  • Get a MetricsSupport service (usable in setting routing rules) that responds to the /metrics endpoint with 404 and an explanatory message.
  • Get a RegistryFactory instance that provides MetricRegistry instances which register no-op metric objects (counters, timers, etc.).

These builders and interfaces also have methods which accept Config objects representing the metrics node from the application configuration.

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.

Enabling and Disabling Metrics Usage by a Component

Helidon contains several components and integrations which register and update metrics. Depending on how the component is written, you might be able to disable just that component’s use of metrics:

Configuration properties file disabling a component’s use of metrics
some-component.metrics.enabled=false
Copied

Check the documentation for a specific component to find out whether that component uses metrics and whether it allows you to disable that use.

Your Helidon SE application can disable a metrics-capable component’s use of metrics programmatically.

Disable metrics use in a metrics-capable component
import io.helidon.metrics.api.ComponentMetricsSettings;
...

    ComponentMetricsSettings.Builder componentMetricsSettingsBuilder = ComponentMetricsSettings.builder()
            .enabled(false); 

    SomeService someService = SomeService.builder()
            ...
            .componentMetricsSettings(componentMetricsSettingsBuilder)
            ...
            .build(); 
Copied
  • Create a ComponentMetricsSettings instance (via its Builder) indicating that metrics usage should be disabled.
  • Create an instance of the service with its metrics usage disabled.

If you disable a component’s use of metrics, Helidon does not register the component’s metrics in the visible metrics registries nor do those metrics ever update their values. The response from the /metrics endpoint excludes that component’s metrics.

Note that if you disable metrics processing entirely, no component updates its metrics regardless of any component-level metrics settings.

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.

Disabling All Metrics of a Given Registry Type

To disable all metrics in a given registry type (application, vendor, or base), add one or more groups to the configuration:

Disabling base and vendor metrics (properties format)
metrics.registries.0.type = base
metrics.registries.0.enabled = false
metrics.registries.1.type = vendor
metrics.registries.1.enabled = false
Copied
Disabling base and vendor metrics (YAML format)
metrics:
  registries:
    - type: base
      enabled: false
    - type: vendor
      enabled: false
Copied
Controlling Metrics by Metric Name

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.

Caution

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:

Disabling metrics by name (properties format)
metrics.registries.0.type = application
metrics.registries.0.filter.exclude = myapp\.supplier\..*
Copied

The following settings select the particular subset of the metrics created in your application code representing updates of customers and suppliers:

Enabling metrics by name (properties format)
metrics.registries.0.type = application
metrics.registries.0.filter.include = myapp\..*\.updates
Copied

If you use the YAML configuration format, enclose the regex patterns in single-quote marks:

Enabling metrics by name (YAML format)
metrics:
  registries:
    - type: application
      filter:
        include: 'myapp\..*\.updates'
Copied

The next example selects only your application’s metrics while excluding those which refer to deletions:

Combining include and exclude
metrics.registries.0.type = application
metrics.registries.0.filter.include = myapp\..*
metrics.registries.0.filter.exclude = myapp\..*/deletes
Copied

Helidon would not update or report the metric myapp.supplier.queries, for example. To include metrics from your application for both updates and queries (but not for other operations), you could change the settings in the previous example to this:

metrics.registries.0.type = application
metrics.registries.0.filter.include = myapp\..*\.updates|myapp\..*\.queries
metrics.registries.0.filter.exclude = myapp\..*/deletes
Copied

Your Helidon SE application can control the collection and reporting of metrics programmatically as well by preparing these settings objects:

and using the resulting MetricsSettings to retrieve a suitable RegistryFactory.

Control metrics by registry type and name
import io.helidon.metrics.api.RegistryFilterSettings;
import org.eclipse.microprofile.metrics.MetricRegistry;
...
    RegistryFilterSettings appFilterSettings = RegistryFilterSettings.builder()  
        .include("myapp\..*\.updates")
        .build();
    RegistrySettings registrySettings = RegistrySettings.builder() 
        .filterSettings(appFilterSettings)
        .build();
    MetricsSettings metricsSettings = MetricsSettings.builder() 
        .registrySettings(MetricRegistry.Type.APPLICATION, appFilterSettings)
        .build();
    RegistryFactory rf = RegistryFactory.getInstance(metricsSettings); 
    MetricRegistry registry = rf.getRegistry(MetricRegistry.Type.APPLICATION); 
Copied
  • Create the registry filter settings to include only those metrics with names indicating updates.
  • Create the registry settings with that filter.
  • Create the metrics settings, associating the registry settings with the APPLICATION metric registry.
  • Set the overall metrics settings and retrieve a registry factory suitably initialized.
  • Obtain a reference to the APPLICATION registry which is set up to create and report on only metrics with names starting with myapp.updates..

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).

Helidon SE 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

You can enable and control these metrics using configuration:

Configuration properties file controlling extended KPI metrics
metrics.key-performance-indicators.extended = true
metrics.key-performance-indicators.long-running.threshold-ms = 2000
Copied

Your Helidon SE application can also control the KPI settings programmatically.

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;
...

    KeyPerformanceIndicatorMetricsSettings.Builder kpiSettingsBuilder =
        KeyPerformanceIndicatorMetricsSettings.builder()
            .extended(true)
            .longRunningThresholdMs(2000); 

    MetricsSettings metricsSettings = MetricsSettings.builder()
            .keyPerformanceIndicatorSettings(kpiSettingsBuilder)
            .build(); 
Copied

Metrics Metadata

Each metric has associated metadata that describes:

  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.

You can get the metadata for any scope, such as /metrics/base, as shown below:

Get the metrics metadata using HTTP OPTIONS method:
 curl -X OPTIONS -H "Accept: application/json"  http://localhost:8080/metrics/base
Copied
JSON response (truncated):
{
  "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"
  },
...
  "jvm.uptime": {
    "unit": "milliseconds",
    "type": "gauge",
    "description": "Displays the start time of the Java virtual machine in milliseconds. This attribute displays the approximate time when the Java virtual machine started.",
    "displayName": "JVM Uptime"
  },
...
  "memory.usedHeap": {
    "unit": "bytes",
    "type": "gauge",
    "description": "Displays the amount of used heap memory in bytes.",
    "displayName": "Used Heap Memory"
  }
}
Copied

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.

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.

Counter Metric

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.

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.Routing;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import io.helidon.webserver.Service;
import java.util.Collections;
import javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import org.eclipse.microprofile.metrics.Counter;  
import org.eclipse.microprofile.metrics.MetricRegistry;

public class GreetingCards implements Service {

  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 update(Routing.Rules rules) {
    rules.get("/", this::getDefaultMessageHandler);
  }

  private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {
    cardCounter.inc();   
    sendResponse(response, "Here are some cards ...");
  }

  private void sendResponse(ServerResponse response, String msg) {
    JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build();
    response.send(returnObject);
  }
}
Copied
  • Import metrics classes.
  • Declare a Counter member variable.
  • Create and register the Counter metric in the MetricRegistry. This Counter will exist for the lifetime of the application.
  • Increment the count.
Update the Main.createRouting method as follows:
    private static Routing createRouting(Config config) {

        MetricsSupport metrics = MetricsSupport.create();
        GreetService greetService = new GreetService(config);

        return Routing.builder()
                .register(JsonSupport.create())
                .register(metrics)
                .register("/greet", greetService)
                .register("/cards", new GreetingCards()) 
            .build();
    }
Copied
  • Add the GreetingCards service to the Routing.builder. Helidon will route any REST requests with the /cards root path to the GreetingCards service.
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
Copied
JSON response:
{
  "cardCount": 1 
}
Copied
  • The count value is one since the method was called once.

Meter Metric

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 following example marks an event each time the /cards endpoint is called.

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 javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import org.eclipse.microprofile.metrics.Meter; 
import org.eclipse.microprofile.metrics.MetricRegistry; 

public class GreetingCards implements Service {

  private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
  private final Meter cardMeter; 

  GreetingCards() {
    RegistryFactory metricsRegistry = RegistryFactory.getInstance();
    MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION);
    cardMeter = appRegistry.meter("cardMeter"); 
  }

  @Override
  public void update(Routing.Rules rules) {
    rules.get("/", this::getDefaultMessageHandler);
  }

  private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {
    cardMeter.mark(); 
    sendResponse(response, "Here are some cards ...");
  }

  private void sendResponse(ServerResponse response, String msg) {
    JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build();
    response.send(returnObject);
  }
}
Copied
  • Import metrics classes.
  • Declare a Meter member variable.
  • Create and register the Meter metric in the MetricRegistry.
  • Mark the occurrence of an event.

Note: you can specify a count parameter such as mark(100) to mark multiple events.

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
Copied
JSON response:
{
  "cardMeter": { 
    "count": 3, 
    "meanRate": 0.17566568722974535,
    "oneMinRate": 0.04413761384322548,
    "fiveMinRate": 0.009753212003766951,
    "fifteenMinRate": 0.0033056752265846544
  }
}
Copied
  • The Meter metric has a set of fields to show various rates, along with the count.
  • The /cards endpoint was called three times.

Timer Metric

(See also 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.

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 javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import org.eclipse.microprofile.metrics.MetricRegistry; 
import org.eclipse.microprofile.metrics.Timer;

public class GreetingCards implements Service {

  private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
  private final Timer cardTimer; 

  GreetingCards() {
    RegistryFactory metricsRegistry = RegistryFactory.getInstance();
    MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION);
    cardTimer = appRegistry.timer("cardTimer"); 
  }

  @Override
  public void update(Routing.Rules rules) {
    rules.get("/", this::getDefaultMessageHandler);
  }

  private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {
    Timer.Context timerContext = cardTimer.time(); 
    sendResponse(response, "Here are some cards ...");
    response.whenSent().thenAccept(res -> timerContext.stop()); 
  }

  private void sendResponse(ServerResponse response, String msg) {
    JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build();
    response.send(returnObject);
  }
}
Copied
  • Import metrics classes.
  • Declare a Timer member variable.
  • Create and register the Timer metric in the MetricRegistry.
  • Start the timer.
  • Stop the timer.
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
Copied
JSON response:
{
  "cardTimer": {
    "count": 1,
    "meanRate": 0.03843465264149663, 
    "oneMinRate": 0.014712537947741825,
    "fiveMinRate": 0.0032510706679223173,
    "fifteenMinRate": 0.0011018917421948848,
    "min": 40876527,  
    "max": 40876527,
    "mean": 40876527,
    "stddev": 0.0,
    "p50": 40876527,
    "p75": 40876527,
    "p95": 40876527,
    "p98": 40876527,
    "p99": 40876527,
    "p999": 40876527
  }
}
Copied
  • These are the same fields used by Meter.
  • These 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.

Histogram Metric

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 /cards endpoint is invoked.

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 java.util.Random;
import javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import org.eclipse.microprofile.metrics.Histogram; 
import org.eclipse.microprofile.metrics.MetricRegistry; 

public class GreetingCards implements Service {

  private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
  private final Histogram cardHistogram; 

  GreetingCards() {
    RegistryFactory metricsRegistry = RegistryFactory.getInstance();
    MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION);
    cardHistogram = appRegistry.histogram("cardHistogram"); 
  }

  @Override
  public void update(Routing.Rules rules) {
    rules.get("/", this::getDefaultMessageHandler);
  }

  private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {

    Random r = new Random();
    for (int i = 0; i < 1000; i++) {  
      cardHistogram.update(1 + r.nextInt(25)); 
    }
    sendResponse(response, "Here are some cards ...");
  }

  private void sendResponse(ServerResponse response, String msg) {
    JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build();
    response.send(returnObject);
  }
}
Copied
  • Import metrics classes.
  • Declare a Histogram member variable.
  • Create and register the Histogram metric in the MetricRegistry.
  • Update the Histogram metric with a random number.
  • Loop, loading the histogram with numbers.
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
Copied
JSON response:
{
  "cardHistogram": { 
    "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
  }
}
Copied
  • This is the histogram data. Some of these values will change each time you invoke the /cards endpoint.

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 how a Gauge is used to get the current temperature.

Add new imports to Main.java and replace the Main.createRouting method with the following code:
import io.helidon.metrics.RegistryFactory;
import java.util.Random;
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<Integer>)() ->  new Random().nextInt(100)); 

      GreetService greetService = new GreetService(config);
      return Routing.builder()
          .register(JsonSupport.create())
          .register(metrics)                  // Metrics at "/metrics"
          .register("/greet", greetService)
          .register("/cards", new GreetingCards())
          .build();
    }
Copied
  • Register the Gauge, providing a lambda function that will return a random temperature.
Update the GreetingCards class with the following code to use the Counter metric which will simplify the JSON output:
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 javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.MetricRegistry;

public class GreetingCards implements Service {

  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 update(Routing.Rules rules) {
    rules.get("/", this::getDefaultMessageHandler);
  }

  private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {
    cardCounter.inc();   
    sendResponse(response, "Here are some cards ...");
  }

  private void sendResponse(ServerResponse response, String msg) {
    JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build();
    response.send(returnObject);
  }
}
Copied
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
Copied
JSON response from /metrics/application:
{
  "cardCount": 1,
  "temperature": 11 
}
Copied
  • The current temperature is returned. Invoke the /metrics/application endpoint again and you should get a different value.

Simple Timer Metric

The SimpleTimer metric counts invocations and accumulates duration (in seconds). 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.

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 javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import org.eclipse.microprofile.metrics.MetricRegistry; 
import org.eclipse.microprofile.metrics.SimpleTimer;

public class GreetingCards implements Service {

  private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
  private final SimpleTimer cardTimer; 

  GreetingCards() {
    RegistryFactory metricsRegistry = RegistryFactory.getInstance();
    MetricRegistry appRegistry = metricsRegistry.getRegistry(MetricRegistry.Type.APPLICATION);
    cardTimer = appRegistry.simpleTimer("cardSimpleTimer"); 
  }

  @Override
  public void update(Routing.Rules rules) {
    rules.get("/", this::getDefaultMessageHandler);
  }

  private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) {
    cardTimer.time(() -> sendResponse(response, "Here are some cards ...")); 
  }

  private void sendResponse(ServerResponse response, String msg) {
    JsonObject returnObject = JSON.createObjectBuilder().add("message", msg).build();
    response.send(returnObject);
  }
}
Copied
  • Import metrics classes, particularly the SimpleTimer interface for this example.
  • Declare a SimpleTimer member variable.
  • Create and register the SimpleTimer metric in the MetricRegistry.
  • Wrap the business logic in the simple timer’s time method which updates the count and the total elapsed time.
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
Copied
JSON response:
{
  "cardSimpleTimer":
    {
      "count":1, 
      "elapsedTime":0.034274025 
    }
}
Copied
  • How many times the getDefaultMessageHandler method ran.
  • Cumulative time spent in the getDefaultMessageHandler method during its executions.

Integration with Kubernetes and Prometheus

Kubernetes Integration

The following example shows how to integrate the Helidon SE application with Kubernetes.

Stop the application and build the docker image:
docker build -t helidon-metrics-se .
Copied
Create the Kubernetes YAML specification, named metrics.yaml, with the following content:
kind: Service
apiVersion: v1
metadata:
  name: helidon-metrics 
  labels:
    app: helidon-metrics
  annotations:
    prometheus.io/scrape: 'true' 
spec:
  type: NodePort
  selector:
    app: helidon-metrics
  ports:
    - port: 8080
      targetPort: 8080
      name: http
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: helidon-metrics
spec:
  replicas: 1 
  selector:
    matchLabels:
      app: helidon-metrics
  template:
    metadata:
      labels:
        app: helidon-metrics
        version: v1
    spec:
      containers:
        - name: helidon-metrics
          image: helidon-metrics-se
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
Copied
  • A service of type NodePort that serves the default routes on port 8080.
  • An annotation that will allow Prometheus to discover and scrape the application pod.
  • A deployment with one replica of a pod.
Create and deploy the application into Kubernetes:
kubectl apply -f ./metrics.yaml
Copied
Get the service information:
kubectl get service/helidon-metrics
Copied
NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
helidon-metrics   NodePort   10.99.159.2   <none>        8080:31143/TCP   8s 
Copied
  • A service of type NodePort that serves the default routes on port 31143.
Verify the metrics endpoint using port 30116, your port will likely be different:
curl http://localhost:31143/metrics
Copied

Leave the application running in Kubernetes since it will be used for Prometheus integration.

Prometheus Integration

The metrics service that you just deployed into Kubernetes is already annotated with prometheus.io/scrape:. This will allow Prometheus to discover the service and scrape the metrics. In this exercise, you will install Prometheus into Kubernetes, then verify that it discovered the Helidon metrics in your application.

Install Prometheus and wait until the pod is ready:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install metrics prometheus-community/prometheus
export POD_NAME=$(kubectl get pod -l app.kubernetes.io/name=prometheus -l  app.kubernetes.io/component=server -o jsonpath="{.items[0].metadata.name}")
kubectl get pod $POD_NAME
Copied

You will see output similar to the following. Repeat the kubectl get pod command until you see 2/2 and Running. This may take up to one minute.

metrics-prometheus-server-5fc5dc86cb-79lk4   2/2     Running   0          46s
Copied
Create a port-forward, so you can access the server URL:
kubectl --namespace default port-forward $POD_NAME 7090:9090
Copied

Now open your browser and navigate to http://localhost:7090/targets. Search for helidon on the page, and you will see your Helidon application as one of the Prometheus targets.

Final Cleanup

You can now delete the Kubernetes resources that were just created during this example.

Delete the Prometheus Kubernetes resources:
helm delete --purge metrics
Copied
Delete the application Kubernetes resources:
kubectl delete -f ./metrics.yaml
Copied

Summary

This guide demonstrated how to use metrics in a Helidon SE application using various combinations of 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

  • Integrate Helidon metrics with Kubernetes and Prometheus

Refer to the following references for additional information: