15
15
*/
16
16
package io .micrometer .java21 .instrument .binder .jdk ;
17
17
18
- import io .micrometer .core .instrument .Counter ;
19
- import io .micrometer .core .instrument .MeterRegistry ;
20
- import io .micrometer .core .instrument .Tag ;
21
- import io .micrometer .core .instrument .Timer ;
18
+ import io .micrometer .core .instrument .*;
22
19
import io .micrometer .core .instrument .binder .MeterBinder ;
23
20
import jdk .jfr .consumer .RecordingStream ;
24
21
25
22
import java .io .Closeable ;
26
23
import java .time .Duration ;
27
24
import java .util .Objects ;
25
+ import java .util .concurrent .atomic .LongAdder ;
28
26
29
27
import static java .util .Collections .emptyList ;
30
28
@@ -41,37 +39,69 @@ public class VirtualThreadMetrics implements MeterBinder, Closeable {
41
39
42
40
private static final String SUBMIT_FAILED_EVENT = "jdk.VirtualThreadSubmitFailed" ;
43
41
44
- private final RecordingStream recordingStream ;
42
+ private static final String START_EVENT = "jdk.VirtualThreadStart" ;
45
43
46
- private final Iterable <Tag > tags ;
44
+ private static final String END_EVENT = "jdk.VirtualThreadEnd" ;
45
+
46
+ private static final String SUBMIT_FAILED_METRIC_NAME = "jvm.threads.virtual.submit.failed" ;
47
+
48
+ private static final String VT_PINNED_METRIC_NAME = "jvm.threads.virtual.pinned" ;
47
49
50
+ private static final String VT_ACTIVE_METRIC_NAME = "jvm.threads.virtual.active" ;
51
+
52
+ private final RecordingConfig recordingCfg ;
53
+
54
+ private RecordingStream recordingStream ;
55
+
56
+ private final Iterable <Tag > tags ;
57
+
58
+ private boolean activeMetricEnabled ;
59
+
48
60
public VirtualThreadMetrics () {
49
61
this (new RecordingConfig (), emptyList ());
50
62
}
51
63
64
+ public VirtualThreadMetrics (RecordingConfig config ) {
65
+ this (config , emptyList ());
66
+ }
67
+
52
68
public VirtualThreadMetrics (Iterable <Tag > tags ) {
53
69
this (new RecordingConfig (), tags );
54
70
}
55
71
56
- private VirtualThreadMetrics (RecordingConfig config , Iterable <Tag > tags ) {
57
- this .recordingStream = createRecordingStream ( config ) ;
72
+ public VirtualThreadMetrics (RecordingConfig config , Iterable <Tag > tags ) {
73
+ this .recordingCfg = config ;
58
74
this .tags = tags ;
59
75
}
60
76
61
77
@ Override
62
78
public void bindTo (MeterRegistry registry ) {
63
- Timer pinnedTimer = Timer .builder ("jvm.threads.virtual.pinned" )
79
+ if (this .recordingStream == null ) {
80
+ this .recordingStream = createRecordingStream (this .recordingCfg );
81
+ }
82
+ Timer pinnedTimer = Timer .builder (VT_PINNED_METRIC_NAME )
64
83
.description ("The duration while the virtual thread was pinned without releasing its platform thread" )
65
84
.tags (tags )
66
85
.register (registry );
67
86
68
- Counter submitFailedCounter = Counter .builder ("jvm.threads.virtual.submit.failed" )
87
+ Counter submitFailedCounter = Counter .builder (SUBMIT_FAILED_METRIC_NAME )
69
88
.description ("The number of events when starting or unparking a virtual thread failed" )
70
89
.tags (tags )
71
90
.register (registry );
72
91
73
92
recordingStream .onEvent (PINNED_EVENT , event -> pinnedTimer .record (event .getDuration ()));
74
93
recordingStream .onEvent (SUBMIT_FAILED_EVENT , event -> submitFailedCounter .increment ());
94
+
95
+ if (activeMetricEnabled ) {
96
+ final LongAdder activeCounter = new LongAdder ();
97
+ this .recordingStream .onEvent (START_EVENT , event -> activeCounter .increment ());
98
+ this .recordingStream .onEvent (END_EVENT , event -> activeCounter .decrement ());
99
+
100
+ Gauge .builder (VT_ACTIVE_METRIC_NAME , activeCounter ::doubleValue )
101
+ .description ("The number of active virtual threads" )
102
+ .tags (tags )
103
+ .register (registry );
104
+ }
75
105
}
76
106
77
107
private RecordingStream createRecordingStream (RecordingConfig config ) {
@@ -80,6 +110,10 @@ private RecordingStream createRecordingStream(RecordingConfig config) {
80
110
recordingStream .enable (SUBMIT_FAILED_EVENT );
81
111
recordingStream .setMaxAge (config .maxAge );
82
112
recordingStream .setMaxSize (config .maxSizeBytes );
113
+ if (activeMetricEnabled ) {
114
+ recordingStream .enable (START_EVENT );
115
+ recordingStream .enable (END_EVENT );
116
+ }
83
117
recordingStream .startAsync ();
84
118
85
119
return recordingStream ;
@@ -90,12 +124,16 @@ public void close() {
90
124
recordingStream .close ();
91
125
}
92
126
93
- private record RecordingConfig (Duration maxAge , long maxSizeBytes , Duration pinnedThreshold ) {
127
+ public void setActiveMetricEnabled (boolean activeMetricEnabled ) {
128
+ this .activeMetricEnabled = activeMetricEnabled ;
129
+ }
130
+
131
+ public record RecordingConfig (Duration maxAge , long maxSizeBytes , Duration pinnedThreshold ) {
94
132
private RecordingConfig () {
95
133
this (Duration .ofSeconds (5 ), 10L * 1024 * 1024 , Duration .ofMillis (20 ));
96
134
}
97
135
98
- private RecordingConfig {
136
+ public RecordingConfig {
99
137
Objects .requireNonNull (maxAge , "maxAge parameter must not be null" );
100
138
Objects .requireNonNull (pinnedThreshold , "pinnedThreshold must not be null" );
101
139
if (maxSizeBytes < 0 ) {
0 commit comments