From b3d119361727ff1cb0cf5b666a5462a1fa5c4058 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Fri, 16 Jun 2023 17:57:46 -0500
Subject: [PATCH 01/67] Initial commit of MP based on Micrometer
---
bom/pom.xml | 10 +
dependencies/pom.xml | 9 +-
metrics/microprofile/cdi/pom.xml | 86 +++
metrics/microprofile/microprofile/pom.xml | 109 ++++
.../microprofile/GlobalTagsHelper.java | 130 +++++
.../metrics/microprofile/MpCounter.java | 43 ++
.../metrics/microprofile/MpMetric.java | 32 ++
.../metrics/microprofile/MpMetricId.java | 53 ++
.../microprofile/MpMetricRegistry.java | 532 ++++++++++++++++++
.../helidon/metrics/microprofile/MpTags.java | 153 +++++
.../src/main/java/module-info.java | 27 +
.../metrics/microprofile/TestCounters.java | 56 ++
.../microprofile/TestGlobalTagHelper.java | 92 +++
metrics/microprofile/pom.xml | 38 ++
metrics/pom.xml | 1 +
15 files changed, 1367 insertions(+), 4 deletions(-)
create mode 100644 metrics/microprofile/cdi/pom.xml
create mode 100644 metrics/microprofile/microprofile/pom.xml
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/GlobalTagsHelper.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/module-info.java
create mode 100644 metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
create mode 100644 metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestGlobalTagHelper.java
create mode 100644 metrics/microprofile/pom.xml
diff --git a/bom/pom.xml b/bom/pom.xml
index c02d198d0d2..e85782157f9 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -525,6 +525,16 @@
helidon-metrics-trace-exemplar
${helidon.version}
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile
+ ${helidon.version}
+
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-cdi
+ ${helidon.version}
+
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index f3648b664a7..bda53b46be5 100644
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -98,7 +98,8 @@
1.4.0
2.6.2
2.10
- 1.6.6
+ 1.11.1
+ 1.11.1
3.4.3
3.3.0
4.4.0
@@ -109,7 +110,7 @@
2.0
4.0
2.1
- 4.0
+ 5.0.1
3.1
3.0
3.0
@@ -141,7 +142,7 @@
0.25.0
1.0.2
42.4.3
- 0.9.0
+ 0.15.0
2.0.0
3.3.4
2.0
@@ -620,7 +621,7 @@
io.micrometer
micrometer-registry-prometheus
- ${version.lib.micrometer}
+ ${version.lib.micrometer-prometheus}
diff --git a/metrics/microprofile/cdi/pom.xml b/metrics/microprofile/cdi/pom.xml
new file mode 100644
index 00000000000..2d4e65cd408
--- /dev/null
+++ b/metrics/microprofile/cdi/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-project
+ 4.0.0-SNAPSHOT
+
+ 4.0.0
+
+ helidon-metrics-microprofile-cdi
+
+ Helidon Metrics MicroProfile CDI elements
+
+
+ CDI elements (extension, producers, etc.) for MicroProfile Metrics support
+
+
+
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile
+
+
+ org.eclipse.microprofile.metrics
+ microprofile-metrics-api
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.helidon.config
+ helidon-config-metadata-processor
+ ${helidon.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metrics/microprofile/microprofile/pom.xml b/metrics/microprofile/microprofile/pom.xml
new file mode 100644
index 00000000000..beb2b3cb37a
--- /dev/null
+++ b/metrics/microprofile/microprofile/pom.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-project
+ 4.0.0-SNAPSHOT
+
+ 4.0.0
+
+ helidon-metrics-microprofile
+
+ Helidon Metrics MicroProfile Implementations
+
+
+ Helidon-specific implementations of the MicroProfile Metrics interfaces
+
+
+
+
+ io.helidon.common
+ helidon-common-http
+
+
+ org.eclipse.microprofile.metrics
+ microprofile-metrics-api
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+
+
+ io.micrometer
+ micrometer-core
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+ true
+
+
+ io.prometheus
+ simpleclient
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.helidon.config
+ helidon-config-metadata-processor
+ ${helidon.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/GlobalTagsHelper.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/GlobalTagsHelper.java
new file mode 100644
index 00000000000..561bb41fccf
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/GlobalTagsHelper.java
@@ -0,0 +1,130 @@
+/*
+ * 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.microprofile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import io.micrometer.core.instrument.Tag;
+
+/**
+ * Manages retrieving and dispensing global tags.
+ *
+ * The internal tags data starts as Optional.empty(). Once {@link #globalTags(String)} has been invoked the internal tags
+ * data Optional will no longer be empty, but the Tag[] it holds might be zero-length if there are no global tags to be
+ * concerned with.
+ *
+ */
+class GlobalTagsHelper {
+
+ // Static instance is used normally at runtime to share a single set of global tags across possibly multiple metric
+ // registries. Otherwise, regular instances of the helper are typically created only for testing.
+ private static final GlobalTagsHelper INSTANCE = new GlobalTagsHelper();
+
+ private Optional tags = Optional.empty();
+
+ /**
+ * Sets the tags for normal use.
+ *
+ * @param tagExpression tag assignments
+ */
+ static void globalTags(String tagExpression) {
+ INSTANCE.tags(tagExpression);
+ }
+
+ /**
+ * Retrieves the tags for normal use.
+ *
+ * @return tags derived from the earlier assignment
+ */
+ static Optional globalTags() {
+ return INSTANCE.tags();
+ }
+
+ /**
+ * For testing or internal use; sets the tags according to the comma-separate list of "name=value" settings.
+ *
+ * The expression can contain escaped "=" and "," characters.
+ *
+ *
+ * @param tagExpression tag assignments
+ */
+ Optional tags(String tagExpression) {
+
+ // The following regex splits on non-escaped commas.
+ String[] assignments = tagExpression.split("(? problems = new ArrayList<>();
+
+ for (String assignment : assignments) {
+ List assignmentProblems = new ArrayList<>();
+ if (assignment.isBlank()) {
+ assignmentProblems.add("empty assignment found at position " + position + ": " + tagExpression);
+ } else {
+
+ // The following regex splits on non-escaped equals signs.
+ String[] parts = assignment.split("(? tags() {
+ return tags;
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java
new file mode 100644
index 00000000000..eec066d24d6
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.microprofile;
+
+import io.micrometer.core.instrument.Counter;
+
+/**
+ * Implementation of {@link org.eclipse.microprofile.metrics.Counter}.
+ */
+public class MpCounter extends MpMetric implements org.eclipse.microprofile.metrics.Counter {
+
+ MpCounter(Counter delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public void inc() {
+ delegate().increment();
+ }
+
+ @Override
+ public void inc(long n) {
+ delegate().increment(n);
+ }
+
+ @Override
+ public long getCount() {
+ return (long) delegate().count();
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
new file mode 100644
index 00000000000..1999455cf5f
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
@@ -0,0 +1,32 @@
+/*
+ * 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.microprofile;
+
+import io.micrometer.core.instrument.Meter;
+import org.eclipse.microprofile.metrics.Metric;
+
+class MpMetric implements Metric {
+
+ private final M delegate;
+
+ MpMetric(M delegate) {
+ this.delegate = delegate;
+ }
+
+ M delegate() {
+ return delegate;
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
new file mode 100644
index 00000000000..7528199ea99
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
@@ -0,0 +1,53 @@
+/*
+ * 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.microprofile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tags;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.Tag;
+
+public class MpMetricId extends MetricID {
+
+ // Functionally equivalent to the superclass MetricID, so we don't need equals or hashCode to account for any private
+ // fields here.
+
+ private Tags fullTags = Tags.empty();
+ private final Meter.Id meterId;
+
+ MpMetricId(String name, Tag[] tags, Tag[] automaticTags, String baseUnit, String description, Meter.Type type) {
+ super(name, tags);
+ for (Tag tag : tags) {
+ fullTags = fullTags.and(io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue()));
+ }
+ for (Tag tag : automaticTags) {
+ fullTags = fullTags.and(io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue()));
+ }
+
+ meterId = new Meter.Id(name, fullTags, baseUnit, description, type);
+ }
+
+ public String name() {
+ return getName();
+ }
+
+ Meter.Id meterId() {
+ return meterId;
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
new file mode 100644
index 00000000000..dfe341543d5
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
@@ -0,0 +1,532 @@
+/*
+ * 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.microprofile;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Metric;
+import org.eclipse.microprofile.metrics.MetricFilter;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
+
+class MpMetricRegistry implements MetricRegistry {
+
+ public static final String MP_APPLICATION_TAG_NAME = "mp_app";
+
+ public static final String MP_SCOPE_TAG_NAME = "mp_scope";
+
+ private final String scope;
+ private final MeterRegistry meterRegistry;
+ private final Map metadata = new ConcurrentHashMap<>();
+ private final Map> metricsById = new ConcurrentHashMap<>();
+ private final Map> metricIdsByName = new ConcurrentHashMap<>();
+ private final Map>> metricsByName = new ConcurrentHashMap<>();
+
+ private final ReentrantLock accessLock = new ReentrantLock();
+
+ /**
+ * Creates a new {@link MetricRegistry} with the given scope, delegating to the
+ * given {@link io.micrometer.core.instrument.MeterRegistry}.
+ *
+ * @param scope scope for the metric registry
+ * @param meterRegistry meter registry to which to delegate
+ * @return new {@code MetricRegistry}
+ */
+ static MpMetricRegistry create(String scope, MeterRegistry meterRegistry) {
+ return new MpMetricRegistry(scope, meterRegistry);
+ }
+
+ private MpMetricRegistry(String scope, MeterRegistry meterRegistry) {
+ this.scope = scope;
+ this.meterRegistry = meterRegistry;
+ }
+
+ @Override
+ public Counter counter(String s) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ meterRegistry::counter,
+ s);
+ }
+
+ @Override
+ public Counter counter(String s, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ meterRegistry::counter,
+ s,
+ tags);
+ }
+
+ @Override
+ public Counter counter(MetricID metricID) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ meterRegistry::counter,
+ metricID.getName(),
+ metricID.getTagsAsArray());
+ }
+
+ @Override
+ public Counter counter(Metadata metadata) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ meterRegistry::counter,
+ validatedMetadata(metadata));
+ }
+
+ @Override
+ public Counter counter(Metadata metadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ meterRegistry::counter,
+ validatedMetadata(metadata),
+ tags);
+ }
+
+ @Override
+ public Gauge gauge(String s, T t, Function function, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Gauge gauge(MetricID metricID, T t, Function function) {
+ return null;
+ }
+
+ @Override
+ public Gauge gauge(Metadata metadata, T t, Function function, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Gauge gauge(String s, Supplier supplier, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Gauge gauge(MetricID metricID, Supplier supplier) {
+ return null;
+ }
+
+ @Override
+ public Gauge gauge(Metadata metadata, Supplier supplier, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Histogram histogram(String s) {
+ return null;
+ }
+
+ @Override
+ public Histogram histogram(String s, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Histogram histogram(MetricID metricID) {
+ return null;
+ }
+
+ @Override
+ public Histogram histogram(Metadata metadata) {
+ return null;
+ }
+
+ @Override
+ public Histogram histogram(Metadata metadata, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Timer timer(String s) {
+ return null;
+ }
+
+ @Override
+ public Timer timer(String s, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Timer timer(MetricID metricID) {
+ return null;
+ }
+
+ @Override
+ public Timer timer(Metadata metadata) {
+ return null;
+ }
+
+ @Override
+ public Timer timer(Metadata metadata, Tag... tags) {
+ return null;
+ }
+
+ @Override
+ public Metric getMetric(MetricID metricID) {
+ return metricsById.get(metricID);
+ }
+
+ @Override
+ public T getMetric(MetricID metricID, Class aClass) {
+ return aClass.cast(metricsById.get(metricID));
+ }
+
+ @Override
+ public Counter getCounter(MetricID metricID) {
+ return (Counter) metricsById.get(metricID);
+ }
+
+ @Override
+ public Gauge> getGauge(MetricID metricID) {
+ return (Gauge>) metricsById.get(metricID);
+ }
+
+ @Override
+ public Histogram getHistogram(MetricID metricID) {
+ return (Histogram) metricsById.get(metricID);
+ }
+
+ @Override
+ public Timer getTimer(MetricID metricID) {
+ return (Timer) metricsById.get(metricID);
+ }
+
+ @Override
+ public Metadata getMetadata(String s) {
+ return metadata.get(s);
+ }
+
+ @Override
+ public boolean remove(String s) {
+ return access(() -> {
+ boolean removeResult = false;
+ // Capture the list of IDs first and then iterate over that because the remove method updates the list from the map.
+ List doomedMetricIds = new ArrayList<>(metricIdsByName.get(s));
+ for (MetricID metricId : doomedMetricIds) {
+ removeResult |= remove(metricId);
+ }
+ return removeResult;
+
+
+ });
+ }
+
+ @Override
+ public boolean remove(MetricID metricID) {
+ return access(() -> {
+ MpMetric> doomedMpMetric = metricsById.remove(metricID);
+ boolean removeResult = doomedMpMetric != null;
+ List idsByName = metricIdsByName.get(metricID.getName());
+ if (idsByName != null) {
+ idsByName.remove(metricID);
+ }
+ meterRegistry.remove(doomedMpMetric.delegate());
+ return removeResult;
+ });
+ }
+
+ @Override
+ public void removeMatching(MetricFilter metricFilter) {
+ List doomedMetricIds = new ArrayList<>();
+ access(() -> {
+ metricsById.forEach((id, metric) -> {
+ if (metricFilter.matches(id, metric)) {
+ doomedMetricIds.add(id);
+ }
+ });
+ for (MetricID doomedMetricId : doomedMetricIds) {
+ remove(doomedMetricId);
+ }
+ });
+ }
+
+ @Override
+ public SortedSet getNames() {
+ return new TreeSet<>(metadata.keySet());
+ }
+
+ @Override
+ public SortedSet getMetricIDs() {
+ return new TreeSet<>(metricsById.keySet());
+ }
+
+ @Override
+ public SortedMap getGauges() {
+ return null;
+ }
+
+ @Override
+ public SortedMap getGauges(MetricFilter metricFilter) {
+ return null;
+ }
+
+ @Override
+ public SortedMap getCounters() {
+ return getCounters(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getCounters(MetricFilter metricFilter) {
+ return getMetrics(Counter.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getHistograms() {
+ return null;
+ }
+
+ @Override
+ public SortedMap getHistograms(MetricFilter metricFilter) {
+ return null;
+ }
+
+ @Override
+ public SortedMap getTimers() {
+ return null;
+ }
+
+ @Override
+ public SortedMap getTimers(MetricFilter metricFilter) {
+ return null;
+ }
+
+ @Override
+ public SortedMap getMetrics(MetricFilter metricFilter) {
+ return null;
+ }
+
+ @Override
+ public SortedMap getMetrics(Class aClass, MetricFilter metricFilter) {
+ return metricsById.entrySet()
+ .stream()
+ .filter(e -> aClass.isInstance(e.getValue()))
+ .filter(e -> metricFilter.matches(e.getKey(), e.getValue()))
+ .collect(TreeMap::new,
+ (tm, entry) -> tm.put(entry.getKey(), aClass.cast(entry.getValue())),
+ TreeMap::putAll);
+ }
+
+ @Override
+ public Map getMetrics() {
+ return Collections.unmodifiableMap(metricsById);
+ }
+
+ @Override
+ public Map getMetadata() {
+ return Collections.unmodifiableMap(metadata);
+ }
+
+ @Override
+ public String getScope() {
+ return scope;
+ }
+
+ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
+ Function metricFactory,
+ BiFunction,
+ T> meterFactory,
+ String name,
+ Tag... tags) {
+ return getOrCreateAndStoreMetric(type,
+ metricFactory,
+ meterFactory,
+ validatedMetadata(name),
+ tags);
+ }
+
+ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
+ Function metricFactory,
+ BiFunction,
+ T> meterFactory,
+ Metadata validMetadata,
+ Tag... tags) {
+
+ /*
+ * From the metadata create a candidate MpMetricID, validate it (to make sure the tag names are consistent with any
+ * previously-registered metrics with the same name and that the user did not specify any reserved tags), and then
+ * augment the inner meter ID with the scope tag and, if an app name is specified via config, the app name tag.
+ */
+ MpMetricId mpMetricId = validAugmentedMpMetricId(validMetadata, type, tags);
+ return access(() -> {
+ MpMetric> result = metricsById.get(mpMetricId);
+ if (result != null) {
+ return (M) result;
+ }
+
+ T delegate = meterFactory.apply(mpMetricId.name(), MpTags.fromMp(mpMetricId.getTags()));
+
+ M newMetric = metricFactory.apply(delegate);
+ storeMetadataIfAbsent(validMetadata);
+ metricsById.put(mpMetricId, newMetric);
+ metricsById.put(mpMetricId, newMetric);
+ metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
+ metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
+ return newMetric;
+ });
+ }
+
+ private void storeMetadataIfAbsent(Metadata validatedMetadata) {
+ metadata.putIfAbsent(validatedMetadata.getName(), validatedMetadata);
+ }
+
+ /**
+ * Returns a validated {@link io.helidon.metrics.microprofile.MpMetricId} derived from the provided metadata, tags,
+ * and meter Type, further augmented with MicroProfile-supported additional tags (app name, scope).
+ *
+ * @param proposedMetadata {@link Metadata} to use in populating the {@code MpMetricId}
+ * @param meterType the non-MP metric meterType
+ * @param tags tags to use in preparing the metric ID
+ * @return validated {@code MpMetricId}
+ */
+ private MpMetricId validAugmentedMpMetricId(Metadata proposedMetadata, Meter.Type meterType, Tag... tags) {
+ MpMetricId metricId = new MpMetricId(proposedMetadata.getName(),
+ tags,
+ automaticTags(),
+ proposedMetadata.getUnit(),
+ proposedMetadata.getDescription(),
+ meterType);
+ checkTagNameSetConsistencyWithStoredIds(metricId);
+ MpTags.checkForReservedTags(metricId.getTags().keySet());
+ return metricId;
+ }
+
+ private Tag[] automaticTags() {
+ List result = new ArrayList<>();
+ result.add(new Tag(MpMetricRegistry.MP_SCOPE_TAG_NAME, scope));
+ String mpAppValue = MpTags.mpAppValue();
+ if (mpAppValue != null && !mpAppValue.isBlank()) {
+ result.add(new Tag(MpMetricRegistry.MP_APPLICATION_TAG_NAME, mpAppValue));
+ }
+ return result.toArray(new Tag[0]);
+ }
+
+
+
+ /**
+ * Returns a {@link org.eclipse.microprofile.metrics.Metadata} derived from the specified name, validated for consistency
+ * against any previously-registered metadata under the same name.
+ *
+ * @param name name associated with the metadata
+ * @return valid {@code Metadata} derived from the name
+ */
+ private Metadata validatedMetadata(String name) {
+ return validatedMetadata(Metadata.builder()
+ .withName(name)
+ .build());
+ }
+
+ /**
+ * Returns the provided {@link org.eclipse.microprofile.metrics.Metadata} once validated for consistency against any
+ * previously-registered metadata with the same name.
+ *
+ * @param proposedMetadata candidate {@code Metadata} to validate
+ * @return validated {@code Metadata}
+ */
+ private Metadata validatedMetadata(Metadata proposedMetadata) {
+ Metadata storedMetadata = metadata.get(proposedMetadata.getName());
+ if (storedMetadata == null) {
+ return proposedMetadata;
+ }
+ if (!isConsistentMetadata(storedMetadata, proposedMetadata)) {
+ throw new IllegalArgumentException(String.format(
+ "Supplied metadata %s is inconsistent with previously-registered metadata %s",
+ proposedMetadata,
+ storedMetadata));
+ }
+ return storedMetadata;
+ }
+
+
+ /**
+ * Returns whether the two metadata instances are consistent with each other.
+ *
+ * @param a one {@code Metadata} instance
+ * @param b the other {@code Metadata} instance
+ * @return {@code true} if the two instances contain consistent metadata; {@code false} otherwise
+ */
+ boolean isConsistentMetadata(Metadata a, Metadata b) {
+ return a.equals(b);
+ }
+
+ MpMetricId consistentMpMetricId(Metadata metadata, Meter.Type type, Tag... tags) {
+ MpTags.checkForReservedTags(tags);
+ MpMetricId id = new MpMetricId(metadata.getName(),
+ tags,
+ automaticTags(),
+ metadata.getUnit(),
+ metadata.getDescription(),
+ type);
+ MpTags.checkTagNameSetConsistency(id, metricIdsByName.get(metadata.getName()));
+ return id;
+ }
+
+ /**
+ * Checks that the tag names in the provided ID are consistent with the tag names in any previously-registered ID
+ * with the same name; throws {@code IllegalArgumentException} if inconsistent.
+ *
+ * @param mpMetricId metric ID with tags to check
+ */
+ MpMetricId checkTagNameSetConsistencyWithStoredIds(MpMetricId mpMetricId) {
+ MpTags.checkTagNameSetConsistency(mpMetricId, metricIdsByName.get(mpMetricId.getName()));
+ return mpMetricId;
+ }
+
+ private T access(Callable work) {
+ accessLock.lock();
+ try {
+ return work.call();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ accessLock.unlock();
+ }
+ }
+
+ private void access(Runnable work) {
+ accessLock.lock();
+ try {
+ work.run();
+ } finally {
+ accessLock.unlock();
+ }
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java
new file mode 100644
index 00000000000..4fb2ce369e1
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java
@@ -0,0 +1,153 @@
+/*
+ * 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.microprofile;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.micrometer.core.instrument.Tags;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigValue;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.Tag;
+
+class MpTags {
+
+ private static final Set RESERVED_TAG_NAMES = new HashSet<>();
+ private static String mpAppValue;
+
+ static {
+ RESERVED_TAG_NAMES.add(MpMetricRegistry.MP_APPLICATION_TAG_NAME);
+ RESERVED_TAG_NAMES.add(MpMetricRegistry.MP_SCOPE_TAG_NAME);
+ }
+
+ static String mpAppValue() {
+ return mpAppValue;
+ }
+
+ static void config(Config config) {
+ ConfigValue configValue = config.getConfigValue(MpMetricRegistry.MP_APPLICATION_TAG_NAME);
+ if (configValue.getValue() != null) {
+ mpAppValue = configValue.getValue();
+ }
+ }
+
+ static Tags fromMp(List tags) {
+ return Tags.of(tags.stream()
+ .map(MpTags::fromMp)
+ .toList());
+ }
+
+ static Tags fromMp(Tag... tags) {
+ return Tags.of(Arrays.stream(tags).map(MpTags::fromMp).toList());
+ }
+
+ static Tags fromMp(Map tags) {
+ return tags.entrySet().stream().collect(Tags::empty,
+ (meterTags, entry) -> meterTags.and(entry.getKey(), entry.getValue()),
+ Tags::and);
+ }
+
+ static io.micrometer.core.instrument.Tag fromMp(Tag tag) {
+ return io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue());
+ }
+
+ /**
+ * Checks that the tag names in the provided metric ID are consistent with those in the provided list of metric IDs, throwing
+ * an exception if not.
+ *
+ * @param mpMetricId the metric ID to check for consistent tag names
+ * @param mpMetricIds other metric IDs
+ */
+ static void checkTagNameSetConsistency(MetricID mpMetricId, List mpMetricIds) {
+ if (mpMetricIds == null || mpMetricIds.isEmpty()) {
+ return;
+ }
+
+ for (MetricID id : mpMetricIds) {
+ if (!isConsistentTagNameSet(id, mpMetricId)) {
+ throw new IllegalArgumentException(String.format("""
+ Provided tag names are inconsistent with tag names on previously-registered metric with the same name %s; \
+ previous: %s, requested: %s""",
+ mpMetricId.getName(),
+ id.getTags().keySet(),
+ mpMetricId.getTags().keySet()));
+ }
+ }
+ }
+
+ static void checkTagNameSetConsistency(String metricName, Set candidateTags, Iterable> establishedTagGroups) {
+ for (Set establishedTags : establishedTagGroups) {
+ if (!candidateTags.equals(establishedTags)) {
+ throw new IllegalArgumentException(String.format("""
+ Provided tag names are inconsistent with tag names on previously-registered metric with the same name %s; \
+ registered tags: %s, requested tags: %s""",
+ metricName,
+ Arrays.asList(establishedTags),
+ Arrays.asList(candidateTags)));
+ }
+ }
+ }
+
+ /**
+ * Returns whether the tag names in the two provided metric IDs are consistent with each other.
+ *
+ * @param a one metric ID
+ * @param b another metric ID
+ * @return {@code true} if the tag name sets in the two IDs are consistent; {@code false} otherwise
+ */
+ static boolean isConsistentTagNameSet(MetricID a, MetricID b) {
+ return a.getTags().keySet().equals(b.getTags().keySet());
+ }
+
+ static void checkForReservedTags(Tag... tags) {
+ if (tags != null) {
+ Set offendingReservedTags = null;
+ for (Tag tag : tags) {
+ if (RESERVED_TAG_NAMES.contains(tag.getTagName())) {
+ if (offendingReservedTags == null) {
+ offendingReservedTags = new HashSet<>();
+ }
+ offendingReservedTags.add(tag.getTagName());
+ }
+ }
+ if (offendingReservedTags != null) {
+ throw new IllegalArgumentException("Provided tags includes reserved name(s) " + offendingReservedTags);
+ }
+ }
+ }
+
+ static void checkForReservedTags(Set tagNames) {
+ if (tagNames != null) {
+ Set offendingReservedTags = null;
+ for (String tagName : tagNames) {
+ if (RESERVED_TAG_NAMES.contains(tagName)) {
+ if (offendingReservedTags == null) {
+ offendingReservedTags = new HashSet<>();
+ }
+ offendingReservedTags.add(tagName);
+ }
+ }
+ if (offendingReservedTags != null) {
+ throw new IllegalArgumentException("Provided tags includes reserved name(s) " + offendingReservedTags);
+ }
+ }
+ }
+
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/module-info.java b/metrics/microprofile/microprofile/src/main/java/module-info.java
new file mode 100644
index 00000000000..3e77b06ecb1
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/module-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Helidon MicroProfile Metrics implementation
+ */
+module io.helidon.metrics.microprofile {
+
+ requires microprofile.metrics.api;
+ requires microprofile.config.api;
+ requires micrometer.core;
+
+ //requires micrometer.registry.prometheus;
+}
\ No newline at end of file
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
new file mode 100644
index 00000000000..f36dbb80449
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
@@ -0,0 +1,56 @@
+/*
+ * 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.microprofile;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Counter;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class TestCounters {
+
+ static PrometheusMeterRegistry prometheusMeterRegistry;
+ static MeterRegistry meterRegistry;
+ static MpMetricRegistry mpMetricRegistry;
+
+ @BeforeAll
+ static void setup() {
+ PrometheusConfig config = new PrometheusConfig() {
+ @Override
+ public String get(String s) {
+ return null;
+ }
+ };
+
+ prometheusMeterRegistry = new PrometheusMeterRegistry(config);
+ meterRegistry = Metrics.globalRegistry.add(prometheusMeterRegistry);
+
+ mpMetricRegistry = MpMetricRegistry.create("myscope", meterRegistry);
+ }
+
+ @Test
+ void testCounter() {
+ Counter counter = mpMetricRegistry.counter("myCounter");
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestGlobalTagHelper.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestGlobalTagHelper.java
new file mode 100644
index 00000000000..0341196071a
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestGlobalTagHelper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.microprofile;
+
+import java.util.Optional;
+
+import io.helidon.common.testing.junit5.OptionalMatcher;
+import io.micrometer.core.instrument.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.arrayWithSize;
+import static org.hamcrest.Matchers.is;
+
+
+class TestGlobalTagHelper {
+
+ @Test
+ void checkSingle() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ Optional tagsOpt = helper.tags("a=4");
+ assertThat("Optional tags", tagsOpt, OptionalMatcher.optionalPresent());
+ Tag[] tags = tagsOpt.get();
+ assertThat("Single value assignments", tags, arrayWithSize(1));
+ assertThat("Single assignment key", tags[0].getKey(), is("a"));
+ assertThat("Single assignment value", tags[0].getValue(), is("4"));
+ }
+
+ @Test
+ void checkMultiple() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ Optional tagsOpt = helper.tags("a=11,b=12,c=13");
+ assertThat("Optional tags", tagsOpt, OptionalMatcher.optionalPresent());
+ Tag[] tags = tagsOpt.get();
+
+ assertThat("Multiple value assignments", tags, arrayWithSize(3));
+ assertThat("Multiple assignment key 0", tags[0].getKey(), is("a"));
+ assertThat("Multiple assignment value 0", tags[0].getValue(), is("11"));
+ assertThat("Multiple assignment key 1", tags[1].getKey(), is("b"));
+ assertThat("Multiple assignment value 1", tags[1].getValue(), is("12"));
+ assertThat("Multiple assignment key 2", tags[2].getKey(), is("c"));
+ assertThat("Multiple assignment value 2", tags[2].getValue(), is("13"));
+ }
+
+ @Test
+ void checkQuoted() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ Optional tagsOpt = helper.tags("d=r\\=3,e=4,f=0\\,1,g=hi");
+ assertThat("Optional tags", tagsOpt, OptionalMatcher.optionalPresent());
+ Tag[] tags = tagsOpt.get();
+ assertThat("Quoted value assignments", tags, arrayWithSize(4));
+ assertThat("Quoted assignment key 0", tags[0].getKey(), is("d"));
+ assertThat("Quoted assignment value 0", tags[0].getValue(), is("r=3"));
+ assertThat("Quoted assignment key 1", tags[1].getKey(), is("e"));
+ assertThat("Quoted assignment value 1", tags[1].getValue(), is("4"));
+ assertThat("Quoted assignment key 2", tags[2].getKey(), is("f"));
+ assertThat("Quoted assignment value 2", tags[2].getValue(), is("0,1"));
+ assertThat("Quoted assignment key 3", tags[3].getKey(), is("g"));
+ assertThat("Quoted assignment value 3", tags[3].getValue(), is("hi"));
+ }
+
+ @Test
+ void checkErrors() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> helper.tags(""));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("empty"));
+
+ ex = assertThrows(IllegalArgumentException.class, () -> helper.tags("a="));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("found 1"));
+
+ ex = assertThrows(IllegalArgumentException.class, () -> helper.tags("=1"));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("left"));
+
+ ex = assertThrows(IllegalArgumentException.class, () -> helper.tags("a*=1,"));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("tag name"));
+ }
+}
diff --git a/metrics/microprofile/pom.xml b/metrics/microprofile/pom.xml
new file mode 100644
index 00000000000..7c85df1e4f7
--- /dev/null
+++ b/metrics/microprofile/pom.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.metrics
+ helidon-metrics-project
+ 4.0.0-SNAPSHOT
+
+ pom
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-project
+
+ Helidon Metrics MicroProfile Project
+
+
+ microprofile
+ cdi
+
+
diff --git a/metrics/pom.xml b/metrics/pom.xml
index fe55a9edab9..f8cd6271d82 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -35,5 +35,6 @@
trace-exemplar
api
service-api
+ microprofile
From 88e75c1731252085a58859e2c096e2bee6d1e1d6 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Mon, 19 Jun 2023 10:33:57 -0500
Subject: [PATCH 02/67] More tests; start feature work; start CDI work
---
.../microprofile/cdi/MetricsCdiExtension.java | 17 +
.../cdi/src/main/java/module-info.java | 17 +
metrics/microprofile/feature/pom.xml | 105 ++++++
.../feature/MpMetricsFeature.java | 17 +
.../microprofile/feature/package-info.java | 16 +
.../feature/src/main/java/module-info.java | 17 +
.../metrics/microprofile/BaseRegistry.java | 319 ++++++++++++++++++
.../microprofile/MpFunctionCounter.java | 87 +++++
.../microprofile/MpRegistryFactory.java | 17 +
.../microprofile/PrometheusFormatter.java | 17 +
.../metrics/microprofile/TestFormatter.java | 17 +
11 files changed, 646 insertions(+)
create mode 100644 metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
create mode 100644 metrics/microprofile/cdi/src/main/java/module-info.java
create mode 100644 metrics/microprofile/feature/pom.xml
create mode 100644 metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
create mode 100644 metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
create mode 100644 metrics/microprofile/feature/src/main/java/module-info.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
create mode 100644 metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
diff --git a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
new file mode 100644
index 00000000000..12f3533bc4c
--- /dev/null
+++ b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
@@ -0,0 +1,17 @@
+/*
+ * 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.microprofile.cdi;public class MetricsCdiExtension {
+}
diff --git a/metrics/microprofile/cdi/src/main/java/module-info.java b/metrics/microprofile/cdi/src/main/java/module-info.java
new file mode 100644
index 00000000000..05cf3e72a97
--- /dev/null
+++ b/metrics/microprofile/cdi/src/main/java/module-info.java
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+module $MODULE_NAME$ {
+}
\ No newline at end of file
diff --git a/metrics/microprofile/feature/pom.xml b/metrics/microprofile/feature/pom.xml
new file mode 100644
index 00000000000..8d2bb5e36f5
--- /dev/null
+++ b/metrics/microprofile/feature/pom.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-project
+ 4.0.0-SNAPSHOT
+
+ 4.0.0
+
+ helidon-metrics-microprofile-feature
+
+ Helidon Metrics MicroProfile Web Feature
+
+
+ Web feature supporting the /metrics (or otherwise configured) endpoint
+
+
+
+
+ io.helidon.common
+ helidon-common-http
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+
+
+ io.micrometer
+ micrometer-core
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+ true
+
+
+ io.prometheus
+ simpleclient
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.helidon.config
+ helidon-config-metadata-processor
+ ${helidon.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
new file mode 100644
index 00000000000..fffadad8eaa
--- /dev/null
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
@@ -0,0 +1,17 @@
+/*
+ * 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.microprofile.feature;public class MpMetricsFeature {
+}
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
new file mode 100644
index 00000000000..8a1bd052dba
--- /dev/null
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
@@ -0,0 +1,16 @@
+/*
+ * 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.microprofile.feature;
\ No newline at end of file
diff --git a/metrics/microprofile/feature/src/main/java/module-info.java b/metrics/microprofile/feature/src/main/java/module-info.java
new file mode 100644
index 00000000000..05cf3e72a97
--- /dev/null
+++ b/metrics/microprofile/feature/src/main/java/module-info.java
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+module $MODULE_NAME$ {
+}
\ No newline at end of file
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java
new file mode 100644
index 00000000000..1a444dbc0c0
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2018, 2022 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;
+
+import java.lang.management.ClassLoadingMXBean;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.lang.management.ThreadMXBean;
+import java.util.List;
+import java.util.function.Function;
+
+import io.helidon.metrics.api.BaseMetricsSettings;
+import io.helidon.metrics.api.MetricsSettings;
+
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Metric;
+import org.eclipse.microprofile.metrics.MetricType;
+import org.eclipse.microprofile.metrics.MetricUnits;
+import org.eclipse.microprofile.metrics.Tag;
+
+/**
+ * Registry for base metrics as required by Microprofile metrics specification.
+ *
+ *
+ * - All "General JVM Stats" are supported (section 4.1. of the spec).
+ * - All "Thread JVM Stats" are supported (section 4.2. of the spec).
+ * - NONE of "Thread Pool Stats" are supported (section 4.3. of the spec) - Vendor specific approach.
+ * - All "ClassLoading JVM Stats" are supported (section 4.4. of the spec).
+ * - Available Processors and System Load Average (where available from JVM) metrics from "Operating System"
+ * (section 4.5 of the spec).
+ *
+ *
+ * Each metric can be disabled using {@link BaseMetricsSettings.Builder#enableBaseMetric(String, boolean)} or by using the
+ * equivalent configuration property
+ * {@code helidon.metrics.base.${metric_name}.enabled=false}. Further, to suppress
+ * all base metrics use {@link BaseMetricsSettings.Builder#enabled(boolean)} or set the equivalent config property
+ * {@code {{@value BaseMetricsSettings.Builder#}}metrics.base.enabled=false}.
+ */
+final class BaseRegistry extends Registry {
+
+ private static final Tag[] NO_TAGS = new Tag[0];
+
+ private static final Metadata MEMORY_USED_HEAP = Metadata.builder()
+ .withName("memory.usedHeap")
+ .withDisplayName("Used Heap Memory")
+ .withDescription("Displays the amount of used heap memory in bytes.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.BYTES)
+ .build();
+
+ private static final Metadata MEMORY_COMMITTED_HEAP = Metadata.builder()
+ .withName("memory.committedHeap")
+ .withDisplayName("Committed Heap Memory")
+ .withDescription(
+ "Displays the amount of memory in bytes that is "
+ + "committed for the Java virtual "
+ + "machine to use. This amount of memory is "
+ + "guaranteed for the Java virtual "
+ + "machine to use.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.BYTES)
+ .build();
+
+ private static final Metadata MEMORY_MAX_HEAP = Metadata.builder()
+ .withName("memory.maxHeap")
+ .withDisplayName("Max Heap Memory")
+ .withDescription(
+ "Displays the maximum amount of heap memory in bytes that can"
+ + " be used for "
+ + "memory management. This attribute displays -1 if "
+ + "the maximum heap "
+ + "memory size is undefined. This amount of memory is "
+ + "not guaranteed to be "
+ + "available for memory management if it is greater "
+ + "than the amount of "
+ + "committed memory. The Java virtual machine may fail"
+ + " to allocate memory "
+ + "even if the amount of used memory does not exceed "
+ + "this maximum size.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.BYTES)
+ .build();
+
+ private static final Metadata JVM_UPTIME = Metadata.builder()
+ .withName("jvm.uptime")
+ .withDisplayName("JVM Uptime")
+ .withDescription(
+ "Displays the start time of the Java virtual machine in "
+ + "milliseconds. This "
+ + "attribute displays the approximate time when the Java "
+ + "virtual machine "
+ + "started.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.MILLISECONDS)
+ .build();
+
+ private static final Metadata THREAD_COUNT = Metadata.builder()
+ .withName("thread.count")
+ .withDisplayName("Thread Count")
+ .withDescription("Displays the current number of live threads including both "
+ + "daemon and nondaemon threads")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata THREAD_DAEMON_COUNT = Metadata.builder()
+ .withName("thread.daemon.count")
+ .withDisplayName("Daemon Thread Count")
+ .withDescription("Displays the current number of live daemon threads.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata THREAD_MAX_COUNT = Metadata.builder()
+ .withName("thread.max.count")
+ .withDisplayName("Peak Thread Count")
+ .withDescription("Displays the peak live thread count since the Java "
+ + "virtual machine started or "
+ + "peak was reset. This includes daemon and "
+ + "non-daemon threads.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata CL_LOADED_COUNT = Metadata.builder()
+ .withName("classloader.loadedClasses.count")
+ .withDisplayName("Current Loaded Class Count")
+ .withDescription("Displays the number of classes that are currently loaded in "
+ + "the Java virtual machine.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata CL_LOADED_TOTAL = Metadata.builder()
+ .withName("classloader.loadedClasses.total")
+ .withDisplayName("Total Loaded Class Count")
+ .withDescription("Displays the total number of classes that have been loaded "
+ + "since the Java virtual machine has started execution.")
+ .withType(MetricType.COUNTER)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata CL_UNLOADED_COUNT = Metadata.builder()
+ .withName("classloader.unloadedClasses.total")
+ .withDisplayName("Total Unloaded Class Count")
+ .withDescription("Displays the total number of classes unloaded since the Java "
+ + "virtual machine has started execution.")
+ .withType(MetricType.COUNTER)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata OS_AVAILABLE_CPU = Metadata.builder()
+ .withName("cpu.availableProcessors")
+ .withDisplayName("Available Processors")
+ .withDescription("Displays the number of processors available to the Java "
+ + "virtual machine. This "
+ + "value may change during a particular invocation of"
+ + " the virtual machine.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata OS_LOAD_AVERAGE = Metadata.builder()
+ .withName("cpu.systemLoadAverage")
+ .withDisplayName("System Load Average")
+ .withDescription("Displays the system load average for the last minute. The "
+ + "system load average "
+ + "is the sum of the number of runnable entities "
+ + "queued to the available "
+ + "processors and the number of runnable entities "
+ + "running on the available "
+ + "processors averaged over a period of time. The way "
+ + "in which the load average "
+ + "is calculated is operating system specific but is "
+ + "typically a damped timedependent "
+ + "average. If the load average is not available, a "
+ + "negative value is "
+ + "displayed. This attribute is designed to provide a "
+ + "hint about the system load "
+ + "and may be queried frequently. The load average may"
+ + " be unavailable on some "
+ + "platforms where it is expensive to implement this "
+ + "method.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private final MetricsSettings metricsSettings;
+
+ private BaseRegistry(MetricsSettings metricsSettings) {
+ super(Type.BASE, metricsSettings.registrySettings(Type.BASE));
+ this.metricsSettings = metricsSettings;
+ }
+
+ public static Registry create(MetricsSettings metricsSettings) {
+
+ BaseRegistry result = new BaseRegistry(metricsSettings);
+
+ if (!metricsSettings.baseMetricsSettings().isEnabled()) {
+ return result;
+ }
+ MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
+
+ // load all base metrics
+ register(result, MEMORY_USED_HEAP, memoryBean.getHeapMemoryUsage(), MemoryUsage::getUsed);
+ register(result, MEMORY_COMMITTED_HEAP, memoryBean.getHeapMemoryUsage(), MemoryUsage::getCommitted);
+ register(result, MEMORY_MAX_HEAP, memoryBean.getHeapMemoryUsage(), MemoryUsage::getMax);
+
+ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
+ register(result, JVM_UPTIME, runtimeBean, RuntimeMXBean::getUptime);
+
+ ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+ register(result, THREAD_COUNT, threadBean, ThreadMXBean::getThreadCount);
+ register(result, THREAD_DAEMON_COUNT, threadBean, ThreadMXBean::getDaemonThreadCount);
+ register(result, THREAD_MAX_COUNT, threadBean, ThreadMXBean::getPeakThreadCount);
+
+ ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean();
+ register(result, CL_LOADED_COUNT, clBean, ClassLoadingMXBean::getLoadedClassCount);
+ register(result, CL_LOADED_TOTAL, (SimpleCounter) clBean::getTotalLoadedClassCount);
+ register(result, CL_UNLOADED_COUNT, (SimpleCounter) clBean::getUnloadedClassCount);
+
+ OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
+ register(result, OS_AVAILABLE_CPU, osBean, OperatingSystemMXBean::getAvailableProcessors);
+ register(result, OS_LOAD_AVERAGE, osBean, OperatingSystemMXBean::getSystemLoadAverage);
+
+ List gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
+ for (GarbageCollectorMXBean gcBean : gcBeans) {
+ String poolName = gcBean.getName();
+ register(result, gcCountMeta(), (SimpleCounter) gcBean::getCollectionCount,
+ new Tag("name", poolName));
+ register(result, gcTimeMeta(), gcBean, GarbageCollectorMXBean::getCollectionTime,
+ new Tag("name", poolName));
+ }
+
+ return result;
+ }
+
+ private static Metadata gcTimeMeta() {
+ return Metadata.builder()
+ .withName("gc.time")
+ .withDisplayName("Garbage Collection Time")
+ .withDescription(
+ "Displays the approximate accumulated collection elapsed time in milliseconds. "
+ + "This attribute displays -1 if the collection elapsed time is undefined for this "
+ + "collector. The Java virtual machine implementation may use a high resolution "
+ + "timer to measure the elapsed time. This attribute may display the same value "
+ + "even if the collection count has been incremented if the collection elapsed "
+ + "time is very short.")
+ .withType(MetricType.GAUGE)
+ .withUnit(MetricUnits.MILLISECONDS)
+ .build();
+ }
+
+ private static Metadata gcCountMeta() {
+ return Metadata.builder()
+ .withName("gc.total")
+ .withDisplayName("Garbage Collection Count")
+ .withDescription(
+ "Displays the total number of collections that have occurred. This attribute lists "
+ + "-1 if the collection count is undefined for this collector.")
+ .withType(MetricType.COUNTER)
+ .withUnit(MetricUnits.NONE)
+ .build();
+ }
+
+ private static void register(BaseRegistry registry, Metadata meta, Metric metric, Tag... tags) {
+ if (registry.metricsSettings.baseMetricsSettings().isBaseMetricEnabled(meta.getName())
+ && registry.metricsSettings.isMetricEnabled(Type.BASE, meta.getName())) {
+ registry.register(meta, metric, tags);
+ }
+ }
+
+ private static void register(BaseRegistry registry,
+ Metadata meta,
+ T object,
+ Function func,
+ Tag... tags) {
+ if (registry.metricsSettings.baseMetricsSettings().isBaseMetricEnabled(meta.getName())
+ && registry.metricsSettings.isMetricEnabled(Type.BASE, meta.getName())) {
+ registry.gauge(meta, object, func, tags);
+ }
+ }
+
+ private static void register(BaseRegistry registry, Metadata meta, T object, Function func) {
+ register(registry, meta, object, func, NO_TAGS);
+ }
+
+ @FunctionalInterface
+ private interface SimpleCounter extends Counter {
+ @Override
+ default void inc() {
+ throw new IllegalStateException("Cannot increase a system counter");
+ }
+
+ @Override
+ default void inc(long n) {
+ throw new IllegalStateException("Cannot increase a system counter");
+ }
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
new file mode 100644
index 00000000000..ed7ae155c0f
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.microprofile;
+
+import java.util.function.ToDoubleFunction;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tags;
+
+class MpFunctionalCounter extends MpCounter {
+
+ static Builder builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoubleFunction fn) {
+ return new Builder(mpMetricRegistry, name, origin, fn);
+ }
+
+ private MpFunctionalCounter(Builder builder) {
+ super(delegate(builder));
+ }
+
+ private static Counter delegate(Builder builder) {
+ return builder.mpMetricRegistry
+ .meterRegistry()
+ .more()
+ .counter(new Meter.Id(builder.name,
+ builder.tags,
+ builder.baseUnit,
+ builder.description,
+ Meter.Type.COUNTER),
+ builder.origin,
+ builder.fn);
+ }
+
+ static class Builder implements io.helidon.common.Builder, MpFunctionalCounter> {
+
+ private final MpMetricRegistry mpMetricRegistry;
+
+ private final String name;
+ private final ToDoubleFunction fn;
+ private final T origin;
+ private Tags tags;
+ private String description;
+ private String baseUnit;
+
+ private Builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoubleFunction fn) {
+ this.mpMetricRegistry = mpMetricRegistry;
+ this.name = name;
+ this.origin = origin;
+ this.fn = fn;
+ }
+
+ Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ Builder tags(Tags tags) {
+ this.tags = tags;
+ return this;
+ }
+
+ Builder baseUnit(String baseUnit) {
+ this.baseUnit = baseUnit;
+ return this;
+ }
+
+ @Override
+ public MpFunctionalCounter build() {
+ return new MpFunctionalCounter(this);
+ }
+
+
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
new file mode 100644
index 00000000000..860429a78c1
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
@@ -0,0 +1,17 @@
+/*
+ * 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.microprofile;public class MpRegistryFactory {
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
new file mode 100644
index 00000000000..25b2d6cbd4b
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
@@ -0,0 +1,17 @@
+/*
+ * 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.microprofile;public class PrometheusFormatter {
+}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
new file mode 100644
index 00000000000..5d0073a2f0b
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
@@ -0,0 +1,17 @@
+/*
+ * 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.microprofile;public class TestFormatter {
+}
From a0a83243467b99afc567fc0f5a1c7a8c5a2583d8 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Mon, 19 Jun 2023 10:36:03 -0500
Subject: [PATCH 03/67] More tests; start feature work; start CDI work
---
bom/pom.xml | 5 +
.../common/media/type/MediaTypeEnum.java | 3 +-
.../helidon/common/media/type/MediaTypes.java | 4 +
dependencies/pom.xml | 5 +
metrics/microprofile/cdi/pom.xml | 16 +-
.../microprofile/cdi/MetricsCdiExtension.java | 28 ++-
.../cdi/src/main/java/module-info.java | 14 +-
metrics/microprofile/feature/pom.xml | 14 +-
.../feature/MpMetricsFeature.java | 100 ++++++++++-
.../microprofile/feature/package-info.java | 4 +
.../feature/src/main/java/module-info.java | 8 +-
.../metrics/microprofile/BaseRegistry.java | 98 +++-------
.../microprofile/MpFunctionCounter.java | 64 ++++---
.../metrics/microprofile/MpMetricId.java | 28 ++-
.../microprofile/MpMetricRegistry.java | 43 ++++-
.../microprofile/MpRegistryFactory.java | 97 +++++++++-
.../microprofile/PrometheusFormatter.java | 167 +++++++++++++++++-
.../src/main/java/module-info.java | 7 +
.../metrics/microprofile/TestCounters.java | 41 +++++
.../metrics/microprofile/TestFormatter.java | 59 ++++++-
metrics/microprofile/pom.xml | 1 +
21 files changed, 684 insertions(+), 122 deletions(-)
diff --git a/bom/pom.xml b/bom/pom.xml
index e85782157f9..02b44f05d64 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -535,6 +535,11 @@
helidon-metrics-microprofile-cdi
${helidon.version}
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-feature
+ ${helidon.version}
+
diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java
index aeb25553fdf..40d197c557d 100644
--- a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java
+++ b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -34,6 +34,7 @@ enum MediaTypeEnum implements MediaType {
TEXT_PLAIN("text", "plain"),
TEXT_XML("text", "xml"),
TEXT_HTML("text", "html"),
+ APPLICATION_OPENMETRICS_TEXT("application", "openmetrics-text"),
APPLICATION_OPENAPI_YAML("application", "vnd.oai.openapi"),
APPLICATION_OPENAPI_JSON("application", "vnd.oai.openapi+json"),
APPLICATION_X_YAML("application", "x-yaml"),
diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java
index 66fe780fdb1..a46e36a1076 100644
--- a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java
+++ b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java
@@ -94,6 +94,10 @@ public final class MediaTypes {
* {@code application/vnd.oai.openapi+json} media type.
*/
public static final MediaType APPLICATION_OPENAPI_JSON = MediaTypeEnum.APPLICATION_OPENAPI_JSON;
+ /**
+ * {@code application/openmetrics-text} media type.
+ */
+ public static final MediaType APPLICATION_OPENMETRICS_TEXT = MediaTypeEnum.APPLICATION_OPENMETRICS_TEXT;
/**
* {@code application/x-yaml} media type.
*/
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index bda53b46be5..2d81b1273e8 100644
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -496,6 +496,11 @@
simpleclient
${version.lib.prometheus}
+
+ io.prometheus
+ simpleclient_common
+ ${version.lib.prometheus}
+
io.zipkin.zipkin2
zipkin
diff --git a/metrics/microprofile/cdi/pom.xml b/metrics/microprofile/cdi/pom.xml
index 2d4e65cd408..34372e2d23d 100644
--- a/metrics/microprofile/cdi/pom.xml
+++ b/metrics/microprofile/cdi/pom.xml
@@ -27,7 +27,7 @@
helidon-metrics-microprofile-cdi
- Helidon Metrics MicroProfile CDI elements
+ Helidon Metrics MicroProfile CDI Component
CDI elements (extension, producers, etc.) for MicroProfile Metrics support
@@ -42,6 +42,14 @@
org.eclipse.microprofile.metrics
microprofile-metrics-api
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-feature
+
+
+ io.helidon.microprofile.service-common
+ helidon-microprofile-service-common
+
org.junit.jupiter
junit-jupiter-api
@@ -62,6 +70,12 @@
hamcrest-all
test
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile-feature
+ 4.0.0-SNAPSHOT
+ compile
+
diff --git a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
index 12f3533bc4c..c80fcf3423d 100644
--- a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
+++ b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
@@ -13,5 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.metrics.microprofile.cdi;public class MetricsCdiExtension {
+package io.helidon.metrics.microprofile.cdi;
+
+import java.util.function.Function;
+
+import io.helidon.config.Config;
+import io.helidon.metrics.microprofile.feature.MpMetricsFeature;
+import io.helidon.microprofile.servicecommon.HelidonRestCdiExtension;
+
+import jakarta.enterprise.inject.spi.ProcessManagedBean;
+
+public class MetricsCdiExtension extends HelidonRestCdiExtension {
+
+ private static final System.Logger LOGGER = System.getLogger(MetricsCdiExtension.class.getName());
+
+ private static final Function FEATURE_FACTORY =
+ (Config config) -> MpMetricsFeature.builder().config(config).build();
+
+ /**
+ * Common initialization for concrete implementations.
+ */
+ protected MetricsCdiExtension() {
+ super(LOGGER, FEATURE_FACTORY, "mp.metrics");
+ }
+
+ @Override
+ protected void processManagedBean(ProcessManagedBean> processManagedBean) {
+ }
}
diff --git a/metrics/microprofile/cdi/src/main/java/module-info.java b/metrics/microprofile/cdi/src/main/java/module-info.java
index 05cf3e72a97..ef67ebd4cc7 100644
--- a/metrics/microprofile/cdi/src/main/java/module-info.java
+++ b/metrics/microprofile/cdi/src/main/java/module-info.java
@@ -13,5 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-module $MODULE_NAME$ {
+
+/**
+ * MicroProfile metrics module
+ */
+module helidon.metrics.microprofile.cdi {
+
+ requires io.helidon.metrics.microprofile;
+ requires io.helidon.metrics.microprofile.feature;
+ requires io.helidon.microprofile.cdi;
+ requires io.helidon.microprofile.servicecommon;
+ requires jakarta.cdi;
+ requires io.helidon.config;
+ requires microprofile.config.api;
}
\ No newline at end of file
diff --git a/metrics/microprofile/feature/pom.xml b/metrics/microprofile/feature/pom.xml
index 8d2bb5e36f5..75d10f9c085 100644
--- a/metrics/microprofile/feature/pom.xml
+++ b/metrics/microprofile/feature/pom.xml
@@ -42,6 +42,10 @@
io.helidon.microprofile.config
helidon-microprofile-config
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile
+
io.micrometer
micrometer-core
@@ -49,12 +53,14 @@
io.micrometer
micrometer-registry-prometheus
- true
io.prometheus
simpleclient
- true
+
+
+ io.prometheus
+ simpleclient_common
org.junit.jupiter
@@ -81,6 +87,10 @@
helidon-common-testing-junit5
test
+
+ io.helidon.nima.service-common
+ helidon-nima-service-common
+
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
index fffadad8eaa..e62c2b29efc 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
@@ -13,5 +13,103 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.metrics.microprofile.feature;public class MpMetricsFeature {
+package io.helidon.metrics.microprofile.feature;
+
+import java.util.Optional;
+
+import io.helidon.common.http.Http;
+import io.helidon.common.media.type.MediaType;
+import io.helidon.common.media.type.MediaTypes;
+import io.helidon.config.Config;
+import io.helidon.metrics.microprofile.PrometheusFormatter;
+import io.helidon.nima.servicecommon.HelidonFeatureSupport;
+import io.helidon.nima.webserver.http.HttpRules;
+import io.helidon.nima.webserver.http.HttpService;
+import io.helidon.nima.webserver.http.ServerRequest;
+import io.helidon.nima.webserver.http.ServerResponse;
+
+public class MpMetricsFeature extends HelidonFeatureSupport {
+
+ private static final System.Logger LOGGER = System.getLogger(MpMetricsFeature.class.getName());
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static MpMetricsFeature create() {
+ return builder().build();
+ }
+ protected MpMetricsFeature(System.Logger logger, Builder builder, String serviceName) {
+ super(logger, builder, serviceName);
+ }
+
+ @Override
+ public Optional service() {
+ if (enabled()) {
+ return Optional.of(this::configureRoutes);
+ } else {
+ return Optional.of(this::configureDisabledRoutes);
+ }
+ }
+
+ private void configureRoutes(HttpRules rules) {
+ rules.get("/", this::prepareResponse);
+ }
+
+ private void configureDisabledRoutes(HttpRules rules) {
+ rules.get("/",this::prepareDisabledResponse);
+ }
+
+ private void prepareDisabledResponse(ServerRequest req, ServerResponse resp) {
+ resp.status(Http.Status.NOT_IMPLEMENTED_501)
+ .header(Http.Header.CONTENT_TYPE, MediaTypes.TEXT_PLAIN.text())
+ .send("Metrics is disabled");
+ }
+
+ private void prepareResponse(ServerRequest req, ServerResponse resp) {
+
+ Optional requestedMediaType = req.headers()
+ .bestAccepted(PrometheusFormatter.MEDIA_TYPE_TO_FORMAT
+ .keySet()
+ .toArray(new MediaType[0]));
+ if (requestedMediaType.isEmpty()) {
+ LOGGER.log(System.Logger.Level.TRACE,
+ "Unable to compose Prometheus format response; request accepted types were "
+ + req.headers().acceptedTypes());
+ resp.status(Http.Status.UNSUPPORTED_MEDIA_TYPE_415).send();
+ }
+
+ try {
+ MediaType resultMediaType = requestedMediaType.get();
+ resp.status(Http.Status.OK_200);
+ resp.headers().contentType(resultMediaType);
+ resp.send(PrometheusFormatter.filteredOutput(resultMediaType, scope(req), metricName(req)));
+ } catch (Exception ex) {
+ resp.status(Http.Status.INTERNAL_SERVER_ERROR_500);
+ resp.send("Error preparing metrics output; " + ex.getMessage());
+ logger().log(System.Logger.Level.ERROR, "Error preparing metrics output", ex);
+ }
+ }
+
+ private Optional scope(ServerRequest req) {
+ return req.query().first("scope");
+ }
+
+ private Optional metricName(ServerRequest req) {
+ return req.query().first("name");
+ }
+
+ public static class Builder extends HelidonFeatureSupport.Builder {
+
+ private static final String DEFAULT_WEB_CONTEXT = "/metrics";
+
+ Builder() {
+ super(DEFAULT_WEB_CONTEXT);
+ }
+
+ @Override
+ public MpMetricsFeature build() {
+ return new MpMetricsFeature(LOGGER, this, "MP-metrics");
+ }
+ }
}
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
index 8a1bd052dba..aca929be9f3 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
@@ -13,4 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+/**
+ * MicroProfile metrics web feature
+ */
package io.helidon.metrics.microprofile.feature;
\ No newline at end of file
diff --git a/metrics/microprofile/feature/src/main/java/module-info.java b/metrics/microprofile/feature/src/main/java/module-info.java
index 05cf3e72a97..0db664b7042 100644
--- a/metrics/microprofile/feature/src/main/java/module-info.java
+++ b/metrics/microprofile/feature/src/main/java/module-info.java
@@ -13,5 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-module $MODULE_NAME$ {
+module io.helidon.metrics.microprofile.feature {
+
+ requires io.helidon.metrics.microprofile;
+ requires io.helidon.nima.servicecommon;
+ requires simpleclient.common;
+
+ exports io.helidon.metrics.microprofile.feature;
}
\ No newline at end of file
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java
index 1a444dbc0c0..a7b2939ac29 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/BaseRegistry.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.metrics;
+package io.helidon.metrics.microprofile;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.GarbageCollectorMXBean;
@@ -26,14 +26,10 @@
import java.lang.management.ThreadMXBean;
import java.util.List;
import java.util.function.Function;
+import java.util.function.ToDoubleFunction;
-import io.helidon.metrics.api.BaseMetricsSettings;
-import io.helidon.metrics.api.MetricsSettings;
-
-import org.eclipse.microprofile.metrics.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Metric;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Tag;
@@ -49,40 +45,30 @@
* (section 4.5 of the spec).
*
*
- * Each metric can be disabled using {@link BaseMetricsSettings.Builder#enableBaseMetric(String, boolean)} or by using the
- * equivalent configuration property
- * {@code helidon.metrics.base.${metric_name}.enabled=false}. Further, to suppress
- * all base metrics use {@link BaseMetricsSettings.Builder#enabled(boolean)} or set the equivalent config property
- * {@code {{@value BaseMetricsSettings.Builder#}}metrics.base.enabled=false}.
*/
-final class BaseRegistry extends Registry {
+final class BaseRegistry extends MpMetricRegistry {
private static final Tag[] NO_TAGS = new Tag[0];
private static final Metadata MEMORY_USED_HEAP = Metadata.builder()
.withName("memory.usedHeap")
- .withDisplayName("Used Heap Memory")
.withDescription("Displays the amount of used heap memory in bytes.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.BYTES)
.build();
private static final Metadata MEMORY_COMMITTED_HEAP = Metadata.builder()
.withName("memory.committedHeap")
- .withDisplayName("Committed Heap Memory")
.withDescription(
"Displays the amount of memory in bytes that is "
+ "committed for the Java virtual "
+ "machine to use. This amount of memory is "
+ "guaranteed for the Java virtual "
+ "machine to use.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.BYTES)
.build();
private static final Metadata MEMORY_MAX_HEAP = Metadata.builder()
.withName("memory.maxHeap")
- .withDisplayName("Max Heap Memory")
.withDescription(
"Displays the maximum amount of heap memory in bytes that can"
+ " be used for "
@@ -96,92 +82,74 @@ final class BaseRegistry extends Registry {
+ " to allocate memory "
+ "even if the amount of used memory does not exceed "
+ "this maximum size.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.BYTES)
.build();
private static final Metadata JVM_UPTIME = Metadata.builder()
.withName("jvm.uptime")
- .withDisplayName("JVM Uptime")
.withDescription(
"Displays the start time of the Java virtual machine in "
+ "milliseconds. This "
+ "attribute displays the approximate time when the Java "
+ "virtual machine "
+ "started.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.MILLISECONDS)
.build();
private static final Metadata THREAD_COUNT = Metadata.builder()
.withName("thread.count")
- .withDisplayName("Thread Count")
.withDescription("Displays the current number of live threads including both "
+ "daemon and nondaemon threads")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata THREAD_DAEMON_COUNT = Metadata.builder()
.withName("thread.daemon.count")
- .withDisplayName("Daemon Thread Count")
.withDescription("Displays the current number of live daemon threads.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata THREAD_MAX_COUNT = Metadata.builder()
.withName("thread.max.count")
- .withDisplayName("Peak Thread Count")
.withDescription("Displays the peak live thread count since the Java "
+ "virtual machine started or "
+ "peak was reset. This includes daemon and "
+ "non-daemon threads.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata CL_LOADED_COUNT = Metadata.builder()
.withName("classloader.loadedClasses.count")
- .withDisplayName("Current Loaded Class Count")
.withDescription("Displays the number of classes that are currently loaded in "
+ "the Java virtual machine.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata CL_LOADED_TOTAL = Metadata.builder()
.withName("classloader.loadedClasses.total")
- .withDisplayName("Total Loaded Class Count")
.withDescription("Displays the total number of classes that have been loaded "
+ "since the Java virtual machine has started execution.")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata CL_UNLOADED_COUNT = Metadata.builder()
.withName("classloader.unloadedClasses.total")
- .withDisplayName("Total Unloaded Class Count")
.withDescription("Displays the total number of classes unloaded since the Java "
+ "virtual machine has started execution.")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata OS_AVAILABLE_CPU = Metadata.builder()
.withName("cpu.availableProcessors")
- .withDisplayName("Available Processors")
.withDescription("Displays the number of processors available to the Java "
+ "virtual machine. This "
+ "value may change during a particular invocation of"
+ " the virtual machine.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata OS_LOAD_AVERAGE = Metadata.builder()
.withName("cpu.systemLoadAverage")
- .withDisplayName("System Load Average")
.withDescription("Displays the system load average for the last minute. The "
+ "system load average "
+ "is the sum of the number of runnable entities "
@@ -200,24 +168,13 @@ final class BaseRegistry extends Registry {
+ " be unavailable on some "
+ "platforms where it is expensive to implement this "
+ "method.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
- private final MetricsSettings metricsSettings;
-
- private BaseRegistry(MetricsSettings metricsSettings) {
- super(Type.BASE, metricsSettings.registrySettings(Type.BASE));
- this.metricsSettings = metricsSettings;
- }
-
- public static Registry create(MetricsSettings metricsSettings) {
+ static MpMetricRegistry create(MeterRegistry meterRegistry) {
- BaseRegistry result = new BaseRegistry(metricsSettings);
+ BaseRegistry result = new BaseRegistry(meterRegistry);
- if (!metricsSettings.baseMetricsSettings().isEnabled()) {
- return result;
- }
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
// load all base metrics
@@ -235,8 +192,8 @@ public static Registry create(MetricsSettings metricsSettings) {
ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean();
register(result, CL_LOADED_COUNT, clBean, ClassLoadingMXBean::getLoadedClassCount);
- register(result, CL_LOADED_TOTAL, (SimpleCounter) clBean::getTotalLoadedClassCount);
- register(result, CL_UNLOADED_COUNT, (SimpleCounter) clBean::getUnloadedClassCount);
+ registerCounter(result, CL_LOADED_TOTAL, clBean, ClassLoadingMXBean::getTotalLoadedClassCount);
+ registerCounter(result, CL_UNLOADED_COUNT, clBean, ClassLoadingMXBean::getUnloadedClassCount);
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
register(result, OS_AVAILABLE_CPU, osBean, OperatingSystemMXBean::getAvailableProcessors);
@@ -245,7 +202,7 @@ public static Registry create(MetricsSettings metricsSettings) {
List gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcBean : gcBeans) {
String poolName = gcBean.getName();
- register(result, gcCountMeta(), (SimpleCounter) gcBean::getCollectionCount,
+ registerCounter(result, gcCountMeta(), gcBean, GarbageCollectorMXBean::getCollectionCount,
new Tag("name", poolName));
register(result, gcTimeMeta(), gcBean, GarbageCollectorMXBean::getCollectionTime,
new Tag("name", poolName));
@@ -257,7 +214,6 @@ public static Registry create(MetricsSettings metricsSettings) {
private static Metadata gcTimeMeta() {
return Metadata.builder()
.withName("gc.time")
- .withDisplayName("Garbage Collection Time")
.withDescription(
"Displays the approximate accumulated collection elapsed time in milliseconds. "
+ "This attribute displays -1 if the collection elapsed time is undefined for this "
@@ -265,7 +221,6 @@ private static Metadata gcTimeMeta() {
+ "timer to measure the elapsed time. This attribute may display the same value "
+ "even if the collection count has been incremented if the collection elapsed "
+ "time is very short.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.MILLISECONDS)
.build();
}
@@ -273,47 +228,34 @@ private static Metadata gcTimeMeta() {
private static Metadata gcCountMeta() {
return Metadata.builder()
.withName("gc.total")
- .withDisplayName("Garbage Collection Count")
.withDescription(
"Displays the total number of collections that have occurred. This attribute lists "
+ "-1 if the collection count is undefined for this collector.")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
}
- private static void register(BaseRegistry registry, Metadata meta, Metric metric, Tag... tags) {
- if (registry.metricsSettings.baseMetricsSettings().isBaseMetricEnabled(meta.getName())
- && registry.metricsSettings.isMetricEnabled(Type.BASE, meta.getName())) {
- registry.register(meta, metric, tags);
- }
- }
-
private static void register(BaseRegistry registry,
Metadata meta,
T object,
Function func,
Tag... tags) {
- if (registry.metricsSettings.baseMetricsSettings().isBaseMetricEnabled(meta.getName())
- && registry.metricsSettings.isMetricEnabled(Type.BASE, meta.getName())) {
- registry.gauge(meta, object, func, tags);
- }
+ registry.gauge(meta, object, func, tags);
+ }
+
+ private static void registerCounter(BaseRegistry registry,
+ Metadata meta,
+ T object,
+ ToDoubleFunction func,
+ Tag... tags) {
+ registry.counter(meta, object, func, tags);
}
private static void register(BaseRegistry registry, Metadata meta, T object, Function func) {
register(registry, meta, object, func, NO_TAGS);
}
- @FunctionalInterface
- private interface SimpleCounter extends Counter {
- @Override
- default void inc() {
- throw new IllegalStateException("Cannot increase a system counter");
- }
-
- @Override
- default void inc(long n) {
- throw new IllegalStateException("Cannot increase a system counter");
- }
+ private BaseRegistry(MeterRegistry meterRegistry) {
+ super(MpRegistryFactory.BASE_SCOPE, meterRegistry);
}
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
index ed7ae155c0f..55527a313e4 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
@@ -17,34 +17,58 @@
import java.util.function.ToDoubleFunction;
-import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Tags;
+import org.eclipse.microprofile.metrics.Counter;
-class MpFunctionalCounter extends MpCounter {
+class MpFunctionCounter extends MpMetric implements Meter, Counter {
static Builder builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoubleFunction fn) {
- return new Builder(mpMetricRegistry, name, origin, fn);
+ return new Builder<>(mpMetricRegistry, name, origin, fn);
}
- private MpFunctionalCounter(Builder builder) {
+ private MpFunctionCounter(Builder> builder) {
super(delegate(builder));
}
- private static Counter delegate(Builder builder) {
+ @Override
+ public Id getId() {
+ return delegate().getId();
+ }
+
+ @Override
+ public Iterable measure() {
+ return delegate().measure();
+ }
+
+ @Override
+ public void inc() {
+ throw new UnsupportedOperationException("Not allowed on " + MpFunctionCounter.class.getName());
+ }
+
+ @Override
+ public void inc(long l) {
+ throw new UnsupportedOperationException("Not allowed on " + MpFunctionCounter.class.getName());
+ }
+
+ @Override
+ public long getCount() {
+ return (long) delegate().count();
+ }
+
+ private static FunctionCounter delegate(Builder builder) {
return builder.mpMetricRegistry
.meterRegistry()
.more()
- .counter(new Meter.Id(builder.name,
- builder.tags,
- builder.baseUnit,
- builder.description,
- Meter.Type.COUNTER),
+ .counter(builder.name,
+ builder.tags,
builder.origin,
builder.fn);
}
- static class Builder implements io.helidon.common.Builder, MpFunctionalCounter> {
+ static class Builder implements io.helidon.common.Builder, MpFunctionCounter> {
private final MpMetricRegistry mpMetricRegistry;
@@ -52,8 +76,6 @@ static class Builder implements io.helidon.common.Builder, MpFunct
private final ToDoubleFunction fn;
private final T origin;
private Tags tags;
- private String description;
- private String baseUnit;
private Builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoubleFunction fn) {
this.mpMetricRegistry = mpMetricRegistry;
@@ -62,26 +84,14 @@ private Builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoub
this.fn = fn;
}
- Builder description(String description) {
- this.description = description;
- return this;
- }
-
Builder tags(Tags tags) {
this.tags = tags;
return this;
}
- Builder baseUnit(String baseUnit) {
- this.baseUnit = baseUnit;
- return this;
- }
-
@Override
- public MpFunctionalCounter build() {
- return new MpFunctionalCounter(this);
+ public MpFunctionCounter build() {
+ return new MpFunctionCounter(this);
}
-
-
}
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
index 7528199ea99..ff2db8e3eb7 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
@@ -17,6 +17,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Tags;
@@ -25,9 +26,6 @@
public class MpMetricId extends MetricID {
- // Functionally equivalent to the superclass MetricID, so we don't need equals or hashCode to account for any private
- // fields here.
-
private Tags fullTags = Tags.empty();
private final Meter.Id meterId;
@@ -47,7 +45,31 @@ public String name() {
return getName();
}
+ public Tags fullTags() {
+ return fullTags;
+ }
+
Meter.Id meterId() {
return meterId;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ MpMetricId that = (MpMetricId) o;
+ return fullTags.equals(that.fullTags) && meterId.equals(that.meterId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), fullTags, meterId);
+ }
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
index dfe341543d5..3b6b9910c46 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
@@ -29,6 +29,7 @@
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
@@ -70,7 +71,7 @@ static MpMetricRegistry create(String scope, MeterRegistry meterRegistry) {
return new MpMetricRegistry(scope, meterRegistry);
}
- private MpMetricRegistry(String scope, MeterRegistry meterRegistry) {
+ protected MpMetricRegistry(String scope, MeterRegistry meterRegistry) {
this.scope = scope;
this.meterRegistry = meterRegistry;
}
@@ -118,6 +119,10 @@ public Counter counter(Metadata metadata, Tag... tags) {
tags);
}
+ Counter counter(Metadata metadata, T valueOrigin, ToDoubleFunction fn, Tag... tags) {
+ return getOrCreateAndStoreFunctionCounter(validatedMetadata(metadata), valueOrigin, fn);
+ }
+
@Override
public Gauge gauge(String s, T t, Function function, Tag... tags) {
return null;
@@ -358,6 +363,10 @@ public String getScope() {
return scope;
}
+ MeterRegistry meterRegistry() {
+ return meterRegistry;
+ }
+
, T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
Function metricFactory,
BiFunction, T extends Meter> M getOrCreateAndStoreMetric(Meter.Type
return (M) result;
}
- T delegate = meterFactory.apply(mpMetricId.name(), MpTags.fromMp(mpMetricId.getTags()));
+ T delegate = meterFactory.apply(mpMetricId.name(), mpMetricId.fullTags());
M newMetric = metricFactory.apply(delegate);
storeMetadataIfAbsent(validMetadata);
metricsById.put(mpMetricId, newMetric);
- metricsById.put(mpMetricId, newMetric);
metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
return newMetric;
});
}
+ Counter getOrCreateAndStoreFunctionCounter(Metadata validMetadata,
+ T valueOrigin,
+ ToDoubleFunction fn,
+ Tag... tags) {
+
+ /*
+ * From the metadata create a candidate MpMetricID, validate it (to make sure the tag names are consistent with any
+ * previously-registered metrics with the same name and that the user did not specify any reserved tags), and then
+ * augment the inner meter ID with the scope tag and, if an app name is specified via config, the app name tag.
+ */
+ MpMetricId mpMetricId = validAugmentedMpMetricId(validMetadata, Meter.Type.COUNTER, tags);
+ return access(() -> {
+ MpMetric> result = metricsById.get(mpMetricId);
+ if (result != null) {
+ return (MpFunctionCounter) result;
+ }
+
+ MpFunctionCounter newMetric = MpFunctionCounter.builder(this,
+ validMetadata.getName(),
+ valueOrigin, fn)
+ .build();
+
+ storeMetadataIfAbsent(validMetadata);
+ metricsById.put(mpMetricId, newMetric);
+ metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
+ metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
+ return newMetric;
+ });
+ }
private void storeMetadataIfAbsent(Metadata validatedMetadata) {
metadata.putIfAbsent(validatedMetadata.getName(), validatedMetadata);
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
index 860429a78c1..0273227d6d7 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
@@ -13,5 +13,100 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.metrics.microprofile;public class MpRegistryFactory {
+package io.helidon.metrics.microprofile;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+
+public class MpRegistryFactory {
+
+ public static final String APPLICATION_SCOPE = "application";
+ public static final String BASE_SCOPE = "base";
+ public static final String VENDOR_SCOPE = "vendor";
+
+ private static MpRegistryFactory INSTANCE;
+
+ private static final ReentrantLock lock = new ReentrantLock();
+ private final Exception creation;
+
+ private Config mpConfig;
+ private final Map registries = new HashMap<>();
+ private final PrometheusMeterRegistry prometheusMeterRegistry;
+
+
+ public static MpRegistryFactory create(Config mpConfig) {
+ lock.lock();
+ try {
+ if (INSTANCE == null) {
+ INSTANCE = new MpRegistryFactory(mpConfig);
+ return INSTANCE;
+ } else {
+ throw new IllegalStateException("Attempt to set up MpRegistryFactory multiple times; previous invocation follows: ",
+ INSTANCE.creation);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public static MpRegistryFactory get() {
+ lock.lock();
+ try {
+ if (INSTANCE == null) {
+ INSTANCE = new MpRegistryFactory(ConfigProvider.getConfig());
+ }
+ return INSTANCE;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private MpRegistryFactory(Config mpConfig) {
+ creation = new Exception("Initial creation of " + MpRegistryFactory.class.getSimpleName());
+ this.mpConfig = mpConfig;
+ prometheusMeterRegistry = findOrAddPrometheusRegistry();
+ registries.put(APPLICATION_SCOPE, MpMetricRegistry.create(APPLICATION_SCOPE, Metrics.globalRegistry));
+ registries.put(BASE_SCOPE, BaseRegistry.create(Metrics.globalRegistry));
+ registries.put(VENDOR_SCOPE, MpMetricRegistry.create(VENDOR_SCOPE, Metrics.globalRegistry));
+ }
+
+ public MetricRegistry registry(String scope) {
+// if (INSTANCE == null) {
+// throw new IllegalStateException("Attempt to use " + MpRegistryFactory.class.getName() + " before invoking create");
+// }
+ return registries.computeIfAbsent(scope,
+ s -> MpMetricRegistry.create(s, Metrics.globalRegistry));
+ }
+
+ public PrometheusMeterRegistry prometheusMeterRegistry() {
+// if (INSTANCE == null) {
+// throw new IllegalStateException("Attempt to use " + MpRegistryFactory.class.getName() + " before invoking create");
+// }
+ return prometheusMeterRegistry;
+ }
+
+ private PrometheusMeterRegistry findOrAddPrometheusRegistry() {
+// if (INSTANCE == null) {
+// throw new IllegalStateException("Attempt to use " + MpRegistryFactory.class.getName() + " before invoking create");
+// }
+ return Metrics.globalRegistry.getRegistries().stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseGet(() -> {
+ PrometheusMeterRegistry result = new PrometheusMeterRegistry(
+ s -> mpConfig.getOptionalValue("mp.metrics." + s, String.class)
+ .orElse(null));
+ Metrics.addRegistry(result);
+ return result;
+ });
+ }
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
index 25b2d6cbd4b..2d2287160bf 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
@@ -13,5 +13,170 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.metrics.microprofile;public class PrometheusFormatter {
+package io.helidon.metrics.microprofile;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import io.helidon.common.media.type.MediaType;
+import io.helidon.common.media.type.MediaTypes;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import io.prometheus.client.exporter.common.TextFormat;
+
+/**
+ * Retrieves and prepares meter output according to the formats supported by the Prometheus meter registry.
+ *
+ * Because the Prometheus exposition format is flat, and because some meter types have multiple values, the meter names
+ * in the output repeat the actual meter name with suffixes to indicate the specific quantities (e.g.,
+ * count, total, max) each reported value conveys. Further, meter names in the output might need the prefix
+ * "m_" if the actual meter name starts with a digit or underscore and underscores replace special characters.
+ *
+ */
+public class PrometheusFormatter {
+ public static final Map MEDIA_TYPE_TO_FORMAT = Map.of(MediaTypes.TEXT_PLAIN,
+ TextFormat.CONTENT_TYPE_004,
+ MediaTypes.APPLICATION_OPENMETRICS_TEXT,
+ TextFormat.CONTENT_TYPE_OPENMETRICS_100);
+ private static final String PROMETHEUS_TYPE_PREFIX = "# TYPE";
+ private static final String PROMETHEUS_HELP_PREFIX = "# HELP";
+
+ public static String filteredOutput(MediaType resultMediaType,
+ Optional scopeSelection,
+ Optional meterNameSelection) {
+ return formattedOutput(MpRegistryFactory.get().prometheusMeterRegistry(),
+ resultMediaType,
+ scopeSelection,
+ meterNameSelection);
+ }
+
+ static String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
+ MediaType resultMediaType,
+ Optional scopeSelection,
+ Optional meterNameSelection) {
+ String rawPrometheusOutput = prometheusMeterRegistry
+ .scrape(PrometheusFormatter.MEDIA_TYPE_TO_FORMAT.get(resultMediaType),
+ meterNamesOfInterest(prometheusMeterRegistry, meterNameSelection));
+
+ return filter(rawPrometheusOutput, scopeSelection, meterNameSelection);
+
+ }
+
+ static String filter(String output, Optional scope, Optional meterName) {
+ /*
+ * Output looks like repeating sections of this:
+ *
+ * # HELP xxx
+ * # TYPE yyy
+ * meter-name{tagA=value1,tagB=value2} data
+ * meter-name{tagA=value3,tagB=value4} otherData
+ * ... (possibly more lines for the same meter with different tag values)
+ *
+ *
+ * If we are limiting by scope or meter name, then suppress the help and type if there is no meter data.
+ */
+ Pattern scopePattern = scope.isPresent()
+ ? Pattern.compile("\\{.*mp_scope=\"" + scope.get() + "\")}")
+ : null;
+
+ StringBuilder allOutput = new StringBuilder();
+ StringBuilder typeAndHelpOutput = new StringBuilder();
+ StringBuilder metricOutput = new StringBuilder();
+
+ String[] lines = output.split("\r?\n");
+
+ for (String line : lines) {
+ if (line.startsWith(PROMETHEUS_HELP_PREFIX)) {
+ allOutput.append(flushForMeterAndClear(typeAndHelpOutput, metricOutput));
+ typeAndHelpOutput.append(line)
+ .append(System.lineSeparator());
+ } else if (line.startsWith(PROMETHEUS_TYPE_PREFIX)) {
+ typeAndHelpOutput.append(line)
+ .append(System.lineSeparator());
+ } else if (scopePattern == null || scopePattern.matcher(line).matches()) {
+ metricOutput.append(line)
+ .append(System.lineSeparator());
+ }
+ }
+ return allOutput.append(flushForMeterAndClear(typeAndHelpOutput, metricOutput))
+ .toString()
+ .replaceFirst("# EOF\r?\n?", "");
+
+ }
+
+ private static String flushForMeterAndClear(StringBuilder helpAndType, StringBuilder metricData) {
+ StringBuilder result = new StringBuilder();
+ if (!metricData.isEmpty()) {
+ result.append(helpAndType.toString())
+ .append(metricData);
+ }
+ helpAndType.setLength(0);
+ metricData.setLength(0);
+ return result.toString();
+ }
+
+ static Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterRegistry,
+ Optional meterNameSelection) {
+ if (meterNameSelection.isEmpty()) {
+ return null; // null passed to PrometheusMeterRegistry.scrape means "no selection based on meter name
+ }
+
+ // Meter names in the output include units (if specified) so retrieve matching meters to get the units.
+ String normalizedMeterName = normalizeMeterName(meterNameSelection.get());
+ Set unitsForMeter = new HashSet<>();
+ unitsForMeter.add("");
+
+ Set suffixesForMeter = new HashSet<>();
+ suffixesForMeter.add("");
+
+ prometheusMeterRegistry.find(meterNameSelection.get())
+ .meters()
+ .forEach(meter -> {
+ Meter.Id meterId = meter.getId();
+ unitsForMeter.add("_" + meterId.getBaseUnit());
+ suffixesForMeter.addAll(meterNameSuffixes(meterId.getType()));
+ });
+
+ Set result = new HashSet<>();
+ unitsForMeter.forEach(units -> suffixesForMeter.forEach(
+ suffix -> result.add(normalizedMeterName + units + suffix)));
+
+ return result;
+ }
+
+ static Set meterNameSuffixes(Meter.Type meterType) {
+ return switch (meterType) {
+ case COUNTER -> Set.of("_total");
+ case LONG_TASK_TIMER -> Set.of("_count", "_sum", "_max");
+ case DISTRIBUTION_SUMMARY, TIMER, GAUGE, OTHER -> Set.of();
+ };
+ }
+
+ /**
+ * Convert the meter name to the format used by the Prometheus simple client.
+ *
+ * @param meterName name of the meter
+ * @return normalized meter name
+ */
+ static String normalizeMeterName(String meterName) {
+ String result = meterName;
+
+ // Convert special characters to underscores.
+ result = result.replaceAll("[-+.!?@#$%^&*`'\\s]+", "_");
+
+ // Prometheus simple client adds the prefix "m_" if a meter name starts with a digit or an underscore.
+ if (result.matches("^[0-9_]+.*")) {
+ result = "m_" + result;
+ }
+
+ // Remove non-identifier characters.
+ result = result.replaceAll("[^A-Za-z0-9_]", "");
+
+ return result;
+ }
+
}
diff --git a/metrics/microprofile/microprofile/src/main/java/module-info.java b/metrics/microprofile/microprofile/src/main/java/module-info.java
index 3e77b06ecb1..ecf469d854d 100644
--- a/metrics/microprofile/microprofile/src/main/java/module-info.java
+++ b/metrics/microprofile/microprofile/src/main/java/module-info.java
@@ -22,6 +22,13 @@
requires microprofile.metrics.api;
requires microprofile.config.api;
requires micrometer.core;
+ requires io.helidon.common;
+ requires micrometer.registry.prometheus;
+ requires java.management;
+ requires io.helidon.common.media.type;
+ requires simpleclient.common;
+
+ exports io.helidon.metrics.microprofile;
//requires micrometer.registry.prometheus;
}
\ No newline at end of file
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
index f36dbb80449..e0cbb9b3213 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
@@ -15,16 +15,23 @@
*/
package io.helidon.metrics.microprofile;
+import java.util.Arrays;
+
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Tag;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertThrows;
class TestCounters {
@@ -53,4 +60,38 @@ void testCounter() {
counter.inc();
assertThat("Updated counter", counter.getCount(), is(1L));
}
+
+ @Test
+ void testConflictingTags() {
+ Counter counter = mpMetricRegistry.counter("conflictingCounterDueToTags"); // name only
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> mpMetricRegistry.counter("conflictingCounterDueToTags",
+ new Tag[] {new Tag("tag1", "value1")}));
+ assertThat("Inconsistent tags check", ex.getMessage(), containsString("inconsistent"));
+ }
+
+ @Test
+ void testConsistentTags() {
+ Tag[] tags = {new Tag("tag1", "value1"), new Tag("tag2", "value2")};
+ Counter counter1 = mpMetricRegistry.counter("sameTag", tags);
+ Counter counter2 = mpMetricRegistry.counter("sameTag", Arrays.copyOf(tags, tags.length));
+ assertThat("Reregistered meter", counter2, is(sameInstance(counter1)));
+ }
+
+ @Test
+ void conflictingMetadata() {
+ mpMetricRegistry.counter(Metadata.builder()
+ .withName("counterWithMetadata")
+ .withDescription("first")
+ .build());
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> mpMetricRegistry.counter(Metadata.builder()
+ .withName("counterWithMetadata")
+ .withDescription("second")
+ .build()));
+
+ assertThat("Error message",
+ ex.getMessage().matches(".*?metadata.*?inconsistent.*?"),
+ is(true));
+ }
}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
index 5d0073a2f0b..0af569168b3 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
@@ -13,5 +13,62 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.metrics.microprofile;public class TestFormatter {
+package io.helidon.metrics.microprofile;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.helidon.common.media.type.MediaTypes;
+
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Counter;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.is;
+
+public class TestFormatter {
+
+ private static MpRegistryFactory mpRegistryFactory;
+
+ @BeforeAll
+ static void init() {
+ mpRegistryFactory = MpRegistryFactory.get();
+ }
+
+ @Test
+ void testSimpleCounterFormatting() {
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry("scope1");
+
+ Counter counter = reg.counter("myCounter");
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+
+ PrometheusMeterRegistry promReg = ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ .stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
+
+ String promFormat = PrometheusFormatter.formattedOutput(promReg,
+ MediaTypes.TEXT_PLAIN,
+ Optional.empty(),
+ Optional.empty());
+
+ // Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
+ // catpure the meter value, further uninteresting text.
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^myCounter_total\\{.*mp_scope=\"([^\"]+).*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Output matches expected", matcher.matches(), is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(2));
+ assertThat("Captured mp_scope value", matcher.group(1), is("scope1"));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(2)), is(1.0D));
+ }
}
diff --git a/metrics/microprofile/pom.xml b/metrics/microprofile/pom.xml
index 7c85df1e4f7..6fcf4f76000 100644
--- a/metrics/microprofile/pom.xml
+++ b/metrics/microprofile/pom.xml
@@ -34,5 +34,6 @@
microprofile
cdi
+ feature
From 9d1ce270a3a7bcdcad289e8f1b62b109d19d067f Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Mon, 19 Jun 2023 17:31:39 -0500
Subject: [PATCH 04/67] Added basics for timers, gauges
---
.../feature/MpMetricsFeature.java | 1 -
.../metrics/microprofile/MpCounter.java | 10 +-
.../microprofile/MpFunctionCounter.java | 2 +-
.../helidon/metrics/microprofile/MpGauge.java | 92 ++++++
.../metrics/microprofile/MpHistogram.java | 58 ++++
.../metrics/microprofile/MpMetric.java | 11 +-
.../microprofile/MpMetricRegistry.java | 305 +++++++++++++-----
.../microprofile/MpRegistryFactory.java | 30 +-
.../metrics/microprofile/MpSnapshot.java | 67 ++++
.../helidon/metrics/microprofile/MpTimer.java | 86 +++++
.../metrics/microprofile/MetricsMatcher.java | 59 ++++
.../metrics/microprofile/TestFormatter.java | 21 +-
.../metrics/microprofile/TestHistograms.java | 76 +++++
.../metrics/microprofile/TestTimers.java | 92 ++++++
14 files changed, 802 insertions(+), 108 deletions(-)
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java
create mode 100644 metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/MetricsMatcher.java
create mode 100644 metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java
create mode 100644 metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestTimers.java
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
index e62c2b29efc..9f52e5174c1 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
@@ -20,7 +20,6 @@
import io.helidon.common.http.Http;
import io.helidon.common.media.type.MediaType;
import io.helidon.common.media.type.MediaTypes;
-import io.helidon.config.Config;
import io.helidon.metrics.microprofile.PrometheusFormatter;
import io.helidon.nima.servicecommon.HelidonFeatureSupport;
import io.helidon.nima.webserver.http.HttpRules;
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java
index eec066d24d6..168b7345242 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpCounter.java
@@ -16,14 +16,20 @@
package io.helidon.metrics.microprofile;
import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
/**
* Implementation of {@link org.eclipse.microprofile.metrics.Counter}.
*/
public class MpCounter extends MpMetric implements org.eclipse.microprofile.metrics.Counter {
- MpCounter(Counter delegate) {
- super(delegate);
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate meter which actually records data
+ */
+ MpCounter(Counter delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
}
@Override
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
index 55527a313e4..9b3d7d0d051 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpFunctionCounter.java
@@ -30,7 +30,7 @@ static Builder builder(MpMetricRegistry mpMetricRegistry, String name, T
}
private MpFunctionCounter(Builder> builder) {
- super(delegate(builder));
+ super(delegate(builder), builder.mpMetricRegistry.meterRegistry());
}
@Override
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java
new file mode 100644
index 00000000000..6c78df493f2
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java
@@ -0,0 +1,92 @@
+/*
+ * 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.microprofile;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+
+abstract class MpGauge extends MpMetric implements org.eclipse.microprofile.metrics.Gauge {
+
+ /*
+ * The MicroProfile metrics API parameterizes its gauge type as Gauge which is the type of
+ * value the gauge reports via its getValue() method. To register a gauge, the developer passes us a function-plus-target or
+ * a supplier with the return value from the function or supplier similarly parameterized with the subtype of Number.
+ *
+ * On the other hand, each Micrometer gauge is not parameterized and reports a double value.
+ *
+ * As a result, we do not have what we need to (easily) instantiate the correct subtype of Number, set its value based on
+ * the Micrometer delegate's value() result, and return the correctly-typed and -assigned value from our getValue() method.
+ *
+ * To work around this, we keep track ourselves of the function and target or supplier which report the gauge value.
+ * Then our getValue() implementation simply invokes the function on the target or the supplier rather than delegating to
+ * the Micrometer gauge value() method (which would do exactly the same thing anyway).
+ *
+ * This way the typing works out (with the expected unchecked cast).
+ */
+ private MpGauge(Gauge delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ static FunctionBased create(T target,
+ Function function,
+ Gauge delegate,
+ MeterRegistry meterRegistry) {
+ return new FunctionBased<>(target, function, delegate, meterRegistry);
+ }
+
+ static SupplierBased create(Supplier supplier,
+ Gauge delegate,
+ MeterRegistry meterRegistry) {
+ return new SupplierBased<>(supplier,delegate,meterRegistry);
+ }
+
+ static class FunctionBased extends MpGauge {
+
+ private final T target;
+ private final Function function;
+
+
+
+ private FunctionBased(T target, Function function, Gauge delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ this.target = target;
+ this.function = function;
+ }
+
+ @Override
+ public N getValue() {
+ return (N) function.apply(target);
+ }
+ }
+
+ static class SupplierBased extends MpGauge {
+
+ private final Supplier supplier;
+
+ private SupplierBased(Supplier supplier, Gauge delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ this.supplier = supplier;
+ }
+
+ @Override
+ public N getValue() {
+ return supplier.get();
+ }
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java
new file mode 100644
index 00000000000..d7d7b2a50f5
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.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.microprofile;
+
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+public class MpHistogram extends MpMetric implements Histogram {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate meter which actually records data
+ */
+ MpHistogram(DistributionSummary delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ @Override
+ public void update(int i) {
+ delegate().record(i);
+ }
+
+ @Override
+ public void update(long l) {
+ delegate().record(l);
+ }
+
+ @Override
+ public long getCount() {
+ return delegate().count();
+ }
+
+ @Override
+ public long getSum() {
+ return (long) delegate().totalAmount();
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ return new MpSnapshot(delegate().takeSnapshot());
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
index 1999455cf5f..b8c93c6dbee 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
@@ -16,17 +16,24 @@
package io.helidon.metrics.microprofile;
import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
import org.eclipse.microprofile.metrics.Metric;
-class MpMetric implements Metric {
+abstract class MpMetric implements Metric {
private final M delegate;
+ private final MeterRegistry meterRegistry;
- MpMetric(M delegate) {
+ MpMetric(M delegate, MeterRegistry meterRegistry) {
this.delegate = delegate;
+ this.meterRegistry = meterRegistry;
}
M delegate() {
return delegate;
}
+
+ protected MeterRegistry meterRegistry() {
+ return meterRegistry;
+ }
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
index 3b6b9910c46..0dd9d4bd650 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
@@ -31,6 +31,7 @@
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
+import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import org.eclipse.microprofile.metrics.Counter;
@@ -44,12 +45,25 @@
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.Timer;
+/**
+ * Implementation of the MicroProfile {@link org.eclipse.microprofile.metrics.MetricRegistry}.
+ *
+ * The methods in this ipmlementation which find or create-and-register metrics (in this registry) and the meters to which
+ * we delegate (in the meter registry) use functions or suppliers as parameters so our code can invoke them at the correct
+ * times to creating the a new metric and register a new meter. For example, we want to make sure that the metadata provided
+ * with or derived during a new registration agrees with previously-recorded metadata for the same metric name *before* we
+ * create a new meter or a new metric or update our data structures which track them. This way we can reuse rather than
+ * recopy the code for creating metadata and metric IDs, updating data structures, etc.
+ *
+ */
class MpMetricRegistry implements MetricRegistry {
public static final String MP_APPLICATION_TAG_NAME = "mp_app";
public static final String MP_SCOPE_TAG_NAME = "mp_scope";
+ private static final double[] DEFAULT_PERCENTILES = {0.5, 0.75, 0.95, 0.98, 0.99, 0.999};
+
private final String scope;
private final MeterRegistry meterRegistry;
private final Map metadata = new ConcurrentHashMap<>();
@@ -80,7 +94,7 @@ protected MpMetricRegistry(String scope, MeterRegistry meterRegistry) {
public Counter counter(String s) {
return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
MpCounter::new,
- meterRegistry::counter,
+ this::counterFactory,
s);
}
@@ -88,7 +102,7 @@ public Counter counter(String s) {
public Counter counter(String s, Tag... tags) {
return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
MpCounter::new,
- meterRegistry::counter,
+ this::counterFactory,
s,
tags);
}
@@ -97,7 +111,7 @@ public Counter counter(String s, Tag... tags) {
public Counter counter(MetricID metricID) {
return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
MpCounter::new,
- meterRegistry::counter,
+ this::counterFactory,
metricID.getName(),
metricID.getTagsAsArray());
}
@@ -106,7 +120,7 @@ public Counter counter(MetricID metricID) {
public Counter counter(Metadata metadata) {
return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
MpCounter::new,
- meterRegistry::counter,
+ this::counterFactory,
validatedMetadata(metadata));
}
@@ -114,93 +128,129 @@ public Counter counter(Metadata metadata) {
public Counter counter(Metadata metadata, Tag... tags) {
return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
MpCounter::new,
- meterRegistry::counter,
+ this::counterFactory,
validatedMetadata(metadata),
tags);
}
Counter counter(Metadata metadata, T valueOrigin, ToDoubleFunction fn, Tag... tags) {
- return getOrCreateAndStoreFunctionCounter(validatedMetadata(metadata), valueOrigin, fn);
+ return getOrCreateAndStoreFunctionCounter(validatedMetadata(metadata), valueOrigin, fn, tags);
}
@Override
public Gauge gauge(String s, T t, Function function, Tag... tags) {
- return null;
+ return getOrCreateAndStoreGauge(t, function, validatedMetadata(s), tags);
}
@Override
public Gauge gauge(MetricID metricID, T t, Function function) {
- return null;
+ return getOrCreateAndStoreGauge(metricID.getName(), t, function, metricID.getTagsAsArray());
}
@Override
public Gauge gauge(Metadata metadata, T t, Function function, Tag... tags) {
- return null;
+ return getOrCreateAndStoreGauge(t, function, metadata, tags);
}
@Override
public Gauge gauge(String s, Supplier supplier, Tag... tags) {
- return null;
+ return getOrCreateAndStoreGauge(supplier, validatedMetadata(s), tags);
}
@Override
public Gauge gauge(MetricID metricID, Supplier supplier) {
- return null;
+ return getOrCreateAndStoreGauge(metricID.getName(), supplier, metricID.getTagsAsArray());
}
@Override
public Gauge gauge(Metadata metadata, Supplier supplier, Tag... tags) {
- return null;
+ return getOrCreateAndStoreGauge(supplier, metadata, tags);
}
@Override
public Histogram histogram(String s) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ s);
}
@Override
public Histogram histogram(String s, Tag... tags) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ s,
+ tags);
}
@Override
public Histogram histogram(MetricID metricID) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ metricID.getName(),
+ metricID.getTagsAsArray());
}
@Override
public Histogram histogram(Metadata metadata) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ validatedMetadata(metadata));
}
@Override
public Histogram histogram(Metadata metadata, Tag... tags) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ validatedMetadata(metadata),
+ tags);
}
@Override
public Timer timer(String s) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ s);
}
@Override
public Timer timer(String s, Tag... tags) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ s,
+ tags);
}
@Override
public Timer timer(MetricID metricID) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ metricID.getName(),
+ metricID.getTagsAsArray());
}
@Override
public Timer timer(Metadata metadata) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ validatedMetadata(metadata));
}
@Override
public Timer timer(Metadata metadata, Tag... tags) {
- return null;
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ validatedMetadata(metadata),
+ tags);
}
@Override
@@ -368,8 +418,8 @@ MeterRegistry meterRegistry() {
}
, T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
- Function metricFactory,
- BiFunction metricFactory,
+ BiFunction,
T> meterFactory,
String name,
@@ -382,8 +432,8 @@ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type
}
, T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
- Function metricFactory,
- BiFunction metricFactory,
+ BiFunction,
T> meterFactory,
Metadata validMetadata,
@@ -401,9 +451,9 @@ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type
return (M) result;
}
- T delegate = meterFactory.apply(mpMetricId.name(), mpMetricId.fullTags());
+ T delegate = meterFactory.apply(validMetadata, mpMetricId.fullTags());
- M newMetric = metricFactory.apply(delegate);
+ M newMetric = metricFactory.apply(delegate, meterRegistry);
storeMetadataIfAbsent(validMetadata);
metricsById.put(mpMetricId, newMetric);
metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
@@ -413,34 +463,132 @@ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type
}
Counter getOrCreateAndStoreFunctionCounter(Metadata validMetadata,
- T valueOrigin,
- ToDoubleFunction fn,
- Tag... tags) {
+ T valueOrigin,
+ ToDoubleFunction fn,
+ Tag... tags) {
- /*
- * From the metadata create a candidate MpMetricID, validate it (to make sure the tag names are consistent with any
- * previously-registered metrics with the same name and that the user did not specify any reserved tags), and then
- * augment the inner meter ID with the scope tag and, if an app name is specified via config, the app name tag.
- */
- MpMetricId mpMetricId = validAugmentedMpMetricId(validMetadata, Meter.Type.COUNTER, tags);
- return access(() -> {
- MpMetric> result = metricsById.get(mpMetricId);
- if (result != null) {
- return (MpFunctionCounter) result;
- }
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ (ctr, mr) ->
+ MpFunctionCounter.builder(this,
+ validMetadata.getName(),
+ valueOrigin, fn)
+ .tags(MpTags.fromMp(tags))
+ .build(),
+ this::counterFactory,
+ validMetadata,
+ tags);
+ }
- MpFunctionCounter newMetric = MpFunctionCounter.builder(this,
- validMetadata.getName(),
- valueOrigin, fn)
- .build();
+ // Following should be unneeded thanks to the preceding method.
+ // Counter getOrCreateAndStoreFunctionCounter(Metadata validMetadata,
+ // T valueOrigin,
+ // ToDoubleFunction fn,
+ // Tag... tags) {
+ //
+ // /*
+ // * From the metadata create a candidate MpMetricID, validate it (to make sure the tag names are consistent with any
+ // * previously-registered metrics with the same name and that the user did not specify any reserved tags), and then
+ // * augment the inner meter ID with the scope tag and, if an app name is specified via config, the app name tag.
+ // */
+ // MpMetricId mpMetricId = validAugmentedMpMetricId(validMetadata, Meter.Type.COUNTER, tags);
+ // return access(() -> {
+ // MpMetric> result = metricsById.get(mpMetricId);
+ // if (result != null) {
+ // return (MpFunctionCounter) result;
+ // }
+ //
+ // MpFunctionCounter newMetric = MpFunctionCounter.builder(this,
+ // validMetadata.getName(),
+ // valueOrigin, fn)
+ // .tags(MpTags.fromMp(tags))
+ // .build();
+ //
+ // storeMetadataIfAbsent(validMetadata);
+ // metricsById.put(mpMetricId, newMetric);
+ // metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
+ // metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
+ // return newMetric;
+ // });
+ // }
+
+ Gauge getOrCreateAndStoreGauge(T t, Function function, Metadata validMetadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.GAUGE,
+ (io.micrometer.core.instrument.Gauge delegate, MeterRegistry registry) ->
+ MpGauge.create(t, function, delegate, registry),
+ (Metadata validM, Iterable ts)
+ -> io.micrometer.core.instrument.Gauge
+ .builder(validM.getName(),
+ t,
+ target -> function.apply(target).doubleValue())
+ .description(validM.getDescription())
+ .tags(ts)
+ .baseUnit(validM.getUnit())
+ .strongReference(true)
+ .register(meterRegistry),
+ validMetadata,
+ tags);
+ }
- storeMetadataIfAbsent(validMetadata);
- metricsById.put(mpMetricId, newMetric);
- metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
- metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
- return newMetric;
- });
+ Gauge getOrCreateAndStoreGauge(String name, T t, Function function, Tag... tags) {
+ return getOrCreateAndStoreGauge(t, function, validatedMetadata(name), tags);
+ }
+
+ Gauge getOrCreateAndStoreGauge(Supplier supplier, Metadata validMetadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.GAUGE,
+ (io.micrometer.core.instrument.Gauge delegate, MeterRegistry registry) ->
+ MpGauge.create(supplier, delegate, registry),
+ (Metadata validM, Iterable ts)
+ -> io.micrometer.core.instrument.Gauge
+ .builder(validM.getName(),
+ (Supplier) supplier)
+ .description(validM.getDescription())
+ .tags(ts)
+ .baseUnit(validM.getUnit())
+ .strongReference(true)
+ .register(meterRegistry),
+ validMetadata,
+ tags);
+ }
+
+
+ Gauge getOrCreateAndStoreGauge(String name, Supplier supplier, Tag... tags) {
+ return getOrCreateAndStoreGauge(supplier, validatedMetadata(name), tags);
+ }
+
+ /**
+ * Returns whether the two metadata instances are consistent with each other.
+ *
+ * @param a one {@code Metadata} instance
+ * @param b the other {@code Metadata} instance
+ * @return {@code true} if the two instances contain consistent metadata; {@code false} otherwise
+ */
+ boolean isConsistentMetadata(Metadata a, Metadata b) {
+ return a.equals(b);
+ }
+
+ MpMetricId consistentMpMetricId(Metadata metadata, Meter.Type type, Tag... tags) {
+ MpTags.checkForReservedTags(tags);
+ MpMetricId id = new MpMetricId(metadata.getName(),
+ tags,
+ automaticTags(),
+ metadata.getUnit(),
+ metadata.getDescription(),
+ type);
+ MpTags.checkTagNameSetConsistency(id, metricIdsByName.get(metadata.getName()));
+ return id;
+ }
+
+ /**
+ * Checks that the tag names in the provided ID are consistent with the tag names in any previously-registered ID
+ * with the same name; throws {@code IllegalArgumentException} if inconsistent.
+ *
+ * @param mpMetricId metric ID with tags to check
+ */
+ MpMetricId checkTagNameSetConsistencyWithStoredIds(MpMetricId mpMetricId) {
+ MpTags.checkTagNameSetConsistency(mpMetricId, metricIdsByName.get(mpMetricId.getName()));
+ return mpMetricId;
}
+
private void storeMetadataIfAbsent(Metadata validatedMetadata) {
metadata.putIfAbsent(validatedMetadata.getName(), validatedMetadata);
}
@@ -487,8 +635,8 @@ private Tag[] automaticTags() {
*/
private Metadata validatedMetadata(String name) {
return validatedMetadata(Metadata.builder()
- .withName(name)
- .build());
+ .withName(name)
+ .build());
}
/**
@@ -512,39 +660,30 @@ private Metadata validatedMetadata(Metadata proposedMetadata) {
return storedMetadata;
}
-
- /**
- * Returns whether the two metadata instances are consistent with each other.
- *
- * @param a one {@code Metadata} instance
- * @param b the other {@code Metadata} instance
- * @return {@code true} if the two instances contain consistent metadata; {@code false} otherwise
- */
- boolean isConsistentMetadata(Metadata a, Metadata b) {
- return a.equals(b);
+ private io.micrometer.core.instrument.Counter counterFactory(Metadata metadata,
+ Iterable tags) {
+ return meterRegistry.counter(metadata.getName(), tags);
}
- MpMetricId consistentMpMetricId(Metadata metadata, Meter.Type type, Tag... tags) {
- MpTags.checkForReservedTags(tags);
- MpMetricId id = new MpMetricId(metadata.getName(),
- tags,
- automaticTags(),
- metadata.getUnit(),
- metadata.getDescription(),
- type);
- MpTags.checkTagNameSetConsistency(id, metricIdsByName.get(metadata.getName()));
- return id;
+ private DistributionSummary distributionSummaryFactory(Metadata metadata,
+ Iterable tags) {
+ return DistributionSummary.builder(metadata.getName())
+ .description(metadata.getDescription())
+ .baseUnit(metadata.getUnit())
+ .tags(tags)
+ .publishPercentiles(DEFAULT_PERCENTILES)
+ .percentilePrecision(MpRegistryFactory.get().distributionSummaryPrecision())
+ .register(meterRegistry);
}
- /**
- * Checks that the tag names in the provided ID are consistent with the tag names in any previously-registered ID
- * with the same name; throws {@code IllegalArgumentException} if inconsistent.
- *
- * @param mpMetricId metric ID with tags to check
- */
- MpMetricId checkTagNameSetConsistencyWithStoredIds(MpMetricId mpMetricId) {
- MpTags.checkTagNameSetConsistency(mpMetricId, metricIdsByName.get(mpMetricId.getName()));
- return mpMetricId;
+ private io.micrometer.core.instrument.Timer timerFactory(Metadata metadata,
+ Iterable tags) {
+ return io.micrometer.core.instrument.Timer.builder(metadata.getName())
+ .description(metadata.getDescription())
+ .tags(tags)
+ .publishPercentiles(DEFAULT_PERCENTILES)
+ .percentilePrecision(MpRegistryFactory.get().timerPrecision())
+ .register(meterRegistry);
}
private T access(Callable work) {
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
index 0273227d6d7..f05be3981f6 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
@@ -15,7 +15,6 @@
*/
package io.helidon.metrics.microprofile;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
@@ -31,6 +30,11 @@ public class MpRegistryFactory {
public static final String APPLICATION_SCOPE = "application";
public static final String BASE_SCOPE = "base";
public static final String VENDOR_SCOPE = "vendor";
+ public static final String HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX = "helidon.distribution-summary.precision";
+ public static final String TIMER_PRECISION_CONFIG_KEY_SUFFIX = "helidon.timer.precision";
+
+ private static final int HISTOGRAM_PRECISION_DEFAULT = 3;
+ private static final int TIMER_PRECISION_DEFAULT = 3;
private static MpRegistryFactory INSTANCE;
@@ -40,6 +44,8 @@ public class MpRegistryFactory {
private Config mpConfig;
private final Map registries = new HashMap<>();
private final PrometheusMeterRegistry prometheusMeterRegistry;
+ private final int distributionSummaryPrecision;
+ private final int timerPrecision;
public static MpRegistryFactory create(Config mpConfig) {
@@ -73,30 +79,34 @@ private MpRegistryFactory(Config mpConfig) {
creation = new Exception("Initial creation of " + MpRegistryFactory.class.getSimpleName());
this.mpConfig = mpConfig;
prometheusMeterRegistry = findOrAddPrometheusRegistry();
+ distributionSummaryPrecision = mpConfig.getOptionalValue("mp.metrics." + HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX,
+ Integer.class).orElse(HISTOGRAM_PRECISION_DEFAULT);
+ timerPrecision = mpConfig.getOptionalValue("mp.metrics." + TIMER_PRECISION_CONFIG_KEY_SUFFIX,
+ Integer.class).orElse(TIMER_PRECISION_DEFAULT);
registries.put(APPLICATION_SCOPE, MpMetricRegistry.create(APPLICATION_SCOPE, Metrics.globalRegistry));
registries.put(BASE_SCOPE, BaseRegistry.create(Metrics.globalRegistry));
registries.put(VENDOR_SCOPE, MpMetricRegistry.create(VENDOR_SCOPE, Metrics.globalRegistry));
+
}
public MetricRegistry registry(String scope) {
-// if (INSTANCE == null) {
-// throw new IllegalStateException("Attempt to use " + MpRegistryFactory.class.getName() + " before invoking create");
-// }
return registries.computeIfAbsent(scope,
s -> MpMetricRegistry.create(s, Metrics.globalRegistry));
}
public PrometheusMeterRegistry prometheusMeterRegistry() {
-// if (INSTANCE == null) {
-// throw new IllegalStateException("Attempt to use " + MpRegistryFactory.class.getName() + " before invoking create");
-// }
return prometheusMeterRegistry;
}
+ int distributionSummaryPrecision() {
+ return distributionSummaryPrecision;
+ }
+
+ int timerPrecision() {
+ return timerPrecision;
+ }
+
private PrometheusMeterRegistry findOrAddPrometheusRegistry() {
-// if (INSTANCE == null) {
-// throw new IllegalStateException("Attempt to use " + MpRegistryFactory.class.getName() + " before invoking create");
-// }
return Metrics.globalRegistry.getRegistries().stream()
.filter(PrometheusMeterRegistry.class::isInstance)
.map(PrometheusMeterRegistry.class::cast)
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java
new file mode 100644
index 00000000000..8d48cb8f8b8
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java
@@ -0,0 +1,67 @@
+/*
+ * 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.microprofile;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+import io.micrometer.core.instrument.distribution.HistogramSnapshot;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+/**
+ * Implementation of {@link org.eclipse.microprofile.metrics.Snapshot}.
+ */
+public class MpSnapshot extends Snapshot {
+
+ private final HistogramSnapshot delegate;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate histogram snapshot which provides the actual data
+ */
+ MpSnapshot(HistogramSnapshot delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public long size() {
+ return delegate.count();
+ }
+
+ @Override
+ public double getMax() {
+ return delegate.max();
+ }
+
+ @Override
+ public double getMean() {
+ return delegate.mean();
+ }
+
+ @Override
+ public PercentileValue[] percentileValues() {
+ return Arrays.stream(delegate.percentileValues())
+ .map(vap -> new PercentileValue(vap.percentile(), vap.value()))
+ .toArray(PercentileValue[]::new);
+ }
+
+ @Override
+ public void dump(OutputStream outputStream) {
+ delegate.outputSummary(new PrintStream(outputStream), 1);
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java
new file mode 100644
index 00000000000..193f7ca3efe
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.microprofile;
+
+import java.time.Duration;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+public class MpTimer extends MpMetric implements org.eclipse.microprofile.metrics.Timer {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate meter which actually records data
+ */
+ MpTimer(Timer delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ @Override
+ public void update(Duration duration) {
+ delegate().record(duration);
+ }
+
+ @Override
+ public T time(Callable callable) throws Exception {
+ return delegate().recordCallable(callable);
+ }
+
+ @Override
+ public void time(Runnable runnable) {
+ delegate().record(runnable);
+ }
+
+ @Override
+ public Context time() {
+ return new Context();
+ }
+
+ @Override
+ public Duration getElapsedTime() {
+ return Duration.ofNanos((long) delegate().totalTime(TimeUnit.NANOSECONDS));
+ }
+
+ @Override
+ public long getCount() {
+ return delegate().count();
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ return new MpSnapshot(delegate().takeSnapshot());
+ }
+
+ public class Context implements org.eclipse.microprofile.metrics.Timer.Context {
+
+ private final Timer.Sample sample = Timer.start(meterRegistry());
+
+ @Override
+ public long stop() {
+ return sample.stop(delegate());
+ }
+
+ @Override
+ public void close() {
+ stop();
+ }
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/MetricsMatcher.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/MetricsMatcher.java
new file mode 100644
index 00000000000..58191a8cce3
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/MetricsMatcher.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 2022 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.microprofile;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Hamcrest Matcher for Metrics-related value checking.
+ *
+ * Includes:
+ *
+ * - {@link #withinTolerance(Number)} for checking within a configured range (tolerance) either side of an expected
+ * value
+ * - {@link #withinTolerance(Number, double)} for checking within a specified range
+ *
+ */
+class MetricsMatcher {
+
+ static final Double VARIANCE = Double.valueOf(System.getProperty("helidon.histogram.tolerance", "0.001"));
+
+ static TypeSafeMatcher withinTolerance(final Number expected) {
+ return withinTolerance(expected, VARIANCE);
+ }
+
+ static TypeSafeMatcher withinTolerance(final Number expected, double variance) {
+ return new TypeSafeMatcher<>() {
+
+ private final double v = variance;
+
+ @Override
+ protected boolean matchesSafely(Number item) {
+ return Math.abs(expected.doubleValue() - item.doubleValue()) <= expected.doubleValue() * v;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("withinTolerance expected value in range [")
+ .appendValue(expected.doubleValue() * (1.0 - v))
+ .appendText(", ")
+ .appendValue(expected.doubleValue() * (1.0 + v))
+ .appendText("]");
+ }
+ };
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
index 0af569168b3..21f495d74c7 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
@@ -28,12 +28,12 @@
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
public class TestFormatter {
+ private static final String SCOPE = "formatScope";
+ private static final String COUNTER_NAME = "formatCounter";
private static MpRegistryFactory mpRegistryFactory;
@BeforeAll
@@ -43,9 +43,9 @@ static void init() {
@Test
void testSimpleCounterFormatting() {
- MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry("scope1");
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
- Counter counter = reg.counter("myCounter");
+ Counter counter = reg.counter(COUNTER_NAME);
counter.inc();
assertThat("Updated counter", counter.getCount(), is(1L));
@@ -63,12 +63,15 @@ void testSimpleCounterFormatting() {
// Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
// catpure the meter value, further uninteresting text.
- Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^myCounter_total\\{.*mp_scope=\"([^\"]+).*?}\\s+(\\S+).*?",
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + COUNTER_NAME
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
Pattern.MULTILINE + Pattern.DOTALL);
Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
- assertThat("Output matches expected", matcher.matches(), is(true));
- assertThat("Output matcher groups", matcher.groupCount(), is(2));
- assertThat("Captured mp_scope value", matcher.group(1), is("scope1"));
- assertThat("Captured metric value as ", Double.parseDouble(matcher.group(2)), is(1.0D));
+ assertThat("Output " + System.lineSeparator() + promFormat + System.lineSeparator() + " matches expected", matcher.matches(), is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(1));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
}
}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java
new file mode 100644
index 00000000000..ebce0a4134b
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java
@@ -0,0 +1,76 @@
+/*
+ * 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.microprofile;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Snapshot;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.metrics.microprofile.MetricsMatcher.withinTolerance;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+
+public class TestHistograms {
+
+ static PrometheusMeterRegistry prometheusMeterRegistry;
+
+ static MeterRegistry meterRegistry;
+
+ static MpMetricRegistry mpMetricRegistry;
+
+ @BeforeAll
+ static void setup() {
+ PrometheusConfig config = new PrometheusConfig() {
+ @Override
+ public String get(String s) {
+ return null;
+ }
+ };
+
+ prometheusMeterRegistry = new PrometheusMeterRegistry(config);
+ meterRegistry = Metrics.globalRegistry.add(prometheusMeterRegistry);
+
+ mpMetricRegistry = MpMetricRegistry.create("histoScope", meterRegistry);
+ }
+
+ @Test
+ void testHistogram() {
+ Histogram histogram = mpMetricRegistry.histogram("myHisto");
+ histogram.update(4);
+ histogram.update(24);
+ assertThat("Count", histogram.getCount(), is(2L));
+ assertThat("Sum", histogram.getSum(), is(28L));
+ Snapshot snapshot = histogram.getSnapshot();
+ assertThat("Mean", snapshot.getMean(), is(14.0D));
+ assertThat("Max", snapshot.getMax(), is(24.0D));
+ Snapshot.PercentileValue[] percentileValues = snapshot.percentileValues();
+
+ double[] expectedPercents = {0.5, 0.75, 0.95, 0.98, 0.99, 0.999};
+ double[] expectedValues = {4.0, 24.0, 24.0, 24.0, 24.0, 24.0};
+
+ for (int i = 0; i < percentileValues.length; i++ ) {
+ assertThat("Percentile " + i + " %", percentileValues[i].getPercentile(), is(expectedPercents[i]));
+ assertThat("Percentile " + i + " value", percentileValues[i].getValue(), is(withinTolerance(expectedValues[i])));
+ }
+ }
+}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestTimers.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestTimers.java
new file mode 100644
index 00000000000..f12b532ac13
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestTimers.java
@@ -0,0 +1,92 @@
+/*
+ * 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.microprofile;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Snapshot;
+import org.eclipse.microprofile.metrics.Timer;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.metrics.microprofile.MetricsMatcher.withinTolerance;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class TestTimers {
+
+ static PrometheusMeterRegistry prometheusMeterRegistry;
+
+ static MeterRegistry meterRegistry;
+
+ static MpMetricRegistry mpMetricRegistry;
+
+ @BeforeAll
+ static void setup() {
+ PrometheusConfig config = new PrometheusConfig() {
+ @Override
+ public String get(String s) {
+ return null;
+ }
+ };
+
+ prometheusMeterRegistry = new PrometheusMeterRegistry(config);
+ meterRegistry = Metrics.globalRegistry.add(prometheusMeterRegistry);
+
+ mpMetricRegistry = MpMetricRegistry.create("timerScope", meterRegistry);
+ }
+
+ @Test
+ void testTimer() {
+ Timer timer = mpMetricRegistry.timer("myTimer");
+ timer.update(Duration.ofSeconds(2));
+ try (Timer.Context context = timer.time()) {
+ TimeUnit.SECONDS.sleep(3);
+ // Don't explicitly stop the context; the try-with-resources will close it which stops the context.
+ // Doing both adds an additional sample which skews the stats (and disturbs this test).
+ } catch (InterruptedException ex) {
+ fail("Thread interrupted while waiting for some time to pass");
+ }
+
+ assertThat("Count", timer.getCount(), is(2L));
+ assertThat("Sum", timer.getElapsedTime().getSeconds(), is(withinTolerance(5)));
+
+ Snapshot snapshot = timer.getSnapshot();
+ assertThat("Mean", toMillis(snapshot.getMean()), is(withinTolerance(2500L, 0.01)));
+ assertThat("Max", toMillis(snapshot.getMax()), is(withinTolerance(3000L, 0.01)));
+ Snapshot.PercentileValue[] percentileValues = snapshot.percentileValues();
+
+ double[] expectedPercents = {0.5, 0.75, 0.95, 0.98, 0.99, 0.999};
+ double[] expectedMilliseconds = {2000.0, 3000.0, 3000.0, 3000.0, 3000.0, 3000.0};
+
+ for (int i = 0; i < percentileValues.length; i++ ) {
+ assertThat("Percentile " + i + " %", percentileValues[i].getPercentile(), is(expectedPercents[i]));
+ assertThat("Percentile " + i + " value",
+ toMillis(percentileValues[i].getValue()),
+ is(withinTolerance(expectedMilliseconds[i], 0.01)));
+ }
+ }
+
+ private static long toMillis(double value) {
+ return TimeUnit.NANOSECONDS.toMillis((long) value);
+ }
+}
From b19be5a4f4904c749c62d61b1f3268dcc8a44454 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Tue, 20 Jun 2023 18:03:50 -0500
Subject: [PATCH 05/67] Improve formatter API; fix and expand tests; plug
feature in; start on extension
---
.../microprofile/cdi/MetricsCdiExtension.java | 4 +-
.../cdi/src/main/java/module-info.java | 6 +
metrics/microprofile/feature/pom.xml | 14 ++
.../feature/MetricsObserveProvider.java | 75 ++++++++++
.../feature/MpMetricsFeature.java | 10 +-
.../feature/src/main/java/module-info.java | 6 +
.../microprofile/feature/MpFeatureTest.java | 63 +++++++++
.../microprofile/PrometheusFormatter.java | 86 +++++++++---
.../metrics/microprofile/TestCounters.java | 2 +-
.../metrics/microprofile/TestFormatter.java | 131 +++++++++++++++++-
.../metrics/microprofile/TestHistograms.java | 2 +-
11 files changed, 365 insertions(+), 34 deletions(-)
create mode 100644 metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java
create mode 100644 metrics/microprofile/feature/src/test/java/io/helidon/metrics/microprofile/feature/MpFeatureTest.java
diff --git a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
index c80fcf3423d..55f0be7ed57 100644
--- a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
+++ b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
@@ -28,12 +28,12 @@ public class MetricsCdiExtension extends HelidonRestCdiExtension FEATURE_FACTORY =
- (Config config) -> MpMetricsFeature.builder().config(config).build();
+ (Config helidonConfig) -> MpMetricsFeature.builder().config(helidonConfig).build();
/**
* Common initialization for concrete implementations.
*/
- protected MetricsCdiExtension() {
+ public MetricsCdiExtension() {
super(LOGGER, FEATURE_FACTORY, "mp.metrics");
}
diff --git a/metrics/microprofile/cdi/src/main/java/module-info.java b/metrics/microprofile/cdi/src/main/java/module-info.java
index ef67ebd4cc7..b2f61861a5f 100644
--- a/metrics/microprofile/cdi/src/main/java/module-info.java
+++ b/metrics/microprofile/cdi/src/main/java/module-info.java
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+import io.helidon.metrics.microprofile.cdi.MetricsCdiExtension;
+
+import jakarta.enterprise.inject.spi.Extension;
+
/**
* MicroProfile metrics module
*/
@@ -26,4 +30,6 @@
requires jakarta.cdi;
requires io.helidon.config;
requires microprofile.config.api;
+
+ provides Extension with MetricsCdiExtension;
}
\ No newline at end of file
diff --git a/metrics/microprofile/feature/pom.xml b/metrics/microprofile/feature/pom.xml
index 75d10f9c085..6bdff8ff70e 100644
--- a/metrics/microprofile/feature/pom.xml
+++ b/metrics/microprofile/feature/pom.xml
@@ -38,6 +38,10 @@
io.helidon.common
helidon-common-http
+
+ io.helidon.nima.observe
+ helidon-nima-observe
+
io.helidon.microprofile.config
helidon-microprofile-config
@@ -62,6 +66,11 @@
io.prometheus
simpleclient_common
+
+ io.helidon.microprofile.cdi
+ helidon-microprofile-cdi
+ test
+
org.junit.jupiter
junit-jupiter-api
@@ -91,6 +100,11 @@
io.helidon.nima.service-common
helidon-nima-service-common
+
+ io.helidon.microprofile.tests
+ helidon-microprofile-tests-junit5
+ test
+
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java
new file mode 100644
index 00000000000..af1b8a12865
--- /dev/null
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.microprofile.feature;
+
+import io.helidon.common.http.Http;
+import io.helidon.config.Config;
+import io.helidon.nima.observe.spi.ObserveProvider;
+import io.helidon.nima.webserver.http.HttpRouting;
+
+public class MetricsObserveProvider implements ObserveProvider {
+
+ private final MpMetricsFeature feature;
+
+ /**
+ * Do not use - required by service loader.
+ *
+ * @deprecated use {@link #create}
+ */
+ @Deprecated
+ public MetricsObserveProvider() {
+ this(null);
+ }
+
+ private MetricsObserveProvider(MpMetricsFeature feature) {
+ this.feature = feature;
+ }
+
+ public static ObserveProvider create() {
+ return create(MpMetricsFeature.create());
+ }
+
+ public static ObserveProvider create(MpMetricsFeature feature) {
+ return new MetricsObserveProvider(feature);
+ }
+
+ @Override
+ public String configKey() {
+ return "mp.metrics";
+ }
+
+ @Override
+ public String defaultEndpoint() {
+ return feature == null ? "metrics" : feature.configuredContext();
+ }
+
+ @Override
+ public void register(Config config, String componentPath, HttpRouting.Builder routing) {
+ MpMetricsFeature observer = feature == null
+ ? MpMetricsFeature.builder().webContext(componentPath)
+ .config(config)
+ .build()
+ : feature;
+
+ if (observer.enabled()) {
+ observer.context(componentPath);
+ routing.addFeature(observer);
+ } else {
+ routing.get(componentPath + "/*", (req, resp) -> resp.status(Http.Status.SERVICE_UNAVAILABLE_503)
+ .send());
+ }
+ }
+}
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
index 9f52e5174c1..f3f2b779d63 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
@@ -51,6 +51,10 @@ public Optional service() {
}
}
+ protected void context(String componentPath) {
+ super.context(componentPath);
+ }
+
private void configureRoutes(HttpRules rules) {
rules.get("/", this::prepareResponse);
}
@@ -78,11 +82,15 @@ private void prepareResponse(ServerRequest req, ServerResponse resp) {
resp.status(Http.Status.UNSUPPORTED_MEDIA_TYPE_415).send();
}
+ PrometheusFormatter.Builder formatterBuilder = PrometheusFormatter.builder().resultMediaType(requestedMediaType.get());
+ scope(req).ifPresent(formatterBuilder::scope);
+ metricName(req).ifPresent(formatterBuilder::meterName);
+
try {
MediaType resultMediaType = requestedMediaType.get();
resp.status(Http.Status.OK_200);
resp.headers().contentType(resultMediaType);
- resp.send(PrometheusFormatter.filteredOutput(resultMediaType, scope(req), metricName(req)));
+ resp.send(formatterBuilder.build().filteredOutput());
} catch (Exception ex) {
resp.status(Http.Status.INTERNAL_SERVER_ERROR_500);
resp.send("Error preparing metrics output; " + ex.getMessage());
diff --git a/metrics/microprofile/feature/src/main/java/module-info.java b/metrics/microprofile/feature/src/main/java/module-info.java
index 0db664b7042..d750388fca6 100644
--- a/metrics/microprofile/feature/src/main/java/module-info.java
+++ b/metrics/microprofile/feature/src/main/java/module-info.java
@@ -1,3 +1,6 @@
+import io.helidon.metrics.microprofile.feature.MetricsObserveProvider;
+import io.helidon.nima.observe.spi.ObserveProvider;
+
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
@@ -16,8 +19,11 @@
module io.helidon.metrics.microprofile.feature {
requires io.helidon.metrics.microprofile;
+ requires io.helidon.nima.observe;
requires io.helidon.nima.servicecommon;
requires simpleclient.common;
exports io.helidon.metrics.microprofile.feature;
+
+ provides ObserveProvider with MetricsObserveProvider;
}
\ No newline at end of file
diff --git a/metrics/microprofile/feature/src/test/java/io/helidon/metrics/microprofile/feature/MpFeatureTest.java b/metrics/microprofile/feature/src/test/java/io/helidon/metrics/microprofile/feature/MpFeatureTest.java
new file mode 100644
index 00000000000..25b3ca017ec
--- /dev/null
+++ b/metrics/microprofile/feature/src/test/java/io/helidon/metrics/microprofile/feature/MpFeatureTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.microprofile.feature;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.helidon.metrics.microprofile.MpRegistryFactory;
+import io.helidon.microprofile.tests.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+@HelidonTest
+public class MpFeatureTest {
+
+ @Inject
+ private WebTarget webTarget;
+
+ @Disabled
+ @Test
+ void testEndpoint() {
+ MetricRegistry metricRegistry = MpRegistryFactory.get().registry(MpRegistryFactory.APPLICATION_SCOPE);
+ Counter counter = metricRegistry.counter("endpointCounter");
+ counter.inc(4);
+
+ String metricsResponse = webTarget.path("/metrics")
+ .request()
+ .accept(MediaType.TEXT_PLAIN)
+ .get(String.class);
+
+ Pattern pattern = Pattern.compile(".*^endpointCounter_total\\{.*?mp_scope=\"application\".*?}\\s*(\\S*).*?");
+ Matcher matcher = pattern.matcher(metricsResponse);
+
+ assertThat("/metrics response", matcher.matches(), is(true));
+ assertThat("Captured groups", matcher.groupCount(), is(1));
+ assertThat("Captured counter value", Integer.parseInt(matcher.group(1)), is(4));
+ }
+
+
+}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
index 2d2287160bf..8d0264feeca 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
@@ -17,7 +17,6 @@
import java.util.HashSet;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
@@ -42,22 +41,35 @@ public class PrometheusFormatter {
TextFormat.CONTENT_TYPE_004,
MediaTypes.APPLICATION_OPENMETRICS_TEXT,
TextFormat.CONTENT_TYPE_OPENMETRICS_100);
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
private static final String PROMETHEUS_TYPE_PREFIX = "# TYPE";
private static final String PROMETHEUS_HELP_PREFIX = "# HELP";
- public static String filteredOutput(MediaType resultMediaType,
- Optional scopeSelection,
- Optional meterNameSelection) {
+ private final String scopeSelection;
+ private final String meterSelection;
+ private final MediaType resultMediaType;
+
+ private PrometheusFormatter(Builder builder) {
+ scopeSelection = builder.scopeSelection;
+ meterSelection = builder.meterNameSelection;
+ resultMediaType = builder.resultMediaType;
+ }
+
+ public String filteredOutput() {
return formattedOutput(MpRegistryFactory.get().prometheusMeterRegistry(),
resultMediaType,
scopeSelection,
- meterNameSelection);
+ meterSelection);
}
static String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
MediaType resultMediaType,
- Optional scopeSelection,
- Optional meterNameSelection) {
+ String scopeSelection,
+ String meterNameSelection) {
String rawPrometheusOutput = prometheusMeterRegistry
.scrape(PrometheusFormatter.MEDIA_TYPE_TO_FORMAT.get(resultMediaType),
meterNamesOfInterest(prometheusMeterRegistry, meterNameSelection));
@@ -66,7 +78,7 @@ static String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
}
- static String filter(String output, Optional scope, Optional meterName) {
+ static String filter(String output, String scope, String meterName) {
/*
* Output looks like repeating sections of this:
*
@@ -77,32 +89,35 @@ static String filter(String output, Optional scope, Optional met
* ... (possibly more lines for the same meter with different tag values)
*
*
- * If we are limiting by scope or meter name, then suppress the help and type if there is no meter data.
+ * To select using scope or meter name, always accumulate the type and help information.
+ * Then, once we have the line containing the actual meter ID, if that line matches the selection
+ * add the previously-gathered help and type and the meter line to the output.
*/
- Pattern scopePattern = scope.isPresent()
- ? Pattern.compile("\\{.*mp_scope=\"" + scope.get() + "\")}")
+ Pattern scopePattern = scope != null
+ ? Pattern.compile(".*?\\{.*?mp_scope=\"" + scope + "\".*?}.*?")
: null;
StringBuilder allOutput = new StringBuilder();
- StringBuilder typeAndHelpOutput = new StringBuilder();
- StringBuilder metricOutput = new StringBuilder();
+ StringBuilder typeAndHelpOutputForCurrentMeter = new StringBuilder();
+ StringBuilder meterOutputForCurrentMeter = new StringBuilder();
String[] lines = output.split("\r?\n");
+
for (String line : lines) {
if (line.startsWith(PROMETHEUS_HELP_PREFIX)) {
- allOutput.append(flushForMeterAndClear(typeAndHelpOutput, metricOutput));
- typeAndHelpOutput.append(line)
+ allOutput.append(flushForMeterAndClear(typeAndHelpOutputForCurrentMeter, meterOutputForCurrentMeter));
+ typeAndHelpOutputForCurrentMeter.append(line)
.append(System.lineSeparator());
} else if (line.startsWith(PROMETHEUS_TYPE_PREFIX)) {
- typeAndHelpOutput.append(line)
+ typeAndHelpOutputForCurrentMeter.append(line)
.append(System.lineSeparator());
} else if (scopePattern == null || scopePattern.matcher(line).matches()) {
- metricOutput.append(line)
+ meterOutputForCurrentMeter.append(line)
.append(System.lineSeparator());
}
}
- return allOutput.append(flushForMeterAndClear(typeAndHelpOutput, metricOutput))
+ return allOutput.append(flushForMeterAndClear(typeAndHelpOutputForCurrentMeter, meterOutputForCurrentMeter))
.toString()
.replaceFirst("# EOF\r?\n?", "");
@@ -120,20 +135,20 @@ private static String flushForMeterAndClear(StringBuilder helpAndType, StringBui
}
static Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterRegistry,
- Optional meterNameSelection) {
- if (meterNameSelection.isEmpty()) {
+ String meterNameSelection) {
+ if (meterNameSelection == null || meterNameSelection.isEmpty()) {
return null; // null passed to PrometheusMeterRegistry.scrape means "no selection based on meter name
}
// Meter names in the output include units (if specified) so retrieve matching meters to get the units.
- String normalizedMeterName = normalizeMeterName(meterNameSelection.get());
+ String normalizedMeterName = normalizeMeterName(meterNameSelection);
Set unitsForMeter = new HashSet<>();
unitsForMeter.add("");
Set suffixesForMeter = new HashSet<>();
suffixesForMeter.add("");
- prometheusMeterRegistry.find(meterNameSelection.get())
+ prometheusMeterRegistry.find(meterNameSelection)
.meters()
.forEach(meter -> {
Meter.Id meterId = meter.getId();
@@ -179,4 +194,31 @@ static String normalizeMeterName(String meterName) {
return result;
}
+ public static class Builder implements io.helidon.common.Builder {
+
+ private String meterNameSelection;
+ private String scopeSelection;
+ private MediaType resultMediaType = MediaTypes.TEXT_PLAIN;
+
+ @Override
+ public PrometheusFormatter build() {
+ return new PrometheusFormatter(this);
+ }
+
+ public Builder meterName(String filter) {
+ meterNameSelection = filter;
+ return identity();
+ }
+
+ public Builder scope(String filter) {
+ scopeSelection = filter;
+ return identity();
+ }
+
+ public Builder resultMediaType(MediaType resultMediaType) {
+ this.resultMediaType = resultMediaType;
+ return identity();
+ }
+ }
+
}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
index e0cbb9b3213..ff4da01d23b 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestCounters.java
@@ -49,7 +49,7 @@ public String get(String s) {
};
prometheusMeterRegistry = new PrometheusMeterRegistry(config);
- meterRegistry = Metrics.globalRegistry.add(prometheusMeterRegistry);
+ meterRegistry = Metrics.globalRegistry;
mpMetricRegistry = MpMetricRegistry.create("myscope", meterRegistry);
}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
index 21f495d74c7..d24c49a1c48 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
@@ -24,6 +24,7 @@
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.MetricRegistry;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -33,7 +34,6 @@
public class TestFormatter {
private static final String SCOPE = "formatScope";
- private static final String COUNTER_NAME = "formatCounter";
private static MpRegistryFactory mpRegistryFactory;
@BeforeAll
@@ -45,7 +45,8 @@ static void init() {
void testSimpleCounterFormatting() {
MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
- Counter counter = reg.counter(COUNTER_NAME);
+ String counterName = "formatCounter";
+ Counter counter = reg.counter(counterName);
counter.inc();
assertThat("Updated counter", counter.getCount(), is(1L));
@@ -58,20 +59,136 @@ void testSimpleCounterFormatting() {
String promFormat = PrometheusFormatter.formattedOutput(promReg,
MediaTypes.TEXT_PLAIN,
- Optional.empty(),
- Optional.empty());
+ null,
+ null);
// Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
- // catpure the meter value, further uninteresting text.
+ // capture the meter value, further uninteresting text.
Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
- + COUNTER_NAME
+ + counterName
+ "_total\\{.*mp_scope=\""
+ SCOPE
+ "\".*?}\\s+(\\S+).*?",
Pattern.MULTILINE + Pattern.DOTALL);
Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
- assertThat("Output " + System.lineSeparator() + promFormat + System.lineSeparator() + " matches expected", matcher.matches(), is(true));
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(true));
assertThat("Output matcher groups", matcher.groupCount(), is(1));
assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
}
+
+ @Test
+ void testScopeSelection() {
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
+
+ String otherScope = "other";
+ MetricRegistry otherRegistry = mpRegistryFactory.registry(otherScope);
+
+ String counterName = "formatCounterWithScope";
+ Counter counter = reg.counter(counterName);
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+
+ // Even though we register this "other" counter in a different MP registry, it should
+ // find its way into the shared Prometheus meter registry (with the correct mp_scope tag).
+ Counter otherCounter = otherRegistry.counter(counterName);
+ otherCounter.inc(2L);
+
+ PrometheusMeterRegistry promReg = ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ .stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
+
+ PrometheusFormatter formatter = PrometheusFormatter.builder()
+ .resultMediaType(MediaTypes.TEXT_PLAIN)
+ .scope(SCOPE)
+ .build();
+ String promFormat = formatter.filteredOutput();
+
+ // Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
+ // capture the meter value, further uninteresting text.
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(1));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
+
+ // Make sure the "other" counter is not also present in the output; it should have been suppressed
+ // because of the scope filtering we requested.
+ Pattern unexpectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + otherScope
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ matcher = unexpectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(false));
+
+ }
+
+ @Test
+ void testNameSelection() {
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
+
+ String counterName = "formatCounterWithName";
+ Counter counter = reg.counter(counterName);
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+
+ String otherCounterName = "otherFormatCounterWithName";
+ Counter otherCounter = reg.counter(otherCounterName);
+ otherCounter.inc(3L);
+
+ PrometheusMeterRegistry promReg = ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ .stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
+
+ PrometheusFormatter formatter = PrometheusFormatter.builder()
+ .resultMediaType(MediaTypes.TEXT_PLAIN)
+ .meterName(counterName)
+ .build();
+ String promFormat = formatter.filteredOutput();
+
+ // Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
+ // capture the meter value, further uninteresting text.
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(1));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
+
+ // Make sure the counter with an unmatching name does not also appear in the output.
+ Pattern unexpectedNameAndTagValue = Pattern.compile(".*?^"
+ + otherCounterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ matcher = unexpectedNameAndTagValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(false));
+ }
}
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java
index ebce0a4134b..0edb306fa07 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestHistograms.java
@@ -48,7 +48,7 @@ public String get(String s) {
};
prometheusMeterRegistry = new PrometheusMeterRegistry(config);
- meterRegistry = Metrics.globalRegistry.add(prometheusMeterRegistry);
+ meterRegistry = Metrics.globalRegistry;
mpMetricRegistry = MpMetricRegistry.create("histoScope", meterRegistry);
}
From 97dc30d71ed49698358aaa8961d67e55e0c82672 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Tue, 20 Jun 2023 18:58:46 -0500
Subject: [PATCH 06/67] Finish missing method impls in metric registry impl;
improve formatter test
---
.../microprofile/MpMetricRegistry.java | 30 +++++++------------
.../metrics/microprofile/TestFormatter.java | 12 ++++----
2 files changed, 16 insertions(+), 26 deletions(-)
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
index 0dd9d4bd650..0c034054ad9 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricRegistry.java
@@ -344,12 +344,12 @@ public SortedSet getMetricIDs() {
@Override
public SortedMap getGauges() {
- return null;
+ return getGauges(MetricFilter.ALL);
}
@Override
public SortedMap getGauges(MetricFilter metricFilter) {
- return null;
+ return getMetrics(Gauge.class, metricFilter);
}
@Override
@@ -364,27 +364,31 @@ public SortedMap getCounters(MetricFilter metricFilter) {
@Override
public SortedMap getHistograms() {
- return null;
+ return getHistograms(MetricFilter.ALL);
}
@Override
public SortedMap getHistograms(MetricFilter metricFilter) {
- return null;
+ return getMetrics(Histogram.class, metricFilter);
}
@Override
public SortedMap getTimers() {
- return null;
+ return getTimers(MetricFilter.ALL);
}
@Override
public SortedMap getTimers(MetricFilter metricFilter) {
- return null;
+ return getMetrics(Timer.class, metricFilter);
}
@Override
public SortedMap getMetrics(MetricFilter metricFilter) {
- return null;
+ return metricsById.entrySet().stream()
+ .filter(e -> metricFilter.matches(e.getKey(), e.getValue()))
+ .collect(TreeMap::new,
+ (map, e) -> map.put(e.getKey(), e.getValue()),
+ TreeMap::putAll);
}
@Override
@@ -566,18 +570,6 @@ boolean isConsistentMetadata(Metadata a, Metadata b) {
return a.equals(b);
}
- MpMetricId consistentMpMetricId(Metadata metadata, Meter.Type type, Tag... tags) {
- MpTags.checkForReservedTags(tags);
- MpMetricId id = new MpMetricId(metadata.getName(),
- tags,
- automaticTags(),
- metadata.getUnit(),
- metadata.getDescription(),
- type);
- MpTags.checkTagNameSetConsistency(id, metricIdsByName.get(metadata.getName()));
- return id;
- }
-
/**
* Checks that the tag names in the provided ID are consistent with the tag names in any previously-registered ID
* with the same name; throws {@code IllegalArgumentException} if inconsistent.
diff --git a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
index d24c49a1c48..5c23ec7f1a1 100644
--- a/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
+++ b/metrics/microprofile/microprofile/src/test/java/io/helidon/metrics/microprofile/TestFormatter.java
@@ -50,17 +50,15 @@ void testSimpleCounterFormatting() {
counter.inc();
assertThat("Updated counter", counter.getCount(), is(1L));
- PrometheusMeterRegistry promReg = ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
.stream()
.filter(PrometheusMeterRegistry.class::isInstance)
.map(PrometheusMeterRegistry.class::cast)
.findFirst()
.orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
- String promFormat = PrometheusFormatter.formattedOutput(promReg,
- MediaTypes.TEXT_PLAIN,
- null,
- null);
+ PrometheusFormatter formatter = PrometheusFormatter.builder().build();
+ String promFormat = formatter.filteredOutput();
// Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
// capture the meter value, further uninteresting text.
@@ -95,7 +93,7 @@ void testScopeSelection() {
Counter otherCounter = otherRegistry.counter(counterName);
otherCounter.inc(2L);
- PrometheusMeterRegistry promReg = ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
.stream()
.filter(PrometheusMeterRegistry.class::isInstance)
.map(PrometheusMeterRegistry.class::cast)
@@ -151,7 +149,7 @@ void testNameSelection() {
Counter otherCounter = reg.counter(otherCounterName);
otherCounter.inc(3L);
- PrometheusMeterRegistry promReg = ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
.stream()
.filter(PrometheusMeterRegistry.class::isInstance)
.map(PrometheusMeterRegistry.class::cast)
From e4a9a673385b62bdcf38938482852ef4fad6134b Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Wed, 21 Jun 2023 00:35:44 -0500
Subject: [PATCH 07/67] Fix some checkstyle, JavaDoc comments, etc.
---
metrics/microprofile/cdi/pom.xml | 6 -
.../feature/MetricsObserveProvider.java | 14 +++
.../feature/MpMetricsFeature.java | 26 +++-
.../microprofile/feature/package-info.java | 4 +-
.../helidon/metrics/microprofile/MpGauge.java | 2 +-
.../metrics/microprofile/MpHistogram.java | 3 +
.../metrics/microprofile/MpMetric.java | 4 +
.../metrics/microprofile/MpMetricId.java | 30 ++++-
.../microprofile/MpRegistryFactory.java | 112 +++++++++++++-----
.../metrics/microprofile/MpSnapshot.java | 3 +-
.../helidon/metrics/microprofile/MpTags.java | 65 +++++++++-
.../helidon/metrics/microprofile/MpTimer.java | 12 ++
.../microprofile/PrometheusFormatter.java | 98 +++++++++++++--
.../metrics/microprofile/package-info.java | 20 ++++
14 files changed, 343 insertions(+), 56 deletions(-)
create mode 100644 metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/package-info.java
diff --git a/metrics/microprofile/cdi/pom.xml b/metrics/microprofile/cdi/pom.xml
index 34372e2d23d..10e3648f176 100644
--- a/metrics/microprofile/cdi/pom.xml
+++ b/metrics/microprofile/cdi/pom.xml
@@ -70,12 +70,6 @@
hamcrest-all
test
-
- io.helidon.metrics.microprofile
- helidon-metrics-microprofile-feature
- 4.0.0-SNAPSHOT
- compile
-
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java
index af1b8a12865..10b467d4bac 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MetricsObserveProvider.java
@@ -20,6 +20,9 @@
import io.helidon.nima.observe.spi.ObserveProvider;
import io.helidon.nima.webserver.http.HttpRouting;
+/**
+ * Observe provider for MP metrics.
+ */
public class MetricsObserveProvider implements ObserveProvider {
private final MpMetricsFeature feature;
@@ -38,10 +41,21 @@ private MetricsObserveProvider(MpMetricsFeature feature) {
this.feature = feature;
}
+ /**
+ * Creates a new provider instance, also creating a new feature instance to use.
+ *
+ * @return new provider
+ */
public static ObserveProvider create() {
return create(MpMetricsFeature.create());
}
+ /**
+ * Creates a new provider instance using an existing feature instance.
+ *
+ * @param feature feature instance the provider uses
+ * @return new provider
+ */
public static ObserveProvider create(MpMetricsFeature feature) {
return new MetricsObserveProvider(feature);
}
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
index f3f2b779d63..99072de2250 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/MpMetricsFeature.java
@@ -27,17 +27,38 @@
import io.helidon.nima.webserver.http.ServerRequest;
import io.helidon.nima.webserver.http.ServerResponse;
+/**
+ * MP metrics feature implementation.
+ */
public class MpMetricsFeature extends HelidonFeatureSupport {
private static final System.Logger LOGGER = System.getLogger(MpMetricsFeature.class.getName());
+ /**
+ * Creates a new builder for the MP metrics feature.
+ *
+ * @return new builder
+ */
public static Builder builder() {
return new Builder();
}
+ /**
+ * Creates a new default MP metrics feature.
+ *
+ * @return newly-created feature
+ */
public static MpMetricsFeature create() {
return builder().build();
}
+
+ /**
+ * Create a new instance.
+ *
+ * @param logger logger for the feature
+ * @param builder builder to use
+ * @param serviceName name of the service
+ */
protected MpMetricsFeature(System.Logger logger, Builder builder, String serviceName) {
super(logger, builder, serviceName);
}
@@ -60,7 +81,7 @@ private void configureRoutes(HttpRules rules) {
}
private void configureDisabledRoutes(HttpRules rules) {
- rules.get("/",this::prepareDisabledResponse);
+ rules.get("/", this::prepareDisabledResponse);
}
private void prepareDisabledResponse(ServerRequest req, ServerResponse resp) {
@@ -106,6 +127,9 @@ private Optional metricName(ServerRequest req) {
return req.query().first("name");
}
+ /**
+ * Builder for the MP metrics feature.
+ */
public static class Builder extends HelidonFeatureSupport.Builder {
private static final String DEFAULT_WEB_CONTEXT = "/metrics";
diff --git a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
index aca929be9f3..f0ad12aa561 100644
--- a/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
+++ b/metrics/microprofile/feature/src/main/java/io/helidon/metrics/microprofile/feature/package-info.java
@@ -15,6 +15,6 @@
*/
/**
- * MicroProfile metrics web feature
+ * MicroProfile metrics web feature.
*/
-package io.helidon.metrics.microprofile.feature;
\ No newline at end of file
+package io.helidon.metrics.microprofile.feature;
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java
index 6c78df493f2..7fcc1137a0e 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpGauge.java
@@ -53,7 +53,7 @@ static FunctionBased create(T target,
static SupplierBased create(Supplier supplier,
Gauge delegate,
MeterRegistry meterRegistry) {
- return new SupplierBased<>(supplier,delegate,meterRegistry);
+ return new SupplierBased<>(supplier, delegate, meterRegistry);
}
static class FunctionBased extends MpGauge {
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java
index d7d7b2a50f5..d51b9c66002 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpHistogram.java
@@ -20,6 +20,9 @@
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Snapshot;
+/**
+ * Implementation of the MicroProfile Metrics {@link org.eclipse.microprofile.metrics.Histogram}.
+ */
public class MpHistogram extends MpMetric implements Histogram {
/**
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
index b8c93c6dbee..f924b8e2a5c 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetric.java
@@ -33,6 +33,10 @@ M delegate() {
return delegate;
}
+ /**
+ * Returns the meter registry associated with this metric.
+ * @return the meter registry
+ */
protected MeterRegistry meterRegistry() {
return meterRegistry;
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
index ff2db8e3eb7..505eb238eda 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpMetricId.java
@@ -15,8 +15,6 @@
*/
package io.helidon.metrics.microprofile;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import io.micrometer.core.instrument.Meter;
@@ -24,11 +22,24 @@
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.Tag;
+/**
+ * Creates a new instance of the metric ID implementation.
+ */
public class MpMetricId extends MetricID {
private Tags fullTags = Tags.empty();
private final Meter.Id meterId;
+ /**
+ * Creates a new instance of the metric ID.
+ *
+ * @param name metric name
+ * @param tags tags
+ * @param automaticTags automatically-added tags (e.g., app ID or scope)
+ * @param baseUnit unit for the metric
+ * @param description description of the metric
+ * @param type meter type for the metric
+ */
MpMetricId(String name, Tag[] tags, Tag[] automaticTags, String baseUnit, String description, Meter.Type type) {
super(name, tags);
for (Tag tag : tags) {
@@ -41,14 +52,29 @@ public class MpMetricId extends MetricID {
meterId = new Meter.Id(name, fullTags, baseUnit, description, type);
}
+ /**
+ * Returns the metric name.
+ *
+ * @return metric name
+ */
public String name() {
return getName();
}
+ /**
+ * Returns all tags, including those "hidden" for differentiating scope.
+ *
+ * @return returns all tags
+ */
public Tags fullTags() {
return fullTags;
}
+ /**
+ * Returns the underlying implementation's meter ID.
+ *
+ * @return underlying meter ID
+ */
Meter.Id meterId() {
return meterId;
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
index f05be3981f6..e4244497a99 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpRegistryFactory.java
@@ -25,20 +25,42 @@
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.metrics.MetricRegistry;
+/**
+ * Factory class for finding existing or creating new metric registries.
+ */
public class MpRegistryFactory {
+ /**
+ * Scope name for the application metric registry.
+ */
public static final String APPLICATION_SCOPE = "application";
+
+ /**
+ * Scope name for the base metric registry.
+ */
public static final String BASE_SCOPE = "base";
+
+ /**
+ * Scope name for the vendor metric registry.
+ */
public static final String VENDOR_SCOPE = "vendor";
+
+ /**
+ * Config key suffix for distribution summary (histogram) precision.
+ */
public static final String HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX = "helidon.distribution-summary.precision";
+
+ /**
+ * Config key suffix for timer precision.
+ */
public static final String TIMER_PRECISION_CONFIG_KEY_SUFFIX = "helidon.timer.precision";
private static final int HISTOGRAM_PRECISION_DEFAULT = 3;
private static final int TIMER_PRECISION_DEFAULT = 3;
- private static MpRegistryFactory INSTANCE;
+ private static MpRegistryFactory instance;
- private static final ReentrantLock lock = new ReentrantLock();
+ private static final ReentrantLock LOCK = new ReentrantLock();
private final Exception creation;
private Config mpConfig;
@@ -47,65 +69,97 @@ public class MpRegistryFactory {
private final int distributionSummaryPrecision;
private final int timerPrecision;
-
+ /**
+ * Creates a new registry factory using the specified MicroProfile configuration.
+ *
+ * @param mpConfig the MicroProfile config to use in preparing the registry factory
+ * @return registry factory
+ */
public static MpRegistryFactory create(Config mpConfig) {
- lock.lock();
+ LOCK.lock();
try {
- if (INSTANCE == null) {
- INSTANCE = new MpRegistryFactory(mpConfig);
- return INSTANCE;
+ if (instance == null) {
+ instance = new MpRegistryFactory(mpConfig);
+ return instance;
} else {
- throw new IllegalStateException("Attempt to set up MpRegistryFactory multiple times; previous invocation follows: ",
- INSTANCE.creation);
+ throw new IllegalStateException(
+ "Attempt to set up MpRegistryFactory multiple times; previous invocation follows: ",
+ instance.creation);
}
} finally {
- lock.unlock();
+ LOCK.unlock();
}
}
+ /**
+ * Returns the singleton registry factory, creating it if needed.
+ *
+ * @return the registry factory
+ */
public static MpRegistryFactory get() {
- lock.lock();
+ LOCK.lock();
try {
- if (INSTANCE == null) {
- INSTANCE = new MpRegistryFactory(ConfigProvider.getConfig());
+ if (instance == null) {
+ instance = new MpRegistryFactory(ConfigProvider.getConfig());
}
- return INSTANCE;
+ return instance;
} finally {
- lock.unlock();
+ LOCK.unlock();
}
}
- private MpRegistryFactory(Config mpConfig) {
- creation = new Exception("Initial creation of " + MpRegistryFactory.class.getSimpleName());
- this.mpConfig = mpConfig;
- prometheusMeterRegistry = findOrAddPrometheusRegistry();
- distributionSummaryPrecision = mpConfig.getOptionalValue("mp.metrics." + HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX,
- Integer.class).orElse(HISTOGRAM_PRECISION_DEFAULT);
- timerPrecision = mpConfig.getOptionalValue("mp.metrics." + TIMER_PRECISION_CONFIG_KEY_SUFFIX,
- Integer.class).orElse(TIMER_PRECISION_DEFAULT);
- registries.put(APPLICATION_SCOPE, MpMetricRegistry.create(APPLICATION_SCOPE, Metrics.globalRegistry));
- registries.put(BASE_SCOPE, BaseRegistry.create(Metrics.globalRegistry));
- registries.put(VENDOR_SCOPE, MpMetricRegistry.create(VENDOR_SCOPE, Metrics.globalRegistry));
-
- }
-
+ /**
+ * Returns the {@link org.eclipse.microprofile.metrics.MetricRegistry} for the specified scope.
+ *
+ * @param scope scope for the meter registry to get or create
+ * @return previously-existing or newly-created {@code MeterRegistry} for the specified scope
+ */
public MetricRegistry registry(String scope) {
return registries.computeIfAbsent(scope,
s -> MpMetricRegistry.create(s, Metrics.globalRegistry));
}
+ /**
+ * Returns the Prometheus meter registry known to the registry factory.
+ *
+ * @return Prometheus meter registry
+ */
public PrometheusMeterRegistry prometheusMeterRegistry() {
return prometheusMeterRegistry;
}
+ /**
+ * Returns the precision for use with distribution summaries.
+ *
+ * @return distribution summary precision
+ */
int distributionSummaryPrecision() {
return distributionSummaryPrecision;
}
+ /**
+ * Returns the precision for use with timers.
+ *
+ * @return timer precision
+ */
int timerPrecision() {
return timerPrecision;
}
+ private MpRegistryFactory(Config mpConfig) {
+ creation = new Exception("Initial creation of " + MpRegistryFactory.class.getSimpleName());
+ this.mpConfig = mpConfig;
+ prometheusMeterRegistry = findOrAddPrometheusRegistry();
+ distributionSummaryPrecision = mpConfig.getOptionalValue("mp.metrics." + HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX,
+ Integer.class).orElse(HISTOGRAM_PRECISION_DEFAULT);
+ timerPrecision = mpConfig.getOptionalValue("mp.metrics." + TIMER_PRECISION_CONFIG_KEY_SUFFIX,
+ Integer.class).orElse(TIMER_PRECISION_DEFAULT);
+ registries.put(APPLICATION_SCOPE, MpMetricRegistry.create(APPLICATION_SCOPE, Metrics.globalRegistry));
+ registries.put(BASE_SCOPE, BaseRegistry.create(Metrics.globalRegistry));
+ registries.put(VENDOR_SCOPE, MpMetricRegistry.create(VENDOR_SCOPE, Metrics.globalRegistry));
+
+ }
+
private PrometheusMeterRegistry findOrAddPrometheusRegistry() {
return Metrics.globalRegistry.getRegistries().stream()
.filter(PrometheusMeterRegistry.class::isInstance)
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java
index 8d48cb8f8b8..be841478bed 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpSnapshot.java
@@ -17,6 +17,7 @@
import java.io.OutputStream;
import java.io.PrintStream;
+import java.nio.charset.Charset;
import java.util.Arrays;
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
@@ -62,6 +63,6 @@ public PercentileValue[] percentileValues() {
@Override
public void dump(OutputStream outputStream) {
- delegate.outputSummary(new PrintStream(outputStream), 1);
+ delegate.outputSummary(new PrintStream(outputStream, false, Charset.defaultCharset()), 1);
}
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java
index 4fb2ce369e1..0b142b6b530 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTags.java
@@ -27,6 +27,9 @@
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.Tag;
+/**
+ * Tags utility class.
+ */
class MpTags {
private static final Set RESERVED_TAG_NAMES = new HashSet<>();
@@ -37,10 +40,26 @@ class MpTags {
RESERVED_TAG_NAMES.add(MpMetricRegistry.MP_SCOPE_TAG_NAME);
}
+ /**
+ * Hidden constructor for utility class.
+ */
+ private MpTags() {
+ }
+
+ /**
+ * Returns the "app" value.
+ *
+ * @return the app value
+ */
static String mpAppValue() {
return mpAppValue;
}
+ /**
+ * Returns the configuration used to prepare tags for metric IDs.
+ *
+ * @param config config used in setting tags in the IDs
+ */
static void config(Config config) {
ConfigValue configValue = config.getConfigValue(MpMetricRegistry.MP_APPLICATION_TAG_NAME);
if (configValue.getValue() != null) {
@@ -48,22 +67,46 @@ static void config(Config config) {
}
}
+ /**
+ * Converts MicroProfile tags to the underlying implementation {@link io.micrometer.core.instrument.Tags} abstraction.
+ *
+ * @param tags MicroProfile tags to convert
+ * @return underlying implementation tags abstraction
+ */
static Tags fromMp(List tags) {
return Tags.of(tags.stream()
.map(MpTags::fromMp)
.toList());
}
+ /**
+ * Converts MicroProfile tags to the underlying implementation {@link io.micrometer.core.instrument.Tags} abstraction.
+ *
+ * @param tags MicroProfile tags to convert
+ * @return underlying implementation tags abstraction
+ */
static Tags fromMp(Tag... tags) {
return Tags.of(Arrays.stream(tags).map(MpTags::fromMp).toList());
}
+ /**
+ * Converts MicroProfile tags to the underlying implementation {@link io.micrometer.core.instrument.Tags} abstraction.
+ *
+ * @param tags MicroProfile tags to convert
+ * @return underlying implementation tags abstraction
+ */
static Tags fromMp(Map tags) {
return tags.entrySet().stream().collect(Tags::empty,
(meterTags, entry) -> meterTags.and(entry.getKey(), entry.getValue()),
Tags::and);
}
+ /**
+ * Converts a MicroProfile tag to the underlying implementation {@link Tags} abstraction.
+ *
+ * @param tag MicroProfile tag to convert
+ * @return underlying implementation tags abstraction
+ */
static io.micrometer.core.instrument.Tag fromMp(Tag tag) {
return io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue());
}
@@ -92,6 +135,15 @@ static void checkTagNameSetConsistency(MetricID mpMetricId, List mpMet
}
}
+ /**
+ * Verifies that the candidate tag names are consistent with the specified groups of tags (typically tags previously recorded
+ * for meters with the same name).
+ *
+ * @param metricName name of the meter ID being checked (for error reporting)
+ * @param candidateTags candidate tags
+ * @param establishedTagGroups groups of tags previously established against which to check the candidates
+ * @throws java.lang.IllegalArgumentException if the candidate tags are not consistent with the established tag groups
+ */
static void checkTagNameSetConsistency(String metricName, Set candidateTags, Iterable> establishedTagGroups) {
for (Set establishedTags : establishedTagGroups) {
if (!candidateTags.equals(establishedTags)) {
@@ -116,6 +168,12 @@ static boolean isConsistentTagNameSet(MetricID a, MetricID b) {
return a.getTags().keySet().equals(b.getTags().keySet());
}
+ /**
+ * Checks to make sure the specified tags do not include any reserved tag names.
+ *
+ * @param tags tags to check
+ * @throws java.lang.IllegalArgumentException if any of names of the specified tags are reserved
+ */
static void checkForReservedTags(Tag... tags) {
if (tags != null) {
Set offendingReservedTags = null;
@@ -133,6 +191,12 @@ static void checkForReservedTags(Tag... tags) {
}
}
+ /**
+ * Checks to make sure the specified tags do not include any reserved tag names.
+ *
+ * @param tagNames tag names to check
+ * @throws java.lang.IllegalArgumentException if any of names of the specified tags are reserved
+ */
static void checkForReservedTags(Set tagNames) {
if (tagNames != null) {
Set offendingReservedTags = null;
@@ -149,5 +213,4 @@ static void checkForReservedTags(Set tagNames) {
}
}
}
-
}
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java
index 193f7ca3efe..f80b396a6be 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/MpTimer.java
@@ -23,6 +23,9 @@
import io.micrometer.core.instrument.Timer;
import org.eclipse.microprofile.metrics.Snapshot;
+/**
+ * Implementation of MicroProfile metrics {@link org.eclipse.microprofile.metrics.Timer}.
+ */
public class MpTimer extends MpMetric implements org.eclipse.microprofile.metrics.Timer {
/**
@@ -69,8 +72,17 @@ public Snapshot getSnapshot() {
return new MpSnapshot(delegate().takeSnapshot());
}
+ /**
+ * Implementation of MicroProfile Metrics {@link org.eclipse.microprofile.metrics.Timer.Context}.
+ */
public class Context implements org.eclipse.microprofile.metrics.Timer.Context {
+ /**
+ * Used only internally.
+ */
+ private Context() {
+ }
+
private final Timer.Sample sample = Timer.start(meterRegistry());
@Override
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
index 8d0264feeca..6dc27d2f26a 100644
--- a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/PrometheusFormatter.java
@@ -37,11 +37,19 @@
*
*/
public class PrometheusFormatter {
+ /**
+ * Mapping from supported media types to the corresponding Prometheus registry content types.
+ */
public static final Map MEDIA_TYPE_TO_FORMAT = Map.of(MediaTypes.TEXT_PLAIN,
TextFormat.CONTENT_TYPE_004,
MediaTypes.APPLICATION_OPENMETRICS_TEXT,
TextFormat.CONTENT_TYPE_OPENMETRICS_100);
+ /**
+ * Returns a new builder for constructing a formatter.
+ *
+ * @return new builder
+ */
public static Builder builder() {
return new Builder();
}
@@ -59,6 +67,12 @@ private PrometheusFormatter(Builder builder) {
resultMediaType = builder.resultMediaType;
}
+ /**
+ * Returns the Prometheus output governed by the previously-specified media type, optionally filtered
+ * by the previously-specified scope and meter name.
+ *
+ * @return filtered Prometheus output
+ */
public String filteredOutput() {
return formattedOutput(MpRegistryFactory.get().prometheusMeterRegistry(),
resultMediaType,
@@ -66,7 +80,17 @@ public String filteredOutput() {
meterSelection);
}
- static String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
+ /**
+ * Retrieves the Prometheus-format report from the specified registry, according to the specified media type,
+ * filtered by the specified scope and meter name, and returns the filtered Prometheus-format output.
+ *
+ * @param prometheusMeterRegistry registry to query
+ * @param resultMediaType media type which controls the exact output format
+ * @param scopeSelection scope to select; null if no scope selection required
+ * @param meterNameSelection meter name to select; null if no meter name selection required
+ * @return filtered output
+ */
+ String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
MediaType resultMediaType,
String scopeSelection,
String meterNameSelection) {
@@ -74,11 +98,21 @@ static String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
.scrape(PrometheusFormatter.MEDIA_TYPE_TO_FORMAT.get(resultMediaType),
meterNamesOfInterest(prometheusMeterRegistry, meterNameSelection));
- return filter(rawPrometheusOutput, scopeSelection, meterNameSelection);
-
+ return filter(rawPrometheusOutput, scopeSelection);
}
- static String filter(String output, String scope, String meterName) {
+ /**
+ * Filter the Prometheus-format report by the specified scope.
+ *
+ * @param output Prometheus-format report
+ * @param scope scope to filter; null means no filtering by scope
+ * @return output filtered by scope (if specified)
+ */
+ static String filter(String output, String scope) {
+ if (scope == null) {
+ return output;
+ }
+
/*
* Output looks like repeating sections of this:
*
@@ -93,9 +127,7 @@ static String filter(String output, String scope, String meterName) {
* Then, once we have the line containing the actual meter ID, if that line matches the selection
* add the previously-gathered help and type and the meter line to the output.
*/
- Pattern scopePattern = scope != null
- ? Pattern.compile(".*?\\{.*?mp_scope=\"" + scope + "\".*?}.*?")
- : null;
+ Pattern scopePattern = Pattern.compile(".*?\\{.*?mp_scope=\"" + scope + "\".*?}.*?");
StringBuilder allOutput = new StringBuilder();
StringBuilder typeAndHelpOutputForCurrentMeter = new StringBuilder();
@@ -112,7 +144,7 @@ static String filter(String output, String scope, String meterName) {
} else if (line.startsWith(PROMETHEUS_TYPE_PREFIX)) {
typeAndHelpOutputForCurrentMeter.append(line)
.append(System.lineSeparator());
- } else if (scopePattern == null || scopePattern.matcher(line).matches()) {
+ } else if (scopePattern.matcher(line).matches()) {
meterOutputForCurrentMeter.append(line)
.append(System.lineSeparator());
}
@@ -120,7 +152,6 @@ static String filter(String output, String scope, String meterName) {
return allOutput.append(flushForMeterAndClear(typeAndHelpOutputForCurrentMeter, meterOutputForCurrentMeter))
.toString()
.replaceFirst("# EOF\r?\n?", "");
-
}
private static String flushForMeterAndClear(StringBuilder helpAndType, StringBuilder metricData) {
@@ -134,6 +165,14 @@ private static String flushForMeterAndClear(StringBuilder helpAndType, StringBui
return result.toString();
}
+ /**
+ * Prepares a set containing the names of meters from the specified Prometheus meter registry which match
+ * the specified meter name selection.
+ *
+ * @param prometheusMeterRegistry Prometheus meter registry to query
+ * @param meterNameSelection meter name to select
+ * @return set of matching meter names
+ */
static Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterRegistry,
String meterNameSelection) {
if (meterNameSelection == null || meterNameSelection.isEmpty()) {
@@ -163,6 +202,12 @@ static Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterR
return result;
}
+ /**
+ * Returns the Prometheus-format meter name suffixes for the given meter type.
+ *
+ * @param meterType {@link io.micrometer.core.instrument.Meter.Type} of interest
+ * @return suffixes used in reporting the corresponding meter's value(s)
+ */
static Set meterNameSuffixes(Meter.Type meterType) {
return switch (meterType) {
case COUNTER -> Set.of("_total");
@@ -194,27 +239,54 @@ static String normalizeMeterName(String meterName) {
return result;
}
+ /**
+ * Builder for creating a tailored Prometheus formatter.
+ */
public static class Builder implements io.helidon.common.Builder {
private String meterNameSelection;
private String scopeSelection;
private MediaType resultMediaType = MediaTypes.TEXT_PLAIN;
+ /**
+ * Used only internally.
+ */
+ private Builder() {
+ }
+
@Override
public PrometheusFormatter build() {
return new PrometheusFormatter(this);
}
- public Builder meterName(String filter) {
- meterNameSelection = filter;
+ /**
+ * Sets the meter name with which to filter the output.
+ *
+ * @param meterName meter name to select
+ * @return updated builder
+ */
+ public Builder meterName(String meterName) {
+ meterNameSelection = meterName;
return identity();
}
- public Builder scope(String filter) {
- scopeSelection = filter;
+ /**
+ * Sets the scope value with which to filter the output.
+ *
+ * @param scope scope to select
+ * @return updated builder
+ */
+ public Builder scope(String scope) {
+ scopeSelection = scope;
return identity();
}
+ /**
+ * Sets the {@link io.helidon.common.media.type.MediaType} which controls the formatting of the resulting output.
+ *
+ * @param resultMediaType media type
+ * @return updated builder
+ */
public Builder resultMediaType(MediaType resultMediaType) {
this.resultMediaType = resultMediaType;
return identity();
diff --git a/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/package-info.java b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/package-info.java
new file mode 100644
index 00000000000..0a6e34b556b
--- /dev/null
+++ b/metrics/microprofile/microprofile/src/main/java/io/helidon/metrics/microprofile/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implementation of MicroProfile Metrics interfaces.
+ */
+package io.helidon.metrics.microprofile;
From 4693d8333cdaba441ecf805e367f9b941e60501d Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Wed, 21 Jun 2023 00:37:53 -0500
Subject: [PATCH 08/67] Further style, etc. fixes
---
.../microprofile/cdi/MetricsCdiExtension.java | 3 +++
.../microprofile/cdi/package-info.java | 20 +++++++++++++++++++
.../cdi/src/main/java/module-info.java | 2 +-
3 files changed, 24 insertions(+), 1 deletion(-)
create mode 100644 metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/package-info.java
diff --git a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
index 55f0be7ed57..25556ea9aa4 100644
--- a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
+++ b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/MetricsCdiExtension.java
@@ -23,6 +23,9 @@
import jakarta.enterprise.inject.spi.ProcessManagedBean;
+/**
+ * MP Metrics CDI extension.
+ */
public class MetricsCdiExtension extends HelidonRestCdiExtension {
private static final System.Logger LOGGER = System.getLogger(MetricsCdiExtension.class.getName());
diff --git a/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/package-info.java b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/package-info.java
new file mode 100644
index 00000000000..1ee2aca447c
--- /dev/null
+++ b/metrics/microprofile/cdi/src/main/java/io/helidon/metrics/microprofile/cdi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * MP Metrics CDI extension and related files.
+ */
+package io.helidon.metrics.microprofile.cdi;
diff --git a/metrics/microprofile/cdi/src/main/java/module-info.java b/metrics/microprofile/cdi/src/main/java/module-info.java
index b2f61861a5f..69e9593dc06 100644
--- a/metrics/microprofile/cdi/src/main/java/module-info.java
+++ b/metrics/microprofile/cdi/src/main/java/module-info.java
@@ -32,4 +32,4 @@
requires microprofile.config.api;
provides Extension with MetricsCdiExtension;
-}
\ No newline at end of file
+}
From c7b178e211f1ff7c31442d4862a7355b4419a8bb Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Wed, 21 Jun 2023 23:08:01 -0500
Subject: [PATCH 09/67] metrics/api stable; metrics/metrics WIP
---
.../helidon/metrics/api/AbstractRegistry.java | 251 ++-----
.../io/helidon/metrics/api/LabeledSample.java | 8 +-
.../helidon/metrics/api/LabeledSnapshot.java | 65 +-
.../io/helidon/metrics/api/MetricStore.java | 199 +++--
.../helidon/metrics/api/MetricsSettings.java | 22 +-
.../metrics/api/MetricsSettingsImpl.java | 70 +-
.../metrics/api/NoOpMetricFactory.java | 55 ++
.../helidon/metrics/api/NoOpMetricImpl.java | 239 +-----
.../metrics/api/NoOpMetricRegistry.java | 67 +-
.../metrics/api/NoOpRegistryFactory.java | 21 +-
.../java/io/helidon/metrics/api/Registry.java | 11 +-
.../helidon/metrics/api/RegistryFactory.java | 23 +-
.../helidon/metrics/api/RegistrySettings.java | 4 +-
.../java/io/helidon/metrics/api/Sample.java | 6 +-
.../metrics/api/spi/MetricFactory.java | 92 +++
.../helidon/metrics/api/MetricStoreTests.java | 48 +-
.../metrics/api/TestMetricsSettings.java | 32 +-
.../java/io/helidon/metrics/api/TestNoOp.java | 4 +-
.../helidon/metrics/api/TestNoOpRegistry.java | 21 +-
.../api/TestRegistrySettingsProperties.java | 7 +-
.../resources/registrySettings.properties | 6 +-
.../api/src/test/resources/testconfig.yaml | 12 +-
metrics/metrics/pom.xml | 4 +
.../java/io/helidon/metrics/BaseRegistry.java | 70 +-
.../ExecutorServiceMetricsObserver.java | 23 +-
.../io/helidon/metrics/HelidonCounter.java | 10 +-
.../io/helidon/metrics/HelidonHistogram.java | 79 +-
.../helidon/metrics/HelidonMetricFactory.java | 58 ++
.../io/helidon/metrics/HelidonSnapshot.java | 73 ++
.../java/io/helidon/metrics/HelidonTimer.java | 58 +-
.../java/io/helidon/metrics/Registry.java | 85 +--
.../io/helidon/metrics/RegistryFactory.java | 41 +-
.../io/helidon/metrics/WeightedSnapshot.java | 99 +--
.../io/helidon/metrics/WrappedSnapshot.java | 77 +-
.../metrics/src/main/java/module-info.java | 3 +-
.../helidon/metrics/RegistryFactoryTest.java | 14 +-
.../java/io/helidon/metrics/RegistryTest.java | 35 +-
metrics/pom.xml | 1 -
microprofile/metrics-feature/pom.xml | 129 ++++
.../metrics/feature/feature/package-info.java | 20 +
.../src/main/java/module-info.java | 29 +
microprofile/metrics-micrometer/pom.xml | 109 +++
.../metrics/microprofile/package-info.java | 20 +
.../src/main/java/module-info.java | 34 +
microprofile/metrics/pom.xml | 16 +
.../microprofile/metrics/BaseRegistry.java | 263 +++++++
.../metrics/GlobalTagsHelper.java | 130 ++++
.../metrics/InterceptorConcurrentGauge.java | 75 --
.../metrics/InterceptorMetered.java | 72 --
.../metrics/InterceptorSimplyTimed.java | 79 --
.../InterceptorSyntheticRestRequest.java | 6 +-
.../metrics/MetricsCdiExtension.java | 63 +-
.../metrics/MetricsObserveProvider.java.save | 89 +++
.../microprofile/metrics/MpCounter.java | 49 ++
.../metrics/MpFunctionCounter.java | 97 +++
.../helidon/microprofile/metrics/MpGauge.java | 92 +++
.../microprofile/metrics/MpHistogram.java | 61 ++
.../microprofile/metrics/MpMetric.java | 43 ++
.../microprofile/metrics/MpMetricId.java | 101 +++
.../metrics/MpMetricRegistry.java | 700 ++++++++++++++++++
.../metrics/MpMetricsFeature.java.save | 146 ++++
.../metrics/MpRegistryFactory.java.save | 189 +++++
.../microprofile/metrics/MpSnapshot.java | 68 ++
.../helidon/microprofile/metrics/MpTags.java | 216 ++++++
.../helidon/microprofile/metrics/MpTimer.java | 98 +++
.../metrics/PrometheusFormatter.java | 296 ++++++++
.../metrics/SyntheticRestRequestWorkItem.java | 16 +-
.../metrics/src/main/java/module-info.java | 17 +-
.../metrics/HelloWorldAsyncResponseTest.java | 10 +-
...ldRestEndpointSimpleTimerDisabledTest.java | 2 +-
.../microprofile/metrics/HelloWorldTest.java | 2 +-
.../microprofile/metrics/MetricsMatcher.java | 59 ++
.../metrics/MetricsMpServiceTest.java | 4 +-
.../microprofile/metrics/MpFeatureTest.java | 62 ++
.../microprofile/metrics/TestCounters.java | 97 +++
.../microprofile/metrics/TestFormatter.java | 191 +++++
.../metrics/TestGlobalTagHelper.java | 92 +++
.../microprofile/metrics/TestHistograms.java | 76 ++
.../microprofile/metrics/TestTimers.java | 92 +++
.../metrics/TestVetoedResource.java | 24 +-
.../nima/observe/metrics/MetricsFeature.java | 2 +-
81 files changed, 4449 insertions(+), 1580 deletions(-)
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java
create mode 100644 metrics/api/src/main/java/io/helidon/metrics/api/spi/MetricFactory.java
create mode 100644 metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetricFactory.java
create mode 100644 metrics/metrics/src/main/java/io/helidon/metrics/HelidonSnapshot.java
create mode 100644 microprofile/metrics-feature/pom.xml
create mode 100644 microprofile/metrics-feature/src/main/java/io/helidon/microprofile/metrics/feature/feature/package-info.java
create mode 100644 microprofile/metrics-feature/src/main/java/module-info.java
create mode 100644 microprofile/metrics-micrometer/pom.xml
create mode 100644 microprofile/metrics-micrometer/src/main/java/io/helidon/metrics/microprofile/package-info.java
create mode 100644 microprofile/metrics-micrometer/src/main/java/module-info.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/BaseRegistry.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/GlobalTagsHelper.java
delete mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java
delete mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java
delete mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsObserveProvider.java.save
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpCounter.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpFunctionCounter.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpGauge.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpHistogram.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetric.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricId.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricRegistry.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricsFeature.java.save
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpRegistryFactory.java.save
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpSnapshot.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTags.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTimer.java
create mode 100644 microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/PrometheusFormatter.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMatcher.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MpFeatureTest.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestCounters.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestFormatter.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestGlobalTagHelper.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestHistograms.java
create mode 100644 microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestTimers.java
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/AbstractRegistry.java b/metrics/api/src/main/java/io/helidon/metrics/api/AbstractRegistry.java
index 8b54c788e96..e48faaf2f60 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/AbstractRegistry.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/AbstractRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -22,23 +22,19 @@
import java.util.Optional;
import java.util.SortedMap;
import java.util.SortedSet;
-import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
-import org.eclipse.microprofile.metrics.ConcurrentGauge;
+import io.helidon.metrics.api.spi.MetricFactory;
+
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Meter;
import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricFilter;
import org.eclipse.microprofile.metrics.MetricID;
-import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
-import org.eclipse.microprofile.metrics.SimpleTimer;
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.Timer;
@@ -53,27 +49,24 @@ public abstract class AbstractRegistry implements Registry {
private static final Tag[] NO_TAGS = new Tag[0];
- private final MetricRegistry.Type type;
-
- private final Map> metricFactories = prepareMetricFactories();
+ private final String scope;
private final MetricStore metricStore;
+ private final MetricFactory metricFactory;
/**
* Create a registry of a certain type.
*
- * @param type Registry type.
+ * @param scope Registry type.
* @param registrySettings registry settings which influence this registry
+ * @param metricFactory metric factory to use in creating new metrics
*/
- protected AbstractRegistry(Type type,
- RegistrySettings registrySettings) {
- this.type = type;
- this.metricStore = MetricStore.create(registrySettings,
- prepareMetricFactories(),
- this::createGauge,
- this::createGauge,
- type,
- this::toImpl);
+ protected AbstractRegistry(String scope,
+ RegistrySettings registrySettings,
+ MetricFactory metricFactory) {
+ this.scope = scope;
+ this.metricStore = MetricStore.create(registrySettings, metricFactory, scope);
+ this.metricFactory = metricFactory;
}
/**
@@ -87,21 +80,6 @@ public static boolean isMarkedAsDeleted(Metric metric) {
&& ((HelidonMetric) metric).isDeleted();
}
- @Override
- public T register(String name, T metric) throws IllegalArgumentException {
- return metricStore.register(name, metric);
- }
-
- @Override
- public T register(Metadata metadata, T metric) throws IllegalArgumentException {
- return register(metadata, metric, NO_TAGS);
- }
-
- @Override
- public T register(Metadata metadata, T metric, Tag... tags) throws IllegalArgumentException {
- return metricStore.register(metadata, metric, tags);
- }
-
@Override
public Optional find(String name) {
return Optional.ofNullable(metricStore.untaggedOrFirstMetricInstance(name));
@@ -182,37 +160,7 @@ public Histogram getHistogram(MetricID metricID) {
return getMetric(metricID, Histogram.class);
}
- @Override
- public Meter meter(String name) {
- return meter(name, NO_TAGS);
- }
-
- @Override
- public Meter meter(Metadata metadata) {
- return meter(metadata, NO_TAGS);
- }
-
- @Override
- public Meter meter(String name, Tag... tags) {
- return metricStore.getOrRegisterMetric(name, Meter.class, tags);
- }
-
- @Override
- public Meter meter(Metadata metadata, Tag... tags) {
- return metricStore.getOrRegisterMetric(metadata, Meter.class, tags);
- }
-
- @Override
- public Meter meter(MetricID metricID) {
- return metricStore.getOrRegisterMetric(metricID, Meter.class);
- }
-
- @Override
- public Meter getMeter(MetricID metricID) {
- return getMetric(metricID, Meter.class);
- }
-
- @Override
+ @Override
public Timer timer(String name) {
return timer(name, NO_TAGS);
}
@@ -242,66 +190,6 @@ public Timer getTimer(MetricID metricID) {
return getMetric(metricID, Timer.class);
}
- @Override
- public ConcurrentGauge concurrentGauge(String name) {
- return concurrentGauge(name, NO_TAGS);
- }
-
- @Override
- public ConcurrentGauge concurrentGauge(Metadata metadata) {
- return concurrentGauge(metadata, NO_TAGS);
- }
-
- @Override
- public ConcurrentGauge concurrentGauge(String name, Tag... tags) {
- return metricStore.getOrRegisterMetric(name, ConcurrentGauge.class, tags);
- }
-
- @Override
- public ConcurrentGauge concurrentGauge(Metadata metadata, Tag... tags) {
- return metricStore.getOrRegisterMetric(metadata, ConcurrentGauge.class, tags);
- }
-
- @Override
- public ConcurrentGauge concurrentGauge(MetricID metricID) {
- return metricStore.getOrRegisterMetric(metricID, ConcurrentGauge.class);
- }
-
- @Override
- public ConcurrentGauge getConcurrentGauge(MetricID metricID) {
- return getMetric(metricID, ConcurrentGauge.class);
- }
-
- @Override
- public SimpleTimer simpleTimer(String name) {
- return simpleTimer(name, NO_TAGS);
- }
-
- @Override
- public SimpleTimer simpleTimer(Metadata metadata) {
- return simpleTimer(metadata, NO_TAGS);
- }
-
- @Override
- public SimpleTimer simpleTimer(String name, Tag... tags) {
- return metricStore.getOrRegisterMetric(name, SimpleTimer.class, tags);
- }
-
- @Override
- public SimpleTimer simpleTimer(Metadata metadata, Tag... tags) {
- return metricStore.getOrRegisterMetric(metadata, SimpleTimer.class, tags);
- }
-
- @Override
- public SimpleTimer getSimpleTimer(MetricID metricID) {
- return getMetric(metricID, SimpleTimer.class);
- }
-
- @Override
- public SimpleTimer simpleTimer(MetricID metricID) {
- return metricStore.getOrRegisterMetric(metricID, SimpleTimer.class);
- }
-
@Override
public Gauge gauge(String name, T object, Function func, Tag... tags) {
return metricStore.getOrRegisterGauge(name, object, func, tags);
@@ -404,16 +292,6 @@ public SortedMap getHistograms(MetricFilter filter) {
return metricStore.getSortedMetrics(filter, Histogram.class);
}
- @Override
- public SortedMap getMeters() {
- return getMeters(MetricFilter.ALL);
- }
-
- @Override
- public SortedMap getMeters(MetricFilter filter) {
- return metricStore.getSortedMetrics(filter, Meter.class);
- }
-
@Override
public SortedMap getTimers() {
return getTimers(MetricFilter.ALL);
@@ -424,26 +302,6 @@ public SortedMap getTimers(MetricFilter filter) {
return metricStore.getSortedMetrics(filter, Timer.class);
}
- @Override
- public SortedMap getConcurrentGauges() {
- return getConcurrentGauges(MetricFilter.ALL);
- }
-
- @Override
- public SortedMap getConcurrentGauges(MetricFilter filter) {
- return metricStore.getSortedMetrics(filter, ConcurrentGauge.class);
- }
-
- @Override
- public SortedMap getSimpleTimers() {
- return getSimpleTimers(MetricFilter.ALL);
- }
-
- @Override
- public SortedMap getSimpleTimers(MetricFilter filter) {
- return metricStore.getSortedMetrics(filter, SimpleTimer.class);
- }
-
@Override
public Map getMetadata() {
return Collections.unmodifiableMap(metricStore.metadata());
@@ -494,13 +352,13 @@ public void update(RegistrySettings registrySettings) {
*
* @return The type.
*/
- public String type() {
- return type.getName();
+ public String scope() {
+ return scope;
}
@Override
- public Type getType() {
- return type;
+ public String getScope() {
+ return scope;
}
/**
@@ -514,7 +372,7 @@ public boolean empty() {
@Override
public String toString() {
- return type() + ": " + metricStore.metrics().size() + " metrics";
+ return scope() + ": " + metricStore.metrics().size() + " metrics";
}
/**
@@ -526,23 +384,6 @@ public Stream stream() {
return metricStore.stream();
}
- /**
- * Creates a new instance of an implementation wrapper around the indicated metric.
- *
- * @param metadata {@code Metadata} for the metric
- * @param metric the existing metric to be wrapped by the impl
- * @return new wrapper implementation around the specified metric instance
- */
- protected abstract HelidonMetric toImpl(Metadata metadata, Metric metric);
-
- /**
- * Provides a map from MicroProfile metric type to a factory which creates a concrete metric instance of the MP metric type
- * which also extends the implementation metric base class for the concrete implementation (e.g., no-op or full-featured).
- *
- * @return map from each MicroProfile metric type to the correspondingfactory method
- */
- protected abstract Map> prepareMetricFactories();
-
// -- Package private -----------------------------------------------------
/**
* Returns a list of metric IDs given a metric name.
@@ -588,39 +429,35 @@ protected Gauge createGauge(Metadata metadata, T object
// -- Private methods -----------------------------------------------------
- private static boolean enforceConsistentMetadataType(Metadata existingMetadata, MetricType newType, Tag... tags) {
- if (!existingMetadata.getTypeRaw().equals(newType)) {
- throw new IllegalArgumentException("Attempting to register a new metric "
- + new MetricID(existingMetadata.getName(), tags)
- + " of type "
- + newType.toString()
- + " found pre-existing metadata with conflicting type "
- + existingMetadata.getTypeRaw().toString());
- }
- return true;
- }
+// private static boolean enforceConsistentMetadataType(Metadata existingMetadata, MetricType newType, Tag... tags) {
+// if (!existingMetadata.getTypeRaw().equals(newType)) {
+// throw new IllegalArgumentException("Attempting to register a new metric "
+// + new MetricID(existingMetadata.getName(), tags)
+// + " of type "
+// + newType.toString()
+// + " found pre-existing metadata with conflicting type "
+// + existingMetadata.getTypeRaw().toString());
+// }
+// return true;
+// }
/**
- * Infers the {@link MetricType} from a provided candidate type and a metric instance.
+ * Infers the specific subtype of {@link Metric} from a provided metric instance.
*
- * @param candidateType type of metric to use if not invalid; typically computed from an existing metric
* @param metric the metric for which to derive the metric type
- * @return the {@code MetricType} of the metric
+ * @return the specific subtype of {@code Metric} of the provided metric instance
*/
- protected static MetricType deriveType(MetricType candidateType, Metric metric) {
- if (candidateType != MetricType.INVALID) {
- return candidateType;
- }
+ protected static Class extends Metric> deriveType(Metric metric) {
/*
* A metric could be passed as a lambda, in which case neither
* MetricType.from() nor, for example, Counter.class.isAssignableFrom,
* works. Check each specific metric class using instanceof.
*/
- return Stream.of(Counter.class, Gauge.class, Histogram.class, Meter.class, Timer.class, ConcurrentGauge.class)
+ return (RegistryFactory.METRIC_TYPES).stream()
.filter(clazz -> clazz.isInstance(metric))
- .map(MetricType::from)
.findFirst()
- .orElse(MetricType.INVALID);
+ .orElseThrow(() -> new IllegalArgumentException("Cannot map metric of type " + metric.getClass().getName()
+ + " to one of " + RegistryFactory.METRIC_TYPES));
}
/**
@@ -628,16 +465,16 @@ protected static MetricType deriveType(MetricType candidateType, Metric metric)
*
* @return map from MicroProfile metric type to factory functions.
*/
- protected Map> metricFactories() {
- return metricFactories;
+ protected MetricFactory metricFactory() {
+ return metricFactory;
}
- /**
- * Prepares the map from Java types of implementation metrics to the corresponding {@link MetricType}.
- *
- * @return prepared map for a given metrics implementation
- */
- protected abstract Map, MetricType> prepareMetricToTypeMap();
+// /**
+// * Prepares the map from Java types of implementation metrics to the corresponding {@link MetricType}.
+// *
+// * @return prepared map for a given metrics implementation
+// */
+// protected abstract Map, MetricType> prepareMetricToTypeMap();
/**
* Gauge factories based on either functions or suppliers.
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSample.java b/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSample.java
index 0fe9e7f1859..793a4896f8e 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSample.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -20,7 +20,7 @@
* Base implementation of {@link Sample.Labeled}.
*/
public class LabeledSample implements Sample.Labeled {
- private final long value;
+ private final double value;
private final String label;
private final long timestamp;
@@ -31,14 +31,14 @@ public class LabeledSample implements Sample.Labeled {
* @param label label
* @param timestamp timestamp
*/
- protected LabeledSample(long value, String label, long timestamp) {
+ protected LabeledSample(double value, String label, long timestamp) {
this.value = value;
this.label = label;
this.timestamp = timestamp;
}
@Override
- public long value() {
+ public double value() {
return value;
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSnapshot.java b/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSnapshot.java
index 7c478bf9e4d..b9cdabbc1be 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSnapshot.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/LabeledSnapshot.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -23,21 +23,6 @@
* Internal interface prescribing minimum behavior of a snapshot needed to produce output.
*/
public interface LabeledSnapshot {
- /**
- * Value of a specific quantile.
- *
- * @param quantile quantile to get value for
- * @return derived value of the quantile
- */
- Derived value(double quantile);
-
- /**
- * Median value.
- *
- * @return median
- */
- Derived median();
-
/**
* Maximal value.
*
@@ -45,13 +30,6 @@ public interface LabeledSnapshot {
*/
Labeled max();
- /**
- * Minimal value.
- *
- * @return min
- */
- Labeled min();
-
/**
* Mean value.
*
@@ -60,44 +38,9 @@ public interface LabeledSnapshot {
Derived mean();
/**
- * Standard deviation.
- *
- * @return standard deviation
- */
- Derived stdDev();
-
- /**
- * 75th percentile value.
- *
- * @return 75th percentile value
- */
- Derived sample75thPercentile();
-
- /**
- * 95th percentile value.
- *
- * @return 95th percentile value
- */
- Derived sample95thPercentile();
-
- /**
- * 98th percentile value.
- *
- * @return 98th percentile value
- */
- Derived sample98thPercentile();
-
- /**
- * 99th percentile value.
- *
- * @return 99th percentile value
- */
- Derived sample99thPercentile();
-
- /**
- * 99.9 percentile value.
+ * Number of values represented by the snapshot.
*
- * @return 99.9 percentile value
+ * @return number of values
*/
- Derived sample999thPercentile();
+ long size();
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricStore.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricStore.java
index 4535679ab39..1b2f9327f5d 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricStore.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricStore.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -31,21 +31,24 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import io.helidon.metrics.api.spi.MetricFactory;
+
+import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricFilter;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
/**
* Abstracts the multiple data stores used for holding metrics information and the various ways of accessing and updating them.
@@ -64,39 +67,25 @@ class MetricStore {
private final Map allMetadata = new ConcurrentHashMap<>(); // metric name -> metadata
private volatile RegistrySettings registrySettings;
- private final Map> metricFactories;
- private final AbstractRegistry.GaugeFactory.SupplierBased supplierBasedGaugeFactory;
- private final AbstractRegistry.GaugeFactory.FunctionBased functionBasedGaugeFactory;
- private final MetricRegistry.Type registryType;
- private final BiFunction toImpl;
+ private final MetricFactory metricFactory;
+ private final MetricFactory noOpMetricFactory = new NoOpMetricFactory();
+ private final String scope;
static MetricStore create(RegistrySettings registrySettings,
- Map> metricFactories,
- AbstractRegistry.GaugeFactory.SupplierBased supplierBasedGaugeFactory,
- AbstractRegistry.GaugeFactory.FunctionBased functionBasedGaugeFactory,
- MetricRegistry.Type registryType,
- BiFunction toImpl) {
+ MetricFactory metricFactory,
+ String scope) {
return new MetricStore(registrySettings,
- metricFactories,
- supplierBasedGaugeFactory,
- functionBasedGaugeFactory,
- registryType,
- toImpl);
+ metricFactory,
+ scope);
}
private MetricStore(RegistrySettings registrySettings,
- Map> metricFactories,
- AbstractRegistry.GaugeFactory.SupplierBased supplierBasedGaugeFactory,
- AbstractRegistry.GaugeFactory.FunctionBased functionBasedGaugeFactory,
- MetricRegistry.Type registryType,
- BiFunction toImpl) {
+ MetricFactory metricFactory,
+ String scope) {
this.registrySettings = registrySettings;
- this.metricFactories = metricFactories;
- this.supplierBasedGaugeFactory = supplierBasedGaugeFactory;
- this.functionBasedGaugeFactory = functionBasedGaugeFactory;
- this.registryType = registryType;
- this.toImpl = toImpl;
+ this.metricFactory = metricFactory;
+ this.scope = scope;
}
void update(RegistrySettings registrySettings) {
@@ -108,7 +97,7 @@ U getOrRegisterMetric(MetricID metricID, Class clazz) {
clazz,
() -> allMetrics.get(metricID),
() -> metricID,
- () -> getConsistentMetadataLocked(metricID.getName(), MetricType.from(clazz)));
+ () -> getConsistentMetadataLocked(metricID.getName()));
}
U getOrRegisterMetric(String metricName, Class clazz, Tag... tags) {
@@ -116,41 +105,40 @@ U getOrRegisterMetric(String metricName, Class clazz, Tag.
clazz,
() -> getMetricLocked(metricName, tags),
() -> new MetricID(metricName, tags),
- () -> getConsistentMetadataLocked(metricName, MetricType.from(clazz)));
+ () -> getConsistentMetadataLocked(metricName));
}
U getOrRegisterMetric(Metadata newMetadata, Class clazz, Tag... tags) {
+ Class extends Metric> newBaseType = baseMetricClass(clazz);
return writeAccess(() -> {
HelidonMetric metric = getMetricLocked(newMetadata.getName(), tags);
if (metric == null) {
- Metadata metadataToUse = newMetadata.getTypeRaw().equals(MetricType.INVALID)
- ? Metadata.builder(newMetadata).withType(MetricType.from(clazz)).build()
- : newMetadata;
- Metadata metadata = getConsistentMetadataLocked(metadataToUse);
- metric = registerMetricLocked(new MetricID(metadata.getName(), tags),
- createEnabledAwareMetric(clazz, metadata));
+ metric = registerMetricLocked(new MetricID(newMetadata.getName(), tags),
+ createEnabledAwareMetric(clazz, newMetadata));
} else {
+ if (!baseMetricClass(metric.getClass()).isAssignableFrom(newBaseType)) {
+ throw new IllegalArgumentException("Attempt to register new metric of type " + clazz.getName()
+ + " when previously-registered metric " + newMetadata.getName() + Arrays.asList(tags)
+ + " is of incompatible type " + metric.getClass());
+ }
enforceConsistentMetadata(metric.metadata(), newMetadata);
}
- return toType(metric, clazz);
+ return clazz.cast(metric);
});
}
Gauge getOrRegisterGauge(String name, T object, Function func, Tag... tags) {
return getOrRegisterGauge(() -> getMetricLocked(name, tags),
- () -> getConsistentMetadataLocked(name, MetricType.GAUGE),
+ () -> getConsistentMetadataLocked(name),
() -> new MetricID(name, tags),
- (Metadata metadata) -> functionBasedGaugeFactory.createGauge(metadata,
- object,
- func));
+ (Metadata metadata) -> metricFactory.gauge(scope, metadata, object, func, tags));
}
Gauge getOrRegisterGauge(String name, Supplier valueSupplier, Tag... tags) {
return getOrRegisterGauge(() -> getMetricLocked(name, tags),
- () -> getConsistentMetadataLocked(name, MetricType.GAUGE),
+ () -> getConsistentMetadataLocked(name),
() -> new MetricID(name, tags),
- (Metadata metadata) -> supplierBasedGaugeFactory.createGauge(metadata,
- valueSupplier));
+ (Metadata metadata) -> metricFactory.gauge(scope, metadata, valueSupplier, tags));
}
Gauge getOrRegisterGauge(Metadata newMetadata,
@@ -160,9 +148,7 @@ Gauge getOrRegisterGauge(Metadata newMetadata,
return getOrRegisterGauge(() -> getMetricLocked(newMetadata.getName(), tags),
() -> getConsistentMetadataLocked(newMetadata),
() -> new MetricID(newMetadata.getName(), tags),
- (Metadata metadata) -> functionBasedGaugeFactory.createGauge(metadata,
- object,
- valueFunction));
+ (Metadata metadata) -> metricFactory.gauge(scope, newMetadata, object, valueFunction, tags));
}
Gauge getOrRegisterGauge(Metadata newMetadata,
@@ -172,25 +158,34 @@ Gauge getOrRegisterGauge(Metadata newMetadata,
return getOrRegisterGauge(() -> getMetricLocked(metricName, tags),
() -> getConsistentMetadataLocked(newMetadata),
() -> new MetricID(metricName, tags),
- (Metadata metadata) -> supplierBasedGaugeFactory.createGauge(metadata,
- valueSupplier));
+ (Metadata metadata) -> metricFactory.gauge(scope, newMetadata, valueSupplier, tags));
}
Gauge getOrRegisterGauge(MetricID metricID, T object, Function valueFunction) {
return getOrRegisterGauge(() -> allMetrics.get(metricID),
() -> allMetadata.get(metricID.getName()),
() -> metricID,
- (Metadata metadata) -> functionBasedGaugeFactory.createGauge(metadata,
- object,
- valueFunction));
+ (Metadata metadata) -> metricFactory.gauge(scope, metadata, object, valueFunction));
}
Gauge getOrRegisterGauge(MetricID metricID, Supplier valueSupplier) {
return getOrRegisterGauge(() -> allMetrics.get(metricID),
() -> allMetadata.get(metricID.getName()),
() -> metricID,
- (Metadata metadata) -> supplierBasedGaugeFactory.createGauge(metadata,
- valueSupplier));
+ (Metadata metadata) -> metricFactory.gauge(scope, metadata, valueSupplier));
+ }
+
+ private static Class extends Metric> baseMetricClass(Class> clazz) {
+
+ for (Class extends Metric> baseClass : RegistryFactory.METRIC_TYPES) {
+ if (baseClass.isAssignableFrom(clazz)) {
+ return baseClass;
+ }
+ }
+ throw new IllegalArgumentException("Unable to map metric type "
+ + clazz.getName()
+ + " to one of "
+ + RegistryFactory.METRIC_TYPES);
}
private Gauge getOrRegisterGauge(Supplier metricFinder,
@@ -208,23 +203,6 @@ private Gauge getOrRegisterGauge(Supplier m
});
}
- U register(Metadata metadata, U metric, Tag... tags) {
- return writeAccess(() -> {
- final String metricName = metadata.getName();
- getConsistentMetadataLocked(metadata);
- registerMetricLocked(new MetricID(metricName, tags), toImpl.apply(metadata, metric));
- return metric;
- });
- }
-
- U register(String name, U metric) {
- return writeAccess(() -> {
- Metadata metadata = getConsistentMetadataLocked(name, toType(metric));
- registerMetricLocked(new MetricID(name), toImpl.apply(metadata, metric));
- return metric;
- });
- }
-
boolean remove(MetricID metricID) {
return writeAccess(() -> {
final List metricIDsForName = allMetricIDsByName.get(metricID.getName());
@@ -387,10 +365,9 @@ private U getOrRegisterMetric(String metricName,
HelidonMetric metric = metricFactory.get();
if (metric == null) {
try {
- MetricType metricType = MetricType.from(clazz);
Metadata metadata = metadataFactory.get();
if (metadata == null) {
- metadata = registerMetadataLocked(metricName, metricType);
+ metadata = registerMetadataLocked(metricName);
}
metric = registerMetricLocked(metricIDFactory.get(),
createEnabledAwareMetric(clazz, metadata));
@@ -398,7 +375,7 @@ private U getOrRegisterMetric(String metricName,
throw new RuntimeException("Error attempting to register new metric " + metricIDFactory.get(), e);
}
}
- return toType(metric, clazz);
+ return clazz.cast(metric);
});
}
@@ -423,18 +400,10 @@ private HelidonMetric registerMetricLocked(MetricID metricID, HelidonMetric metr
return metric;
}
- private Metadata getConsistentMetadataLocked(String metricName, MetricType metricType) {
+ private Metadata getConsistentMetadataLocked(String metricName) {
Metadata result = allMetadata.get(metricName);
- if (result != null) {
- if (result.getTypeRaw() != metricType) {
- throw new IllegalArgumentException("Existing metadata has type "
- + result.getType()
- + " but "
- + metricType
- + " was requested");
- }
- } else {
- result = registerMetadataLocked(metricName, metricType);
+ if (result == null) {
+ result = registerMetadataLocked(metricName);
}
return result;
}
@@ -449,10 +418,9 @@ private Metadata getConsistentMetadataLocked(Metadata newMetadata) {
return newMetadata;
}
- private Metadata registerMetadataLocked(String metricName, MetricType metricType) {
+ private Metadata registerMetadataLocked(String metricName) {
return registerMetadataLocked(Metadata.builder()
.withName(metricName)
- .withType(metricType)
.withUnit(MetricUnits.NONE)
.build());
}
@@ -464,34 +432,43 @@ private Metadata registerMetadataLocked(Metadata metadata) {
private HelidonMetric createEnabledAwareMetric(Class clazz, Metadata metadata) {
String metricName = metadata.getName();
- MetricType metricType = MetricType.from(clazz);
- return registrySettings.isMetricEnabled(metricName)
- ? metricFactories.get(MetricType.from(clazz)).apply(registryType.getName(), metadata)
- : NoOpMetricRegistry.noOpMetricFactories().get(metricType).apply(metricName, metadata);
+ Class extends Metric> baseClass = baseMetricClass(clazz);
+ Metric result;
+ MetricFactory factoryToUse = registrySettings.isMetricEnabled(metricName) ? metricFactory : noOpMetricFactory;
+ if (baseClass.isAssignableFrom(Counter.class)) {
+ result = factoryToUse.counter(scope, metadata);
+ } else if (baseClass.isAssignableFrom(Histogram.class)) {
+ result = factoryToUse.summary(scope, metadata);
+ } else if (baseClass.isAssignableFrom(Timer.class)) {
+ result = factoryToUse.timer(scope, metadata);
+ } else {
+ throw new IllegalArgumentException("Cannot identify correct metric type for " + clazz.getName());
+ }
+ return (HelidonMetric) result;
}
private HelidonMetric createEnabledAwareGauge(Metadata metadata,
Function> gaugeFactory) {
String metricName = metadata.getName();
- return registrySettings.isMetricEnabled(metricName)
- ? (HelidonMetric) gaugeFactory.apply(metadata)
- : NoOpMetricRegistry.noOpMetricFactories().get(MetricType.GAUGE).apply(metricName, metadata);
- }
-
- private U toType(T m1, Class clazz) {
- MetricType type1 = toType(m1);
- MetricType type2 = MetricType.from(clazz);
- if (type1 == type2) {
- return clazz.cast(m1);
- }
- throw new IllegalArgumentException("Metric types " + type1.toString()
- + " and " + type2.toString() + " do not match");
- }
-
- private MetricType toType(Metric metric) {
- Class extends Metric> clazz = toMetricClass(metric);
- return MetricType.from(clazz == null ? metric.getClass() : clazz);
- }
+ return (HelidonMetric) (registrySettings.isMetricEnabled(metricName)
+ ? gaugeFactory.apply(metadata)
+ : noOpMetricFactory.gauge(scope, metadata, null));
+ }
+
+// private U toType(T m1, Class clazz) {
+// MetricType type1 = toType(m1);
+// MetricType type2 = MetricType.from(clazz);
+// if (type1 == type2) {
+// return clazz.cast(m1);
+// }
+// throw new IllegalArgumentException("Metric types " + type1.toString()
+// + " and " + type2.toString() + " do not match");
+// }
+//
+// private MetricType toType(Metric metric) {
+// Class extends Metric> clazz = toMetricClass(metric);
+// return MetricType.from(clazz == null ? metric.getClass() : clazz);
+// }
private static Class extends Metric> toMetricClass(T metric) {
// Find subtype of Metric, needed for user-defined metrics
@@ -555,8 +532,6 @@ static boolean metadataMatches(T a, U b
return false;
}
return a.getName().equals(b.getName())
- && a.getTypeRaw().equals(b.getTypeRaw())
- && a.getDisplayName().equals(b.getDisplayName())
&& Objects.equals(a.getDescription(), b.getDescription())
&& Objects.equals(a.getUnit(), b.getUnit());
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettings.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettings.java
index 0cb28455810..fecfe7a290d 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettings.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettings.java
@@ -21,8 +21,6 @@
import io.helidon.config.metadata.Configured;
import io.helidon.config.metadata.ConfiguredOption;
-import org.eclipse.microprofile.metrics.MetricRegistry;
-
/**
* Settings which control behavior for metrics overall.
*
@@ -91,21 +89,21 @@ static Builder builder(MetricsSettings metricsSettings) {
BaseMetricsSettings baseMetricsSettings();
/**
- * Reports whether the specified metric is enabled in the indicated registry type.
+ * Reports whether the specified metric is enabled in the indicated registry scope.
*
- * @param registryType which registry type to check
+ * @param scope which scope to check
* @param metricName name of the metric to check
* @return true if metrics overall is enabled and if the metric is enabled in the specified registry; false otherwise
*/
- boolean isMetricEnabled(MetricRegistry.Type registryType, String metricName);
+ boolean isMetricEnabled(String scope, String metricName);
/**
- * Returns the {@link RegistrySettings} for the indicated registry type.
+ * Returns the {@link RegistrySettings} for the indicated registry scope.
*
- * @param registryType registry type of interest
- * @return {@code RegistrySettings} for the selected type
+ * @param scope scope of interest
+ * @return {@code RegistrySettings} for the selected scope
*/
- RegistrySettings registrySettings(MetricRegistry.Type registryType);
+ RegistrySettings registrySettings(String scope);
/**
* Returns the global tags, if any.
@@ -207,16 +205,16 @@ interface Builder extends io.helidon.common.Builder {
Builder baseMetricsSettings(BaseMetricsSettings.Builder baseMetricsSettingsBuilder);
/**
- * Sets the registry settings for the specified registry type.
+ * Sets the registry settings for the specified scope.
*
- * @param registryType type of registry for which to assign settings
+ * @param scope scope of registry for which to assign settings
* @param registrySettings assigned registry settings
* @return updated builder
*/
@ConfiguredOption(key = REGISTRIES_CONFIG_KEY,
kind = ConfiguredOption.Kind.MAP,
type = RegistrySettings.class)
- Builder registrySettings(MetricRegistry.Type registryType, RegistrySettings registrySettings);
+ Builder registrySettings(String scope, RegistrySettings registrySettings);
/**
* Sets the global tags to be applied to all metrics.
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettingsImpl.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettingsImpl.java
index 1ec56a36543..6702e9e6b4a 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettingsImpl.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsSettingsImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -17,25 +17,26 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import io.helidon.config.Config;
import io.helidon.config.ConfigValue;
-import org.eclipse.microprofile.metrics.MetricRegistry;
-
class MetricsSettingsImpl implements MetricsSettings {
private static final RegistrySettings DEFAULT_REGISTRY_SETTINGS = RegistrySettings.create();
+ private static final Set PREDEFINED_SCOPES = Set.of(Registry.APPLICATION_SCOPE,
+ Registry.BASE_SCOPE,
+ Registry.VENDOR_SCOPE);
+
private final boolean isEnabled;
private final KeyPerformanceIndicatorMetricsSettings kpiMetricsSettings;
private final BaseMetricsSettings baseMetricsSettings;
- private final EnumMap registrySettings;
+ private final Map registrySettings;
private final Map globalTags;
private final String appTagValue;
@@ -64,17 +65,17 @@ public BaseMetricsSettings baseMetricsSettings() {
}
@Override
- public boolean isMetricEnabled(MetricRegistry.Type registryType, String metricName) {
+ public boolean isMetricEnabled(String scope, String metricName) {
if (!isEnabled) {
return false;
}
- RegistrySettings registrySettings = this.registrySettings.get(registryType);
+ RegistrySettings registrySettings = this.registrySettings.get(scope);
return registrySettings == null || registrySettings.isMetricEnabled(metricName);
}
@Override
- public RegistrySettings registrySettings(MetricRegistry.Type registryType) {
- return registrySettings.getOrDefault(registryType, DEFAULT_REGISTRY_SETTINGS);
+ public RegistrySettings registrySettings(String scope) {
+ return registrySettings.getOrDefault(scope, DEFAULT_REGISTRY_SETTINGS);
}
@Override
@@ -88,7 +89,7 @@ public String appTagValue() {
}
// For testing and within-package use only
- Map registrySettings() {
+ Map registrySettings() {
return registrySettings;
}
@@ -98,14 +99,14 @@ static class Builder implements MetricsSettings.Builder {
private KeyPerformanceIndicatorMetricsSettings.Builder kpiMetricsSettingsBuilder =
KeyPerformanceIndicatorMetricsSettings.builder();
private BaseMetricsSettings.Builder baseMetricsSettingsBuilder = BaseMetricsSettings.builder();
- private final EnumMap registrySettings = prepareRegistrySettings();
+ private final Map registrySettings = prepareRegistrySettings();
private Map globalTags = Collections.emptyMap();
private String appTagValue;
- private static EnumMap prepareRegistrySettings() {
- EnumMap result = new EnumMap<>(MetricRegistry.Type.class);
- for (MetricRegistry.Type type : MetricRegistry.Type.values()) {
- result.put(type, RegistrySettings.create());
+ private static Map prepareRegistrySettings() {
+ Map result = new HashMap<>();
+ for (String predefinedScope : PREDEFINED_SCOPES) {
+ result.put(predefinedScope, RegistrySettings.create());
}
return result;
}
@@ -119,10 +120,7 @@ protected Builder(MetricsSettings serviceSettings) {
serviceSettings.keyPerformanceIndicatorSettings());
baseMetricsSettingsBuilder = BaseMetricsSettings.builder(serviceSettings.baseMetricsSettings());
- for (MetricRegistry.Type metricRegistryType : MetricRegistry.Type.values()) {
- registrySettings.put(metricRegistryType,
- ((MetricsSettingsImpl) serviceSettings).registrySettings().get(metricRegistryType));
- }
+ registrySettings.putAll(((MetricsSettingsImpl) serviceSettings).registrySettings());
}
@Override
@@ -153,7 +151,7 @@ public Builder config(Config metricsSettingsConfig) {
.ifPresent(this::enabled);
metricsSettingsConfig.get(REGISTRIES_CONFIG_KEY)
- .asList(TypedRegistrySettingsImpl::create)
+ .asList(ScopedRegistrySettingsImpl::create)
.ifPresent(this::addAllTypedRegistrySettings);
metricsSettingsConfig.get(GLOBAL_TAGS_CONFIG_KEY)
@@ -174,8 +172,8 @@ public Builder keyPerformanceIndicatorSettings(
}
@Override
- public MetricsSettings.Builder registrySettings(MetricRegistry.Type registryType, RegistrySettings registrySettings) {
- this.registrySettings.put(registryType, registrySettings);
+ public MetricsSettings.Builder registrySettings(String scope, RegistrySettings registrySettings) {
+ this.registrySettings.put(scope, registrySettings);
return this;
}
@@ -191,10 +189,8 @@ public MetricsSettings.Builder appTagValue(String appTagValue) {
return this;
}
- private void addAllTypedRegistrySettings(List typedRegistrySettingsList) {
- for (TypedRegistrySettingsImpl typedRegistrySettings : typedRegistrySettingsList) {
- registrySettings.put(typedRegistrySettings.registryType, typedRegistrySettings);
- }
+ private void addAllTypedRegistrySettings(List typedRegistrySettingsList) {
+ typedRegistrySettingsList.forEach(settings -> registrySettings.put(settings.scope, settings));
}
private Map globalTagsExpressionToMap(Config globalTagExpression) {
@@ -221,30 +217,28 @@ private Map globalTagsExpressionToMap(Config globalTagExpression
return result;
}
- private static class TypedRegistrySettingsImpl extends RegistrySettingsImpl {
+ private static class ScopedRegistrySettingsImpl extends RegistrySettingsImpl {
- static TypedRegistrySettingsImpl create(Config registrySettingsConfig) {
+ static ScopedRegistrySettingsImpl create(Config registrySettingsConfig) {
RegistrySettingsImpl.Builder builder = RegistrySettingsImpl.builder();
builder.config(registrySettingsConfig);
- ConfigValue typeNameValue = registrySettingsConfig.get(RegistrySettings.Builder.TYPE_CONFIG_KEY)
+ ConfigValue scopeValue = registrySettingsConfig.get(RegistrySettings.Builder.SCOPE_CONFIG_KEY)
.asString();
- if (!typeNameValue.isPresent()) {
- throw new IllegalArgumentException("Missing metric registry type in registry settings: "
+ if (!scopeValue.isPresent()) {
+ throw new IllegalArgumentException("Missing metric registry scope in registry settings: "
+ registrySettingsConfig);
}
- MetricRegistry.Type type = MetricRegistry.Type.valueOf(typeNameValue.get().toUpperCase(Locale.ROOT));
- return new TypedRegistrySettingsImpl(type, builder);
+ return new ScopedRegistrySettingsImpl(scopeValue.get(), builder);
}
- private final MetricRegistry.Type registryType;
+ private final String scope;
- private TypedRegistrySettingsImpl(MetricRegistry.Type registryType, RegistrySettingsImpl.Builder builder) {
+ private ScopedRegistrySettingsImpl(String scope, RegistrySettingsImpl.Builder builder) {
super(builder);
- this.registryType = registryType;
+ this.scope = scope;
}
-
}
}
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java
new file mode 100644
index 00000000000..1da4377a902
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.Supplier;
+
+import io.helidon.metrics.api.spi.MetricFactory;
+
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
+
+class NoOpMetricFactory implements MetricFactory {
+ @Override
+ public Counter counter(String scope, Metadata metadata, Tag... tags) {
+ return NoOpMetricImpl.NoOpCounterImpl.create(scope, metadata, tags);
+ }
+
+ @Override
+ public Timer timer(String scope, Metadata metadata, Tag... tags) {
+ return NoOpMetricImpl.NoOpTimerImpl.create(scope, metadata, tags);
+ }
+
+ @Override
+ public Histogram summary(String scope, Metadata metadata, Tag... tags) {
+ return NoOpMetricImpl.NoOpHistogramImpl.create(scope, metadata, tags);
+ }
+
+ @Override
+ public Gauge gauge(String scope, Metadata metadata, Supplier supplier, Tag... tags) {
+ return NoOpMetricImpl.NoOpGaugeImpl.create(scope, metadata, supplier, tags);
+ }
+
+ @Override
+ public Gauge gauge(String scope, Metadata metadata, T target, Function fn, Tag... tags) {
+ return NoOpMetricImpl.NoOpGaugeImpl.create(scope, metadata, target, fn, tags);
+ }
+}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java
index 24b148ad091..6c033ed6e38 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -18,16 +18,15 @@
import java.io.OutputStream;
import java.time.Duration;
import java.util.concurrent.Callable;
+import java.util.function.Function;
import java.util.function.Supplier;
-import org.eclipse.microprofile.metrics.ConcurrentGauge;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Meter;
-import org.eclipse.microprofile.metrics.SimpleTimer;
import org.eclipse.microprofile.metrics.Snapshot;
+import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.Timer;
/**
@@ -41,11 +40,7 @@ protected NoOpMetricImpl(String registryType, Metadata metadata) {
static class NoOpCounterImpl extends NoOpMetricImpl implements Counter {
- static NoOpCounterImpl create(String registryType, Metadata metadata) {
- return new NoOpCounterImpl(registryType, metadata);
- }
-
- static NoOpCounterImpl create(String registryType, Metadata metadata, Counter counter) {
+ static NoOpCounterImpl create(String registryType, Metadata metadata, Tag... tags) {
return new NoOpCounterImpl(registryType, metadata);
}
@@ -67,69 +62,52 @@ public long getCount() {
}
}
- static class NoOpConcurrentGaugeImpl extends NoOpMetricImpl implements ConcurrentGauge {
+ static class NoOpGaugeImpl extends NoOpMetricImpl implements Gauge {
- static NoOpConcurrentGaugeImpl create(String registryType, Metadata metadata) {
- return new NoOpConcurrentGaugeImpl(registryType, metadata);
- }
+ private final Supplier supplier;
+ private final T target;
+ private final Function fn;
- private NoOpConcurrentGaugeImpl(String registryType, Metadata metadata) {
- super(registryType, metadata);
- }
-
- @Override
- public long getCount() {
- return 0;
+ static NoOpGaugeImpl create(String registryType,
+ Metadata metadata,
+ Supplier supplier,
+ Tag... tags) {
+ return new NoOpGaugeImpl<>(registryType, metadata, supplier);
}
- @Override
- public long getMax() {
- return 0;
+ static NoOpGaugeImpl create(String registryType,
+ Metadata metadata,
+ T target,
+ Function fn,
+ Tag... tags) {
+ return new NoOpGaugeImpl<>(registryType, metadata, target, fn);
}
- @Override
- public long getMin() {
- return 0;
+ private NoOpGaugeImpl(String registryType, Metadata metadata, Supplier supplier) {
+ super(registryType, metadata);
+ this.supplier = supplier;
+ target = null;
+ fn = null;
}
- @Override
- public void inc() {
+ private NoOpGaugeImpl(String registryType, Metadata metadata, T target, Function fn) {
+ super(registryType, metadata);
+ this.target = target;
+ this.fn = fn;
+ supplier = null;
}
@Override
- public void dec() {
- }
- }
-
- static class NoOpGaugeImpl extends NoOpMetricImpl implements Gauge {
- // TODO uncomment above once MP metrics enforces the Number restriction
-
- private final Supplier value;
-
- static NoOpGaugeImpl create(String registryType, Metadata metadata, Gauge metric) {
- // TODO uncomment above once MP metrics enforces the Number restriction
- return new NoOpGaugeImpl<>(registryType, metadata, metric);
+ public N getValue() {
+ return supplier != null ? supplier.get() : fn.apply(target);
}
- static NoOpGaugeImpl create(String registryType, Metadata metadata) {
- // TODO uncomment above once MP metrics enforces the Number restriction
- return new NoOpGaugeImpl<>(registryType, metadata, () -> null);
- }
-
- private NoOpGaugeImpl(String registryType, Metadata metadata, Gauge metric) {
- super(registryType, metadata);
- value = metric::getValue;
- }
- @Override
- public T getValue() {
- return value.get();
- }
}
static class NoOpHistogramImpl extends NoOpMetricImpl implements Histogram {
- static NoOpHistogramImpl create(String registryType, Metadata metadata) {
+ static NoOpHistogramImpl create(String registryType, Metadata metadata, Tag... tags) {
return new NoOpHistogramImpl(registryType, metadata);
}
@@ -162,23 +140,14 @@ public long getSum() {
}
static class NoOpSnapshot extends Snapshot {
- @Override
- public double getValue(double quantile) {
- return 0;
- }
@Override
- public long[] getValues() {
- return new long[0];
- }
-
- @Override
- public int size() {
+ public long size() {
return 0;
}
@Override
- public long getMax() {
+ public double getMax() {
return 0;
}
@@ -188,13 +157,8 @@ public double getMean() {
}
@Override
- public long getMin() {
- return 0;
- }
-
- @Override
- public double getStdDev() {
- return 0;
+ public PercentileValue[] percentileValues() {
+ return new PercentileValue[0];
}
@Override
@@ -206,50 +170,6 @@ static Snapshot snapshot() {
return new NoOpSnapshot();
}
- static class NoOpMeterImpl extends NoOpMetricImpl implements Meter {
-
- static NoOpMeterImpl create(String registryType, Metadata metadata) {
- return new NoOpMeterImpl(registryType, metadata);
- }
-
- private NoOpMeterImpl(String registryType, Metadata metadata) {
- super(registryType, metadata);
- }
-
- @Override
- public void mark() {
- }
-
- @Override
- public void mark(long n) {
- }
-
- @Override
- public long getCount() {
- return 0;
- }
-
- @Override
- public double getFifteenMinuteRate() {
- return 0;
- }
-
- @Override
- public double getFiveMinuteRate() {
- return 0;
- }
-
- @Override
- public double getMeanRate() {
- return 0;
- }
-
- @Override
- public double getOneMinuteRate() {
- return 0;
- }
- }
-
static class NoOpTimerImpl extends NoOpMetricImpl implements Timer {
static class Context implements Timer.Context {
@@ -263,7 +183,7 @@ public void close() {
}
}
- static NoOpTimerImpl create(String registryType, Metadata metadata) {
+ static NoOpTimerImpl create(String registryType, Metadata metadata, Tag... tags) {
return new NoOpTimerImpl(registryType, metadata);
}
@@ -300,26 +220,6 @@ public long getCount() {
return 0;
}
- @Override
- public double getFifteenMinuteRate() {
- return 0;
- }
-
- @Override
- public double getFiveMinuteRate() {
- return 0;
- }
-
- @Override
- public double getMeanRate() {
- return 0;
- }
-
- @Override
- public double getOneMinuteRate() {
- return 0;
- }
-
@Override
public Snapshot getSnapshot() {
return snapshot();
@@ -330,69 +230,4 @@ private static Timer.Context timerContext() {
return new NoOpTimerImpl.Context() {
};
}
-
- static class NoOpSimpleTimerImpl extends NoOpMetricImpl implements SimpleTimer {
-
- static class Context implements SimpleTimer.Context {
- @Override
- public long stop() {
- return 0;
- }
-
- @Override
- public void close() {
- }
- }
-
- static NoOpSimpleTimerImpl create(String registryType, Metadata metadata) {
- return new NoOpSimpleTimerImpl(registryType, metadata);
- }
-
- private NoOpSimpleTimerImpl(String registryType, Metadata metadata) {
- super(registryType, metadata);
- }
-
- @Override
- public void update(Duration duration) {
- }
-
- @Override
- public Duration getMaxTimeDuration() {
- return Duration.ZERO;
- }
-
- @Override
- public Duration getMinTimeDuration() {
- return Duration.ZERO;
- }
-
- @Override
- public T time(Callable event) throws Exception {
- return event.call();
- }
-
- @Override
- public void time(Runnable event) {
- event.run();
- }
-
- @Override
- public SimpleTimer.Context time() {
- return simpleTimerContext();
- }
-
- @Override
- public Duration getElapsedTime() {
- return null;
- }
-
- @Override
- public long getCount() {
- return 0;
- }
- }
-
- private static SimpleTimer.Context simpleTimerContext() {
- return new NoOpSimpleTimerImpl.Context();
- }
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricRegistry.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricRegistry.java
index 09b4e12f602..2fe9f004f3c 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricRegistry.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -16,41 +16,28 @@
package io.helidon.metrics.api;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
-import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.microprofile.metrics.Gauge;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
/**
* Implementation of {@link MetricRegistry} which returns no-op metrics implementations.
*/
class NoOpMetricRegistry extends AbstractRegistry {
- static final Map> NO_OP_METRIC_FACTORIES =
- Map.of(MetricType.COUNTER, NoOpMetricImpl.NoOpCounterImpl::create,
- MetricType.GAUGE, NoOpMetricImpl.NoOpGaugeImpl::create,
- MetricType.HISTOGRAM, NoOpMetricImpl.NoOpHistogramImpl::create,
- MetricType.METERED, NoOpMetricImpl.NoOpMeterImpl::create,
- MetricType.TIMER, NoOpMetricImpl.NoOpTimerImpl::create,
- MetricType.SIMPLE_TIMER, NoOpMetricImpl.NoOpSimpleTimerImpl::create,
- MetricType.CONCURRENT_GAUGE, NoOpMetricImpl.NoOpConcurrentGaugeImpl::create);
-
private static final RegistrySettings REGISTRY_SETTINGS = RegistrySettings.builder().enabled(false).build();
- private NoOpMetricRegistry(MetricRegistry.Type type) {
- super(type, REGISTRY_SETTINGS);
+ private NoOpMetricRegistry(String scope) {
+ super(scope, REGISTRY_SETTINGS, new NoOpMetricFactory());
}
- public static NoOpMetricRegistry create(MetricRegistry.Type type) {
- return new NoOpMetricRegistry(type);
+ public static NoOpMetricRegistry create(String scope) {
+ return new NoOpMetricRegistry(scope);
}
@Override
@@ -78,55 +65,17 @@ public Optional metricsByName(String name) {
return Optional.empty();
}
- @Override
- protected HelidonMetric toImpl(Metadata metadata, Metric metric) {
- String registryTypeName = type();
- MetricType metricType = AbstractRegistry.deriveType(metadata.getTypeRaw(), metric);
- return switch (metricType) {
- case COUNTER -> NoOpMetricImpl.NoOpCounterImpl.create(registryTypeName, metadata);
- case GAUGE -> NoOpMetricImpl.NoOpGaugeImpl.create(registryTypeName, metadata, (Gauge>) metric);
- case HISTOGRAM -> NoOpMetricImpl.NoOpHistogramImpl.create(registryTypeName, metadata);
- case METERED -> NoOpMetricImpl.NoOpMeterImpl.create(registryTypeName, metadata);
- case TIMER -> NoOpMetricImpl.NoOpTimerImpl.create(registryTypeName, metadata);
- case SIMPLE_TIMER -> NoOpMetricImpl.NoOpSimpleTimerImpl.create(registryTypeName, metadata);
- case CONCURRENT_GAUGE -> NoOpMetricImpl.NoOpConcurrentGaugeImpl.create(registryTypeName, metadata);
- case INVALID -> throw new IllegalArgumentException("Unexpected metric type " + metricType
- + ": " + metric.getClass().getName());
- };
- }
-
- @Override
- protected Map> prepareMetricFactories() {
- return noOpMetricFactories();
- }
-
@Override
protected Gauge createGauge(Metadata metadata, T object, Function func) {
- return NoOpMetricImpl.NoOpGaugeImpl.create(type(),
+ return NoOpMetricImpl.NoOpGaugeImpl.create(scope(),
metadata,
() -> func.apply(object));
}
@Override
protected Gauge createGauge(Metadata metadata, Supplier supplier) {
- return NoOpMetricImpl.NoOpGaugeImpl.create(type(),
+ return NoOpMetricImpl.NoOpGaugeImpl.create(scope(),
metadata,
- () -> supplier.get());
+ supplier::get);
}
-
- @Override
- protected Map, MetricType> prepareMetricToTypeMap() {
- return Map.of(NoOpMetricImpl.NoOpConcurrentGaugeImpl.class, MetricType.CONCURRENT_GAUGE,
- NoOpMetricImpl.NoOpCounterImpl.class, MetricType.COUNTER,
- NoOpMetricImpl.NoOpGaugeImpl.class, MetricType.GAUGE,
- NoOpMetricImpl.NoOpHistogramImpl.class, MetricType.HISTOGRAM,
- NoOpMetricImpl.NoOpMeterImpl.class, MetricType.METERED,
- NoOpMetricImpl.NoOpTimerImpl.class, MetricType.TIMER,
- NoOpMetricImpl.NoOpSimpleTimerImpl.class, MetricType.SIMPLE_TIMER);
- }
-
- protected static Map> noOpMetricFactories() {
- return NO_OP_METRIC_FACTORIES;
- }
-
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpRegistryFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpRegistryFactory.java
index 6bc6008aae1..35e8d7b4167 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpRegistryFactory.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpRegistryFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -16,10 +16,8 @@
package io.helidon.metrics.api;
-import java.util.EnumMap;
-import java.util.stream.Stream;
-
-import org.eclipse.microprofile.metrics.MetricRegistry.Type;
+import java.util.HashMap;
+import java.util.Map;
/**
* No-op implementation of {@link RegistryFactory}.
@@ -36,18 +34,19 @@ public static NoOpRegistryFactory create() {
return new NoOpRegistryFactory();
}
- private static final EnumMap NO_OP_REGISTRIES = Stream.of(Type.values())
+ private static final Map NO_OP_REGISTRIES = Registry.BUILT_IN_SCOPES
+ .stream()
.collect(
- () -> new EnumMap<>(Type.class),
- (map, type) -> map.put(type, NoOpMetricRegistry.create(type)),
- EnumMap::putAll);
+ HashMap::new,
+ (map, scope) -> map.put(scope, NoOpMetricRegistry.create(scope)),
+ Map::putAll);
private NoOpRegistryFactory() {
}
@Override
- public Registry getRegistry(Type type) {
- return NO_OP_REGISTRIES.get(type);
+ public Registry getRegistry(String scope) {
+ return NO_OP_REGISTRIES.get(scope);
}
@Override
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Registry.java b/metrics/api/src/main/java/io/helidon/metrics/api/Registry.java
index 94f94400e48..58c86ccd586 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/Registry.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Registry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -18,6 +18,7 @@
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.microprofile.metrics.MetricID;
@@ -27,6 +28,12 @@
* Helidon Metric registry.
*/
public interface Registry extends MetricRegistry {
+
+ /**
+ * Built-in scope names.
+ */
+ Set BUILT_IN_SCOPES = Set.of(BASE_SCOPE, VENDOR_SCOPE, APPLICATION_SCOPE);
+
/**
* Whether a metric is enabled. Metrics can be disabled by name (disables all IDs associated with that name).
*
@@ -74,7 +81,7 @@ public interface Registry extends MetricRegistry {
*
* @return type
*/
- String type();
+ String scope();
/**
* Get all metric IDs for a specified name.
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/RegistryFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/RegistryFactory.java
index 0b2608e4b7f..01d81c54dbf 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/RegistryFactory.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/RegistryFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -16,10 +16,16 @@
package io.helidon.metrics.api;
+import java.util.Set;
+
import io.helidon.config.Config;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricRegistry.Type;
+import org.eclipse.microprofile.metrics.Timer;
/**
* Behavior of a {@code RegistryFactory}, capable of providing metrics registries of various types (application, base, vendor)
@@ -36,6 +42,11 @@
*/
public interface RegistryFactory {
+ /**
+ * Set of specific types of metrics.
+ */
+ Set> METRIC_TYPES = Set.of(Counter.class, Gauge.class, Histogram.class, Timer.class);
+
/**
* Returns a {@code RegistryFactory} according to the default metrics settings.
*
@@ -114,12 +125,12 @@ static RegistryFactory getInstance(ComponentMetricsSettings componentMetricsSett
}
/**
- * Returns a {@link MetricRegistry} instance of the requested type.
+ * Returns a {@link MetricRegistry} instance of the requested scope.
*
- * @param type {@link MetricRegistry.Type} of the registry to be returned
- * @return the {@code MetricRegistry} of the requested type
+ * @param scope scope of the registry to be returned
+ * @return the {@code MetricRegistry} of the requested scope
*/
- Registry getRegistry(Type type);
+ Registry getRegistry(String scope);
/**
* Updates the metrics settings for the {@code RegistryFactory}.
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/RegistrySettings.java b/metrics/api/src/main/java/io/helidon/metrics/api/RegistrySettings.java
index a315d488ac8..646edd5ecc6 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/RegistrySettings.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/RegistrySettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -95,7 +95,7 @@ interface Builder extends io.helidon.common.Builder {
/**
* Config key within the registry's config section identifying which registry type the settings apply to.
*/
- String TYPE_CONFIG_KEY = "type";
+ String SCOPE_CONFIG_KEY = "scope";
/**
* Sets whether the metric type should be enabled.
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/Sample.java b/metrics/api/src/main/java/io/helidon/metrics/api/Sample.java
index ecc32e5da84..ea0c917b859 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/Sample.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/Sample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -48,7 +48,7 @@ static Derived derived(double value) {
* @param value value
* @return a new labeled value if exemplar handling is supported
*/
- static Labeled labeled(long value) {
+ static Labeled labeled(double value) {
return ExemplarServiceManager.isActive()
? new LabeledSample(value, ExemplarServiceManager.exemplarLabel(), System.currentTimeMillis())
: new LabeledSample(value, ExemplarServiceManager.INACTIVE_LABEL, 0);
@@ -100,7 +100,7 @@ interface Labeled extends Sample {
* The value.
* @return value
*/
- long value();
+ double value();
/**
* Value label.
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/spi/MetricFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/spi/MetricFactory.java
new file mode 100644
index 00000000000..b3a0efdae44
--- /dev/null
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/spi/MetricFactory.java
@@ -0,0 +1,92 @@
+/*
+ * 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.spi;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
+
+/**
+ * Factory for creating a Helidon-specific metric given its name, metadata, and tags.
+ */
+public interface MetricFactory {
+
+ /**
+ * Creates a counter.
+ *
+ * @param scope registry scope
+ * @param metadata metadata describing the counter
+ * @param tags tags further identifying the counter
+ * @return new counter
+ */
+ Counter counter(String scope, Metadata metadata, Tag... tags);
+
+ /**
+ * Creates a timer.
+ *
+ * @param scope registry scope
+ * @param metadata metadata describing the timer
+ * @param tags tags further identifying the timer
+ * @return new timer
+ */
+ Timer timer(String scope, Metadata metadata, Tag... tags);
+
+ /**
+ * Creates a histogram/distribution summary.
+ *
+ * @param scope registry scope
+ * @param metadata metadata describing the summary
+ * @param tags tags further identifying the summary
+ * @return new summary
+ */
+ Histogram summary(String scope, Metadata metadata, Tag... tags);
+
+ /**
+ * Creates a gauge.
+ *
+ * @param scope registry scope
+ * @param metadata metadata describing the gauge
+ * @param supplier supplier of the gauge value
+ * @param tags tags further identifying the gauge
+ * @return new gauge
+ * @param gauge's value type
+ */
+ Gauge gauge(String scope, Metadata metadata, Supplier supplier, Tag... tags);
+
+ /**
+ * Creates a gauge.
+ *
+ * @param scope registry scope
+ * @param metadata metadata describing the gauge
+ * @param target object which dispenses the gauge value
+ * @param fn function which, when applied to the target, reveals the gauge value
+ * @param tags tags further identifying the gauge
+ * @return new counter
+ * @param gauge's value type
+ * @param type of the target which reveals the value
+ */
+ Gauge gauge(String scope,
+ Metadata metadata,
+ T target,
+ Function fn,
+ Tag... tags);
+}
diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/MetricStoreTests.java b/metrics/api/src/test/java/io/helidon/metrics/api/MetricStoreTests.java
index 94f1618e2a8..5a7e7a298da 100644
--- a/metrics/api/src/test/java/io/helidon/metrics/api/MetricStoreTests.java
+++ b/metrics/api/src/test/java/io/helidon/metrics/api/MetricStoreTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -18,9 +18,8 @@
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
-import org.eclipse.microprofile.metrics.SimpleTimer;
import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -39,23 +38,18 @@ class MetricStoreTests {
void testConflictingMetadata() {
Metadata meta1 = Metadata.builder()
.withName("a")
- .withType(MetricType.SIMPLE_TIMER)
.build();
Metadata meta2 = Metadata.builder(meta1)
- .withType(MetricType.COUNTER)
.build();
- NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.Type.APPLICATION);
+ NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.APPLICATION_SCOPE);
MetricStore store = MetricStore.create(REGISTRY_SETTINGS,
- NoOpMetricRegistry.NO_OP_METRIC_FACTORIES,
- null,
- null,
- MetricRegistry.Type.APPLICATION,
- registry::toImpl);
+ new NoOpMetricFactory(),
+ MetricRegistry.APPLICATION_SCOPE);
- store.getOrRegisterMetric(meta1, SimpleTimer.class, NO_TAGS);
+ store.getOrRegisterMetric(meta1, Timer.class, NO_TAGS);
assertThrows(IllegalArgumentException.class, () ->
store.getOrRegisterMetric(meta2, Counter.class, NO_TAGS));
@@ -65,17 +59,13 @@ void testConflictingMetadata() {
void testSameNameNoTags() {
Metadata metadata = Metadata.builder()
.withName("a")
- .withType(MetricType.COUNTER)
.build();
- NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.Type.APPLICATION);
+ NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.APPLICATION_SCOPE);
MetricStore store = MetricStore.create(REGISTRY_SETTINGS,
- NoOpMetricRegistry.NO_OP_METRIC_FACTORIES,
- null,
- null,
- MetricRegistry.Type.APPLICATION,
- registry::toImpl);
+ new NoOpMetricFactory(),
+ MetricRegistry.APPLICATION_SCOPE);
Counter counter1 = store.getOrRegisterMetric(metadata, Counter.class, NO_TAGS);
Counter counter2 = store.getOrRegisterMetric(metadata, Counter.class, NO_TAGS);
@@ -87,17 +77,13 @@ void testSameNameSameTwoTags() {
Tag[] tags = {new Tag("foo", "1"), new Tag("bar", "1")};
Metadata metadata = Metadata.builder()
.withName("a")
- .withType(MetricType.COUNTER)
.build();
- NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.Type.APPLICATION);
+ NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.APPLICATION_SCOPE);
MetricStore store = MetricStore.create(REGISTRY_SETTINGS,
- NoOpMetricRegistry.NO_OP_METRIC_FACTORIES,
- null,
- null,
- MetricRegistry.Type.APPLICATION,
- registry::toImpl);
+ new NoOpMetricFactory(),
+ MetricRegistry.APPLICATION_SCOPE);
Counter counter1 = store.getOrRegisterMetric(metadata, Counter.class, tags);
Counter counter2 = store.getOrRegisterMetric(metadata, Counter.class, tags);
@@ -110,17 +96,13 @@ void testSameNameOverlappingButDifferentTags() {
Tag[] tags2 = {new Tag("foo", "1"), new Tag("bar", "1")};
Metadata metadata = Metadata.builder()
.withName("a")
- .withType(MetricType.COUNTER)
.build();
- NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.Type.APPLICATION);
+ NoOpMetricRegistry registry = NoOpMetricRegistry.create(MetricRegistry.APPLICATION_SCOPE);
MetricStore store = MetricStore.create(REGISTRY_SETTINGS,
- NoOpMetricRegistry.NO_OP_METRIC_FACTORIES,
- null,
- null,
- MetricRegistry.Type.APPLICATION,
- registry::toImpl);
+ new NoOpMetricFactory(),
+ MetricRegistry.APPLICATION_SCOPE);
Counter counter1 = store.getOrRegisterMetric(metadata, Counter.class, tags1);
Counter counter2 = store.getOrRegisterMetric(metadata, Counter.class, tags2);
diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java b/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java
index 98fb8127659..5829910c2a1 100644
--- a/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java
+++ b/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -99,10 +99,10 @@ void testKpi() {
@Test
void testNoRegistrySettings() {
MetricsSettingsImpl metricsSettings = (MetricsSettingsImpl) MetricsSettings.builder().config(withRESTSettings).build();
- for (MetricRegistry.Type registryType : MetricRegistry.Type.values()) {
- assertThat("Registry settings with no config for " + registryType,
+ for (String scope : Registry.BUILT_IN_SCOPES) {
+ assertThat("Registry settings with no config for " + scope,
metricsSettings.registrySettings().keySet(),
- not(contains(registryType)));
+ not(contains(scope)));
}
}
@@ -110,14 +110,14 @@ void testNoRegistrySettings() {
void testOneRegistrySettings() {
MetricsSettingsImpl metricsSettings = (MetricsSettingsImpl) MetricsSettings.builder().config(withOneRegistrySettings)
.build();
- for (MetricRegistry.Type type : Set.of(MetricRegistry.Type.VENDOR, MetricRegistry.Type.BASE)) {
- assertThat("Registry settings lacking config for " + type,
+ for (String scope : Set.of(Registry.VENDOR_SCOPE, Registry.BASE_SCOPE)) {
+ assertThat("Registry settings lacking config for " + scope,
metricsSettings.registrySettings().keySet(),
- not(contains(type)));
+ not(contains(scope)));
}
- RegistrySettings registrySettings = metricsSettings.registrySettings().get(MetricRegistry.Type.APPLICATION);
+ RegistrySettings registrySettings = metricsSettings.registrySettings().get(Registry.APPLICATION_SCOPE);
- assertThat("Registry settings for " + MetricRegistry.Type.APPLICATION,
+ assertThat("Registry settings for " + Registry.APPLICATION_SCOPE,
registrySettings,
notNullValue());
@@ -128,7 +128,7 @@ void testOneRegistrySettings() {
registrySettings.isMetricEnabled("anything"),
is(false));
assertThat("Metrics enabled for any name via metrics settings",
- metricsSettings.isMetricEnabled(MetricRegistry.Type.APPLICATION, "anything"),
+ metricsSettings.isMetricEnabled(Registry.APPLICATION_SCOPE, "anything"),
is(false));
}
@@ -138,9 +138,9 @@ void testTwoRegistrySettings() {
.build();
assertThat("Registry settings lacking config for 'application'",
metricsSettings.registrySettings().keySet(),
- not(contains(MetricRegistry.Type.APPLICATION)));
+ not(contains(Registry.APPLICATION_SCOPE)));
- RegistrySettings vendorSettings = metricsSettings.registrySettings().get(MetricRegistry.Type.VENDOR);
+ RegistrySettings vendorSettings = metricsSettings.registrySettings().get(Registry.VENDOR_SCOPE);
assertThat("Vendor settings", vendorSettings, notNullValue());
assertThat("Vendor enabled", vendorSettings.isEnabled(), is(true));
assertThat("Rejectable name vendor.nogood.here accepted",
@@ -150,10 +150,10 @@ void testTwoRegistrySettings() {
vendorSettings.isMetricEnabled("vendor.ok"),
is(true));
assertThat("Acceptable name vendor.ok via metrics settings",
- metricsSettings.isMetricEnabled(MetricRegistry.Type.VENDOR, "vendor.ok"),
+ metricsSettings.isMetricEnabled(Registry.VENDOR_SCOPE, "vendor.ok"),
is(false));
- RegistrySettings baseSettings = metricsSettings.registrySettings().get(MetricRegistry.Type.BASE);
+ RegistrySettings baseSettings = metricsSettings.registrySettings().get(Registry.BASE_SCOPE);
assertThat("Base settings", baseSettings, notNullValue());
assertThat("Base enabled", baseSettings.isEnabled(), is(false));
@@ -175,10 +175,10 @@ void testRejectOthersWithPositivePatterns() {
MetricsSettingsImpl metricsSettings = (MetricsSettingsImpl) MetricsSettings.builder().config(withSimpleFilter).build();
assertThat("Approvable name app.ok.go",
- metricsSettings.isMetricEnabled(MetricRegistry.Type.APPLICATION, "app.ok.go"),
+ metricsSettings.isMetricEnabled(Registry.APPLICATION_SCOPE, "app.ok.go"),
is(true));
assertThat("Rejectable name app.no.please",
- metricsSettings.isMetricEnabled(MetricRegistry.Type.APPLICATION, "app.no.please"),
+ metricsSettings.isMetricEnabled(Registry.APPLICATION_SCOPE, "app.no.please"),
is(false));
}
}
diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOp.java b/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOp.java
index ae56ec12be0..98f7c723208 100644
--- a/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOp.java
+++ b/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOp.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -35,7 +35,7 @@ static void setUpFactory() {
@Test
void testCounterAndGetCounters() {
- MetricRegistry appRegistry = factory.getRegistry(MetricRegistry.Type.APPLICATION);
+ MetricRegistry appRegistry = factory.getRegistry(Registry.APPLICATION_SCOPE);
Counter counter = appRegistry.counter("disabledCounter");
counter.inc();
diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOpRegistry.java b/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOpRegistry.java
index 437535964df..19c5d440208 100644
--- a/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOpRegistry.java
+++ b/metrics/api/src/test/java/io/helidon/metrics/api/TestNoOpRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -17,11 +17,8 @@
import java.time.Duration;
-import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
-import org.eclipse.microprofile.metrics.SimpleTimer;
-import org.junit.jupiter.api.Assertions;
+import org.eclipse.microprofile.metrics.Timer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -37,20 +34,20 @@ public class TestNoOpRegistry {
@BeforeAll
static void setUpRegistry() {
RegistryFactory registryFactory = RegistryFactory.create(MetricsSettings.builder().enabled(false).build());
- appRegistry = registryFactory.getRegistry(MetricRegistry.Type.APPLICATION);
+ appRegistry = registryFactory.getRegistry(Registry.APPLICATION_SCOPE);
}
@Test
void checkUpdatesAreNoOps() {
- SimpleTimer simpleTimer = appRegistry.simpleTimer("testSimpleTimer");
- simpleTimer.update(Duration.ofSeconds(4));
- assertThat("Updated SimpleTimer count", simpleTimer.getCount(), is(0L));
+ Timer timer = appRegistry.timer("testSimpleTimer");
+ timer.update(Duration.ofSeconds(4));
+ assertThat("Updated Timer count", timer.getCount(), is(0L));
}
@Test
void checkDifferentInstances() {
- SimpleTimer simplerTimer = appRegistry.simpleTimer("testForUnsupportedOp");
- SimpleTimer otherSimpleTimer = appRegistry.simpleTimer("someOtherName");
- assertThat("Same metric instance for different names", otherSimpleTimer, is(not(sameInstance(simplerTimer))));
+ Timer timer = appRegistry.timer("testForUnsupportedOp");
+ Timer otherTimer = appRegistry.timer("someOtherName");
+ assertThat("Same metric instance for different names", otherTimer, is(not(sameInstance(timer))));
}
}
diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/TestRegistrySettingsProperties.java b/metrics/api/src/test/java/io/helidon/metrics/api/TestRegistrySettingsProperties.java
index fba8bc0d34b..162b6c52633 100644
--- a/metrics/api/src/test/java/io/helidon/metrics/api/TestRegistrySettingsProperties.java
+++ b/metrics/api/src/test/java/io/helidon/metrics/api/TestRegistrySettingsProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -18,7 +18,6 @@
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
-import org.eclipse.microprofile.metrics.MetricRegistry;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -38,7 +37,7 @@ static void prep() {
void testInclude() {
MetricsSettings metricsSettings = MetricsSettings.create(metricsConfig);
assertThat("'pass.me' metric is enabled",
- metricsSettings.registrySettings(MetricRegistry.Type.VENDOR).isMetricEnabled("pass.me"),
+ metricsSettings.registrySettings(Registry.VENDOR_SCOPE).isMetricEnabled("pass.me"),
is(true));
}
@@ -46,7 +45,7 @@ void testInclude() {
void testExclude() {
MetricsSettings metricsSettings = MetricsSettings.create(metricsConfig);
assertThat("'ignore.me' metric is enabled",
- metricsSettings.registrySettings(MetricRegistry.Type.VENDOR).isMetricEnabled("ignore.me"),
+ metricsSettings.registrySettings(Registry.VENDOR_SCOPE).isMetricEnabled("ignore.me"),
is(false));
}
}
diff --git a/metrics/api/src/test/resources/registrySettings.properties b/metrics/api/src/test/resources/registrySettings.properties
index 65d14fcdf9c..255e3b245f2 100644
--- a/metrics/api/src/test/resources/registrySettings.properties
+++ b/metrics/api/src/test/resources/registrySettings.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2021 Oracle and/or its affiliates.
+# Copyright (c) 2021, 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.
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-metrics.registries.0.type = application
+metrics.registries.0.scope = application
metrics.registries.0.filter.exclude = ignore\..*
-metrics.registries.1.type = vendor
+metrics.registries.1.scope = vendor
metrics.registries.1.filter.include = pass\..*
diff --git a/metrics/api/src/test/resources/testconfig.yaml b/metrics/api/src/test/resources/testconfig.yaml
index dcc1f24d3e2..39244f234f3 100644
--- a/metrics/api/src/test/resources/testconfig.yaml
+++ b/metrics/api/src/test/resources/testconfig.yaml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2021 Oracle and/or its affiliates.
+# Copyright (c) 2021, 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.
@@ -45,17 +45,17 @@ withRESTSettings:
withOneRegistrySettings:
metrics:
registries:
- - type: application
+ - scope: application
enabled: false
withTwoRegistrySettings:
metrics:
enabled: false
registries:
- - type: vendor
+ - scope: vendor
filter:
exclude: 'vendor\.nogood\..*'
- - type: base
+ - scope: base
enabled: false
filter:
include: 'base\.good\..*'
@@ -63,7 +63,7 @@ withTwoRegistrySettings:
registrySettingsWithBadFilterSyntax:
metrics:
registries:
- - type: application
+ - scope: application
filter:
include: 'ok'
exclude: 'bad('
@@ -71,6 +71,6 @@ registrySettingsWithBadFilterSyntax:
withSimpleFilter:
metrics:
registries:
- - type: application
+ - scope: application
filter:
include: 'app\.ok\..*'
\ No newline at end of file
diff --git a/metrics/metrics/pom.xml b/metrics/metrics/pom.xml
index b4f209a5039..f0e673531c9 100644
--- a/metrics/metrics/pom.xml
+++ b/metrics/metrics/pom.xml
@@ -58,6 +58,10 @@
org.eclipse.microprofile.metrics
microprofile-metrics-api
+
+ io.micrometer
+ micrometer-core
+
io.helidon.config
helidon-config
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java b/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java
index 1a444dbc0c0..7764dea587a 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -26,14 +26,14 @@
import java.lang.management.ThreadMXBean;
import java.util.List;
import java.util.function.Function;
+import java.util.function.ToDoubleFunction;
import io.helidon.metrics.api.BaseMetricsSettings;
import io.helidon.metrics.api.MetricsSettings;
+import io.helidon.metrics.api.RegistryFactory;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Metric;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Tag;
@@ -61,28 +61,23 @@ final class BaseRegistry extends Registry {
private static final Metadata MEMORY_USED_HEAP = Metadata.builder()
.withName("memory.usedHeap")
- .withDisplayName("Used Heap Memory")
.withDescription("Displays the amount of used heap memory in bytes.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.BYTES)
.build();
private static final Metadata MEMORY_COMMITTED_HEAP = Metadata.builder()
.withName("memory.committedHeap")
- .withDisplayName("Committed Heap Memory")
.withDescription(
"Displays the amount of memory in bytes that is "
+ "committed for the Java virtual "
+ "machine to use. This amount of memory is "
+ "guaranteed for the Java virtual "
+ "machine to use.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.BYTES)
.build();
private static final Metadata MEMORY_MAX_HEAP = Metadata.builder()
.withName("memory.maxHeap")
- .withDisplayName("Max Heap Memory")
.withDescription(
"Displays the maximum amount of heap memory in bytes that can"
+ " be used for "
@@ -96,92 +91,74 @@ final class BaseRegistry extends Registry {
+ " to allocate memory "
+ "even if the amount of used memory does not exceed "
+ "this maximum size.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.BYTES)
.build();
private static final Metadata JVM_UPTIME = Metadata.builder()
.withName("jvm.uptime")
- .withDisplayName("JVM Uptime")
.withDescription(
"Displays the start time of the Java virtual machine in "
+ "milliseconds. This "
+ "attribute displays the approximate time when the Java "
+ "virtual machine "
+ "started.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.MILLISECONDS)
.build();
private static final Metadata THREAD_COUNT = Metadata.builder()
.withName("thread.count")
- .withDisplayName("Thread Count")
.withDescription("Displays the current number of live threads including both "
+ "daemon and nondaemon threads")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata THREAD_DAEMON_COUNT = Metadata.builder()
.withName("thread.daemon.count")
- .withDisplayName("Daemon Thread Count")
.withDescription("Displays the current number of live daemon threads.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata THREAD_MAX_COUNT = Metadata.builder()
.withName("thread.max.count")
- .withDisplayName("Peak Thread Count")
.withDescription("Displays the peak live thread count since the Java "
+ "virtual machine started or "
+ "peak was reset. This includes daemon and "
+ "non-daemon threads.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata CL_LOADED_COUNT = Metadata.builder()
.withName("classloader.loadedClasses.count")
- .withDisplayName("Current Loaded Class Count")
.withDescription("Displays the number of classes that are currently loaded in "
+ "the Java virtual machine.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata CL_LOADED_TOTAL = Metadata.builder()
.withName("classloader.loadedClasses.total")
- .withDisplayName("Total Loaded Class Count")
.withDescription("Displays the total number of classes that have been loaded "
+ "since the Java virtual machine has started execution.")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata CL_UNLOADED_COUNT = Metadata.builder()
.withName("classloader.unloadedClasses.total")
- .withDisplayName("Total Unloaded Class Count")
.withDescription("Displays the total number of classes unloaded since the Java "
+ "virtual machine has started execution.")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata OS_AVAILABLE_CPU = Metadata.builder()
.withName("cpu.availableProcessors")
- .withDisplayName("Available Processors")
.withDescription("Displays the number of processors available to the Java "
+ "virtual machine. This "
+ "value may change during a particular invocation of"
+ " the virtual machine.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata OS_LOAD_AVERAGE = Metadata.builder()
.withName("cpu.systemLoadAverage")
- .withDisplayName("System Load Average")
.withDescription("Displays the system load average for the last minute. The "
+ "system load average "
+ "is the sum of the number of runnable entities "
@@ -200,14 +177,13 @@ final class BaseRegistry extends Registry {
+ " be unavailable on some "
+ "platforms where it is expensive to implement this "
+ "method.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private final MetricsSettings metricsSettings;
private BaseRegistry(MetricsSettings metricsSettings) {
- super(Type.BASE, metricsSettings.registrySettings(Type.BASE));
+ super(Registry.BASE_SCOPE, metricsSettings.registrySettings(Registry.BASE_SCOPE));
this.metricsSettings = metricsSettings;
}
@@ -235,8 +211,8 @@ public static Registry create(MetricsSettings metricsSettings) {
ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean();
register(result, CL_LOADED_COUNT, clBean, ClassLoadingMXBean::getLoadedClassCount);
- register(result, CL_LOADED_TOTAL, (SimpleCounter) clBean::getTotalLoadedClassCount);
- register(result, CL_UNLOADED_COUNT, (SimpleCounter) clBean::getUnloadedClassCount);
+ register(result, CL_LOADED_TOTAL, clBean, ClassLoadingMXBean::getTotalLoadedClassCount);
+ register(result, CL_UNLOADED_COUNT, clBean, ClassLoadingMXBean::getUnloadedClassCount);
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
register(result, OS_AVAILABLE_CPU, osBean, OperatingSystemMXBean::getAvailableProcessors);
@@ -245,8 +221,8 @@ public static Registry create(MetricsSettings metricsSettings) {
List gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcBean : gcBeans) {
String poolName = gcBean.getName();
- register(result, gcCountMeta(), (SimpleCounter) gcBean::getCollectionCount,
- new Tag("name", poolName));
+ register(result, gcCountMeta(), gcBean, GarbageCollectorMXBean::getCollectionCount,
+ new Tag("name", poolName));
register(result, gcTimeMeta(), gcBean, GarbageCollectorMXBean::getCollectionTime,
new Tag("name", poolName));
}
@@ -257,7 +233,6 @@ public static Registry create(MetricsSettings metricsSettings) {
private static Metadata gcTimeMeta() {
return Metadata.builder()
.withName("gc.time")
- .withDisplayName("Garbage Collection Time")
.withDescription(
"Displays the approximate accumulated collection elapsed time in milliseconds. "
+ "This attribute displays -1 if the collection elapsed time is undefined for this "
@@ -265,7 +240,6 @@ private static Metadata gcTimeMeta() {
+ "timer to measure the elapsed time. This attribute may display the same value "
+ "even if the collection count has been incremented if the collection elapsed "
+ "time is very short.")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.MILLISECONDS)
.build();
}
@@ -273,20 +247,19 @@ private static Metadata gcTimeMeta() {
private static Metadata gcCountMeta() {
return Metadata.builder()
.withName("gc.total")
- .withDisplayName("Garbage Collection Count")
.withDescription(
"Displays the total number of collections that have occurred. This attribute lists "
+ "-1 if the collection count is undefined for this collector.")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
}
- private static void register(BaseRegistry registry, Metadata meta, Metric metric, Tag... tags) {
- if (registry.metricsSettings.baseMetricsSettings().isBaseMetricEnabled(meta.getName())
- && registry.metricsSettings.isMetricEnabled(Type.BASE, meta.getName())) {
- registry.register(meta, metric, tags);
- }
+ private static void register(BaseRegistry registry,
+ Metadata meta,
+ T object,
+ ToDoubleFunction func,
+ Tag... tags) {
+ registry.gauge(meta, object, func::applyAsDouble, tags);
}
private static void register(BaseRegistry registry,
@@ -295,7 +268,7 @@ private static void register(BaseRegistry registry,
Function func,
Tag... tags) {
if (registry.metricsSettings.baseMetricsSettings().isBaseMetricEnabled(meta.getName())
- && registry.metricsSettings.isMetricEnabled(Type.BASE, meta.getName())) {
+ && registry.metricsSettings.isMetricEnabled(Registry.BASE_SCOPE, meta.getName())) {
registry.gauge(meta, object, func, tags);
}
}
@@ -303,17 +276,4 @@ private static void register(BaseRegistry registry,
private static void register(BaseRegistry registry, Metadata meta, T object, Function func) {
register(registry, meta, object, func, NO_TAGS);
}
-
- @FunctionalInterface
- private interface SimpleCounter extends Counter {
- @Override
- default void inc() {
- throw new IllegalStateException("Cannot increase a system counter");
- }
-
- @Override
- default void inc(long n) {
- throw new IllegalStateException("Cannot increase a system counter");
- }
- }
}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/ExecutorServiceMetricsObserver.java b/metrics/metrics/src/main/java/io/helidon/metrics/ExecutorServiceMetricsObserver.java
index 2b2fd6073a2..4ee07da2644 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/ExecutorServiceMetricsObserver.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/ExecutorServiceMetricsObserver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -34,7 +34,6 @@
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Tag;
@@ -61,7 +60,7 @@ public class ExecutorServiceMetricsObserver implements ExecutorServiceSupplierOb
);
private final LazyValue registry = LazyValue
- .create(() -> io.helidon.metrics.api.RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.VENDOR));
+ .create(() -> io.helidon.metrics.api.RegistryFactory.getInstance().getRegistry(Registry.VENDOR_SCOPE));
/**
* Creates a new instance of the observer.
@@ -130,15 +129,14 @@ private void registerMetrics(ExecutorService executorService, List) () -> {
+ registry.get().gauge(metadata, () -> {
try {
- return mi.method().invoke(executorService);
+ return (Number) mi.method().invoke(executorService);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
@@ -226,48 +224,41 @@ private static class MetadataTemplates {
private static final Metadata ACTIVE_COUNT_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "active-count")
.withDescription("Active count")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata COMPLETED_TASK_COUNT_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "completed-task-count")
.withDescription("Completed task count")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata POOL_SIZE_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "pool-size")
.withDescription("Pool size")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata LARGEST_POOL_SIZE_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "largest-pool-size")
.withDescription("Largest pool size")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata TASK_COUNT_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "task-count")
.withDescription("Task count")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata QUEUE_REMAINING_CAPACITY_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "queue.remaining-capacity")
.withDescription("Queue remaining capacity")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
private static final Metadata QUEUE_SIZE_METADATA = Metadata.builder()
.withName(METRIC_NAME_PREFIX + "queue.size")
.withDescription("Queue size")
- .withType(MetricType.GAUGE)
.withUnit(MetricUnits.NONE)
.build();
@@ -278,7 +269,7 @@ private static class MetadataTemplates {
*
* @param type of the gauge
*/
- private static class GaugeFactory {
+ private static class GaugeFactory {
/**
* Creates a gauge factory using template metadata (we have to adjust the name) and a function on the executor service
@@ -289,7 +280,7 @@ private static class GaugeFactory {
* @param type of the gauge
* @return the new gauge factory
*/
- private static GaugeFactory create(
+ private static GaugeFactory create(
Metadata templateMetadata,
Function valueFunction) {
return new GaugeFactory<>(templateMetadata, valueFunction);
@@ -310,7 +301,7 @@ private Gauge registerGauge(MetricRegistry registry,
Set metricIDs) {
Tag[] tags = tags(supplierCategory, supplierIndex, index);
metricIDs.add(new MetricID(templateMetadata.getName(), tags));
- return registry.register(templateMetadata, () -> valueFunction.apply(executor), tags);
+ return registry.gauge(templateMetadata, () -> valueFunction.apply(executor), tags);
}
}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonCounter.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonCounter.java
index b93245f20db..f65606051d1 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonCounter.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonCounter.java
@@ -30,16 +30,18 @@
* Implementation of {@link Counter}.
*/
final class HelidonCounter extends MetricImpl implements Counter, SampledMetric {
- private final Counter delegate;
+ private final io.micrometer.core.instrument.Counter delegate;
- private HelidonCounter(String registryType, Metadata metadata, Counter delegate) {
+ private HelidonCounter(String registryType, Metadata metadata, io.micrometer.core.instrument.Counter delegate) {
super(registryType, metadata);
-
this.delegate = delegate;
}
static HelidonCounter create(String registryType, Metadata metadata) {
- return create(registryType, metadata, new CounterImpl());
+ return create(registryType, metadata, io.micrometer.core.instrument.Counter.builder(metadata.getName())
+ .baseUnit(metadata.getUnit())
+ .description(metadata.getDescription())
+ .);
}
static HelidonCounter create(String registryType, Metadata metadata, Counter metric) {
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonHistogram.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonHistogram.java
index 5c8f8702934..ae53d189c4c 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonHistogram.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonHistogram.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -17,11 +17,10 @@
package io.helidon.metrics;
import java.util.Objects;
-import java.util.concurrent.atomic.LongAdder;
import io.helidon.metrics.api.LabeledSnapshot;
-import io.helidon.metrics.api.SnapshotMetric;
+import io.micrometer.core.instrument.DistributionSummary;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.Snapshot;
@@ -29,8 +28,8 @@
/**
* Implementation of {@link Histogram}.
*/
-final class HelidonHistogram extends MetricImpl implements Histogram, SnapshotMetric {
- private final Histogram delegate;
+final class HelidonHistogram extends MetricImpl implements Histogram {
+ private final DistributionSummary delegate;
private HelidonHistogram(String type, Metadata metadata, Histogram delegate) {
super(type, metadata);
@@ -92,68 +91,6 @@ HistogramImpl getDelegate() {
: null;
}
- static final class HistogramImpl implements Histogram {
- private final LongAdder counter = new LongAdder();
- private final LongAdder sum = new LongAdder();
- private final ExponentiallyDecayingReservoir reservoir;
-
- private HistogramImpl(Clock clock) {
- this.reservoir = new ExponentiallyDecayingReservoir(clock);
- }
-
- public void update(int value) {
- update((long) value);
- }
-
- @Override
- public long getSum() {
- return sum.sum();
- }
-
- @Override
- public void update(long value) {
- counter.increment();
- sum.add(value);
- reservoir.update(value, ExemplarServiceManager.exemplarLabel());
- }
-
- public void update(long value, long timestamp) {
- counter.increment();
- sum.add(value);
- reservoir.update(value, timestamp, ExemplarServiceManager.exemplarLabel());
- }
-
- @Override
- public long getCount() {
- return counter.sum();
- }
-
- @Override
- public Snapshot getSnapshot() {
- return reservoir.getSnapshot();
- }
-
- WeightedSnapshot snapshot() {
- return reservoir.getSnapshot();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), getCount());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- HistogramImpl that = (HistogramImpl) o;
- return getCount() == that.getCount();
- }
- }
@Override
public boolean equals(Object o) {
@@ -178,16 +115,8 @@ protected String toStringDetails() {
StringBuilder sb = new StringBuilder();
sb.append(", count='").append(getCount()).append('\'');
if (null != snapshot) {
- sb.append(", min='").append(snapshot.getMin()).append('\'');
sb.append(", max='").append(snapshot.getMax()).append('\'');
sb.append(", mean='").append(snapshot.getMean()).append('\'');
- sb.append(", stddev='").append(snapshot.getStdDev()).append('\'');
- sb.append(", p50='").append(snapshot.getMedian()).append('\'');
- sb.append(", p75='").append(snapshot.get75thPercentile()).append('\'');
- sb.append(", p95='").append(snapshot.get95thPercentile()).append('\'');
- sb.append(", p98='").append(snapshot.get98thPercentile()).append('\'');
- sb.append(", p99='").append(snapshot.get99thPercentile()).append('\'');
- sb.append(", p999='").append(snapshot.get999thPercentile()).append('\'');
}
return sb.toString();
}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetricFactory.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetricFactory.java
new file mode 100644
index 00000000000..a6bc2cd0c59
--- /dev/null
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetricFactory.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;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import io.helidon.metrics.api.spi.MetricFactory;
+
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
+
+/**
+ * Helidon-specific implementation of metric factory methods.
+ */
+class HelidonMetricFactory implements MetricFactory {
+ @Override
+ public Counter counter(String scope, Metadata metadata, Tag... tags) {
+ return HelidonCounter.create(scope, metadata);
+ }
+
+ @Override
+ public Timer timer(String scope, Metadata metadata, Tag... tags) {
+ return HelidonTimer.create(scope, metadata);
+ }
+
+ @Override
+ public Histogram summary(String scope, Metadata metadata, Tag... tags) {
+ return HelidonHistogram.create(scope, metadata);
+ }
+
+ @Override
+ public Gauge gauge(String scope, Metadata metadata, Supplier supplier, Tag... tags) {
+ return HelidonGauge.create(scope, metadata, supplier);
+ }
+
+ @Override
+ public Gauge gauge(String scope, Metadata metadata, T target, Function fn, Tag... tags) {
+ return HelidonGauge.create(scope, metadata, target, fn);
+ }
+}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonSnapshot.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonSnapshot.java
new file mode 100644
index 00000000000..8d13ecbc0bc
--- /dev/null
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonSnapshot.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import io.micrometer.core.instrument.distribution.HistogramSnapshot;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+/**
+ * Snapshot implementation.
+ */
+public class HelidonSnapshot extends Snapshot {
+
+ /**
+ * Creates a new snapshot.
+ *
+ * @param histogramSnapshot the underlying snapshot data
+ * @return new snapshot wrapper around the data
+ */
+ public static HelidonSnapshot create(HistogramSnapshot histogramSnapshot) {
+ return new HelidonSnapshot(histogramSnapshot);
+ }
+
+ private final HistogramSnapshot delegate;
+
+ private HelidonSnapshot(HistogramSnapshot histogramSnapshot) {
+ delegate = histogramSnapshot;
+ }
+
+ @Override
+ public long size() {
+ return delegate.count();
+ }
+
+ @Override
+ public double getMax() {
+ return delegate.max();
+ }
+
+ @Override
+ public double getMean() {
+ return delegate.mean();
+ }
+
+ @Override
+ public PercentileValue[] percentileValues() {
+ return Arrays.stream(delegate.percentileValues())
+ .map(pv -> new PercentileValue(pv.percentile(), pv.value()))
+ .toArray(PercentileValue[]::new);
+ }
+
+ @Override
+ public void dump(OutputStream output) {
+ delegate.outputSummary(new PrintStream(output, false, Charset.defaultCharset()), 1);
+ }
+}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java
index 16b26673291..a265fa9e95c 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/HelidonTimer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -25,8 +25,6 @@
import io.helidon.metrics.api.SnapshotMetric;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Meter;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.Snapshot;
import org.eclipse.microprofile.metrics.Timer;
@@ -83,26 +81,6 @@ public long getCount() {
return delegate.getCount();
}
- @Override
- public double getFifteenMinuteRate() {
- return delegate.getFifteenMinuteRate();
- }
-
- @Override
- public double getFiveMinuteRate() {
- return delegate.getFiveMinuteRate();
- }
-
- @Override
- public double getMeanRate() {
- return delegate.getMeanRate();
- }
-
- @Override
- public double getOneMinuteRate() {
- return delegate.getOneMinuteRate();
- }
-
@Override
public Snapshot getSnapshot() {
return delegate.getSnapshot();
@@ -145,19 +123,13 @@ public void close() {
}
private static class TimerImpl implements Timer {
- private final Meter meter;
private final HelidonHistogram histogram;
private final Clock clock;
private long elapsedTimeNanos;
TimerImpl(String repoType, String name, Clock clock) {
- this.meter = HelidonMeter.create(repoType, Metadata.builder()
- .withName(name)
- .withType(MetricType.METERED)
- .build(), clock);
this.histogram = HelidonHistogram.create(repoType, Metadata.builder()
.withName(name)
- .withType(MetricType.HISTOGRAM)
.build(), clock);
this.clock = clock;
}
@@ -204,26 +176,6 @@ public long getCount() {
return histogram.getCount();
}
- @Override
- public double getFifteenMinuteRate() {
- return meter.getFifteenMinuteRate();
- }
-
- @Override
- public double getFiveMinuteRate() {
- return meter.getFiveMinuteRate();
- }
-
- @Override
- public double getMeanRate() {
- return meter.getMeanRate();
- }
-
- @Override
- public double getOneMinuteRate() {
- return meter.getOneMinuteRate();
- }
-
@Override
public Snapshot getSnapshot() {
return histogram.getSnapshot();
@@ -232,14 +184,13 @@ public Snapshot getSnapshot() {
private void update(long nanos) {
if (nanos >= 0) {
histogram.update(nanos);
- meter.mark();
elapsedTimeNanos += nanos;
}
}
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), meter, histogram, elapsedTimeNanos);
+ return Objects.hash(super.hashCode(), histogram, elapsedTimeNanos);
}
@Override
@@ -251,7 +202,7 @@ public boolean equals(Object o) {
return false;
}
TimerImpl that = (TimerImpl) o;
- return meter.equals(that.meter) && histogram.equals(that.histogram) && elapsedTimeNanos == that.elapsedTimeNanos;
+ return histogram.equals(that.histogram) && elapsedTimeNanos == that.elapsedTimeNanos;
}
}
@@ -277,9 +228,6 @@ protected String toStringDetails() {
StringBuilder sb = new StringBuilder();
sb.append(", count='").append(getCount()).append('\'');
sb.append(", elapsedTime='").append(getElapsedTime()).append('\'');
- sb.append(", fifteenMinuteRate='").append(getFifteenMinuteRate()).append('\'');
- sb.append(", fiveMinuteRate='").append(getFiveMinuteRate()).append('\'');
- sb.append(", meanRate='").append(getMeanRate()).append('\'');
return sb.toString();
}
}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java b/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java
index 1b85d9569af..62a3c7f62cc 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/Registry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -16,9 +16,7 @@
package io.helidon.metrics;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -27,17 +25,9 @@
import io.helidon.metrics.api.MetricInstance;
import io.helidon.metrics.api.RegistrySettings;
-import org.eclipse.microprofile.metrics.ConcurrentGauge;
-import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
-import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
-import org.eclipse.microprofile.metrics.Meter;
-import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricID;
-import org.eclipse.microprofile.metrics.MetricType;
-import org.eclipse.microprofile.metrics.SimpleTimer;
-import org.eclipse.microprofile.metrics.Timer;
/**
* Metrics registry.
@@ -47,14 +37,14 @@ public class Registry extends AbstractRegistry {
private final AtomicReference registrySettings = new AtomicReference<>();
/**
- * Create a registry of a certain type.
+ * Create a registry of a certain scope.
*
- * @param type Registry type.
+ * @param scope Registry scope.
* @param registrySettings Registry settings to use in creating the registry.
* @return The newly created registry.
*/
- public static Registry create(Type type, RegistrySettings registrySettings) {
- return new Registry(type, registrySettings);
+ public static Registry create(String scope, RegistrySettings registrySettings) {
+ return new Registry(scope, registrySettings);
}
@Override
@@ -68,82 +58,27 @@ public boolean enabled(String metricName) {
return registrySettings.get().isMetricEnabled(metricName);
}
- @Override
- protected HelidonMetric toImpl(Metadata metadata, Metric metric) {
-
- MetricType metricType = deriveType(metadata.getTypeRaw(), metric);
- switch (metricType) {
- case COUNTER:
- return HelidonCounter.create(type(), metadata, (Counter) metric);
- case GAUGE:
- return HelidonGauge.create(type(), metadata, (Gauge) metric);
- case HISTOGRAM:
- return HelidonHistogram.create(type(), metadata, (Histogram) metric);
- case METERED:
- return HelidonMeter.create(type(), metadata, (Meter) metric);
- case TIMER:
- return HelidonTimer.create(type(), metadata, (Timer) metric);
- case SIMPLE_TIMER:
- return HelidonSimpleTimer.create(type(), metadata, (SimpleTimer) metric);
- case CONCURRENT_GAUGE:
- return HelidonConcurrentGauge.create(type(), metadata, (ConcurrentGauge) metric);
- case INVALID:
- default:
- throw new IllegalArgumentException("Unexpected metric type " + metricType
- + ": " + metric.getClass().getName());
- }
- }
-
/**
* Creates a new instance.
*
- * @param type registry type for the new registry
+ * @param scope registry scope for the new registry
* @param registrySettings registry settings to influence the created registry
*/
- protected Registry(Type type, RegistrySettings registrySettings) {
- super(type, registrySettings);
+ protected Registry(String scope, RegistrySettings registrySettings) {
+ super(scope, registrySettings, new HelidonMetricFactory());
this.registrySettings.set(registrySettings);
}
- @Override
- protected Map, MetricType> prepareMetricToTypeMap() {
- return Map.of(
- HelidonConcurrentGauge.class, MetricType.CONCURRENT_GAUGE,
- HelidonCounter.class, MetricType.COUNTER,
- HelidonGauge.class, MetricType.GAUGE,
- HelidonHistogram.class, MetricType.HISTOGRAM,
- HelidonMeter.class, MetricType.METERED,
- HelidonTimer.class, MetricType.TIMER,
- HelidonSimpleTimer.class, MetricType.SIMPLE_TIMER);
- }
-
@Override
protected Gauge createGauge(Metadata metadata, Supplier supplier) {
- return HelidonGauge.create(type(), metadata, supplier);
- }
-
- @Override
- protected Map> prepareMetricFactories() {
- // Omit gauge because creating a gauge requires an existing delegate instance.
- // These factory methods do not use delegates.
- return Map.of(MetricType.COUNTER, HelidonCounter::create,
- MetricType.HISTOGRAM, HelidonHistogram::create,
- MetricType.METERED, HelidonMeter::create,
- MetricType.TIMER, HelidonTimer::create,
- MetricType.SIMPLE_TIMER, HelidonSimpleTimer::create,
- MetricType.CONCURRENT_GAUGE, HelidonConcurrentGauge::create);
+ return HelidonGauge.create(scope(), metadata, supplier);
}
@Override
protected Gauge createGauge(Metadata metadata,
T object,
Function func) {
- return HelidonGauge.create(type(), metadata, object, func);
- }
-
- @Override
- protected Map> metricFactories() {
- return super.metricFactories();
+ return HelidonGauge.create(scope(), metadata, object, func);
}
@Override
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java b/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java
index 0aeb7ddda0b..25257f3fe80 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/RegistryFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -16,15 +16,14 @@
package io.helidon.metrics;
-import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import io.helidon.config.Config;
import io.helidon.metrics.api.MetricsSettings;
-import org.eclipse.microprofile.metrics.MetricRegistry.Type;
-
/**
* Access point to all registries.
*
@@ -32,7 +31,7 @@
*
* - A singleton instance, obtained through {@link #getInstance()} or {@link #getInstance(io.helidon.config.Config)}.
* This instance is lazily initialized - the latest call that provides a config instance before a
- * {@link org.eclipse.microprofile.metrics.MetricRegistry.Type#BASE} registry is obtained would be used to configure
+ * {@link org.eclipse.microprofile.metrics.MetricRegistry#BASE_SCOPE} registry is obtained would be used to configure
* the base registry (as that is the only configurable registry in current implementation)
*
* - A custom instance, obtained through {@link #create(Config)} or {@link #create()}. This would create a
@@ -44,7 +43,7 @@
// see Github issue #360
public class RegistryFactory implements io.helidon.metrics.api.RegistryFactory {
- private final EnumMap registries = new EnumMap<>(Type.class);
+ private final Map registries = new HashMap<>();
private final Lock metricsSettingsAccess = new ReentrantLock(true);
private MetricsSettings metricsSettings;
@@ -57,14 +56,14 @@ public class RegistryFactory implements io.helidon.metrics.api.RegistryFactory {
*/
protected RegistryFactory(MetricsSettings metricsSettings, Registry appRegistry, Registry vendorRegistry) {
this.metricsSettings = metricsSettings;
- registries.put(Type.APPLICATION, appRegistry);
- registries.put(Type.VENDOR, vendorRegistry);
+ registries.put(Registry.APPLICATION_SCOPE, appRegistry);
+ registries.put(Registry.VENDOR_SCOPE, vendorRegistry);
}
private RegistryFactory(MetricsSettings metricsSettings) {
this(metricsSettings,
- Registry.create(Type.APPLICATION, metricsSettings.registrySettings(Type.APPLICATION)),
- Registry.create(Type.VENDOR, metricsSettings.registrySettings(Type.VENDOR)));
+ Registry.create(Registry.APPLICATION_SCOPE, metricsSettings.registrySettings(Registry.APPLICATION_SCOPE)),
+ Registry.create(Registry.VENDOR_SCOPE, metricsSettings.registrySettings(Registry.VENDOR_SCOPE)));
}
private void accessMetricsSettings(Runnable operation) {
@@ -132,27 +131,27 @@ public static RegistryFactory getInstance(Config config) {
return RegistryFactory.class.cast(io.helidon.metrics.api.RegistryFactory.getInstance(config));
}
- Registry getARegistry(Type type) {
- if (type == Type.BASE) {
+ Registry getARegistry(String scope) {
+ if (Registry.BASE_SCOPE.equals(scope)) {
ensureBase();
}
- return registries.get(type);
+ return registries.get(scope);
}
/**
* Get a registry based on its type.
- * For {@link Type#APPLICATION} and {@link Type#VENDOR} returns a modifiable registry,
- * for {@link Type#BASE} returns a final registry (cannot register new metrics).
+ * For {@value Registry#APPLICATION_SCOPE} and {@value Registry#VENDOR_SCOPE} returns a modifiable registry,
+ * for {@value Registry#BASE_SCOPE} returns a final registry (cannot register new metrics).
*
- * @param type type of registry
+ * @param scope type of registry
* @return MetricRegistry for the type defined.
*/
@Override
- public Registry getRegistry(Type type) {
- if (type == Type.BASE) {
+ public Registry getRegistry(String scope) {
+ if (Registry.BASE_SCOPE.equals(scope)) {
ensureBase();
}
- return registries.get(type);
+ return registries.get(scope);
}
@Override
@@ -179,10 +178,10 @@ public void stop() {
}
private void ensureBase() {
- if (null == registries.get(Type.BASE)) {
+ if (null == registries.get(Registry.BASE_SCOPE)) {
accessMetricsSettings(() -> {
Registry registry = BaseRegistry.create(metricsSettings);
- registries.put(Type.BASE, registry);
+ registries.put(Registry.BASE_SCOPE, registry);
});
}
}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/WeightedSnapshot.java b/metrics/metrics/src/main/java/io/helidon/metrics/WeightedSnapshot.java
index dd6025dd6f3..d3897a65214 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/WeightedSnapshot.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/WeightedSnapshot.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -45,7 +45,7 @@
/**
* A statistical snapshot of a {@link WeightedSnapshot}.
*/
-class WeightedSnapshot extends Snapshot implements LabeledSnapshot {
+class WeightedSnapshot extends HelidonSnapshot implements LabeledSnapshot {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final WeightedSample[] copy;
@@ -83,22 +83,22 @@ class WeightedSnapshot extends Snapshot implements LabeledSnapshot {
}
}
- /**
- * Returns the value at the given quantile.
- *
- * @param quantile a given quantile, in {@code [0..1]}
- * @return the value in the distribution at {@code quantile}
- */
- @Override
- public double getValue(double quantile) {
- return value(quantile).value();
- }
-
- @Override
- public Derived value(double quantile) {
- int posx = slot(quantile);
- return posx == -1 ? Derived.ZERO : derived(copy[posx].value(), copy[posx]);
- }
+// /**
+// * Returns the value at the given quantile.
+// *
+// * @param quantile a given quantile, in {@code [0..1]}
+// * @return the value in the distribution at {@code quantile}
+// */
+// @Override
+// public double getValue(double quantile) {
+// return value(quantile).value();
+// }
+//
+// @Override
+// public Derived value(double quantile) {
+// int posx = slot(quantile);
+// return posx == -1 ? Derived.ZERO : derived(copy[posx].value(), copy[posx]);
+// }
int slot(double quantile) {
if ((quantile < 0.0) || (quantile > 1.0) || Double.isNaN(quantile)) {
@@ -131,7 +131,7 @@ int slot(double quantile) {
* @return the number of values
*/
@Override
- public int size() {
+ public long size() {
return copy.length;
}
@@ -155,37 +155,6 @@ public long[] getValues() {
return values;
}
- @Override
- public Derived median() {
- return value(0.5);
- }
-
- @Override
- public Derived sample75thPercentile() {
- return value(0.75);
- }
-
- @Override
- public Derived sample95thPercentile() {
- return value(0.95);
- }
-
- @Override
- public Derived sample98thPercentile() {
- return value(0.98);
- }
-
- @Override
- public Derived sample99thPercentile() {
- return value(0.99);
- }
-
- @Override
- public Derived sample999thPercentile() {
- return value(0.999);
- }
-
-
/**
* Returns the highest value in the snapshot.
*
@@ -275,35 +244,7 @@ static int slotNear(Sample target, Sample[] values) {
: higherSlot - 1;
}
- /**
- * Returns the weighted standard deviation of the values in the snapshot.
- *
- * @return the weighted standard deviation value
- */
- @Override
- public double getStdDev() {
- return stdDev().value();
- }
- @Override
- public Derived stdDev() {
- // two-pass algorithm for variance, avoids numeric overflow
-
- if (copy.length <= 1) {
- return Derived.ZERO;
- }
-
-
- final double mean = mean().value();
- double variance = 0;
-
- for (int i = 0; i < copy.length; i++) {
- final double diff = copy[i].value() - mean;
- variance += normWeights[i] * diff * diff;
- }
-
- return derived(Math.sqrt(variance));
- }
/**
* Writes the values of the snapshot to the given stream.
@@ -347,7 +288,7 @@ static class WeightedSample extends LabeledSample {
this(value, 1.0, 0, "");
}
- long getValue() {
+ double getValue() {
return value();
}
diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/WrappedSnapshot.java b/metrics/metrics/src/main/java/io/helidon/metrics/WrappedSnapshot.java
index 912b02b124f..8a1aae47f81 100644
--- a/metrics/metrics/src/main/java/io/helidon/metrics/WrappedSnapshot.java
+++ b/metrics/metrics/src/main/java/io/helidon/metrics/WrappedSnapshot.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 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.
@@ -26,83 +26,23 @@
class WrappedSnapshot implements LabeledSnapshot {
- private final Snapshot delegate;
-
- private final Labeled[] samples;
- private final Derived median;
private final Labeled max;
- private final Labeled min;
private final Derived mean;
- private final Derived stdDev;
-
- private final Derived sample75th;
- private final Derived sample95th;
- private final Derived sample98th;
- private final Derived sample99th;
- private final Derived sample999th;
+ private final long size;
static WrappedSnapshot create(Snapshot delegate) {
return new WrappedSnapshot(delegate);
}
private WrappedSnapshot(Snapshot delegate) {
- this.delegate = delegate;
- long[] values = delegate.getValues();
- samples = new Labeled[values.length];
-
- for (int i = 0; i < values.length; i++) {
- samples[i] = labeled(values[i]);
- }
+ Snapshot.PercentileValue[] percentileValues = delegate.percentileValues();
// We cannot access the weight of each sample to create a faithful array of WeightedSamples for each original sample,
// so we pre-store the typical calculations.
- median = derived(delegate.getMedian());
max = labeled(delegate.getMax());
- min = labeled(delegate.getMin());
mean = derived(delegate.getMean());
- stdDev = derived(delegate.getStdDev());
-
- sample75th = derived(delegate.get75thPercentile());
- sample95th = derived(delegate.get95thPercentile());
- sample98th = derived(delegate.get98thPercentile());
- sample99th = derived(delegate.get99thPercentile());
- sample999th = derived(delegate.get999thPercentile());
- }
-
- @Override
- public Derived value(double quantile) {
- return derived(delegate.getValue(quantile));
- }
-
- @Override
- public Derived median() {
- return median;
- }
-
- @Override
- public Derived sample75thPercentile() {
- return sample75th;
- }
-
- @Override
- public Derived sample95thPercentile() {
- return sample95th;
- }
-
- @Override
- public Derived sample98thPercentile() {
- return sample98th;
- }
-
- @Override
- public Derived sample99thPercentile() {
- return sample99th;
- }
-
- @Override
- public Derived sample999thPercentile() {
- return sample999th;
+ size = percentileValues.length;
}
@Override
@@ -116,12 +56,7 @@ public Derived mean() {
}
@Override
- public Labeled min() {
- return min;
- }
-
- @Override
- public Derived stdDev() {
- return stdDev;
+ public long size() {
+ return size;
}
}
diff --git a/metrics/metrics/src/main/java/module-info.java b/metrics/metrics/src/main/java/module-info.java
index f27f7788006..fd2d9306fd3 100644
--- a/metrics/metrics/src/main/java/module-info.java
+++ b/metrics/metrics/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -38,6 +38,7 @@
requires java.management;
requires jakarta.json;
requires io.helidon.common.configurable;
+ requires transitive micrometer.core;
exports io.helidon.metrics;
diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/RegistryFactoryTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/RegistryFactoryTest.java
index 5a832d6fa67..87af4a1330f 100644
--- a/metrics/metrics/src/test/java/io/helidon/metrics/RegistryFactoryTest.java
+++ b/metrics/metrics/src/test/java/io/helidon/metrics/RegistryFactoryTest.java
@@ -62,15 +62,15 @@ static void createInstance() {
.build();
configured = RegistryFactory.create(config);
- baseUn = unconfigured.getRegistry(MetricRegistry.Type.BASE);
- appUn = unconfigured.getRegistry(MetricRegistry.Type.APPLICATION);
- vendorUn = unconfigured.getRegistry(MetricRegistry.Type.VENDOR);
+ baseUn = unconfigured.getRegistry(Registry.BASE_SCOPE);
+ appUn = unconfigured.getRegistry(Registry.APPLICATION_SCOPE);
+ vendorUn = unconfigured.getRegistry(Registry.VENDOR_SCOPE);
- base = configured.getRegistry(MetricRegistry.Type.BASE);
- app = configured.getRegistry(MetricRegistry.Type.APPLICATION);
- vendor = configured.getRegistry(MetricRegistry.Type.VENDOR);
+ base = configured.getRegistry(Registry.BASE_SCOPE);
+ app = configured.getRegistry(Registry.APPLICATION_SCOPE);
+ vendor = configured.getRegistry(Registry.VENDOR_SCOPE);
- vendorMod = ((io.helidon.metrics.RegistryFactory) configured).getARegistry(MetricRegistry.Type.VENDOR);
+ vendorMod = ((io.helidon.metrics.RegistryFactory) configured).getARegistry(Registry.VENDOR_SCOPE);
}
@Test
diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java b/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java
index 390ae2af25a..3a7533fe426 100644
--- a/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java
+++ b/metrics/metrics/src/test/java/io/helidon/metrics/RegistryTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2019, 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.
@@ -26,13 +26,11 @@
import org.eclipse.microprofile.metrics.MetricFilter;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
-import org.eclipse.microprofile.metrics.SimpleTimer;
import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
import org.hamcrest.core.IsSame;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -57,7 +55,7 @@ public class RegistryTest {
@BeforeAll
static void createInstance() {
- registry = new Registry(MetricRegistry.Type.BASE, RegistrySettings.create());
+ registry = new Registry(MetricRegistry.BASE_SCOPE, RegistrySettings.create());
}
@Test
@@ -77,11 +75,9 @@ void testSameIDDifferentType() {
void testSameNameDifferentTagsDifferentTypes() {
Metadata metadata1 = Metadata.builder()
.withName("counter2")
- .withType(MetricType.COUNTER)
.build();
Metadata metadata2 = Metadata.builder()
.withName("counter2")
- .withType(MetricType.TIMER)
.build();
registry.counter(metadata1, tag1);
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
@@ -93,16 +89,12 @@ void testSameNameDifferentTagsDifferentTypes() {
void testSameIDSameReuseDifferentOtherMetadata() {
Metadata metadata1 = Metadata.builder()
.withName("counter5")
- .withDisplayName("display name")
.withDescription("description")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
Metadata metadata2 = Metadata.builder()
.withName("counter5")
- .withDisplayName("OTHER display name")
- .withDescription("description")
- .withType(MetricType.COUNTER)
+ .withDescription("other description")
.withUnit(MetricUnits.NONE)
.build();
@@ -116,7 +108,6 @@ void testSameIDSameReuseDifferentOtherMetadata() {
void testRemovalOfExistingMetricByName() {
Metadata metadata = Metadata.builder()
.withName("counter6")
- .withType(MetricType.COUNTER)
.build();
registry.counter(metadata);
registry.counter(metadata, tag1);
@@ -131,7 +122,6 @@ void testRemovalOfExistingMetricByName() {
void testRemovalOfExistingMetricByMetricID() {
Metadata metadata = Metadata.builder()
.withName("counter7")
- .withType(MetricType.COUNTER)
.build();
registry.counter(metadata);
@@ -174,15 +164,15 @@ void testGetCounterAfterCreate() {
@Test
void testGetType() {
- assertThat("Registry type", registry.getType(), is(MetricRegistry.Type.BASE));
+ assertThat("Registry type", registry.getScope(), is(MetricRegistry.Type.BASE));
MetricRegistry vendorRegistry = io.helidon.metrics.api.RegistryFactory.getInstance()
- .getRegistry(MetricRegistry.Type.VENDOR);
- assertThat("Registry type for vendor registry", vendorRegistry.getType(), is(MetricRegistry.Type.VENDOR));
+ .getRegistry(MetricRegistry.VENDOR_SCOPE);
+ assertThat("Registry type for vendor registry", vendorRegistry.getScope(), is(MetricRegistry.VENDOR_SCOPE));
MetricRegistry baseRegistry = io.helidon.metrics.api.RegistryFactory.getInstance()
- .getRegistry(MetricRegistry.Type.BASE);
- assertThat("Registry type for base registry", baseRegistry.getType(), is(MetricRegistry.Type.BASE));
+ .getRegistry(MetricRegistry.BASE_SCOPE);
+ assertThat("Registry type for base registry", baseRegistry.getScope(), is(MetricRegistry.BASE_SCOPE));
}
@Test
@@ -209,7 +199,6 @@ void testGetMetricWithoutCreate() {
void testGetMetadata() {
Metadata metadata = Metadata.builder()
.withName("counter11")
- .withType(MetricType.COUNTER)
.build();
Counter counter = registry.counter(metadata, tag1);
@@ -243,10 +232,10 @@ void testGetMetricsWithFilterAndType() {
MetricID metricIDb = new MetricID("simpleTimer1", tag1);
Counter counter = registry.counter(metricIDa);
- SimpleTimer simpleTimer = registry.simpleTimer(metricIDb);
+ Timer simpleTimer = registry.timer(metricIDb);
- Map matchingMetrics = registry.getMetrics(SimpleTimer.class,
- new MetricNameFilter(metricIDb.getName()));
+ Map matchingMetrics = registry.getMetrics(Timer.class,
+ new MetricNameFilter(metricIDb.getName()));
assertThat("Metrics matching filter", matchingMetrics, hasEntry(metricIDb, simpleTimer));
assertThat("Metrics not matching filter", matchingMetrics, not(hasEntry(metricIDa, counter)));
}
diff --git a/metrics/pom.xml b/metrics/pom.xml
index f8cd6271d82..fe55a9edab9 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -35,6 +35,5 @@
trace-exemplar
api
service-api
- microprofile
diff --git a/microprofile/metrics-feature/pom.xml b/microprofile/metrics-feature/pom.xml
new file mode 100644
index 00000000000..a0cc2e00951
--- /dev/null
+++ b/microprofile/metrics-feature/pom.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+ io.helidon.microprofile
+ helidon-microprofile-project
+ 4.0.0-SNAPSHOT
+
+ 4.0.0
+
+ helidon-microprofile-metrics-feature
+
+ Helidon Metrics MicroProfile Web Feature
+
+
+ Web feature supporting the /metrics (or otherwise configured) endpoint
+
+
+
+
+ io.helidon.common
+ helidon-common-http
+
+
+ io.helidon.nima.observe
+ helidon-nima-observe
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+
+
+ io.helidon.metrics.microprofile
+ helidon-metrics-microprofile
+
+
+ io.micrometer
+ micrometer-core
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+
+
+ io.prometheus
+ simpleclient
+
+
+ io.prometheus
+ simpleclient_common
+
+
+ io.helidon.microprofile.cdi
+ helidon-microprofile-cdi
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+ io.helidon.nima.service-common
+ helidon-nima-service-common
+
+
+ io.helidon.microprofile.tests
+ helidon-microprofile-tests-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.helidon.config
+ helidon-config-metadata-processor
+ ${helidon.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microprofile/metrics-feature/src/main/java/io/helidon/microprofile/metrics/feature/feature/package-info.java b/microprofile/metrics-feature/src/main/java/io/helidon/microprofile/metrics/feature/feature/package-info.java
new file mode 100644
index 00000000000..28ea0dc368d
--- /dev/null
+++ b/microprofile/metrics-feature/src/main/java/io/helidon/microprofile/metrics/feature/feature/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * MicroProfile metrics web feature.
+ */
+package io.helidon.microprofile.metrics.feature.feature;
diff --git a/microprofile/metrics-feature/src/main/java/module-info.java b/microprofile/metrics-feature/src/main/java/module-info.java
new file mode 100644
index 00000000000..f3cce2f45e4
--- /dev/null
+++ b/microprofile/metrics-feature/src/main/java/module-info.java
@@ -0,0 +1,29 @@
+import io.helidon.microprofile.metrics.feature.feature.MetricsObserveProvider;
+import io.helidon.nima.observe.spi.ObserveProvider;
+
+/*
+ * 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.
+ */
+module io.helidon.metrics.microprofile.feature {
+
+ requires io.helidon.metrics.microprofile;
+ requires io.helidon.nima.observe;
+ requires io.helidon.nima.servicecommon;
+ requires simpleclient.common;
+
+ exports io.helidon.microprofile.metrics.feature.feature;
+
+ provides ObserveProvider with MetricsObserveProvider;
+}
\ No newline at end of file
diff --git a/microprofile/metrics-micrometer/pom.xml b/microprofile/metrics-micrometer/pom.xml
new file mode 100644
index 00000000000..c2b4fb2c825
--- /dev/null
+++ b/microprofile/metrics-micrometer/pom.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ io.helidon.microprofile
+ helidon-microprofile-project
+ 4.0.0-SNAPSHOT
+
+ 4.0.0
+
+ helidon-metrics-microprofile-micrometer
+
+ Helidon Metrics MicroProfile Implementation using Micrometer
+
+
+ Micrometer-specific implementations of the MicroProfile Metrics interfaces
+
+
+
+
+ io.helidon.common
+ helidon-common-http
+
+
+ org.eclipse.microprofile.metrics
+ microprofile-metrics-api
+
+
+ io.helidon.microprofile.config
+ helidon-microprofile-config
+
+
+ io.micrometer
+ micrometer-core
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+ true
+
+
+ io.prometheus
+ simpleclient
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+ io.helidon.config
+ helidon-config-metadata-processor
+ ${helidon.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microprofile/metrics-micrometer/src/main/java/io/helidon/metrics/microprofile/package-info.java b/microprofile/metrics-micrometer/src/main/java/io/helidon/metrics/microprofile/package-info.java
new file mode 100644
index 00000000000..0a6e34b556b
--- /dev/null
+++ b/microprofile/metrics-micrometer/src/main/java/io/helidon/metrics/microprofile/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implementation of MicroProfile Metrics interfaces.
+ */
+package io.helidon.metrics.microprofile;
diff --git a/microprofile/metrics-micrometer/src/main/java/module-info.java b/microprofile/metrics-micrometer/src/main/java/module-info.java
new file mode 100644
index 00000000000..ecf469d854d
--- /dev/null
+++ b/microprofile/metrics-micrometer/src/main/java/module-info.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.
+ */
+
+/**
+ * Helidon MicroProfile Metrics implementation
+ */
+module io.helidon.metrics.microprofile {
+
+ requires microprofile.metrics.api;
+ requires microprofile.config.api;
+ requires micrometer.core;
+ requires io.helidon.common;
+ requires micrometer.registry.prometheus;
+ requires java.management;
+ requires io.helidon.common.media.type;
+ requires simpleclient.common;
+
+ exports io.helidon.metrics.microprofile;
+
+ //requires micrometer.registry.prometheus;
+}
\ No newline at end of file
diff --git a/microprofile/metrics/pom.xml b/microprofile/metrics/pom.xml
index a4e22150195..ce16519d6b6 100644
--- a/microprofile/metrics/pom.xml
+++ b/microprofile/metrics/pom.xml
@@ -60,10 +60,26 @@
io.helidon.nima.observe
helidon-nima-observe-metrics
+
+
io.helidon.metrics
helidon-metrics
+
+
+
+ io.micrometer
+ micrometer-core
+
+
org.eclipse.microprofile.metrics
microprofile-metrics-api
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/BaseRegistry.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/BaseRegistry.java
new file mode 100644
index 00000000000..73c87d18b68
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/BaseRegistry.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2018, 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.microprofile.metrics;
+
+import java.lang.management.ClassLoadingMXBean;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.lang.management.ThreadMXBean;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.ToDoubleFunction;
+
+import io.helidon.metrics.api.RegistryFactory;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.MetricUnits;
+import org.eclipse.microprofile.metrics.Tag;
+
+/**
+ * Registry for base metrics as required by Microprofile metrics specification.
+ *
+ *
+ * - All "General JVM Stats" are supported (section 4.1. of the spec).
+ * - All "Thread JVM Stats" are supported (section 4.2. of the spec).
+ * - NONE of "Thread Pool Stats" are supported (section 4.3. of the spec) - Vendor specific approach.
+ * - All "ClassLoading JVM Stats" are supported (section 4.4. of the spec).
+ * - Available Processors and System Load Average (where available from JVM) metrics from "Operating System"
+ * (section 4.5 of the spec).
+ *
+ *
+ */
+final class BaseRegistry extends MpMetricRegistry {
+
+ private static final Tag[] NO_TAGS = new Tag[0];
+
+ private static final Metadata MEMORY_USED_HEAP = Metadata.builder()
+ .withName("memory.usedHeap")
+ .withDescription("Displays the amount of used heap memory in bytes.")
+ .withUnit(MetricUnits.BYTES)
+ .build();
+
+ private static final Metadata MEMORY_COMMITTED_HEAP = Metadata.builder()
+ .withName("memory.committedHeap")
+ .withDescription(
+ "Displays the amount of memory in bytes that is "
+ + "committed for the Java virtual "
+ + "machine to use. This amount of memory is "
+ + "guaranteed for the Java virtual "
+ + "machine to use.")
+ .withUnit(MetricUnits.BYTES)
+ .build();
+
+ private static final Metadata MEMORY_MAX_HEAP = Metadata.builder()
+ .withName("memory.maxHeap")
+ .withDescription(
+ "Displays the maximum amount of heap memory in bytes that can"
+ + " be used for "
+ + "memory management. This attribute displays -1 if "
+ + "the maximum heap "
+ + "memory size is undefined. This amount of memory is "
+ + "not guaranteed to be "
+ + "available for memory management if it is greater "
+ + "than the amount of "
+ + "committed memory. The Java virtual machine may fail"
+ + " to allocate memory "
+ + "even if the amount of used memory does not exceed "
+ + "this maximum size.")
+ .withUnit(MetricUnits.BYTES)
+ .build();
+
+ private static final Metadata JVM_UPTIME = Metadata.builder()
+ .withName("jvm.uptime")
+ .withDescription(
+ "Displays the start time of the Java virtual machine in "
+ + "milliseconds. This "
+ + "attribute displays the approximate time when the Java "
+ + "virtual machine "
+ + "started.")
+ .withUnit(MetricUnits.MILLISECONDS)
+ .build();
+
+ private static final Metadata THREAD_COUNT = Metadata.builder()
+ .withName("thread.count")
+ .withDescription("Displays the current number of live threads including both "
+ + "daemon and nondaemon threads")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata THREAD_DAEMON_COUNT = Metadata.builder()
+ .withName("thread.daemon.count")
+ .withDescription("Displays the current number of live daemon threads.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata THREAD_MAX_COUNT = Metadata.builder()
+ .withName("thread.max.count")
+ .withDescription("Displays the peak live thread count since the Java "
+ + "virtual machine started or "
+ + "peak was reset. This includes daemon and "
+ + "non-daemon threads.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata CL_LOADED_COUNT = Metadata.builder()
+ .withName("classloader.loadedClasses.count")
+ .withDescription("Displays the number of classes that are currently loaded in "
+ + "the Java virtual machine.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata CL_LOADED_TOTAL = Metadata.builder()
+ .withName("classloader.loadedClasses.total")
+ .withDescription("Displays the total number of classes that have been loaded "
+ + "since the Java virtual machine has started execution.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata CL_UNLOADED_COUNT = Metadata.builder()
+ .withName("classloader.unloadedClasses.total")
+ .withDescription("Displays the total number of classes unloaded since the Java "
+ + "virtual machine has started execution.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata OS_AVAILABLE_CPU = Metadata.builder()
+ .withName("cpu.availableProcessors")
+ .withDescription("Displays the number of processors available to the Java "
+ + "virtual machine. This "
+ + "value may change during a particular invocation of"
+ + " the virtual machine.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ private static final Metadata OS_LOAD_AVERAGE = Metadata.builder()
+ .withName("cpu.systemLoadAverage")
+ .withDescription("Displays the system load average for the last minute. The "
+ + "system load average "
+ + "is the sum of the number of runnable entities "
+ + "queued to the available "
+ + "processors and the number of runnable entities "
+ + "running on the available "
+ + "processors averaged over a period of time. The way "
+ + "in which the load average "
+ + "is calculated is operating system specific but is "
+ + "typically a damped timedependent "
+ + "average. If the load average is not available, a "
+ + "negative value is "
+ + "displayed. This attribute is designed to provide a "
+ + "hint about the system load "
+ + "and may be queried frequently. The load average may"
+ + " be unavailable on some "
+ + "platforms where it is expensive to implement this "
+ + "method.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+
+ static MpMetricRegistry create(MeterRegistry meterRegistry) {
+
+ BaseRegistry result = new BaseRegistry(meterRegistry);
+
+ MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
+
+ // load all base metrics
+ register(result, MEMORY_USED_HEAP, memoryBean.getHeapMemoryUsage(), MemoryUsage::getUsed);
+ register(result, MEMORY_COMMITTED_HEAP, memoryBean.getHeapMemoryUsage(), MemoryUsage::getCommitted);
+ register(result, MEMORY_MAX_HEAP, memoryBean.getHeapMemoryUsage(), MemoryUsage::getMax);
+
+ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
+ register(result, JVM_UPTIME, runtimeBean, RuntimeMXBean::getUptime);
+
+ ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+ register(result, THREAD_COUNT, threadBean, ThreadMXBean::getThreadCount);
+ register(result, THREAD_DAEMON_COUNT, threadBean, ThreadMXBean::getDaemonThreadCount);
+ register(result, THREAD_MAX_COUNT, threadBean, ThreadMXBean::getPeakThreadCount);
+
+ ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean();
+ register(result, CL_LOADED_COUNT, clBean, ClassLoadingMXBean::getLoadedClassCount);
+ registerCounter(result, CL_LOADED_TOTAL, clBean, ClassLoadingMXBean::getTotalLoadedClassCount);
+ registerCounter(result, CL_UNLOADED_COUNT, clBean, ClassLoadingMXBean::getUnloadedClassCount);
+
+ OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
+ register(result, OS_AVAILABLE_CPU, osBean, OperatingSystemMXBean::getAvailableProcessors);
+ register(result, OS_LOAD_AVERAGE, osBean, OperatingSystemMXBean::getSystemLoadAverage);
+
+ List gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
+ for (GarbageCollectorMXBean gcBean : gcBeans) {
+ String poolName = gcBean.getName();
+ registerCounter(result, gcCountMeta(), gcBean, GarbageCollectorMXBean::getCollectionCount,
+ new Tag("name", poolName));
+ register(result, gcTimeMeta(), gcBean, GarbageCollectorMXBean::getCollectionTime,
+ new Tag("name", poolName));
+ }
+
+ return result;
+ }
+
+ private static Metadata gcTimeMeta() {
+ return Metadata.builder()
+ .withName("gc.time")
+ .withDescription(
+ "Displays the approximate accumulated collection elapsed time in milliseconds. "
+ + "This attribute displays -1 if the collection elapsed time is undefined for this "
+ + "collector. The Java virtual machine implementation may use a high resolution "
+ + "timer to measure the elapsed time. This attribute may display the same value "
+ + "even if the collection count has been incremented if the collection elapsed "
+ + "time is very short.")
+ .withUnit(MetricUnits.MILLISECONDS)
+ .build();
+ }
+
+ private static Metadata gcCountMeta() {
+ return Metadata.builder()
+ .withName("gc.total")
+ .withDescription(
+ "Displays the total number of collections that have occurred. This attribute lists "
+ + "-1 if the collection count is undefined for this collector.")
+ .withUnit(MetricUnits.NONE)
+ .build();
+ }
+
+ private static void register(BaseRegistry registry,
+ Metadata meta,
+ T object,
+ Function func,
+ Tag... tags) {
+ registry.gauge(meta, object, func, tags);
+ }
+
+ private static void registerCounter(BaseRegistry registry,
+ Metadata meta,
+ T object,
+ ToDoubleFunction func,
+ Tag... tags) {
+ registry.counter(meta, object, func, tags);
+ }
+
+ private static void register(BaseRegistry registry, Metadata meta, T object, Function func) {
+ register(registry, meta, object, func, NO_TAGS);
+ }
+
+ private BaseRegistry(MeterRegistry meterRegistry) {
+ super(RegistryFactory.BASE_SCOPE, meterRegistry);
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/GlobalTagsHelper.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/GlobalTagsHelper.java
new file mode 100644
index 00000000000..271291f8abb
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/GlobalTagsHelper.java
@@ -0,0 +1,130 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import io.micrometer.core.instrument.Tag;
+
+/**
+ * Manages retrieving and dispensing global tags.
+ *
+ * The internal tags data starts as Optional.empty(). Once {@link #globalTags(String)} has been invoked the internal tags
+ * data Optional will no longer be empty, but the Tag[] it holds might be zero-length if there are no global tags to be
+ * concerned with.
+ *
+ */
+class GlobalTagsHelper {
+
+ // Static instance is used normally at runtime to share a single set of global tags across possibly multiple metric
+ // registries. Otherwise, regular instances of the helper are typically created only for testing.
+ private static final GlobalTagsHelper INSTANCE = new GlobalTagsHelper();
+
+ private Optional tags = Optional.empty();
+
+ /**
+ * Sets the tags for normal use.
+ *
+ * @param tagExpression tag assignments
+ */
+ static void globalTags(String tagExpression) {
+ INSTANCE.tags(tagExpression);
+ }
+
+ /**
+ * Retrieves the tags for normal use.
+ *
+ * @return tags derived from the earlier assignment
+ */
+ static Optional globalTags() {
+ return INSTANCE.tags();
+ }
+
+ /**
+ * For testing or internal use; sets the tags according to the comma-separate list of "name=value" settings.
+ *
+ * The expression can contain escaped "=" and "," characters.
+ *
+ *
+ * @param tagExpression tag assignments
+ */
+ Optional tags(String tagExpression) {
+
+ // The following regex splits on non-escaped commas.
+ String[] assignments = tagExpression.split("(? problems = new ArrayList<>();
+
+ for (String assignment : assignments) {
+ List assignmentProblems = new ArrayList<>();
+ if (assignment.isBlank()) {
+ assignmentProblems.add("empty assignment found at position " + position + ": " + tagExpression);
+ } else {
+
+ // The following regex splits on non-escaped equals signs.
+ String[] parts = assignment.split("(? tags() {
+ return tags;
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java
deleted file mode 100644
index 4489fe4ad04..00000000000
--- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorConcurrentGauge.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2019, 2022 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.microprofile.metrics;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import jakarta.annotation.Priority;
-import jakarta.enterprise.util.AnnotationLiteral;
-import jakarta.interceptor.Interceptor;
-import jakarta.interceptor.InterceptorBinding;
-import org.eclipse.microprofile.metrics.ConcurrentGauge;
-
-/**
- * Interceptor for {@link ConcurrentGauge} annotation.
- */
-@InterceptorConcurrentGauge.Binding
-@Interceptor
-@Priority(Interceptor.Priority.PLATFORM_BEFORE + 11)
-final class InterceptorConcurrentGauge extends MetricsInterceptorBase.WithPostCompletion {
- InterceptorConcurrentGauge() {
- super(org.eclipse.microprofile.metrics.annotation.ConcurrentGauge.class, ConcurrentGauge.class);
- }
-
- static Binding.Literal binding() {
- return Binding.Literal.instance();
- }
-
- @Override
- void preInvoke(ConcurrentGauge metric) {
- metric.inc();
- }
-
- @Override
- void postComplete(ConcurrentGauge metric) {
- metric.dec();
- }
- @Inherited
- @InterceptorBinding
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @org.eclipse.microprofile.metrics.annotation.ConcurrentGauge
- @interface Binding {
- class Literal extends AnnotationLiteral implements Binding {
-
- private static final long serialVersionUID = 1L;
-
- private static final Literal INSTANCE = new Literal();
-
- static Literal instance() {
- return INSTANCE;
- }
-
- private Literal() {
- }
- }
- }
-}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java
deleted file mode 100644
index e60c7b4e485..00000000000
--- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorMetered.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2018, 2022 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.microprofile.metrics;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import jakarta.annotation.Priority;
-import jakarta.enterprise.util.AnnotationLiteral;
-import jakarta.interceptor.Interceptor;
-import jakarta.interceptor.InterceptorBinding;
-import org.eclipse.microprofile.metrics.Meter;
-import org.eclipse.microprofile.metrics.annotation.Metered;
-
-/**
- * Interceptor for {@link Metered} annotation.
- */
-@InterceptorMetered.Binding
-@Interceptor
-@Priority(Interceptor.Priority.PLATFORM_BEFORE + 9)
-final class InterceptorMetered extends MetricsInterceptorBase {
-
- InterceptorMetered() {
- super(Metered.class, Meter.class);
- }
-
- static Binding.Literal binding() {
- return Binding.Literal.instance();
- }
- @Override
- void preInvoke(Meter metric) {
- metric.mark();
- }
- @Inherited
- @InterceptorBinding
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Metered
- @interface Binding {
- class Literal extends AnnotationLiteral implements Binding {
-
- private static final long serialVersionUID = 1L;
-
- private static final Literal INSTANCE = new Literal();
-
- static Literal instance() {
- return INSTANCE;
- }
-
- private Literal() {
- }
- }
- }
-}
-
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java
deleted file mode 100644
index 628fd2ddf93..00000000000
--- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSimplyTimed.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (c) 2020, 2022 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.microprofile.metrics;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import jakarta.annotation.Priority;
-import jakarta.enterprise.util.AnnotationLiteral;
-import jakarta.interceptor.Interceptor;
-import jakarta.interceptor.InterceptorBinding;
-import org.eclipse.microprofile.metrics.SimpleTimer;
-import org.eclipse.microprofile.metrics.annotation.Metered;
-import org.eclipse.microprofile.metrics.annotation.SimplyTimed;
-
-/**
- * Interceptor for {@link SimplyTimed} annotation.
- *
- * Note that this interceptor fires only for explicit {@code SimplyTimed} annotations.
- * The CDI extension adds synthetic {@code SimplyTimed} annotations to each JAX-RS
- * method, and the separate {@link InterceptorSyntheticRestRequest} interceptor deals with those.
- *
- */
-@InterceptorSimplyTimed.Binding
-@Interceptor
-@Priority(Interceptor.Priority.PLATFORM_BEFORE + 10)
-final class InterceptorSimplyTimed extends InterceptorTimedBase {
-
- InterceptorSimplyTimed() {
- super(SimplyTimed.class, SimpleTimer.class);
- }
-
- static Binding.Literal binding() {
- return Binding.Literal.instance();
- }
-
- @Override
- void postComplete(SimpleTimer metric) {
- metric.update(duration());
- }
-
- @Inherited
- @InterceptorBinding
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Metered
- @interface Binding {
- class Literal extends AnnotationLiteral implements Binding {
-
- private static final long serialVersionUID = 1L;
-
- private static final Literal INSTANCE = new Literal();
-
- static Literal instance() {
- return INSTANCE;
- }
-
- private Literal() {
- }
- }
- }
-}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticRestRequest.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticRestRequest.java
index 09b0bf2b6af..0824cf31a7a 100644
--- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticRestRequest.java
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/InterceptorSyntheticRestRequest.java
@@ -72,8 +72,8 @@ public Iterable workItems(Executable executable) {
@Override
public void preInvocation(InvocationContext context, SyntheticRestRequestWorkItem workItem) {
- MetricsInterceptorBase.verifyMetric(workItem.successfulSimpleTimerMetricID(),
- workItem.successfulSimpleTimer());
+ MetricsInterceptorBase.verifyMetric(workItem.successfulTimerMetricID(),
+ workItem.successfulTimer());
MetricsInterceptorBase.verifyMetric(workItem.unmappedExceptionCounterMetricID(),
workItem.unmappedExceptionCounter());
if (LOGGER.isLoggable(Level.TRACE)) {
@@ -104,7 +104,7 @@ void updateRestRequestMetrics(ServerResponse serverResponse, Throwable throwable
// Because our SimpleTimer implementation does not update the metric if the elapsed time is 0, make sure to record
// a duration of at least 1 nanosecond.
long elapsedNanos = endNanos > startNanos ? endNanos - startNanos : 1;
- workItem.successfulSimpleTimer().update(Duration.ofNanos(elapsedNanos));
+ workItem.successfulTimer().update(Duration.ofNanos(elapsedNanos));
} else {
workItem.unmappedExceptionCounter().inc();
}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java
index 3b26c541d77..4c13b7d9a7f 100644
--- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsCdiExtension.java
@@ -44,7 +44,6 @@
import io.helidon.config.ConfigValue;
import io.helidon.config.mp.MpConfig;
import io.helidon.metrics.api.MetricsSettings;
-import io.helidon.metrics.api.RegistryFactory;
import io.helidon.microprofile.metrics.MetricAnnotationInfo.RegistrationPrep;
import io.helidon.microprofile.metrics.MetricUtil.LookupResult;
import io.helidon.microprofile.metrics.spi.MetricAnnotationDiscoveryObserver;
@@ -90,15 +89,11 @@
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
-import org.eclipse.microprofile.metrics.SimpleTimer;
import org.eclipse.microprofile.metrics.Tag;
-import org.eclipse.microprofile.metrics.annotation.ConcurrentGauge;
+import org.eclipse.microprofile.metrics.Timer;
import org.eclipse.microprofile.metrics.annotation.Counted;
import org.eclipse.microprofile.metrics.annotation.Gauge;
-import org.eclipse.microprofile.metrics.annotation.Metered;
-import org.eclipse.microprofile.metrics.annotation.SimplyTimed;
import org.eclipse.microprofile.metrics.annotation.Timed;
import static jakarta.interceptor.Interceptor.Priority.LIBRARY_BEFORE;
@@ -122,19 +117,16 @@
*
*/
public class MetricsCdiExtension extends HelidonRestCdiExtension {
-
+// TODO change above to use the real MetricsFeature, not the temporary MP-specific one.
private static final System.Logger LOGGER = System.getLogger(MetricsCdiExtension.class.getName());
static final Set> ALL_METRIC_ANNOTATIONS = Set.of(
- Counted.class, Metered.class, Timed.class, ConcurrentGauge.class, SimplyTimed.class, Gauge.class);
+ Counted.class, Timed.class, Gauge.class); // There is no annotation for histograms.
private static final Map, AnnotationLiteral>> INTERCEPTED_METRIC_ANNOTATIONS =
Map.of(
Counted.class, InterceptorCounted.binding(),
- Metered.class, InterceptorMetered.binding(),
- Timed.class, InterceptorTimed.binding(),
- ConcurrentGauge.class, InterceptorConcurrentGauge.binding(),
- SimplyTimed.class, InterceptorSimplyTimed.binding());
+ Timed.class, InterceptorTimed.binding());
private static final List> JAX_RS_ANNOTATIONS
= Arrays.asList(GET.class, PUT.class, POST.class, HEAD.class, OPTIONS.class, DELETE.class, PATCH.class);
@@ -150,30 +142,26 @@ public class MetricsCdiExtension extends HelidonRestCdiExtension
static final String REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME = "rest-request.enabled";
private static final boolean REST_ENDPOINTS_METRIC_ENABLED_DEFAULT_VALUE = false;
- static final String SYNTHETIC_SIMPLE_TIMER_METRIC_NAME = "REST.request";
+ static final String SYNTHETIC_TIMER_METRIC_NAME = "REST.request";
static final String SYNTHETIC_SIMPLE_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME =
- SYNTHETIC_SIMPLE_TIMER_METRIC_NAME + ".unmappedException.total";
+ SYNTHETIC_TIMER_METRIC_NAME + ".unmappedException.total";
static final Metadata SYNTHETIC_SIMPLE_TIMER_METADATA = Metadata.builder()
- .withName(SYNTHETIC_SIMPLE_TIMER_METRIC_NAME)
- .withDisplayName("Total Requests and Response Time")
+ .withName(SYNTHETIC_TIMER_METRIC_NAME)
.withDescription("""
The number of invocations and total response time of this RESTful resource method since the \
start of the server. The metric will not record the elapsed time nor count of a REST \
request if it resulted in an unmapped exception. Also tracks the highest recorded time \
duration within the previous completed full minute and lowest recorded time duration within \
the previous completed full minute.""")
- .withType(MetricType.SIMPLE_TIMER)
.withUnit(MetricUnits.NANOSECONDS)
.build();
static final Metadata SYNTHETIC_SIMPLE_TIMER_UNMAPPED_EXCEPTION_METADATA = Metadata.builder()
.withName(SYNTHETIC_SIMPLE_TIMER_METRIC_UNMAPPED_EXCEPTION_NAME)
- .withDisplayName("Total Unmapped Exceptions count")
.withDescription("""
The total number of unmapped exceptions that occur from this RESTful resouce method since \
the start of the server.""")
- .withType(MetricType.COUNTER)
.withUnit(MetricUnits.NONE)
.build();
@@ -366,10 +354,7 @@ void before(@Observes BeforeBeanDiscovery discovery) {
discovery.addAnnotatedType(RegistryProducer.class, RegistryProducer.class.getName());
discovery.addAnnotatedType(MetricProducer.class, MetricProducer.class.getName());
discovery.addAnnotatedType(InterceptorCounted.class, InterceptorCounted.class.getName());
- discovery.addAnnotatedType(InterceptorMetered.class, InterceptorMetered.class.getName());
discovery.addAnnotatedType(InterceptorTimed.class, InterceptorTimed.class.getName());
- discovery.addAnnotatedType(InterceptorConcurrentGauge.class, InterceptorConcurrentGauge.class.getName());
- discovery.addAnnotatedType(InterceptorSimplyTimed.class, InterceptorSimplyTimed.class.getName());
// Telling CDI about our private SyntheticRestRequest annotation and its interceptor
// is enough for CDI to intercept invocations of methods so annotated.
@@ -399,11 +384,7 @@ public void clearAnnotationInfo(@Observes AfterDeploymentValidation adv) {
* @param pat ProcessAnnotatedType event
*/
private void recordMetricAnnotatedClass(@Observes @Priority(Interceptor.Priority.APPLICATION + 500 + 10)
- @WithAnnotations({Counted.class,
- Metered.class,
- Timed.class,
- ConcurrentGauge.class,
- SimplyTimed.class}) ProcessAnnotatedType> pat) {
+ @WithAnnotations({Counted.class, Timed.class}) ProcessAnnotatedType> pat) {
if (isConcreteNonInterceptor(pat)) {
recordAnnotatedType(pat);
recordStereotypes(pat);
@@ -606,18 +587,18 @@ private void recordSimplyTimedForRestResources(@Observes
}
/**
- * Creates or looks up the {@code SimpleTimer} instance for measuring REST requests on any JAX-RS method.
+ * Creates or looks up the {@code Timer} instance for measuring REST requests on any JAX-RS method.
*
- * @param method the {@code Method} for which the SimpleTimer instance is needed
- * @return the located or created {@code SimpleTimer}
+ * @param method the {@code Method} for which the Timer instance is needed
+ * @return the located or created {@code Timer}
*/
- static SimpleTimer restEndpointSimpleTimer(Method method) {
- // By spec, the synthetic SimpleTimers are always in the base registry.
+ static Timer restEndpointTimer(Method method) {
+ // By spec, the synthetic Timers are always in the base registry.
LOGGER.log(Level.DEBUG,
() -> String.format("Registering synthetic SimpleTimer for %s#%s", method.getDeclaringClass().getName(),
method.getName()));
return getRegistryForSyntheticRestRequestMetrics()
- .simpleTimer(SYNTHETIC_SIMPLE_TIMER_METADATA, syntheticRestRequestMetricTags(method));
+ .timer(SYNTHETIC_SIMPLE_TIMER_METADATA, syntheticRestRequestMetricTags(method));
}
/**
@@ -636,20 +617,20 @@ static Counter restEndpointCounter(Method method) {
private void registerAndSaveRestRequestMetrics(Method method) {
workItemsManager.put(method, SyntheticRestRequest.class,
- SyntheticRestRequestWorkItem.create(restEndpointSimpleTimerMetricID(method),
- restEndpointSimpleTimer(method),
+ SyntheticRestRequestWorkItem.create(restEndpointTimerMetricID(method),
+ restEndpointTimer(method),
restEndpointCounterMetricID(method),
restEndpointCounter(method)));
}
/**
- * Creates the {@link MetricID} for the synthetic {@link SimplyTimed} metric we add to each JAX-RS method.
+ * Creates the {@link MetricID} for the synthetic {@link Timed} metric we add to each JAX-RS method.
*
* @param method Java method of interest
* @return {@code MetricID} for the simpletimer for this Java method
*/
- static MetricID restEndpointSimpleTimerMetricID(Method method) {
- return new MetricID(SYNTHETIC_SIMPLE_TIMER_METRIC_NAME, syntheticRestRequestMetricTags(method));
+ static MetricID restEndpointTimerMetricID(Method method) {
+ return new MetricID(SYNTHETIC_TIMER_METRIC_NAME, syntheticRestRequestMetricTags(method));
}
/**
@@ -766,7 +747,7 @@ public HttpRules registerService(@Observes @Priority(LIBRARY_BEFORE + 10) @Initi
});
// registry factory is available in global
- Contexts.globalContext().register(RegistryFactory.getInstance());
+ Contexts.globalContext().register(MpRegistryFactory.getInstance());
return defaultRouting;
}
@@ -889,13 +870,11 @@ private void registerAnnotatedGauges(BeanManager bm) {
} else {
Metadata md = Metadata.builder()
.withName(gaugeID.getName())
- .withDisplayName(gaugeAnnotation.displayName())
.withDescription(gaugeAnnotation.description())
- .withType(MetricType.GAUGE)
.withUnit(gaugeAnnotation.unit())
.build();
LOGGER.log(Level.DEBUG, () -> String.format("Registering gauge with metadata %s", md.toString()));
- registry.register(md, dg, gaugeID.getTagsAsList().toArray(new Tag[0]));
+ registry.gauge(md, dg, DelegatingGauge::getValue, gaugeID.getTagsAsList().toArray(new Tag[0]));
}
} catch (Throwable t) {
gaugeProblems.add(new IllegalArgumentException(
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsObserveProvider.java.save b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsObserveProvider.java.save
new file mode 100644
index 00000000000..802db19f3f7
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsObserveProvider.java.save
@@ -0,0 +1,89 @@
+/*
+ * 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.microprofile.metrics;
+
+import io.helidon.common.http.Http;
+import io.helidon.config.Config;
+import io.helidon.nima.observe.spi.ObserveProvider;
+import io.helidon.nima.webserver.http.HttpRouting;
+
+/**
+ * Observe provider for MP metrics.
+ */
+public class MetricsObserveProvider implements ObserveProvider {
+
+ private final MpMetricsFeature feature;
+
+ /**
+ * Do not use - required by service loader.
+ *
+ * @deprecated use {@link #create}
+ */
+ @Deprecated
+ public MetricsObserveProvider() {
+ this(null);
+ }
+
+ private MetricsObserveProvider(MpMetricsFeature feature) {
+ this.feature = feature;
+ }
+
+ /**
+ * Creates a new provider instance, also creating a new feature instance to use.
+ *
+ * @return new provider
+ */
+ public static ObserveProvider create() {
+ return create(MpMetricsFeature.create());
+ }
+
+ /**
+ * Creates a new provider instance using an existing feature instance.
+ *
+ * @param feature feature instance the provider uses
+ * @return new provider
+ */
+ public static ObserveProvider create(MpMetricsFeature feature) {
+ return new MetricsObserveProvider(feature);
+ }
+
+ @Override
+ public String configKey() {
+ return "mp.metrics";
+ }
+
+ @Override
+ public String defaultEndpoint() {
+ return feature == null ? "metrics" : feature.configuredContext();
+ }
+
+ @Override
+ public void register(Config config, String componentPath, HttpRouting.Builder routing) {
+ MpMetricsFeature observer = feature == null
+ ? MpMetricsFeature.builder().webContext(componentPath)
+ .config(config)
+ .build()
+ : feature;
+
+ if (observer.enabled()) {
+ observer.context(componentPath);
+ routing.addFeature(observer);
+ } else {
+ routing.get(componentPath + "/*", (req, resp) -> resp.status(Http.Status.SERVICE_UNAVAILABLE_503)
+ .send());
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpCounter.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpCounter.java
new file mode 100644
index 00000000000..3bcc2d2d06e
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpCounter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.microprofile.metrics;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+
+/**
+ * Implementation of {@link org.eclipse.microprofile.metrics.Counter}.
+ */
+public class MpCounter extends MpMetric implements org.eclipse.microprofile.metrics.Counter {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate meter which actually records data
+ */
+ MpCounter(Counter delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ @Override
+ public void inc() {
+ delegate().increment();
+ }
+
+ @Override
+ public void inc(long n) {
+ delegate().increment(n);
+ }
+
+ @Override
+ public long getCount() {
+ return (long) delegate().count();
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpFunctionCounter.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpFunctionCounter.java
new file mode 100644
index 00000000000..c83358b196f
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpFunctionCounter.java
@@ -0,0 +1,97 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.function.ToDoubleFunction;
+
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.Measurement;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tags;
+import org.eclipse.microprofile.metrics.Counter;
+
+class MpFunctionCounter extends MpMetric implements Meter, Counter {
+
+ static Builder builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoubleFunction fn) {
+ return new Builder<>(mpMetricRegistry, name, origin, fn);
+ }
+
+ private MpFunctionCounter(Builder> builder) {
+ super(delegate(builder), builder.mpMetricRegistry.meterRegistry());
+ }
+
+ @Override
+ public Id getId() {
+ return delegate().getId();
+ }
+
+ @Override
+ public Iterable measure() {
+ return delegate().measure();
+ }
+
+ @Override
+ public void inc() {
+ throw new UnsupportedOperationException("Not allowed on " + MpFunctionCounter.class.getName());
+ }
+
+ @Override
+ public void inc(long l) {
+ throw new UnsupportedOperationException("Not allowed on " + MpFunctionCounter.class.getName());
+ }
+
+ @Override
+ public long getCount() {
+ return (long) delegate().count();
+ }
+
+ private static FunctionCounter delegate(Builder builder) {
+ return builder.mpMetricRegistry
+ .meterRegistry()
+ .more()
+ .counter(builder.name,
+ builder.tags,
+ builder.origin,
+ builder.fn);
+ }
+
+ static class Builder implements io.helidon.common.Builder, MpFunctionCounter> {
+
+ private final MpMetricRegistry mpMetricRegistry;
+
+ private final String name;
+ private final ToDoubleFunction fn;
+ private final T origin;
+ private Tags tags;
+
+ private Builder(MpMetricRegistry mpMetricRegistry, String name, T origin, ToDoubleFunction fn) {
+ this.mpMetricRegistry = mpMetricRegistry;
+ this.name = name;
+ this.origin = origin;
+ this.fn = fn;
+ }
+
+ Builder tags(Tags tags) {
+ this.tags = tags;
+ return this;
+ }
+
+ @Override
+ public MpFunctionCounter build() {
+ return new MpFunctionCounter(this);
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpGauge.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpGauge.java
new file mode 100644
index 00000000000..1bd68548666
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpGauge.java
@@ -0,0 +1,92 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+
+abstract class MpGauge extends MpMetric implements org.eclipse.microprofile.metrics.Gauge {
+
+ /*
+ * The MicroProfile metrics API parameterizes its gauge type as Gauge which is the type of
+ * value the gauge reports via its getValue() method. To register a gauge, the developer passes us a function-plus-target or
+ * a supplier with the return value from the function or supplier similarly parameterized with the subtype of Number.
+ *
+ * On the other hand, each Micrometer gauge is not parameterized and reports a double value.
+ *
+ * As a result, we do not have what we need to (easily) instantiate the correct subtype of Number, set its value based on
+ * the Micrometer delegate's value() result, and return the correctly-typed and -assigned value from our getValue() method.
+ *
+ * To work around this, we keep track ourselves of the function and target or supplier which report the gauge value.
+ * Then our getValue() implementation simply invokes the function on the target or the supplier rather than delegating to
+ * the Micrometer gauge value() method (which would do exactly the same thing anyway).
+ *
+ * This way the typing works out (with the expected unchecked cast).
+ */
+ private MpGauge(Gauge delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ static FunctionBased create(T target,
+ Function function,
+ Gauge delegate,
+ MeterRegistry meterRegistry) {
+ return new FunctionBased<>(target, function, delegate, meterRegistry);
+ }
+
+ static SupplierBased create(Supplier supplier,
+ Gauge delegate,
+ MeterRegistry meterRegistry) {
+ return new SupplierBased<>(supplier, delegate, meterRegistry);
+ }
+
+ static class FunctionBased extends MpGauge {
+
+ private final T target;
+ private final Function function;
+
+
+
+ private FunctionBased(T target, Function function, Gauge delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ this.target = target;
+ this.function = function;
+ }
+
+ @Override
+ public N getValue() {
+ return (N) function.apply(target);
+ }
+ }
+
+ static class SupplierBased extends MpGauge {
+
+ private final Supplier supplier;
+
+ private SupplierBased(Supplier supplier, Gauge delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ this.supplier = supplier;
+ }
+
+ @Override
+ public N getValue() {
+ return supplier.get();
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpHistogram.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpHistogram.java
new file mode 100644
index 00000000000..dbee331a095
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpHistogram.java
@@ -0,0 +1,61 @@
+/*
+ * 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.microprofile.metrics;
+
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+/**
+ * Implementation of the MicroProfile Metrics {@link org.eclipse.microprofile.metrics.Histogram}.
+ */
+public class MpHistogram extends MpMetric implements Histogram {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate meter which actually records data
+ */
+ MpHistogram(DistributionSummary delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ @Override
+ public void update(int i) {
+ delegate().record(i);
+ }
+
+ @Override
+ public void update(long l) {
+ delegate().record(l);
+ }
+
+ @Override
+ public long getCount() {
+ return delegate().count();
+ }
+
+ @Override
+ public long getSum() {
+ return (long) delegate().totalAmount();
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ return new MpSnapshot(delegate().takeSnapshot());
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetric.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetric.java
new file mode 100644
index 00000000000..ba3c14fa104
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetric.java
@@ -0,0 +1,43 @@
+/*
+ * 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.microprofile.metrics;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.eclipse.microprofile.metrics.Metric;
+
+abstract class MpMetric implements Metric {
+
+ private final M delegate;
+ private final MeterRegistry meterRegistry;
+
+ MpMetric(M delegate, MeterRegistry meterRegistry) {
+ this.delegate = delegate;
+ this.meterRegistry = meterRegistry;
+ }
+
+ M delegate() {
+ return delegate;
+ }
+
+ /**
+ * Returns the meter registry associated with this metric.
+ * @return the meter registry
+ */
+ protected MeterRegistry meterRegistry() {
+ return meterRegistry;
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricId.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricId.java
new file mode 100644
index 00000000000..c87ecbe6416
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricId.java
@@ -0,0 +1,101 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.Objects;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tags;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.Tag;
+
+/**
+ * Creates a new instance of the metric ID implementation.
+ */
+public class MpMetricId extends MetricID {
+
+ private Tags fullTags = Tags.empty();
+ private final Meter.Id meterId;
+
+ /**
+ * Creates a new instance of the metric ID.
+ *
+ * @param name metric name
+ * @param tags tags
+ * @param automaticTags automatically-added tags (e.g., app ID or scope)
+ * @param baseUnit unit for the metric
+ * @param description description of the metric
+ * @param type meter type for the metric
+ */
+ MpMetricId(String name, Tag[] tags, Tag[] automaticTags, String baseUnit, String description, Meter.Type type) {
+ super(name, tags);
+ for (Tag tag : tags) {
+ fullTags = fullTags.and(io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue()));
+ }
+ for (Tag tag : automaticTags) {
+ fullTags = fullTags.and(io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue()));
+ }
+
+ meterId = new Meter.Id(name, fullTags, baseUnit, description, type);
+ }
+
+ /**
+ * Returns the metric name.
+ *
+ * @return metric name
+ */
+ public String name() {
+ return getName();
+ }
+
+ /**
+ * Returns all tags, including those "hidden" for differentiating scope.
+ *
+ * @return returns all tags
+ */
+ public Tags fullTags() {
+ return fullTags;
+ }
+
+ /**
+ * Returns the underlying implementation's meter ID.
+ *
+ * @return underlying meter ID
+ */
+ Meter.Id meterId() {
+ return meterId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ MpMetricId that = (MpMetricId) o;
+ return fullTags.equals(that.fullTags) && meterId.equals(that.meterId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), fullTags, meterId);
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricRegistry.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricRegistry.java
new file mode 100644
index 00000000000..ce307096217
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricRegistry.java
@@ -0,0 +1,700 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
+
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Metric;
+import org.eclipse.microprofile.metrics.MetricFilter;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
+
+/**
+ * Implementation of the MicroProfile {@link org.eclipse.microprofile.metrics.MetricRegistry}.
+ *
+ * The methods in this ipmlementation which find or create-and-register metrics (in this registry) and the meters to which
+ * we delegate (in the meter registry) use functions or suppliers as parameters so our code can invoke them at the correct
+ * times to creating the a new metric and register a new meter. For example, we want to make sure that the metadata provided
+ * with or derived during a new registration agrees with previously-recorded metadata for the same metric name *before* we
+ * create a new meter or a new metric or update our data structures which track them. This way we can reuse rather than
+ * recopy the code for creating metadata and metric IDs, updating data structures, etc.
+ *
+ */
+class MpMetricRegistry implements MetricRegistry {
+
+ public static final String MP_APPLICATION_TAG_NAME = "mp_app";
+
+ public static final String MP_SCOPE_TAG_NAME = "mp_scope";
+
+ private static final double[] DEFAULT_PERCENTILES = {0.5, 0.75, 0.95, 0.98, 0.99, 0.999};
+
+ private final String scope;
+ private final MeterRegistry meterRegistry;
+ private final Map metadata = new ConcurrentHashMap<>();
+ private final Map> metricsById = new ConcurrentHashMap<>();
+ private final Map> metricIdsByName = new ConcurrentHashMap<>();
+ private final Map>> metricsByName = new ConcurrentHashMap<>();
+
+ private final ReentrantLock accessLock = new ReentrantLock();
+
+ /**
+ * Creates a new {@link MetricRegistry} with the given scope, delegating to the
+ * given {@link io.micrometer.core.instrument.MeterRegistry}.
+ *
+ * @param scope scope for the metric registry
+ * @param meterRegistry meter registry to which to delegate
+ * @return new {@code MetricRegistry}
+ */
+ static MpMetricRegistry create(String scope, MeterRegistry meterRegistry) {
+ return new MpMetricRegistry(scope, meterRegistry);
+ }
+
+ protected MpMetricRegistry(String scope, MeterRegistry meterRegistry) {
+ this.scope = scope;
+ this.meterRegistry = meterRegistry;
+ }
+
+ @Override
+ public Counter counter(String s) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ this::counterFactory,
+ s);
+ }
+
+ @Override
+ public Counter counter(String s, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ this::counterFactory,
+ s,
+ tags);
+ }
+
+ @Override
+ public Counter counter(MetricID metricID) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ this::counterFactory,
+ metricID.getName(),
+ metricID.getTagsAsArray());
+ }
+
+ @Override
+ public Counter counter(Metadata metadata) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ this::counterFactory,
+ validatedMetadata(metadata));
+ }
+
+ @Override
+ public Counter counter(Metadata metadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ MpCounter::new,
+ this::counterFactory,
+ validatedMetadata(metadata),
+ tags);
+ }
+
+ Counter counter(Metadata metadata, T valueOrigin, ToDoubleFunction fn, Tag... tags) {
+ return getOrCreateAndStoreFunctionCounter(validatedMetadata(metadata), valueOrigin, fn, tags);
+ }
+
+ @Override
+ public Gauge gauge(String s, T t, Function function, Tag... tags) {
+ return getOrCreateAndStoreGauge(t, function, validatedMetadata(s), tags);
+ }
+
+ @Override
+ public Gauge gauge(MetricID metricID, T t, Function function) {
+ return getOrCreateAndStoreGauge(metricID.getName(), t, function, metricID.getTagsAsArray());
+ }
+
+ @Override
+ public Gauge gauge(Metadata metadata, T t, Function function, Tag... tags) {
+ return getOrCreateAndStoreGauge(t, function, metadata, tags);
+ }
+
+ @Override
+ public Gauge gauge(String s, Supplier supplier, Tag... tags) {
+ return getOrCreateAndStoreGauge(supplier, validatedMetadata(s), tags);
+ }
+
+ @Override
+ public Gauge gauge(MetricID metricID, Supplier supplier) {
+ return getOrCreateAndStoreGauge(metricID.getName(), supplier, metricID.getTagsAsArray());
+ }
+
+ @Override
+ public Gauge gauge(Metadata metadata, Supplier supplier, Tag... tags) {
+ return getOrCreateAndStoreGauge(supplier, metadata, tags);
+ }
+
+ @Override
+ public Histogram histogram(String s) {
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ s);
+ }
+
+ @Override
+ public Histogram histogram(String s, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ s,
+ tags);
+ }
+
+ @Override
+ public Histogram histogram(MetricID metricID) {
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ metricID.getName(),
+ metricID.getTagsAsArray());
+ }
+
+ @Override
+ public Histogram histogram(Metadata metadata) {
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ validatedMetadata(metadata));
+ }
+
+ @Override
+ public Histogram histogram(Metadata metadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.DISTRIBUTION_SUMMARY,
+ MpHistogram::new,
+ this::distributionSummaryFactory,
+ validatedMetadata(metadata),
+ tags);
+ }
+
+ @Override
+ public Timer timer(String s) {
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ s);
+ }
+
+ @Override
+ public Timer timer(String s, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ s,
+ tags);
+ }
+
+ @Override
+ public Timer timer(MetricID metricID) {
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ metricID.getName(),
+ metricID.getTagsAsArray());
+ }
+
+ @Override
+ public Timer timer(Metadata metadata) {
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ validatedMetadata(metadata));
+ }
+
+ @Override
+ public Timer timer(Metadata metadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.TIMER,
+ MpTimer::new,
+ this::timerFactory,
+ validatedMetadata(metadata),
+ tags);
+ }
+
+ @Override
+ public Metric getMetric(MetricID metricID) {
+ return metricsById.get(metricID);
+ }
+
+ @Override
+ public T getMetric(MetricID metricID, Class aClass) {
+ return aClass.cast(metricsById.get(metricID));
+ }
+
+ @Override
+ public Counter getCounter(MetricID metricID) {
+ return (Counter) metricsById.get(metricID);
+ }
+
+ @Override
+ public Gauge> getGauge(MetricID metricID) {
+ return (Gauge>) metricsById.get(metricID);
+ }
+
+ @Override
+ public Histogram getHistogram(MetricID metricID) {
+ return (Histogram) metricsById.get(metricID);
+ }
+
+ @Override
+ public Timer getTimer(MetricID metricID) {
+ return (Timer) metricsById.get(metricID);
+ }
+
+ @Override
+ public Metadata getMetadata(String s) {
+ return metadata.get(s);
+ }
+
+ @Override
+ public boolean remove(String s) {
+ return access(() -> {
+ boolean removeResult = false;
+ // Capture the list of IDs first and then iterate over that because the remove method updates the list from the map.
+ List doomedMetricIds = new ArrayList<>(metricIdsByName.get(s));
+ for (MetricID metricId : doomedMetricIds) {
+ removeResult |= remove(metricId);
+ }
+ return removeResult;
+
+
+ });
+ }
+
+ @Override
+ public boolean remove(MetricID metricID) {
+ return access(() -> {
+ MpMetric> doomedMpMetric = metricsById.remove(metricID);
+ boolean removeResult = doomedMpMetric != null;
+ List idsByName = metricIdsByName.get(metricID.getName());
+ if (idsByName != null) {
+ idsByName.remove(metricID);
+ }
+ meterRegistry.remove(doomedMpMetric.delegate());
+ return removeResult;
+ });
+ }
+
+ @Override
+ public void removeMatching(MetricFilter metricFilter) {
+ List doomedMetricIds = new ArrayList<>();
+ access(() -> {
+ metricsById.forEach((id, metric) -> {
+ if (metricFilter.matches(id, metric)) {
+ doomedMetricIds.add(id);
+ }
+ });
+ for (MetricID doomedMetricId : doomedMetricIds) {
+ remove(doomedMetricId);
+ }
+ });
+ }
+
+ @Override
+ public SortedSet getNames() {
+ return new TreeSet<>(metadata.keySet());
+ }
+
+ @Override
+ public SortedSet getMetricIDs() {
+ return new TreeSet<>(metricsById.keySet());
+ }
+
+ @Override
+ public SortedMap getGauges() {
+ return getGauges(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getGauges(MetricFilter metricFilter) {
+ return getMetrics(Gauge.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getCounters() {
+ return getCounters(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getCounters(MetricFilter metricFilter) {
+ return getMetrics(Counter.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getHistograms() {
+ return getHistograms(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getHistograms(MetricFilter metricFilter) {
+ return getMetrics(Histogram.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getTimers() {
+ return getTimers(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getTimers(MetricFilter metricFilter) {
+ return getMetrics(Timer.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getMetrics(MetricFilter metricFilter) {
+ return metricsById.entrySet().stream()
+ .filter(e -> metricFilter.matches(e.getKey(), e.getValue()))
+ .collect(TreeMap::new,
+ (map, e) -> map.put(e.getKey(), e.getValue()),
+ TreeMap::putAll);
+ }
+
+ @Override
+ public SortedMap getMetrics(Class aClass, MetricFilter metricFilter) {
+ return metricsById.entrySet()
+ .stream()
+ .filter(e -> aClass.isInstance(e.getValue()))
+ .filter(e -> metricFilter.matches(e.getKey(), e.getValue()))
+ .collect(TreeMap::new,
+ (tm, entry) -> tm.put(entry.getKey(), aClass.cast(entry.getValue())),
+ TreeMap::putAll);
+ }
+
+ @Override
+ public Map getMetrics() {
+ return Collections.unmodifiableMap(metricsById);
+ }
+
+ @Override
+ public Map getMetadata() {
+ return Collections.unmodifiableMap(metadata);
+ }
+
+ @Override
+ public String getScope() {
+ return scope;
+ }
+
+ MeterRegistry meterRegistry() {
+ return meterRegistry;
+ }
+
+ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
+ BiFunction metricFactory,
+ BiFunction,
+ T> meterFactory,
+ String name,
+ Tag... tags) {
+ return getOrCreateAndStoreMetric(type,
+ metricFactory,
+ meterFactory,
+ validatedMetadata(name),
+ tags);
+ }
+
+ , T extends Meter> M getOrCreateAndStoreMetric(Meter.Type type,
+ BiFunction metricFactory,
+ BiFunction,
+ T> meterFactory,
+ Metadata validMetadata,
+ Tag... tags) {
+
+ /*
+ * From the metadata create a candidate MpMetricID, validate it (to make sure the tag names are consistent with any
+ * previously-registered metrics with the same name and that the user did not specify any reserved tags), and then
+ * augment the inner meter ID with the scope tag and, if an app name is specified via config, the app name tag.
+ */
+ MpMetricId mpMetricId = validAugmentedMpMetricId(validMetadata, type, tags);
+ return access(() -> {
+ MpMetric> result = metricsById.get(mpMetricId);
+ if (result != null) {
+ return (M) result;
+ }
+
+ T delegate = meterFactory.apply(validMetadata, mpMetricId.fullTags());
+
+ M newMetric = metricFactory.apply(delegate, meterRegistry);
+ storeMetadataIfAbsent(validMetadata);
+ metricsById.put(mpMetricId, newMetric);
+ metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
+ metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
+ return newMetric;
+ });
+ }
+
+ Counter getOrCreateAndStoreFunctionCounter(Metadata validMetadata,
+ T valueOrigin,
+ ToDoubleFunction fn,
+ Tag... tags) {
+
+ return getOrCreateAndStoreMetric(Meter.Type.COUNTER,
+ (ctr, mr) ->
+ MpFunctionCounter.builder(this,
+ validMetadata.getName(),
+ valueOrigin, fn)
+ .tags(MpTags.fromMp(tags))
+ .build(),
+ this::counterFactory,
+ validMetadata,
+ tags);
+ }
+
+ // Following should be unneeded thanks to the preceding method.
+ // Counter getOrCreateAndStoreFunctionCounter(Metadata validMetadata,
+ // T valueOrigin,
+ // ToDoubleFunction fn,
+ // Tag... tags) {
+ //
+ // /*
+ // * From the metadata create a candidate MpMetricID, validate it (to make sure the tag names are consistent with any
+ // * previously-registered metrics with the same name and that the user did not specify any reserved tags), and then
+ // * augment the inner meter ID with the scope tag and, if an app name is specified via config, the app name tag.
+ // */
+ // MpMetricId mpMetricId = validAugmentedMpMetricId(validMetadata, Meter.Type.COUNTER, tags);
+ // return access(() -> {
+ // MpMetric> result = metricsById.get(mpMetricId);
+ // if (result != null) {
+ // return (MpFunctionCounter) result;
+ // }
+ //
+ // MpFunctionCounter newMetric = MpFunctionCounter.builder(this,
+ // validMetadata.getName(),
+ // valueOrigin, fn)
+ // .tags(MpTags.fromMp(tags))
+ // .build();
+ //
+ // storeMetadataIfAbsent(validMetadata);
+ // metricsById.put(mpMetricId, newMetric);
+ // metricIdsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(mpMetricId);
+ // metricsByName.computeIfAbsent(validMetadata.getName(), n -> new ArrayList<>()).add(newMetric);
+ // return newMetric;
+ // });
+ // }
+
+ Gauge getOrCreateAndStoreGauge(T t, Function function, Metadata validMetadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.GAUGE,
+ (io.micrometer.core.instrument.Gauge delegate, MeterRegistry registry) ->
+ MpGauge.create(t, function, delegate, registry),
+ (Metadata validM, Iterable ts)
+ -> io.micrometer.core.instrument.Gauge
+ .builder(validM.getName(),
+ t,
+ target -> function.apply(target).doubleValue())
+ .description(validM.getDescription())
+ .tags(ts)
+ .baseUnit(validM.getUnit())
+ .strongReference(true)
+ .register(meterRegistry),
+ validMetadata,
+ tags);
+ }
+
+ Gauge getOrCreateAndStoreGauge(String name, T t, Function function, Tag... tags) {
+ return getOrCreateAndStoreGauge(t, function, validatedMetadata(name), tags);
+ }
+
+ Gauge getOrCreateAndStoreGauge(Supplier supplier, Metadata validMetadata, Tag... tags) {
+ return getOrCreateAndStoreMetric(Meter.Type.GAUGE,
+ (io.micrometer.core.instrument.Gauge delegate, MeterRegistry registry) ->
+ MpGauge.create(supplier, delegate, registry),
+ (Metadata validM, Iterable ts)
+ -> io.micrometer.core.instrument.Gauge
+ .builder(validM.getName(),
+ (Supplier) supplier)
+ .description(validM.getDescription())
+ .tags(ts)
+ .baseUnit(validM.getUnit())
+ .strongReference(true)
+ .register(meterRegistry),
+ validMetadata,
+ tags);
+ }
+
+
+ Gauge getOrCreateAndStoreGauge(String name, Supplier supplier, Tag... tags) {
+ return getOrCreateAndStoreGauge(supplier, validatedMetadata(name), tags);
+ }
+
+ /**
+ * Returns whether the two metadata instances are consistent with each other.
+ *
+ * @param a one {@code Metadata} instance
+ * @param b the other {@code Metadata} instance
+ * @return {@code true} if the two instances contain consistent metadata; {@code false} otherwise
+ */
+ boolean isConsistentMetadata(Metadata a, Metadata b) {
+ return a.equals(b);
+ }
+
+ /**
+ * Checks that the tag names in the provided ID are consistent with the tag names in any previously-registered ID
+ * with the same name; throws {@code IllegalArgumentException} if inconsistent.
+ *
+ * @param mpMetricId metric ID with tags to check
+ */
+ MpMetricId checkTagNameSetConsistencyWithStoredIds(MpMetricId mpMetricId) {
+ MpTags.checkTagNameSetConsistency(mpMetricId, metricIdsByName.get(mpMetricId.getName()));
+ return mpMetricId;
+ }
+
+ private void storeMetadataIfAbsent(Metadata validatedMetadata) {
+ metadata.putIfAbsent(validatedMetadata.getName(), validatedMetadata);
+ }
+
+ /**
+ * Returns a validated {@link io.helidon.microprofile.metrics.MpMetricId} derived from the provided metadata, tags,
+ * and meter Type, further augmented with MicroProfile-supported additional tags (app name, scope).
+ *
+ * @param proposedMetadata {@link Metadata} to use in populating the {@code MpMetricId}
+ * @param meterType the non-MP metric meterType
+ * @param tags tags to use in preparing the metric ID
+ * @return validated {@code MpMetricId}
+ */
+ private MpMetricId validAugmentedMpMetricId(Metadata proposedMetadata, Meter.Type meterType, Tag... tags) {
+ MpMetricId metricId = new MpMetricId(proposedMetadata.getName(),
+ tags,
+ automaticTags(),
+ proposedMetadata.getUnit(),
+ proposedMetadata.getDescription(),
+ meterType);
+ checkTagNameSetConsistencyWithStoredIds(metricId);
+ MpTags.checkForReservedTags(metricId.getTags().keySet());
+ return metricId;
+ }
+
+ private Tag[] automaticTags() {
+ List result = new ArrayList<>();
+ result.add(new Tag(MpMetricRegistry.MP_SCOPE_TAG_NAME, scope));
+ String mpAppValue = MpTags.mpAppValue();
+ if (mpAppValue != null && !mpAppValue.isBlank()) {
+ result.add(new Tag(MpMetricRegistry.MP_APPLICATION_TAG_NAME, mpAppValue));
+ }
+ return result.toArray(new Tag[0]);
+ }
+
+
+
+ /**
+ * Returns a {@link org.eclipse.microprofile.metrics.Metadata} derived from the specified name, validated for consistency
+ * against any previously-registered metadata under the same name.
+ *
+ * @param name name associated with the metadata
+ * @return valid {@code Metadata} derived from the name
+ */
+ private Metadata validatedMetadata(String name) {
+ return validatedMetadata(Metadata.builder()
+ .withName(name)
+ .build());
+ }
+
+ /**
+ * Returns the provided {@link org.eclipse.microprofile.metrics.Metadata} once validated for consistency against any
+ * previously-registered metadata with the same name.
+ *
+ * @param proposedMetadata candidate {@code Metadata} to validate
+ * @return validated {@code Metadata}
+ */
+ private Metadata validatedMetadata(Metadata proposedMetadata) {
+ Metadata storedMetadata = metadata.get(proposedMetadata.getName());
+ if (storedMetadata == null) {
+ return proposedMetadata;
+ }
+ if (!isConsistentMetadata(storedMetadata, proposedMetadata)) {
+ throw new IllegalArgumentException(String.format(
+ "Supplied metadata %s is inconsistent with previously-registered metadata %s",
+ proposedMetadata,
+ storedMetadata));
+ }
+ return storedMetadata;
+ }
+
+ private io.micrometer.core.instrument.Counter counterFactory(Metadata metadata,
+ Iterable tags) {
+ return meterRegistry.counter(metadata.getName(), tags);
+ }
+
+ private DistributionSummary distributionSummaryFactory(Metadata metadata,
+ Iterable tags) {
+ return DistributionSummary.builder(metadata.getName())
+ .description(metadata.getDescription())
+ .baseUnit(metadata.getUnit())
+ .tags(tags)
+ .publishPercentiles(DEFAULT_PERCENTILES)
+ .percentilePrecision(MpRegistryFactory.get().distributionSummaryPrecision())
+ .register(meterRegistry);
+ }
+
+ private io.micrometer.core.instrument.Timer timerFactory(Metadata metadata,
+ Iterable tags) {
+ return io.micrometer.core.instrument.Timer.builder(metadata.getName())
+ .description(metadata.getDescription())
+ .tags(tags)
+ .publishPercentiles(DEFAULT_PERCENTILES)
+ .percentilePrecision(MpRegistryFactory.get().timerPrecision())
+ .register(meterRegistry);
+ }
+
+ private T access(Callable work) {
+ accessLock.lock();
+ try {
+ return work.call();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ accessLock.unlock();
+ }
+ }
+
+ private void access(Runnable work) {
+ accessLock.lock();
+ try {
+ work.run();
+ } finally {
+ accessLock.unlock();
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricsFeature.java.save b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricsFeature.java.save
new file mode 100644
index 00000000000..32254a7f222
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpMetricsFeature.java.save
@@ -0,0 +1,146 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.Optional;
+
+import io.helidon.common.http.Http;
+import io.helidon.common.media.type.MediaType;
+import io.helidon.common.media.type.MediaTypes;
+import io.helidon.nima.servicecommon.HelidonFeatureSupport;
+import io.helidon.nima.webserver.http.HttpRules;
+import io.helidon.nima.webserver.http.HttpService;
+import io.helidon.nima.webserver.http.ServerRequest;
+import io.helidon.nima.webserver.http.ServerResponse;
+
+/**
+ * MP metrics feature implementation.
+ */
+public class MpMetricsFeature extends HelidonFeatureSupport {
+
+ private static final System.Logger LOGGER = System.getLogger(MpMetricsFeature.class.getName());
+
+ /**
+ * Creates a new builder for the MP metrics feature.
+ *
+ * @return new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a new default MP metrics feature.
+ *
+ * @return newly-created feature
+ */
+ public static MpMetricsFeature create() {
+ return builder().build();
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param logger logger for the feature
+ * @param builder builder to use
+ * @param serviceName name of the service
+ */
+ protected MpMetricsFeature(System.Logger logger, Builder builder, String serviceName) {
+ super(logger, builder, serviceName);
+ }
+
+ @Override
+ public Optional service() {
+ if (enabled()) {
+ return Optional.of(this::configureRoutes);
+ } else {
+ return Optional.of(this::configureDisabledRoutes);
+ }
+ }
+
+
+ protected void context(String componentPath) {
+ super.context(componentPath);
+ }
+
+ private void configureRoutes(HttpRules rules) {
+ rules.get("/", this::prepareResponse);
+ }
+
+ private void configureDisabledRoutes(HttpRules rules) {
+ rules.get("/", this::prepareDisabledResponse);
+ }
+
+ private void prepareDisabledResponse(ServerRequest req, ServerResponse resp) {
+ resp.status(Http.Status.NOT_IMPLEMENTED_501)
+ .header(Http.Header.CONTENT_TYPE, MediaTypes.TEXT_PLAIN.text())
+ .send("Metrics is disabled");
+ }
+
+ private void prepareResponse(ServerRequest req, ServerResponse resp) {
+
+ Optional requestedMediaType = req.headers()
+ .bestAccepted(PrometheusFormatter.MEDIA_TYPE_TO_FORMAT
+ .keySet()
+ .toArray(new MediaType[0]));
+ if (requestedMediaType.isEmpty()) {
+ LOGGER.log(System.Logger.Level.TRACE,
+ "Unable to compose Prometheus format response; request accepted types were "
+ + req.headers().acceptedTypes());
+ resp.status(Http.Status.UNSUPPORTED_MEDIA_TYPE_415).send();
+ }
+
+ PrometheusFormatter.Builder formatterBuilder = PrometheusFormatter.builder().resultMediaType(requestedMediaType.get());
+ scope(req).ifPresent(formatterBuilder::scope);
+ metricName(req).ifPresent(formatterBuilder::meterName);
+
+ try {
+ MediaType resultMediaType = requestedMediaType.get();
+ resp.status(Http.Status.OK_200);
+ resp.headers().contentType(resultMediaType);
+ resp.send(formatterBuilder.build().filteredOutput());
+ } catch (Exception ex) {
+ resp.status(Http.Status.INTERNAL_SERVER_ERROR_500);
+ resp.send("Error preparing metrics output; " + ex.getMessage());
+ logger().log(System.Logger.Level.ERROR, "Error preparing metrics output", ex);
+ }
+ }
+
+ private Optional scope(ServerRequest req) {
+ return req.query().first("scope");
+ }
+
+ private Optional metricName(ServerRequest req) {
+ return req.query().first("name");
+ }
+
+ /**
+ * Builder for the MP metrics feature.
+ */
+ public static class Builder extends HelidonFeatureSupport.Builder {
+
+ private static final String DEFAULT_WEB_CONTEXT = "/metrics";
+
+ Builder() {
+ super(DEFAULT_WEB_CONTEXT);
+ }
+
+ @Override
+ public MpMetricsFeature build() {
+ return new MpMetricsFeature(LOGGER, this, "MP-metrics");
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpRegistryFactory.java.save b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpRegistryFactory.java.save
new file mode 100644
index 00000000000..a9602fa026c
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpRegistryFactory.java.save
@@ -0,0 +1,189 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import io.helidon.metrics.api.Registry;
+import io.helidon.metrics.api.RegistryFactory;
+
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+
+/**
+ * Factory class for finding existing or creating new metric registries.
+ */
+public class MpRegistryFactory implements RegistryFactory {
+
+ /**
+ * Scope name for the application metric registry.
+ */
+ public static final String APPLICATION_SCOPE = "application";
+
+ /**
+ * Scope name for the base metric registry.
+ */
+ public static final String BASE_SCOPE = "base";
+
+ /**
+ * Scope name for the vendor metric registry.
+ */
+ public static final String VENDOR_SCOPE = "vendor";
+
+ /**
+ * Config key suffix for distribution summary (histogram) precision.
+ */
+ public static final String HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX = "helidon.distribution-summary.precision";
+
+ /**
+ * Config key suffix for timer precision.
+ */
+ public static final String TIMER_PRECISION_CONFIG_KEY_SUFFIX = "helidon.timer.precision";
+
+ private static final int HISTOGRAM_PRECISION_DEFAULT = 3;
+ private static final int TIMER_PRECISION_DEFAULT = 3;
+
+ private static MpRegistryFactory instance;
+
+ private static final ReentrantLock LOCK = new ReentrantLock();
+ private final Exception creation;
+
+ private Config mpConfig;
+ private final Map registries = new HashMap<>();
+ private final PrometheusMeterRegistry prometheusMeterRegistry;
+ private final int distributionSummaryPrecision;
+ private final int timerPrecision;
+
+ /**
+ * Creates a new registry factory using the specified MicroProfile configuration.
+ *
+ * @param mpConfig the MicroProfile config to use in preparing the registry factory
+ * @return registry factory
+ */
+ public static MpRegistryFactory create(Config mpConfig) {
+ LOCK.lock();
+ try {
+ if (instance == null) {
+ instance = new MpRegistryFactory(mpConfig);
+ return instance;
+ } else {
+ throw new IllegalStateException(
+ "Attempt to set up MpRegistryFactory multiple times; previous invocation follows: ",
+ instance.creation);
+ }
+ } finally {
+ LOCK.unlock();
+ }
+ }
+
+ /**
+ * Returns the singleton registry factory, creating it if needed.
+ *
+ * @return the registry factory
+ */
+ public static MpRegistryFactory getInstance() {
+ LOCK.lock();
+ try {
+ if (instance == null) {
+ instance = new MpRegistryFactory(ConfigProvider.getConfig());
+ }
+ return instance;
+ } finally {
+ LOCK.unlock();
+ }
+ }
+
+ @Override
+ public Registry getRegistry(String scope) {
+ return ;
+ }
+
+ @Override
+ public boolean enabled() {
+ return false;
+ }
+
+ /**
+ * Returns the {@link org.eclipse.microprofile.metrics.MetricRegistry} for the specified scope.
+ *
+ * @param scope scope for the meter registry to get or create
+ * @return previously-existing or newly-created {@code MeterRegistry} for the specified scope
+ */
+ public MetricRegistry registry(String scope) {
+ return registries.computeIfAbsent(scope,
+ s -> MpMetricRegistry.create(s, Metrics.globalRegistry));
+ }
+
+ /**
+ * Returns the Prometheus meter registry known to the registry factory.
+ *
+ * @return Prometheus meter registry
+ */
+ public PrometheusMeterRegistry prometheusMeterRegistry() {
+ return prometheusMeterRegistry;
+ }
+
+ /**
+ * Returns the precision for use with distribution summaries.
+ *
+ * @return distribution summary precision
+ */
+ int distributionSummaryPrecision() {
+ return distributionSummaryPrecision;
+ }
+
+ /**
+ * Returns the precision for use with timers.
+ *
+ * @return timer precision
+ */
+ int timerPrecision() {
+ return timerPrecision;
+ }
+
+ private MpRegistryFactory(Config mpConfig) {
+ creation = new Exception("Initial creation of " + MpRegistryFactory.class.getSimpleName());
+ this.mpConfig = mpConfig;
+ prometheusMeterRegistry = findOrAddPrometheusRegistry();
+ distributionSummaryPrecision = mpConfig.getOptionalValue("mp.metrics." + HISTOGRAM_PRECISION_CONFIG_KEY_SUFFIX,
+ Integer.class).orElse(HISTOGRAM_PRECISION_DEFAULT);
+ timerPrecision = mpConfig.getOptionalValue("mp.metrics." + TIMER_PRECISION_CONFIG_KEY_SUFFIX,
+ Integer.class).orElse(TIMER_PRECISION_DEFAULT);
+ registries.put(APPLICATION_SCOPE, MpMetricRegistry.create(APPLICATION_SCOPE, Metrics.globalRegistry));
+ registries.put(BASE_SCOPE, BaseRegistry.create(Metrics.globalRegistry));
+ registries.put(VENDOR_SCOPE, MpMetricRegistry.create(VENDOR_SCOPE, Metrics.globalRegistry));
+
+ }
+
+ private PrometheusMeterRegistry findOrAddPrometheusRegistry() {
+ return Metrics.globalRegistry.getRegistries().stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseGet(() -> {
+ PrometheusMeterRegistry result = new PrometheusMeterRegistry(
+ s -> mpConfig.getOptionalValue("mp.metrics." + s, String.class)
+ .orElse(null));
+ Metrics.addRegistry(result);
+ return result;
+ });
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpSnapshot.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpSnapshot.java
new file mode 100644
index 00000000000..5286b013024
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpSnapshot.java
@@ -0,0 +1,68 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import io.micrometer.core.instrument.distribution.HistogramSnapshot;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+/**
+ * Implementation of {@link org.eclipse.microprofile.metrics.Snapshot}.
+ */
+public class MpSnapshot extends Snapshot {
+
+ private final HistogramSnapshot delegate;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate histogram snapshot which provides the actual data
+ */
+ MpSnapshot(HistogramSnapshot delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public long size() {
+ return delegate.count();
+ }
+
+ @Override
+ public double getMax() {
+ return delegate.max();
+ }
+
+ @Override
+ public double getMean() {
+ return delegate.mean();
+ }
+
+ @Override
+ public PercentileValue[] percentileValues() {
+ return Arrays.stream(delegate.percentileValues())
+ .map(vap -> new PercentileValue(vap.percentile(), vap.value()))
+ .toArray(PercentileValue[]::new);
+ }
+
+ @Override
+ public void dump(OutputStream outputStream) {
+ delegate.outputSummary(new PrintStream(outputStream, false, Charset.defaultCharset()), 1);
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTags.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTags.java
new file mode 100644
index 00000000000..f9be7c3f9c2
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTags.java
@@ -0,0 +1,216 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.micrometer.core.instrument.Tags;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigValue;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.Tag;
+
+/**
+ * Tags utility class.
+ */
+class MpTags {
+
+ private static final Set RESERVED_TAG_NAMES = new HashSet<>();
+ private static String mpAppValue;
+
+ static {
+ RESERVED_TAG_NAMES.add(MpMetricRegistry.MP_APPLICATION_TAG_NAME);
+ RESERVED_TAG_NAMES.add(MpMetricRegistry.MP_SCOPE_TAG_NAME);
+ }
+
+ /**
+ * Hidden constructor for utility class.
+ */
+ private MpTags() {
+ }
+
+ /**
+ * Returns the "app" value.
+ *
+ * @return the app value
+ */
+ static String mpAppValue() {
+ return mpAppValue;
+ }
+
+ /**
+ * Returns the configuration used to prepare tags for metric IDs.
+ *
+ * @param config config used in setting tags in the IDs
+ */
+ static void config(Config config) {
+ ConfigValue configValue = config.getConfigValue(MpMetricRegistry.MP_APPLICATION_TAG_NAME);
+ if (configValue.getValue() != null) {
+ mpAppValue = configValue.getValue();
+ }
+ }
+
+ /**
+ * Converts MicroProfile tags to the underlying implementation {@link io.micrometer.core.instrument.Tags} abstraction.
+ *
+ * @param tags MicroProfile tags to convert
+ * @return underlying implementation tags abstraction
+ */
+ static Tags fromMp(List tags) {
+ return Tags.of(tags.stream()
+ .map(MpTags::fromMp)
+ .toList());
+ }
+
+ /**
+ * Converts MicroProfile tags to the underlying implementation {@link io.micrometer.core.instrument.Tags} abstraction.
+ *
+ * @param tags MicroProfile tags to convert
+ * @return underlying implementation tags abstraction
+ */
+ static Tags fromMp(Tag... tags) {
+ return Tags.of(Arrays.stream(tags).map(MpTags::fromMp).toList());
+ }
+
+ /**
+ * Converts MicroProfile tags to the underlying implementation {@link io.micrometer.core.instrument.Tags} abstraction.
+ *
+ * @param tags MicroProfile tags to convert
+ * @return underlying implementation tags abstraction
+ */
+ static Tags fromMp(Map tags) {
+ return tags.entrySet().stream().collect(Tags::empty,
+ (meterTags, entry) -> meterTags.and(entry.getKey(), entry.getValue()),
+ Tags::and);
+ }
+
+ /**
+ * Converts a MicroProfile tag to the underlying implementation {@link Tags} abstraction.
+ *
+ * @param tag MicroProfile tag to convert
+ * @return underlying implementation tags abstraction
+ */
+ static io.micrometer.core.instrument.Tag fromMp(Tag tag) {
+ return io.micrometer.core.instrument.Tag.of(tag.getTagName(), tag.getTagValue());
+ }
+
+ /**
+ * Checks that the tag names in the provided metric ID are consistent with those in the provided list of metric IDs, throwing
+ * an exception if not.
+ *
+ * @param mpMetricId the metric ID to check for consistent tag names
+ * @param mpMetricIds other metric IDs
+ */
+ static void checkTagNameSetConsistency(MetricID mpMetricId, List mpMetricIds) {
+ if (mpMetricIds == null || mpMetricIds.isEmpty()) {
+ return;
+ }
+
+ for (MetricID id : mpMetricIds) {
+ if (!isConsistentTagNameSet(id, mpMetricId)) {
+ throw new IllegalArgumentException(String.format("""
+ Provided tag names are inconsistent with tag names on previously-registered metric with the same name %s; \
+ previous: %s, requested: %s""",
+ mpMetricId.getName(),
+ id.getTags().keySet(),
+ mpMetricId.getTags().keySet()));
+ }
+ }
+ }
+
+ /**
+ * Verifies that the candidate tag names are consistent with the specified groups of tags (typically tags previously recorded
+ * for meters with the same name).
+ *
+ * @param metricName name of the meter ID being checked (for error reporting)
+ * @param candidateTags candidate tags
+ * @param establishedTagGroups groups of tags previously established against which to check the candidates
+ * @throws java.lang.IllegalArgumentException if the candidate tags are not consistent with the established tag groups
+ */
+ static void checkTagNameSetConsistency(String metricName, Set candidateTags, Iterable> establishedTagGroups) {
+ for (Set establishedTags : establishedTagGroups) {
+ if (!candidateTags.equals(establishedTags)) {
+ throw new IllegalArgumentException(String.format("""
+ Provided tag names are inconsistent with tag names on previously-registered metric with the same name %s; \
+ registered tags: %s, requested tags: %s""",
+ metricName,
+ Arrays.asList(establishedTags),
+ Arrays.asList(candidateTags)));
+ }
+ }
+ }
+
+ /**
+ * Returns whether the tag names in the two provided metric IDs are consistent with each other.
+ *
+ * @param a one metric ID
+ * @param b another metric ID
+ * @return {@code true} if the tag name sets in the two IDs are consistent; {@code false} otherwise
+ */
+ static boolean isConsistentTagNameSet(MetricID a, MetricID b) {
+ return a.getTags().keySet().equals(b.getTags().keySet());
+ }
+
+ /**
+ * Checks to make sure the specified tags do not include any reserved tag names.
+ *
+ * @param tags tags to check
+ * @throws java.lang.IllegalArgumentException if any of names of the specified tags are reserved
+ */
+ static void checkForReservedTags(Tag... tags) {
+ if (tags != null) {
+ Set offendingReservedTags = null;
+ for (Tag tag : tags) {
+ if (RESERVED_TAG_NAMES.contains(tag.getTagName())) {
+ if (offendingReservedTags == null) {
+ offendingReservedTags = new HashSet<>();
+ }
+ offendingReservedTags.add(tag.getTagName());
+ }
+ }
+ if (offendingReservedTags != null) {
+ throw new IllegalArgumentException("Provided tags includes reserved name(s) " + offendingReservedTags);
+ }
+ }
+ }
+
+ /**
+ * Checks to make sure the specified tags do not include any reserved tag names.
+ *
+ * @param tagNames tag names to check
+ * @throws java.lang.IllegalArgumentException if any of names of the specified tags are reserved
+ */
+ static void checkForReservedTags(Set tagNames) {
+ if (tagNames != null) {
+ Set offendingReservedTags = null;
+ for (String tagName : tagNames) {
+ if (RESERVED_TAG_NAMES.contains(tagName)) {
+ if (offendingReservedTags == null) {
+ offendingReservedTags = new HashSet<>();
+ }
+ offendingReservedTags.add(tagName);
+ }
+ }
+ if (offendingReservedTags != null) {
+ throw new IllegalArgumentException("Provided tags includes reserved name(s) " + offendingReservedTags);
+ }
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTimer.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTimer.java
new file mode 100644
index 00000000000..1bbfd8c756d
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MpTimer.java
@@ -0,0 +1,98 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.time.Duration;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import org.eclipse.microprofile.metrics.Snapshot;
+
+/**
+ * Implementation of MicroProfile metrics {@link org.eclipse.microprofile.metrics.Timer}.
+ */
+public class MpTimer extends MpMetric implements org.eclipse.microprofile.metrics.Timer {
+
+ /**
+ * Creates a new instance.
+ *
+ * @param delegate meter which actually records data
+ */
+ MpTimer(Timer delegate, MeterRegistry meterRegistry) {
+ super(delegate, meterRegistry);
+ }
+
+ @Override
+ public void update(Duration duration) {
+ delegate().record(duration);
+ }
+
+ @Override
+ public T time(Callable callable) throws Exception {
+ return delegate().recordCallable(callable);
+ }
+
+ @Override
+ public void time(Runnable runnable) {
+ delegate().record(runnable);
+ }
+
+ @Override
+ public Context time() {
+ return new Context();
+ }
+
+ @Override
+ public Duration getElapsedTime() {
+ return Duration.ofNanos((long) delegate().totalTime(TimeUnit.NANOSECONDS));
+ }
+
+ @Override
+ public long getCount() {
+ return delegate().count();
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ return new MpSnapshot(delegate().takeSnapshot());
+ }
+
+ /**
+ * Implementation of MicroProfile Metrics {@link org.eclipse.microprofile.metrics.Timer.Context}.
+ */
+ public class Context implements org.eclipse.microprofile.metrics.Timer.Context {
+
+ /**
+ * Used only internally.
+ */
+ private Context() {
+ }
+
+ private final Timer.Sample sample = Timer.start(meterRegistry());
+
+ @Override
+ public long stop() {
+ return sample.stop(delegate());
+ }
+
+ @Override
+ public void close() {
+ stop();
+ }
+ }
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/PrometheusFormatter.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/PrometheusFormatter.java
new file mode 100644
index 00000000000..67c59e6f837
--- /dev/null
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/PrometheusFormatter.java
@@ -0,0 +1,296 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import io.helidon.common.media.type.MediaType;
+import io.helidon.common.media.type.MediaTypes;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import io.prometheus.client.exporter.common.TextFormat;
+
+/**
+ * Retrieves and prepares meter output according to the formats supported by the Prometheus meter registry.
+ *
+ * Because the Prometheus exposition format is flat, and because some meter types have multiple values, the meter names
+ * in the output repeat the actual meter name with suffixes to indicate the specific quantities (e.g.,
+ * count, total, max) each reported value conveys. Further, meter names in the output might need the prefix
+ * "m_" if the actual meter name starts with a digit or underscore and underscores replace special characters.
+ *
+ */
+public class PrometheusFormatter {
+ /**
+ * Mapping from supported media types to the corresponding Prometheus registry content types.
+ */
+ public static final Map MEDIA_TYPE_TO_FORMAT = Map.of(MediaTypes.TEXT_PLAIN,
+ TextFormat.CONTENT_TYPE_004,
+ MediaTypes.APPLICATION_OPENMETRICS_TEXT,
+ TextFormat.CONTENT_TYPE_OPENMETRICS_100);
+
+ /**
+ * Returns a new builder for constructing a formatter.
+ *
+ * @return new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private static final String PROMETHEUS_TYPE_PREFIX = "# TYPE";
+ private static final String PROMETHEUS_HELP_PREFIX = "# HELP";
+
+ private final String scopeSelection;
+ private final String meterSelection;
+ private final MediaType resultMediaType;
+
+ private PrometheusFormatter(Builder builder) {
+ scopeSelection = builder.scopeSelection;
+ meterSelection = builder.meterNameSelection;
+ resultMediaType = builder.resultMediaType;
+ }
+
+ /**
+ * Returns the Prometheus output governed by the previously-specified media type, optionally filtered
+ * by the previously-specified scope and meter name.
+ *
+ * @return filtered Prometheus output
+ */
+ public String filteredOutput() {
+ return formattedOutput(MpRegistryFactory.get().prometheusMeterRegistry(),
+ resultMediaType,
+ scopeSelection,
+ meterSelection);
+ }
+
+ /**
+ * Retrieves the Prometheus-format report from the specified registry, according to the specified media type,
+ * filtered by the specified scope and meter name, and returns the filtered Prometheus-format output.
+ *
+ * @param prometheusMeterRegistry registry to query
+ * @param resultMediaType media type which controls the exact output format
+ * @param scopeSelection scope to select; null if no scope selection required
+ * @param meterNameSelection meter name to select; null if no meter name selection required
+ * @return filtered output
+ */
+ String formattedOutput(PrometheusMeterRegistry prometheusMeterRegistry,
+ MediaType resultMediaType,
+ String scopeSelection,
+ String meterNameSelection) {
+ String rawPrometheusOutput = prometheusMeterRegistry
+ .scrape(PrometheusFormatter.MEDIA_TYPE_TO_FORMAT.get(resultMediaType),
+ meterNamesOfInterest(prometheusMeterRegistry, meterNameSelection));
+
+ return filter(rawPrometheusOutput, scopeSelection);
+ }
+
+ /**
+ * Filter the Prometheus-format report by the specified scope.
+ *
+ * @param output Prometheus-format report
+ * @param scope scope to filter; null means no filtering by scope
+ * @return output filtered by scope (if specified)
+ */
+ static String filter(String output, String scope) {
+ if (scope == null) {
+ return output;
+ }
+
+ /*
+ * Output looks like repeating sections of this:
+ *
+ * # HELP xxx
+ * # TYPE yyy
+ * meter-name{tagA=value1,tagB=value2} data
+ * meter-name{tagA=value3,tagB=value4} otherData
+ * ... (possibly more lines for the same meter with different tag values)
+ *
+ *
+ * To select using scope or meter name, always accumulate the type and help information.
+ * Then, once we have the line containing the actual meter ID, if that line matches the selection
+ * add the previously-gathered help and type and the meter line to the output.
+ */
+ Pattern scopePattern = Pattern.compile(".*?\\{.*?mp_scope=\"" + scope + "\".*?}.*?");
+
+ StringBuilder allOutput = new StringBuilder();
+ StringBuilder typeAndHelpOutputForCurrentMeter = new StringBuilder();
+ StringBuilder meterOutputForCurrentMeter = new StringBuilder();
+
+ String[] lines = output.split("\r?\n");
+
+
+ for (String line : lines) {
+ if (line.startsWith(PROMETHEUS_HELP_PREFIX)) {
+ allOutput.append(flushForMeterAndClear(typeAndHelpOutputForCurrentMeter, meterOutputForCurrentMeter));
+ typeAndHelpOutputForCurrentMeter.append(line)
+ .append(System.lineSeparator());
+ } else if (line.startsWith(PROMETHEUS_TYPE_PREFIX)) {
+ typeAndHelpOutputForCurrentMeter.append(line)
+ .append(System.lineSeparator());
+ } else if (scopePattern.matcher(line).matches()) {
+ meterOutputForCurrentMeter.append(line)
+ .append(System.lineSeparator());
+ }
+ }
+ return allOutput.append(flushForMeterAndClear(typeAndHelpOutputForCurrentMeter, meterOutputForCurrentMeter))
+ .toString()
+ .replaceFirst("# EOF\r?\n?", "");
+ }
+
+ private static String flushForMeterAndClear(StringBuilder helpAndType, StringBuilder metricData) {
+ StringBuilder result = new StringBuilder();
+ if (!metricData.isEmpty()) {
+ result.append(helpAndType.toString())
+ .append(metricData);
+ }
+ helpAndType.setLength(0);
+ metricData.setLength(0);
+ return result.toString();
+ }
+
+ /**
+ * Prepares a set containing the names of meters from the specified Prometheus meter registry which match
+ * the specified meter name selection.
+ *
+ * @param prometheusMeterRegistry Prometheus meter registry to query
+ * @param meterNameSelection meter name to select
+ * @return set of matching meter names
+ */
+ static Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterRegistry,
+ String meterNameSelection) {
+ if (meterNameSelection == null || meterNameSelection.isEmpty()) {
+ return null; // null passed to PrometheusMeterRegistry.scrape means "no selection based on meter name
+ }
+
+ // Meter names in the output include units (if specified) so retrieve matching meters to get the units.
+ String normalizedMeterName = normalizeMeterName(meterNameSelection);
+ Set unitsForMeter = new HashSet<>();
+ unitsForMeter.add("");
+
+ Set suffixesForMeter = new HashSet<>();
+ suffixesForMeter.add("");
+
+ prometheusMeterRegistry.find(meterNameSelection)
+ .meters()
+ .forEach(meter -> {
+ Meter.Id meterId = meter.getId();
+ unitsForMeter.add("_" + meterId.getBaseUnit());
+ suffixesForMeter.addAll(meterNameSuffixes(meterId.getType()));
+ });
+
+ Set result = new HashSet<>();
+ unitsForMeter.forEach(units -> suffixesForMeter.forEach(
+ suffix -> result.add(normalizedMeterName + units + suffix)));
+
+ return result;
+ }
+
+ /**
+ * Returns the Prometheus-format meter name suffixes for the given meter type.
+ *
+ * @param meterType {@link io.micrometer.core.instrument.Meter.Type} of interest
+ * @return suffixes used in reporting the corresponding meter's value(s)
+ */
+ static Set meterNameSuffixes(Meter.Type meterType) {
+ return switch (meterType) {
+ case COUNTER -> Set.of("_total");
+ case LONG_TASK_TIMER -> Set.of("_count", "_sum", "_max");
+ case DISTRIBUTION_SUMMARY, TIMER, GAUGE, OTHER -> Set.of();
+ };
+ }
+
+ /**
+ * Convert the meter name to the format used by the Prometheus simple client.
+ *
+ * @param meterName name of the meter
+ * @return normalized meter name
+ */
+ static String normalizeMeterName(String meterName) {
+ String result = meterName;
+
+ // Convert special characters to underscores.
+ result = result.replaceAll("[-+.!?@#$%^&*`'\\s]+", "_");
+
+ // Prometheus simple client adds the prefix "m_" if a meter name starts with a digit or an underscore.
+ if (result.matches("^[0-9_]+.*")) {
+ result = "m_" + result;
+ }
+
+ // Remove non-identifier characters.
+ result = result.replaceAll("[^A-Za-z0-9_]", "");
+
+ return result;
+ }
+
+ /**
+ * Builder for creating a tailored Prometheus formatter.
+ */
+ public static class Builder implements io.helidon.common.Builder {
+
+ private String meterNameSelection;
+ private String scopeSelection;
+ private MediaType resultMediaType = MediaTypes.TEXT_PLAIN;
+
+ /**
+ * Used only internally.
+ */
+ private Builder() {
+ }
+
+ @Override
+ public PrometheusFormatter build() {
+ return new PrometheusFormatter(this);
+ }
+
+ /**
+ * Sets the meter name with which to filter the output.
+ *
+ * @param meterName meter name to select
+ * @return updated builder
+ */
+ public Builder meterName(String meterName) {
+ meterNameSelection = meterName;
+ return identity();
+ }
+
+ /**
+ * Sets the scope value with which to filter the output.
+ *
+ * @param scope scope to select
+ * @return updated builder
+ */
+ public Builder scope(String scope) {
+ scopeSelection = scope;
+ return identity();
+ }
+
+ /**
+ * Sets the {@link io.helidon.common.media.type.MediaType} which controls the formatting of the resulting output.
+ *
+ * @param resultMediaType media type
+ * @return updated builder
+ */
+ public Builder resultMediaType(MediaType resultMediaType) {
+ this.resultMediaType = resultMediaType;
+ return identity();
+ }
+ }
+
+}
diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticRestRequestWorkItem.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticRestRequestWorkItem.java
index de080990346..00ede915259 100644
--- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticRestRequestWorkItem.java
+++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/SyntheticRestRequestWorkItem.java
@@ -17,27 +17,27 @@
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.MetricID;
-import org.eclipse.microprofile.metrics.SimpleTimer;
+import org.eclipse.microprofile.metrics.Timer;
/**
* Represents metrics-related work done before and/or after Helidon processes REST requests.
*
- * An optional MP metrics feature (which Helidon implements) updates a {@link SimpleTimer} for each REST request
+ * An optional MP metrics feature (which Helidon implements) updates a {@link Timer} for each REST request
* successfully processed or throwing a mapped exception and updates a {@link Counter} for each unmapped exception thrown
* during the handling of a REST request.
*
*/
-record SyntheticRestRequestWorkItem(MetricID successfulSimpleTimerMetricID,
- SimpleTimer successfulSimpleTimer,
+record SyntheticRestRequestWorkItem(MetricID successfulTimerMetricID,
+ Timer successfulTimer,
MetricID unmappedExceptionCounterMetricID,
Counter unmappedExceptionCounter) implements MetricWorkItem {
- static SyntheticRestRequestWorkItem create(MetricID successfulSimpleTimerMetricID,
- SimpleTimer successfulSimpleTimer,
+ static SyntheticRestRequestWorkItem create(MetricID successfulTimerMetricID,
+ Timer successfulTimer,
MetricID unmappedExceptionCounterMetricID,
Counter unmappedExceptionCounter) {
- return new SyntheticRestRequestWorkItem(successfulSimpleTimerMetricID,
- successfulSimpleTimer,
+ return new SyntheticRestRequestWorkItem(successfulTimerMetricID,
+ successfulTimer,
unmappedExceptionCounterMetricID,
unmappedExceptionCounter);
}
diff --git a/microprofile/metrics/src/main/java/module-info.java b/microprofile/metrics/src/main/java/module-info.java
index 9b34469c04f..ff96e3d9b4e 100644
--- a/microprofile/metrics/src/main/java/module-info.java
+++ b/microprofile/metrics/src/main/java/module-info.java
@@ -37,13 +37,28 @@
requires io.helidon.microprofile.servicecommon;
requires io.helidon.microprofile.server;
requires io.helidon.microprofile.config;
+
requires transitive io.helidon.metrics.api;
- requires transitive io.helidon.metrics.serviceapi;
+
+ // TODO The following line(s) are temporarily commented while MP binds directly to Micrometer and provides the /metrics
+ // feature itself. Later MP metrics will bind instead to the neutral Helidon metrics API and these lines or equivalent ones
+ // will return then.
+// requires transitive io.helidon.metrics.serviceapi;
+
requires io.helidon.nima.observe.metrics;
requires transitive microprofile.config.api;
requires microprofile.metrics.api;
requires io.helidon.config.mp;
+
+ // TODO - The following lines are temporary while the MP metrics implementation binds directly to Micrometer and provides the
+ // full metrics implementation (including the feature supporting /metrics). Later when MP metrics binds to the neutral
+ // Helidon metrics API these two lines will disappear from here.
+ requires io.helidon.nima.servicecommon; // because this component temporarily provides its own /metrics feature.
+ requires java.management; // Because the base registry which uses JMX beans is temporarily in this component.
+ requires micrometer.core;
+
+ // TODO end of temp lines
exports io.helidon.microprofile.metrics;
exports io.helidon.microprofile.metrics.spi;
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java
index e5122fa1e4e..a84c9a6776b 100644
--- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseTest.java
@@ -77,9 +77,9 @@ public class HelloWorldAsyncResponseTest {
@Test
public void test() throws Exception {
MetricID metricID = MetricsCdiExtension
- .restEndpointSimpleTimerMetricID(HelloWorldResource.class.getMethod("slowMessage",
- AsyncResponse.class,
- ServerResponse.class));
+ .restEndpointTimerMetricID(HelloWorldResource.class.getMethod("slowMessage",
+ AsyncResponse.class,
+ ServerResponse.class));
SortedMap simpleTimers = registry.getSimpleTimers();
@@ -149,7 +149,7 @@ public void testAsyncWithArg() {
SimpleTimer getSyntheticSimpleTimer() {
MetricID metricID = null;
try {
- metricID = MetricsCdiExtension.restEndpointSimpleTimerMetricID(
+ metricID = MetricsCdiExtension.restEndpointTimerMetricID(
HelloWorldResource.class.getMethod("slowMessageWithArg",
String.class, AsyncResponse.class));
} catch (NoSuchMethodException e) {
@@ -160,7 +160,7 @@ SimpleTimer getSyntheticSimpleTimer() {
SimpleTimer syntheticSimpleTimer = simpleTimers.get(metricID);
// We should not need to retry here. Annotation processing creates the synthetic simple timers long before tests run.
assertThat("Synthetic simple timer "
- + MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME,
+ + MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_NAME,
syntheticSimpleTimer, is(notNullValue()));
return syntheticSimpleTimer;
}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java
index f687e859a2b..27dcf6bf9d7 100644
--- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldRestEndpointSimpleTimerDisabledTest.java
@@ -47,7 +47,7 @@ static void init() {
boolean isSyntheticSimpleTimerPresent() {
return !syntheticSimpleTimerRegistry.getSimpleTimers((metricID, metric) ->
- metricID.getName().equals(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME))
+ metricID.getName().equals(MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_NAME))
.isEmpty();
}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java
index c5776a8f030..c9fc107e16d 100644
--- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldTest.java
@@ -213,7 +213,7 @@ SimpleTimer getSyntheticSimpleTimer(String methodName, Class>... paramTypes) {
}
SimpleTimer getSyntheticSimpleTimer(Method method) {
- return getSyntheticSimpleTimer(MetricsCdiExtension.restEndpointSimpleTimerMetricID(method));
+ return getSyntheticSimpleTimer(MetricsCdiExtension.restEndpointTimerMetricID(method));
}
SimpleTimer getSyntheticSimpleTimer(MetricID metricID) {
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMatcher.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMatcher.java
new file mode 100644
index 00000000000..b94119c54db
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMatcher.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 2022 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.microprofile.metrics;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Hamcrest Matcher for Metrics-related value checking.
+ *
+ * Includes:
+ *
+ * - {@link #withinTolerance(Number)} for checking within a configured range (tolerance) either side of an expected
+ * value
+ * - {@link #withinTolerance(Number, double)} for checking within a specified range
+ *
+ */
+class MetricsMatcher {
+
+ static final Double VARIANCE = Double.valueOf(System.getProperty("helidon.histogram.tolerance", "0.001"));
+
+ static TypeSafeMatcher withinTolerance(final Number expected) {
+ return withinTolerance(expected, VARIANCE);
+ }
+
+ static TypeSafeMatcher withinTolerance(final Number expected, double variance) {
+ return new TypeSafeMatcher<>() {
+
+ private final double v = variance;
+
+ @Override
+ protected boolean matchesSafely(Number item) {
+ return Math.abs(expected.doubleValue() - item.doubleValue()) <= expected.doubleValue() * v;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("withinTolerance expected value in range [")
+ .appendValue(expected.doubleValue() * (1.0 - v))
+ .appendText(", ")
+ .appendValue(expected.doubleValue() * (1.0 + v))
+ .appendText("]");
+ }
+ };
+ }
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java
index 0feda37442f..fa2fdbe953c 100644
--- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java
@@ -43,7 +43,7 @@ public static void wrapupTest() {
static MetricRegistry cleanUpSyntheticSimpleTimerRegistry() {
MetricRegistry result = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.BASE);
- result.remove(MetricsCdiExtension.SYNTHETIC_SIMPLE_TIMER_METRIC_NAME);
+ result.remove(MetricsCdiExtension.SYNTHETIC_TIMER_METRIC_NAME);
return result;
}
@@ -54,7 +54,7 @@ static MetricRegistry cleanUpSyntheticSimpleTimerRegistry() {
@RegistryType(type = MetricRegistry.Type.BASE)
private MetricRegistry baseRegistry;
- MetricRegistry syntheticSimpleTimerRegistry() {
+ MetricRegistry syntheticTimerTimerRegistry() {
return baseRegistry;
}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MpFeatureTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MpFeatureTest.java
new file mode 100644
index 00000000000..fa10480dbc1
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MpFeatureTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.helidon.metrics.api.RegistryFactory;
+import io.helidon.microprofile.tests.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+@HelidonTest
+public class MpFeatureTest {
+
+ @Inject
+ private WebTarget webTarget;
+
+ @Disabled
+ @Test
+ void testEndpoint() {
+ MetricRegistry metricRegistry = RegistryFactory.getInstance().getRegistry(RegistryFactory.APPLICATION_SCOPE);
+ Counter counter = metricRegistry.counter("endpointCounter");
+ counter.inc(4);
+
+ String metricsResponse = webTarget.path("/metrics")
+ .request()
+ .accept(MediaType.TEXT_PLAIN)
+ .get(String.class);
+
+ Pattern pattern = Pattern.compile(".*^endpointCounter_total\\{.*?mp_scope=\"application\".*?}\\s*(\\S*).*?");
+ Matcher matcher = pattern.matcher(metricsResponse);
+
+ assertThat("/metrics response", matcher.matches(), is(true));
+ assertThat("Captured groups", matcher.groupCount(), is(1));
+ assertThat("Captured counter value", Integer.parseInt(matcher.group(1)), is(4));
+ }
+
+
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestCounters.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestCounters.java
new file mode 100644
index 00000000000..f7eebe23c43
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestCounters.java
@@ -0,0 +1,97 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.Arrays;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Tag;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class TestCounters {
+
+ static PrometheusMeterRegistry prometheusMeterRegistry;
+ static MeterRegistry meterRegistry;
+ static MpMetricRegistry mpMetricRegistry;
+
+ @BeforeAll
+ static void setup() {
+ PrometheusConfig config = new PrometheusConfig() {
+ @Override
+ public String get(String s) {
+ return null;
+ }
+ };
+
+ prometheusMeterRegistry = new PrometheusMeterRegistry(config);
+ meterRegistry = Metrics.globalRegistry;
+
+ mpMetricRegistry = MpMetricRegistry.create("myscope", meterRegistry);
+ }
+
+ @Test
+ void testCounter() {
+ Counter counter = mpMetricRegistry.counter("myCounter");
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+ }
+
+ @Test
+ void testConflictingTags() {
+ Counter counter = mpMetricRegistry.counter("conflictingCounterDueToTags"); // name only
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> mpMetricRegistry.counter("conflictingCounterDueToTags",
+ new Tag[] {new Tag("tag1", "value1")}));
+ assertThat("Inconsistent tags check", ex.getMessage(), containsString("inconsistent"));
+ }
+
+ @Test
+ void testConsistentTags() {
+ Tag[] tags = {new Tag("tag1", "value1"), new Tag("tag2", "value2")};
+ Counter counter1 = mpMetricRegistry.counter("sameTag", tags);
+ Counter counter2 = mpMetricRegistry.counter("sameTag", Arrays.copyOf(tags, tags.length));
+ assertThat("Reregistered meter", counter2, is(sameInstance(counter1)));
+ }
+
+ @Test
+ void conflictingMetadata() {
+ mpMetricRegistry.counter(Metadata.builder()
+ .withName("counterWithMetadata")
+ .withDescription("first")
+ .build());
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> mpMetricRegistry.counter(Metadata.builder()
+ .withName("counterWithMetadata")
+ .withDescription("second")
+ .build()));
+
+ assertThat("Error message",
+ ex.getMessage().matches(".*?metadata.*?inconsistent.*?"),
+ is(true));
+ }
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestFormatter.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestFormatter.java
new file mode 100644
index 00000000000..3d28b8c7870
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestFormatter.java
@@ -0,0 +1,191 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.helidon.common.media.type.MediaTypes;
+
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class TestFormatter {
+
+ private static final String SCOPE = "formatScope";
+ private static MpRegistryFactory mpRegistryFactory;
+
+ @BeforeAll
+ static void init() {
+ mpRegistryFactory = MpRegistryFactory.get();
+ }
+
+ @Test
+ void testSimpleCounterFormatting() {
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
+
+ String counterName = "formatCounter";
+ Counter counter = reg.counter(counterName);
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+
+ ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ .stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
+
+ PrometheusFormatter formatter = PrometheusFormatter.builder().build();
+ String promFormat = formatter.filteredOutput();
+
+ // Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
+ // capture the meter value, further uninteresting text.
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(1));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
+ }
+
+ @Test
+ void testScopeSelection() {
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
+
+ String otherScope = "other";
+ MetricRegistry otherRegistry = mpRegistryFactory.registry(otherScope);
+
+ String counterName = "formatCounterWithScope";
+ Counter counter = reg.counter(counterName);
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+
+ // Even though we register this "other" counter in a different MP registry, it should
+ // find its way into the shared Prometheus meter registry (with the correct mp_scope tag).
+ Counter otherCounter = otherRegistry.counter(counterName);
+ otherCounter.inc(2L);
+
+ ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ .stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
+
+ PrometheusFormatter formatter = PrometheusFormatter.builder()
+ .resultMediaType(MediaTypes.TEXT_PLAIN)
+ .scope(SCOPE)
+ .build();
+ String promFormat = formatter.filteredOutput();
+
+ // Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
+ // capture the meter value, further uninteresting text.
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(1));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
+
+ // Make sure the "other" counter is not also present in the output; it should have been suppressed
+ // because of the scope filtering we requested.
+ Pattern unexpectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + otherScope
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ matcher = unexpectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(false));
+
+ }
+
+ @Test
+ void testNameSelection() {
+ MpMetricRegistry reg = (MpMetricRegistry) mpRegistryFactory.registry(SCOPE);
+
+ String counterName = "formatCounterWithName";
+ Counter counter = reg.counter(counterName);
+ counter.inc();
+ assertThat("Updated counter", counter.getCount(), is(1L));
+
+ String otherCounterName = "otherFormatCounterWithName";
+ Counter otherCounter = reg.counter(otherCounterName);
+ otherCounter.inc(3L);
+
+ ((CompositeMeterRegistry) reg.meterRegistry()).getRegistries()
+ .stream()
+ .filter(PrometheusMeterRegistry.class::isInstance)
+ .map(PrometheusMeterRegistry.class::cast)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find Prometheus registry"));
+
+ PrometheusFormatter formatter = PrometheusFormatter.builder()
+ .resultMediaType(MediaTypes.TEXT_PLAIN)
+ .meterName(counterName)
+ .build();
+ String promFormat = formatter.filteredOutput();
+
+ // Want to match: any uninteresting lines, start-of-line, the meter name, the tags (capturing the scope tag value),
+ // capture the meter value, further uninteresting text.
+ Pattern expectedNameAndTagAndValue = Pattern.compile(".*?^"
+ + counterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ Matcher matcher = expectedNameAndTagAndValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(true));
+ assertThat("Output matcher groups", matcher.groupCount(), is(1));
+ assertThat("Captured metric value as ", Double.parseDouble(matcher.group(1)), is(1.0D));
+
+ // Make sure the counter with an unmatching name does not also appear in the output.
+ Pattern unexpectedNameAndTagValue = Pattern.compile(".*?^"
+ + otherCounterName
+ + "_total\\{.*mp_scope=\""
+ + SCOPE
+ + "\".*?}\\s+(\\S+).*?",
+ Pattern.MULTILINE + Pattern.DOTALL);
+ matcher = unexpectedNameAndTagValue.matcher(promFormat);
+ assertThat("Pattern match check: output " + System.lineSeparator() + promFormat + System.lineSeparator(),
+ matcher.matches(),
+ is(false));
+ }
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestGlobalTagHelper.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestGlobalTagHelper.java
new file mode 100644
index 00000000000..20d7067ad94
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestGlobalTagHelper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.util.Optional;
+
+import io.helidon.common.testing.junit5.OptionalMatcher;
+import io.micrometer.core.instrument.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.arrayWithSize;
+import static org.hamcrest.Matchers.is;
+
+
+class TestGlobalTagHelper {
+
+ @Test
+ void checkSingle() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ Optional tagsOpt = helper.tags("a=4");
+ assertThat("Optional tags", tagsOpt, OptionalMatcher.optionalPresent());
+ Tag[] tags = tagsOpt.get();
+ assertThat("Single value assignments", tags, arrayWithSize(1));
+ assertThat("Single assignment key", tags[0].getKey(), is("a"));
+ assertThat("Single assignment value", tags[0].getValue(), is("4"));
+ }
+
+ @Test
+ void checkMultiple() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ Optional tagsOpt = helper.tags("a=11,b=12,c=13");
+ assertThat("Optional tags", tagsOpt, OptionalMatcher.optionalPresent());
+ Tag[] tags = tagsOpt.get();
+
+ assertThat("Multiple value assignments", tags, arrayWithSize(3));
+ assertThat("Multiple assignment key 0", tags[0].getKey(), is("a"));
+ assertThat("Multiple assignment value 0", tags[0].getValue(), is("11"));
+ assertThat("Multiple assignment key 1", tags[1].getKey(), is("b"));
+ assertThat("Multiple assignment value 1", tags[1].getValue(), is("12"));
+ assertThat("Multiple assignment key 2", tags[2].getKey(), is("c"));
+ assertThat("Multiple assignment value 2", tags[2].getValue(), is("13"));
+ }
+
+ @Test
+ void checkQuoted() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ Optional tagsOpt = helper.tags("d=r\\=3,e=4,f=0\\,1,g=hi");
+ assertThat("Optional tags", tagsOpt, OptionalMatcher.optionalPresent());
+ Tag[] tags = tagsOpt.get();
+ assertThat("Quoted value assignments", tags, arrayWithSize(4));
+ assertThat("Quoted assignment key 0", tags[0].getKey(), is("d"));
+ assertThat("Quoted assignment value 0", tags[0].getValue(), is("r=3"));
+ assertThat("Quoted assignment key 1", tags[1].getKey(), is("e"));
+ assertThat("Quoted assignment value 1", tags[1].getValue(), is("4"));
+ assertThat("Quoted assignment key 2", tags[2].getKey(), is("f"));
+ assertThat("Quoted assignment value 2", tags[2].getValue(), is("0,1"));
+ assertThat("Quoted assignment key 3", tags[3].getKey(), is("g"));
+ assertThat("Quoted assignment value 3", tags[3].getValue(), is("hi"));
+ }
+
+ @Test
+ void checkErrors() {
+ GlobalTagsHelper helper = new GlobalTagsHelper();
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> helper.tags(""));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("empty"));
+
+ ex = assertThrows(IllegalArgumentException.class, () -> helper.tags("a="));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("found 1"));
+
+ ex = assertThrows(IllegalArgumentException.class, () -> helper.tags("=1"));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("left"));
+
+ ex = assertThrows(IllegalArgumentException.class, () -> helper.tags("a*=1,"));
+ assertThat("Exception for empty assignments", ex.getMessage(), containsString("tag name"));
+ }
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestHistograms.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestHistograms.java
new file mode 100644
index 00000000000..72a7b88f8b1
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestHistograms.java
@@ -0,0 +1,76 @@
+/*
+ * 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.microprofile.metrics;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Snapshot;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.microprofile.metrics.MetricsMatcher.withinTolerance;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+
+public class TestHistograms {
+
+ static PrometheusMeterRegistry prometheusMeterRegistry;
+
+ static MeterRegistry meterRegistry;
+
+ static MpMetricRegistry mpMetricRegistry;
+
+ @BeforeAll
+ static void setup() {
+ PrometheusConfig config = new PrometheusConfig() {
+ @Override
+ public String get(String s) {
+ return null;
+ }
+ };
+
+ prometheusMeterRegistry = new PrometheusMeterRegistry(config);
+ meterRegistry = Metrics.globalRegistry;
+
+ mpMetricRegistry = MpMetricRegistry.create("histoScope", meterRegistry);
+ }
+
+ @Test
+ void testHistogram() {
+ Histogram histogram = mpMetricRegistry.histogram("myHisto");
+ histogram.update(4);
+ histogram.update(24);
+ assertThat("Count", histogram.getCount(), is(2L));
+ assertThat("Sum", histogram.getSum(), is(28L));
+ Snapshot snapshot = histogram.getSnapshot();
+ assertThat("Mean", snapshot.getMean(), is(14.0D));
+ assertThat("Max", snapshot.getMax(), is(24.0D));
+ Snapshot.PercentileValue[] percentileValues = snapshot.percentileValues();
+
+ double[] expectedPercents = {0.5, 0.75, 0.95, 0.98, 0.99, 0.999};
+ double[] expectedValues = {4.0, 24.0, 24.0, 24.0, 24.0, 24.0};
+
+ for (int i = 0; i < percentileValues.length; i++ ) {
+ assertThat("Percentile " + i + " %", percentileValues[i].getPercentile(), is(expectedPercents[i]));
+ assertThat("Percentile " + i + " value", percentileValues[i].getValue(), is(withinTolerance(expectedValues[i])));
+ }
+ }
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestTimers.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestTimers.java
new file mode 100644
index 00000000000..73799ea0523
--- /dev/null
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestTimers.java
@@ -0,0 +1,92 @@
+/*
+ * 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.microprofile.metrics;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.eclipse.microprofile.metrics.Snapshot;
+import org.eclipse.microprofile.metrics.Timer;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.microprofile.metrics.MetricsMatcher.withinTolerance;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class TestTimers {
+
+ static PrometheusMeterRegistry prometheusMeterRegistry;
+
+ static MeterRegistry meterRegistry;
+
+ static MpMetricRegistry mpMetricRegistry;
+
+ @BeforeAll
+ static void setup() {
+ PrometheusConfig config = new PrometheusConfig() {
+ @Override
+ public String get(String s) {
+ return null;
+ }
+ };
+
+ prometheusMeterRegistry = new PrometheusMeterRegistry(config);
+ meterRegistry = Metrics.globalRegistry.add(prometheusMeterRegistry);
+
+ mpMetricRegistry = MpMetricRegistry.create("timerScope", meterRegistry);
+ }
+
+ @Test
+ void testTimer() {
+ Timer timer = mpMetricRegistry.timer("myTimer");
+ timer.update(Duration.ofSeconds(2));
+ try (Timer.Context context = timer.time()) {
+ TimeUnit.SECONDS.sleep(3);
+ // Don't explicitly stop the context; the try-with-resources will close it which stops the context.
+ // Doing both adds an additional sample which skews the stats (and disturbs this test).
+ } catch (InterruptedException ex) {
+ fail("Thread interrupted while waiting for some time to pass");
+ }
+
+ assertThat("Count", timer.getCount(), is(2L));
+ assertThat("Sum", timer.getElapsedTime().getSeconds(), is(withinTolerance(5)));
+
+ Snapshot snapshot = timer.getSnapshot();
+ assertThat("Mean", toMillis(snapshot.getMean()), is(withinTolerance(2500L, 0.01)));
+ assertThat("Max", toMillis(snapshot.getMax()), is(withinTolerance(3000L, 0.01)));
+ Snapshot.PercentileValue[] percentileValues = snapshot.percentileValues();
+
+ double[] expectedPercents = {0.5, 0.75, 0.95, 0.98, 0.99, 0.999};
+ double[] expectedMilliseconds = {2000.0, 3000.0, 3000.0, 3000.0, 3000.0, 3000.0};
+
+ for (int i = 0; i < percentileValues.length; i++ ) {
+ assertThat("Percentile " + i + " %", percentileValues[i].getPercentile(), is(expectedPercents[i]));
+ assertThat("Percentile " + i + " value",
+ toMillis(percentileValues[i].getValue()),
+ is(withinTolerance(expectedMilliseconds[i], 0.01)));
+ }
+ }
+
+ private static long toMillis(double value) {
+ return TimeUnit.NANOSECONDS.toMillis((long) value);
+ }
+}
diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestVetoedResource.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestVetoedResource.java
index 544bdd82c19..68b449c53a8 100644
--- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestVetoedResource.java
+++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestVetoedResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2020, 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.
@@ -43,31 +43,31 @@ void testNoAnnotatedMetricForVetoedResource() throws NoSuchMethodException {
}
@Test
- void testNoSyntheticSimplyTimedMetricForVetoedResource() throws NoSuchMethodException {
+ void testNoSyntheticTimedMetricForVetoedResource() throws NoSuchMethodException {
// Makes sure that a vetoed JAX-RS resource with an explicit metric annotation was not registered with a synthetic
- // SimplyTimed metric.
+ // Timed metric.
Method method = VetoedResource.class.getMethod("get");
assertThat(
- "Metrics CDI extension incorrectly registered a synthetic simple timer on a vetoed resource JAX-RS endpoint "
+ "Metrics CDI extension incorrectly registered a synthetic timer on a vetoed resource JAX-RS endpoint "
+ "method with an explicit metrics annotation",
- syntheticSimpleTimerRegistry()
- .getSimpleTimers()
- .containsKey(MetricsCdiExtension.restEndpointSimpleTimerMetricID(method)),
+ syntheticTimerTimerRegistry()
+ .getTimers()
+ .containsKey(MetricsCdiExtension.restEndpointTimerMetricID(method)),
is(false));
}
@Test
- void testNoSyntheticSimplyTimedMetricForVetoedResourceWithJaxRsEndpointButOtherwiseUnmeasured() throws NoSuchMethodException {
+ void testNoSyntheticTimedMetricForVetoedResourceWithJaxRsEndpointButOtherwiseUnmeasured() throws NoSuchMethodException {
// Makes sure that a vetoed JAX-RS resource with no explicit metric annotation was not registered with a synthetic
- // SimpleTimed metric.
+ // Timed metric.
Method method = VetoedJaxRsButOtherwiseUnmeasuredResource.class.getMethod("get");
assertThat(
- "Metrics CDI extension incorrectly registered a synthetic simple timer on JAX-RS endpoint method with no "
+ "Metrics CDI extension incorrectly registered a synthetic timer on JAX-RS endpoint method with no "
+ "explicit metrics annotation: "
+ VetoedJaxRsButOtherwiseUnmeasuredResource.class.getName() + "#" + method.getName(),
MetricsCdiExtension.getRegistryForSyntheticRestRequestMetrics()
- .getSimpleTimers()
- .containsKey(MetricsCdiExtension.restEndpointSimpleTimerMetricID(method)),
+ .getTimers()
+ .containsKey(MetricsCdiExtension.restEndpointTimerMetricID(method)),
is(false));
}
}
diff --git a/nima/observe/metrics/src/main/java/io/helidon/nima/observe/metrics/MetricsFeature.java b/nima/observe/metrics/src/main/java/io/helidon/nima/observe/metrics/MetricsFeature.java
index 75429f6990f..8869c4fe2b4 100644
--- a/nima/observe/metrics/src/main/java/io/helidon/nima/observe/metrics/MetricsFeature.java
+++ b/nima/observe/metrics/src/main/java/io/helidon/nima/observe/metrics/MetricsFeature.java
@@ -239,7 +239,7 @@ private void setUpEndpoints(HttpRules rules) {
// routing to each scope
Stream.of(app, base, vendor)
.forEach(registry -> {
- String type = registry.type();
+ String type = registry.scope();
rules.get("/" + type, (req, res) -> getAll(req, res, registry))
.get("/" + type + "/{metric}", (req, res) -> getByName(req, res, registry))
From ea53e8da87af8b97e1ae88e8f55995d0a3224005 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com"
Date: Thu, 22 Jun 2023 00:52:58 -0500
Subject: [PATCH 10/67] More improvements to metrics/api and metrics/metrics
---
.../metrics/api/NoOpMetricFactory.java | 6 +
.../helidon/metrics/api/NoOpMetricImpl.java | 35 +-
.../io/helidon/metrics/api/SampledMetric.java | 4 +-
.../metrics/api/spi/MetricFactory.java | 21 +-
.../metrics/HelidonConcurrentGauge.java | 238 --------------
.../io/helidon/metrics/HelidonCounter.java | 104 +++---
.../java/io/helidon/metrics/HelidonGauge.java | 145 ++++++--
.../io/helidon/metrics/HelidonHistogram.java | 44 ++-
.../java/io/helidon/metrics/HelidonMeter.java | 220 -------------
.../helidon/metrics/HelidonMetricFactory.java | 16 +-
.../helidon/metrics/HelidonSimpleTimer.java | 309 ------------------
.../java/io/helidon/metrics/HelidonTimer.java | 156 ++-------
.../java/io/helidon/metrics/MetricImpl.java | 15 +
.../helidon/metrics/HelidonCounterTest.java | 33 +-
14 files changed, 313 insertions(+), 1033 deletions(-)
delete mode 100644 metrics/metrics/src/main/java/io/helidon/metrics/HelidonConcurrentGauge.java
delete mode 100644 metrics/metrics/src/main/java/io/helidon/metrics/HelidonMeter.java
delete mode 100644 metrics/metrics/src/main/java/io/helidon/metrics/HelidonSimpleTimer.java
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java
index 1da4377a902..b499cc618c6 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricFactory.java
@@ -17,6 +17,7 @@
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
import io.helidon.metrics.api.spi.MetricFactory;
@@ -52,4 +53,9 @@ public Gauge gauge(String scope, Metadata metadata, Suppli
public Gauge gauge(String scope, Metadata metadata, T target, Function fn, Tag... tags) {
return NoOpMetricImpl.NoOpGaugeImpl.create(scope, metadata, target, fn, tags);
}
+
+ @Override
+ public Gauge gauge(String scope, Metadata metadata, T target, ToDoubleFunction fn, Tag... tags) {
+ return NoOpMetricImpl.NoOpGaugeImpl.create(scope, metadata, target, fn, tags);
+ }
}
diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java
index 6c033ed6e38..d692da25957 100644
--- a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java
+++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMetricImpl.java
@@ -20,6 +20,7 @@
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
@@ -83,11 +84,19 @@ static NoOpGaugeImpl create(String registryType,
return new NoOpGaugeImpl<>(registryType, metadata, target, fn);
}
+ static NoOpGaugeImplToDoubleFn create(String registryType,
+ Metadata metadata,
+ T target,
+ ToDoubleFunction fn,
+ Tag... tags) {
+ return new NoOpGaugeImplToDoubleFn(registryType, metadata, target, fn, tags);
+ }
+
private NoOpGaugeImpl(String registryType, Metadata metadata, Supplier supplier) {
super(registryType, metadata);
this.supplier = supplier;
target = null;
- fn = null;
+ this.fn = null;
}
private NoOpGaugeImpl(String registryType, Metadata metadata, T target, Function fn) {
@@ -97,12 +106,34 @@ private NoOpGaugeImpl(String registryType, Metadata metadata, T target, Function
supplier = null;
}
+
+
@Override
public N getValue() {
- return supplier != null ? supplier.get() : fn.apply(target);
+ return supplier != null
+ ? supplier.get()
+ : fn.apply(target);
}
+ }
+
+ static class NoOpGaugeImplToDoubleFn extends NoOpMetricImpl implements Gauge