diff --git a/docs/src/main/asciidoc/includes/guides/metrics.adoc b/docs/src/main/asciidoc/includes/guides/metrics.adoc index a0c5cc69d2c..ddb4c1f74cc 100644 --- a/docs/src/main/asciidoc/includes/guides/metrics.adoc +++ b/docs/src/main/asciidoc/includes/guides/metrics.adoc @@ -212,7 +212,7 @@ By adding a `metrics` section to your application configuration you can control // end::controlling-intro-part-1[] // tag::controlling-intro-part-2[] * Select whether to collect <>. -* Select which <> to report. +* Control reporting of <>. // end::controlling-intro-part-2[] // end::controlling-intro[] @@ -402,36 +402,39 @@ endif::se-flavor[] // end::KPI[] // tag::virtualThreadsMetrics[] -[[observing-vthreads]] -==== Observing Virtual Threads Behavior +[[controlling-vthreads]] +==== Controlling Meters Related to Virtual Threads Behavior :vthreads-prefix: vthreads -Helidon maintains several {metrics} related to virtual threads as summarized in the next table. Helidon might rely on Java Flight Recorder (JFR) events and JMX MXBeans in computing the {metric} values. Be aware that limitations or changes in the values provided by these sources are outside the control of Helidon. +Helidon optionally maintains several {metrics} related to virtual threads as summarized in the next table. Helidon might rely on Java Flight Recorder (JFR) events and JMX MBeans in computing the {metric} values. Be aware that limitations or changes in the values provided by these sources are outside the control of Helidon. + +For performance reasons Helidon does not report virtual thread {metrics} unless you enable them using configuration. .{metrics_uc} for Virtual Threads -[cols="2,5,3,1"] +[cols="2,5,3"] |=== -| {metric_uc} name | Usage | Source | Reported by default +| {metric_uc} name | Usage | Source -| `{vthreads-prefix}.count` | Current number of active virtual threads. | JFR `jdk.virtualThreadStart` and `jdk.virtualThreadEnd` events| no -| `{vthreads-prefix}.pinned` | Number of times virtual threads have been pinned. | JFR `jdk.virtualThreadPinned` event | yes -| `{vthreads-prefix}.recentPinned` | Distribution of the duration of thread pinning. ^1^ | JFR `jdk.virtualThreadPinned` event | yes -| `{vthreads-prefix}.started` | Number of virtual threads started. | JFR `jdk.virtualThreadStart` event | no -| `{vthreads-prefix}.submitFailed` | Number of times submissions of a virtual thread to a platform carrier thread failed. | JFR `jdk.virtualThreadSubmitFailed` event | yes +| `{vthreads-prefix}.count` | Current number of active virtual threads. | JFR `jdk.virtualThreadStart` and `jdk.virtualThreadEnd` events +| `{vthreads-prefix}.pinned` | Number of times virtual threads have been pinned. | JFR `jdk.virtualThreadPinned` event +| `{vthreads-prefix}.recentPinned` | Distribution of the duration of thread pinning. ^1^ | JFR `jdk.virtualThreadPinned` event +| `{vthreads-prefix}.started` | Number of virtual threads started. | JFR `jdk.virtualThreadStart` event +| `{vthreads-prefix}.submitFailed` | Number of times submissions of a virtual thread to a platform carrier thread failed. | JFR `jdk.virtualThreadSubmitFailed` event |=== ^1^ Distribution summaries can discard stale data, so the `recentPinned` summary might not reflect all thread pinning activity. +^1^ Distribution summaries can discard stale data, so the `recentPinned` summary might not reflect all thread pinning activity. // tag::virtualThreadsMetricsConfig[] ==== Configuring Virtual Threads {metrics_uc} -===== Disabling Virtual Threads {metrics_uc} Entirely -Gathering data to compute the {metrics} for virtual threads is designed to be as efficient as possible, but doing so still imposes a small load on the server and by default Helidon exposes only the {metrics} related to virtual threads noted in the table above. +===== Enabling Virtual Threads {metrics_uc} +Gathering data to compute the {metrics} for virtual threads is designed to be as efficient as possible, but doing so still imposes a load on the server and by default Helidon does not report {metrics} related to virtual threads. -To disable all {metrics} describing virtual threads, include a config setting as shown in the following example: +To enable the {metrics} describing virtual threads include a config setting as shown in the following example. -.Disabling virtual thread metrics entirely +.Enabling virtual thread {metrics} ifdef::mp-flavor[] [source,properties] ---- -metrics.virtual-threads.enabled = false +metrics.virtual-threads.enabled = true ---- endif::mp-flavor[] ifdef::se-flavor[] @@ -439,12 +442,12 @@ ifdef::se-flavor[] ---- metrics: virtual-threads: - enabled: false + enabled: true ---- endif::se-flavor[] ===== Controlling Measurements of Pinned Virtual Threads -Helidon measures pinned virtual threads only when the thread is pinned for a length of time at or above a threshold. Control the threshold by configuring the threshold as shown in the example below. +Helidon measures pinned virtual threads only when the thread is pinned for a length of time at or above a threshold. Control the threshold as shown in the example below. .Setting virtual thread pinning threshold to 100 ms ifdef::mp-flavor[] @@ -464,29 +467,6 @@ metrics: endif::se-flavor[] The threshold value is a `Duration` string, such as `PT0.100S` for 100 milliseconds. -===== Controlling Virtual Thread Counts -For performance reasons Helidon does not by default report the {metrics} related to the count of virtual threads. -Enable these {metrics} using configuration. - -[CAUTION] -Enabling virtual thread counts can degrade the performance of your server. Do so with care. - -.Enabling Virtual Thread Counts -ifdef::mp-flavor[] -[source,properties] ----- -metrics.virtual-threads.count.enabled=true ----- -endif::[] -ifdef::se-flavor[] -[source,yaml] ----- -metrics: - virtual-threads: - count: - enabled: true ----- -endif::[] // end::virtualThreadsMetricsConfig[] // end::virtualThreadsMetrics[] diff --git a/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc b/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc index ebec8c0d16a..0006f83f297 100644 --- a/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc +++ b/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc @@ -86,6 +86,7 @@ Metrics configuration is quite extensive and powerful and, therefore, a bit comp The rest of this section illustrates some of the most common scenarios: * <> +* <>. * <> ifdef::mp-flavor[] * <> @@ -113,9 +114,9 @@ server: enabled: false ---- endif::[] -Helidon does not update metrics, and the `{metrics-endpoint}` endpoints respond with `404`.. +Helidon does not update metrics, and the `{metrics-endpoint}` endpoints respond with `404`. -==== Enabling {metrics_uc} for Virtual Thread Counts +[#config-virtual-threads] include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetricsConfig] [#config-kpi] diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java index a93ac06e6fb..692a270c1f0 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java @@ -205,20 +205,8 @@ static List createTags(String pairs) { * @return true to include meters related to virtual threads */ @Option.Configured("virtual-threads.enabled") - @Option.DefaultBoolean(true) - boolean virtualThreadsEnabled(); - - /** - * Whether the virtual thread count should be exposed as a meter. - *

