55
66package io .opentelemetry .instrumentation .runtimemetrics .java8 ;
77
8+ import static io .opentelemetry .api .common .AttributeKey .stringKey ;
9+ import static java .util .Arrays .asList ;
810import static java .util .Collections .emptyList ;
11+ import static java .util .Collections .unmodifiableList ;
912
1013import com .sun .management .GarbageCollectionNotificationInfo ;
1114import io .opentelemetry .api .OpenTelemetry ;
1215import io .opentelemetry .api .common .AttributeKey ;
1316import io .opentelemetry .api .common .Attributes ;
1417import io .opentelemetry .api .metrics .DoubleHistogram ;
15- import io .opentelemetry .api .metrics .DoubleHistogramBuilder ;
1618import io .opentelemetry .api .metrics .Meter ;
17- import io .opentelemetry .extension . incubator . metrics . ExtendedDoubleHistogramBuilder ;
19+ import io .opentelemetry .instrumentation . api . internal . SemconvStability ;
1820import io .opentelemetry .instrumentation .runtimemetrics .java8 .internal .JmxRuntimeMetricsUtil ;
1921import java .lang .management .GarbageCollectorMXBean ;
2022import java .lang .management .ManagementFactory ;
2426import java .util .concurrent .TimeUnit ;
2527import java .util .function .Function ;
2628import java .util .logging .Logger ;
29+ import javax .annotation .Nullable ;
2730import javax .management .Notification ;
2831import javax .management .NotificationEmitter ;
2932import javax .management .NotificationFilter ;
3841 * <pre>{@code
3942 * GarbageCollector.registerObservers(GlobalOpenTelemetry.get());
4043 * }</pre>
44+ *
45+ * <p>Example metrics being exported:
46+ *
47+ * <pre>
48+ * process.runtime.jvm.gc.duration{gc="G1 Young Generation",action="end of minor GC"} 0.022
49+ * </pre>
50+ *
51+ * <p>In case you enable the preview of stable JVM semantic conventions (e.g. by setting the {@code
52+ * otel.semconv-stability.opt-in} system property to {@code jvm}), the metrics being exported will
53+ * follow <a
54+ * href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/jvm-metrics.md">the
55+ * most recent JVM semantic conventions</a>. This is how the example above looks when stable JVM
56+ * semconv is enabled:
57+ *
58+ * <pre>
59+ * jvm.gc.duration{jvm.gc.name="G1 Young Generation",jvm.gc.action="end of minor GC"} 0.022
60+ * </pre>
4161 */
4262public final class GarbageCollector {
4363
4464 private static final Logger logger = Logger .getLogger (GarbageCollector .class .getName ());
4565
4666 private static final double MILLIS_PER_S = TimeUnit .SECONDS .toMillis (1 );
4767
48- private static final AttributeKey <String > GC_KEY = AttributeKey .stringKey ("gc" );
49- private static final AttributeKey <String > ACTION_KEY = AttributeKey .stringKey ("action" );
68+ private static final AttributeKey <String > GC_KEY = stringKey ("gc" );
69+ private static final AttributeKey <String > ACTION_KEY = stringKey ("action" );
70+
71+ // TODO: use the opentelemetry-semconv classes once we have metrics attributes there
72+ private static final AttributeKey <String > JVM_GC_NAME = stringKey ("jvm.gc.name" );
73+ private static final AttributeKey <String > JVM_GC_ACTION = stringKey ("jvm.gc.action" );
74+ static final List <Double > GC_DURATION_BUCKETS = unmodifiableList (asList (0.01 , 0.1 , 1. , 10. ));
5075
5176 private static final NotificationFilter GC_FILTER =
5277 notification ->
@@ -76,13 +101,27 @@ static List<AutoCloseable> registerObservers(
76101 Function <Notification , GarbageCollectionNotificationInfo > notificationInfoExtractor ) {
77102 Meter meter = JmxRuntimeMetricsUtil .getMeter (openTelemetry );
78103
79- DoubleHistogramBuilder gcDurationBuilder =
80- meter
81- .histogramBuilder ("process.runtime.jvm.gc.duration" )
82- .setDescription ("Duration of JVM garbage collection actions" )
83- .setUnit ("s" );
84- setGcDurationBuckets (gcDurationBuilder );
85- DoubleHistogram gcDuration = gcDurationBuilder .build ();
104+ DoubleHistogram oldGcDuration = null ;
105+ DoubleHistogram stableGcDuration = null ;
106+
107+ if (SemconvStability .emitOldJvmSemconv ()) {
108+ oldGcDuration =
109+ meter
110+ .histogramBuilder ("process.runtime.jvm.gc.duration" )
111+ .setDescription ("Duration of JVM garbage collection actions" )
112+ .setUnit ("s" )
113+ .setExplicitBucketBoundariesAdvice (emptyList ())
114+ .build ();
115+ }
116+ if (SemconvStability .emitStableJvmSemconv ()) {
117+ stableGcDuration =
118+ meter
119+ .histogramBuilder ("jvm.gc.duration" )
120+ .setDescription ("Duration of JVM garbage collection actions." )
121+ .setUnit ("s" )
122+ .setExplicitBucketBoundariesAdvice (GC_DURATION_BUCKETS )
123+ .build ();
124+ }
86125
87126 List <AutoCloseable > result = new ArrayList <>();
88127 for (GarbageCollectorMXBean gcBean : gcBeans ) {
@@ -91,42 +130,45 @@ static List<AutoCloseable> registerObservers(
91130 }
92131 NotificationEmitter notificationEmitter = (NotificationEmitter ) gcBean ;
93132 GcNotificationListener listener =
94- new GcNotificationListener (gcDuration , notificationInfoExtractor );
133+ new GcNotificationListener (oldGcDuration , stableGcDuration , notificationInfoExtractor );
95134 notificationEmitter .addNotificationListener (listener , GC_FILTER , null );
96135 result .add (() -> notificationEmitter .removeNotificationListener (listener ));
97136 }
98137 return result ;
99138 }
100139
101- private static void setGcDurationBuckets (DoubleHistogramBuilder builder ) {
102- if (!(builder instanceof ExtendedDoubleHistogramBuilder )) {
103- // that shouldn't really happen
104- return ;
105- }
106- ((ExtendedDoubleHistogramBuilder ) builder ).setExplicitBucketBoundariesAdvice (emptyList ());
107- }
108-
109140 private static final class GcNotificationListener implements NotificationListener {
110141
111- private final DoubleHistogram gcDuration ;
142+ @ Nullable private final DoubleHistogram oldGcDuration ;
143+ @ Nullable private final DoubleHistogram stableGcDuration ;
112144 private final Function <Notification , GarbageCollectionNotificationInfo >
113145 notificationInfoExtractor ;
114146
115147 private GcNotificationListener (
116- DoubleHistogram gcDuration ,
148+ @ Nullable DoubleHistogram oldGcDuration ,
149+ @ Nullable DoubleHistogram stableGcDuration ,
117150 Function <Notification , GarbageCollectionNotificationInfo > notificationInfoExtractor ) {
118- this .gcDuration = gcDuration ;
151+ this .oldGcDuration = oldGcDuration ;
152+ this .stableGcDuration = stableGcDuration ;
119153 this .notificationInfoExtractor = notificationInfoExtractor ;
120154 }
121155
122156 @ Override
123157 public void handleNotification (Notification notification , Object unused ) {
124158 GarbageCollectionNotificationInfo notificationInfo =
125159 notificationInfoExtractor .apply (notification );
126- gcDuration .record (
127- notificationInfo .getGcInfo ().getDuration () / MILLIS_PER_S ,
128- Attributes .of (
129- GC_KEY , notificationInfo .getGcName (), ACTION_KEY , notificationInfo .getGcAction ()));
160+
161+ String gcName = notificationInfo .getGcName ();
162+ String gcAction = notificationInfo .getGcAction ();
163+ double duration = notificationInfo .getGcInfo ().getDuration () / MILLIS_PER_S ;
164+
165+ if (oldGcDuration != null ) {
166+ oldGcDuration .record (duration , Attributes .of (GC_KEY , gcName , ACTION_KEY , gcAction ));
167+ }
168+ if (stableGcDuration != null ) {
169+ stableGcDuration .record (
170+ duration , Attributes .of (JVM_GC_NAME , gcName , JVM_GC_ACTION , gcAction ));
171+ }
130172 }
131173 }
132174
0 commit comments