001package io.prometheus.client.hotspot;
002
003import com.sun.management.OperatingSystemMXBean;
004import com.sun.management.UnixOperatingSystemMXBean;
005import java.io.BufferedReader;
006import java.io.FileReader;
007import java.io.IOException;
008import java.io.FileNotFoundException;
009import java.lang.management.RuntimeMXBean;
010import java.lang.management.ManagementFactory;
011import java.util.ArrayList;
012import java.util.List;
013import java.util.logging.Logger;
014
015import io.prometheus.client.Collector;
016
017/**
018 * Exports the standard exports common across all prometheus clients.
019 * <p>
020 * This includes stats like CPU time spent and memory usage.
021 * <p>
022 * Example usage:
023 * <pre>
024 * {@code
025 *   new StandardExports().register();
026 * }
027 * </pre>
028 */
029public class StandardExports extends Collector {
030  private static final Logger LOGGER = Logger.getLogger(StandardExports.class.getName());
031
032  private StatusReader statusReader;
033  private OperatingSystemMXBean osBean;
034  private RuntimeMXBean runtimeBean;
035  private boolean unix;
036  private boolean linux;
037
038  public StandardExports() {
039    this(new StatusReader(),
040         (OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean(),
041         ManagementFactory.getRuntimeMXBean());
042  }
043
044  StandardExports(StatusReader statusReader, OperatingSystemMXBean osBean, RuntimeMXBean runtimeBean) {
045      this.statusReader = statusReader;
046      this.osBean = osBean;
047      this.runtimeBean = runtimeBean;
048      if (osBean instanceof UnixOperatingSystemMXBean) {
049        unix = true;
050      }
051      if (osBean.getName().indexOf("Linux") == 0) {
052        linux = true;
053      }
054  }
055
056  MetricFamilySamples singleMetric(String name, Type type, String help, double value) {
057    ArrayList<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
058    samples.add(new MetricFamilySamples.Sample(name, new ArrayList<String>(), new ArrayList<String>(), value));
059    return new MetricFamilySamples(name, type, help, samples);
060  }
061
062  private final static double KB = 1024;
063
064  public List<MetricFamilySamples> collect() {
065    List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
066
067    mfs.add(singleMetric("process_cpu_seconds_total", Type.COUNTER, "Total user and system CPU time spent in seconds.",
068        osBean.getProcessCpuTime() / NANOSECONDS_PER_SECOND));
069
070    mfs.add(singleMetric("process_start_time_seconds", Type.GAUGE, "Start time of the process since unix epoch in seconds.",
071        runtimeBean.getStartTime() / MILLISECONDS_PER_SECOND));
072
073    if (unix) {
074      UnixOperatingSystemMXBean unixBean = (UnixOperatingSystemMXBean) osBean;
075      mfs.add(singleMetric("process_open_fds", Type.GAUGE, "Number of open file descriptors.",
076          unixBean.getOpenFileDescriptorCount()));
077      mfs.add(singleMetric("process_max_fds", Type.GAUGE, "Maximum number of open file descriptors.",
078          unixBean.getMaxFileDescriptorCount()));
079    }
080
081    // There's no standard Java or POSIX way to get memory stats,
082    // so add support for just Linux for now.
083    if (linux) {
084      try {
085      collectMemoryMetricsLinux(mfs);
086      } catch (Exception e) {
087        // If the format changes, log a warning and return what we can.
088        LOGGER.warning(e.toString());
089      }
090    }
091    return mfs;
092  }
093
094  void collectMemoryMetricsLinux(List<MetricFamilySamples> mfs) {
095    // statm/stat report in pages, and it's non-trivial to get pagesize from Java
096    // so we parse status instead.
097    BufferedReader br = null;
098    try {
099      br = statusReader.procSelfStatusReader();
100      String line;
101      while ((line = br.readLine()) != null) {
102        if (line.startsWith("VmSize:")) {
103          mfs.add(singleMetric("process_virtual_memory_bytes", Type.GAUGE,
104              "Virtual memory size in bytes.",
105              Float.parseFloat(line.split("\\s+")[1]) * KB));
106        } else if (line.startsWith("VmRSS:")) {
107          mfs.add(singleMetric("process_resident_memory_bytes", Type.GAUGE,
108              "Resident memory size in bytes.",
109              Float.parseFloat(line.split("\\s+")[1]) * KB));
110        }
111      }
112    } catch (IOException e) {
113      LOGGER.fine(e.toString());
114    } finally {
115      if (br != null) {
116        try {
117          br.close();
118        } catch (IOException e) {
119          LOGGER.fine(e.toString());
120        }
121      }
122    }
123  }
124
125  static class StatusReader {
126    BufferedReader procSelfStatusReader() throws FileNotFoundException {
127      return new BufferedReader(new FileReader("/proc/self/status"));
128    }
129  }
130}