Skip to content

Commit 01145f1

Browse files
authored
Add metrics related to virtual threads (helidon-io#9619)
1 parent 6c9f21a commit 01145f1

14 files changed

Lines changed: 477 additions & 44 deletions

File tree

docs/src/main/asciidoc/includes/guides/metrics.adoc

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
///////////////////////////////////////////////////////////////////////////////
22

3-
Copyright (c) 2021, 2024 Oracle and/or its affiliates.
3+
Copyright (c) 2021, 2025 Oracle and/or its affiliates.
44

55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -104,13 +104,27 @@ curl http://localhost:8080{metrics-endpoint}
104104
// end::build-and-run-intro[]
105105
106106
///////////////////////////////////////////////////////////////////////////////
107+
// 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).
107108
// tag::metrics-prometheus-output[]
108-
# TYPE base:classloader_current_loaded_class_count counter
109-
# HELP base:classloader_current_loaded_class_count Displays the number of classes that are currently loaded in the Java virtual machine.
110-
base:classloader_current_loaded_class_count 7511
111-
# TYPE base:classloader_total_loaded_class_count counter
112-
# 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.
113-
base:classloader_total_loaded_class_count 7512
109+
[source,text,subs="attributes+"]
110+
.Text response (partial):
111+
----
112+
# HELP classloader_loadedClasses_count Displays the number of classes that are currently loaded in the Java virtual machine.
113+
# TYPE classloader_loadedClasses_count gauge
114+
classloader_loadedClasses_count{{prom-output-scope-prefix}scope="base",} 4878.0
115+
# HELP classloader_unloadedClasses_total Displays the total number of classes unloaded since the Java virtual machine has started execution.
116+
# TYPE classloader_unloadedClasses_total counter
117+
classloader_unloadedClasses_total{{prom-output-scope-prefix}scope="base",} 0.0
118+
# HELP classloader_loadedClasses_total Displays the total number of classes that have been loaded since the Java virtual machine has started execution.
119+
# TYPE classloader_loadedClasses_total counter
120+
classloader_loadedClasses_total{{prom-output-scope-prefix}scope="base",} 4878.0
121+
# HELP vthreads_submitFailures Virtual thread submit failures since metrics start-up
122+
# TYPE vthreads_submitFailures gauge
123+
vthreads_submitFailures{{prom-output-scope-prefix}scope="base",} 0.0
124+
# HELP vthreads_pinned Number of pinned virtual threads since metrics start-up
125+
# TYPE vthreads_pinned gauge
126+
vthreads_pinned{{prom-output-scope-prefix}scope="base",} 0.0
127+
----
114128
// end::metrics-prometheus-output[]
115129
///////////////////////////////////////////////////////////////////////////////
116130
@@ -125,25 +139,41 @@ curl -H "Accept: application/json" http://localhost:8080{metrics-endpoint}
125139
// end::curl-metrics-json[]
126140
127141
// tag::base-metrics-json-output[]
128-
"gc.total;name=G1 Young Generation": 1,
129-
"cpu.systemLoadAverage": 4.451171875,
130-
"classloader.loadedClasses.count": 3582,
131-
"thread.count": 18,
142+
"gc.total;name=G1 Young Generation": 2,
143+
"cpu.systemLoadAverage": 11.0546875,
144+
"classloader.loadedClasses.count": 5124.0,
145+
"thread.count": 23.0,
132146
"classloader.unloadedClasses.total": 0,
133-
"jvm.uptime": 36.9478,
147+
"vthreads.recentPinned": {
148+
"count": 0,
149+
"max": 0.0,
150+
"mean": 0.0,
151+
"elapsedTime": 0.0,
152+
"p0.5": 0.0,
153+
"p0.75": 0.0,
154+
"p0.95": 0.0,
155+
"p0.98": 0.0,
156+
"p0.99": 0.0,
157+
"p0.999": 0.0
158+
},
159+
"jvm.uptime": 138.233,
134160
"gc.time;name=G1 Young Generation": 0,
135161
"memory.committedHeap": 541065216,
136-
"thread.max.count": 19,
137-
"cpu.availableProcessors": 8,
138-
"classloader.loadedClasses.total": 3582,
139-
"thread.daemon.count": 16,
162+
"thread.max.count": 26.0,
163+
"vthreads.pinned": 0,
164+
"cpu.availableProcessors": 8.0,
165+
"classloader.loadedClasses.total": 5124,
166+
"thread.daemon.count": 20.0,
140167
"memory.maxHeap": 8589934592,
141-
"memory.usedHeap": 20491248
168+
"memory.usedHeap": 2.774652E+7,
169+
"thread.starts": 28.0,
170+
"vthreads.submitFailures": 0
142171
// end::base-metrics-json-output[]
143172
// tag::vendor-metrics-json-output[]
144173
"vendor": {
145174
"requests.count": 3
146175
}
176+
147177
// end::vendor-metrics-json-output[]
148178
149179
// tag::get-single-metric[]
@@ -182,6 +212,7 @@ By adding a `metrics` section to your application configuration you can control
182212
// end::controlling-intro-part-1[]
183213
// tag::controlling-intro-part-2[]
184214
* Select whether to collect <<basic-and-extended-kpi,extended key performance indicator {metrics}>>.
215+
* Select which <<observing-vthreads,virtual threads {metrics}>> to report.
185216
// end::controlling-intro-part-2[]
186217
// end::controlling-intro[]
187218
@@ -369,6 +400,52 @@ server:
369400
----
370401
endif::se-flavor[]
371402
// end::KPI[]
403+
404+
// tag::virtualThreadsMetrics[]
405+
[[observing-vthreads]]
406+
==== Observing Virtual Threads Behavior
407+
:vthreads-prefix: vthreads
408+
Helidon maintains several {metrics} related to virtual threads as summarized in the next table.
409+
410+
.{metrics_uc} for Virtual Threads
411+
[cols="2,5,1"]
412+
|===
413+
| {metric_uc} name | Usage | Reported by default
414+
415+
| `{vthreads-prefix}.count` | Current number of active virtual threads. | no
416+
| `{vthreads-prefix}.pinned` | Number of times virtual threads have been pinned. | yes
417+
| `{vthreads-prefix}.recentPinned` | Distribution of the duration of thread pinning. ^1^ | yes
418+
| `{vthreads-prefix}.started` | Number of virtual threads started. | no
419+
| `{vthreads-prefix}.submitFailed` | Number of times submissions of a virtual thread to a platform carrier thread failed. | yes
420+
|===
421+
^1^ Distribution summaries can discard stale data, so the `recentPinned` summary might not reflect all thread pinning activity.
422+
423+
// tag::virtualThreadsMetricsConfig[]
424+
For performance reasons Helidon does not by default report the {metrics} related to the count of virtual threads.
425+
Enable these {metrics} using configuration.
426+
427+
[CAUTION]
428+
Enabling virtual thread counts can degrade the performance of your server. Do so with care.
429+
430+
.Enabling Virtual Thread Counts
431+
ifdef::mp-flavor[]
432+
[source,properties]
433+
----
434+
metrics.virtual-threads.count.enabled=true
435+
----
436+
endif::[]
437+
ifdef::se-flavor[]
438+
[source,yaml]
439+
----
440+
metrics:
441+
virtual-threads:
442+
count:
443+
enabled: true
444+
----
445+
endif::[]
446+
// end::virtualThreadsMetricsConfig[]
447+
448+
// end::virtualThreadsMetrics[]
372449
// end::controlling[]
373450
374451
// tag::metrics-metadata[]

docs/src/main/asciidoc/includes/metrics/metrics-config.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
///////////////////////////////////////////////////////////////////////////////
22

3-
Copyright (c) 2021, 2024 Oracle and/or its affiliates.
3+
Copyright (c) 2021, 2025 Oracle and/or its affiliates.
44

55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -115,6 +115,9 @@ server:
115115
endif::[]
116116
Helidon does not update metrics, and the `{metrics-endpoint}` endpoints respond with `404`..
117117
118+
==== Enabling {metrics_uc} for Virtual Thread Counts
119+
include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetricsConfig]
120+
118121
[#config-kpi]
119122
==== Collecting Basic and Extended Key Performance Indicator (KPI) {metrics_uc}
120123

docs/src/main/asciidoc/mp/guides/metrics.adoc

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
///////////////////////////////////////////////////////////////////////////////
22

3-
Copyright (c) 2019, 2024 Oracle and/or its affiliates.
3+
Copyright (c) 2019, 2025 Oracle and/or its affiliates.
44

55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -28,26 +28,15 @@ include::{rootdir}/includes/mp.adoc[]
2828
:metric_uc: Metric
2929
:metrics_uc: Metrics
3030
:metrics-endpoint: /metrics
31+
:prom-output-scope-prefix: mp_
3132
3233
include::{rootdir}/includes/guides/metrics.adoc[tag=intro]
3334
include::{rootdir}/includes/guides/metrics.adoc[tag=create-sample-project]
3435
include::{rootdir}/includes/guides/metrics.adoc[tag=using-built-in-metrics-intro]
3536
include::{rootdir}/includes/guides/metrics.adoc[tag=build-and-run-intro]
3637
3738
38-
[source,text]
39-
.Text response: (partial)
40-
----
41-
# HELP classloader_loadedClasses_total Displays the total number of classes that have been loaded since the Java virtual machine has started execution.
42-
# TYPE classloader_loadedClasses_total counter
43-
classloader_loadedClasses_total{mp_scope="base",} 8146.0
44-
# HELP requests_count_total Each request (regardless of HTTP method) will increase this counter
45-
# TYPE requests_count_total counter
46-
requests_count_total{mp_scope="vendor",} 1.0
47-
# 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.
48-
# TYPE jvm_uptime_seconds gauge
49-
jvm_uptime_seconds{mp_scope="base",} 7.3770
50-
----
39+
include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-prometheus-output]
5140
5241
include::{rootdir}/includes/guides/metrics.adoc[tag=curl-metrics-json]
5342
@@ -72,8 +61,10 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=curl-metrics-json]
7261
"cpu.systemLoadAverage": 10.3388671875,
7362
"classloader.loadedClasses.count": 8224,
7463
"thread.count": 19,
64+
"vthreads.pinned": 0,
7565
"classloader.unloadedClasses.total": 0,
76-
"jvm.uptime": 36.8224
66+
"jvm.uptime": 36.8224,
67+
"vthreads.submitFailures": 0
7768
}
7869
}
7970
----
@@ -90,6 +81,8 @@ include::{rootdir}/includes/guides/metrics.adoc[tag=disabling-whole]
9081
9182
include::{rootdir}/includes/guides/metrics.adoc[tag=KPI]
9283
84+
include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetrics]
85+
9386
[[controlling-rest-request-metrics]]
9487
==== Controlling `REST.request` Metrics
9588
Helidon MP implements the optional family of metrics, all with the name `REST.request`, as described in the