- * Enabling the virtual thread count meters can degrade performance of the server because the server must monitor Java - * Flight Recorder events for virtual thread starts and stops to maintain the count. - * - * @return true if the metrics system should compute virtual thread count meters - */ - @Option.Configured("virtual-threads.count.enabled") @Option.DefaultBoolean(false) - boolean virtualThreadCountEnabled(); + boolean virtualThreadsEnabled(); /** * Threshold for sampling pinned virtual threads to include in the pinned threads meter. diff --git a/metrics/system-meters/pom.xml b/metrics/system-meters/pom.xml index 5b6a2eea5a6..f51843ea50c 100644 --- a/metrics/system-meters/pom.xml +++ b/metrics/system-meters/pom.xml @@ -94,21 +94,21 @@ default-test - **/TestVirtualThreadsMetersWithCounts.java + **/TestVirtualThreads*.java - test-with-virtual-thread-counts + test-with-virtual-thread-meters test - **/TestVirtualThreadsMetersWithCounts.java + **/TestVirtualThreads*.java - true + true diff --git a/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/VThreadSystemMetersProvider.java b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/VThreadSystemMetersProvider.java index 917f33d7f10..8d3c423e063 100644 --- a/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/VThreadSystemMetersProvider.java +++ b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/VThreadSystemMetersProvider.java @@ -102,17 +102,15 @@ public VThreadSystemMetersProvider() { listenFor(recordingStream, Map.of("jdk.VirtualThreadSubmitFailed", this::recordSubmitFail, "jdk.VirtualThreadPinned", this::recordThreadPin)); - if (metricsFactory.metricsConfig().virtualThreadCountEnabled()) { - meterBuilders.add(Gauge.builder(METER_NAME_PREFIX + COUNT, () -> virtualThreads) - .description("Active virtual threads") - .scope(METER_SCOPE)); - meterBuilders.add(Gauge.builder(METER_NAME_PREFIX + STARTS, () -> virtualThreadStarts) - .description("Number of virtual thread starts") - .scope(METER_SCOPE)); - - listenFor(recordingStream, Map.of("jdk.VirtualThreadStart", this::recordThreadStart, - "jdk.VirtualThreadEnd", this::recordThreadEnd)); - } + meterBuilders.add(Gauge.builder(METER_NAME_PREFIX + COUNT, () -> virtualThreads) + .description("Active virtual threads") + .scope(METER_SCOPE)); + meterBuilders.add(Gauge.builder(METER_NAME_PREFIX + STARTS, () -> virtualThreadStarts) + .description("Number of virtual thread starts") + .scope(METER_SCOPE)); + + listenFor(recordingStream, Map.of("jdk.VirtualThreadStart", this::recordThreadStart, + "jdk.VirtualThreadEnd", this::recordThreadEnd)); recordingStream.startAsync(); return meterBuilders; diff --git a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersConfigs.java b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersConfigs.java index 9bbfbf9fbdf..5c2574a3025 100644 --- a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersConfigs.java +++ b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersConfigs.java @@ -47,29 +47,12 @@ void checkDefault() { MetricsFactory metricsFactory = MetricsFactory.getInstance(config); VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider(); var meterBuilders = provider.meterBuilders(metricsFactory); - assertThat("Default meter builders", - meterBuilders, - containsInAnyOrder(allOf(withName(equalTo(METER_NAME_PREFIX + PINNED)), - instanceOf(Gauge.Builder.class)), - allOf(withName(equalTo(METER_NAME_PREFIX + SUBMIT_FAILURES)), - instanceOf(Gauge.Builder.class)), - allOf(withName(equalTo(METER_NAME_PREFIX + RECENT_PINNED)), - instanceOf(Timer.Builder.class)))); - assertThat("Pinned thread threshold", provider.pinnedVirtualThreadsThresholdMillis(), equalTo(20L)); - } - - @Test - void checkVirtualThreadMetersDisabled() { - Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.enabled", "false"))); - MetricsFactory metricsFactory = MetricsFactory.getInstance(config); - VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider(); - var meterBuilders = provider.meterBuilders(metricsFactory); - assertThat("Meter builders with virtual threads meters disabled", meterBuilders, empty()); + assertThat("Meter builders with default config", meterBuilders, empty()); } @Test void checkVirtualThreadCountMetersEnabled() { - Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.count.enabled", "true"))); + Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.enabled", "true"))); MetricsFactory metricsFactory = MetricsFactory.getInstance(config); VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider(); var meterBuilders = provider.meterBuilders(metricsFactory); @@ -91,7 +74,8 @@ void checkVirtualThreadCountMetersEnabled() { @Test void checkPinnedThreadThreshold() { - Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.pinned.threshold", "PT0.040S"))); + Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.enabled", "true", + "virtual-threads.pinned.threshold", "PT0.040S"))); MetricsFactory metricsFactory = MetricsFactory.getInstance(config); VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider(); provider.meterBuilders(metricsFactory); @@ -102,7 +86,8 @@ void checkPinnedThreadThreshold() { @Test void checkRecentPinnedTimerLookup() { - Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.pinned.threshold", "PT0.040S"))); + Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.enabled", "true", + "virtual-threads.pinned.threshold", "PT0.040S"))); MetricsFactory metricsFactory = MetricsFactory.getInstance(config); VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider(); provider.meterBuilders(metricsFactory); diff --git a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithoutCounts.java b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithoutCounts.java deleted file mode 100644 index fa0fda5b42f..00000000000 --- a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithoutCounts.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2025 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.metrics.systemmeters; - -import java.util.List; - -import io.helidon.common.testing.junit5.OptionalMatcher; - -import org.junit.jupiter.api.Test; - -import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.COUNT; -import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.METER_NAME_PREFIX; -import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.STARTS; -import static org.hamcrest.MatcherAssert.assertThat; - -class TestVirtualThreadsMetersWithoutCounts extends TestVirtualThreadsMetersBase { - - @Test - void checkVthreadCountsAreAbsent() { - assertThat("Starts meter", - meterRegistry().gauge(METER_NAME_PREFIX + STARTS, List.of()), - OptionalMatcher.optionalEmpty()); - assertThat("Count gauge", - meterRegistry().gauge(METER_NAME_PREFIX + COUNT, List.of()), - OptionalMatcher.optionalEmpty()); - } -} diff --git a/tests/integration/packaging/mp-1/src/main/java/io/helidon/tests/integration/packaging/mp1/Mp1Main.java b/tests/integration/packaging/mp-1/src/main/java/io/helidon/tests/integration/packaging/mp1/Mp1Main.java index bd75a8a6cb8..c9f4c89f2d6 100644 --- a/tests/integration/packaging/mp-1/src/main/java/io/helidon/tests/integration/packaging/mp1/Mp1Main.java +++ b/tests/integration/packaging/mp-1/src/main/java/io/helidon/tests/integration/packaging/mp1/Mp1Main.java @@ -218,7 +218,7 @@ private static void testBean(int port, String jwtToken) { }); invoke(collector, "Application metric registry", "Timers.size(): 1", aBean::appRegistry); - invoke(collector, "Base metric registry", "Timers.size(): 1", aBean::baseRegistry); + invoke(collector, "Base metric registry", "Timers.size(): 0", aBean::baseRegistry); // JWT-Auth validateJwtProtectedResource(collector, target, jwtToken);