001package io.prometheus.client.hotspot; 002 003import io.prometheus.client.Collector; 004import io.prometheus.client.CounterMetricFamily; 005import io.prometheus.client.GaugeMetricFamily; 006 007import java.lang.management.ManagementFactory; 008import java.lang.management.ThreadInfo; 009import java.lang.management.ThreadMXBean; 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.HashMap; 013import java.util.List; 014import java.util.Map; 015 016/** 017 * Exports metrics about JVM thread areas. 018 * <p> 019 * Example usage: 020 * <pre> 021 * {@code 022 * new ThreadExports().register(); 023 * } 024 * </pre> 025 * Example metrics being exported: 026 * <pre> 027 * jvm_threads_current{} 300 028 * jvm_threads_daemon{} 200 029 * jvm_threads_peak{} 410 030 * jvm_threads_started_total{} 1200 031 * </pre> 032 */ 033public class ThreadExports extends Collector { 034 private final ThreadMXBean threadBean; 035 036 public ThreadExports() { 037 this(ManagementFactory.getThreadMXBean()); 038 } 039 040 public ThreadExports(ThreadMXBean threadBean) { 041 this.threadBean = threadBean; 042 } 043 044 void addThreadMetrics(List<MetricFamilySamples> sampleFamilies) { 045 sampleFamilies.add( 046 new GaugeMetricFamily( 047 "jvm_threads_current", 048 "Current thread count of a JVM", 049 threadBean.getThreadCount())); 050 051 sampleFamilies.add( 052 new GaugeMetricFamily( 053 "jvm_threads_daemon", 054 "Daemon thread count of a JVM", 055 threadBean.getDaemonThreadCount())); 056 057 sampleFamilies.add( 058 new GaugeMetricFamily( 059 "jvm_threads_peak", 060 "Peak thread count of a JVM", 061 threadBean.getPeakThreadCount())); 062 063 sampleFamilies.add( 064 new CounterMetricFamily( 065 "jvm_threads_started_total", 066 "Started thread count of a JVM", 067 threadBean.getTotalStartedThreadCount())); 068 069 sampleFamilies.add( 070 new GaugeMetricFamily( 071 "jvm_threads_deadlocked", 072 "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or ownable synchronizers", 073 nullSafeArrayLength(threadBean.findDeadlockedThreads()))); 074 075 sampleFamilies.add( 076 new GaugeMetricFamily( 077 "jvm_threads_deadlocked_monitor", 078 "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors", 079 nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads()))); 080 081 GaugeMetricFamily threadStateFamily = new GaugeMetricFamily( 082 "jvm_threads_state", 083 "Current count of threads by state", 084 Collections.singletonList("state")); 085 086 Map<Thread.State, Integer> threadStateCounts = getThreadStateCountMap(); 087 for (Map.Entry<Thread.State, Integer> entry : threadStateCounts.entrySet()) { 088 threadStateFamily.addMetric( 089 Collections.singletonList(entry.getKey().toString()), 090 entry.getValue() 091 ); 092 } 093 sampleFamilies.add(threadStateFamily); 094 } 095 096 private Map<Thread.State, Integer> getThreadStateCountMap() { 097 // Get thread information without computing any stack traces 098 ThreadInfo[] allThreads = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 0); 099 100 // Initialize the map with all thread states 101 HashMap<Thread.State, Integer> threadCounts = new HashMap<Thread.State, Integer>(); 102 for (Thread.State state : Thread.State.values()) { 103 threadCounts.put(state, 0); 104 } 105 106 // Collect the actual thread counts 107 for (ThreadInfo curThread : allThreads) { 108 if (curThread != null) { 109 Thread.State threadState = curThread.getThreadState(); 110 threadCounts.put(threadState, threadCounts.get(threadState) + 1); 111 } 112 } 113 114 return threadCounts; 115 } 116 117 private static double nullSafeArrayLength(long[] array) { 118 return null == array ? 0 : array.length; 119 } 120 121 public List<MetricFamilySamples> collect() { 122 List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>(); 123 addThreadMetrics(mfs); 124 return mfs; 125 } 126}