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.Predicate; 007 008import java.lang.management.ManagementFactory; 009import java.lang.management.ThreadInfo; 010import java.lang.management.ThreadMXBean; 011import java.util.ArrayList; 012import java.util.Arrays; 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 public static final String UNKNOWN = "UNKNOWN"; 040 041 public static final String JVM_THREADS_STATE = "jvm_threads_state"; 042 043 private static final String JVM_THREADS_CURRENT = "jvm_threads_current"; 044 private static final String JVM_THREADS_DAEMON = "jvm_threads_daemon"; 045 private static final String JVM_THREADS_PEAK = "jvm_threads_peak"; 046 private static final String JVM_THREADS_STARTED_TOTAL = "jvm_threads_started_total"; 047 private static final String JVM_THREADS_DEADLOCKED = "jvm_threads_deadlocked"; 048 private static final String JVM_THREADS_DEADLOCKED_MONITOR = "jvm_threads_deadlocked_monitor"; 049 050 private final ThreadMXBean threadBean; 051 052 public ThreadExports() { 053 this(ManagementFactory.getThreadMXBean()); 054 } 055 056 public ThreadExports(ThreadMXBean threadBean) { 057 this.threadBean = threadBean; 058 } 059 060 void addThreadMetrics(List<MetricFamilySamples> sampleFamilies, Predicate<String> nameFilter) { 061 if (nameFilter.test(JVM_THREADS_CURRENT)) { 062 sampleFamilies.add( 063 new GaugeMetricFamily( 064 JVM_THREADS_CURRENT, 065 "Current thread count of a JVM", 066 threadBean.getThreadCount())); 067 } 068 069 if (nameFilter.test(JVM_THREADS_DAEMON)) { 070 sampleFamilies.add( 071 new GaugeMetricFamily( 072 JVM_THREADS_DAEMON, 073 "Daemon thread count of a JVM", 074 threadBean.getDaemonThreadCount())); 075 } 076 077 if (nameFilter.test(JVM_THREADS_PEAK)) { 078 sampleFamilies.add( 079 new GaugeMetricFamily( 080 JVM_THREADS_PEAK, 081 "Peak thread count of a JVM", 082 threadBean.getPeakThreadCount())); 083 } 084 085 if (nameFilter.test(JVM_THREADS_STARTED_TOTAL)) { 086 sampleFamilies.add( 087 new CounterMetricFamily( 088 JVM_THREADS_STARTED_TOTAL, 089 "Started thread count of a JVM", 090 threadBean.getTotalStartedThreadCount())); 091 } 092 093 if (nameFilter.test(JVM_THREADS_DEADLOCKED)) { 094 sampleFamilies.add( 095 new GaugeMetricFamily( 096 JVM_THREADS_DEADLOCKED, 097 "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or ownable synchronizers", 098 nullSafeArrayLength(threadBean.findDeadlockedThreads()))); 099 } 100 101 if (nameFilter.test(JVM_THREADS_DEADLOCKED_MONITOR)) { 102 sampleFamilies.add( 103 new GaugeMetricFamily( 104 JVM_THREADS_DEADLOCKED_MONITOR, 105 "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors", 106 nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads()))); 107 } 108 109 if (nameFilter.test(JVM_THREADS_STATE)) { 110 GaugeMetricFamily threadStateFamily = new GaugeMetricFamily( 111 JVM_THREADS_STATE, 112 "Current count of threads by state", 113 Collections.singletonList("state")); 114 115 Map<String, Integer> threadStateCounts = getThreadStateCountMap(); 116 for (Map.Entry<String, Integer> entry : threadStateCounts.entrySet()) { 117 threadStateFamily.addMetric( 118 Collections.singletonList(entry.getKey()), 119 entry.getValue() 120 ); 121 } 122 sampleFamilies.add(threadStateFamily); 123 } 124 } 125 126 private Map<String, Integer> getThreadStateCountMap() { 127 long[] threadIds = threadBean.getAllThreadIds(); 128 129 // Code to remove any thread id values <= 0 130 int writePos = 0; 131 for (int i = 0; i < threadIds.length; i++) { 132 if (threadIds[i] > 0) { 133 threadIds[writePos++] = threadIds[i]; 134 } 135 } 136 137 int numberOfInvalidThreadIds = threadIds.length - writePos; 138 threadIds = Arrays.copyOf(threadIds, writePos); 139 140 // Get thread information without computing any stack traces 141 ThreadInfo[] allThreads = threadBean.getThreadInfo(threadIds, 0); 142 143 // Initialize the map with all thread states 144 HashMap<String, Integer> threadCounts = new HashMap<String, Integer>(); 145 for (Thread.State state : Thread.State.values()) { 146 threadCounts.put(state.name(), 0); 147 } 148 149 // Collect the actual thread counts 150 for (ThreadInfo curThread : allThreads) { 151 if (curThread != null) { 152 Thread.State threadState = curThread.getThreadState(); 153 threadCounts.put(threadState.name(), threadCounts.get(threadState.name()) + 1); 154 } 155 } 156 157 // Add the thread count for invalid thread ids 158 threadCounts.put(UNKNOWN, numberOfInvalidThreadIds); 159 160 return threadCounts; 161 } 162 163 private static double nullSafeArrayLength(long[] array) { 164 return null == array ? 0 : array.length; 165 } 166 167 @Override 168 public List<MetricFamilySamples> collect() { 169 return collect(null); 170 } 171 172 @Override 173 public List<MetricFamilySamples> collect(Predicate<String> nameFilter) { 174 List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>(); 175 addThreadMetrics(mfs, nameFilter == null ? ALLOW_ALL : nameFilter); 176 return mfs; 177 } 178}