001package io.prometheus.metrics.instrumentation.jvm; 002 003import static java.util.Objects.requireNonNull; 004 005import io.prometheus.metrics.config.PrometheusProperties; 006import io.prometheus.metrics.core.metrics.CounterWithCallback; 007import io.prometheus.metrics.core.metrics.GaugeWithCallback; 008import io.prometheus.metrics.model.registry.PrometheusRegistry; 009import java.lang.management.ManagementFactory; 010import java.lang.management.ThreadInfo; 011import java.lang.management.ThreadMXBean; 012import java.util.Arrays; 013import java.util.HashMap; 014import java.util.Map; 015import javax.annotation.Nullable; 016 017/** 018 * JVM Thread metrics. The {@link JvmThreadsMetrics} are registered as part of the {@link 019 * JvmMetrics} like this: 020 * 021 * <pre>{@code 022 * JvmMetrics.builder().register(); 023 * }</pre> 024 * 025 * However, if you want only the {@link JvmThreadsMetrics} you can also register them directly: 026 * 027 * <pre>{@code 028 * JvmThreadMetrics.builder().register(); 029 * }</pre> 030 * 031 * Example metrics being exported: 032 * 033 * <pre> 034 * # HELP jvm_threads_current Current thread count of a JVM 035 * # TYPE jvm_threads_current gauge 036 * jvm_threads_current 10.0 037 * # HELP jvm_threads_daemon Daemon thread count of a JVM 038 * # TYPE jvm_threads_daemon gauge 039 * jvm_threads_daemon 8.0 040 * # HELP jvm_threads_deadlocked Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or ownable synchronizers 041 * # TYPE jvm_threads_deadlocked gauge 042 * jvm_threads_deadlocked 0.0 043 * # HELP jvm_threads_deadlocked_monitor Cycles of JVM-threads that are in deadlock waiting to acquire object monitors 044 * # TYPE jvm_threads_deadlocked_monitor gauge 045 * jvm_threads_deadlocked_monitor 0.0 046 * # HELP jvm_threads_peak Peak thread count of a JVM 047 * # TYPE jvm_threads_peak gauge 048 * jvm_threads_peak 10.0 049 * # HELP jvm_threads_started_total Started thread count of a JVM 050 * # TYPE jvm_threads_started_total counter 051 * jvm_threads_started_total 10.0 052 * # HELP jvm_threads_state Current count of threads by state 053 * # TYPE jvm_threads_state gauge 054 * jvm_threads_state{state="BLOCKED"} 0.0 055 * jvm_threads_state{state="NEW"} 0.0 056 * jvm_threads_state{state="RUNNABLE"} 5.0 057 * jvm_threads_state{state="TERMINATED"} 0.0 058 * jvm_threads_state{state="TIMED_WAITING"} 2.0 059 * jvm_threads_state{state="UNKNOWN"} 0.0 060 * jvm_threads_state{state="WAITING"} 3.0 061 * </pre> 062 */ 063public class JvmThreadsMetrics { 064 065 private static final String UNKNOWN = "UNKNOWN"; 066 private static final String JVM_THREADS_STATE = "jvm_threads_state"; 067 private static final String JVM_THREADS_CURRENT = "jvm_threads_current"; 068 private static final String JVM_THREADS_DAEMON = "jvm_threads_daemon"; 069 private static final String JVM_THREADS_PEAK = "jvm_threads_peak"; 070 private static final String JVM_THREADS_STARTED_TOTAL = "jvm_threads_started_total"; 071 private static final String JVM_THREADS_DEADLOCKED = "jvm_threads_deadlocked"; 072 private static final String JVM_THREADS_DEADLOCKED_MONITOR = "jvm_threads_deadlocked_monitor"; 073 074 private final PrometheusProperties config; 075 private final ThreadMXBean threadBean; 076 private final boolean isNativeImage; 077 078 private JvmThreadsMetrics( 079 boolean isNativeImage, ThreadMXBean threadBean, PrometheusProperties config) { 080 this.config = config; 081 this.threadBean = threadBean; 082 this.isNativeImage = isNativeImage; 083 } 084 085 private void register(PrometheusRegistry registry) { 086 087 GaugeWithCallback.builder(config) 088 .name(JVM_THREADS_CURRENT) 089 .help("Current thread count of a JVM") 090 .callback(callback -> callback.call(threadBean.getThreadCount())) 091 .register(registry); 092 093 GaugeWithCallback.builder(config) 094 .name(JVM_THREADS_DAEMON) 095 .help("Daemon thread count of a JVM") 096 .callback(callback -> callback.call(threadBean.getDaemonThreadCount())) 097 .register(registry); 098 099 GaugeWithCallback.builder(config) 100 .name(JVM_THREADS_PEAK) 101 .help("Peak thread count of a JVM") 102 .callback(callback -> callback.call(threadBean.getPeakThreadCount())) 103 .register(registry); 104 105 CounterWithCallback.builder(config) 106 .name(JVM_THREADS_STARTED_TOTAL) 107 .help("Started thread count of a JVM") 108 .callback(callback -> callback.call(threadBean.getTotalStartedThreadCount())) 109 .register(registry); 110 111 if (!isNativeImage) { 112 GaugeWithCallback.builder(config) 113 .name(JVM_THREADS_DEADLOCKED) 114 .help( 115 "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or " 116 + "ownable synchronizers") 117 .callback( 118 callback -> callback.call(nullSafeArrayLength(threadBean.findDeadlockedThreads()))) 119 .register(registry); 120 121 GaugeWithCallback.builder(config) 122 .name(JVM_THREADS_DEADLOCKED_MONITOR) 123 .help("Cycles of JVM-threads that are in deadlock waiting to acquire object monitors") 124 .callback( 125 callback -> 126 callback.call(nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads()))) 127 .register(registry); 128 129 GaugeWithCallback.builder(config) 130 .name(JVM_THREADS_STATE) 131 .help("Current count of threads by state") 132 .labelNames("state") 133 .callback( 134 callback -> { 135 Map<String, Integer> threadStateCounts = getThreadStateCountMap(threadBean); 136 for (Map.Entry<String, Integer> entry : threadStateCounts.entrySet()) { 137 callback.call(entry.getValue(), entry.getKey()); 138 } 139 }) 140 .register(registry); 141 } 142 } 143 144 private Map<String, Integer> getThreadStateCountMap(ThreadMXBean threadBean) { 145 long[] threadIds = threadBean.getAllThreadIds(); 146 147 // Code to remove any thread id values <= 0 148 int writePos = 0; 149 for (int i = 0; i < threadIds.length; i++) { 150 if (threadIds[i] > 0) { 151 threadIds[writePos++] = threadIds[i]; 152 } 153 } 154 155 final int numberOfInvalidThreadIds = threadIds.length - writePos; 156 threadIds = Arrays.copyOf(threadIds, writePos); 157 158 // Get thread information without computing any stack traces 159 ThreadInfo[] allThreads = threadBean.getThreadInfo(threadIds, 0); 160 161 // Initialize the map with all thread states 162 Map<String, Integer> threadCounts = new HashMap<>(); 163 for (Thread.State state : Thread.State.values()) { 164 threadCounts.put(state.name(), 0); 165 } 166 167 // Collect the actual thread counts 168 for (ThreadInfo curThread : allThreads) { 169 if (curThread != null) { 170 Thread.State threadState = curThread.getThreadState(); 171 threadCounts.put( 172 threadState.name(), requireNonNull(threadCounts.get(threadState.name())) + 1); 173 } 174 } 175 176 // Add the thread count for invalid thread ids 177 threadCounts.put(UNKNOWN, numberOfInvalidThreadIds); 178 179 return threadCounts; 180 } 181 182 private double nullSafeArrayLength(long[] array) { 183 return null == array ? 0 : array.length; 184 } 185 186 public static Builder builder() { 187 return new Builder(PrometheusProperties.get()); 188 } 189 190 public static Builder builder(PrometheusProperties config) { 191 return new Builder(config); 192 } 193 194 public static class Builder { 195 196 private final PrometheusProperties config; 197 @Nullable private Boolean isNativeImage; 198 @Nullable private ThreadMXBean threadBean; 199 200 private Builder(PrometheusProperties config) { 201 this.config = config; 202 } 203 204 /** Package private. For testing only. */ 205 Builder threadBean(ThreadMXBean threadBean) { 206 this.threadBean = threadBean; 207 return this; 208 } 209 210 /** Package private. For testing only. */ 211 Builder isNativeImage(boolean isNativeImage) { 212 this.isNativeImage = isNativeImage; 213 return this; 214 } 215 216 public void register() { 217 register(PrometheusRegistry.defaultRegistry); 218 } 219 220 public void register(PrometheusRegistry registry) { 221 ThreadMXBean threadBean = 222 this.threadBean != null ? this.threadBean : ManagementFactory.getThreadMXBean(); 223 boolean isNativeImage = 224 this.isNativeImage != null ? this.isNativeImage : NativeImageChecker.isGraalVmNativeImage; 225 new JvmThreadsMetrics(isNativeImage, threadBean, config).register(registry); 226 } 227 } 228}