Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various cleanups in instrumentation-jvm #1014

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,34 @@
import io.prometheus.metrics.config.PrometheusProperties;
import io.prometheus.metrics.core.metrics.Counter;
import io.prometheus.metrics.model.registry.PrometheusRegistry;

import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import javax.management.openmbean.CompositeData;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import javax.management.openmbean.CompositeData;

/**
* JVM memory allocation metrics. The {@link JvmMemoryPoolAllocationMetrics} are registered as part of the {@link JvmMetrics} like this:
* JVM memory allocation metrics. The {@link JvmMemoryPoolAllocationMetrics} are registered as part
* of the {@link JvmMetrics} like this:
*
* <pre>{@code
* JvmMetrics.builder().register();
* JvmMetrics.builder().register();
* }</pre>
* However, if you want only the {@link JvmMemoryPoolAllocationMetrics} you can also register them directly:
*
* However, if you want only the {@link JvmMemoryPoolAllocationMetrics} you can also register them
* directly:
*
* <pre>{@code
* JvmMemoryAllocationMetrics.builder().register();
* JvmMemoryAllocationMetrics.builder().register();
* }</pre>
*
* Example metrics being exported:
*
* <pre>
* # HELP jvm_memory_pool_allocated_bytes_total Total bytes allocated in a given JVM memory pool. Only updated after GC, not continuously.
* # TYPE jvm_memory_pool_allocated_bytes_total counter
Expand All @@ -40,131 +46,134 @@
*/
public class JvmMemoryPoolAllocationMetrics {

private static final String JVM_MEMORY_POOL_ALLOCATED_BYTES_TOTAL = "jvm_memory_pool_allocated_bytes_total";
private static final String JVM_MEMORY_POOL_ALLOCATED_BYTES_TOTAL =
"jvm_memory_pool_allocated_bytes_total";

private final List<GarbageCollectorMXBean> garbageCollectorBeans;

private JvmMemoryPoolAllocationMetrics(
List<GarbageCollectorMXBean> garbageCollectorBeans, PrometheusProperties config) {
this.garbageCollectorBeans = garbageCollectorBeans;
}

private void register(PrometheusRegistry registry) {

Counter allocatedCounter =
Counter.builder()
.name(JVM_MEMORY_POOL_ALLOCATED_BYTES_TOTAL)
.help(
"Total bytes allocated in a given JVM memory pool. Only updated after GC, not continuously.")
.labelNames("pool")
.register(registry);

AllocationCountingNotificationListener listener =
new AllocationCountingNotificationListener(allocatedCounter);
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorBeans) {
if (garbageCollectorMXBean instanceof NotificationEmitter) {
((NotificationEmitter) garbageCollectorMXBean)
.addNotificationListener(listener, null, null);
}
}
}

