Skip to content

Commit 6757db3

Browse files
committed
Add standard metrics via hotspot.
1 parent bc8875d commit 6757db3

6 files changed

Lines changed: 316 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ assets:
2828
* groupId: _io.prometheus_
2929
* artifactId: _simpleclient_
3030
* version: _0.0.5_
31+
* [Hotspot metrics](http://mvnrepository.com/artifact/io.prometheus.client/simpleclient_hotspot)
32+
* groupId: _io.prometheus_
33+
* artifactId: _simpleclient_hotspot_
34+
* version: _0.0.5_
3135
* [Exposition Servlet](http://mvnrepository.com/artifact/io.prometheus.client/simpleclient_servlet)
3236
* groupId: _io.prometheus_
3337
* artifactId: _simpleclient_servlet_

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<module>utility</module>
4949
<module>simpleclient</module>
5050
<module>simpleclient_common</module>
51+
<module>simpleclient_hotspot</module>
5152
<module>simpleclient_servlet</module>
5253
<module>simpleclient_pushgateway</module>
5354
</modules>

simpleclient_hotspot/pom.xml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>io.prometheus</groupId>
7+
<artifactId>parent</artifactId>
8+
<version>0.0.6-SNAPSHOT</version>
9+
</parent>
10+
11+
<groupId>io.prometheus</groupId>
12+
<artifactId>simpleclient_hotspot</artifactId>
13+
14+
<name>Prometheus Java Client Metrics</name>
15+
<description>
16+
Prometheus Java Client Metrics
17+
</description>
18+
19+
<licenses>
20+
<license>
21+
<name>The Apache Software License, Version 2.0</name>
22+
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
23+
<distribution>repo</distribution>
24+
</license>
25+
</licenses>
26+
27+
<developers>
28+
<developer>
29+
<id>brian-brazil</id>
30+
<name>Brian Brazil</name>
31+
<email>brian.brazil@boxever.com</email>
32+
</developer>
33+
</developers>
34+
35+
<properties>
36+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
37+
</properties>
38+
39+
<dependencies>
40+
<dependency>
41+
<groupId>io.prometheus</groupId>
42+
<artifactId>simpleclient</artifactId>
43+
<version>0.0.6-SNAPSHOT</version>
44+
</dependency>
45+
<!-- Test Dependencies Follow -->
46+
<dependency>
47+
<groupId>junit</groupId>
48+
<artifactId>junit</artifactId>
49+
<version>4.11</version>
50+
<scope>test</scope>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.mockito</groupId>
54+
<artifactId>mockito-all</artifactId>
55+
<version>1.8.4</version>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>io.prometheus</groupId>
60+
<artifactId>simpleclient_servlet</artifactId>
61+
<version>0.0.6-SNAPSHOT</version>
62+
</dependency>
63+
<dependency>
64+
<groupId>org.eclipse.jetty</groupId>
65+
<artifactId>jetty-servlet</artifactId>
66+
<version>8.1.7.v20120910</version>
67+
<scope>test</scope>
68+
</dependency>
69+
</dependencies>
70+
<build>
71+
<plugins>
72+
<plugin>
73+
<artifactId>maven-compiler-plugin</artifactId>
74+
<version>3.1</version>
75+
<configuration>
76+
<source>1.6</source>
77+
<target>1.6</target>
78+
</configuration>
79+
</plugin>
80+
</plugins>
81+
</build>
82+
</project>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package io.prometheus.client.hotspot;
2+
3+
import com.sun.management.OperatingSystemMXBean;
4+
import com.sun.management.UnixOperatingSystemMXBean;
5+
import java.io.BufferedReader;
6+
import java.io.FileReader;
7+
import java.io.IOException;
8+
import java.io.FileNotFoundException;
9+
import java.lang.management.RuntimeMXBean;
10+
import java.lang.management.ManagementFactory;
11+
import java.util.logging.Logger;
12+
import java.util.Vector;
13+
14+
import io.prometheus.client.Collector;
15+
16+
/**
17+
* Exports the standard exports common across all prometheus clients.
18+
* <p>
19+
* This includes stats like CPU time spent and memory usage.
20+
* <p>
21+
* Example usage:
22+
* <pre>
23+
* {@code
24+
* new StandardExports().register();
25+
* }
26+
* </pre>
27+
*/
28+
public class StandardExports extends Collector {
29+
private static final Logger LOGGER = Logger.getLogger(StandardExports.class.getName());
30+
31+
private StatusReader statusReader;
32+
private OperatingSystemMXBean osBean;
33+
private RuntimeMXBean runtimeBean;
34+
private boolean unix;
35+
private boolean linux;
36+
37+
public StandardExports() {
38+
this(new StatusReader(),
39+
(OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean(),
40+
ManagementFactory.getRuntimeMXBean());
41+
}
42+
43+
StandardExports(StatusReader statusReader, OperatingSystemMXBean osBean, RuntimeMXBean runtimeBean) {
44+
this.statusReader = statusReader;
45+
this.osBean = osBean;
46+
this.runtimeBean = runtimeBean;
47+
if (osBean instanceof UnixOperatingSystemMXBean) {
48+
unix = true;
49+
}
50+
if (osBean.getName().indexOf("Linux") == 0) {
51+
linux = true;
52+
}
53+
}
54+
55+
MetricFamilySamples singleMetric(String name, Type type, String help, double value) {
56+
Vector<MetricFamilySamples.Sample> samples = new Vector<MetricFamilySamples.Sample>();
57+
samples.add(new MetricFamilySamples.Sample(name, new String[]{}, new Vector<String>(), value));
58+
return new MetricFamilySamples(name, type, help, samples);
59+
}
60+
61+
private final static double NANOSECONDS_PER_SECOND = 1E9;
62+
private final static double MILLISECONDS_PER_SECOND = 1E3;
63+
private final static double KB = 1024;
64+
65+
public MetricFamilySamples[] collect() {
66+
Vector<MetricFamilySamples> mfs = new Vector<MetricFamilySamples>();
67+
68+
mfs.add(singleMetric("process_cpu_seconds_total", Type.COUNTER, "CPU time used by the process in seconds.",
69+
osBean.getProcessCpuTime() / NANOSECONDS_PER_SECOND));
70+
71+
mfs.add(singleMetric("process_start_time_seconds", Type.GAUGE, "Start time of the process, in unixtime.",
72+
runtimeBean.getStartTime() / MILLISECONDS_PER_SECOND));
73+
74+
if (unix) {
75+
UnixOperatingSystemMXBean unixBean = (UnixOperatingSystemMXBean) osBean;
76+
mfs.add(singleMetric("process_open_fds", Type.GAUGE, "The number of open file descriptors.",
77+
unixBean.getOpenFileDescriptorCount()));
78+
mfs.add(singleMetric("process_max_fds", Type.GAUGE, "The maximum number of open file descriptors.",
79+
unixBean.getMaxFileDescriptorCount()));
80+
}
81+
82+
// There's no standard Java or POSIX way to get memory stats,
83+
// so add support for just Linux for now.
84+
if (linux) {
85+
try {
86+
collectMemoryMetricsLinux(mfs);
87+
} catch (Exception e) {
88+
// If the format changes, log a warning and return what we can.
89+
LOGGER.warning(e.toString());
90+
}
91+
}
92+
93+
return mfs.toArray(new MetricFamilySamples[]{});
94+
}
95+
96+
void collectMemoryMetricsLinux(Vector<MetricFamilySamples> mfs) {
97+
// statm/stat report in pages, and it's non-trivial to get pagesize from Java
98+
// so we parse status instead.
99+
BufferedReader br = null;
100+
try {
101+
br = statusReader.procSelfStatusReader();
102+
String line;
103+
while ((line = br.readLine()) != null) {
104+
if (line.startsWith("VmSize:")) {
105+
mfs.add(singleMetric("process_virtual_memory_bytes", Type.GAUGE,
106+
"The virtual memory size of the process, in bytes.",
107+
Float.parseFloat(line.split(" +")[1]) * KB));
108+
} else if (line.startsWith("VmRSS:")) {
109+
mfs.add(singleMetric("process_resident_memory_bytes", Type.GAUGE,
110+
"The resident memory size of the process, in bytes.",
111+
Float.parseFloat(line.split(" +")[1]) * KB));
112+
}
113+
}
114+
} catch (IOException e) {
115+
LOGGER.fine(e.toString());
116+
} finally {
117+
if (br != null) {
118+
try {
119+
br.close();
120+
} catch (IOException e) {
121+
LOGGER.fine(e.toString());
122+
}
123+
}
124+
}
125+
}
126+
127+
static class StatusReader {
128+
BufferedReader procSelfStatusReader() throws FileNotFoundException {
129+
return new BufferedReader(new FileReader("/proc/self/status"));
130+
}
131+
}
132+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.prometheus.client.hotspot;
2+
3+
import io.prometheus.client.exporter.MetricsServlet;
4+
import io.prometheus.client.hotspot.StandardExports;
5+
import org.eclipse.jetty.server.Server;
6+
import org.eclipse.jetty.servlet.ServletContextHandler;
7+
import org.eclipse.jetty.servlet.ServletHolder;
8+
9+
public class ExampleExporter {
10+
11+
public static void main(String[] args) throws Exception {
12+
new StandardExports().register();
13+
Server server = new Server(1234);
14+
ServletContextHandler context = new ServletContextHandler();
15+
context.setContextPath("/");
16+
server.setHandler(context);
17+
context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics");
18+
server.start();
19+
while(true) {
20+
}
21+
}
22+
23+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.prometheus.client.hotspot;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNull;
5+
6+
import com.sun.management.UnixOperatingSystemMXBean;
7+
import java.io.BufferedReader;
8+
import java.io.StringReader;
9+
import java.io.FileNotFoundException;
10+
import java.util.Vector;
11+
import java.lang.management.RuntimeMXBean;
12+
13+
import io.prometheus.client.CollectorRegistry;
14+
import org.junit.Before;
15+
import org.junit.Test;
16+
import org.mockito.Mockito;
17+
18+
public class StandardExportsTest {
19+
20+
static class StatusReaderTest extends StandardExports.StatusReader {
21+
BufferedReader procSelfStatusReader() throws FileNotFoundException {
22+
return new BufferedReader(new StringReader("Name: cat\nVmSize: 5900 kB\nVmRSS: 360 kB\n"));
23+
}
24+
}
25+
26+
UnixOperatingSystemMXBean osBean;
27+
RuntimeMXBean runtimeBean;
28+
29+
@Before
30+
public void setUp() {
31+
osBean = Mockito.mock(UnixOperatingSystemMXBean.class);
32+
Mockito.when(osBean.getName()).thenReturn("Linux");
33+
Mockito.when(osBean.getProcessCpuTime()).thenReturn(123L);
34+
Mockito.when(osBean.getOpenFileDescriptorCount()).thenReturn(10L);
35+
Mockito.when(osBean.getMaxFileDescriptorCount()).thenReturn(20L);
36+
runtimeBean = Mockito.mock(RuntimeMXBean.class);
37+
Mockito.when(runtimeBean.getStartTime()).thenReturn(456L);
38+
}
39+
40+
@Test
41+
public void testStandardExports() {
42+
CollectorRegistry registry = new CollectorRegistry();
43+
new StandardExports(new StatusReaderTest(), osBean, runtimeBean).register(registry);
44+
45+
assertEquals(123 / 1.0E9,
46+
registry.getSampleValue("process_cpu_seconds_total", new String[]{}, new String[]{}), .0000001);
47+
assertEquals(10,
48+
registry.getSampleValue("process_open_fds", new String[]{}, new String[]{}), .001);
49+
assertEquals(20,
50+
registry.getSampleValue("process_max_fds", new String[]{}, new String[]{}), .001);
51+
assertEquals(456 / 1.0E3,
52+
registry.getSampleValue("process_start_time_seconds", new String[]{}, new String[]{}), .0000001);
53+
assertEquals(5900 * 1024,
54+
registry.getSampleValue("process_virtual_memory_bytes", new String[]{}, new String[]{}), .001);
55+
assertEquals(360 * 1024,
56+
registry.getSampleValue("process_resident_memory_bytes", new String[]{}, new String[]{}), .001);
57+
}
58+
59+
@Test
60+
public void testBrokenProcStatusReturnsOtherStats() {
61+
class StatusReaderBroken extends StandardExports.StatusReader {
62+
BufferedReader procSelfStatusReader() throws FileNotFoundException {
63+
return new BufferedReader(new StringReader("Name: cat\nVmSize:\n"));
64+
}
65+
}
66+
67+
CollectorRegistry registry = new CollectorRegistry();
68+
new StandardExports(new StatusReaderBroken(), osBean, runtimeBean).register(registry);
69+
70+
assertEquals(123 / 1.0E9,
71+
registry.getSampleValue("process_cpu_seconds_total", new String[]{}, new String[]{}), .0000001);
72+
assertNull(registry.getSampleValue("process_resident_memory_bytes", new String[]{}, new String[]{}));
73+
}
74+
}

0 commit comments

Comments
 (0)