From b732151955ed453532f9c5cad43a552318caa76b Mon Sep 17 00:00:00 2001 From: gniadeck <77535280+gniadeck@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:13:45 +0100 Subject: [PATCH 1/4] Add GC pause duration histogram Signed-off-by: gniadeck <77535280+gniadeck@users.noreply.github.com> --- .../jvm/JvmGarbageCollectorMetrics.java | 46 +++++++++++++++++++ .../jvm/JvmGarbageCollectorMetricsTest.java | 4 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java index 262e2df5f..8ac2d7c58 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java @@ -1,6 +1,8 @@ package io.prometheus.metrics.instrumentation.jvm; +import com.sun.management.GarbageCollectionNotificationInfo; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.core.metrics.Histogram; import io.prometheus.metrics.core.metrics.SummaryWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.Quantiles; @@ -9,6 +11,8 @@ import java.lang.management.ManagementFactory; import java.util.List; import javax.annotation.Nullable; +import javax.management.NotificationEmitter; +import javax.management.openmbean.CompositeData; /** * JVM Garbage Collector metrics. The {@link JvmGarbageCollectorMetrics} are registered as part of @@ -39,6 +43,7 @@ public class JvmGarbageCollectorMetrics { private static final String JVM_GC_COLLECTION_SECONDS = "jvm_gc_collection_seconds"; + private static final String JVM_GC_DURATION_SECONDS = "jvm_gc_duration_seconds"; private final PrometheusProperties config; private final List garbageCollectorBeans; @@ -67,6 +72,47 @@ private void register(PrometheusRegistry registry) { } }) .register(registry); + + registerGCDurationHistogram(registry); + } + + private void registerGCDurationHistogram(PrometheusRegistry registry) { + double[] buckets = {0.01, 0.1, 1, 10}; + + Histogram gcDurationHistogram = + Histogram.builder(config) + .name(JVM_GC_DURATION_SECONDS) + .help("JVM GC pause duration histogram.") + .unit(Unit.SECONDS) + .labelNames("gc", "action", "cause") + .classicUpperBounds(buckets) + .register(registry); + + for (GarbageCollectorMXBean gcBean : garbageCollectorBeans) { + + if (!(gcBean instanceof NotificationEmitter)) { + continue; + } + + NotificationEmitter notificationEmitter = (NotificationEmitter) gcBean; + + notificationEmitter.addNotificationListener( + (notification, handback) -> { + if (!GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION.equals( + notification.getType())) { + return; + } + + GarbageCollectionNotificationInfo info = + GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); + + gcDurationHistogram + .labelValues(info.getGcName(), info.getGcAction(), info.getGcCause()) + .observe(Unit.millisToSeconds(info.getGcInfo().getDuration())); + }, + null, + null); + } } public static Builder builder() { diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java index 177f29d2e..e631574e3 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java @@ -58,7 +58,9 @@ public void testGoodCase() throws IOException { @Test public void testIgnoredMetricNotScraped() { MetricNameFilter filter = - MetricNameFilter.builder().nameMustNotBeEqualTo("jvm_gc_collection_seconds").build(); + MetricNameFilter.builder() + .nameMustNotBeEqualTo("jvm_gc_collection_seconds", "jvm_gc_duration_seconds") + .build(); PrometheusRegistry registry = new PrometheusRegistry(); JvmGarbageCollectorMetrics.builder() From 7f5ad1ba91b4c59eea1e276c3d37ff108bbcb78e Mon Sep 17 00:00:00 2001 From: gniadeck <77535280+gniadeck@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:19:40 +0100 Subject: [PATCH 2/4] align gc label name with opentelemetry spec Signed-off-by: gniadeck <77535280+gniadeck@users.noreply.github.com> --- .../metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java index 8ac2d7c58..83bcedad9 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java @@ -84,7 +84,7 @@ private void registerGCDurationHistogram(PrometheusRegistry registry) { .name(JVM_GC_DURATION_SECONDS) .help("JVM GC pause duration histogram.") .unit(Unit.SECONDS) - .labelNames("gc", "action", "cause") + .labelNames("name", "action", "cause") .classicUpperBounds(buckets) .register(registry); From 42cf5ef553a3a327d1d5b0c56255e5f43457094e Mon Sep 17 00:00:00 2001 From: gniadeck <77535280+gniadeck@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:23:53 +0100 Subject: [PATCH 3/4] simplification, spotless fixes Signed-off-by: gniadeck <77535280+gniadeck@users.noreply.github.com> --- .../jvm/JvmGarbageCollectorMetrics.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java index 83bcedad9..bd58ab862 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java @@ -94,24 +94,24 @@ private void registerGCDurationHistogram(PrometheusRegistry registry) { continue; } - NotificationEmitter notificationEmitter = (NotificationEmitter) gcBean; - - notificationEmitter.addNotificationListener( - (notification, handback) -> { - if (!GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION.equals( - notification.getType())) { - return; - } - - GarbageCollectionNotificationInfo info = - GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); - - gcDurationHistogram - .labelValues(info.getGcName(), info.getGcAction(), info.getGcCause()) - .observe(Unit.millisToSeconds(info.getGcInfo().getDuration())); - }, - null, - null); + ((NotificationEmitter) gcBean) + .addNotificationListener( + (notification, handback) -> { + if (!GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION.equals( + notification.getType())) { + return; + } + + GarbageCollectionNotificationInfo info = + GarbageCollectionNotificationInfo.from( + (CompositeData) notification.getUserData()); + + gcDurationHistogram + .labelValues(info.getGcName(), info.getGcAction(), info.getGcCause()) + .observe(Unit.millisToSeconds(info.getGcInfo().getDuration())); + }, + null, + null); } } From 042e03ce28cad1555a8baa2b1d7a51982bf9e7c5 Mon Sep 17 00:00:00 2001 From: gniadeck <77535280+gniadeck@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:56:42 +0100 Subject: [PATCH 4/4] align naming with opentelemetry semantic conventions Signed-off-by: gniadeck <77535280+gniadeck@users.noreply.github.com> --- .../jvm/JvmGarbageCollectorMetrics.java | 11 +++++------ .../jvm/JvmGarbageCollectorMetricsTest.java | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java index bd58ab862..ab36ad40d 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java @@ -43,7 +43,7 @@ public class JvmGarbageCollectorMetrics { private static final String JVM_GC_COLLECTION_SECONDS = "jvm_gc_collection_seconds"; - private static final String JVM_GC_DURATION_SECONDS = "jvm_gc_duration_seconds"; + private static final String JVM_GC_DURATION = "jvm_gc_duration"; private final PrometheusProperties config; private final List garbageCollectorBeans; @@ -81,10 +81,9 @@ private void registerGCDurationHistogram(PrometheusRegistry registry) { Histogram gcDurationHistogram = Histogram.builder(config) - .name(JVM_GC_DURATION_SECONDS) - .help("JVM GC pause duration histogram.") - .unit(Unit.SECONDS) - .labelNames("name", "action", "cause") + .name(JVM_GC_DURATION) + .help("Duration of JVM garbage collection actions.") + .labelNames("jvm_gc_action", "jvm_gc_name", "jvm_gc_cause") .classicUpperBounds(buckets) .register(registry); @@ -107,7 +106,7 @@ private void registerGCDurationHistogram(PrometheusRegistry registry) { (CompositeData) notification.getUserData()); gcDurationHistogram - .labelValues(info.getGcName(), info.getGcAction(), info.getGcCause()) + .labelValues(info.getGcAction(), info.getGcName(), info.getGcCause()) .observe(Unit.millisToSeconds(info.getGcInfo().getDuration())); }, null, diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java index e631574e3..8d8564996 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java @@ -59,7 +59,7 @@ public void testGoodCase() throws IOException { public void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder() - .nameMustNotBeEqualTo("jvm_gc_collection_seconds", "jvm_gc_duration_seconds") + .nameMustNotBeEqualTo("jvm_gc_collection_seconds", "jvm_gc_duration") .build(); PrometheusRegistry registry = new PrometheusRegistry();