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  }
094
095  private Map<Thread.State, Integer> getThreadStateCountMap() {
096    // Get thread information without computing any stack traces
097    ThreadInfo[] allThreads = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 0);
098
099    // Initialize the map with all thread states
100    HashMap<Thread.State, Integer> threadCounts = new HashMap<Thread.State, Integer>();
101    for (Thread.State state : Thread.State.values()) {
102      threadCounts.put(state, 0);
103    }
104
105    // Collect the actual thread counts
106    for (ThreadInfo curThread : allThreads) {
107      if (curThread != null) {
108        Thread.State threadState = curThread.getThreadState();
109        threadCounts.put(threadState, threadCounts.get(threadState) + 1);
110      }
111    }
112
113    return threadCounts;
114  }
115
116  private static double nullSafeArrayLength(long[] array) {
117    return null == array ? 0 : array.length;
118  }
119
120  public List<MetricFamilySamples> collect() {
121    List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
122    addThreadMetrics(mfs);
123    return mfs;
124  }
125}