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