diff --git a/docs/src/main/asciidoc/includes/guides/metrics.adoc b/docs/src/main/asciidoc/includes/guides/metrics.adoc index acaca1dddff..a9e84af4ff2 100644 --- a/docs/src/main/asciidoc/includes/guides/metrics.adoc +++ b/docs/src/main/asciidoc/includes/guides/metrics.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2021, 2024 Oracle and/or its affiliates. + Copyright (c) 2021, 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. @@ -104,13 +104,27 @@ curl http://localhost:8080{metrics-endpoint} // end::build-and-run-intro[] /////////////////////////////////////////////////////////////////////////////// +// Referrer must set :prom-output-scope-prefix: to mp_ if the output is to match MP (the scope tag name is mp_scope) and to empty to match SE (the tag name is just scope). // tag::metrics-prometheus-output[] -# TYPE base:classloader_current_loaded_class_count counter -# HELP base:classloader_current_loaded_class_count Displays the number of classes that are currently loaded in the Java virtual machine. -base:classloader_current_loaded_class_count 7511 -# TYPE base:classloader_total_loaded_class_count counter -# HELP base:classloader_total_loaded_class_count Displays the total number of classes that have been loaded since the Java virtual machine has started execution. -base:classloader_total_loaded_class_count 7512 +[source,text,subs="attributes+"] +.Text response (partial): +---- +# HELP classloader_loadedClasses_count Displays the number of classes that are currently loaded in the Java virtual machine. +# TYPE classloader_loadedClasses_count gauge +classloader_loadedClasses_count{{prom-output-scope-prefix}scope="base",} 4878.0 +# HELP classloader_unloadedClasses_total Displays the total number of classes unloaded since the Java virtual machine has started execution. +# TYPE classloader_unloadedClasses_total counter +classloader_unloadedClasses_total{{prom-output-scope-prefix}scope="base",} 0.0 +# HELP classloader_loadedClasses_total Displays the total number of classes that have been loaded since the Java virtual machine has started execution. +# TYPE classloader_loadedClasses_total counter +classloader_loadedClasses_total{{prom-output-scope-prefix}scope="base",} 4878.0 +# HELP vthreads_submitFailures Virtual thread submit failures since metrics start-up +# TYPE vthreads_submitFailures gauge +vthreads_submitFailures{{prom-output-scope-prefix}scope="base",} 0.0 +# HELP vthreads_pinned Number of pinned virtual threads since metrics start-up +# TYPE vthreads_pinned gauge +vthreads_pinned{{prom-output-scope-prefix}scope="base",} 0.0 +---- // end::metrics-prometheus-output[] /////////////////////////////////////////////////////////////////////////////// @@ -125,25 +139,41 @@ curl -H "Accept: application/json" http://localhost:8080{metrics-endpoint} // end::curl-metrics-json[] // tag::base-metrics-json-output[] - "gc.total;name=G1 Young Generation": 1, - "cpu.systemLoadAverage": 4.451171875, - "classloader.loadedClasses.count": 3582, - "thread.count": 18, + "gc.total;name=G1 Young Generation": 2, + "cpu.systemLoadAverage": 11.0546875, + "classloader.loadedClasses.count": 5124.0, + "thread.count": 23.0, "classloader.unloadedClasses.total": 0, - "jvm.uptime": 36.9478, + "vthreads.recentPinned": { + "count": 0, + "max": 0.0, + "mean": 0.0, + "elapsedTime": 0.0, + "p0.5": 0.0, + "p0.75": 0.0, + "p0.95": 0.0, + "p0.98": 0.0, + "p0.99": 0.0, + "p0.999": 0.0 + }, + "jvm.uptime": 138.233, "gc.time;name=G1 Young Generation": 0, "memory.committedHeap": 541065216, - "thread.max.count": 19, - "cpu.availableProcessors": 8, - "classloader.loadedClasses.total": 3582, - "thread.daemon.count": 16, + "thread.max.count": 26.0, + "vthreads.pinned": 0, + "cpu.availableProcessors": 8.0, + "classloader.loadedClasses.total": 5124, + "thread.daemon.count": 20.0, "memory.maxHeap": 8589934592, - "memory.usedHeap": 20491248 + "memory.usedHeap": 2.774652E+7, + "thread.starts": 28.0, + "vthreads.submitFailures": 0 // end::base-metrics-json-output[] // tag::vendor-metrics-json-output[] "vendor": { "requests.count": 3 } + // end::vendor-metrics-json-output[] // tag::get-single-metric[] @@ -182,6 +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. // end::controlling-intro-part-2[] // end::controlling-intro[] @@ -369,6 +400,52 @@ server: ---- endif::se-flavor[] // end::KPI[] + +// tag::virtualThreadsMetrics[] +[[observing-vthreads]] +==== Observing Virtual Threads Behavior +:vthreads-prefix: vthreads +Helidon maintains several {metrics} related to virtual threads as summarized in the next table. + +.{metrics_uc} for Virtual Threads +[cols="2,5,1"] +|=== +| {metric_uc} name | Usage | Reported by default + +| `{vthreads-prefix}.count` | Current number of active virtual threads. | no +| `{vthreads-prefix}.pinned` | Number of times virtual threads have been pinned. | yes +| `{vthreads-prefix}.recentPinned` | Distribution of the duration of thread pinning. ^1^ | yes +| `{vthreads-prefix}.started` | Number of virtual threads started. | no +| `{vthreads-prefix}.submitFailed` | Number of times submissions of a virtual thread to a platform carrier thread failed. | yes +|=== +^1^ Distribution summaries can discard stale data, so the `recentPinned` summary might not reflect all thread pinning activity. + +// tag::virtualThreadsMetricsConfig[] +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[] // end::controlling[] // tag::metrics-metadata[] diff --git a/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc b/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc index 91c76d059e3..ebec8c0d16a 100644 --- a/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc +++ b/docs/src/main/asciidoc/includes/metrics/metrics-config.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2021, 2024 Oracle and/or its affiliates. + Copyright (c) 2021, 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. @@ -115,6 +115,9 @@ server: endif::[] Helidon does not update metrics, and the `{metrics-endpoint}` endpoints respond with `404`.. +==== Enabling {metrics_uc} for Virtual Thread Counts +include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetricsConfig] + [#config-kpi] ==== Collecting Basic and Extended Key Performance Indicator (KPI) {metrics_uc} diff --git a/docs/src/main/asciidoc/mp/guides/metrics.adoc b/docs/src/main/asciidoc/mp/guides/metrics.adoc index 48d8593ef68..0b387a69a95 100644 --- a/docs/src/main/asciidoc/mp/guides/metrics.adoc +++ b/docs/src/main/asciidoc/mp/guides/metrics.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2019, 2024 Oracle and/or its affiliates. + Copyright (c) 2019, 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. @@ -28,6 +28,7 @@ include::{rootdir}/includes/mp.adoc[] :metric_uc: Metric :metrics_uc: Metrics :metrics-endpoint: /metrics +:prom-output-scope-prefix: mp_ include::{rootdir}/includes/guides/metrics.adoc[tag=intro] include::{rootdir}/includes/guides/metrics.adoc[tag=create-sample-project] @@ -35,19 +36,7 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=using-built-in-metrics-intro include::{rootdir}/includes/guides/metrics.adoc[tag=build-and-run-intro] -[source,text] -.Text response: (partial) ----- -# HELP classloader_loadedClasses_total Displays the total number of classes that have been loaded since the Java virtual machine has started execution. -# TYPE classloader_loadedClasses_total counter -classloader_loadedClasses_total{mp_scope="base",} 8146.0 -# HELP requests_count_total Each request (regardless of HTTP method) will increase this counter -# TYPE requests_count_total counter -requests_count_total{mp_scope="vendor",} 1.0 -# HELP jvm_uptime_seconds Displays the start time of the Java virtual machine in seconds. This attribute displays the approximate time when the Java virtual machine started. -# TYPE jvm_uptime_seconds gauge -jvm_uptime_seconds{mp_scope="base",} 7.3770 ----- +include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-prometheus-output] include::{rootdir}/includes/guides/metrics.adoc[tag=curl-metrics-json] @@ -72,8 +61,10 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=curl-metrics-json] "cpu.systemLoadAverage": 10.3388671875, "classloader.loadedClasses.count": 8224, "thread.count": 19, + "vthreads.pinned": 0, "classloader.unloadedClasses.total": 0, - "jvm.uptime": 36.8224 + "jvm.uptime": 36.8224, + "vthreads.submitFailures": 0 } } ---- @@ -90,6 +81,8 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=disabling-whole] include::{rootdir}/includes/guides/metrics.adoc[tag=KPI] +include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetrics] + [[controlling-rest-request-metrics]] ==== Controlling `REST.request` Metrics Helidon MP implements the optional family of metrics, all with the name `REST.request`, as described in the diff --git a/docs/src/main/asciidoc/se/guides/metrics.adoc b/docs/src/main/asciidoc/se/guides/metrics.adoc index 09b391125bf..7f1df20a929 100644 --- a/docs/src/main/asciidoc/se/guides/metrics.adoc +++ b/docs/src/main/asciidoc/se/guides/metrics.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2019, 2024 Oracle and/or its affiliates. + Copyright (c) 2019, 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. @@ -33,6 +33,7 @@ include::{rootdir}/includes/se.adoc[] :meter_uc: Meter :meters_uc: Meters :metrics-endpoint: /observe/metrics +:prom-output-scope-prefix: include::{rootdir}/includes/guides/metrics.adoc[tag=intro] include::{rootdir}/includes/guides/metrics.adoc[tag=create-sample-project] @@ -63,11 +64,7 @@ You do not need to change any of the generated source code. include::{rootdir}/includes/guides/metrics.adoc[tag=build-and-run-intro] -[source,text] -.Text response: ----- include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-prometheus-output] ----- You can get the same data in JSON format. @@ -138,6 +135,8 @@ include::{rootdir}/../java/io/helidon/docs/se/guides/MetricsSnippets.java[tag=sn <6> Add the metrics observer to the `ObserveFeature`. <7> Add the `ObserveFeature` to the `WebServer`. +include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetrics] + // end of Controlling Metrics section include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-metadata] 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 4b4531b6165..0d49613591e 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * Copyright (c) 2023, 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. @@ -198,6 +198,18 @@ static List createTags(String pairs) { @Option.DefaultBoolean(false) boolean restRequestEnabled(); + /** + * 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(); + /** * Metrics configuration node. * diff --git a/metrics/system-meters/pom.xml b/metrics/system-meters/pom.xml index 6b5e5e021a5..5b6a2eea5a6 100644 --- a/metrics/system-meters/pom.xml +++ b/metrics/system-meters/pom.xml @@ -62,6 +62,11 @@ helidon-common-testing-junit5 test + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -78,4 +83,37 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + + + **/TestVirtualThreadsMetersWithCounts.java + + + + + test-with-virtual-thread-counts + + test + + + + **/TestVirtualThreadsMetersWithCounts.java + + + true + + + + + + + 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 a90c79efe0e..1aa4eb1f486 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024 Oracle and/or its affiliates. + * Copyright (c) 2023, 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. @@ -100,6 +100,10 @@ public class SystemMetersProvider implements MetersProvider { + "virtual machine started or " + "peak was reset. This includes daemon and " + "non-daemon threads."); + private static final Metadata.Builder THREAD_STARTS = Metadata.builder() + .withName("thread.starts") + .withDescription("Displays the total number of platform threads created and also started " + + "since the Java virtual machine started."); private static final Metadata.Builder CL_LOADED_COUNT = Metadata.builder() .withName("classloader.loadedClasses.count") .withDescription("Displays the number of classes that are currently loaded in " @@ -242,6 +246,7 @@ private Metadata metadata(Metadata.Builder metadataBuilderWithCamelCaseName) { registerGauge(result, metadata(THREAD_COUNT), threadBean, ThreadMXBean::getThreadCount); registerGauge(result, metadata(THREAD_DAEMON_COUNT), threadBean, ThreadMXBean::getDaemonThreadCount); registerGauge(result, metadata(THREAD_MAX_COUNT), threadBean, ThreadMXBean::getPeakThreadCount); + registerGauge(result, metadata(THREAD_STARTS), threadBean, ThreadMXBean::getTotalStartedThreadCount); ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean(); registerGauge(result, metadata(CL_LOADED_COUNT), clBean, ClassLoadingMXBean::getLoadedClassCount); 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 new file mode 100644 index 00000000000..58063ab3fc6 --- /dev/null +++ b/metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/VThreadSystemMetersProvider.java @@ -0,0 +1,148 @@ +/* + * 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.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import io.helidon.common.LazyValue; +import io.helidon.metrics.api.Gauge; +import io.helidon.metrics.api.Meter; +import io.helidon.metrics.api.Metrics; +import io.helidon.metrics.api.MetricsFactory; +import io.helidon.metrics.api.Timer; +import io.helidon.metrics.spi.MetersProvider; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; + +/** + * Provides system meters for virtual threads using Java Flight Recorder events. + *

