package com.etsy.statsd.profiler.profilers; import com.etsy.statsd.profiler.Arguments; import com.etsy.statsd.profiler.Profiler; import com.etsy.statsd.profiler.reporter.Reporter; import com.google.common.collect.Maps; import java.lang.management.*; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Profiles memory usage and GC statistics * * @author Andrew Johnson */ public class MemoryProfiler extends Profiler { public static final long PERIOD = 10; private final MemoryMXBean memoryMXBean; private final List<GarbageCollectorMXBean> gcMXBeans; private final HashMap<GarbageCollectorMXBean, AtomicLong> gcTimes = new HashMap<>(); private final ClassLoadingMXBean classLoadingMXBean; private final List<MemoryPoolMXBean> memoryPoolMXBeans; public MemoryProfiler(Reporter reporter, Arguments arguments) { super(reporter, arguments); memoryMXBean = ManagementFactory.getMemoryMXBean(); gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); classLoadingMXBean = ManagementFactory.getClassLoadingMXBean(); memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); for (GarbageCollectorMXBean b : gcMXBeans) { gcTimes.put(b, new AtomicLong()); } } /** * Profile memory usage and GC statistics */ @Override public void profile() { recordStats(); } @Override public void flushData() { recordStats(); } @Override public long getPeriod() { return PERIOD; } @Override public TimeUnit getTimeUnit() { return TimeUnit.SECONDS; } @Override protected void handleArguments(Arguments arguments) { /* No arguments needed */ } /** * Records all memory statistics */ private void recordStats() { long finalizationPendingCount = memoryMXBean.getObjectPendingFinalizationCount(); MemoryUsage heap = memoryMXBean.getHeapMemoryUsage(); MemoryUsage nonHeap = memoryMXBean.getNonHeapMemoryUsage(); Map<String, Long> metrics = Maps.newHashMap(); metrics.put("pending-finalization-count", finalizationPendingCount); recordMemoryUsage("heap.total", heap, metrics); recordMemoryUsage("nonheap.total", nonHeap, metrics); for (GarbageCollectorMXBean gcMXBean : gcMXBeans) { String gcName = gcMXBean.getName().replace(" ", "_"); metrics.put("gc." + gcName + ".count", gcMXBean.getCollectionCount()); final long time = gcMXBean.getCollectionTime(); final long prevTime = gcTimes.get(gcMXBean).get(); final long runtime = time - prevTime; metrics.put("gc." + gcName + ".time", time); metrics.put("gc." + gcName + ".runtime", runtime); if (runtime > 0) { gcTimes.get(gcMXBean).set(time); } } long loadedClassCount = classLoadingMXBean.getLoadedClassCount(); long totalLoadedClassCount = classLoadingMXBean.getTotalLoadedClassCount(); long unloadedClassCount = classLoadingMXBean.getUnloadedClassCount(); metrics.put("loaded-class-count", loadedClassCount); metrics.put("total-loaded-class-count", totalLoadedClassCount); metrics.put("unloaded-class-count", unloadedClassCount); for (MemoryPoolMXBean memoryPoolMXBean: memoryPoolMXBeans) { String type = poolTypeToMetricName(memoryPoolMXBean.getType()); String name = poolNameToMetricName(memoryPoolMXBean.getName()); String prefix = type + '.' + name; MemoryUsage usage = memoryPoolMXBean.getUsage(); recordMemoryUsage(prefix, usage, metrics); } recordGaugeValues(metrics); } /** * Records memory usage * * @param prefix The prefix to use for this object * @param memory The MemoryUsage object containing the memory usage info */ private static void recordMemoryUsage(String prefix, MemoryUsage memory, Map<String, Long> metrics) { metrics.put(prefix + ".init", memory.getInit()); metrics.put(prefix + ".used", memory.getUsed()); metrics.put(prefix + ".committed", memory.getCommitted()); metrics.put(prefix + ".max", memory.getMax()); } /** * Formats a MemoryType into a valid metric name * * @param memoryType a MemoryType * @return a valid metric name */ private static String poolTypeToMetricName(MemoryType memoryType) { switch (memoryType) { case HEAP: return "heap"; case NON_HEAP: return "nonheap"; default: return "unknown"; } } /** * Formats a pool name into a valid metric name * * @param poolName a pool name * @return a valid metric name */ private static String poolNameToMetricName(String poolName) { return poolName.toLowerCase().replaceAll("\\s+", "-"); } }