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