- Metrics-Capable Modules
This document explains Helidon SE metrics-capable components and applications and describes how to create and control them.
Introduction
Think of Helidon metrics in three related but different parts:
The Helidon metrics API allows your code to register, look-up, remove, and update metrics using the
RegistryFactory,MetricRegistry, and individual metrics interfaces.Helidon provides two implementations of the Helidon metrics API and selects which one to use at runtime, based on what components are present on the runtime path and whether metrics is configured to be enabled or disabled.
The built-in Helidon metrics web service supports the
/metricsendpoints by which clients can retrieve metadata and values of the registered metrics. Your Helidon SE app provides this feature (if at all) by explicitly using theMetricsSupportinterface.Most Helidon applications are web-based and their developers choose to expose the built-in metrics web service. But by separating the parts of metrics this way, Helidon allows non-web apps to work with metrics as well, just without the web service support.
As you plan and write Helidon components and applications, you make some choices about exactly how your code will use metrics. This guide gives some background information, describes the choices you face, explains their ramifications, and provides some code examples.
Categorizing Metrics Usage
We can place each Helidon component and Helidon application into one of three categories based on how it relies on metrics. The type of module dictates the compile-time dependency you declare in the project pom.xml.
| Registers, updates, removes metrics? | Refers to metrics values? | Category |
|---|---|---|
| times | times | metrics-independent |
| check | times | metrics-capable |
| check | check | metrics-dependent |
Whenever possible, if your component or app uses metrics write it as metrics-capable code.
Understanding the Two Metrics Implementations
Helidon provides two metrics implementations.
Full-featured metrics allows registering, removing, and updating metrics and observing metrics' changing values. The
helidon-metricscomponent contains full-featured metrics.Minimal metrics supports registering, removing, and updating metrics. The metrics objects provided by the minimal implementation are no-ops: their values never change. The minimal implementation is part of the
helidon-metrics-apicomponent.
Any code compiled with helidon-metrics-api can assume that the runtime path will include the minimal implementation.
Both implementations support all the operations of the RegistryFactory and the MetricRegistry. The full implementation provides fully-functional metrics instances (counters, timers, etc.). In the minimal implementations, metrics do not update their values.
For Helidon to use the full implementation, two conditions must hold:
The
helidon-metricscomponent must be on the runtime path.Metrics must be enabled, using either a builder or configuration. (Enabled is the default.)
Otherwise, provided that the runtime path includes helidon-metrics-api, Helidon activates the minimal implementation.
Understanding the Two Metrics Service Implementations
Helidon includes two implementations of support for the metrics web service endpoint /metrics (or whatever context value is configured).
The full-service implementation sends responses which describe the metadata and current values for the metrics registered in metric registries. The helidon-metrics component contains this implementation.
The helidon-metrics-service-api component contains the API for the metrics web service support (the MetricsSupport interface) and also a minimal implementation. This implementation simply responds with 404 and an explanatory message that metrics are disabled.
Any code compiled with helidon-metrics-service-api can assume that the runtime path will contain the minimal implementation.
Helidon activates the full implementation if the runtime path includes the full implementation and metrics is configured as enabled; Helidon uses the minimal implementation otherwise.
Enabling and Disabling Metrics
Using either builder-style settings or configuration, your component or Helidon SE application can let end users control at runtime whether Helidon should use full-featured metrics. If an end user sets metrics.enabled to false, then Helidon activates the minimal metrics and metrics service implementations provided they are in the runtime path.
Further, users can set component-name.metrics.enabled to false which disables metrics for just that component so long as the component was written to check that setting and act on it accordingly.
Designing and Writing Metrics-capable Applications and Components
Whoever packages and deploys your application or component can control what code will be on the runtime path and whether metrics is enabled or not. As a result, wherever possible, construct your modules which use metrics so that they do not make decisions based on the values of metrics; that is, design them to be metrics-capable, not metrics-dependent. Doing so allows your code to operate regardless of whether the full-featured metrics implementation is active at runtime.
Declaring Dependencies
- Include this dependency:Dependency for Helidon metrics API
<dependency> <groupId>io.helidon.metrics</groupId> <artifactId>helidon-metrics-api</artifactId> </dependency>content_copyThis module defines the metrics API:
RegistryFactory,MetricRegistry, and the various metrics themselves. - To permit the use of the built-in metrics web service support for the
/metricsendpoint, add this dependency:Dependency for metrics web service support<dependency> <groupId>io.helidon.metrics</groupId> <artifactId>helidon-metrics-service-api</artifactId> </dependency>content_copyThis module defines the metrics web service API:
MetricsSupport.Use the
MetricsSupportinterface fromhelidon-metrics-service-apiin your SE app initialization code to create a service you can register with the web server. (See the example below.) - Declare an explicit runtime dependency on the full-featured metrics implementation:Dependency for full metrics and metrics service implementations
<dependency> <groupId>io.helidon.metrics</groupId> <artifactId>helidon-metrics</artifactId> <scope>runtime</scope> </dependency>content_copy
Writing the Metrics-capable Code
The way you write a metrics-capable module depends on whether it is a component (that is, not an application) or an application.
Writing a Non-application Component
Write your non-application component to accept component-specific configuration that includes an optional metrics section which can include an optional enabled setting. Helidon defaults the value to true. The following example shows one way to accomplish this:
import io.helidon.config.Config;
import io.helidon.metrics.api.ComponentMetricsSettings;
import io.helidon.metrics.api.MetricsSettings;
import io.helidon.metrics.api.RegistryFactory;
import org.eclipse.microprofile.metrics.MetricRegistry;
public class UtilComponent {
private final MetricRegistry metricRegistry;
public static class Builder implements io.helidon.common.Builder<UtilComponent> {
private ComponentMetricsSettings.Builder componentMetricsSettingsBuilder = ComponentMetricsSettings.builder();
public Builder componentMetricsSettings(ComponentMetricsSettings.Builder componentMetricsSettingsBuilder) {
this.componentMetricsSettingsBuilder = componentMetricsSettingsBuilder;
return this;
}
public Builder config(Config componentConfig) {
componentConfig
.get(ComponentMetricsSettings.Builder.METRICS_CONFIG_KEY)
.as(ComponentMetricsSettings::create)
.ifPresent(this::componentMetricsSettings);
return this;
}
public UtilComponent build() {
return new UtilComponent(this);
}
...
}
private UtilComponent(Builder builder) {
...
metricRegistry = RegistryFactory
.getInstance(builder.componentMetricsSettingsBuilder.build())
.getRegistry(MetricRegistry.Type.VENDOR);
}
MetricRegistry metricRegistry() {
return metricRegistry;
}
}- Other code in the component uses this metric registry for registering, looking up, and removing metrics.
- Applications which use instances of
MyComponentuse thisBuilderto set up and create those instances. - Applications which layer on your component invoke this method to set up the component-level metrics behavior they want your component to use.
- If an application supports configuration, it passes the util config to this method.
- The constructor for your component obtains the
MetricRegistrywhich the rest of your component will use. - Provides easy access to the
MetricRegistrywhich the component’s metrics code should use.
Helidon returns either a full-featured RegistryFactory or a minimal one, depending on:
whether the full-featured metrics implementation is on the runtime path,
whether metrics overall is enabled or disabled, and
whether the component metrics settings requests enabled or disabled metrics.
Writing and Packaging a Metrics-capable Helidon SE Application
Write your SE application similarly, but do not use the ComponentMetricsSettings. Instead, build a MetricsSettings object from the configuration.
import io.helidon.config.Config;
import io.helidon.metrics.api.MetricsSettings;
import io.helidon.metrics.api.RegistryFactory;
import io.helidon.webserver.WebServer;
import org.eclipse.microprofile.metrics.MetricRegistry;
public class MyApp {
private static MetricsSettings metricsSettings;
static MetricRegistry metricRegistry;
public static void main(final String[] args) {
startServer();
}
static Single<WebServer> startServer() {
...
Config config = Config.create();
metricsSettings = MetricsSettings.builder()
.config(config)
.build();
metricRegistry = RegistryFactory.getInstance(metricsSettings)
.getRegistry(MetricRegistry.Type.APPLICATION);
WebServer server = WebServer.builder(createRouting(config))
.config(config.get("server"))
.addMediaSupport(JsonpSupport.create())
.build();
...
}
private static Routing createRouting(Config config) {
RestServiceSettings restServiceSettings = RestServiceSettings.create(config);
MetricsSupport metricsSupport = MetricsSupport.create(metricsSettings, restServiceSettings);
GreetService greetService = new GreetService(config);
return Routing.builder()
.register(metricsSupport)
.register("/greet", greetService)
.build();
}
}- Create and save
MetricsSettingsfrom config. - Use
MetricsSettingsto get a suitableRegistryFactory, and use that to get the application registry. - Pass
configtocreateRoutingwhich returns theRoutingto initialize the web server. - Use the
configto createRestServiceSettingswhich controls the routing name, web context, and CORS set-up for the metrics endpoint. - Create the
MetricsSupportinstance using the metrics and REST service settings. - Add the properly initialized
MetricsSupportinstance as a service to the routing, along with the app’s own service.
Helidon uses the enabled value from MetricsSettings in providing the correct implementations of both the RegistryFactory and the MetricsSupport.
An Example: Docker Images
Here is an example showing how useful metrics-capable code can be.
You (or others) could assemble a Docker image with your metrics-capable app as its top layer or your metrics-capable component in a middle layer, built on a lower layer containing several Helidon modules including the full metrics implementation. When that Docker image runs, your app will run with full-featured metrics support.
Separately, someone could build a similar Docker image which does not include the Helidon metrics implementation. In this Docker image, your app or component will run successfully but will not incur the overhead of actually updating the metrics it uses.
Users can create different Docker images, some with full metrics support and some without, which all use a single version of your metrics-capable app or component which runs properly in either environment without change.
Advantages of Writing Metrics-capable Modules
By writing a metrics-capable app or component, you give packagers and deployers of your code the flexibility to include or exclude the full metrics implementation at runtime as they see fit.
Because your one module works correctly in either environment:
The consumers of your app benefit by not needing to understand and choose between two different implementations of your module, or having to add both your main module and an optional add-on which adds metrics support to your module.
You benefit by writing and maintaining a single module, not two: one that is metrics-independent and one that is metrics-dependent.