001package io.prometheus.client.hotspot; 002 003import io.prometheus.client.Collector; 004import io.prometheus.client.CounterMetricFamily; 005import io.prometheus.client.GaugeMetricFamily; 006 007import java.io.BufferedReader; 008import java.io.FileNotFoundException; 009import java.io.FileReader; 010import java.io.IOException; 011import java.lang.management.ManagementFactory; 012import java.lang.management.OperatingSystemMXBean; 013import java.lang.management.RuntimeMXBean; 014import java.lang.reflect.InvocationTargetException; 015import java.lang.reflect.Method; 016import java.util.ArrayList; 017import java.util.List; 018import java.util.logging.Level; 019import java.util.logging.Logger; 020 021/** 022 * Exports the standard exports common across all prometheus clients. 023 * <p> 024 * This includes stats like CPU time spent and memory usage. 025 * <p> 026 * Example usage: 027 * <pre> 028 * {@code 029 * new StandardExports().register(); 030 * } 031 * </pre> 032 */ 033public class StandardExports extends Collector { 034 private static final Logger LOGGER = Logger.getLogger(StandardExports.class.getName()); 035 036 private final StatusReader statusReader; 037 private final OperatingSystemMXBean osBean; 038 private final RuntimeMXBean runtimeBean; 039 private final boolean linux; 040 041 public StandardExports() { 042 this(new StatusReader(), 043 ManagementFactory.getOperatingSystemMXBean(), 044 ManagementFactory.getRuntimeMXBean()); 045 } 046 047 StandardExports(StatusReader statusReader, OperatingSystemMXBean osBean, RuntimeMXBean runtimeBean) { 048 this.statusReader = statusReader; 049 this.osBean = osBean; 050 this.runtimeBean = runtimeBean; 051 this.linux = (osBean.getName().indexOf("Linux") == 0); 052 } 053 054 private final static double KB = 1024; 055 056 @Override 057 public List<MetricFamilySamples> collect() { 058 List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>(); 059 060 try { 061 // There exist at least 2 similar but unrelated UnixOperatingSystemMXBean interfaces, in 062 // com.sun.management and com.ibm.lang.management. Hence use reflection and recursively go 063 // through implemented interfaces until the method can be made accessible and invoked. 064 Long processCpuTime = callLongGetter("getProcessCpuTime", osBean); 065 mfs.add(new CounterMetricFamily("process_cpu_seconds_total", "Total user and system CPU time spent in seconds.", 066 processCpuTime / NANOSECONDS_PER_SECOND)); 067 } 068 catch (Exception e) { 069 LOGGER.log(Level.FINE,"Could not access process cpu time", e); 070 } 071 072 mfs.add(new GaugeMetricFamily("process_start_time_seconds", "Start time of the process since unix epoch in seconds.", 073 runtimeBean.getStartTime() / MILLISECONDS_PER_SECOND)); 074 075 // There exist at least 2 similar but unrelated UnixOperatingSystemMXBean interfaces, in 076 // com.sun.management and com.ibm.lang.management. Hence use reflection and recursively go 077 // through implemented interfaces until the method can be made accessible and invoked. 078 try { 079 Long openFdCount = callLongGetter("getOpenFileDescriptorCount", osBean); 080 mfs.add(new GaugeMetricFamily( 081 "process_open_fds", "Number of open file descriptors.", openFdCount)); 082 Long maxFdCount = callLongGetter("getMaxFileDescriptorCount", osBean); 083 mfs.add(new GaugeMetricFamily( 084 "process_max_fds", "Maximum number of open file descriptors.", maxFdCount)); 085 } catch (Exception e) { 086 // Ignore, expected on non-Unix OSs. 087 } 088 089 // There's no standard Java or POSIX way to get memory stats, 090 // so add support for just Linux for now. 091 if (linux) { 092 try { 093 collectMemoryMetricsLinux(mfs); 094 } catch (Exception e) { 095 // If the format changes, log a warning and return what we can. 096 LOGGER.warning(e.toString()); 097 } 098 } 099 return mfs; 100 } 101 102 static Long callLongGetter(String getterName, Object obj) 103 throws NoSuchMethodException, InvocationTargetException { 104 return callLongGetter(obj.getClass().getMethod(getterName), obj); 105 } 106 107 /** 108 * Attempts to call a method either directly or via one of the implemented interfaces. 109 * <p> 110 * A Method object refers to a specific method declared in a specific class. The first invocation 111 * might happen with method == SomeConcreteClass.publicLongGetter() and will fail if 112 * SomeConcreteClass is not public. We then recurse over all interfaces implemented by 113 * SomeConcreteClass (or extended by those interfaces and so on) until we eventually invoke 114 * callMethod() with method == SomePublicInterface.publicLongGetter(), which will then succeed. 115 * <p> 116 * There is a built-in assumption that the method will never return null (or, equivalently, that 117 * it returns the primitive data type, i.e. {@code long} rather than {@code Long}). If this 118 * assumption doesn't hold, the method might be called repeatedly and the returned value will be 119 * the one produced by the last call. 120 */ 121 static Long callLongGetter(Method method, Object obj) throws InvocationTargetException { 122 try { 123 return (Long) method.invoke(obj); 124 } catch (IllegalAccessException e) { 125 // Expected, the declaring class or interface might not be public. 126 } 127 128 // Iterate over all implemented/extended interfaces and attempt invoking the method with the 129 // same name and parameters on each. 130 for (Class<?> clazz : method.getDeclaringClass().getInterfaces()) { 131 try { 132 Method interfaceMethod = clazz.getMethod(method.getName(), method.getParameterTypes()); 133 Long result = callLongGetter(interfaceMethod, obj); 134 if (result != null) { 135 return result; 136 } 137 } catch (NoSuchMethodException e) { 138 // Expected, class might implement multiple, unrelated interfaces. 139 } 140 } 141 142 return null; 143 } 144 145 void collectMemoryMetricsLinux(List<MetricFamilySamples> mfs) { 146 // statm/stat report in pages, and it's non-trivial to get pagesize from Java 147 // so we parse status instead. 148 BufferedReader br = null; 149 try { 150 br = statusReader.procSelfStatusReader(); 151 String line; 152 while ((line = br.readLine()) != null) { 153 if (line.startsWith("VmSize:")) { 154 mfs.add(new GaugeMetricFamily("process_virtual_memory_bytes", 155 "Virtual memory size in bytes.", 156 Float.parseFloat(line.split("\\s+")[1]) * KB)); 157 } else if (line.startsWith("VmRSS:")) { 158 mfs.add(new GaugeMetricFamily("process_resident_memory_bytes", 159 "Resident memory size in bytes.", 160 Float.parseFloat(line.split("\\s+")[1]) * KB)); 161 } 162 } 163 } catch (IOException e) { 164 LOGGER.fine(e.toString()); 165 } finally { 166 if (br != null) { 167 try { 168 br.close(); 169 } catch (IOException e) { 170 LOGGER.fine(e.toString()); 171 } 172 } 173 } 174 } 175 176 static class StatusReader { 177 BufferedReader procSelfStatusReader() throws FileNotFoundException { 178 return new BufferedReader(new FileReader("/proc/self/status")); 179 } 180 } 181}