docs/src/main/asciidoc/se/guides/metrics.adoc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
///////////////////////////////////////////////////////////////////////////////
22

3-
Copyright (c) 2019, 2024 Oracle and/or its affiliates.
3+
Copyright (c) 2019, 2025 Oracle and/or its affiliates.
44

55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ include::{rootdir}/includes/se.adoc[]
3333
:meter_uc: Meter
3434
:meters_uc: Meters
3535
:metrics-endpoint: /observe/metrics
36+
:prom-output-scope-prefix:
3637
3738
include::{rootdir}/includes/guides/metrics.adoc[tag=intro]
3839
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.
6364
6465
include::{rootdir}/includes/guides/metrics.adoc[tag=build-and-run-intro]
6566
66-
[source,text]
67-
.Text response:
68-
----
6967
include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-prometheus-output]
70-
----
7168
7269
You can get the same data in JSON format.
7370
@@ -138,6 +135,8 @@ include::{rootdir}/../java/io/helidon/docs/se/guides/MetricsSnippets.java[tag=sn
138135
<6> Add the metrics observer to the `ObserveFeature`.
139136
<7> Add the `ObserveFeature` to the `WebServer`.
140137
138+
include::{rootdir}/includes/guides/metrics.adoc[tag=virtualThreadsMetrics]
139+
141140
// end of Controlling Metrics section
142141
143142
include::{rootdir}/includes/guides/metrics.adoc[tag=metrics-metadata]

metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -198,6 +198,18 @@ static List<Tag> createTags(String pairs) {
198198
@Option.DefaultBoolean(false)
199199
boolean restRequestEnabled();
200200

201+
/**
202+
* Whether the virtual thread count should be exposed as a meter.
203+
* <p>
204+
* Enabling the virtual thread count meters can degrade performance of the server because the server must monitor Java
205+
* Flight Recorder events for virtual thread starts and stops to maintain the count.
206+
*
207+
* @return true if the metrics system should compute virtual thread count meters
208+
*/
209+
@Option.Configured("virtual-threads.count.enabled")
210+
@Option.DefaultBoolean(false)
211+
boolean virtualThreadCountEnabled();
212+
201213
/**
202214
* Metrics configuration node.
203215
*

metrics/system-meters/pom.xml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@
6262
<artifactId>helidon-common-testing-junit5</artifactId>
6363
<scope>test</scope>
6464
</dependency>
65+
<dependency>
66+
<groupId>io.helidon.webserver.testing.junit5</groupId>
67+
<artifactId>helidon-webserver-testing-junit5</artifactId>
68+
<scope>test</scope>
69+
</dependency>
6570
<dependency>
6671
<groupId>org.junit.jupiter</groupId>
6772
<artifactId>junit-jupiter-api</artifactId>
@@ -78,4 +83,37 @@
7883
<scope>test</scope>
7984
</dependency>
8085
</dependencies>
86+
87+
<build>
88+
<plugins>
89+
<plugin>
90+
<groupId>org.apache.maven.plugins</groupId>
91+
<artifactId>maven-surefire-plugin</artifactId>
92+
<executions>
93+
<execution>
94+
<id>default-test</id>
95+
<configuration>
96+
<excludes>
97+
<exclude>**/TestVirtualThreadsMetersWithCounts.java</exclude>
98+
</excludes>
99+
</configuration>
100+
</execution>
101+
<execution>
102+
<id>test-with-virtual-thread-counts</id>
103+
<goals>
104+
<goal>test</goal>
105+
</goals>
106+
<configuration>
107+
<includes>
108+
<include>**/TestVirtualThreadsMetersWithCounts.java</include>
109+
</includes>
110+
<systemPropertyVariables>
111+
<metrics.virtual-threads.count.enabled>true</metrics.virtual-threads.count.enabled>
112+
</systemPropertyVariables>
113+
</configuration>
114+
</execution>
115+
</executions>
116+
</plugin>
117+
</plugins>
118+
</build>
81119
</project>

metrics/system-meters/src/main/java/io/helidon/metrics/systemmeters/SystemMetersProvider.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -100,6 +100,10 @@ public class SystemMetersProvider implements MetersProvider {
100100
+ "virtual machine started or "
101101
+ "peak was reset. This includes daemon and "
102102
+ "non-daemon threads.");
103+
private static final Metadata.Builder THREAD_STARTS = Metadata.builder()
104+
.withName("thread.starts")
105+
.withDescription("Displays the total number of platform threads created and also started "
106+
+ "since the Java virtual machine started.");
103107
private static final Metadata.Builder CL_LOADED_COUNT = Metadata.builder()
104108
.withName("classloader.loadedClasses.count")
105109
.withDescription("Displays the number of classes that are currently loaded in "
@@ -242,6 +246,7 @@ private Metadata metadata(Metadata.Builder metadataBuilderWithCamelCaseName) {
242246
registerGauge(result, metadata(THREAD_COUNT), threadBean, ThreadMXBean::getThreadCount);
243247
registerGauge(result, metadata(THREAD_DAEMON_COUNT), threadBean, ThreadMXBean::getDaemonThreadCount);
244248
registerGauge(result, metadata(THREAD_MAX_COUNT), threadBean, ThreadMXBean::getPeakThreadCount);
249+
registerGauge(result, metadata(THREAD_STARTS), threadBean, ThreadMXBean::getTotalStartedThreadCount);
245250

246251
ClassLoadingMXBean clBean = ManagementFactory.getClassLoadingMXBean();
247252
registerGauge(result, metadata(CL_LOADED_COUNT), clBean, ClassLoadingMXBean::getLoadedClassCount);

0 commit comments

Comments
 (0)