From 19d3b611f71eac70de3eda3a389b9d25c1cb3693 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Thu, 27 Jul 2023 16:47:15 -0500
Subject: [PATCH 01/40] Initial commit of neutral metrics API with high-level
interfaces; more to come
---
.../io/helidon/metrics/api/MeterRegistry.java | 366 ++++++++++++++++++
.../metrics/api/MetricFactoryManager.java | 39 ++
.../java/io/helidon/metrics/api/Metrics.java | 184 +++++++++
3 files changed, 589 insertions(+)
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java b/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java
new file mode 100644
index 00000000000..52baa6f96b4
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.ToDoubleFunction;
+
+/**
+ * Manages the look-up and registration of meters.
+ *
+ *
+ * This interface supports two types of retrieval (using {@link io.helidon.metrics.api.Counter} as an example):
+ *
+ * - retrieve or create - {@link #counter(io.helidon.metrics.api.Meter.Id)} - returns the meter if it was previously
+ * registered, otherwise creates and registers the meter
+ * - retrieve only - {@link #getCounter(io.helidon.metrics.api.Meter.Id)}
- returns an {@link java.util.Optional}
+ * for the meter, empty if the meter has not been registered and non-empty if it has been registered.
+ *
+ *
+ *
+ * For most meter types, this interface provides two general variants of the retrieve-or-create-method for each meter
+ * (again using {@link io.helidon.metrics.api.Counter} as an example):
+ *
+ * - by ID - {@link #counter(io.helidon.metrics.api.Meter.Id)} - the caller prepares the ID
+ * - by name and tags - {@link #counter(String, Iterable)} and {@link #counter(String, String...)}- the caller need not
+ * prepare the ID
+ *
+ *
+ */
+public interface MeterRegistry {
+
+ /**
+ * Returns all previously-registered meters.
+ *
+ * @return registered meters
+ */
+ List meters();
+
+ /**
+ * Returns previously-registered meters which match the specified {@link java.util.function.Predicate}.
+ *
+ * @param filter the predicate with which to evaluate each {@link io.helidon.metrics.api.Meter}
+ * @return meters which match the predicate
+ */
+ Iterable meters(Predicate filter);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its ID.
+ *
+ * @param id {@link Meter.Id} to register or locate
+ * @return new or previously-registered counter
+ */
+ Counter counter(Meter.Id id);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its ID or registers a new one
+ * which wraps an external target object which provides the counter value.
+ *
+ *
+ * The counter returned rejects attempts to increment its value because the external object, not the counter itself,
+ * maintains the value.
+ *
+ *
+ * @param id {@link Meter.Id} to register or locate
+ * @param target object which provides the counter value
+ * @param fn function which, when applied to the target, returns the counter value
+ * @return the target object
+ * @param type of the target object
+ */
+ Counter counter(Meter.Id id, T target, ToDoubleFunction fn);
+
+ /**
+ * Locates a previous-registered {@link io.helidon.metrics.api.Counter} by its ID.
+ *
+ * @param id {@link io.helidon.metrics.api.Meter.Id} to locate
+ * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getCounter(Meter.Id id);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter}.
+ *
+ * @param name counter name
+ * @param tags tags for further identifying the counter
+ * @return new or existing counter
+ */
+ Counter counter(String name, Iterable tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
+ *
+ * @param name counter name
+ * @param tags counter {@link io.helidon.metrics.api.Tag} instances which further identify the counter
+ * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getCounter(String name, Iterable tags);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter}.
+ *
+ * @param name counter name
+ * @param tags key/value pairs; MUST be an even number of arguments
+ * @return new or existing counter
+ */
+ Counter counter(String name, String... tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
+ *
+ * @param name counter name
+ * @param tags counter {@link io.helidon.metrics.api.Tag} instances which further identify the counter
+ * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getCounter(String name, String... tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name dnd tags or registers a new one
+ * which wraps an external target object which provides the counter value.
+ *
+ *
+ * The counter returned rejects attempts to increment its value because the external object, not the counter itself,
+ * maintains the value.
+ *
+ *
+ * @param name counter name
+ * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the counter
+ * @param target object which provides the counter value
+ * @param fn function which, when applied to the target, returns the counter value
+ * @return the target object
+ * @param type of the target object
+ */
+ Counter counter(String name, Iterable tags, T target, ToDoubleFunction fn);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary}.
+ *
+ * @param id {@link Meter.Id} for the summary
+ * @param distributionStatisticsConfig configuration governing distribution statistics calculations
+ * @param scale scaling factor to apply to every sample recorded by the summary
+ * @return new or existing summary
+ */
+ DistributionSummary summary(Meter.Id id,
+ DistributionStatisticsConfig distributionStatisticsConfig,
+ double scale);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary} by its ID.
+ *
+ * @param id {@link io.helidon.metrics.api.Meter.Id} to locate
+ * @return {@link java.util.Optional} of {@code DistributionSummary} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getSummary(Meter.Id id);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary}.
+ *
+ * @param name summary name
+ * @param tags tags for further identifying the summary
+ * @return new or existing distribution summary
+ */
+ DistributionSummary summary(String name, Iterable tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary} by its name and tags.
+ *
+ * @param name summary name to locate
+ * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the summary
+ * @return {@link java.util.Optional} of {@code DistributionSummary} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getSummary(String name, Iterable tags);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary}.
+ *
+ * @param name summary name
+ * @param tags key/value pairs; MUST be an even number of arguments
+ * @return new or existing distribution summary
+ */
+ DistributionSummary summary(String name, String... tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary} by its name and tags.
+ *
+ * @param name summary name to locate
+ * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the summary
+ * @return {@link java.util.Optional} of {@code DistributionSummary} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getSummary(String name, Tag... tags);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Timer}.
+ *
+ * @param id ID for the timer
+ * @param distributionStatisticsConfig configuration governing distribution statistics calculations
+ * @return new or existing timer
+ */
+ Timer timer(Meter.Id id, DistributionStatisticsConfig distributionStatisticsConfig);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Timer} by its ID.
+ *
+ * @param id ID for the timer
+ * @return {@link java.util.Optional} of {@code Timer} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getTimer(Meter.Id id);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Timer}.
+ *
+ * @param name timer name
+ * @param tags tags for further identifying the timer
+ * @return new or existing timer
+ */
+ Timer timer(String name, Iterable tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Timer} by its name and tags.
+ *
+ * @param name timer name
+ * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the timer
+ * @return {@link java.util.Optional} of {@code Timer} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getTimer(String name, Iterable tags);
+
+ /**
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Timer}.
+ *
+ * @param name timer name
+ * @param tags key/value pairs; MUST be an even number of arguments
+ * @return new or existing timer
+ */
+ Timer timer(String name, String... tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Timer} by its name and tags.
+ *
+ * @param name timer name
+ * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the timer
+ * @return {@link java.util.Optional} of {@code Timer} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getTimer(String name, Tag... tags);
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object returned by applying
+ * the specified {@code valueFunction}.
+ *
+ * @param id {@link io.helidon.metrics.api.Meter.Id} of the gauge
+ * @param stateObject object to which the {@code valueFunction} is applied to obtain the gauge's value
+ * @param fn function which, when applied to the {@code stateObject}, produces an instantaneous gauge value
+ * @param type of the state object which yields the gauge's value
+ * @return state object
+ */
+ T gauge(Meter.Id id,
+ T stateObject,
+ ToDoubleFunction fn);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Gauge} by its ID.
+ *
+ * @param id ID for the gauge
+ * @return {@link java.util.Optional} of {@code Gauge} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getGauge(Meter.Id id);
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object returned by applying
+ * the specified {@code valueFunction}.
+ *
+ * @param name name of the gauge
+ * @param tags further identification of the gauge
+ * @param stateObject object to which the {@code valueFunction} is applied to obtain the gauge's value
+ * @param valueFunction function which, when applied to the {@code stateObject}, produces an instantaneous gauge value
+ * @param type of the state object which yields the gauge's value
+ * @return state object
+ */
+ T gauge(String name,
+ Iterable tags,
+ T stateObject,
+ ToDoubleFunction valueFunction);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Gauge} by its name and tags.
+ *
+ * @param name name of the gauge
+ * @param tags {@link Tag} instances which further identify the gauge
+ * @return {@link java.util.Optional} of {@code Gauge} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getGauge(String name, Iterable tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Gauge} by its name and tags.
+ *
+ * @param name name of the gauge
+ * @param tags {@link Tag} instances which further identify the gauge
+ * @return {@link java.util.Optional} of {@code Gauge} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getGauge(String name, Tag... tags);
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the specified {@link Number}
+ * instance.
+ *
+ * @param name name of the gauge
+ * @param tags further identifies the gauge
+ * @param number thread-safe implementation of {@link Number} used to access the value
+ * @param type of the number from which the gauge value is extracted
+ * @return number argument passed (so the registration can be done as part of an assignment statement)
+ */
+ T gauge(String name, Iterable tags, T number);
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the {@link Number}.
+ *
+ * @param name name of the gauge
+ * @param number thread-safe implementation of {@link Number} used to access the gauge's value
+ * @param type of the state object from which the gauge value is extracted
+ * @return number argument passed (so the registration can be done as part of an assignment statement)
+ */
+ T gauge(String name, T number);
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object by applying the specified
+ * function.
+ *
+ * @param name name of the gauge
+ * @param stateObject state object used to compute a value
+ * @param valueFunction function which, when applied to the {@code stateObject}, yields an instantaneous gauge value
+ * @param type of the state object from which the gauge value is extracted
+ * @return state object argument passed (so the registration can be done as part
+ * of an assignment statement)
+ */
+ T gauge(String name,
+ T stateObject,
+ ToDoubleFunction valueFunction);
+
+ /**
+ * Removes a previously-registered meter.
+ *
+ * @param meter the meter to remove
+ * @return the removed meter; null if the meter is not currently registered
+ */
+ Meter remove(Meter meter);
+
+ /**
+ * Removes a previously-registered meter with the specified ID.
+ *
+ * @param id {@link Meter.Id} of the meter to remove
+ * @return the removed meter; null if the specified meter ID does not correspond to a registered meter
+ */
+ Meter remove(Meter.Id id);
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java
new file mode 100644
index 00000000000..51014059bf6
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.util.ServiceLoader;
+
+import io.helidon.common.HelidonServiceLoader;
+import io.helidon.common.LazyValue;
+import io.helidon.metrics.api.spi.HelidonMetricFactory;
+
+/**
+ * Locates and makes available the highest-weight implementation of {@link io.helidon.metrics.api.spi.HelidonMetricFactory},
+ * using a default no-op implementation if no other is available.
+ */
+class MetricFactoryManager {
+
+ /**
+ * Instance of the highest-weight implementation of {@code MetricFactory}
+ */
+ static final LazyValue INSTANCE =
+ LazyValue.create(() -> HelidonServiceLoader.builder(ServiceLoader.load(HelidonMetricFactory.class))
+ .addService(HelidonNoOpMetricFactory.create(), Double.MIN_VALUE)
+ .build()
+ .iterator()
+ .next());
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java b/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java
new file mode 100644
index 00000000000..0ce26762749
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.util.function.ToDoubleFunction;
+
+/**
+ * A main entry point to the Helidon metrics implementation, allowing access to the global meter registry and providing shortcut
+ * methods to register and locate meters in the global registry and remove meters from it.
+ */
+public interface Metrics {
+
+ /**
+ * Returns the global meter registry.
+ *
+ * @return the global meter registry
+ */
+ static MeterRegistry globalRegistry() {
+ return MetricFactoryManager.INSTANCE.get().globalRegistry();
+ }
+
+ /**
+ * Registers a new or locates a previously-registered counter, using the global registry, which tracks a monotonically
+ * increasing value.
+ *
+ * @param name counter name
+ * @param tags further identification of the counter
+ * @return new or previously-registered counter
+ */
+ static Counter counter(String name, Iterable tags) {
+ return globalRegistry().counter(name, tags);
+ }
+
+ /**
+ * Registers a new or locates a previously-registered counter, using the global registry, which tracks a monotonically
+ * increasing value.
+ *
+ * @param name counter name
+ * @param tags further identification of the counter; MUST be an even number of arguments representing key/value pairs
+ * of tags
+ * @return new or previously-registered counter
+ */
+ static Counter counter(String name, String... tags) {
+ return globalRegistry().counter(name, tags);
+ }
+
+ /**
+ * Registers a new or locates a previously-registered counter, using the global registry, which tracks a monotonically
+ * increasing value that is maintained by an external object, not a counter furnished by the meter registry itself.
+ *
+ *
+ * The counter returned rejects attempts to increment its value because the external object, not the counter itself,
+ * maintains the value.
+ *
+ *
+ * @param name counter name
+ * @param tags further identification of the counter
+ * @param target object which, when the function is applied, yields the counter value
+ * @param fn function which produces the counter value
+ * @return new or existing counter
+ * @param type of the object which furnishes the counter value
+ */
+ static Counter counter(String name, Iterable tags, T target, ToDoubleFunction fn) {
+ return globalRegistry().counter(name, tags, target, fn);
+ }
+
+ /**
+ * Registers a new or locates a previously-registered distribution summary, using the global registry, which measures the
+ * distribution of samples.
+ *
+ * @param name summary name
+ * @param tags further identification of the summary
+ * @return new or previously-registered distribution summary
+ */
+ static DistributionSummary summary(String name, Iterable tags) {
+ return globalRegistry().summary(name, tags);
+ }
+
+ /**
+ * Registers a new or locates a previously-registered distribution summary, using the global registry, which measures the
+ * distribution of samples.
+ *
+ * @param name summary name
+ * @param tags further identification of the summary; MUST be an even number of arguments representing key/value pairs
+ * of tags
+ * @return new or previously-registered distribution summary
+ */
+ static DistributionSummary summary(String name, String... tags) {
+ return globalRegistry().summary(name, tags);
+ }
+
+ /**
+ * Registers a new or locates a previously-registered timer, using the global registry, which measures the time taken for
+ * short tasks and the count of those tasks.
+ *
+ * @param name timer name
+ * @param tags further identification of the timer
+ * @return new or previously-registered timer
+ */
+ static Timer timer(String name, Iterable tags) {
+ return globalRegistry().timer(name, tags);
+ }
+
+ /**
+ * Registers a new or locates a previously-registered timer, using the global registry, which measures the time taken for
+ * short tasks and the count of those tasks.
+ *
+ * @param name timer name
+ * @param tags further identification of the timer; MUST be an even number of arguments representing key/value pairs of tags.
+ * @return new or previously-registered timer
+ */
+ static Timer timer(String name, String... tags) {
+ return globalRegistry().timer(name, tags);
+ }
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge}, using the global registry, that reports the double value
+ * maintained by the specified object and exposed by the object by applying the specified function.
+ *
+ * @param name name of the gauge
+ * @param tags further identification of the gauge
+ * @param obj object which exposes the gauge value
+ * @param valueFunction function which, when applied to the object, yields the gauge value
+ * @param type of the state object which maintains the gauge's value
+ * @return state object
+ */
+ static T gauge(String name, Iterable tags, T obj, ToDoubleFunction valueFunction) {
+ return globalRegistry().gauge(name, tags, obj, valueFunction);
+ }
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge}, using the global registry, which wraps a specific
+ * {@link java.lang.Number} instance.
+ *
+ * @param name name of the gauge
+ * @param tags further identification of the gauge
+ * @param number thread-safe implementation of the specified subtype of {@link java.lang.Number} which is the gauge's value
+ * @param specific subtype of {@code Number} which the wrapped object exposes
+ * @return {@code number} wrapped by this gauge
+ */
+ static N gauge(String name, Iterable tags, N number) {
+ return globalRegistry().gauge(name, tags, number);
+ }
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge}, using the global registry, which wraps a specific
+ * {@link java.lang.Number} instance.
+ *
+ * @param name name of the gauge
+ * @param number thread-safe implementation of the specified subtype of {@link java.lang.Number} which is the gauge's value
+ * @param specific subtype of {@code Number} which the wrapped object exposes
+ * @return {@code number} wrapped by this gauge
+ */
+ static N gauge(String name, N number) {
+ return globalRegistry().gauge(name, number);
+ }
+
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge}, using the global registry, that reports the double value
+ * maintained by the specified object and exposed by the object by applying the specified function.
+ *
+ * @param name name of the gauge
+ * @param obj object which exposes the gauge value
+ * @param valueFunction function which, when applied to the object, yields the gauge value
+ * @param type of the state object which maintains the gauge's value
+ * @return state object
+ */
+ static T gauge(String name, T obj, ToDoubleFunction valueFunction) {
+ return globalRegistry().gauge(name, obj, valueFunction);
+ }
+}
From 6eaa3163342d6f0312a580727e7e22e81ca4f64d Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Fri, 28 Jul 2023 17:18:09 -0500
Subject: [PATCH 02/40] Some clean-up of earlier push; add more API interfaces
and no-op implementation
---
.../java/io/helidon/metrics/api/Clock.java | 47 ++
.../io/helidon/metrics/api/CountAtBucket.java | 51 +++
.../java/io/helidon/metrics/api/Counter.java | 41 ++
.../api/DistributionStatisticsConfig.java | 204 +++++++++
.../metrics/api/DistributionSummary.java | 58 +++
.../java/io/helidon/metrics/api/Gauge.java | 34 ++
.../metrics/api/HelidonNoOpMetricFactory.java | 118 ++++++
.../metrics/api/HistogramSnapshot.java | 106 +++++
.../helidon/metrics/api/HistogramSupport.java | 29 ++
.../java/io/helidon/metrics/api/Meter.java | 113 +++++
.../io/helidon/metrics/api/MeterRegistry.java | 299 ++++++++-----
.../metrics/api/MetricFactoryManager.java | 5 +-
.../java/io/helidon/metrics/api/Metrics.java | 56 ++-
.../io/helidon/metrics/api/NoOpMeter.java | 400 ++++++++++++++++++
.../metrics/api/NoOpMeterRegistry.java | 384 +++++++++++++++++
.../java/io/helidon/metrics/api/NoOpTag.java | 51 +++
.../main/java/io/helidon/metrics/api/Tag.java | 47 ++
.../java/io/helidon/metrics/api/Timer.java | 178 ++++++++
.../metrics/api/ValueAtPercentile.java | 47 ++
.../java/io/helidon/metrics/api/Wrapped.java | 33 ++
.../metrics/api/spi/HelidonMetricFactory.java | 235 ++++++++++
metrics/micrometer/pom.xml | 58 +++
.../micrometer/MicrometerMetricFactory.java | 61 +++
.../metrics/micrometer/MicrometerTag.java | 45 ++
.../metrics/micrometer/MicrometerTags.java | 129 ++++++
.../metrics/micrometer/package-info.java | 20 +
.../micrometer/src/main/java/module-info.java | 28 ++
27 files changed, 2739 insertions(+), 138 deletions(-)
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Clock.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/CountAtBucket.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Counter.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/DistributionStatisticsConfig.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/DistributionSummary.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Gauge.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/HelidonNoOpMetricFactory.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/HistogramSnapshot.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/HistogramSupport.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Meter.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeter.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/NoOpTag.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Tag.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Timer.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/ValueAtPercentile.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/Wrapped.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/spi/HelidonMetricFactory.java
create mode 100644 metrics/micrometer/pom.xml
create mode 100644 metrics/micrometer/src/main/java/io/helidon/metrics/micrometer/MicrometerMetricFactory.java
create mode 100644 metrics/micrometer/src/main/java/io/helidon/metrics/micrometer/MicrometerTag.java
create mode 100644 metrics/micrometer/src/main/java/io/helidon/metrics/micrometer/MicrometerTags.java
create mode 100644 metrics/micrometer/src/main/java/io/helidon/metrics/micrometer/package-info.java
create mode 100644 metrics/micrometer/src/main/java/module-info.java
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Clock.java b/metrics/api/src/main/java/io/helidon/metrics/api/Clock.java
new file mode 100644
index 00000000000..9667a819d3c
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Clock.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+/**
+ * Reports absolute time (and, therefore, is also useful in computing elapsed times).
+ */
+public interface Clock extends Wrapped {
+
+ /**
+ * Returns the current wall time in milliseconds since the epoch.
+ *
+ *
+ * Typically equivalent to System.currentTimeMillis. Should not be used to determine durations.
+ * For that use {@link #monotonicTime()} instead.
+ *
+ *
+ * @return wall time in milliseconds
+ */
+ long wallTime();
+
+ /**
+ * Returns the current time in nanoseconds from a monotonic clock source.
+ *
+ *
+ * The value is only meaningful when compared with another value returned from this method to determine the elapsed time
+ * for an operation. The difference between two samples will have a unit of nanoseconds. The returned value is
+ * typically equivalent to System.nanoTime.
+ *
+ *
+ * @return monotonic time in nanoseconds
+ */
+ long monotonicTime();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/CountAtBucket.java b/metrics/api/src/main/java/io/helidon/metrics/api/CountAtBucket.java
new file mode 100644
index 00000000000..11e53ebacf9
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/CountAtBucket.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Representation of a histogram bucket, including the bucket boundary value and the count of observations in that bucket.
+ *
+ * The bucket boundary value is an upper bound on the observation values that can occupy the bucket.
+ * That is, an observation occupies a bucket if its value is less than or equal to the bucket's boundary value.
+ *
+ */
+public interface CountAtBucket extends Wrapped {
+
+ /**
+ * Returns the bucket boundary.
+ *
+ * @return bucket boundary value
+ */
+ double bucket();
+
+ /**
+ * Returns the bucket boundary interpreted as a time in nanoseconds andexpressed in the specified
+ * {@link java.util.concurrent.TimeUnit}.
+ *
+ * @param unit time unit in which to express the bucket boundary
+ * @return bucket boundary value
+ */
+ double bucket(TimeUnit unit);
+
+ /**
+ * Returns the number of observations in the bucket.
+ *
+ * @return observation count for the bucket
+ */
+ double count();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Counter.java b/metrics/api/src/main/java/io/helidon/metrics/api/Counter.java
new file mode 100644
index 00000000000..04f728a117d
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Counter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+/**
+ * Records a monotonically increasing value.
+ */
+public interface Counter extends Meter {
+
+ /**
+ * Updates the counter by one.
+ */
+ void increment();
+
+ /**
+ * Updates the counter by the specified amount which should be non-negative.
+ *
+ * @param amount amount to add to the counter.
+ */
+ void increment(double amount);
+
+ /**
+ * Returns the cumulative count since this counter was registered.
+ *
+ * @return cumulative count since this counter was registered
+ */
+ double count();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/DistributionStatisticsConfig.java b/metrics/api/src/main/java/io/helidon/metrics/api/DistributionStatisticsConfig.java
new file mode 100644
index 00000000000..50921bcb432
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/DistributionStatisticsConfig.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.time.Duration;
+
+/**
+ * Configuration which controls the behavior of distribution statistics from meters that support them
+ * (for example, timers and distribution summaries).
+ *
+ */
+public interface DistributionStatisticsConfig extends Wrapped {
+
+ /**
+ * Returns whether the configuration is set for percentile histograms which can be aggregated for percentile approximations.
+ *
+ * @return whether percentile histograms are configured
+ */
+ boolean isPercentileHistogram();
+
+ /**
+ * Returns whether the configuration is set to publish percentiles.
+ *
+ * @return true/false
+ */
+ boolean isPublishingPercentiles();
+
+ /**
+ * Returns whether the configuration is set to publish a histogram.
+ *
+ * @return true/false
+ */
+ boolean isPublishingHistogram();
+
+ /**
+ * Returns the settings for non-aggregable percentiles.
+ *
+ * @return percentiles to compute and publish
+ */
+ Iterable percentiles();
+
+ /**
+ * Returns the configured number of digits of precision for percentiles.
+ *
+ * @return digits of precision to maintain for percentile approximations
+ */
+ int percentilePrecision();
+
+ /**
+ * Returns the maximum value that the meter is expected to observe.
+ *
+ * @return maximum value that the meter is expected to observe
+ */
+ double maximumExpectedValue();
+
+ /**
+ * Returns how long decaying past observations remain in the ring buffer.
+ *
+ * @see #bufferLength()
+ * @return time during which samples accumulate in a histogram
+ */
+ Duration expiry();
+
+ /**
+ * Returns the size of the ring buffer for holding decaying observations.
+ *
+ * @return number of observations to keep in the ring buffer
+ */
+ int bufferLength();
+
+ /**
+ * Returns the configured service level objective boundaries.
+ *
+ * @return the SLO boundaries
+ */
+ Iterable serviceLevelObjectiveBoundaries();
+
+ /**
+ * Builder for a new {@link io.helidon.metrics.api.DistributionStatisticsConfig} instance.
+ */
+ interface Builder extends io.helidon.common.Builder {
+
+ /**
+ * Updates the builder with non-null settings from the specified existing config.
+ *
+ * @param config the {@link io.helidon.metrics.api.DistributionStatisticsConfig} instance to use to
+ * set values in this builder
+ * @return updated builder
+ */
+ Builder merge(DistributionStatisticsConfig config);
+
+ /**
+ * Sets how long to keep samples before they are assumed to have decayed to zero and are discareded.
+ *
+ * @param expiry how long to retain samples
+ * @return updated builder
+ */
+ Builder expiry(Duration expiry);
+
+ /**
+ * Sets the size of the ring buffer which holds saved samples as they decay.
+ *
+ * @param bufferLength number of histograms to keep in the ring buffer
+ * @return updated builder
+ */
+ Builder bufferLength(int bufferLength);
+
+ /**
+ * Sets whether to publish percentiles histograms (which are aggregable).
+ *
+ * @param enabled true to publish percentile histograms; false otherwise
+ * @return updated builder
+ */
+ Builder percentilesHistogram(boolean enabled);
+
+ /**
+ * Sets the minimum value that the meter is expected to observe.
+ *
+ * @param min minimum value that this distribution summary is expected to observe
+ * @return updated builder
+ */
+ Builder minimumExpectedValue(double min);
+
+ /**
+ * Sets the maximum value that the meter is expected to observe.
+ *
+ * @param max maximum value that the meter is expected to observe
+ * @return updated builder
+ */
+ Builder maximumExpectedValue(double max);
+
+ /**
+ * Specifies additional time series percentiles.
+ *
+ * The system computes these percentiles locally, so they cannot be aggregated with percentiles computed
+ * elsewhere. In contrast, a percentile histogram triggered by invoking {@link #percentilesHistogram} can
+ * be aggregated.
+ *
+ *
+ * Specify percentiles a decimals, for example express the 95th percentile as {@code 0.95}.
+ *
+ * @param percentiles percentiles to compute and publish
+ * @return updated builder
+ */
+ Builder percentiles(double... percentiles);
+
+ /**
+ * Specifies additional time series percentiles.
+ *
+ * The system computes these percentiles locally, so they cannot be aggregated with percentiles computed
+ * elsewhere. In contrast, a percentile histogram triggered by invoking {@link #percentilesHistogram} can
+ * be aggregated.
+ *
+ *
+ * Specify percentiles a decimals, for example express the 95th percentile as {@code 0.95}.
+ *
+ * @param percentiles percentiles to compute and publish
+ * @return updated builder
+ */
+ Builder percentiles(Iterable percentiles);
+
+ /**
+ * Sets the number of digits of precision to maintain on the dynamic range
+ * histogram used to compute percentile approximations.
+ *
+ * @param digitsOfPrecision digits of precision to maintain for percentile approximations
+ * @return updated builder
+ */
+ Builder percentilePrecision(Integer digitsOfPrecision);
+
+ /**
+ * Sets the service level objective (SLO) boundaries which, when used with
+ * {@link #percentilesHistogram(boolean)}, adds the boundaries defined here
+ * to other buckets used to generate aggregable percentile approximations.
+ *
+ * @param slos SLO boundaries
+ * @return updated builder
+ */
+ Builder serviceLevelObjectives(double... slos);
+
+ /**
+ * Sets the service level objective (SLO) boundaries which, when used with
+ * {@link #percentilesHistogram(boolean)}, adds the boundaries defined here
+ * to other buckets used to generate aggregable percentile approximations.
+ *
+ * @param slos SLO boundaries
+ * @return updated builder
+ */
+ Builder serviceLevelObjectives(Iterable slos);
+ }
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/DistributionSummary.java b/metrics/api/src/main/java/io/helidon/metrics/api/DistributionSummary.java
new file mode 100644
index 00000000000..db1bf6d251b
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/DistributionSummary.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+/**
+ * Records a distribution of values (e.g., sizes of responses returned by a server).
+ */
+public interface DistributionSummary extends Meter {
+
+ /**
+ * Updates the statistics kept by the summary with the specified amount.
+ *
+ * @param amount Amount for an event being measured. For example, if the size in bytes of responses
+ * from a server. If the amount is less than 0 the value will be dropped.
+ */
+ void record(double amount);
+
+ /**
+ * Returns the current count of observations in the distribution summary.
+ *
+ * @return number of observations recorded in the summary
+ */
+ long count();
+
+ /**
+ * Returns the total of the observations recorded by the distribution summary.
+ *
+ * @return total across all recorded events
+ */
+ double totalAmount();
+
+ /**
+ * Returns the mean of the observations recorded by the distribution summary.
+ *
+ * @return average value of events recorded in the summary
+ */
+ double mean();
+
+ /**
+ * Returns the maximum value among the observations recorded by the distribution summary.
+ *
+ * @return maximum value of recorded events
+ */
+ double max();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Gauge.java b/metrics/api/src/main/java/io/helidon/metrics/api/Gauge.java
new file mode 100644
index 00000000000..07990d827b1
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Gauge.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+/**
+ * Measures a value that can increase or decrease and is updated by external logic, not by explicit invocations
+ * of methods on this type.
+ */
+public interface Gauge extends Meter {
+
+ /**
+ * Returns the value of the gauge.
+ *
+ * Invoking this method triggers the sampling of the value or invocation of the function provided when the gauge was
+ * registered.
+ *
+ *
+ * @return current value of the gauge
+ */
+ double value();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/HelidonNoOpMetricFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/HelidonNoOpMetricFactory.java
new file mode 100644
index 00000000000..ec4bd18bdce
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/HelidonNoOpMetricFactory.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.util.function.Function;
+import java.util.function.ToDoubleFunction;
+
+import io.helidon.metrics.api.spi.HelidonMetricFactory;
+
+/**
+ * No-op implementation of the {@link io.helidon.metrics.api.spi.MetricFactory} interface.
+ */
+class HelidonNoOpMetricFactory implements HelidonMetricFactory {
+
+ private final MeterRegistry meterRegistry = null;
+
+ static HelidonNoOpMetricFactory create() {
+ return new HelidonNoOpMetricFactory();
+ }
+
+ @Override
+ public MeterRegistry globalRegistry() {
+ return null;
+ }
+
+ @Override
+ public Tag tagOf(String key, String value) {
+ return new NoOpTag(key, value);
+ }
+
+ @Override
+ public Counter metricsCounter(String name, Iterable tags) {
+ return null;
+ }
+
+ @Override
+ public Counter metricsCounter(String name, String... tags) {
+ return null;
+ }
+
+ @Override
+ public Counter metricsCounter(String name, Iterable tags, T target, Function fn) {
+ return null;
+ }
+
+ @Override
+ public DistributionSummary metricsSummary(String name, Iterable tags) {
+ return null;
+ }
+
+ @Override
+ public DistributionSummary metricsSummary(String name, String... tags) {
+ return null;
+ }
+
+ @Override
+ public Timer metricsTimer(String name, Iterable tags) {
+ return null;
+ }
+
+ @Override
+ public Timer metricsTimer(String name, String... tags) {
+ return null;
+ }
+
+ @Override
+ public T metricsGauge(String name, Iterable tags, T obj, ToDoubleFunction valueFunction) {
+ return null;
+ }
+
+ @Override
+ public T metricsGauge(String name, Iterable tags, T number) {
+ return null;
+ }
+
+ @Override
+ public T metricsGauge(String name, T number) {
+ return null;
+ }
+
+ @Override
+ public T metricsGauge(String name, T obj, ToDoubleFunction valueFunction) {
+ return null;
+ }
+
+ @Override
+ public HistogramSnapshot histogramSnapshotEmpty(long count, double total, double max) {
+ return null;
+ }
+
+ @Override
+ public Timer.Sample timerStart() {
+ return null;
+ }
+
+ @Override
+ public Timer.Sample timerStart(MeterRegistry registry) {
+ return null;
+ }
+
+ @Override
+ public Timer.Sample timerStart(Clock clock) {
+ return null;
+ }
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/HistogramSnapshot.java b/metrics/api/src/main/java/io/helidon/metrics/api/HistogramSnapshot.java
new file mode 100644
index 00000000000..8202c15d22f
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/HistogramSnapshot.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Snapshot in time of a histogram.
+ */
+public interface HistogramSnapshot extends Wrapped {
+
+ /**
+ * Returns an "empty" snapshot which has summary values but no data points.
+ *
+ * @param count count of observations the snapshot should report
+ * @param total total value of observations the snapshot should report
+ * @param max maximum value the snapshot should report
+ * @return empty snapshot reporting the values as specified
+ */
+ static HistogramSnapshot empty(long count, double total, double max) {
+ return MetricFactoryManager.INSTANCE.get().histogramSnapshotEmpty(count, total, max);
+ }
+
+ /**
+ * Returns the count of observations in the snapshot.
+ *
+ * @return count of observations
+ */
+ long count();
+
+ /**
+ * Returns the total value over all observations in the snapshot.
+ *
+ * @return total value over all observations
+ */
+ double total();
+
+ /**
+ * Returns the total value over all observations, interpreting the values as times in nanoseconds and expressing the time
+ * in the specified {@link java.util.concurrent.TimeUnit}.
+ *
+ * @param timeUnit time unit in which to express the total value
+ * @return total value expressed in the selected time unit
+ */
+ double total(TimeUnit timeUnit);
+
+ /**
+ * Returns the maximum value over all observations.
+ *
+ * @return maximum value
+ */
+ double max();
+
+ /**
+ * Returns the average value overall observations.
+ *
+ * @return average value
+ */
+ double mean();
+
+ /**
+ * Returns the average value over all observations, interpreting the values as times in nanoseconds and expressing the
+ * average in the specified {@link java.util.concurrent.TimeUnit}.
+ *
+ * @param timeUnit time unitin which to express the average
+ * @return average value expressed in the selected time unit
+ */
+ double mean(TimeUnit timeUnit);
+
+ /**
+ * Returns the values at the configured percentiles for the histogram.
+ *
+ * @return array of pairs of percentile and the histogram value at that percentile
+ */
+ ValueAtPercentile[] percentileValues();
+
+ /**
+ * Returns information about each of the configured buckets for the histogram.
+ *
+ * @return array of pairs of bucket value and count of observations in that bucket
+ */
+ CountAtBucket[] histogramCounts();
+
+ /**
+ * Dumps a summary of the snapshot to the specified {@link java.io.PrintStream} using the indicated scaling factor for
+ * observations.
+ *
+ * @param out {@code PrintStream} to which to dump the snapshot summary
+ * @param scale scale factor to apply to observations for output
+ */
+ void outputSummary(PrintStream out, double scale);
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/HistogramSupport.java b/metrics/api/src/main/java/io/helidon/metrics/api/HistogramSupport.java
new file mode 100644
index 00000000000..0f9568db08e
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/HistogramSupport.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+/**
+ * Common behavior among meters which support histograms.
+ */
+public interface HistogramSupport extends Meter {
+
+ /**
+ * Returns a snapshot of the data in a histogram.
+ *
+ * @return snapshot of the histogram
+ */
+ HistogramSnapshot takeSnapshot();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Meter.java b/metrics/api/src/main/java/io/helidon/metrics/api/Meter.java
new file mode 100644
index 00000000000..23ed1055419
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Meter.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+/**
+ * Common behavior of all meters.
+ */
+public interface Meter extends Wrapped {
+
+ /**
+ * Unique idenfier for a meter.
+ */
+ interface Id {
+
+ /**
+ * Returns the meter name.
+ *
+ * @return meter name
+ */
+ String name();
+
+ /**
+ * Returns the tags which further identify the meter.
+ *
+ * @return meter tags
+ */
+ Iterable tags();
+
+ /**
+ * Unwraps the ID as the specified type.
+ *
+ * @param c {@link Class} to which to cast this ID
+ * @return the ID cast as the requested type
+ * @param type to cast to
+ */
+ default R unwrap(Class extends R> c) {
+ return c.cast(this);
+ }
+ }
+
+ /**
+ * Type of meter.
+ */
+ enum Type {
+
+ /**
+ * Counter (monotonically increasing value).
+ */
+ COUNTER,
+
+ /**
+ * Gauge (can increase or decrease).
+ */
+ GAUGE,
+
+ /**
+ * Timer (measures count and distribution of completed events).
+ */
+ TIMER,
+
+ /**
+ * Distribution summary (measures distribution of samples).
+ */
+ DISTRIBUTION_SUMMARY,
+
+ /**
+ * Other.
+ */
+ OTHER;
+
+ }
+
+ /**
+ * Returns the meter ID.
+ *
+ * @return meter ID
+ */
+ Id id();
+
+ /**
+ * Returns the meter's base unit.
+ *
+ * @return base unit
+ */
+ String baseUnit();
+
+ /**
+ * Returns the meter's description.
+ *
+ * @return description
+ */
+ String description();
+
+ /**
+ * Returns the meter type.
+ *
+ * @return meter type
+ */
+ Type type();
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java b/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java
index 52baa6f96b4..859738726b8 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MeterRegistry.java
@@ -26,23 +26,16 @@
*
* This interface supports two types of retrieval (using {@link io.helidon.metrics.api.Counter} as an example):
*
- * - retrieve or create - {@link #counter(io.helidon.metrics.api.Meter.Id)} - returns the meter if it was previously
+ *
- retrieve or create - {@link #counter(String, Iterable)} - returns the meter if it was previously
* registered, otherwise creates and registers the meter
- * - retrieve only - {@link #getCounter(io.helidon.metrics.api.Meter.Id)}
- returns an {@link java.util.Optional}
- * for the meter, empty if the meter has not been registered and non-empty if it has been registered.
+ * - retrieve only - {@link #getCounter(String, Iterable)} - returns an {@link java.util.Optional}
+ * for the meter, empty if the meter has not been registered and non-empty if it has been registered.
*
- *
*
- * For most meter types, this interface provides two general variants of the retrieve-or-create-method for each meter
- * (again using {@link io.helidon.metrics.api.Counter} as an example):
- *
- * - by ID - {@link #counter(io.helidon.metrics.api.Meter.Id)} - the caller prepares the ID
- * - by name and tags - {@link #counter(String, Iterable)} and {@link #counter(String, String...)}- the caller need not
- * prepare the ID
- *
+ * The meter registry uniquely identifies each meter by its name and tags (if any).
*
*/
-public interface MeterRegistry {
+public interface MeterRegistry extends Wrapped {
/**
* Returns all previously-registered meters.
@@ -60,76 +53,107 @@ public interface MeterRegistry {
Iterable meters(Predicate filter);
/**
- * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its ID.
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its
+ * {@link io.helidon.metrics.api.Meter.Id}.
*
- * @param id {@link Meter.Id} to register or locate
+ * @param id {@link io.helidon.metrics.api.Meter.Id} for the counter
* @return new or previously-registered counter
*/
Counter counter(Meter.Id id);
/**
- * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its ID or registers a new one
- * which wraps an external target object which provides the counter value.
- *
- *
- * The counter returned rejects attempts to increment its value because the external object, not the counter itself,
- * maintains the value.
- *
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its
+ * {@link io.helidon.metrics.api.Meter.Id}.
*
- * @param id {@link Meter.Id} to register or locate
- * @param target object which provides the counter value
- * @param fn function which, when applied to the target, returns the counter value
- * @return the target object
+ * @param id {@link io.helidon.metrics.api.Meter.Id} for the counter
+ * @param target object which maintains the counter value
+ * @param fn function which, when applied to the target, yields the counter value
+ * @return new or previously-registered counter
* @param type of the target object
*/
- Counter counter(Meter.Id id, T target, ToDoubleFunction fn);
-
+ Counter counter(Meter.Id id,
+ T target,
+ ToDoubleFunction fn);
/**
- * Locates a previous-registered {@link io.helidon.metrics.api.Counter} by its ID.
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
*
- * @param id {@link io.helidon.metrics.api.Meter.Id} to locate
- * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ * @param name counter name
+ * @param tags tags which further identify the counter
+ * @return new or previously-registered counter
*/
- Optional getCounter(Meter.Id id);
+ Counter counter(String name,
+ Iterable tags);
/**
- * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter}.
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
*
* @param name counter name
- * @param tags tags for further identifying the counter
- * @return new or existing counter
+ * @param tags key/value pairs for further identifying the counter; MUST be an even number of arguments
+ * @return new or previously-registered counter
*/
- Counter counter(String name, Iterable tags);
+ Counter counter(String name,
+ String... tags);
/**
- * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
+ * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
*
* @param name counter name
- * @param tags counter {@link io.helidon.metrics.api.Tag} instances which further identify the counter
- * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ * @param tags tags which further identify the counter
+ * @param baseUnit unit for the counter
+ * @param description counter description
+ * @return new or previously-registered counter
*/
- Optional getCounter(String name, Iterable tags);
+ Counter counter(String name,
+ Iterable tags,
+ String baseUnit,
+ String description);
/**
- * Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Counter}.
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags or registers a new one
+ * which wraps an external target object which provides the counter value.
*
- * @param name counter name
- * @param tags key/value pairs; MUST be an even number of arguments
- * @return new or existing counter
+ *
+ * The counter returned rejects attempts to increment its value because the external object, not the counter itself,
+ * maintains the value.
+ *
+ *
+ * @param id {@link io.helidon.metrics.api.Meter.Id} for the counter
+ * @param baseUnit unit for the counter
+ * @param description counter description
+ * @param target object which provides the counter value
+ * @param fn function which, when applied to the target, returns the counter value
+ * @return the target object
+ * @param type of the target object
*/
- Counter counter(String name, String... tags);
+ Counter counter(Meter.Id id,
+ String baseUnit,
+ String description,
+ T target,
+ ToDoubleFunction fn);
/**
- * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
+ * Registers a new or locates a previously-registered counter, using the global registry, which tracks a monotonically
+ * increasing value that is maintained by an external object, not a counter furnished by the meter registry itself.
+ *
+ *
+ * The counter returned rejects attempts to increment its value because the external object, not the counter itself,
+ * maintains the value.
+ *
*
* @param name counter name
- * @param tags counter {@link io.helidon.metrics.api.Tag} instances which further identify the counter
- * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ * @param tags further identification of the counter
+ * @param target object which, when the function is applied, yields the counter value
+ * @param fn function which produces the counter value
+ * @return new or existing counter
+ * @param type of the object which furnishes the counter value
*/
- Optional getCounter(String name, String... tags);
+ Counter counter(String name,
+ Iterable tags,
+ T target,
+ ToDoubleFunction fn);
/**
- * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name dnd tags or registers a new one
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags or registers a new one
* which wraps an external target object which provides the counter value.
*
*
@@ -138,34 +162,57 @@ public interface MeterRegistry {
*
*
* @param name counter name
- * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the counter
+ * @param tags tags which further identify the counter
+ * @param baseUnit unit for the counter
+ * @param description counter description
* @param target object which provides the counter value
* @param fn function which, when applied to the target, returns the counter value
* @return the target object
* @param type of the target object
*/
- Counter counter(String name, Iterable tags, T target, ToDoubleFunction fn);
+ Counter counter(String name,
+ Iterable tags,
+ String baseUnit,
+ String description,
+ T target,
+ ToDoubleFunction fn);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
+ *
+ * @param name counter name
+ * @param tags tags for further identifying the counter
+ * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getCounter(String name,
+ Iterable tags);
+
+ /**
+ * Locates a previously-registered {@link io.helidon.metrics.api.Counter} by its name and tags.
+ *
+ * @param name counter name
+ * @param tags tags for further identifying the counter
+ * @return {@link java.util.Optional} of {@code Counter} if found; {@code Optional.empty()} otherwise
+ */
+ Optional getCounter(String name,
+ String... tags);
/**
* Creates a new or locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary}.
*
- * @param id {@link Meter.Id} for the summary
+ * @param id {@link io.helidon.metrics.api.Meter.Id} for the summary
+ * @param baseUnit unit for the counter
+ * @param description counter description
* @param distributionStatisticsConfig configuration governing distribution statistics calculations
* @param scale scaling factor to apply to every sample recorded by the summary
* @return new or existing summary
*/
DistributionSummary summary(Meter.Id id,
+ String baseUnit,
+ String description,
DistributionStatisticsConfig distributionStatisticsConfig,
double scale);
- /**
- * Locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary} by its ID.
- *
- * @param id {@link io.helidon.metrics.api.Meter.Id} to locate
- * @return {@link java.util.Optional} of {@code DistributionSummary} if found; {@code Optional.empty()} otherwise
- */
- Optional getSummary(Meter.Id id);
-
/**
* Creates a new or locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary}.
*
@@ -173,51 +220,54 @@ DistributionSummary summary(Meter.Id id,
* @param tags tags for further identifying the summary
* @return new or existing distribution summary
*/
- DistributionSummary summary(String name, Iterable tags);
+ DistributionSummary summary(String name,
+ Iterable tags);
/**
* Locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary} by its name and tags.
*
* @param name summary name to locate
- * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the summary
+ * @param tags tags for further identifying the summary
* @return {@link java.util.Optional} of {@code DistributionSummary} if found; {@code Optional.empty()} otherwise
*/
- Optional getSummary(String name, Iterable tags);
+ Optional getSummary(String name,
+ Iterable tags);
/**
* Creates a new or locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary}.
*
* @param name summary name
- * @param tags key/value pairs; MUST be an even number of arguments
+ * @param tags key/value pairs for further identifying the summary; MUST be an even number of arguments
* @return new or existing distribution summary
*/
- DistributionSummary summary(String name, String... tags);
+ DistributionSummary summary(String name,
+ String... tags);
/**
* Locates a previously-registered {@link io.helidon.metrics.api.DistributionSummary} by its name and tags.
*
* @param name summary name to locate
- * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the summary
+ * @param tags tags for further identifying the summary
* @return {@link java.util.Optional} of {@code DistributionSummary} if found; {@code Optional.empty()} otherwise
*/
- Optional getSummary(String name, Tag... tags);
+ Optional getSummary(String name,
+ Tag... tags);
/**
* Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Timer}.
*
- * @param id ID for the timer
+ * @param name timer name
+ * @param tags tags for further identifying the timer
+ * @param baseUnit unit for the timer
+ * @param description timer description
* @param distributionStatisticsConfig configuration governing distribution statistics calculations
* @return new or existing timer
*/
- Timer timer(Meter.Id id, DistributionStatisticsConfig distributionStatisticsConfig);
-
- /**
- * Locates a previously-registered {@link io.helidon.metrics.api.Timer} by its ID.
- *
- * @param id ID for the timer
- * @return {@link java.util.Optional} of {@code Timer} if found; {@code Optional.empty()} otherwise
- */
- Optional getTimer(Meter.Id id);
+ Timer timer(String name,
+ Iterable tags,
+ String baseUnit,
+ String description,
+ DistributionStatisticsConfig distributionStatisticsConfig);
/**
* Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Timer}.
@@ -226,63 +276,47 @@ DistributionSummary summary(Meter.Id id,
* @param tags tags for further identifying the timer
* @return new or existing timer
*/
- Timer timer(String name, Iterable tags);
+ Timer timer(String name,
+ Iterable tags);
/**
* Locates a previously-registered {@link io.helidon.metrics.api.Timer} by its name and tags.
*
* @param name timer name
- * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the timer
+ * @param tags tags for further identifying the timer
* @return {@link java.util.Optional} of {@code Timer} if found; {@code Optional.empty()} otherwise
*/
- Optional getTimer(String name, Iterable tags);
+ Optional getTimer(String name,
+ Iterable tags);
/**
* Creates a new or locates a previously-registered {@link io.helidon.metrics.api.Timer}.
*
* @param name timer name
- * @param tags key/value pairs; MUST be an even number of arguments
+ * @param tags tag key/value pairs for further identifying the timer; MUST be an even number of arguments
* @return new or existing timer
*/
- Timer timer(String name, String... tags);
+ Timer timer(String name,
+ String... tags);
/**
* Locates a previously-registered {@link io.helidon.metrics.api.Timer} by its name and tags.
*
* @param name timer name
- * @param tags {@link io.helidon.metrics.api.Tag} instances which further identify the timer
+ * @param tags tags for further identifying the timer
* @return {@link java.util.Optional} of {@code Timer} if found; {@code Optional.empty()} otherwise
*/
- Optional getTimer(String name, Tag... tags);
-
- /**
- * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object returned by applying
- * the specified {@code valueFunction}.
- *
- * @param id {@link io.helidon.metrics.api.Meter.Id} of the gauge
- * @param stateObject object to which the {@code valueFunction} is applied to obtain the gauge's value
- * @param fn function which, when applied to the {@code stateObject}, produces an instantaneous gauge value
- * @param type of the state object which yields the gauge's value
- * @return state object
- */
- T gauge(Meter.Id id,
- T stateObject,
- ToDoubleFunction fn);
-
- /**
- * Locates a previously-registered {@link io.helidon.metrics.api.Gauge} by its ID.
- *
- * @param id ID for the gauge
- * @return {@link java.util.Optional} of {@code Gauge} if found; {@code Optional.empty()} otherwise
- */
- Optional getGauge(Meter.Id id);
+ Optional getTimer(String name,
+ Tag... tags);
/**
* Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object returned by applying
* the specified {@code valueFunction}.
*
* @param name name of the gauge
- * @param tags further identification of the gauge
+ * @param tags tags for further identifying the gauge
+ * @param baseUnit base unit for the gauge
+ * @param description gauge description
* @param stateObject object to which the {@code valueFunction} is applied to obtain the gauge's value
* @param valueFunction function which, when applied to the {@code stateObject}, produces an instantaneous gauge value
* @param type of the state object which yields the gauge's value
@@ -290,6 +324,8 @@ T gauge(Meter.Id id,
*/
T gauge(String name,
Iterable tags,
+ String baseUnit,
+ String description,
T stateObject,
ToDoubleFunction valueFunction);
@@ -297,31 +333,35 @@ T gauge(String name,
* Locates a previously-registered {@link io.helidon.metrics.api.Gauge} by its name and tags.
*
* @param name name of the gauge
- * @param tags {@link Tag} instances which further identify the gauge
+ * @param tags tags for further identifying the gauge
* @return {@link java.util.Optional} of {@code Gauge} if found; {@code Optional.empty()} otherwise
*/
- Optional getGauge(String name, Iterable tags);
+ Optional getGauge(String name,
+ Iterable tags);
/**
* Locates a previously-registered {@link io.helidon.metrics.api.Gauge} by its name and tags.
*
* @param name name of the gauge
- * @param tags {@link Tag} instances which further identify the gauge
+ * @param tags tags for further identifying the gauge
* @return {@link java.util.Optional} of {@code Gauge} if found; {@code Optional.empty()} otherwise
*/
- Optional getGauge(String name, Tag... tags);
+ Optional getGauge(String name,
+ Tag... tags);
/**
* Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the specified {@link Number}
* instance.
*
* @param name name of the gauge
- * @param tags further identifies the gauge
+ * @param tags tags for further identifying the gauge
* @param number thread-safe implementation of {@link Number} used to access the value
* @param type of the number from which the gauge value is extracted
* @return number argument passed (so the registration can be done as part of an assignment statement)
*/
- T gauge(String name, Iterable tags, T number);
+ T gauge(String name,
+ Iterable tags,
+ T number);
/**
* Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the {@link Number}.
@@ -331,13 +371,15 @@ T gauge(String name,
* @param type of the state object from which the gauge value is extracted
* @return number argument passed (so the registration can be done as part of an assignment statement)
*/
- T gauge(String name, T number);
+ T gauge(String name,
+ T number);
/**
* Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object by applying the specified
* function.
*
* @param name name of the gauge
+ * @param tags tags for further identifying the gauge
* @param stateObject state object used to compute a value
* @param valueFunction function which, when applied to the {@code stateObject}, yields an instantaneous gauge value
* @param type of the state object from which the gauge value is extracted
@@ -345,9 +387,24 @@ T gauge(String name,
* of an assignment statement)
*/
T gauge(String name,
+ Iterable tags,
T stateObject,
ToDoubleFunction valueFunction);
+ /**
+ * Locates or registers a {@link io.helidon.metrics.api.Gauge} that reports the value of the object by applying the specified
+ * function.
+ *
+ * @param name name of the gauge
+ * @param stateObject state object used to compute a value
+ * @param valueFunction function which, when applied to the {@code stateObject}, yields an instantaneous gauge value
+ * @param type of the state object from which the gauge value is extracted
+ * @return state object argument passed (so the registration can be done as part
+ * of an assignment statement)
+ */
+ T gauge(String name,
+ T stateObject,
+ ToDoubleFunction valueFunction);
/**
* Removes a previously-registered meter.
*
@@ -359,8 +416,18 @@ T gauge(String name,
/**
* Removes a previously-registered meter with the specified ID.
*
- * @param id {@link Meter.Id} of the meter to remove
- * @return the removed meter; null if the specified meter ID does not correspond to a registered meter
+ * @param id ID for the meter to remove
+ * @return the removed meter; null if the meter is not currently registered
*/
Meter remove(Meter.Id id);
+
+ /**
+ * Removes a previously-registered meter with the specified name and tags.
+ *
+ * @param name counter name
+ * @param tags tags for further identifying the meter
+ * @return the removed meter; null if the specified name and tags does not correspond to a registered meter
+ */
+ Meter remove(String name,
+ Iterable tags);
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java
index 51014059bf6..7abab9d2fc3 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricFactoryManager.java
@@ -28,7 +28,7 @@
class MetricFactoryManager {
/**
- * Instance of the highest-weight implementation of {@code MetricFactory}
+ * Instance of the highest-weight implementation of {@code MetricFactory}.
*/
static final LazyValue INSTANCE =
LazyValue.create(() -> HelidonServiceLoader.builder(ServiceLoader.load(HelidonMetricFactory.class))
@@ -36,4 +36,7 @@ class MetricFactoryManager {
.build()
.iterator()
.next());
+
+ private MetricFactoryManager() {
+ }
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java b/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java
index 0ce26762749..390fa2aa250 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Metrics.java
@@ -32,18 +32,6 @@ static MeterRegistry globalRegistry() {
return MetricFactoryManager.INSTANCE.get().globalRegistry();
}
- /**
- * Registers a new or locates a previously-registered counter, using the global registry, which tracks a monotonically
- * increasing value.
- *
- * @param name counter name
- * @param tags further identification of the counter
- * @return new or previously-registered counter
- */
- static Counter counter(String name, Iterable tags) {
- return globalRegistry().counter(name, tags);
- }
-
/**
* Registers a new or locates a previously-registered counter, using the global registry, which tracks a monotonically
* increasing value.
@@ -73,7 +61,10 @@ static Counter counter(String name, String... tags) {
* @return new or existing counter
* @param type of the object which furnishes the counter value
*/
- static Counter counter(String name, Iterable tags, T target, ToDoubleFunction fn) {
+ static Counter counter(String name,
+ Iterable tags,
+ T target,
+ ToDoubleFunction fn) {
return globalRegistry().counter(name, tags, target, fn);
}
@@ -85,7 +76,8 @@ static Counter counter(String name, Iterable tags, T target, ToDoubleFu
* @param tags further identification of the summary
* @return new or previously-registered distribution summary
*/
- static DistributionSummary summary(String name, Iterable tags) {
+ static DistributionSummary summary(String name,
+ Iterable tags) {
return globalRegistry().summary(name, tags);
}
@@ -98,7 +90,8 @@ static DistributionSummary summary(String name, Iterable tags) {
* of tags
* @return new or previously-registered distribution summary
*/
- static DistributionSummary summary(String name, String... tags) {
+ static DistributionSummary summary(String name,
+ String... tags) {
return globalRegistry().summary(name, tags);
}
@@ -110,7 +103,8 @@ static DistributionSummary summary(String name, String... tags) {
* @param tags further identification of the timer
* @return new or previously-registered timer
*/
- static Timer timer(String name, Iterable tags) {
+ static Timer timer(String name,
+ Iterable tags) {
return globalRegistry().timer(name, tags);
}
@@ -122,7 +116,8 @@ static Timer timer(String name, Iterable tags) {
* @param tags further identification of the timer; MUST be an even number of arguments representing key/value pairs of tags.
* @return new or previously-registered timer
*/
- static Timer timer(String name, String... tags) {
+ static Timer timer(String name,
+ String... tags) {
return globalRegistry().timer(name, tags);
}
@@ -137,7 +132,10 @@ static Timer timer(String name, String... tags) {
* @param type of the state object which maintains the gauge's value
* @return state object
*/
- static T gauge(String name, Iterable tags, T obj, ToDoubleFunction valueFunction) {
+ static T gauge(String name,
+ Iterable tags,
+ T obj,
+ ToDoubleFunction valueFunction) {
return globalRegistry().gauge(name, tags, obj, valueFunction);
}
@@ -151,7 +149,9 @@ static T gauge(String name, Iterable tags, T obj, ToDoubleFunction v
* @param specific subtype of {@code Number} which the wrapped object exposes
* @return {@code number} wrapped by this gauge
*/
- static N gauge(String name, Iterable tags, N number) {
+ static N gauge(String name,
+ Iterable tags,
+ N number) {
return globalRegistry().gauge(name, tags, number);
}
@@ -164,7 +164,8 @@ static N gauge(String name, Iterable tags, N number) {
* @param specific subtype of {@code Number} which the wrapped object exposes
* @return {@code number} wrapped by this gauge
*/
- static N gauge(String name, N number) {
+ static N gauge(String name,
+ N number) {
return globalRegistry().gauge(name, number);
}
@@ -178,7 +179,20 @@ static N gauge(String name, N number) {
* @param type of the state object which maintains the gauge's value
* @return state object
*/
- static T gauge(String name, T obj, ToDoubleFunction valueFunction) {
+ static T gauge(String name,
+ T obj,
+ ToDoubleFunction valueFunction) {
return globalRegistry().gauge(name, obj, valueFunction);
}
+
+ /**
+ * Creates a {@link Tag} for the specified key and value.
+ *
+ * @param key tag key
+ * @param value tag value
+ * @return new tag
+ */
+ static Tag tag(String key, String value) {
+ return MetricFactoryManager.INSTANCE.get().tagOf(key, value);
+ }
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeter.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeter.java
new file mode 100644
index 00000000000..0bacc75f7c5
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeter.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
+
+class NoOpMeter implements Meter {
+
+ private final Id id;
+ private final String unit;
+ private final String description;
+ private final Type type;
+
+ static class Id implements Meter.Id {
+
+ static Id create(String name, Iterable tags) {
+ return new Id(name, tags);
+ }
+
+ static Id create(String name, Tag... tags) {
+ return new Id(name, Arrays.asList(tags));
+ }
+
+ private final String name;
+ private final List tags = new ArrayList<>(); // must be ordered by tag name for consistency
+
+ private Id(String name, Iterable tags) {
+ this.name = name;
+ tags.forEach(this.tags::add);
+ this.tags.sort(Comparator.comparing(Tag::key));
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List tags() {
+ return tags.stream().toList();
+ }
+
+ Iterable tagsAsIterable() {
+ return tags;
+ }
+
+ String tag(String key) {
+ return tags.stream()
+ .filter(t -> t.key().equals(key))
+ .map(Tag::value)
+ .findFirst()
+ .orElse(null);
+ }
+ }
+
+ private NoOpMeter(NoOpMeter.Builder, ?> builder) {
+ this(new NoOpMeter.Id(builder.name, builder.tags.values()),
+ builder.unit,
+ builder.description,
+ builder.type);
+ }
+
+ private NoOpMeter(Id id, String baseUnit, String description, Type type) {
+ this.id = id;
+ this.unit = baseUnit;
+ this.description = description;
+ this.type = type;
+ }
+
+ @Override
+ public Id id() {
+ return id;
+ }
+
+ @Override
+ public String baseUnit() {
+ return unit;
+ }
+
+ @Override
+ public String description() {
+ return description;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ abstract static class Builder, M extends Meter> implements io.helidon.common.Builder {
+
+ private final String name;
+ private final Map tags = new TreeMap<>(); // tree map for ordering by tag name
+ private String description;
+ private String unit;
+ private final Type type;
+
+ private Builder(String name, Type type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ public abstract M build();
+
+ B tags(String... tags) {
+ if (tags.length % 2 != 0) {
+ throw new IllegalArgumentException("""
+ Tag list must contain an even number of items because they must \
+ be key/value pairs")""");
+ }
+ for (int slot = 0; slot < tags.length / 2; slot++) {
+ this.tags.put(tags[slot * 2], Tag.of(tags[slot * 2], tags[slot * 2 + 1]));
+ }
+ return identity();
+ }
+
+ B tags(Iterable tags) {
+ tags.forEach(tag -> this.tags.put(tag.key(), tag));
+ return identity();
+ }
+
+ B tag(String key, String value) {
+ tags.put(key, Tag.of(key, value));
+ return identity();
+ }
+
+ B description(String description) {
+ this.description = description;
+ return identity();
+ }
+
+ B baseUnit(String unit) {
+ this.unit = unit;
+ return identity();
+ }
+ }
+
+ static class Counter extends NoOpMeter implements io.helidon.metrics.api.Counter {
+
+ static Counter create(String name, Iterable tags) {
+ return builder(name)
+ .tags(tags)
+ .build();
+ }
+
+ static class Builder extends NoOpMeter.Builder {
+
+ protected Builder(String name) {
+ super(name, Type.COUNTER);
+ }
+
+
+ @Override
+ public Counter build() {
+ return new NoOpMeter.Counter(this);
+ }
+
+ }
+
+ static Counter.Builder builder(String name) {
+ return new Builder(name);
+ }
+
+ static FunctionalCounter.Builder builder(String name, T target, ToDoubleFunction fn) {
+ return new FunctionalCounter.Builder<>(name, target, fn);
+ }
+
+ protected Counter(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public void increment() {
+ }
+
+ @Override
+ public void increment(double amount) {
+ }
+
+ @Override
+ public double count() {
+ return 0;
+ }
+ }
+
+ static class FunctionalCounter extends Counter {
+
+ static class Builder extends Counter.Builder {
+
+ private Builder(String name, T target, ToDoubleFunction fn) {
+ super(name);
+ }
+
+ @Override
+ public FunctionalCounter build() {
+ return new FunctionalCounter<>(this);
+ }
+ }
+
+ private FunctionalCounter(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public void increment() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void increment(double amount) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class DistributionSummary extends NoOpMeter implements io.helidon.metrics.api.DistributionSummary {
+
+ static class Builder extends NoOpMeter.Builder {
+
+ private Builder(String name) {
+ super(name, Type.DISTRIBUTION_SUMMARY);
+ }
+
+ @Override
+ public DistributionSummary build() {
+ return new DistributionSummary(this);
+ }
+ }
+
+ static DistributionSummary.Builder builder(String name) {
+ return new DistributionSummary.Builder(name);
+ }
+
+ private DistributionSummary(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public void record(double amount) {
+ }
+
+ @Override
+ public long count() {
+ return 0;
+ }
+
+ @Override
+ public double totalAmount() {
+ return 0;
+ }
+
+ @Override
+ public double mean() {
+ return 0;
+ }
+
+ @Override
+ public double max() {
+ return 0;
+ }
+ }
+
+ static class Gauge extends NoOpMeter implements io.helidon.metrics.api.Gauge {
+
+ static Builder builder(String name) {
+ return new Builder(name);
+ }
+
+ static class Builder extends NoOpMeter.Builder {
+
+ private Builder(String name) {
+ super(name, Type.GAUGE);
+ }
+
+ @Override
+ public Gauge build() {
+ return new Gauge(this);
+ }
+ }
+
+ private Gauge(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public double value() {
+ return 0;
+ }
+ }
+
+ static class Timer extends NoOpMeter implements io.helidon.metrics.api.Timer {
+
+ static class Builder extends NoOpMeter.Builder {
+
+ private Builder(String name) {
+ super(name, Type.TIMER);
+ }
+
+ @Override
+ public Timer build() {
+ return new Timer(this);
+ }
+ }
+
+ static Builder builder(String name) {
+ return new Builder(name);
+ }
+
+ private Timer(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public HistogramSnapshot takeSnapshot() {
+ return null;
+ }
+
+ @Override
+ public void record(long amount, TimeUnit unit) {
+
+ }
+
+ @Override
+ public void record(Duration duration) {
+
+ }
+
+ @Override
+ public T record(Supplier f) {
+ return null;
+ }
+
+ @Override
+ public T recordCallable(Callable f) throws Exception {
+ return null;
+ }
+
+ @Override
+ public void record(Runnable f) {
+
+ }
+
+ @Override
+ public Runnable wrap(Runnable f) {
+ return null;
+ }
+
+ @Override
+ public Callable wrap(Callable f) {
+ return null;
+ }
+
+ @Override
+ public Supplier wrap(Supplier f) {
+ return null;
+ }
+
+ @Override
+ public long count() {
+ return 0;
+ }
+
+ @Override
+ public double totalTime(TimeUnit unit) {
+ return 0;
+ }
+
+ @Override
+ public double mean(TimeUnit unit) {
+ return 0;
+ }
+
+ @Override
+ public double max(TimeUnit unit) {
+ return 0;
+ }
+ }
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java
new file mode 100644
index 00000000000..849b8efb88a
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.metrics.api;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
+
+/**
+ * No-op implementation of {@link io.helidon.metrics.api.MeterRegistry}.
+ */
+class NoOpMeterRegistry implements MeterRegistry {
+
+ private final Map meters = new ConcurrentHashMap<>();
+
+ private final ReentrantLock metersAccess = new ReentrantLock();
+
+ @Override
+ public List meters() {
+ return List.of(meters.values().toArray(new Meter[0]));
+ }
+
+ @Override
+ public Iterable meters(Predicate filter) {
+ return () -> new Iterator<>() {
+
+ private final Iterator iter = meters.values().iterator();
+ private Meter nextMatch = nextMatch();
+
+ private Meter nextMatch() {
+ while (iter.hasNext()) {
+ Meter candidate = iter.next();
+ if (filter.test(candidate)) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextMatch != null;
+ }
+
+ @Override
+ public Meter next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ Meter result = nextMatch;
+ nextMatch = nextMatch();
+ return result;
+ }
+ };
+ }
+
+ @Override
+ public Meter remove(Meter.Id id) {
+ return meters.remove(id);
+ }
+
+ @Override
+ public Meter remove(Meter meter) {
+ return meters.remove(meter.id());
+ }
+
+ @Override
+ public Meter remove(String name, Iterable tags) {
+ return remove(NoOpMeter.Id.create(name, tags));
+ }
+
+ @Override
+ public Counter counter(Meter.Id id) {
+ return findOrRegister(id,
+ Counter.class,
+ () -> NoOpMeter.Counter.builder(id.name())
+ .tags(id.tags())
+ .build());
+ }
+
+ @Override
+ public