-
Notifications
You must be signed in to change notification settings - Fork 830
Add GC duration histogram #1738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b732151
7f5ad1b
42cf5ef
042e03c
e5cc0c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| package io.prometheus.metrics.instrumentation.jvm; | ||
|
|
||
| import static com.sun.management.GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION; | ||
| import static io.prometheus.metrics.instrumentation.jvm.TestUtil.convertToOpenMetricsFormat; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.mockito.ArgumentCaptor.forClass; | ||
| import static org.mockito.Mockito.*; | ||
| import static org.mockito.Mockito.times; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
@@ -12,15 +15,22 @@ | |
| import java.io.IOException; | ||
| import java.lang.management.GarbageCollectorMXBean; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.concurrent.TimeUnit; | ||
| import javax.management.Notification; | ||
| import javax.management.NotificationEmitter; | ||
| import javax.management.NotificationListener; | ||
| import javax.management.openmbean.*; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.mockito.Mockito; | ||
| import org.mockito.ArgumentCaptor; | ||
|
|
||
| class JvmGarbageCollectorMetricsTest { | ||
|
|
||
| private final GarbageCollectorMXBean mockGcBean1 = Mockito.mock(GarbageCollectorMXBean.class); | ||
| private final GarbageCollectorMXBean mockGcBean2 = Mockito.mock(GarbageCollectorMXBean.class); | ||
| private final GarbageCollectorMXBean mockGcBean1 = mock(GarbageCollectorMXBean.class); | ||
| private final GarbageCollectorMXBean mockGcBean2 = mock(GarbageCollectorMXBean.class); | ||
|
|
||
| @BeforeEach | ||
| public void setUp() { | ||
|
|
@@ -58,7 +68,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") | ||
zeitlinger marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also test label values
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a test that simulates a GC notification and asserts the output ( |
||
| .build(); | ||
|
Comment on lines
+71
to
+73
|
||
|
|
||
| PrometheusRegistry registry = new PrometheusRegistry(); | ||
| JvmGarbageCollectorMetrics.builder() | ||
|
|
@@ -70,4 +82,124 @@ public void testIgnoredMetricNotScraped() { | |
| verify(mockGcBean1, times(0)).getCollectionCount(); | ||
| assertThat(snapshots.size()).isZero(); | ||
| } | ||
|
|
||
| @Test | ||
| @SuppressWarnings("rawtypes") | ||
| public void testGCDurationHistogramLabels() throws Exception { | ||
| GarbageCollectorMXBean mockGcBean = | ||
| mock( | ||
| GarbageCollectorMXBean.class, | ||
| withSettings().extraInterfaces(NotificationEmitter.class)); | ||
| when(mockGcBean.getName()).thenReturn("MyGC"); | ||
|
|
||
| PrometheusRegistry registry = new PrometheusRegistry(); | ||
| JvmGarbageCollectorMetrics.builder() | ||
| .garbageCollectorBeans(Collections.singletonList(mockGcBean)) | ||
| .register(registry); | ||
|
|
||
| NotificationListener listener; | ||
| ArgumentCaptor<NotificationListener> captor = forClass(NotificationListener.class); | ||
| verify((NotificationEmitter) mockGcBean) | ||
| .addNotificationListener(captor.capture(), isNull(), isNull()); | ||
| listener = captor.getValue(); | ||
|
|
||
| TabularType memoryTabularType = getMemoryTabularType(); | ||
| TabularData memoryBefore = new TabularDataSupport(memoryTabularType); | ||
| TabularData memoryAfter = new TabularDataSupport(memoryTabularType); | ||
|
|
||
| CompositeType gcInfoType = | ||
| new CompositeType( | ||
| "sun.management.BaseGcInfoCompositeType", | ||
| "gcInfo", | ||
| new String[] { | ||
| "id", "startTime", "endTime", "duration", "memoryUsageBeforeGc", "memoryUsageAfterGc" | ||
| }, | ||
| new String[] { | ||
| "id", "startTime", "endTime", "duration", "memoryUsageBeforeGc", "memoryUsageAfterGc" | ||
| }, | ||
| new OpenType[] { | ||
| SimpleType.LONG, | ||
| SimpleType.LONG, | ||
| SimpleType.LONG, | ||
| SimpleType.LONG, | ||
| memoryTabularType, | ||
| memoryTabularType | ||
| }); | ||
|
|
||
| java.util.Map<String, Object> gcInfoMap = new HashMap<>(); | ||
| gcInfoMap.put("id", 0L); | ||
| gcInfoMap.put("startTime", 100L); | ||
| gcInfoMap.put("endTime", 200L); | ||
| gcInfoMap.put("duration", 100L); | ||
| gcInfoMap.put("memoryUsageBeforeGc", memoryBefore); | ||
| gcInfoMap.put("memoryUsageAfterGc", memoryAfter); | ||
|
|
||
| CompositeData notificationData = getGcNotificationData(gcInfoType, gcInfoMap); | ||
|
|
||
| Notification notification = | ||
| new Notification( | ||
| GARBAGE_COLLECTION_NOTIFICATION, mockGcBean, 1, System.currentTimeMillis(), "gc"); | ||
| notification.setUserData(notificationData); | ||
|
|
||
| listener.handleNotification(notification, null); | ||
|
|
||
| MetricSnapshots snapshots = registry.scrape(); | ||
|
|
||
| String expected = | ||
| """ | ||
| {"jvm.gc.duration_bucket","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC",le="0.01"} 0 | ||
| {"jvm.gc.duration_bucket","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC",le="0.1"} 1 | ||
| {"jvm.gc.duration_bucket","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC",le="1.0"} 1 | ||
| {"jvm.gc.duration_bucket","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC",le="10.0"} 1 | ||
| {"jvm.gc.duration_bucket","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC",le="+Inf"} 1 | ||
| {"jvm.gc.duration_count","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC"} 1 | ||
| {"jvm.gc.duration_sum","jvm.gc.action"="end of minor GC","jvm.gc.cause"="testCause","jvm.gc.name"="MyGC"} 0.1 | ||
| """; | ||
|
|
||
| String metrics = convertToOpenMetricsFormat(snapshots); | ||
|
|
||
| assertThat(metrics).contains(expected); | ||
| } | ||
|
|
||
| private TabularType getMemoryTabularType() throws OpenDataException { | ||
| CompositeType memoryUsageType = | ||
| new CompositeType( | ||
| "java.lang.management.MemoryUsage", | ||
| "MemoryUsage", | ||
| new String[] {"init", "used", "committed", "max"}, | ||
| new String[] {"init", "used", "committed", "max"}, | ||
| new OpenType[] {SimpleType.LONG, SimpleType.LONG, SimpleType.LONG, SimpleType.LONG}); | ||
|
|
||
| CompositeType memoryUsageEntryType = | ||
| new CompositeType( | ||
| "memoryUsageEntry", | ||
| "memoryUsageEntry", | ||
| new String[] {"key", "value"}, | ||
| new String[] {"key", "value"}, | ||
| new OpenType[] {SimpleType.STRING, memoryUsageType}); | ||
|
|
||
| return new TabularType( | ||
| "memoryUsageTabular", "memoryUsageTabular", memoryUsageEntryType, new String[] {"key"}); | ||
| } | ||
|
|
||
| private static CompositeData getGcNotificationData( | ||
| CompositeType gcInfoType, Map<String, Object> gcInfoMap) throws OpenDataException { | ||
| CompositeData gcInfoData = new CompositeDataSupport(gcInfoType, gcInfoMap); | ||
|
|
||
| CompositeType notificationType = | ||
| new CompositeType( | ||
| "sun.management.BaseGarbageCollectionNotifInfoCompositeType", | ||
| "GarbageCollectionNotificationInfo", | ||
| new String[] {"gcAction", "gcName", "gcCause", "gcInfo"}, | ||
| new String[] {"gcAction", "gcName", "gcCause", "gcInfo"}, | ||
| new OpenType[] {SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, gcInfoType}); | ||
|
|
||
| Map<String, Object> notifMap = new HashMap<>(); | ||
| notifMap.put("gcAction", "end of minor GC"); | ||
| notifMap.put("gcName", "MyGC"); | ||
| notifMap.put("gcCause", "testCause"); | ||
| notifMap.put("gcInfo", gcInfoData); | ||
|
|
||
| return new CompositeDataSupport(notificationType, notifMap); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The histogram should include
.unit(Unit.SECONDS)to be consistent with other time-based metrics in this codebase. This is present injvm_gc_collection_seconds(line 62) and other time-based metrics likeprocess_cpu_seconds_totalandjvm_compilation_time_seconds_total.