Skip to content

Commit df64303

Browse files
snagobrian-brazil
authored andcommitted
Add MemoryAllocationExports (#434)
* Add MemoryAllocationExports Counters for total bytes allocated to each memory pool. Can be used to show allocation rate and promotion rate. Signed-off-by: Robin Karlsson <[email protected]>
1 parent 409c825 commit df64303

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/DefaultExports.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public static synchronized void initialize() {
2222
if (!initialized) {
2323
new StandardExports().register();
2424
new MemoryPoolsExports().register();
25+
new MemoryAllocationExports().register();
2526
new BufferPoolsExports().register();
2627
new GarbageCollectorExports().register();
2728
new ThreadExports().register();
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package io.prometheus.client.hotspot;
2+
3+
import com.sun.management.GarbageCollectionNotificationInfo;
4+
import com.sun.management.GcInfo;
5+
import io.prometheus.client.Collector;
6+
import io.prometheus.client.Counter;
7+
8+
import javax.management.Notification;
9+
import javax.management.NotificationEmitter;
10+
import javax.management.NotificationListener;
11+
import javax.management.openmbean.CompositeData;
12+
import java.lang.management.GarbageCollectorMXBean;
13+
import java.lang.management.ManagementFactory;
14+
import java.lang.management.MemoryUsage;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
19+
public class MemoryAllocationExports extends Collector {
20+
private final Counter allocatedCounter = Counter.build()
21+
.name("jvm_memory_pool_allocated_bytes_total")
22+
.help("Total bytes allocated in a given JVM memory pool. Only updated after GC, not continuously.")
23+
.labelNames("pool")
24+
.create();
25+
26+
public MemoryAllocationExports() {
27+
AllocationCountingNotificationListener listener = new AllocationCountingNotificationListener(allocatedCounter);
28+
for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
29+
((NotificationEmitter) garbageCollectorMXBean).addNotificationListener(listener, null, null);
30+
}
31+
}
32+
33+
@Override
34+
public List<MetricFamilySamples> collect() {
35+
return allocatedCounter.collect();
36+
}
37+
38+
static class AllocationCountingNotificationListener implements NotificationListener {
39+
private final Map<String, Long> lastMemoryUsage = new HashMap<String, Long>();
40+
private final Counter counter;
41+
42+
AllocationCountingNotificationListener(Counter counter) {
43+
this.counter = counter;
44+
}
45+
46+
@Override
47+
public synchronized void handleNotification(Notification notification, Object handback) {
48+
GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
49+
GcInfo gcInfo = info.getGcInfo();
50+
Map<String, MemoryUsage> memoryUsageBeforeGc = gcInfo.getMemoryUsageBeforeGc();
51+
Map<String, MemoryUsage> memoryUsageAfterGc = gcInfo.getMemoryUsageAfterGc();
52+
for (Map.Entry<String, MemoryUsage> entry : memoryUsageBeforeGc.entrySet()) {
53+
String memoryPool = entry.getKey();
54+
long before = entry.getValue().getUsed();
55+
long after = memoryUsageAfterGc.get(memoryPool).getUsed();
56+
handleMemoryPool(memoryPool, before, after);
57+
}
58+
}
59+
60+
// Visible for testing
61+
void handleMemoryPool(String memoryPool, long before, long after) {
62+
/*
63+
* Calculate increase in the memory pool by comparing memory used
64+
* after last GC, before this GC, and after this GC.
65+
* See ascii illustration below.
66+
* Make sure to count only increases and ignore decreases.
67+
* (Typically a pool will only increase between GCs or during GCs, not both.
68+
* E.g. eden pools between GCs. Survivor and old generation pools during GCs.)
69+
*
70+
* |<-- diff1 -->|<-- diff2 -->|
71+
* Timeline: |-- last GC --| |---- GC -----|
72+
* ___^__ ___^____ ___^___
73+
* Mem. usage vars: / last \ / before \ / after \
74+
*/
75+
76+
// Get last memory usage after GC and remember memory used after for next time
77+
long last = getAndSet(lastMemoryUsage, memoryPool, after);
78+
// Difference since last GC
79+
long diff1 = before - last;
80+
// Difference during this GC
81+
long diff2 = after - before;
82+
// Make sure to only count increases
83+
if (diff1 < 0) {
84+
diff1 = 0;
85+
}
86+
if (diff2 < 0) {
87+
diff2 = 0;
88+
}
89+
long increase = diff1 + diff2;
90+
if (increase > 0) {
91+
counter.labels(memoryPool).inc(increase);
92+
}
93+
}
94+
95+
private static long getAndSet(Map<String, Long> map, String key, long value) {
96+
Long last = map.put(key, value);
97+
return last == null ? 0 : last;
98+
}
99+
}
100+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.prometheus.client.hotspot;
2+
3+
import io.prometheus.client.Counter;
4+
import org.junit.Test;
5+
6+
import static org.junit.Assert.assertEquals;
7+
8+
public class MemoryAllocationExportsTest {
9+
10+
@Test
11+
public void testListenerLogic() {
12+
Counter counter = Counter.build("test", "test").labelNames("pool").create();
13+
MemoryAllocationExports.AllocationCountingNotificationListener listener =
14+
new MemoryAllocationExports.AllocationCountingNotificationListener(counter);
15+
16+
// Increase by 123
17+
listener.handleMemoryPool("TestPool", 0, 123);
18+
Counter.Child child = counter.labels("TestPool");
19+
assertEquals(123, child.get(), 0.001);
20+
21+
// No increase
22+
listener.handleMemoryPool("TestPool", 123, 123);
23+
assertEquals(123, child.get(), 0.001);
24+
25+
// No increase, then decrease to 0
26+
listener.handleMemoryPool("TestPool", 123, 0);
27+
assertEquals(123, child.get(), 0.001);
28+
29+
// No increase, then increase by 7
30+
listener.handleMemoryPool("TestPool", 0, 7);
31+
assertEquals(130, child.get(), 0.001);
32+
33+
// Increase by 10, then decrease to 10
34+
listener.handleMemoryPool("TestPool", 17, 10);
35+
assertEquals(140, child.get(), 0.001);
36+
37+
// Increase by 7, then increase by 3
38+
listener.handleMemoryPool("TestPool", 17, 20);
39+
assertEquals(150, child.get(), 0.001);
40+
41+
// Decrease to 17, then increase by 3
42+
listener.handleMemoryPool("TestPool", 17, 20);
43+
assertEquals(153, child.get(), 0.001);
44+
}
45+
}

0 commit comments

Comments
 (0)