001package io.prometheus.client.hotspot;
002
003import io.prometheus.client.Collector;
004import io.prometheus.client.CounterMetricFamily;
005import io.prometheus.client.GaugeMetricFamily;
006
007import java.lang.management.ManagementFactory;
008import java.lang.management.ThreadInfo;
009import java.lang.management.ThreadMXBean;
010import java.util.ArrayList;
011import java.util.Collections;
012import java.util.HashMap;
013import java.util.List;
014import java.util.Map;
015
016/**
017 * Exports metrics about JVM thread areas.
018 * <p>
019 * Example usage:
020 * <pre>
021 * {@code
022 *   new ThreadExports().register();
023 * }
024 * </pre>
025 * Example metrics being exported:
026 * <pre>
027 *   jvm_threads_current{} 300
028 *   jvm_threads_daemon{} 200
029 *   jvm_threads_peak{} 410
030 *   jvm_threads_started_total{} 1200
031 * </pre>
032 */
033public class ThreadExports extends Collector {
034  private final ThreadMXBean threadBean;
035
036  public ThreadExports() {
037    this(ManagementFactory.getThreadMXBean());
038  }
039
040  public ThreadExports(ThreadMXBean threadBean) {
041    this.threadBean = threadBean;
042  }
043
044  void addThreadMetrics(List<MetricFamilySamples> sampleFamilies) {
045    sampleFamilies.add(
046        new GaugeMetricFamily(
047          "jvm_threads_current",
048          "Current thread count of a JVM",
049          threadBean.getThreadCount()));
050
051    sampleFamilies.add(
052        new GaugeMetricFamily(
053          "jvm_threads_daemon",
054          "Daemon thread count of a JVM",
055          threadBean.getDaemonThreadCount()));
056
057    sampleFamilies.add(
058        new GaugeMetricFamily(
059          "jvm_threads_peak",
060          "Peak thread count of a JVM",
061          threadBean.getPeakThreadCount()));
062
063    sampleFamilies.add(
064        new CounterMetricFamily(
065          "jvm_threads_started_total",
066          "Started thread count of a JVM",
067          threadBean.getTotalStartedThreadCount()));
068
069    sampleFamilies.add(
070        new GaugeMetricFamily(
071        "jvm_threads_deadlocked",
072        "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or ownable synchronizers",
073        nullSafeArrayLength(threadBean.findDeadlockedThreads())));
074
075    sampleFamilies.add(
076        new GaugeMetricFamily(
077        "jvm_threads_deadlocked_monitor",
078        "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors",
079        nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads())));
080
081    GaugeMetricFamily threadStateFamily = new GaugeMetricFamily(
082      "jvm_threads_state",
083      "Current count of threads by state",
084      Collections.singletonList("state"));
085
086    Map<Thread.State, Integer> threadStateCounts = getThreadStateCountMap();
087    for (Map.Entry<Thread.State, Integer> entry : threadStateCounts.entrySet()) {
088      threadStateFamily.addMetric(
089        Collections.singletonList(entry.getKey().toString()),
090        entry.getValue()
091      );
092    }
093    sampleFamilies.add(threadStateFamily);
094  }
095
096  private Map<Thread.State, Integer> getThreadStateCountMap() {
097    // Get thread information without computing any stack traces
098    ThreadInfo[] allThreads = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 0);
099
100    // Initialize the map with all thread states
101    HashMap<Thread.State, Integer> threadCounts = new HashMap<Thread.State, Integer>();
102    for (Thread.State state : Thread.State.values()) {
103      threadCounts.put(state, 0);
104    }
105
106    // Collect the actual thread counts
107    for (ThreadInfo curThread : allThreads) {
108      if (curThread != null) {
109        Thread.State threadState = curThread.getThreadState();
110        threadCounts.put(threadState, threadCounts.get(threadState) + 1);
111      }
112    }
113
114    return threadCounts;
115  }
116
117  private static double nullSafeArrayLength(long[] array) {
118    return null == array ? 0 : array.length;
119  }
120
121  public List<MetricFamilySamples> collect() {
122    List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
123    addThreadMetrics(mfs);
124    return mfs;
125  }
126}