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