diff --git a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/MeterMetadata.java b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/MeterMetadata.java index cce8959a078..34bb6c09238 100644 --- a/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/MeterMetadata.java +++ b/dbclient/metrics/src/main/java/io/helidon/dbclient/metrics/MeterMetadata.java @@ -61,6 +61,7 @@ static MeterMetadata.Builder builder(Meter meter) { return new MeterMetadata.Builder(meter); } + private static final String SCOPE = "application"; private final String name; private final String description; private final String unit; @@ -118,6 +119,7 @@ , M extends Meter> B apply(B builder) { if (unit != null) { builder.baseUnit(unit); } + builder.scope(SCOPE); return builder; } diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml index 3ad5fd2ecdf..bac22d05152 100644 --- a/examples/cors/pom.xml +++ b/examples/cors/pom.xml @@ -68,8 +68,13 @@ io.helidon.webserver.observe helidon-webserver-observe-metrics + runtime + io.helidon.metrics + helidon-metrics-system-meters + runtime + io.helidon.config helidon-config-yaml @@ -81,10 +86,6 @@ io.helidon.health helidon-health-checks - - io.helidon.metrics - helidon-metrics - jakarta.json jakarta.json-api diff --git a/examples/dbclient/jdbc/pom.xml b/examples/dbclient/jdbc/pom.xml index bbb6b344023..95f0a348c77 100644 --- a/examples/dbclient/jdbc/pom.xml +++ b/examples/dbclient/jdbc/pom.xml @@ -109,9 +109,15 @@ helidon-examples-dbclient-common ${project.version} + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters + runtime diff --git a/examples/dbclient/mongodb/pom.xml b/examples/dbclient/mongodb/pom.xml index 6ca819a5a56..a67b5d02b00 100644 --- a/examples/dbclient/mongodb/pom.xml +++ b/examples/dbclient/mongodb/pom.xml @@ -76,7 +76,17 @@ io.helidon.metrics - helidon-metrics + helidon-metrics-api + + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + + + io.helidon.metrics + helidon-metrics-system-meters + runtime io.helidon.tracing diff --git a/examples/dbclient/pokemons/pom.xml b/examples/dbclient/pokemons/pom.xml index 9b47fa0ebf7..a3fb4739444 100644 --- a/examples/dbclient/pokemons/pom.xml +++ b/examples/dbclient/pokemons/pom.xml @@ -95,9 +95,14 @@ io.helidon.config helidon-config-yaml + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters runtime diff --git a/examples/employee-app/pom.xml b/examples/employee-app/pom.xml index 4681ef2bbdd..2f1d8dfc409 100644 --- a/examples/employee-app/pom.xml +++ b/examples/employee-app/pom.xml @@ -66,9 +66,15 @@ io.helidon.health helidon-health-checks + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters + runtime io.helidon.dbclient diff --git a/examples/integrations/microstream/greetings-se/pom.xml b/examples/integrations/microstream/greetings-se/pom.xml index 200b0b1986e..545a954505f 100644 --- a/examples/integrations/microstream/greetings-se/pom.xml +++ b/examples/integrations/microstream/greetings-se/pom.xml @@ -63,9 +63,14 @@ io.helidon.metrics helidon-metrics-api + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters runtime @@ -98,4 +103,4 @@ - \ No newline at end of file + diff --git a/examples/metrics/exemplar/pom.xml b/examples/metrics/exemplar/pom.xml index 6c169f4956c..27e2eb9584c 100644 --- a/examples/metrics/exemplar/pom.xml +++ b/examples/metrics/exemplar/pom.xml @@ -63,11 +63,7 @@ io.helidon.metrics - helidon-metrics-api - - - io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters runtime diff --git a/examples/metrics/filtering/mp/pom.xml b/examples/metrics/filtering/mp/pom.xml index 179eac3f955..0f75c24fb8e 100644 --- a/examples/metrics/filtering/mp/pom.xml +++ b/examples/metrics/filtering/mp/pom.xml @@ -39,6 +39,16 @@ io.helidon.microprofile.metrics helidon-microprofile-metrics + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + + + io.helidon.metrics + helidon-metrics-system-meters + runtime + io.helidon.webclient helidon-webclient diff --git a/examples/metrics/filtering/mp/src/main/resources/META-INF/microprofile-config.properties b/examples/metrics/filtering/mp/src/main/resources/META-INF/microprofile-config.properties index 81933be305a..d652c78f17b 100644 --- a/examples/metrics/filtering/mp/src/main/resources/META-INF/microprofile-config.properties +++ b/examples/metrics/filtering/mp/src/main/resources/META-INF/microprofile-config.properties @@ -21,5 +21,5 @@ config_ordinal=1000 server.port=8080 server.host=0.0.0.0 -metrics.registries.0.scope = application -metrics.registries.0.filter.exclude = .*Gets +metrics.scoping.scopes.0.name=application +metrics.scoping.scopes.0.filter.exclude = .*Gets diff --git a/examples/metrics/filtering/mp/src/test/java/io/helidon/examples/metrics/filtering/mp/MainTest.java b/examples/metrics/filtering/mp/src/test/java/io/helidon/examples/metrics/filtering/mp/MainTest.java index 42adba4e89b..b183bb405b5 100644 --- a/examples/metrics/filtering/mp/src/test/java/io/helidon/examples/metrics/filtering/mp/MainTest.java +++ b/examples/metrics/filtering/mp/src/test/java/io/helidon/examples/metrics/filtering/mp/MainTest.java @@ -23,7 +23,6 @@ import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.Timer; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -44,8 +43,6 @@ void checkEnabledMetric() { personalizedGreetingsCounter.getCount() - before, is(1L)); } - // TODO metrics - @Disabled @Test void checkDisabledMetric() { Timer getsTimer = appRegistry.timer(GreetResource.TIMER_FOR_GETS); diff --git a/examples/metrics/filtering/se/pom.xml b/examples/metrics/filtering/se/pom.xml index 4bdfd81ef10..c387df292aa 100644 --- a/examples/metrics/filtering/se/pom.xml +++ b/examples/metrics/filtering/se/pom.xml @@ -58,14 +58,6 @@ io.helidon.metrics helidon-metrics-api - - - io.helidon.metrics - helidon-metrics - runtime - io.helidon.config helidon-config-yaml diff --git a/examples/metrics/filtering/se/src/main/java/io/helidon/examples/metrics/filtering/se/Main.java b/examples/metrics/filtering/se/src/main/java/io/helidon/examples/metrics/filtering/se/Main.java index 7e18053dd31..d2b9bb2148b 100644 --- a/examples/metrics/filtering/se/src/main/java/io/helidon/examples/metrics/filtering/se/Main.java +++ b/examples/metrics/filtering/se/src/main/java/io/helidon/examples/metrics/filtering/se/Main.java @@ -16,6 +16,8 @@ package io.helidon.examples.metrics.filtering.se; +import java.util.regex.Pattern; + import io.helidon.common.config.Config; import io.helidon.common.config.GlobalConfig; import io.helidon.logging.common.LogConfig; @@ -73,7 +75,7 @@ static void setup(WebServerConfig.Builder server) { // the metrics feature class will use. ScopeConfig scopeConfig = ScopeConfig.builder() .name(Meter.Scope.APPLICATION) - .exclude(GreetService.TIMER_FOR_GETS) + .exclude(Pattern.compile(GreetService.TIMER_FOR_GETS)) .build(); MetricsConfig initialMetricsConfig = config.get(MetricsConfig.METRICS_CONFIG_KEY) diff --git a/examples/metrics/http-status-count-se/pom.xml b/examples/metrics/http-status-count-se/pom.xml index 294baa594fe..563bcea4d45 100644 --- a/examples/metrics/http-status-count-se/pom.xml +++ b/examples/metrics/http-status-count-se/pom.xml @@ -50,11 +50,6 @@ io.helidon.metrics helidon-metrics-api - - io.helidon.metrics - helidon-metrics - runtime - io.helidon.webserver.observe helidon-webserver-observe @@ -63,6 +58,11 @@ io.helidon.webserver.observe helidon-webserver-observe-metrics + + io.helidon.metrics + helidon-metrics-system-meters + runtime + io.helidon.webserver.observe helidon-webserver-observe-health diff --git a/examples/metrics/kpi/pom.xml b/examples/metrics/kpi/pom.xml index 87a27d39a51..6b8a990db0e 100644 --- a/examples/metrics/kpi/pom.xml +++ b/examples/metrics/kpi/pom.xml @@ -53,11 +53,7 @@ io.helidon.metrics - helidon-metrics-api - - - io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters runtime diff --git a/examples/microprofile/http-status-count-mp/pom.xml b/examples/microprofile/http-status-count-mp/pom.xml index 8dc458572de..6edd55fe149 100644 --- a/examples/microprofile/http-status-count-mp/pom.xml +++ b/examples/microprofile/http-status-count-mp/pom.xml @@ -54,6 +54,16 @@ io.helidon.microprofile.health helidon-microprofile-health + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + + + io.helidon.metrics + helidon-metrics-system-meters + runtime + io.smallrye jandex diff --git a/examples/microprofile/http-status-count-mp/src/test/java/io/helidon/examples/mp/httpstatuscount/MainTest.java b/examples/microprofile/http-status-count-mp/src/test/java/io/helidon/examples/mp/httpstatuscount/MainTest.java index 64a530fe463..bfdeb06c76b 100644 --- a/examples/microprofile/http-status-count-mp/src/test/java/io/helidon/examples/mp/httpstatuscount/MainTest.java +++ b/examples/microprofile/http-status-count-mp/src/test/java/io/helidon/examples/mp/httpstatuscount/MainTest.java @@ -24,7 +24,6 @@ import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.MetricRegistry; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @@ -32,8 +31,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -// TODO metrics -@Disabled @HelidonTest @TestMethodOrder(MethodOrderer.MethodName.class) public class MainTest { diff --git a/examples/microprofile/multiport/pom.xml b/examples/microprofile/multiport/pom.xml index 3d01eccfacb..e9cfb8f0cbd 100644 --- a/examples/microprofile/multiport/pom.xml +++ b/examples/microprofile/multiport/pom.xml @@ -45,6 +45,16 @@ io.helidon.microprofile.health helidon-microprofile-health + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + + + io.helidon.metrics + helidon-metrics-system-meters + runtime + org.junit.jupiter junit-jupiter-api diff --git a/examples/openapi/pom.xml b/examples/openapi/pom.xml index d39d96915fa..6f04e688be9 100644 --- a/examples/openapi/pom.xml +++ b/examples/openapi/pom.xml @@ -62,10 +62,12 @@ io.helidon.webserver.observe helidon-webserver-observe-metrics + runtime io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters + runtime io.helidon.openapi diff --git a/examples/quickstarts/helidon-quickstart-mp/pom.xml b/examples/quickstarts/helidon-quickstart-mp/pom.xml index 4d31166d077..f69a0c45a5f 100644 --- a/examples/quickstarts/helidon-quickstart-mp/pom.xml +++ b/examples/quickstarts/helidon-quickstart-mp/pom.xml @@ -36,6 +36,16 @@ io.helidon.microprofile.bundles helidon-microprofile + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + + + io.helidon.metrics + helidon-metrics-system-meters + runtime + io.smallrye jandex diff --git a/examples/quickstarts/helidon-quickstart-se/pom.xml b/examples/quickstarts/helidon-quickstart-se/pom.xml index aeb93222cca..6c8b46afc77 100644 --- a/examples/quickstarts/helidon-quickstart-se/pom.xml +++ b/examples/quickstarts/helidon-quickstart-se/pom.xml @@ -55,7 +55,7 @@ io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters io.helidon.openapi diff --git a/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml b/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml index 96db45b40a8..0659a5b4f38 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml +++ b/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml @@ -68,6 +68,10 @@ io.helidon.microprofile.bundles helidon-microprofile + + io.helidon.metrics + helidon-metrics + io.smallrye jandex diff --git a/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml b/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml index de72c6df5ee..8dc917c98a6 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml +++ b/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml @@ -78,10 +78,12 @@ io.helidon.webserver.observe helidon-webserver-observe-metrics + runtime io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters + runtime io.helidon.config diff --git a/examples/todo-app/frontend/pom.xml b/examples/todo-app/frontend/pom.xml index 650f1455320..eeabbc2856d 100644 --- a/examples/todo-app/frontend/pom.xml +++ b/examples/todo-app/frontend/pom.xml @@ -125,9 +125,14 @@ io.helidon.security.providers helidon-security-providers-http-sign + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters runtime diff --git a/examples/webclient/standalone/pom.xml b/examples/webclient/standalone/pom.xml index 0263cd37617..f4c937e8aa8 100644 --- a/examples/webclient/standalone/pom.xml +++ b/examples/webclient/standalone/pom.xml @@ -47,8 +47,8 @@ helidon-metrics-api - io.helidon.metrics - helidon-metrics + io.helidon.metrics.providers + helidon-metrics-providers-micrometer runtime diff --git a/examples/webserver/multiport/pom.xml b/examples/webserver/multiport/pom.xml index 4d6684c8425..582e86ba78a 100644 --- a/examples/webserver/multiport/pom.xml +++ b/examples/webserver/multiport/pom.xml @@ -51,9 +51,14 @@ io.helidon.health helidon-health-checks + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + runtime + io.helidon.metrics - helidon-metrics + helidon-metrics-system-meters runtime diff --git a/integrations/cdi/datasource-hikaricp/pom.xml b/integrations/cdi/datasource-hikaricp/pom.xml index 42ee6fe6879..5e4bb936bd0 100644 --- a/integrations/cdi/datasource-hikaricp/pom.xml +++ b/integrations/cdi/datasource-hikaricp/pom.xml @@ -109,6 +109,11 @@ helidon-microprofile-metrics test + + io.helidon.metrics + helidon-metrics + test + io.helidon.microprofile.server helidon-microprofile-server diff --git a/integrations/microstream/metrics/pom.xml b/integrations/microstream/metrics/pom.xml index 29a2295fdeb..6f10fd5b4b6 100644 --- a/integrations/microstream/metrics/pom.xml +++ b/integrations/microstream/metrics/pom.xml @@ -45,11 +45,6 @@ io.helidon.config helidon-config - - io.helidon.metrics - helidon-metrics - runtime - org.junit.jupiter junit-jupiter-api diff --git a/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java b/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java index e9495c11fba..af3abb26d26 100644 --- a/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java +++ b/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java @@ -67,7 +67,6 @@ import com.oracle.bmc.monitoring.responses.UpdateAlarmResponse; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -94,8 +93,6 @@ value = OciMetricsCdiExtensionTest.MetricDataDetailsOCIParams.resourceGroup) @AddConfig(key = "ocimetrics.initialDelay", value = "1") @AddConfig(key = "ocimetrics.delay", value = "2") -// TODO metrics -@Disabled class OciMetricsCdiExtensionTest { private static volatile int testMetricCount = 0; private static CountDownLatch countDownLatch = new CountDownLatch(1); @@ -128,12 +125,15 @@ void testDisableOciMetrics() throws InterruptedException { } private void validateOciMetricsSupport(boolean enabled) throws InterruptedException { - registry.getOrCreate(Counter.builder("baseDummyCounter") - .scope(Meter.Scope.BASE)).increment(); - registry.getOrCreate(Counter.builder("vendorDummyCounter") - .scope(Meter.Scope.VENDOR)).increment(); - registry.getOrCreate(Counter.builder("appDummyCounter") - .scope(Meter.Scope.APPLICATION)).increment(); + Counter c1 = registry.getOrCreate(Counter.builder("baseDummyCounter") + .scope(Meter.Scope.BASE)); + c1.increment(); + Counter c2 = registry.getOrCreate(Counter.builder("vendorDummyCounter") + .scope(Meter.Scope.VENDOR)); + c2.increment(); + Counter c3 = registry.getOrCreate(Counter.builder("appDummyCounter") + .scope(Meter.Scope.APPLICATION)); + c3.increment(); // Wait for signal from metric update that testMetricCount has been retrieved if (!countDownLatch.await(3, TimeUnit.SECONDS)) { @@ -145,7 +145,12 @@ private void validateOciMetricsSupport(boolean enabled) throws InterruptedExcept if (enabled) { assertThat(activateOciMetricsSupportIsInvoked, is(true)); - assertThat(testMetricCount, is(3)); + // System meters in the registry might vary over time. Instead of looking for a specific number of meters, + // make sure the three we added are in the OCI metric data. + long dummyCounterCount = postMetricDataDetails.getMetricData().stream() + .filter(details -> details.getName().contains("DummyCounter")) + .count(); + assertThat(dummyCounterCount, is(3L)); MetricDataDetails metricDataDetails = postMetricDataDetails.getMetricData().get(0); assertThat(metricDataDetails.getCompartmentId(), @@ -158,6 +163,9 @@ private void validateOciMetricsSupport(boolean enabled) throws InterruptedExcept // validate that OCI post metric is never called assertThat(postMetricDataDetails, is(equalTo(null))); } + registry.remove(c1); + registry.remove(c2); + registry.remove(c3); } interface MetricDataDetailsOCIParams { diff --git a/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsData.java b/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsData.java index 0a71333ad70..20b5b617d6f 100644 --- a/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsData.java +++ b/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsData.java @@ -26,6 +26,7 @@ import io.helidon.metrics.api.Counter; import io.helidon.metrics.api.DistributionSummary; +import io.helidon.metrics.api.FunctionalCounter; import io.helidon.metrics.api.Gauge; import io.helidon.metrics.api.HistogramSnapshot; import io.helidon.metrics.api.Meter; @@ -73,14 +74,16 @@ List getMetricDataDetails() { } Stream metricDataDetails(Meter metric) { - if (metric instanceof Counter) { - return forCounter(metric.id(), ((Counter) metric)); - } else if (metric instanceof Gauge) { - return forGauge(metric.id(), ((Gauge) metric)); - } else if (metric instanceof Timer) { - return forTimer(metric.id(), ((Timer) metric)); - } else if (metric instanceof DistributionSummary) { - return forHistogram(metric.id(), ((DistributionSummary) metric)); + if (metric instanceof Counter counter) { + return forCounter(metric.id(), counter); + } else if (metric instanceof FunctionalCounter fCounter) { + return forFunctionalCounter(metric.id(), fCounter); + } else if (metric instanceof Gauge gauge) { + return forGauge(metric.id(), gauge); + } else if (metric instanceof Timer timer) { + return forTimer(metric.id(), timer); + } else if (metric instanceof DistributionSummary summary) { + return forHistogram(metric.id(), summary); } else { return Stream.empty(); } @@ -90,6 +93,10 @@ private Stream forCounter(Meter.Id metricId, Counter counter) return Stream.of(metricDataDetails(counter, metricId, null, counter.count())); } + private Stream forFunctionalCounter(Meter.Id metricId, FunctionalCounter fCounter) { + return Stream.of(metricDataDetails(fCounter, metricId, null, fCounter.count())); + } + private Stream forGauge(Meter.Id metricId, Gauge gauge) { return Stream.of(metricDataDetails(gauge, metricId, null, gauge.value().doubleValue())); } diff --git a/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsSupport.java b/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsSupport.java index 485832d8d90..7a15da26931 100644 --- a/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsSupport.java +++ b/integrations/oci/metrics/metrics/src/main/java/io/helidon/integrations/oci/metrics/OciMetricsSupport.java @@ -33,6 +33,7 @@ import io.helidon.config.metadata.ConfiguredOption; import io.helidon.metrics.api.Counter; import io.helidon.metrics.api.DistributionSummary; +import io.helidon.metrics.api.FunctionalCounter; import io.helidon.metrics.api.Gauge; import io.helidon.metrics.api.Meter; import io.helidon.metrics.api.Timer; @@ -143,6 +144,9 @@ static String textType(Meter metric) { if (metric instanceof Counter) { return "counter"; } + if (metric instanceof FunctionalCounter) { + return "counter"; + } if (metric instanceof Gauge) { return "gauge"; } diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigSupport.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigSupport.java index 35d512ef0e6..841e686c298 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigSupport.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigSupport.java @@ -71,7 +71,10 @@ static boolean isScopeEnabled(MetricsConfig metricsConfig, String scope) { */ @Prototype.PrototypeMethod static boolean isMeterEnabled(MetricsConfig metricsConfig, String name, String targetScope) { - return true; + return metricsConfig.enabled() + && isScopeEnabled(metricsConfig, targetScope) + && (metricsConfig.scoping().scopes().get(targetScope) == null + || metricsConfig.scoping().scopes().get(targetScope).isMeterEnabled(name)); } public static class BuilderDecorator implements Prototype.BuilderDecorator> { diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsFactory.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsFactory.java index 26f71a4edc1..727a3a5da1d 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsFactory.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsFactory.java @@ -310,21 +310,23 @@ MeterRegistry createMeterRegistry(Clock clock, * @return corresponding no-op meter */ default Meter noOpMeter(Meter.Builder builder) { + NoOpMeter.Builder noOpBuilder; if (builder instanceof Counter.Builder cb) { - return NoOpMeter.Counter.builder(cb.name()).build(); + noOpBuilder = NoOpMeter.Counter.builder(cb.name()); + } else if (builder instanceof FunctionalCounter.Builder fcb) { + noOpBuilder = NoOpMeter.FunctionalCounter.builder(fcb.name(), fcb.stateObject(), fcb.fn()); + } else if (builder instanceof DistributionSummary.Builder sb) { + noOpBuilder = NoOpMeter.DistributionSummary.builder(sb.name()); + } else if (builder instanceof Gauge.Builder gb) { + noOpBuilder = NoOpMeter.Gauge.builder(gb.name(), gb.supplier()); + } else if (builder instanceof Timer.Builder tb) { + noOpBuilder = NoOpMeter.Timer.builder(tb.name()); + } else { + throw new IllegalArgumentException("Unrecognized meter builder type " + builder.getClass().getName()); } - if (builder instanceof FunctionalCounter.Builder fcb) { - return NoOpMeter.FunctionalCounter.builder(fcb.name(), fcb.stateObject(), fcb.fn()).build(); - } - if (builder instanceof DistributionSummary.Builder sb) { - return NoOpMeter.DistributionSummary.builder(sb.name()).build(); - } - if (builder instanceof Gauge.Builder gb) { - return NoOpMeter.Gauge.builder(gb.name(), gb.supplier()).build(); - } - if (builder instanceof Timer.Builder tb) { - return NoOpMeter.Timer.builder(tb.name()).build(); - } - throw new IllegalArgumentException("Unrecognized meter builder type " + builder.getClass().getName()); + SystemTagsManager.instance() + .effectiveScope(builder.scope()) + .ifPresent(noOpBuilder::scope); + return noOpBuilder.build(); } } diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java index 4947d442e8f..87b9cc6bb95 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/NoOpMeterRegistry.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import java.util.function.Predicate; @@ -32,6 +33,9 @@ */ class NoOpMeterRegistry implements MeterRegistry, NoOpWrapper { + private final List> onAddListeners = new CopyOnWriteArrayList<>(); + private final List> onRemoveListeners = new CopyOnWriteArrayList<>(); + static Builder builder() { return new Builder(); } @@ -67,6 +71,7 @@ public Optional remove(Meter.Id id) { @Override public Optional remove(Meter meter) { + onRemoveListeners.forEach(listener -> listener.accept(meter)); return Optional.empty(); } @@ -108,21 +113,38 @@ public Iterable scopes() { @Override public , M extends Meter> M getOrCreate(B builder) { NoOpMeter.Builder b = (NoOpMeter.Builder) builder; - return findOrRegister(NoOpMeter.Id.create(b.name(), - b.tags()), - builder); + M result = findOrRegister(NoOpMeter.Id.create(b.name(), + b.tags()), + builder); + onAddListeners.forEach(listener -> listener.accept(result)); + return result; } @Override public MeterRegistry onMeterAdded(Consumer listener) { + onAddListeners.add(listener); return this; } @Override public MeterRegistry onMeterRemoved(Consumer listener) { + onRemoveListeners.add(listener); return this; } + private Optional find(Meter.Id id, Class mClass) { + return Optional.empty(); + } + + private > M findOrRegister(Meter.Id id, B builder) { + NoOpMeter.Builder noOpBuilder = (NoOpMeter.Builder) builder; + // The following cast will always succeed if we create the meter by invoking the builder, + // it will succeed if we retrieved a previously-registered meter of a compatible type, + // and it will (correctly) fail if we found a previously-registered meter of an incompatible + // type compared to what the caller requested. + return (M) noOpBuilder.build(); + } + static class Builder implements MeterRegistry.Builder { @Override @@ -150,17 +172,4 @@ public Builder onMeterRemoved(Consumer removeListener) { return identity(); } } - - private Optional find(Meter.Id id, Class mClass) { - return Optional.empty(); - } - - private > M findOrRegister(Meter.Id id, B builder) { - NoOpMeter.Builder noOpBuilder = (NoOpMeter.Builder) builder; - // The following cast will always succeed if we create the meter by invoking the builder, - // it will succeed if we retrieved a previously-registered meter of a compatible type, - // and it will (correctly) fail if we found a previously-registered meter of an incompatible - // type compared to what the caller requested. - return (M) noOpBuilder.build(); - } } diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigBlueprint.java b/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigBlueprint.java index c88638ee2bb..16203571736 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigBlueprint.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigBlueprint.java @@ -16,6 +16,7 @@ package io.helidon.metrics.api; import java.util.Optional; +import java.util.regex.Pattern; import io.helidon.builder.api.Prototype; import io.helidon.config.metadata.Configured; @@ -51,7 +52,7 @@ interface ScopeConfigBlueprint { * @return include expression */ @ConfiguredOption(key = "filter.include") - Optional include(); + Optional include(); /** * Regular expression for meter names to exclude. @@ -59,7 +60,7 @@ interface ScopeConfigBlueprint { * @return exclude expression */ @ConfiguredOption(key = "filter.exclude") - Optional exclude(); + Optional exclude(); /** * Returns whether the specified meter name within the current scope is enabled according to the scope settings. diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigSupport.java b/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigSupport.java index c75993bb873..e4ff50eb7eb 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigSupport.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/ScopeConfigSupport.java @@ -15,6 +15,9 @@ */ package io.helidon.metrics.api; +import java.util.Objects; +import java.util.regex.Pattern; + import io.helidon.builder.api.Prototype; class ScopeConfigSupport { @@ -31,7 +34,42 @@ private ScopeConfigSupport() { */ @Prototype.PrototypeMethod static boolean isMeterEnabled(ScopeConfig scopeConfig, String name) { - // TODO actually do the filtering using the include and exclude patterns - return scopeConfig.enabled(); + /* + The following must be true for the meter to be enabled: + + 1. The scope itself must be enabled (that's the default). + 2. If there is an exclude pattern, the name must not match it. + 3. If there is an include pattern, the name must match it. + */ + return scopeConfig.enabled() + && scopeConfig.exclude().map(excludePattern -> !excludePattern.matcher(name).matches()).orElse(true) + && scopeConfig.include().map(includePattern -> includePattern.matcher(name).matches()).orElse(true); + + } + + /** + * Sets the include expression using a {@link java.lang.String} compiled automatically + * into a {@link java.util.regex.Pattern}. + * + * @param builderBase builder + * @param includeString include string + */ + @Prototype.BuilderMethod + static void include(ScopeConfig.BuilderBase builderBase, String includeString) { + Objects.requireNonNull(includeString, "include expression"); + builderBase.include(Pattern.compile(includeString)); + } + + /** + * Sets the exclude expression using a {@link java.lang.String} compiled automatically + * into a {@link java.util.regex.Pattern}. + * + * @param builderBase builder + * @param excludeString exclude string + */ + @Prototype.BuilderMethod + static void exclude(ScopeConfig.BuilderBase builderBase, String excludeString) { + Objects.requireNonNull(excludeString, "exclude expression"); + builderBase.exclude(Pattern.compile(excludeString)); } } 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 27e67631576..05acb46d480 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 @@ -19,7 +19,6 @@ import io.helidon.config.ConfigSources; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -45,8 +44,6 @@ void testInclude() { } @Test - // TODO enable once patterns are back - @Disabled void testExclude() { MetricsConfig metricsConfig = MetricsConfig.create(metricsConfigNode); assertThat("'ignore.me' metric is enabled", @@ -62,8 +59,6 @@ void testIncludeYaml() { is(true)); } - // TODO enable once patterns are back - @Disabled @Test void testExcludeYaml() { MetricsConfig metricsConfig = MetricsConfig.create(fromYaml); diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/TestScopeConfig.java b/metrics/api/src/test/java/io/helidon/metrics/api/TestScopeConfig.java index 9dc7699573b..c90119ee759 100644 --- a/metrics/api/src/test/java/io/helidon/metrics/api/TestScopeConfig.java +++ b/metrics/api/src/test/java/io/helidon/metrics/api/TestScopeConfig.java @@ -16,27 +16,26 @@ package io.helidon.metrics.api; import java.util.Map; +import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import io.helidon.config.Config; +import io.helidon.config.ConfigMappingException; import io.helidon.config.ConfigSources; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -// TODO enabled once patterns are back -@Disabled class TestScopeConfig { @Test void checkEnableAllPattern() { ScopeConfig mts = ScopeConfig.builder() .name("test") - .include(".*") + .include(Pattern.compile(".*")) .build(); assertThat("Specific metric enabled with global pattern", @@ -50,7 +49,7 @@ void checkEnableAllPattern() { void checkPrefixPattern() { ScopeConfig mts = ScopeConfig.builder() .name("test") - .include("mine\\..*") + .include(Pattern.compile("mine\\..*")) .build(); assertThat("Specific metric enabled with prefix 'mine.'", @@ -107,13 +106,11 @@ void testMultipleValidConfig() { } @Test - // TODO remove after patterns created - @Disabled void testInvalidConfig() { Map configMap = Map.of("filter.include", "mine\\..*|bad(one"); Config config = Config.just(ConfigSources.create(configMap)); - Assertions.assertThrows(PatternSyntaxException.class, () -> { + Assertions.assertThrows(ConfigMappingException.class, () -> { ScopeConfig.builder() .config(config) .name("test") @@ -121,18 +118,6 @@ void testInvalidConfig() { }); } - @Test - void testPassingEmptyListForPatterns() { - ScopeConfig mts = ScopeConfig.builder() - .include("") - .name("test") - .build(); - - assertThat("Specific metric 'should.work'", - mts.isMeterEnabled("should.work"), - is(true)); - } - @Test void testNullListForPatterns() { ScopeConfig mts = ScopeConfig.builder() @@ -147,7 +132,7 @@ void testNullListForPatterns() { @Test void testSingleNegative() { ScopeConfig mts = ScopeConfig.builder() - .exclude("mine\\..*") + .exclude(Pattern.compile("mine\\..*")) .name("test") .build(); @@ -163,8 +148,8 @@ void testSingleNegative() { @Test void testMultipleMixedPatterns() { ScopeConfig mts = ScopeConfig.builder() - .include("mine\\..*") - .exclude("mine\\.nogood\\..*|yours\\.nogood\\.*") + .include(Pattern.compile("mine\\..*")) + .exclude(Pattern.compile("mine\\.nogood\\..*|yours\\.nogood\\.*")) .name("test") .build(); @@ -190,7 +175,7 @@ void testWithUnescapedDots() { // Users are likely to use dots as literals rather than the regex wildcard; that usage should work so it kind-of does // what the user intended. ScopeConfig mts = ScopeConfig.builder() - .include("mine.*") + .include(Pattern.compile("mine.*")) .name("test") .build(); diff --git a/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeter.java b/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeter.java index c19cd408739..2146d1f9fd1 100644 --- a/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeter.java +++ b/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeter.java @@ -201,12 +201,18 @@ public HB addTag(Tag tag) { public HB description(String description) { this.description = description; - return delegateDescription(description); + if (description != null && !description.isBlank()) { + delegateDescription(description); + } + return identity(); } public HB baseUnit(String baseUnit) { this.baseUnit = baseUnit; - return delegateBaseUnit(baseUnit); + if (baseUnit != null && !baseUnit.isBlank()) { + delegateBaseUnit(baseUnit); + } + return identity(); } public HB scope(String scope) { diff --git a/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeterRegistry.java b/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeterRegistry.java index 454280fe456..af9501f6d50 100644 --- a/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeterRegistry.java +++ b/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MMeterRegistry.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; @@ -79,8 +80,8 @@ class MMeterRegistry implements io.helidon.metrics.api.MeterRegistry { private static final System.Logger LOGGER = System.getLogger(MMeterRegistry.class.getName()); private final io.micrometer.core.instrument.MeterRegistry delegate; - private final List> onAddListeners = new ArrayList<>(); - private final List> onRemoveListeners = new ArrayList<>(); + private final List> onAddListeners = new CopyOnWriteArrayList<>(); + private final List> onRemoveListeners = new CopyOnWriteArrayList<>(); /** * Helidon API clock to be returned by the {@link #clock()} method. @@ -244,10 +245,11 @@ public Iterable scopes() { return scopeMembership.keySet(); } - // TODO enhance after adding back the filtering config @Override public boolean isMeterEnabled(String name, Map tags, Optional scope) { - return metricsConfig.enabled(); + return metricsConfig.enabled() + && (scope.isEmpty() + || metricsConfig.isMeterEnabled(name, scope.get())); } @Override @@ -392,7 +394,14 @@ io.helidon.metrics.api.Meter getOrCreateUntyped(io.helidon.metrics.api.Meter.Bui try { if (!isMeterEnabled(builder.name(), builder.tags(), builder.scope())) { - return metricsFactory.noOpMeter(builder); + lock.lock(); + try { + io.helidon.metrics.api.Meter result = metricsFactory.noOpMeter(builder); + onAddListeners.forEach(listener -> listener.accept(result)); + return result; + } finally { + lock.unlock(); + } } io.helidon.metrics.api.Meter helidonMeter; @@ -482,12 +491,7 @@ Signal to getOrCreate that in fact a new delegate meter was created (because we } onAddListeners.forEach(listener -> { - try { - listener.accept(mMeter); - } catch (Exception ex) { - LOGGER.log(Level.ERROR, "Error invoking on-add callback listener " + listener, ex); - // Continue on with the next listener. - } + listener.accept(mMeter); }); } finally { @@ -566,6 +570,10 @@ private , HM extends MM M meter = registration.apply(delegate()); + /* + Normally, the on-add listener will have removed the pending builder in scope, but do so here again if the listener + did not run--if the meter already exists in the Micrometer meter registry, for example. + */ pendingBuildersInScope.remove(id); HM result = (HM) meters.get(meter); diff --git a/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MicrometerPrometheusFormatter.java b/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MicrometerPrometheusFormatter.java index 0bf5eaaef78..ebad78514a0 100644 --- a/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MicrometerPrometheusFormatter.java +++ b/metrics/providers/micrometer/src/main/java/io/helidon/metrics/providers/micrometer/MicrometerPrometheusFormatter.java @@ -190,7 +190,7 @@ Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterRegistry for (Meter meter : prometheusMeterRegistry.getMeters()) { String meterName = meter.getId().getName(); - if (!namePredicate.test(meterName)) { + if (!namePredicate.test(meterName) || !scopePredicate.test(meter)) { continue; } Set allUnitsForMeterName = new HashSet<>(); @@ -202,13 +202,11 @@ Set meterNamesOfInterest(PrometheusMeterRegistry prometheusMeterRegistry .meters() .forEach(m -> { Meter.Id meterId = m.getId(); - if (scopePredicate.test(m)) { - String normalizedUnit = normalizeUnit(meterId.getBaseUnit()); - if (!normalizedUnit.isBlank()) { - allUnitsForMeterName.add("_" + normalizedUnit); - } - allSuffixesForMeterName.addAll(meterNameSuffixes(meterId.getType())); + String normalizedUnit = normalizeUnit(meterId.getBaseUnit()); + if (!normalizedUnit.isBlank()) { + allUnitsForMeterName.add("_" + normalizedUnit); } + allSuffixesForMeterName.addAll(meterNameSuffixes(meterId.getType())); }); String normalizedMeterName = normalizeNameToPrometheus(meterName); diff --git a/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java index 5fcc7ed2e95..6fea18ac576 100644 --- a/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java +++ b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.function.Function; -import io.helidon.metrics.api.Gauge; import io.helidon.metrics.api.Meter; import io.helidon.metrics.api.MetricsFactory; import io.helidon.metrics.api.Tag; @@ -42,8 +41,6 @@ public class SystemMetersProvider implements MetersProvider { private static final String BYTES = "bytes"; private static final String SECONDS = "seconds"; - private static final String NONE = ""; - private static final String SCOPE = Meter.Scope.BASE; private static final Metadata MEMORY_USED_HEAP = Metadata.builder() .withName("memory.usedHeap") @@ -91,12 +88,10 @@ public class SystemMetersProvider implements MetersProvider { .withName("thread.count") .withDescription("Displays the current number of live threads including both " + "daemon and nondaemon threads") - .withUnit(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(NONE) .build(); private static final Metadata THREAD_MAX_COUNT = Metadata.builder() .withName("thread.max.count") @@ -104,25 +99,21 @@ public class SystemMetersProvider implements MetersProvider { + "virtual machine started or " + "peak was reset. This includes daemon and " + "non-daemon threads.") - .withUnit(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(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(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(NONE) .build(); private static final Metadata OS_AVAILABLE_CPU = Metadata.builder() .withName("cpu.availableProcessors") @@ -130,7 +121,6 @@ public class SystemMetersProvider implements MetersProvider { + "virtual machine. This " + "value may change during a particular invocation of" + " the virtual machine.") - .withUnit(NONE) .build(); private static final Metadata OS_LOAD_AVERAGE = Metadata.builder() .withName("cpu.systemLoadAverage") @@ -152,7 +142,6 @@ public class SystemMetersProvider implements MetersProvider { + " be unavailable on some " + "platforms where it is expensive to implement this " + "method.") - .withUnit(NONE) .build(); private static final Metadata GC_TIME = Metadata.builder() .withName("gc.time") @@ -170,7 +159,6 @@ public class SystemMetersProvider implements MetersProvider { .withDescription( "Displays the total number of collections that have occurred. This attribute lists " + "-1 if the collection count is undefined for this collector.") - .withUnit("") .build(); private MetricsFactory metricsFactory; @@ -213,49 +201,49 @@ private static Function typedFn(Function ge MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); // load all base metrics - registerFunctionalCounter(result, - MEMORY_USED_HEAP, - memoryBean, - typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getUsed)); - registerFunctionalCounter(result, - MEMORY_USED_HEAP, - memoryBean, - typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getCommitted)); - registerFunctionalCounter(result, - MEMORY_COMMITTED_HEAP, - memoryBean, - typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getMax)); + registerGauge(result, + MEMORY_USED_HEAP, + memoryBean, + typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getUsed)); + registerGauge(result, + MEMORY_COMMITTED_HEAP, + memoryBean, + typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getCommitted)); + registerGauge(result, + MEMORY_MAX_HEAP, + memoryBean, + typedFn(MemoryMXBean::getHeapMemoryUsage, MemoryUsage::getMax)); RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); - registerFunctionalCounter(result, JVM_UPTIME, runtimeBean, RuntimeMXBean::getUptime); + registerGauge(result, JVM_UPTIME, runtimeBean, RuntimeMXBean::getUptime); ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); - registerFunctionalCounter(result, THREAD_COUNT, threadBean, intToLong(ThreadMXBean::getThreadCount)); - registerFunctionalCounter(result, THREAD_DAEMON_COUNT, threadBean, intToLong(ThreadMXBean::getDaemonThreadCount)); - registerFunctionalCounter(result, THREAD_MAX_COUNT, threadBean, intToLong(ThreadMXBean::getPeakThreadCount)); + registerGauge(result, THREAD_COUNT, threadBean, ThreadMXBean::getThreadCount); + registerGauge(result, THREAD_DAEMON_COUNT, threadBean, ThreadMXBean::getDaemonThreadCount); + registerGauge(result, THREAD_MAX_COUNT, threadBean, ThreadMXBean::getPeakThreadCount); ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean(); - registerFunctionalCounter(result, CL_LOADED_COUNT, clBean, intToLong(ClassLoadingMXBean::getLoadedClassCount)); + registerGauge(result, CL_LOADED_COUNT, clBean, ClassLoadingMXBean::getLoadedClassCount); registerFunctionalCounter(result, CL_LOADED_TOTAL, clBean, ClassLoadingMXBean::getTotalLoadedClassCount); registerFunctionalCounter(result, CL_UNLOADED_COUNT, clBean, ClassLoadingMXBean::getUnloadedClassCount); OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - registerFunctionalCounter(result, OS_AVAILABLE_CPU, osBean, intToLong(OperatingSystemMXBean::getAvailableProcessors)); + registerGauge(result, OS_AVAILABLE_CPU, osBean, OperatingSystemMXBean::getAvailableProcessors); registerGauge(result, OS_LOAD_AVERAGE, osBean, OperatingSystemMXBean::getSystemLoadAverage); List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); for (GarbageCollectorMXBean gcBean : gcBeans) { String poolName = gcBean.getName(); - registerGauge(result, + registerFunctionalCounter(result, GC_COUNT, gcBean, GarbageCollectorMXBean::getCollectionCount, Tag.create("name", poolName)); // Express the GC time in seconds. - registerGauge(result, + registerFunctionalCounter(result, GC_TIME, gcBean, - bean -> bean.getCollectionTime() / 1000.0D, + bean -> (long) (bean.getCollectionTime() / 1000.0D), Tag.create("name", poolName)); } return result; @@ -266,7 +254,7 @@ private void registerGauge(Collection> T object, Function fn, Tag... tags) { - result.add(Gauge.builder(metadata.name, object, obj -> fn.apply(obj).doubleValue()) + result.add(metricsFactory.gaugeBuilder(metadata.name, object, obj -> fn.apply(obj).doubleValue()) .scope(SCOPE) .description(metadata.description) .baseUnit(metadata.baseUnit) @@ -285,10 +273,6 @@ private void registerFunctionalCounter(Collection> resul .tags(Arrays.asList(tags))); } - private static Function intToLong(Function fn) { - return t -> (long) fn.apply(t); - } - private static class Metadata { private final String name; diff --git a/microprofile/fault-tolerance/pom.xml b/microprofile/fault-tolerance/pom.xml index 148a6aaf93d..c6c8bf21be5 100644 --- a/microprofile/fault-tolerance/pom.xml +++ b/microprofile/fault-tolerance/pom.xml @@ -98,6 +98,11 @@ helidon-microprofile-metrics provided + + io.helidon.metrics + helidon-metrics + test + org.hamcrest hamcrest-all diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java index a2c0cf18f40..73f619170e2 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricAnnotationInfo.java @@ -31,6 +31,7 @@ import org.eclipse.microprofile.metrics.MetadataBuilder; import org.eclipse.microprofile.metrics.Metric; import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.MetricUnits; import org.eclipse.microprofile.metrics.Tag; import org.eclipse.microprofile.metrics.Timer; import org.eclipse.microprofile.metrics.annotation.Counted; @@ -154,13 +155,11 @@ private void validateMetadata(MetricRegistry registry) { // as the MP Metrics spec requires. if (!Timed.class.isAssignableFrom(annotationType)) { - if (metadata.getUnit() != null - && !metadata.getUnit().equals(existingMetadata.getUnit())) { + if (!isConsistentWith(metadata.getUnit(), existingMetadata.getUnit())) { mismatches.add("unit"); } } - if (metadata.getDescription() != null - && !metadata.getDescription().equals(existingMetadata.getDescription())) { + if (!isConsistentWith(metadata.getDescription(), existingMetadata.getDescription())) { mismatches.add("description"); } if (!mismatches.isEmpty()) { @@ -175,6 +174,13 @@ private void validateMetadata(MetricRegistry registry) { mismatches)); } } + + private static boolean isConsistentWith(String s1, String s2) { + String normalizedS1 = s1 == null || s1.isBlank() || s1.equals(MetricUnits.NONE) ? "" : s1; + String normalizedS2 = s2 == null || s2.isBlank() || s2.endsWith(MetricUnits.NONE) ? "" : s2; + return normalizedS2.equals(normalizedS1); + + } } static final Map, Class> ANNOTATION_TYPE_TO_METRIC_TYPE = 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 f28c33a6589..e75c719694d 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 @@ -43,6 +43,7 @@ import io.helidon.config.ConfigSources; import io.helidon.config.ConfigValue; import io.helidon.config.mp.MpConfig; +import io.helidon.metrics.api.MeterRegistry; import io.helidon.metrics.api.MetricsConfig; import io.helidon.metrics.api.MetricsFactory; import io.helidon.microprofile.metrics.MetricAnnotationInfo.RegistrationPrep; @@ -208,9 +209,11 @@ private static MetricsFeature createMetricsService(Config metricsConfigNode) { Contexts.globalContext().register(metricsFactory); MetricsConfig.Builder metricsConfigBuilder = MetricsConfig.builder().config(metricsConfigNode); MetricsConfig metricsConfig = metricsConfigBuilder.build(); + MeterRegistry meterRegistry = metricsFactory.globalRegistry(metricsConfig); + RegistryFactory.getInstance(meterRegistry); // initialize before first use MetricsFeature.Builder builder = MetricsFeature.builder() .metricsConfig(metricsConfigBuilder) - .meterRegistry(metricsFactory.globalRegistry(metricsConfig)) + .meterRegistry(meterRegistry) .metricsConfig(MetricsConfig.builder(metricsConfig)) .webContext("/metrics") .config(metricsConfigNode); diff --git a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/Registry.java b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/Registry.java index 080d576fe8b..2c0cf62248b 100644 --- a/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/Registry.java +++ b/microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/Registry.java @@ -17,6 +17,7 @@ import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -93,7 +94,14 @@ protected static String sanitizeUnit(String unit) { * @param tags explicitly-defined tags from the application code * @return iterable ot Helidon tags */ - protected static Iterable allTags(String scope, Tag[] tags) { + protected static Iterable validatedAllTags(String scope, Tag[] tags) { + if (tags != null && tags.length > 0) { + List tagNames = Arrays.stream(tags).map(Tag::getTagName).toList(); + Collection reservedTagNamesUsed = SystemTagsManager.instance().reservedTagNamesUsed(tagNames); + if (!reservedTagNamesUsed.isEmpty()) { + throw new IllegalArgumentException("Illegal use of reserved tag name(s): " + reservedTagNamesUsed); + } + } return toHelidonTags(SystemTagsManager.instance().withScopeTag(iterableEntries(tags), scope)); } @@ -144,7 +152,7 @@ public Gauge gauge(MetricID metricID, T object, Functio @Override public Gauge gauge(Metadata metadata, T object, Function func, Tag... tags) { return Objects.requireNonNullElseGet(getGauge(metricID(metadata, tags)), - () -> createGauge(metadata, object, func)); + () -> createGauge(metadata, object, func, tags)); } @Override @@ -402,8 +410,11 @@ HelidonMetric onMeterAdded(Meter meter) { if (existingMetric != null) { // This is a bit odd. We've been notified that a new meter was created by the neutral metrics implementation, - // but we seem to already have the corresponding metric in place. Validate the new meter against the - // existing metric anyway but report a warning about the duplicate registration attempt. + // but we seem to already have the corresponding metric in place. (This *can* happen if the metric or all metrics + // are disabled.) + // + // Validate the new meter against the existing metric anyway but report a warning about the duplicate + // registration attempt. collector.warn(String.format("unexpected attempted re-registration of metric %s by meter %s", newMetricID, @@ -518,6 +529,9 @@ private static Iterable toHelidonTags(Iterable> iterableEntries(Tag... tags) { + if (tags == null) { + return Set.of(); + } List> result = new ArrayList<>(); for (Tag tag : tags) { result.add(new AbstractMap.SimpleEntry<>(tag.getTagName(), tag.getTagValue())); @@ -542,6 +556,15 @@ private static void validateMetric(Errors.Collector collector, HelidonMetric existingMetric, Meter meter) { + /* + Watch out for a corner case. If metrics are disabled then we get notification of a new meter when we might not expect + it. Functional counters in the Helidon metrics API are converted to gauges in the Helidon implementation of the MP + metrics API, so a simple type comparison will fail in that case. + */ + if (meter instanceof FunctionalCounter + && io.helidon.metrics.api.Gauge.class.isAssignableFrom(existingMetric.delegateType())) { + return; + } if (!existingMetric.delegateType().isInstance(meter)) { collector.fatal(String.format("existing metric %s is compatible with type %s but new meter is %s", metricID, @@ -578,7 +601,7 @@ private HelidonCounter createCounter(Metadata metadata, Tag... tags) { .scope(scope) .description(metadata.getDescription()) .baseUnit(sanitizeUnit(metadata.getUnit())) - .tags(allTags(scope, tags))); + .tags(validatedAllTags(scope, tags))); } private HelidonCounter createCounter(io.helidon.metrics.api.Counter.Builder counterBuilder) { @@ -594,7 +617,7 @@ private HelidonGauge createGauge(Metadata metadata, T o .apply(object)) .scope(scope) .description(metadata.getDescription()) - .tags(allTags(scope, tags)) + .tags(validatedAllTags(scope, tags)) .baseUnit(sanitizeUnit(metadata.getUnit()))); } @@ -604,7 +627,7 @@ private HelidonGauge createGauge(Metadata metadata, Suppli supplier) .scope(scope) .description(metadata.getDescription()) - .tags(allTags(scope, tags)) + .tags(validatedAllTags(scope, tags)) .baseUnit(sanitizeUnit(metadata.getUnit()))); } @@ -629,7 +652,7 @@ private HelidonHistogram createHistogram(Metadata metadata, Tag... tags) { .scope(scope) .description(metadata.getDescription()) .baseUnit(sanitizeUnit(metadata.getUnit())) - .tags(allTags(scope, tags))); + .tags(validatedAllTags(scope, tags))); } @@ -643,7 +666,7 @@ private HelidonTimer createTimer(Metadata metadata, Tag... tags) { .scope(scope) .description(metadata.getDescription()) .baseUnit(sanitizeUnit(metadata.getUnit())) - .tags(allTags(scope, tags))); + .tags(validatedAllTags(scope, tags))); } private HelidonTimer createTimer(io.helidon.metrics.api.Timer.Builder tBuilder) { 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 16f9804f887..11896175e57 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 @@ -47,8 +47,6 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -// TODO metrics -@Disabled @HelidonTest @AddConfig(key = "metrics." + MetricsCdiExtension.REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME, value = "true") @AddConfig(key = diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseWithRestRequestTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseWithRestRequestTest.java index 754c8c03697..30046fd7005 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseWithRestRequestTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/HelloWorldAsyncResponseWithRestRequestTest.java @@ -39,7 +39,6 @@ import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.Timer; import org.eclipse.microprofile.metrics.annotation.RegistryType; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static io.helidon.common.testing.junit5.MatcherWithRetry.assertThatWithRetry; @@ -50,8 +49,6 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -// TODO metrics -@Disabled @HelidonTest @AddConfig(key = "metrics." + MetricsCdiExtension.REST_ENDPOINTS_METRIC_ENABLED_PROPERTY_NAME, value = "true") class HelloWorldAsyncResponseWithRestRequestTest { diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java index 971c925e6ea..a2cd5443b37 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsTest.java @@ -154,8 +154,8 @@ public void testGaugeMetadata() { // The @Gauge overrides the default units. Plus, the Prometheus output from Micrometer now includes the mp_scope tag and // the value formatted as a double (that's Prometheus exposition format standard). assertThat(promData, containsString("# TYPE gaugeForInjectionTest_minutes gauge")); - assertThat(promData, containsString("\n# HELP gaugeForInjectionTest_minutes")); - assertThat(promData, containsString("\ngaugeForInjectionTest_minutes{mp_scope=\"application\",} " + assertThat(promData, containsString("# HELP gaugeForInjectionTest_minutes")); + assertThat(promData, containsString("gaugeForInjectionTest_minutes{mp_scope=\"application\",} " + (double) expectedValue)); } 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 index 90841634435..2fddf8a723b 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MpFeatureTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MpFeatureTest.java @@ -25,7 +25,6 @@ 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; @@ -37,7 +36,6 @@ public class MpFeatureTest { @Inject private WebTarget webTarget; - @Disabled @Test void testEndpoint() { MetricRegistry metricRegistry = RegistryFactory.getInstance().getRegistry(MetricRegistry.APPLICATION_SCOPE); @@ -49,12 +47,17 @@ void testEndpoint() { .accept(MediaType.TEXT_PLAIN) .get(String.class); - Pattern pattern = Pattern.compile(".*^endpointCounter_total\\{.*?mp_scope=\"application\".*?}\\s*(\\S*).*?"); + Pattern pattern = Pattern.compile(".*^endpointCounter_total\\{.*?mp_scope=\"application\".*?}\\s*(\\S*).*?", + Pattern.DOTALL + Pattern.MULTILINE); 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)); + + /* + Prometheus expresses even counters as decimal values (e.g., 4.0). + */ + assertThat("Captured counter value", Double.parseDouble(matcher.group(1)), is(4.0D)); } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestBasicPerformanceIndicators.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestBasicPerformanceIndicators.java index edf9210499f..498a685cd95 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestBasicPerformanceIndicators.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestBasicPerformanceIndicators.java @@ -24,7 +24,6 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,8 +38,6 @@ class TestBasicPerformanceIndicators { @Inject WebTarget webTarget; - // TODO metrics - @Disabled @Test void checkMetricsVendorURL() { doCheckMetricsVendorURL(webTarget); diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDisabledMetrics.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDisabledMetrics.java index f1607edf028..f2df23834a6 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDisabledMetrics.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestDisabledMetrics.java @@ -15,19 +15,22 @@ */ package io.helidon.microprofile.metrics; +import java.time.Duration; + +import io.helidon.inject.api.Helidon; import io.helidon.microprofile.tests.junit5.AddBean; import io.helidon.microprofile.tests.junit5.AddConfig; import io.helidon.microprofile.tests.junit5.HelidonTest; -import org.junit.jupiter.api.Disabled; +import io.micrometer.core.instrument.noop.NoopMeter; +import org.eclipse.microprofile.metrics.Metric; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -// TODO metrics -@Disabled @HelidonTest @AddConfig(key = "metrics.enabled", value = "false") @AddBean(GaugedBean.class) @@ -37,6 +40,20 @@ class TestDisabledMetrics { void ensureRegistryFactoryIsMinimal() { // Invoking instance() should retrieve the factory previously initialized as disabled. RegistryFactory rf = RegistryFactory.getInstance(); - assertThat("RegistryFactory type", rf, not(instanceOf(RegistryFactory.class))); + /* + Probe each metric to make sure its delegate is a no-op. + */ + for (Metric m : rf.getRegistry(Registry.APPLICATION_SCOPE).getMetrics().values()) { + if (m instanceof HelidonCounter c) { + c.inc(); + assertThat("Expected counter", c.getCount(), is(0L)); + } else if (m instanceof HelidonHistogram h) { + h.update(23L); + assertThat("Histogram count", h.getCount(), is(0L)); + } else if (m instanceof HelidonTimer t) { + t.update(Duration.ofMillis(123L)); + assertThat("Timer count", t.getCount(), is(0L)); + } + } } } diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricsOnOwnSocket.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricsOnOwnSocket.java index 5ea345112a3..edbb9546cf9 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricsOnOwnSocket.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/TestMetricsOnOwnSocket.java @@ -28,7 +28,6 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; @@ -39,15 +38,13 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -// TODO metrics -@Disabled @HelidonTest() // Set up the metrics endpoint on its own socket @AddConfig(key = "server.sockets.0.name", value = "metrics") // No port setting, so use any available one @AddConfig(key = "server.sockets.0.bind-address", value = "0.0.0.0") @AddConfig(key = "metrics.routing", value = "metrics") -@AddConfig(key = "metrics.key-performance-indicators.isExtended", value = "true") +@AddConfig(key = "metrics.key-performance-indicators.extended", value = "true") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class TestMetricsOnOwnSocket { diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml index 75dc399f79b..1bc2be3eefc 100644 --- a/microprofile/tests/tck/pom.xml +++ b/microprofile/tests/tck/pom.xml @@ -33,8 +33,7 @@ tck-config tck-health - - + tck-metrics tck-messaging tck-graphql tck-jwt-auth diff --git a/microprofile/tests/tck/tck-fault-tolerance/pom.xml b/microprofile/tests/tck/tck-fault-tolerance/pom.xml index fa4504f42b1..784a61f0d0a 100644 --- a/microprofile/tests/tck/tck-fault-tolerance/pom.xml +++ b/microprofile/tests/tck/tck-fault-tolerance/pom.xml @@ -68,6 +68,11 @@ microprofile-fault-tolerance-tck test + + io.helidon.metrics.providers + helidon-metrics-providers-micrometer + test + jakarta.enterprise jakarta.enterprise.cdi-api diff --git a/microprofile/tests/tck/tck-messaging/pom.xml b/microprofile/tests/tck/tck-messaging/pom.xml index 0e050eb54de..633dc732dbd 100644 --- a/microprofile/tests/tck/tck-messaging/pom.xml +++ b/microprofile/tests/tck/tck-messaging/pom.xml @@ -42,6 +42,11 @@ ${project.version} test + + io.helidon.metrics.providers + helidon-metrics-providers-micrometer + test + io.helidon.microprofile.tests helidon-arquillian diff --git a/microprofile/tests/tck/tck-metrics/pom.xml b/microprofile/tests/tck/tck-metrics/pom.xml index c01c3729639..b5374163b1b 100644 --- a/microprofile/tests/tck/tck-metrics/pom.xml +++ b/microprofile/tests/tck/tck-metrics/pom.xml @@ -35,6 +35,10 @@ helidon-microprofile-metrics test + + io.helidon.metrics + helidon-metrics + io.helidon.microprofile.tests helidon-arquillian diff --git a/tests/apps/bookstore/bookstore-mp/pom.xml b/tests/apps/bookstore/bookstore-mp/pom.xml index 83ac4ea28d7..68a163004e2 100644 --- a/tests/apps/bookstore/bookstore-mp/pom.xml +++ b/tests/apps/bookstore/bookstore-mp/pom.xml @@ -59,6 +59,10 @@ helidon-tests-apps-bookstore-common ${project.version} + + io.helidon.metrics + helidon-metrics + io.smallrye jandex diff --git a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java index 34fb2384203..c683080f0c2 100644 --- a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java +++ b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java @@ -364,9 +364,8 @@ private void runMetricsAndHealthTest(String edition, String jsonLibrary, boolean .path("/metrics") .header(Http.HeaderNames.ACCEPT, MediaTypes.APPLICATION_JSON.text()) .requestEntity(JsonObject.class); - String scopeTagName = edition.equals("se") ? "scope" : "mp_scope"; assertThat("Checking request count", - jsonObject.getJsonObject("vendor").getInt("requests.count;" + scopeTagName + "=vendor"), greaterThan(0)); + jsonObject.getJsonObject("vendor").getInt("requests.count"), greaterThan(0)); } jsonObject = webClient.get() diff --git a/tests/functional/context-propagation/pom.xml b/tests/functional/context-propagation/pom.xml index 4e1336c0060..9861f450cdf 100644 --- a/tests/functional/context-propagation/pom.xml +++ b/tests/functional/context-propagation/pom.xml @@ -50,6 +50,11 @@ helidon-microprofile-tests-junit5 test + + io.helidon.metrics + helidon-metrics + test + org.junit.jupiter junit-jupiter-api diff --git a/tests/functional/multiport/pom.xml b/tests/functional/multiport/pom.xml index 1542696eb3a..64d4ba086e8 100644 --- a/tests/functional/multiport/pom.xml +++ b/tests/functional/multiport/pom.xml @@ -44,6 +44,16 @@ io.helidon.microprofile.health helidon-microprofile-health + + io.helidon.webserver.observe + helidon-webserver-observe-metrics + test + + + io.helidon.metrics + helidon-metrics-system-meters + test + org.junit.jupiter junit-jupiter-api diff --git a/tests/functional/multiport/src/main/resources/application.yaml b/tests/functional/multiport/src/main/resources/application.yaml index 75a8f05a546..7f635648dbf 100644 --- a/tests/functional/multiport/src/main/resources/application.yaml +++ b/tests/functional/multiport/src/main/resources/application.yaml @@ -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. @@ -37,8 +37,8 @@ metrics: # default is added automatically vendor-metrics-routings: ["metrics", "health"] # possibility to disable base metrics - base: - thread: - daemon.count.enabled: false - max.count.enabled: false - count.enabled: false + scoping: + scopes: + - name: base + filter: + exclude: thread\.daemon\.count|thread\.max\.count|thread\.count diff --git a/tests/functional/multiport/src/test/java/io/helidon/tests/functional/multiport/MainTest.java b/tests/functional/multiport/src/test/java/io/helidon/tests/functional/multiport/MainTest.java index 3a8d7e8a56e..a4cf31c9b69 100644 --- a/tests/functional/multiport/src/test/java/io/helidon/tests/functional/multiport/MainTest.java +++ b/tests/functional/multiport/src/test/java/io/helidon/tests/functional/multiport/MainTest.java @@ -108,17 +108,16 @@ void testEndpoint(Params params) { .get(); if (params.metricsEnabled) { - // TODO metrics -// assertThat("port " + port + " (" + params.socketName + ") should be serving metrics", -// response.getStatusInfo().toEnum(), is(Response.Status.OK)); -// -// response = baseTarget.path(METRICS_URI) -// .path(DISABLED_METRIC) -// .request(MediaType.APPLICATION_JSON_TYPE) -// .get(); -// -// assertThat("Metric " + DISABLED_METRIC + " should be disabled by configuration", -// response.getStatusInfo().toEnum(), is(Response.Status.NOT_FOUND)); + assertThat("port " + port + " (" + params.socketName + ") should be serving metrics", + response.getStatusInfo().toEnum(), is(Response.Status.OK)); + + response = baseTarget.path(METRICS_URI) + .path(DISABLED_METRIC) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(); + + assertThat("Metric " + DISABLED_METRIC + " should be disabled by configuration", + response.getStatusInfo().toEnum(), is(Response.Status.NOT_FOUND)); } else { assertThat("port " + port + " (" + params.socketName + ") should NOT be serving metrics", response.getStatusInfo().toEnum(), is(Response.Status.NOT_FOUND)); diff --git a/tests/integration/dbclient/app/src/test/java/io/helidon/tests/integration/dbclient/app/tests/ServerMetricsCheckIT.java b/tests/integration/dbclient/app/src/test/java/io/helidon/tests/integration/dbclient/app/tests/ServerMetricsCheckIT.java index 9caeecaa8a5..3b1c05d4c16 100644 --- a/tests/integration/dbclient/app/src/test/java/io/helidon/tests/integration/dbclient/app/tests/ServerMetricsCheckIT.java +++ b/tests/integration/dbclient/app/src/test/java/io/helidon/tests/integration/dbclient/app/tests/ServerMetricsCheckIT.java @@ -22,7 +22,6 @@ import io.helidon.tests.integration.harness.TestServiceClient; import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -49,8 +48,6 @@ class ServerMetricsCheckIT { * Read and check Database Client metrics from Helidon Web Server. * */ - // TODO metrics - @Disabled @Test void testHttpMetrics() { LOGGER.log(System.Logger.Level.DEBUG, () -> String.format("Running %s.%s on client", getClass().getSimpleName(), "testHttpMetrics")); diff --git a/tests/integration/health/mp-disabled/pom.xml b/tests/integration/health/mp-disabled/pom.xml index 32957c1cbeb..2fd3daf5764 100644 --- a/tests/integration/health/mp-disabled/pom.xml +++ b/tests/integration/health/mp-disabled/pom.xml @@ -34,6 +34,10 @@ io.helidon.microprofile.bundles helidon-microprofile + + io.helidon.metrics + helidon-metrics + org.junit.jupiter junit-jupiter-api diff --git a/tests/integration/mp-security-client/pom.xml b/tests/integration/mp-security-client/pom.xml index 03467a3e4c1..df459f91ca3 100644 --- a/tests/integration/mp-security-client/pom.xml +++ b/tests/integration/mp-security-client/pom.xml @@ -38,6 +38,11 @@ io.helidon.microprofile helidon-microprofile-security + + io.helidon.metrics + helidon-metrics + test + org.junit.jupiter junit-jupiter-api diff --git a/tests/integration/native-image/mp-1/pom.xml b/tests/integration/native-image/mp-1/pom.xml index e10dc91d914..8533c411cc3 100644 --- a/tests/integration/native-image/mp-1/pom.xml +++ b/tests/integration/native-image/mp-1/pom.xml @@ -66,6 +66,11 @@ helidon-microprofile-access-log runtime + + io.helidon.metrics + helidon-metrics + runtime + io.smallrye jandex diff --git a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java index 4215afd4a0e..bbed65685ac 100644 --- a/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java +++ b/tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java @@ -230,8 +230,7 @@ private static void testBean(int port, String jwtToken) { validateBasicAuthProtectedResource(collector, target); // Metrics - // TODO metrics - //validateMetrics(collector, target); + validateMetrics(collector, target); // Access Log validateAccessLog(collector); @@ -547,7 +546,7 @@ private static void validateMetrics(Errors.Collector collector, WebTarget target .request(MediaType.APPLICATION_JSON) .get(JsonObject.class); - int count = vendor.getInt("requests.count;mp_scope=vendor"); + int count = vendor.getInt("requests.count"); if (count == 0) { collector.fatal("Vendor metric \"requests.count\" must not be zero"); } diff --git a/tests/integration/security/gh2297/pom.xml b/tests/integration/security/gh2297/pom.xml index 587f6b1d773..69c3e2ebc0b 100644 --- a/tests/integration/security/gh2297/pom.xml +++ b/tests/integration/security/gh2297/pom.xml @@ -37,6 +37,11 @@ io.helidon.microprofile.bundles helidon-microprofile + + io.helidon.metrics.providers + helidon-metrics-providers-micrometer + test + org.junit.jupiter junit-jupiter-api diff --git a/tests/integration/security/gh2297/src/test/java/io/helidon/tests/integration/security/gh2297/ProtectedMetricsTest.java b/tests/integration/security/gh2297/src/test/java/io/helidon/tests/integration/security/gh2297/ProtectedMetricsTest.java index 6e72c6d4bbd..f21f7a1d4f0 100644 --- a/tests/integration/security/gh2297/src/test/java/io/helidon/tests/integration/security/gh2297/ProtectedMetricsTest.java +++ b/tests/integration/security/gh2297/src/test/java/io/helidon/tests/integration/security/gh2297/ProtectedMetricsTest.java @@ -27,7 +27,6 @@ import jakarta.ws.rs.core.Response; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; @@ -36,8 +35,6 @@ /** * Unit test for gh2297. */ -// TODO metrics -@Disabled class ProtectedMetricsTest { private static Server server; private static Client client; @@ -84,7 +81,6 @@ void testMetricsEndpointNoUser() { assertThat(response.getStatus(), is(Http.Status.UNAUTHORIZED_401.code())); } - // TODO metrics @Test void testMetricEndpointSuccess() { Response response = metricTarget.request() diff --git a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientGaugeInProgress.java b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientGaugeInProgress.java index 0f0d0c8a4ec..b3b17cd6817 100644 --- a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientGaugeInProgress.java +++ b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientGaugeInProgress.java @@ -35,7 +35,7 @@ class WebClientGaugeInProgress extends WebClientMetric { @Override public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) { Metadata metadata = createMetadata(request, null); - meterRegistry().getOrCreate(Gauge.builder(metadata.name(), holder, AtomicLong::get) + meterRegistry().getOrCreate(Gauge.builder(metadata.name(), holder::get) .description(metadata.description())); boolean update = handlesMethod(request.method()); try { diff --git a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MetricsTest.java b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MetricsTest.java index 29c8331d739..306b32162ad 100644 --- a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MetricsTest.java +++ b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MetricsTest.java @@ -26,7 +26,6 @@ import io.helidon.webclient.spi.WebClientService; import io.helidon.webserver.WebServer; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -125,8 +124,6 @@ public void testMeter() { assertThat(meterSuccess.count(), is(1L)); } - // TODO metrics - @Disabled @Test public void testGaugeInProgress() { WebClientService inProgressAll = WebClientMetrics.gaugeInProgress().nameFormat("gauge.%1$s.%2$s").build(); diff --git a/webserver/observe/metrics/pom.xml b/webserver/observe/metrics/pom.xml index 0e5362a43b0..0f3c5f47efa 100644 --- a/webserver/observe/metrics/pom.xml +++ b/webserver/observe/metrics/pom.xml @@ -37,6 +37,10 @@ io.helidon.metrics helidon-metrics-api + + io.helidon.metrics.providers + helidon-metrics-providers-micrometer + io.helidon.webserver helidon-webserver @@ -58,8 +62,9 @@ helidon-config-metadata - io.helidon.metrics.providers - helidon-metrics-providers-micrometer + io.helidon.metrics + helidon-metrics + test io.helidon.common.testing diff --git a/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/JsonFormatter.java b/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/JsonFormatter.java index 8d602e48233..77ed2a8397d 100644 --- a/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/JsonFormatter.java +++ b/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/JsonFormatter.java @@ -47,7 +47,7 @@ import io.helidon.metrics.api.MeterRegistry; import io.helidon.metrics.api.MeterRegistryFormatter; import io.helidon.metrics.api.MetricsConfig; -import io.helidon.metrics.api.Tag; +import io.helidon.metrics.api.SystemTagsManager; import io.helidon.metrics.api.Timer; import jakarta.json.Json; @@ -213,8 +213,9 @@ && matchesName(name)) { List> tagGroups = new ArrayList<>(); - - List tags = StreamSupport.stream(sanitizeTags(meter.id().tags()).spliterator(), false) + List tags = StreamSupport.stream(SystemTagsManager.instance() + .withoutSystemOrScopeTags(meter.id().tags()) + .spliterator(), false) .map(tag -> jsonEscape(tag.key()) + "=" + jsonEscape(tag.value())) .toList(); if (!tags.isEmpty()) { @@ -245,20 +246,6 @@ && matchesName(name)) { return isAnyOutput.get() ? Optional.of(top.build()) : Optional.empty(); } - private Iterable sanitizeTags(Iterable tags) { - if (metricsConfig.scoping().tagName().isEmpty()) { - return tags; - } - String scopeTagName = metricsConfig.scoping().tagName().get(); - List descopedTags = new ArrayList<>(); - tags.forEach(tag -> { - if (!tag.key().equals(scopeTagName)) { - descopedTags.add(tag); - } - }); - return descopedTags; - } - /** * Creates a map from escape characters to quoted escape-characters so escape characters in tag names and values can * be escaped before sending to JSON for processing. @@ -297,7 +284,9 @@ private static String metricOutputKey(Meter meter) { private static String flatNameAndTags(Meter.Id meterId) { StringJoiner sj = new StringJoiner(";"); sj.add(meterId.name()); - meterId.tags().forEach(tag -> sj.add(tag.key() + "=" + tag.value())); + SystemTagsManager.instance() + .withoutSystemOrScopeTags(meterId.tags()) + .forEach(tag -> sj.add(tag.key() + "=" + tag.value())); return sj.toString(); } @@ -364,6 +353,7 @@ protected Meter meter() { private static MetricOutputBuilder create(Meter meter) { return meter instanceof Counter || meter instanceof io.helidon.metrics.api.Gauge + || meter instanceof FunctionalCounter ? new Flat(meter) : new Structured(meter); } @@ -418,6 +408,10 @@ protected void apply(JsonObjectBuilder builder) { addNarrowed(builder, nameWithTags, gauge.value()); return; } + if (meter() instanceof FunctionalCounter fCounter) { + builder.add(flatNameAndTags(meter().id()), fCounter.count()); + return; + } throw new IllegalArgumentException("Attempt to format meter with structured data as flat JSON " + meter().getClass().getName()); } @@ -453,21 +447,21 @@ protected void apply(JsonObjectBuilder builder) { children.forEach(child -> { Meter.Id childID = child.id(); - if (meter() instanceof Counter typedChild) { + if (child instanceof Counter typedChild) { sameNameBuilder.add(valueId("count", childID), typedChild.count()); - } else if (meter() instanceof DistributionSummary typedChild) { + } else if (child instanceof DistributionSummary typedChild) { sameNameBuilder.add(valueId("count", childID), typedChild.count()); sameNameBuilder.add(valueId("max", childID), typedChild.snapshot().max()); sameNameBuilder.add(valueId("mean", childID), typedChild.snapshot().mean()); sameNameBuilder.add(valueId("total", childID), typedChild.totalAmount()); - } else if (meter() instanceof Timer typedChild) { + } else if (child instanceof Timer typedChild) { sameNameBuilder.add(valueId("count", childID), typedChild.count()); sameNameBuilder.add(valueId("elapsedTime", childID), typedChild.totalTime(TimeUnit.SECONDS)); sameNameBuilder.add(valueId("max", childID), typedChild.max(TimeUnit.SECONDS)); sameNameBuilder.add(valueId("mean", childID), typedChild.mean(TimeUnit.SECONDS)); - } else if (meter() instanceof FunctionalCounter typedChild) { + } else if (child instanceof FunctionalCounter typedChild) { sameNameBuilder.add(valueId("count", childID), typedChild.count()); - } else if (meter() instanceof Gauge typedChild) { + } else if (child instanceof Gauge typedChild) { MetricOutputBuilder.addNarrowed(sameNameBuilder, valueId("value", childID), typedChild.value()); } else { throw new IllegalArgumentException("Unrecognized meter type " @@ -484,7 +478,9 @@ private static String valueId(String valueName, Meter.Id meterId) { private static String tagsPortion(Meter.Id metricID) { StringJoiner sj = new StringJoiner(";", ";", ""); sj.setEmptyValue(""); - metricID.tags().forEach(tag -> sj.add(tag.key() + "=" + tag.value())); + SystemTagsManager.instance() + .withoutSystemOrScopeTags(metricID.tags()) + .forEach(tag -> sj.add(tag.key() + "=" + tag.value())); return sj.toString(); } } diff --git a/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/KeyPerformanceIndicatorMetricsImpls.java b/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/KeyPerformanceIndicatorMetricsImpls.java index 154d0802d43..830305cd2e4 100644 --- a/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/KeyPerformanceIndicatorMetricsImpls.java +++ b/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/KeyPerformanceIndicatorMetricsImpls.java @@ -16,7 +16,9 @@ package io.helidon.webserver.observe.metrics; import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -29,31 +31,26 @@ class KeyPerformanceIndicatorMetricsImpls { + /** + * Name for metric recording number of requests currently deferred (received but not yet processing). + */ + static final String DEFERRED_NAME = "deferred"; /** * Name for metric counting total requests received. */ static final String REQUESTS_COUNT_NAME = "count"; - /** * Name for metric recording current number of requests being processed. */ static final String INFLIGHT_REQUESTS_NAME = "inFlight"; - /** * Name for metric recording rate of requests with processing time exceeding a threshold. */ static final String LONG_RUNNING_REQUESTS_NAME = "longRunning"; - /** * Name for metric recording number requests currently being processed. */ static final String LOAD_NAME = "load"; - - /** - * Name for metric recording number of requests currently deferred (received but not yet processing). - */ - public static final String DEFERRED_NAME = "deferred"; - static final String KPI_METERS_SCOPE = Meter.Scope.VENDOR; private static final Map KPI_METRICS = new HashMap<>(); @@ -64,8 +61,9 @@ private KeyPerformanceIndicatorMetricsImpls() { /** * Provides a KPI metrics instance. * - * @param meterNamePrefix prefix to use for the created metrics (e.g., "requests") - * @param kpiConfig KPI metrics config which may influence the construction of the metrics + * @param kpiMeterRegistry meter registry holding the KPI metrics + * @param meterNamePrefix prefix to use for the created metrics (e.g., "requests") + * @param kpiConfig KPI metrics config which may influence the construction of the metrics * @return properly prepared new KPI metrics instance */ static KeyPerformanceIndicatorSupport.Metrics get(MeterRegistry kpiMeterRegistry, @@ -77,19 +75,26 @@ static KeyPerformanceIndicatorSupport.Metrics get(MeterRegistry kpiMeterRegistry : new Basic(kpiMeterRegistry, meterNamePrefix)); } + static void close() { + KPI_METRICS.clear(); + } + /** * Basic KPI metrics. */ private static class Basic implements KeyPerformanceIndicatorSupport.Metrics { private final Counter totalCount; + private final MeterRegistry meterRegistry; + private final List meters = new ArrayList<>(); protected Basic(MeterRegistry kpiMeterRegistry, String meterNamePrefix) { - totalCount = kpiMeterRegistry.getOrCreate( + meterRegistry = kpiMeterRegistry; + totalCount = add(kpiMeterRegistry.getOrCreate( Counter.builder(meterNamePrefix + REQUESTS_COUNT_NAME) .description( "Each request (regardless of HTTP method) will increase this counter") - .scope(KPI_METERS_SCOPE)); + .scope(KPI_METERS_SCOPE))); } @Override @@ -97,6 +102,17 @@ public void onRequestReceived() { totalCount.increment(); } + @Override + public void close() { + meters.forEach(meterRegistry::remove); + KPI_METRICS.clear(); + } + + protected M add(M meter) { + meters.add(meter); + return meter; + } + protected Counter totalCount() { return totalCount; } @@ -107,19 +123,17 @@ protected Counter totalCount() { */ private static class Extended extends Basic { + protected static final String LOAD_DESCRIPTION = + "Measures the total number of in-flight requests over the life of the server"; private final Gauge inflightRequests; private final DeferredRequests deferredRequests; private final Counter longRunningRequests; private final Counter load; - private final long longRunningRequestThresdholdMs; // The deferred-requests metric is derived from load and totalCount, so no need to have a reference to update // it directly. - + private final long longRunningRequestThresdholdMs; private AtomicInteger inflightRequestsCount = new AtomicInteger(); - protected static final String LOAD_DESCRIPTION = - "Measures the total number of in-flight requests over the life of the server"; - protected Extended(MeterRegistry kpiMeterRegistry, String meterNamePrefix, KeyPerformanceIndicatorMetricsConfig kpiConfig) { @@ -131,22 +145,26 @@ private Extended(MeterRegistry kpiMeterRegistry, String meterNamePrefix, Duratio this.longRunningRequestThresdholdMs = longRunningRequestThreshold.toMillis(); inflightRequests = kpiMeterRegistry.getOrCreate(Gauge.builder(meterNamePrefix + INFLIGHT_REQUESTS_NAME, - inflightRequestsCount, - AtomicInteger::get)); + inflightRequestsCount, + AtomicInteger::get) + .scope(KPI_METERS_SCOPE)); longRunningRequests = kpiMeterRegistry.getOrCreate( Counter.builder(meterNamePrefix + LONG_RUNNING_REQUESTS_NAME) .description("Measures the total number of long-running requests and rates at which they occur") + .scope(KPI_METERS_SCOPE) ); load = kpiMeterRegistry.getOrCreate(Counter.builder(meterNamePrefix + LOAD_NAME) - .description(LOAD_DESCRIPTION)); + .description(LOAD_DESCRIPTION) + .scope(KPI_METERS_SCOPE)); deferredRequests = new DeferredRequests(); kpiMeterRegistry.getOrCreate(Gauge.builder(meterNamePrefix + DEFERRED_NAME, deferredRequests, DeferredRequests::value) - .description("Measures deferred requests")); + .description("Measures deferred requests") + .scope(KPI_METERS_SCOPE)); } @Override diff --git a/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/MetricsFeature.java b/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/MetricsFeature.java index 366badd0c46..c4e2bab0e4d 100644 --- a/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/MetricsFeature.java +++ b/webserver/observe/metrics/src/main/java/io/helidon/webserver/observe/metrics/MetricsFeature.java @@ -99,6 +99,7 @@ public class MetricsFeature extends HelidonFeatureSupport { private final MetricsConfig metricsConfig; private final MeterRegistry meterRegistry; + private KeyPerformanceIndicatorSupport.Metrics kpiMetrics; private MetricsFeature(Builder builder) { super(LOGGER, builder, "Metrics"); @@ -156,13 +157,7 @@ public static Builder builder() { @Override public Optional service() { // main service is responsible for exposing metrics endpoints over HTTP - return Optional.of(rules -> { - if (metricsConfig.enabled()) { - setUpEndpoints(rules); - } else { - setUpDisabledEndpoints(rules); - } - }); + return Optional.of(new MetricsService()); } /** @@ -171,7 +166,7 @@ public Optional service() { * @param rules rules to use */ public void configureVendorMetrics(HttpRouting.Builder rules) { - KeyPerformanceIndicatorSupport.Metrics kpiMetrics = + kpiMetrics = KeyPerformanceIndicatorMetricsImpls.get(meterRegistry, KPI_METER_NAME_PREFIX_WITH_DOT, metricsConfig @@ -214,6 +209,25 @@ Optional output(MediaType mediaType, return formatter.format(); } + /** + * Separate metrics service class with an afterStop method that is properly invoked. + */ + private class MetricsService implements HttpService { + @Override + public void routing(HttpRules rules) { + if (metricsConfig.enabled()) { + setUpEndpoints(rules); + } else { + setUpDisabledEndpoints(rules); + } + } + + @Override + public void afterStop() { + kpiMetrics.close(); + } + } + private MeterRegistryFormatter chooseFormatter(MeterRegistry meterRegistry, MediaType mediaType, Optional scopeTagName, diff --git a/webserver/observe/metrics/src/test/java/io/helidon/webserver/observe/metrics/TestJsonFormatting.java b/webserver/observe/metrics/src/test/java/io/helidon/webserver/observe/metrics/TestJsonFormatting.java index 8dce9ea30c1..9492668572a 100644 --- a/webserver/observe/metrics/src/test/java/io/helidon/webserver/observe/metrics/TestJsonFormatting.java +++ b/webserver/observe/metrics/src/test/java/io/helidon/webserver/observe/metrics/TestJsonFormatting.java @@ -74,14 +74,14 @@ void testRetrievingAll() { JsonObject jsonOutput = checkAndCast(formatter.format()); JsonObject app = jsonOutput.getJsonObject("application"); assertThat("Counter 1", - app.getJsonNumber("c1;t1=v1;the-scope=application").intValue(), + app.getJsonNumber("c1;t1=v1").intValue(), is(4)); assertThat("Counter 2", - app.getJsonNumber("c1;the-scope=application").intValue(), + app.getJsonNumber("c1").intValue(), is(1)); JsonObject timerJson = app.getJsonObject("t1"); assertThat("Timer", timerJson, notNullValue()); - assertThat("Timer count", timerJson.getJsonNumber("count;the-scope=application").intValue(), is(1)); + assertThat("Timer count", timerJson.getJsonNumber("count").intValue(), is(1)); } @@ -110,7 +110,7 @@ void testRetrievingByName() { JsonObject jsonOutput = checkAndCast(formatter.format()); JsonObject app = jsonOutput.getJsonObject("application"); - assertThat("Counter 2", app.getJsonNumber("c2;the-scope=application").intValue(), is(1)); + assertThat("Counter 2", app.getJsonNumber("c2").intValue(), is(1)); assertThat("Timer", app.getJsonObject("t2"), nullValue()); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/KeyPerformanceIndicatorSupport.java b/webserver/webserver/src/main/java/io/helidon/webserver/KeyPerformanceIndicatorSupport.java index 311a8b7da77..4e886d057b2 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/KeyPerformanceIndicatorSupport.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/KeyPerformanceIndicatorSupport.java @@ -157,5 +157,11 @@ default void onRequestStarted() { */ default void onRequestCompleted(boolean isSuccessful, long processingTimeMs) { } + + /** + * Clear (particularly for between tests in the same JVM). + */ + default void close() { + } } }