/** * Copyright (C) 2014 SignalFx, Inc. */ package com.signalfx.codahale.util; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; /** * Report a basic set of JVM metrics to SignalFx. * * @author psi * */ public class BasicJvmMetrics { /** * Report JVM uptime (milliseconds). */ public final Gauge<Long> uptimeGauge; /** * Reports total memory used by the JVM heap (bytes). */ public final Gauge<Long> totalMemoryGauge; /** * Reports current in-use memory in the JVP heap (bytes). */ public final Gauge<Long> usedMemoryGauge; /** * Reports maximum size of JVM heap (bytes). */ public final Gauge<Long> maxMemoryGauge; /** * Reports current CPU load (percent, normalized by number of CPUs available to the JVM. */ public final Gauge<Double> cpuLoadGauge; /** * Reports total number of user and daemon threads. */ public final Gauge<Integer> totalThreadCountGauge; /** * Reports number of daemon threads. */ public final Gauge<Integer> daemonThreadCountGauge; /** * Reports total time spent in garbage collection (nanoseconds). */ public final Gauge<Long> gcTimeGauge; /** * Reports number of young-generation garbage collections. */ public final Gauge<Long> gcYoungCountGauge; /** * Reports number of old-generation garbage collections. */ public final Gauge<Long> gcOldCountGauge; /** * Reports current GC load (percent, normalized by number of CPUs available to the JVM. */ public final Gauge<Double> gcLoadGauge; private final RuntimeMXBean runtimeBean; private final MemoryMXBean memoryBean; private final ThreadMXBean threadBean; private final List<GarbageCollectorMXBean> oldGenGcBeans = new ArrayList<GarbageCollectorMXBean>(); private final List<GarbageCollectorMXBean> youngGenGcBeans = new ArrayList<GarbageCollectorMXBean>(); private final List<GarbageCollectorMXBean> allGcBeans = new ArrayList<GarbageCollectorMXBean>(); // observed name of the old generation memory pool. private static final String OLD_GEN_POOL_NAME = "PS Old Gen"; /** * Construct the basic JVM metrics using a supplied SignalFx MetricFactory. * * @param metricRegistry The registry to give these metrics to */ public BasicJvmMetrics(MetricRegistry metricRegistry) { runtimeBean = ManagementFactory.getRuntimeMXBean(); memoryBean = ManagementFactory.getMemoryMXBean(); threadBean = ManagementFactory.getThreadMXBean(); for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { allGcBeans.add(gcBean); Set<String> poolNames = new HashSet<String>(Arrays.asList(gcBean.getMemoryPoolNames())); if (poolNames.contains(OLD_GEN_POOL_NAME)) { // We'll count garbage collectors managing the OLD_GEN_POOL_NAME as 'old generation' oldGenGcBeans.add(gcBean); } else { // and all others as 'young generation' youngGenGcBeans.add(gcBean); } } this.uptimeGauge = createPeriodicGauge(metricRegistry, "jvm.uptime", new UptimeCallback()); this.totalMemoryGauge = createPeriodicGauge(metricRegistry, "jvm.heap.size", new TotalMemoryCallback()); this.usedMemoryGauge = createPeriodicGauge(metricRegistry, "jvm.heap.used", new UsedMemoryCallback()); this.maxMemoryGauge = createPeriodicGauge(metricRegistry, "jvm.heap.max", new MaxMemoryCallback()); this.cpuLoadGauge = createDoublePeriodicGauge(metricRegistry, "jvm.cpu.load", new CpuLoadCallback()); this.totalThreadCountGauge = createIntegerPeriodicGauge(metricRegistry, "jvm.threads.total", new TotalThreadCountCallback()); this.daemonThreadCountGauge = createIntegerPeriodicGauge(metricRegistry, "jvm.threads.daemon", new DaemonThreadCountCallback()); this.gcTimeGauge = createPeriodicGauge(metricRegistry, "jvm.gc.time", new GcTimeCallback()); this.gcLoadGauge = createDoublePeriodicGauge(metricRegistry, "jvm.gc.load", new GcLoadCallback()); this.gcYoungCountGauge = createPeriodicGauge(metricRegistry, "jvm.gc.young.count", new GcCountCallback(youngGenGcBeans)); this.gcOldCountGauge = createPeriodicGauge(metricRegistry, "jvm.gc.old.count", new GcCountCallback(oldGenGcBeans)); } private Gauge<Long> createPeriodicGauge(MetricRegistry metricRegistry, String name, Gauge<Long> gauge) { return metricRegistry.register(name, gauge); } private Gauge<Integer> createIntegerPeriodicGauge(MetricRegistry metricRegistry, String name, Gauge<Integer> gauge) { return metricRegistry.register(name, gauge); } private Gauge<Double> createDoublePeriodicGauge(MetricRegistry metricRegistry, String name, Gauge<Double> gauge) { return metricRegistry.register(name, gauge); } private class UptimeCallback implements Gauge<Long> { @Override public Long getValue() { return runtimeBean.getUptime(); } } private class TotalMemoryCallback implements Gauge<Long> { @Override public Long getValue() { return memoryBean.getHeapMemoryUsage().getCommitted(); } } private class UsedMemoryCallback implements Gauge<Long> { @Override public Long getValue() { return memoryBean.getHeapMemoryUsage().getUsed(); } } private class MaxMemoryCallback implements Gauge<Long> { @Override public Long getValue() { return memoryBean.getHeapMemoryUsage().getMax(); } } private class TotalThreadCountCallback implements Gauge<Integer> { @Override public Integer getValue() { return threadBean.getThreadCount(); } } private class DaemonThreadCountCallback implements Gauge<Integer> { @Override public Integer getValue() { return threadBean.getDaemonThreadCount(); } } private class GcTimeCallback implements Gauge<Long> { @Override public Long getValue() { long total = 0; for (GarbageCollectorMXBean gcBean : allGcBeans) { long sample = gcBean.getCollectionTime(); if (sample > 0) { total += sample; } } return total; } } private static class GcCountCallback implements Gauge<Long> { final private List<GarbageCollectorMXBean> gcBeans; private GcCountCallback(List<GarbageCollectorMXBean> gcBeans) { this.gcBeans = gcBeans; } @Override public Long getValue() { long total = 0; for (GarbageCollectorMXBean gcBean : gcBeans) { long sample = gcBean.getCollectionCount(); if (sample > 0) { total += sample; } } return total; } } private abstract class LoadCallback<T> implements Gauge<Double> { private static final int PERCENT = 100; private long previousTime; private double previousValue; private Map<T, Long> samples = new HashMap<T, Long>(); private final TimeUnit timeUnit; LoadCallback(TimeUnit timeUnit) { this.previousTime = 0; this.previousValue = 0; this.timeUnit = timeUnit; } protected abstract Map<T, Long> getSamples(); private long computeDelta(Map<T, Long> newSamples) { long delta = 0; for (Map.Entry<T, Long> entry : newSamples.entrySet()) { T key = entry.getKey(); Long sample = entry.getValue(); if (sample < 0) { // not valid for this key } else { Long previous = samples.get(key); if (previous == null) { delta += sample; // first sample } else { delta += sample - previous; } } } samples = newSamples; return delta; } @Override public Double getValue() { long time = runtimeBean.getUptime(); long deltaTime = time - previousTime; if (deltaTime < 100) { return previousValue; } Map<T, Long> samples = getSamples(); long deltaLoad = computeDelta(samples); previousValue = (double) PERCENT * timeUnit.toNanos(deltaLoad) / TimeUnit.MILLISECONDS.toNanos(deltaTime) / Runtime.getRuntime().availableProcessors(); previousTime = time; return previousValue; } } // com.sun.management.OperatingSystemMXBean has a getProcessCpuTime() // but java.lang.management.OperatingSystemMXBean does not private class CpuLoadCallback extends LoadCallback<Long> { CpuLoadCallback() { super(TimeUnit.NANOSECONDS); } @Override protected Map<Long, Long> getSamples() { Map<Long, Long> samples = new HashMap<Long, Long>(); for (long threadId : threadBean.getAllThreadIds()) { samples.put(threadId, threadBean.getThreadCpuTime(threadId)); } return samples; } } // factor stuff out of this and CpuLoadCallback private class GcLoadCallback extends LoadCallback<String> { GcLoadCallback() { super(TimeUnit.MILLISECONDS); } @Override protected Map<String, Long> getSamples() { Map<String, Long> samples = new HashMap<String, Long>(); for (GarbageCollectorMXBean gcBean : allGcBeans) { samples.put(gcBean.getName(), gcBean.getCollectionTime()); } return samples; } } }