// ================================================================================================= // Copyright 2013 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ================================================================================================= package com.twitter.common.metrics; import java.lang.management.ClassLoadingMXBean; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.primitives.Primitives; import com.sun.management.UnixOperatingSystemMXBean; /** * Export a number of standard statistics for the JVM and system. */ public final class JvmStats { private JvmStats() { // Utility class. } private static String normalizeName(String name) { return name.replaceAll("\\W+", "_"); } /** * Use reflection to get optional gauges that only work in Java 1.7. */ private static Multimap<String, AbstractGauge<Long>> buildJava17Gauges() { Multimap<String, AbstractGauge<Long>> gauges = ArrayListMultimap.create(); try { Class<?> bufferPoolMXBean = ClassLoader.getSystemClassLoader().loadClass("java.lang.management.BufferPoolMXBean"); Method getPlatformMxBeans = ManagementFactory.class.getMethod("getPlatformMXBeans", Class.class); @SuppressWarnings("unchecked") List<Object> beans = (List<Object>) getPlatformMxBeans.invoke(null, bufferPoolMXBean); for (Object pool : beans) { @SuppressWarnings("unchecked") String name = (String) bufferPoolMXBean.getMethod("getName").invoke(pool); gauges.put(name, reflectMethodToGauge("count", bufferPoolMXBean, "getCount", pool)); gauges.put(name, reflectMethodToGauge("used", bufferPoolMXBean, "getMemoryUsed", pool)); gauges.put(name, reflectMethodToGauge("max", bufferPoolMXBean, "getTotalCapacity", pool)); } } catch (ClassNotFoundException ex) { // If any of the above reflection fails, it's ok. These gauges just won't be present. } catch (NoSuchMethodException ex) { // ditto } catch (IllegalAccessException ex) { // same } catch (InvocationTargetException ex) { // etc. } return gauges; } /** * Helper method to add a gauge via reflection on an mx bean. */ private static AbstractGauge<Long> reflectMethodToGauge( String gaugeName, Class<?> clazz, String methodName, final Object arg) throws NoSuchMethodException, IllegalArgumentException { final Method method = clazz.getMethod(methodName); if (!Long.class.isAssignableFrom(Primitives.wrap(method.getReturnType()))) { throw new IllegalArgumentException( "mx bean method " + methodName + " can't be stored as Long metric"); } AbstractGauge<Long> gauge = new AbstractGauge<Long>(gaugeName) { @Override public Long read() { try { return (Long) method.invoke(arg); } catch (IllegalAccessException ex) { return 0L; } catch (InvocationTargetException ex) { return 0L; } } }; return gauge; } private interface MemoryReporter { MemoryUsage getUsage(); } private static void registerMemoryStats(final MetricRegistry stats, final MemoryReporter mem) { if (mem.getUsage() != null) { stats.register(new AbstractGauge<Long>("committed") { @Override public Long read() { return mem.getUsage().getCommitted(); } }); stats.register(new AbstractGauge<Long>("max") { @Override public Long read() { return mem.getUsage().getMax(); } }); stats.register(new AbstractGauge<Long>("used") { @Override public Long read() { return mem.getUsage().getUsed(); } }); } } /** * Add a series of system and jvm-level stats to the given registry. */ public static void register(MetricRegistry registry) { final MetricRegistry stats = registry.scope("jvm"); final MemoryMXBean mem = ManagementFactory.getMemoryMXBean(); // memory stats final MetricRegistry heapRegistry = stats.scope("heap"); registerMemoryStats(heapRegistry, new MemoryReporter() { @Override public MemoryUsage getUsage() { return mem.getHeapMemoryUsage(); } }); final MetricRegistry nonHeapRegistry = stats.scope("nonheap"); registerMemoryStats(nonHeapRegistry, new MemoryReporter() { @Override public MemoryUsage getUsage() { return mem.getNonHeapMemoryUsage(); } }); // threads final ThreadMXBean threads = ManagementFactory.getThreadMXBean(); final MetricRegistry threadRegistry = stats.scope("thread"); threadRegistry.register(new AbstractGauge<Integer>("daemon_count") { @Override public Integer read() { return threads.getDaemonThreadCount(); } }); threadRegistry.register(new AbstractGauge<Integer>("count") { @Override public Integer read() { return threads.getThreadCount(); } }); threadRegistry.register(new AbstractGauge<Integer>("peak_count") { @Override public Integer read() { return threads.getPeakThreadCount(); } }); // class loading bean final ClassLoadingMXBean classLoadingBean = ManagementFactory.getClassLoadingMXBean(); stats.register(new AbstractGauge<Integer>("classes_loaded") { @Override public Integer read() { return classLoadingBean.getLoadedClassCount(); } }); stats.register(new AbstractGauge<Long>("total_classes_loaded") { @Override public Long read() { return classLoadingBean.getTotalLoadedClassCount(); } }); stats.register(new AbstractGauge<Long>("classes_unloaded") { @Override public Long read() { return classLoadingBean.getUnloadedClassCount(); } }); // runtime final RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); stats.register(new AbstractGauge<Long>("start_time") { @Override public Long read() { return runtime.getStartTime(); } }); stats.register(new AbstractGauge<Long>("uptime") { @Override public Long read() { return runtime.getUptime(); } }); //stats.register(new AbstractGauge<String>) // os final OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); stats.register(new AbstractGauge<Integer>("num_cpus") { @Override public Integer read() { return os.getAvailableProcessors(); } }); if (os instanceof com.sun.management.OperatingSystemMXBean) { final com.sun.management.OperatingSystemMXBean sunOsMbean = (com.sun.management.OperatingSystemMXBean) os; // if this is indeed an operating system stats.register(new AbstractGauge<Long>("free_physical_memory") { @Override public Long read() { return sunOsMbean.getFreePhysicalMemorySize(); } }); stats.register(new AbstractGauge<Long>("free_swap") { @Override public Long read() { return sunOsMbean.getFreeSwapSpaceSize(); } }); stats.register(new AbstractGauge<Long>("process_cpu_time") { @Override public Long read() { return sunOsMbean.getProcessCpuTime(); } }); } if (os instanceof com.sun.management.UnixOperatingSystemMXBean) { // it's a unix system... I know this! final UnixOperatingSystemMXBean unix = (UnixOperatingSystemMXBean) os; stats.register(new AbstractGauge<Long>("fd_count") { @Override public Long read() { return unix.getOpenFileDescriptorCount(); } }); stats.register(new AbstractGauge<Long>("fd_limit") { @Override public Long read() { return unix.getMaxFileDescriptorCount(); } }); } // mem final List<MemoryPoolMXBean> memPool = ManagementFactory.getMemoryPoolMXBeans(); final MetricRegistry memRegistry = stats.scope("mem"); final MetricRegistry currentMem = memRegistry.scope("current"); final MetricRegistry postGCRegistry = memRegistry.scope("postGC"); for (final MemoryPoolMXBean pool : memPool) { String name = normalizeName(pool.getName()); registerMemoryStats(currentMem.scope(name), new MemoryReporter() { @Override public MemoryUsage getUsage() { return pool.getUsage(); } }); registerMemoryStats(postGCRegistry.scope(name), new MemoryReporter() { @Override public MemoryUsage getUsage() { return pool.getCollectionUsage(); } }); } currentMem.register(new AbstractGauge<Long>("used") { @Override public Long read() { long sum = 0; for (MemoryPoolMXBean pool : memPool) { MemoryUsage usage = pool.getUsage(); if (usage != null) { sum += usage.getUsed(); } } return sum; } }); AbstractGauge<Long> totalPostGCGauge = new AbstractGauge<Long>("used") { @Override public Long read() { long sum = 0; for (MemoryPoolMXBean pool : memPool) { MemoryUsage usage = pool.getCollectionUsage(); if (usage != null) { sum += usage.getUsed(); } } return sum; } }; postGCRegistry.register(totalPostGCGauge); // java 1.7 specific buffer pool gauges Multimap<String, AbstractGauge<Long>> java17gauges = buildJava17Gauges(); if (!java17gauges.isEmpty()) { MetricRegistry bufferRegistry = stats.scope("buffer"); for (String scope : java17gauges.keySet()) { MetricRegistry pool = bufferRegistry.scope(scope); for (AbstractGauge<Long> gauge : java17gauges.get(scope)) { pool.register(gauge); } } } // gc final List<GarbageCollectorMXBean> gcPool = ManagementFactory.getGarbageCollectorMXBeans(); MetricRegistry gcRegistry = stats.scope("gc"); for (final GarbageCollectorMXBean gc : gcPool) { String name = normalizeName(gc.getName()); MetricRegistry scoped = memRegistry.scope(name); scoped.register(new AbstractGauge<Long>("cycles") { @Override public Long read() { return gc.getCollectionCount(); } }); scoped.register(new AbstractGauge<Long>("msec") { @Override public Long read() { return gc.getCollectionTime(); } }); } gcRegistry.register(new AbstractGauge<Long>("cycles") { @Override public Long read() { long sum = 0; for (GarbageCollectorMXBean pool : gcPool) { long count = pool.getCollectionCount(); if (count > 0) { sum += count; } } return sum; } }); gcRegistry.register(new AbstractGauge<Long>("msec") { @Override public Long read() { long sum = 0; for (GarbageCollectorMXBean pool : gcPool) { long msec = pool.getCollectionTime(); if (msec > 0) { sum += msec; } } return sum; } }); } }