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.Predicate;
007
008import java.lang.management.ManagementFactory;
009import java.lang.management.ThreadInfo;
010import java.lang.management.ThreadMXBean;
011import java.util.ArrayList;
012import java.util.Arrays;
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  public static final String UNKNOWN = "UNKNOWN";
040
041  public static final String JVM_THREADS_STATE = "jvm_threads_state";
042
043  private static final String JVM_THREADS_CURRENT = "jvm_threads_current";
044  private static final String JVM_THREADS_DAEMON = "jvm_threads_daemon";
045  private static final String JVM_THREADS_PEAK = "jvm_threads_peak";
046  private static final String JVM_THREADS_STARTED_TOTAL = "jvm_threads_started_total";
047  private static final String JVM_THREADS_DEADLOCKED = "jvm_threads_deadlocked";
048  private static final String JVM_THREADS_DEADLOCKED_MONITOR = "jvm_threads_deadlocked_monitor";
049
050  private final ThreadMXBean threadBean;
051
052  public ThreadExports() {
053    this(ManagementFactory.getThreadMXBean());
054  }
055
056  public ThreadExports(ThreadMXBean threadBean) {
057    this.threadBean = threadBean;
058  }
059
060  void addThreadMetrics(List<MetricFamilySamples> sampleFamilies, Predicate<String> nameFilter) {
061    if (nameFilter.test(JVM_THREADS_CURRENT)) {
062      sampleFamilies.add(
063              new GaugeMetricFamily(
064                      JVM_THREADS_CURRENT,
065                      "Current thread count of a JVM",
066                      threadBean.getThreadCount()));
067    }
068
069    if (nameFilter.test(JVM_THREADS_DAEMON)) {
070      sampleFamilies.add(
071              new GaugeMetricFamily(
072                      JVM_THREADS_DAEMON,
073                      "Daemon thread count of a JVM",
074                      threadBean.getDaemonThreadCount()));
075    }
076
077    if (nameFilter.test(JVM_THREADS_PEAK)) {
078      sampleFamilies.add(
079              new GaugeMetricFamily(
080                      JVM_THREADS_PEAK,
081                      "Peak thread count of a JVM",
082                      threadBean.getPeakThreadCount()));
083    }
084
085    if (nameFilter.test(JVM_THREADS_STARTED_TOTAL)) {
086      sampleFamilies.add(
087              new CounterMetricFamily(
088                      JVM_THREADS_STARTED_TOTAL,
089                      "Started thread count of a JVM",
090                      threadBean.getTotalStartedThreadCount()));
091    }
092
093    if (nameFilter.test(JVM_THREADS_DEADLOCKED)) {
094      sampleFamilies.add(
095              new GaugeMetricFamily(
096                      JVM_THREADS_DEADLOCKED,
097                      "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or ownable synchronizers",
098                      nullSafeArrayLength(threadBean.findDeadlockedThreads())));
099    }
100
101    if (nameFilter.test(JVM_THREADS_DEADLOCKED_MONITOR)) {
102      sampleFamilies.add(
103              new GaugeMetricFamily(
104                      JVM_THREADS_DEADLOCKED_MONITOR,
105                      "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors",
106                      nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads())));
107    }
108
109    if (nameFilter.test(JVM_THREADS_STATE)) {
110      GaugeMetricFamily threadStateFamily = new GaugeMetricFamily(
111              JVM_THREADS_STATE,
112              "Current count of threads by state",
113              Collections.singletonList("state"));
114
115      Map<String, Integer> threadStateCounts = getThreadStateCountMap();
116      for (Map.Entry<String, Integer> entry : threadStateCounts.entrySet()) {
117        threadStateFamily.addMetric(
118                Collections.singletonList(entry.getKey()),
119                entry.getValue()
120        );
121      }
122      sampleFamilies.add(threadStateFamily);
123    }
124  }
125
126  private Map<String, Integer> getThreadStateCountMap() {
127    long[] threadIds = threadBean.getAllThreadIds();
128
129    // Code to remove any thread id values <= 0
130    int writePos = 0;
131    for (int i = 0; i < threadIds.length; i++) {
132      if (threadIds[i] > 0) {
133        threadIds[writePos++] = threadIds[i];
134      }
135    }
136
137    int numberOfInvalidThreadIds = threadIds.length - writePos;
138    threadIds = Arrays.copyOf(threadIds, writePos);
139
140    // Get thread information without computing any stack traces
141    ThreadInfo[] allThreads = threadBean.getThreadInfo(threadIds, 0);
142
143    // Initialize the map with all thread states
144    HashMap<String, Integer> threadCounts = new HashMap<String, Integer>();
145    for (Thread.State state : Thread.State.values()) {
146      threadCounts.put(state.name(), 0);
147    }
148
149    // Collect the actual thread counts
150    for (ThreadInfo curThread : allThreads) {
151      if (curThread != null) {
152        Thread.State threadState = curThread.getThreadState();
153        threadCounts.put(threadState.name(), threadCounts.get(threadState.name()) + 1);
154      }
155    }
156
157    // Add the thread count for invalid thread ids
158    threadCounts.put(UNKNOWN, numberOfInvalidThreadIds);
159
160    return threadCounts;
161  }
162
163  private static double nullSafeArrayLength(long[] array) {
164    return null == array ? 0 : array.length;
165  }
166
167  @Override
168  public List<MetricFamilySamples> collect() {
169    return collect(null);
170  }
171
172  @Override
173  public List<MetricFamilySamples> collect(Predicate<String> nameFilter) {
174    List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
175    addThreadMetrics(mfs, nameFilter == null ? ALLOW_ALL : nameFilter);
176    return mfs;
177  }
178}