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}