+ * The virtual thread meters are all net changes since this object was initialized during metrics start-up. This + * currently happens before the server listeners start so the virtual thread data should be fairly accurate. + *

+ * Further, we track both the number of pinned virtual threads and a distribution summary of their length. That's because + * distribution summaries reset after a while to manage storage and reduce the contribution of stale samples. + * Keeping a separate count gives some indication of the level of thread pinning over the entire lifetime of the server. + *

+ * JFR delivers events in batches. For performance the values we track are stored as longs without + * concern for concurrent updates which should not happen anyway. + */ +public class VThreadSystemMetersProvider implements MetersProvider { + + // Parts of the meter names. + static final String METER_NAME_PREFIX = "vthreads."; + static final String COUNT = "count"; + static final String SUBMIT_FAILURES = "submitFailures"; + static final String PINNED = "pinned"; + static final String RECENT_PINNED = "recentPinned"; + static final String STARTS = "starts"; + + private static final String METER_SCOPE = Meter.Scope.BASE; + + private static final System.Logger LOGGER = System.getLogger(VThreadSystemMetersProvider.class.getName()); + private final LazyValue recentPinnedVirtualThreads = LazyValue.create(this::findPinned); + private long virtualThreadSubmitFails; + private long pinnedVirtualThreads; + private long virtualThreads; + private long virtualThreadStarts; + + /** + * For service loading. + */ + public VThreadSystemMetersProvider() { + } + + @Override + public Collection> meterBuilders(MetricsFactory metricsFactory) { + + var rs = new RecordingStream(); + + List> meterBuilders = new ArrayList<>(List.of( + Gauge.builder(METER_NAME_PREFIX + SUBMIT_FAILURES, () -> virtualThreadSubmitFails) + .description("Virtual thread submit failures") + .scope(METER_SCOPE), + Gauge.builder(METER_NAME_PREFIX + PINNED, () -> pinnedVirtualThreads) + .description("Number of pinned virtual threads") + .scope(METER_SCOPE), + Timer.builder(METER_NAME_PREFIX + RECENT_PINNED) + .description("Pinned virtual thread durations") + .scope(METER_SCOPE))); + + listenFor(rs, 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(rs, Map.of("jdk.VirtualThreadStart", this::recordThreadStart, + "jdk.VirtualThreadEnd", this::recordThreadEnd)); + } + + rs.startAsync(); + return meterBuilders; + } + + private static void listenFor(RecordingStream rs, Map> events) { + // Enable events of interest explicitly (as well as registering the callback) to be sure we get the events + // despite what the defaults might be. + + events.forEach((eventName, callback) -> { + rs.enable(eventName); + rs.onEvent(eventName, callback); + }); + } + + private Timer findPinned() { + var result = Metrics.globalRegistry().timer(METER_NAME_PREFIX + PINNED, List.of()); + if (result.isEmpty()) { + throw new IllegalStateException(METER_NAME_PREFIX + "pinned meter expected but not registered"); + } + return result.get(); + } + + private void recordThreadStart(RecordedEvent event) { + virtualThreads++; + virtualThreadStarts++; + if (virtualThreadStarts < 0) { + LOGGER.log(System.Logger.Level.INFO, + "Metrics counter for virtual thread starts has overflowed; clearing and continuing"); + virtualThreadStarts = 0; + } + } + + private void recordThreadEnd(RecordedEvent event) { + virtualThreads--; + } + + private void recordSubmitFail(RecordedEvent event) { + virtualThreadSubmitFails++; + } + + private void recordThreadPin(RecordedEvent event) { + pinnedVirtualThreads++; + recentPinnedVirtualThreads.get().record(event.getDuration()); + } +} diff --git a/metrics/system-meters/src/main/java/module-info.java b/metrics/system-meters/src/main/java/module-info.java index 5ab7eab1c56..688c2454b8f 100644 --- a/metrics/system-meters/src/main/java/module-info.java +++ b/metrics/system-meters/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 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. @@ -30,8 +30,10 @@ requires io.helidon.common; requires io.helidon.metrics.api; requires java.management; + requires jdk.jfr; provides io.helidon.metrics.spi.MetersProvider - with io.helidon.metrics.systemmeters.SystemMetersProvider; + with io.helidon.metrics.systemmeters.SystemMetersProvider, + io.helidon.metrics.systemmeters.VThreadSystemMetersProvider; } diff --git a/metrics/system-meters/src/main/resources/META-INF/native-image/io.helidon.metrics/helidon-metrics-system-meters/native-image.properties b/metrics/system-meters/src/main/resources/META-INF/native-image/io.helidon.metrics/helidon-metrics-system-meters/native-image.properties new file mode 100644 index 00000000000..f00afb0c1b6 --- /dev/null +++ b/metrics/system-meters/src/main/resources/META-INF/native-image/io.helidon.metrics/helidon-metrics-system-meters/native-image.properties @@ -0,0 +1,17 @@ +# +# 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. +# + +Args=--enable-monitoring=jfr diff --git a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersBase.java b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersBase.java new file mode 100644 index 00000000000..e6752515ceb --- /dev/null +++ b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersBase.java @@ -0,0 +1,58 @@ +/* + * 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 io.helidon.metrics.api.MeterRegistry; +import io.helidon.metrics.api.Metrics; +import io.helidon.webserver.testing.junit5.ServerTest; + +import org.junit.jupiter.api.Test; + +import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.METER_NAME_PREFIX; +import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.PINNED; +import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.RECENT_PINNED; +import static io.helidon.metrics.systemmeters.VThreadSystemMetersProvider.SUBMIT_FAILURES; +import static org.hamcrest.MatcherAssert.assertThat; + +@ServerTest +class TestVirtualThreadsMetersBase { + + private final MeterRegistry meterRegistry; + + TestVirtualThreadsMetersBase() { + meterRegistry = Metrics.globalRegistry(); + } + + MeterRegistry meterRegistry() { + return meterRegistry; + } + + @Test + void checkNonCountVthreadMetersArePresentAfterStartup() { + assertThat("Submit failures gauge", + meterRegistry.gauge(METER_NAME_PREFIX + SUBMIT_FAILURES, List.of()), + OptionalMatcher.optionalPresent()); + assertThat("Pinned gauge", + meterRegistry.gauge(METER_NAME_PREFIX + PINNED, List.of()), + OptionalMatcher.optionalPresent()); + assertThat("Pinned distribution summary", + meterRegistry.timer(METER_NAME_PREFIX + RECENT_PINNED, List.of()), + OptionalMatcher.optionalPresent()); + } +} diff --git a/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithCounts.java b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithCounts.java new file mode 100644 index 00000000000..d0e32bae0ef --- /dev/null +++ b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithCounts.java @@ -0,0 +1,41 @@ +/* + * 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 TestVirtualThreadsMetersWithCounts extends TestVirtualThreadsMetersBase { + + @Test + void testVirtualThreadsCounts() { + assertThat("Starts meter", + meterRegistry().gauge(METER_NAME_PREFIX + STARTS, List.of()), + OptionalMatcher.optionalPresent()); + assertThat("Count gauge", + meterRegistry().gauge(METER_NAME_PREFIX + COUNT, List.of()), + OptionalMatcher.optionalPresent()); + } +} 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 new file mode 100644 index 00000000000..fa0fda5b42f --- /dev/null +++ b/metrics/system-meters/src/test/java/io/helidon/metrics/systemmeters/TestVirtualThreadsMetersWithoutCounts.java @@ -0,0 +1,40 @@ +/* + * 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 2df25015bed..bd75a8a6cb8 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. + * Copyright (c) 2019, 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. @@ -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(): 0", aBean::baseRegistry); + invoke(collector, "Base metric registry", "Timers.size(): 1", aBean::baseRegistry); // JWT-Auth validateJwtProtectedResource(collector, target, jwtToken);