diff --git a/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/ThreadExports.java b/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/ThreadExports.java index 12ebccad0..2128310b8 100644 --- a/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/ThreadExports.java +++ b/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/ThreadExports.java @@ -5,9 +5,13 @@ import io.prometheus.client.GaugeMetricFamily; import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Exports metrics about JVM thread areas. @@ -73,6 +77,40 @@ void addThreadMetrics(List sampleFamilies) { "jvm_threads_deadlocked_monitor", "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors", nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads()))); + + GaugeMetricFamily threadStateFamily = new GaugeMetricFamily( + "jvm_threads_state", + "Current count of threads by state", + Collections.singletonList("state")); + + Map threadStateCounts = getThreadStateCountMap(); + for (Map.Entry entry : threadStateCounts.entrySet()) { + threadStateFamily.addMetric( + Collections.singletonList(entry.getKey().toString()), + entry.getValue() + ); + } + } + + private Map getThreadStateCountMap() { + // Get thread information without computing any stack traces + ThreadInfo[] allThreads = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 0); + + // Initialize the map with all thread states + HashMap threadCounts = new HashMap(); + for (Thread.State state : Thread.State.values()) { + threadCounts.put(state, 0); + } + + // Collect the actual thread counts + for (ThreadInfo curThread : allThreads) { + if (curThread != null) { + Thread.State threadState = curThread.getThreadState(); + threadCounts.put(threadState, threadCounts.get(threadState) + 1); + } + } + + return threadCounts; } private static double nullSafeArrayLength(long[] array) { diff --git a/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/ThreadExportsTest.java b/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/ThreadExportsTest.java index 78c9951db..47397c12b 100644 --- a/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/ThreadExportsTest.java +++ b/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/ThreadExportsTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import org.mockito.Mockito; +import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import static org.junit.Assert.assertEquals; @@ -26,6 +27,8 @@ public void setUp() { when(mockThreadsBean.getTotalStartedThreadCount()).thenReturn(503L); when(mockThreadsBean.findDeadlockedThreads()).thenReturn(new long[]{1L,2L,3L}); when(mockThreadsBean.findMonitorDeadlockedThreads()).thenReturn(new long[]{2L,3L,4L}); + when(mockThreadsBean.getAllThreadIds()).thenReturn(new long[]{3L,4L,5L}); + when(mockThreadsBean.getThreadInfo(new long[]{3L,4L,5L}, 0)).thenReturn(new ThreadInfo[] {}); collectorUnderTest = new ThreadExports(mockThreadsBean).register(registry); }