private final PrometheusProperties config;
private final List<GarbageCollectorMXBean> garbageCollectorBeans;
static class AllocationCountingNotificationListener implements NotificationListener {

private final Map<String, Long> lastMemoryUsage = new HashMap<>();
private final Counter counter;

private JvmMemoryPoolAllocationMetrics(List<GarbageCollectorMXBean> garbageCollectorBeans, PrometheusProperties config) {
this.garbageCollectorBeans = garbageCollectorBeans;
this.config = config;
AllocationCountingNotificationListener(Counter counter) {
this.counter = counter;
}

private void register(PrometheusRegistry registry) {
@Override
public synchronized void handleNotification(Notification notification, Object handback) {
GarbageCollectionNotificationInfo info =
GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
GcInfo gcInfo = info.getGcInfo();
Map<String, MemoryUsage> memoryUsageBeforeGc = gcInfo.getMemoryUsageBeforeGc();
Map<String, MemoryUsage> memoryUsageAfterGc = gcInfo.getMemoryUsageAfterGc();
for (Map.Entry<String, MemoryUsage> entry : memoryUsageBeforeGc.entrySet()) {
String memoryPool = entry.getKey();
long before = entry.getValue().getUsed();
long after = memoryUsageAfterGc.get(memoryPool).getUsed();
handleMemoryPool(memoryPool, before, after);
}
}

Counter allocatedCounter = Counter.builder()
.name(JVM_MEMORY_POOL_ALLOCATED_BYTES_TOTAL)
.help("Total bytes allocated in a given JVM memory pool. Only updated after GC, not continuously.")
.labelNames("pool")
.register(registry);
// Visible for testing
void handleMemoryPool(String memoryPool, long before, long after) {
/*
* Calculate increase in the memory pool by comparing memory used
* after last GC, before this GC, and after this GC.
* See ascii illustration below.
* Make sure to count only increases and ignore decreases.
* (Typically a pool will only increase between GCs or during GCs, not both.
* E.g. eden pools between GCs. Survivor and old generation pools during GCs.)
*
* |<-- diff1 -->|<-- diff2 -->|
* Timeline: |-- last GC --| |---- GC -----|
* ___^__ ___^____ ___^___
* Mem. usage vars: / last \ / before \ / after \
*/

// Get last memory usage after GC and remember memory used after for next time
long last = getAndSet(lastMemoryUsage, memoryPool, after);
// Difference since last GC
long diff1 = before - last;
// Difference during this GC
long diff2 = after - before;
// Make sure to only count increases
if (diff1 < 0) {
diff1 = 0;
}
if (diff2 < 0) {
diff2 = 0;
}
long increase = diff1 + diff2;
if (increase > 0) {
counter.labelValues(memoryPool).inc(increase);
}
}

AllocationCountingNotificationListener listener = new AllocationCountingNotificationListener(allocatedCounter);
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorBeans) {
if (garbageCollectorMXBean instanceof NotificationEmitter) {
((NotificationEmitter) garbageCollectorMXBean).addNotificationListener(listener, null, null);
}
}
private static long getAndSet(Map<String, Long> map, String key, long value) {
Long last = map.put(key, value);
return last == null ? 0 : last;
}
}

public static Builder builder() {
return new Builder(PrometheusProperties.get());
}

public static Builder builder(PrometheusProperties config) {
return new Builder(config);
}

public static class Builder {

private final PrometheusProperties config;
private List<GarbageCollectorMXBean> garbageCollectorBeans;

static class AllocationCountingNotificationListener implements NotificationListener {

private final Map<String, Long> lastMemoryUsage = new HashMap<String, Long>();
private final Counter counter;

AllocationCountingNotificationListener(Counter counter) {
this.counter = counter;
}

@Override
public synchronized void handleNotification(Notification notification, Object handback) {
GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
GcInfo gcInfo = info.getGcInfo();
Map<String, MemoryUsage> memoryUsageBeforeGc = gcInfo.getMemoryUsageBeforeGc();
Map<String, MemoryUsage> memoryUsageAfterGc = gcInfo.getMemoryUsageAfterGc();
for (Map.Entry<String, MemoryUsage> entry : memoryUsageBeforeGc.entrySet()) {
String memoryPool = entry.getKey();
long before = entry.getValue().getUsed();
long after = memoryUsageAfterGc.get(memoryPool).getUsed();
handleMemoryPool(memoryPool, before, after);
}
}

// Visible for testing
void handleMemoryPool(String memoryPool, long before, long after) {
/*
* Calculate increase in the memory pool by comparing memory used
* after last GC, before this GC, and after this GC.
* See ascii illustration below.
* Make sure to count only increases and ignore decreases.
* (Typically a pool will only increase between GCs or during GCs, not both.
* E.g. eden pools between GCs. Survivor and old generation pools during GCs.)
*
* |<-- diff1 -->|<-- diff2 -->|
* Timeline: |-- last GC --| |---- GC -----|
* ___^__ ___^____ ___^___
* Mem. usage vars: / last \ / before \ / after \
*/

// Get last memory usage after GC and remember memory used after for next time
long last = getAndSet(lastMemoryUsage, memoryPool, after);
// Difference since last GC
long diff1 = before - last;
// Difference during this GC
long diff2 = after - before;
// Make sure to only count increases
if (diff1 < 0) {
diff1 = 0;
}
if (diff2 < 0) {
diff2 = 0;
}
long increase = diff1 + diff2;
if (increase > 0) {
counter.labelValues(memoryPool).inc(increase);
}
}

private static long getAndSet(Map<String, Long> map, String key, long value) {
Long last = map.put(key, value);
return last == null ? 0 : last;
}
private Builder(PrometheusProperties config) {
this.config = config;
}

public static Builder builder() {
return new Builder(PrometheusProperties.get());
/** Package private. For testing only. */
Builder withGarbageCollectorBeans(List<GarbageCollectorMXBean> garbageCollectorBeans) {
this.garbageCollectorBeans = garbageCollectorBeans;
return this;
}

public static Builder builder(PrometheusProperties config) {
return new Builder(config);
public void register() {
register(PrometheusRegistry.defaultRegistry);
}

public static class Builder {

private final PrometheusProperties config;
private List<GarbageCollectorMXBean> garbageCollectorBeans;

private Builder(PrometheusProperties config) {
this.config = config;
}

/**
* Package private. For testing only.
*/
Builder withGarbageCollectorBeans(List<GarbageCollectorMXBean> garbageCollectorBeans) {
this.garbageCollectorBeans = garbageCollectorBeans;
return this;
}

public void register() {
register(PrometheusRegistry.defaultRegistry);
}

public void register(PrometheusRegistry registry) {
List<GarbageCollectorMXBean> garbageCollectorBeans = this.garbageCollectorBeans;
if (garbageCollectorBeans == null) {
garbageCollectorBeans = ManagementFactory.getGarbageCollectorMXBeans();
}
new JvmMemoryPoolAllocationMetrics(garbageCollectorBeans, config).register(registry);
}
public void register(PrometheusRegistry registry) {
List<GarbageCollectorMXBean> garbageCollectorBeans = this.garbageCollectorBeans;
if (garbageCollectorBeans == null) {
garbageCollectorBeans = ManagementFactory.getGarbageCollectorMXBeans();
}
new JvmMemoryPoolAllocationMetrics(garbageCollectorBeans, config).register(registry);
}
}
}
Loading