/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.monitor.jvm; import org.helios.apmrouter.jmx.JMXHelper; import org.helios.apmrouter.monitor.AbstractMonitor; import org.helios.apmrouter.util.SystemClock; import javax.management.Attribute; import javax.management.ObjectName; import java.lang.Thread.State; import java.lang.management.*; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * <p>Title: JVMMonitor</p> * <p>Description: A monitor implementation to collect and trace localStats on the JVM's health and status</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.monitor.jvm.JVMMonitor</code></p> */ public class JVMMonitor extends AbstractMonitor { /** The JVM's ThreadMXBean */ protected final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); /** The JVM's MemoryMXBean */ protected final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); /** The JVM's ClassLoaderMXBean */ protected final ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean(); /** The JVM's CompilationMXBean */ protected final CompilationMXBean compilationMXBean = ManagementFactory.getCompilationMXBean(); /** The JVM's OSMXBean */ protected final OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean(); /** The JVM's RuntimeMXBean */ protected final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); /** The JVM's MemoryPoolMXBeans */ protected final Set<MemoryPoolMXBean> memoryPoolMXBeans = new HashSet<MemoryPoolMXBean>(ManagementFactory.getMemoryPoolMXBeans()); /** The JVM's GCMXBeans */ protected final Set<GarbageCollectorMXBean> gcMXBeans = new HashSet<GarbageCollectorMXBean>(ManagementFactory.getGarbageCollectorMXBeans()); /** The number of processors */ public final int PROCESSOR_COUNT = osMXBean.getAvailableProcessors(); /** The last collection timestamp */ protected long lastCollectTime = -1; /** The number of collects after which resetable localStats are reset */ protected int resetLoopCount = 2; /** The number of collects since the last reset */ protected int resetLoops = 0; /** Indicates if this is a reset loop */ protected boolean resetLoop = false; /** The max stack depth to collect on interesting threads */ protected int maxStackDepth = 100; /** A map of the last GC time keyed by the gc-collector name */ protected final Map<String, Long> lastGCTime = new HashMap<String, Long>(gcMXBeans.size()); /** A map of the last GC collection time keyed by the gc-collector name */ protected final Map<String, Long> lastGCCollectTime = new HashMap<String, Long>(gcMXBeans.size()); /** A map of the max pool sizes keyed by pool name */ protected final Map<String, Long> maxPoolSize = new HashMap<String, Long>(gcMXBeans.size()); /** Indicates if the initial static runtime localStats have been collected */ protected boolean initialRuntimeCollected = false; /** Indicates if this is Java 7*/ protected final boolean isJava7; /** The JMX ObjectName pattern for the NIO MXBeans */ protected final ObjectName NIO_MXBEAN_PATTERN = JMXHelper.objectName("java.nio:type=BufferPool,name=*"); /** The JMX ObjectNames for the NIO MXBeans */ protected final Map<String, ObjectName> nioObjectNames = new HashMap<String, ObjectName>(); /** Convenience map for the NIO MXBean attribute unmapping */ protected final Map<String, Long> nioAttrValues = new HashMap<String, Long>(3); /** The attribute names we want to retrieve for the NIO mxbeans */ protected static final String[] NIO_ATTR_NAMES = new String[]{"Count", "MemoryUsed", "TotalCapacity"}; /** * Creates a new JVMMonitor */ public JVMMonitor() { boolean tmp = false; try { Class.forName("java.lang.management.BufferPoolMXBean"); tmp = true; } catch (Exception e) { tmp = false; } isJava7 = tmp; } /** * {@inheritDoc} * @see org.helios.apmrouter.monitor.AbstractMonitor#doCollect(long) */ @Override protected void doCollect(long collectionSweep) { resetLoops++; if(resetLoops==resetLoopCount) { resetLoop = true; resetLoops = 0; } else { resetLoop = false; } lastCollectTime = SystemClock.time(); tracer.traceCounter(Integer.parseInt(runtimeMXBean.getName().split("@")[0]), "PID"); try { collectGc(); } catch (Exception e) {} try { collectThreads(); } catch (Exception e) {} try { collectCompilation(); } catch (Exception e) {} if(maxPoolSize.size()<1) { try { collectInitialMemory(); } catch (Exception e) {} try { collectInitialMemoryPools(); } catch (Exception e) {} } try { collectMemory(); } catch (Exception e) {} try { collectMemoryPools(); } catch (Exception e) {} try { collectClassLoading(); } catch (Exception e) {} if(!initialRuntimeCollected) { try { collectInitialRuntime(); } catch (Exception e) {} } if(isJava7) { try { collectNioBuffers(); } catch (Exception e) {} } } protected void collectNioBuffers() { if(nioObjectNames.isEmpty()) { for(ObjectName on: ManagementFactory.getPlatformMBeanServer().queryNames(NIO_MXBEAN_PATTERN, null)) { nioObjectNames.put(on.getKeyProperty("name"), on); } } for(Map.Entry<String, ObjectName> entry: nioObjectNames.entrySet()) { nioAttrValues.clear(); try { for(Attribute attr: ManagementFactory.getPlatformMBeanServer().getAttributes(entry.getValue(), NIO_ATTR_NAMES).asList()) { nioAttrValues.put(attr.getName(), (Long)attr.getValue()); } Long value = null; if((value=nioAttrValues.get("Count")) != null) { tracer.traceGauge(value, "Count", "platform=JVM", "category=NIOBufferPools", "type=" + entry.getKey()); } if((value=nioAttrValues.get("MemoryUsed")) != null) { tracer.traceGauge(value, "MemoryUsed", "platform=JVM", "category=NIOBufferPools", "type=" + entry.getKey()); } if((value=nioAttrValues.get("TotalCapacity")) != null) { tracer.traceGauge(value, "TotalCapacity", "platform=JVM", "category=NIOBufferPools", "type=" + entry.getKey()); } } catch (Exception e) { } } } /** * Collects GC activity data */ protected void collectGc() { for(GarbageCollectorMXBean gc: gcMXBeans) { long currentTime = SystemClock.time(); String name = gc.getName(); tracer.traceDeltaGauge(gc.getCollectionCount(), "CollectionCount", "platform=JVM", "category=GarbageCollection", "collector=" + name.replace(" ", "")); long time = gc.getCollectionTime(); tracer.traceDeltaGauge(time, "CollectionTime", "platform=JVM", "category=GarbageCollection", "collector=" + name.replace(" ", "")); Long prior = lastGCTime.put(name, time); if(prior!=null) { long gcTime = time-prior; long elapsedTime = lastGCCollectTime.put(name, currentTime)*PROCESSOR_COUNT; tracer.traceGauge(percent(elapsedTime, gcTime), "PercentTimeInCollect", "platform=JVM", "category=GarbageCollection", "collector=" + name.replace(" ", "")); } else { lastGCCollectTime.put(name, currentTime); } } } /** * Collects threading localStats */ protected void collectThreads() { int tc = threadMXBean.getThreadCount(); int dtc = threadMXBean.getDaemonThreadCount(); int ndtc = tc-dtc; tracer.traceGauge(tc, "ThreadCount", "platform=JVM", "category=Threads"); tracer.traceGauge(dtc, "DaemonThreadCount", "platform=JVM", "category=Threads"); tracer.traceGauge(ndtc, "NonDaemonThreadCount", "platform=JVM", "category=Threads"); tracer.traceGauge(threadMXBean.getPeakThreadCount(), "PeakThreadCount", "platform=JVM", "category=Threads"); for(Map.Entry<Thread.State, AtomicInteger> entry: getThreadStates().entrySet()) { tracer.traceGauge(entry.getValue().intValue(), entry.getKey().name(), "platform=JVM", "category=Threads", "type=State"); } if(resetLoop) threadMXBean.resetPeakThreadCount(); long[] deadlocked = threadMXBean.findMonitorDeadlockedThreads(); tracer.traceGauge(deadlocked==null ? 0 : deadlocked.length, "DeadlockedThreadCount", "platform=JVM", "category=Threads"); if(deadlocked != null && deadlocked.length>0) { StringBuilder dlockInfo = new StringBuilder(); ThreadInfo[] tis = threadMXBean.getThreadInfo(deadlocked, maxStackDepth); for(ThreadInfo ti : tis) { dlockInfo.append("\nID:").append(ti.getThreadName()).append("-").append(ti.getThreadId()); dlockInfo.append("\nLockName:").append(ti.getLockName()); dlockInfo.append("\nLockOwnerId:").append(ti.getLockOwnerId()); dlockInfo.append("\nLockOwnerName:").append(ti.getLockOwnerName()); dlockInfo.append("\nStack:"); for(StackTraceElement ste: ti.getStackTrace()) { dlockInfo.append("\n\t").append(ste.toString()); } } tracer.traceString(dlockInfo, "DeadlockedThreadInfo", "platform=JVM", "category=Threads"); } } /** * Collects the number of threads in each thread state * @return an EnumMap with Thread states as the key and the number of threads in that state as the value */ public EnumMap<Thread.State, AtomicInteger> getThreadStates() { EnumMap<Thread.State, AtomicInteger> map = new EnumMap<State, AtomicInteger>(Thread.State.class); for(Thread.State ts: Thread.State.values()) { map.put(ts, new AtomicInteger(0)); } for(ThreadInfo ti : threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds())) { map.get(ti.getThreadState()).incrementAndGet(); } return map; } /** * Collects compilation time */ protected void collectCompilation() { tracer.traceDeltaGauge(compilationMXBean.getTotalCompilationTime(), "CompilationTime", "platform=JVM", "category=Compilation", "compiler=" + compilationMXBean.getName().replace(" ", "")); } /** * Collects the static memory pool data */ protected void collectInitialMemoryPools() { for(MemoryPoolMXBean pool: memoryPoolMXBeans) { MemoryUsage usage = pool.getUsage(); tracer.traceGauge(usage.getInit(), "Initial", "platform=JVM", "category=MemoryPools", "type=" + pool.getType().name(), "pool=" + pool.getName().replace(" ", "")); tracer.traceGauge(usage.getMax(), "Maximum", "platform=JVM", "category=MemoryPools", "type=" + pool.getType().name(), "pool=" + pool.getName().replace(" ", "")); maxPoolSize.put(pool.getName(), usage.getMax()); } } /** * Collects the dynamic memory pool data */ protected void collectMemoryPools() { for(MemoryPoolMXBean pool: memoryPoolMXBeans) { MemoryUsage usage = pool.getUsage(); tracer.traceGauge(usage.getCommitted(), "Committed", "platform=JVM", "category=MemoryPools", "type=" + pool.getType().name(), "pool=" + pool.getName().replace(" ", "")); tracer.traceGauge(usage.getUsed(), "Used", "platform=JVM", "category=MemoryPools", "type=" + pool.getType().name(), "pool=" + pool.getName().replace(" ", "")); tracer.traceGauge(percent(usage.getCommitted(), usage.getUsed()), "PercentUsed", "platform=JVM", "category=MemoryPools", "type=" + pool.getType().name(), "pool=" + pool.getName().replace(" ", "")); tracer.traceGauge(percent(maxPoolSize.get(pool.getName()), usage.getUsed()), "PercentCapacity", "platform=JVM", "category=MemoryPools", "type=" + pool.getType().name(), "pool=" + pool.getName().replace(" ", "")); } } /** * Collects the static memory data */ protected void collectInitialMemory() { MemoryUsage usage = memoryMXBean.getHeapMemoryUsage(); tracer.traceGauge(usage.getInit(), "Initial", "platform=JVM", "category=Memory", "type=Heap"); tracer.traceGauge(usage.getMax(), "Maximum", "platform=JVM", "category=Memory", "type=Heap"); maxPoolSize.put("Heap", usage.getMax()); usage = memoryMXBean.getNonHeapMemoryUsage(); tracer.traceGauge(usage.getInit(), "Initial", "platform=JVM", "category=Memory", "type=NonHeap"); tracer.traceGauge(usage.getMax(), "Maximum", "platform=JVM", "category=Memory", "type=NonHeap"); maxPoolSize.put("NonHeap", usage.getMax()); } /** * Collects the dynamic memory data */ protected void collectMemory() { MemoryUsage usage = memoryMXBean.getHeapMemoryUsage(); tracer.traceGauge(memoryMXBean.getObjectPendingFinalizationCount(), "PendingFinalization", "platform=JVM", "category=Memory"); tracer.traceGauge(usage.getCommitted(), "Committed", "platform=JVM", "category=Memory", "type=Heap"); tracer.traceGauge(usage.getUsed(), "Used", "platform=JVM", "category=Memory", "type=Heap"); tracer.traceGauge(percent(usage.getCommitted(), usage.getUsed()), "PercentUsed", "platform=JVM", "category=Memory", "type=Heap"); tracer.traceGauge(percent(maxPoolSize.get("Heap"), usage.getUsed()), "PercentCapacity", "platform=JVM", "category=Memory", "type=Heap"); usage = memoryMXBean.getNonHeapMemoryUsage(); tracer.traceGauge(usage.getCommitted(), "Committed", "platform=JVM", "category=Memory", "type=NonHeap"); tracer.traceGauge(usage.getUsed(), "Used", "platform=JVM", "category=Memory", "type=NonHeap"); tracer.traceGauge(percent(usage.getCommitted(), usage.getUsed()), "PercentUsed", "platform=JVM", "category=Memory", "type=NonHeap"); tracer.traceGauge(percent(maxPoolSize.get("NonHeap"), usage.getUsed()), "PercentCapacity", "platform=JVM", "category=Memory", "type=NonHeap"); } /** * Collects class loading localStats * FIXME: TotalLoadedClasses needs traceLongLast */ protected void collectClassLoading() { tracer.traceGauge(classLoadingMXBean.getTotalLoadedClassCount(), "TotalLoadedClasses", "platform=JVM", "category=ClassLoading"); tracer.traceDeltaGauge(classLoadingMXBean.getTotalLoadedClassCount(), "ClassLoadRate", "platform=JVM", "category=ClassLoading"); tracer.traceGauge(classLoadingMXBean.getUnloadedClassCount(), "UnloadedClassCount", "platform=JVM", "category=ClassLoading"); tracer.traceDeltaGauge(classLoadingMXBean.getUnloadedClassCount(), "ClassUnloadRate", "platform=JVM", "category=ClassLoading"); tracer.traceGauge(classLoadingMXBean.getLoadedClassCount(), "CurrentClassCount", "platform=JVM", "category=ClassLoading"); } /** * Collects static runtime info */ protected void collectInitialRuntime() { tracer.traceGauge(runtimeMXBean.getStartTime(), "StartTime", "platform=JVM", "category=Runtime"); tracer.traceString(runtimeMXBean.getVmName(), "VmName", "platform=JVM", "category=Runtime"); tracer.traceString(runtimeMXBean.getVmVendor(), "VmVendor", "platform=JVM", "category=Runtime"); tracer.traceString(runtimeMXBean.getVmVersion(), "VmVersion", "platform=JVM", "category=Runtime"); tracer.traceString(runtimeMXBean.getInputArguments().toString(), "InputArguments", "platform=JVM", "category=Runtime"); tracer.traceString(runtimeMXBean.getName(), "RuntimeName", "platform=JVM", "category=Runtime"); initialRuntimeCollected = true; } /** * Collects dynamic runtime info * FIXME: Uptime needs long last */ protected void collectRuntime() { tracer.traceGauge(runtimeMXBean.getUptime(), "UpTime", "platform=JVM", "category=Runtime"); } /** * Calcs a percentage * @param total The total amount * @param part The part of the total * @return The percentage that the part is of the total */ protected long percent(double total, double part) { if(total==0 || part==0) { return 0L; } double d = part/total*100; return (long)d; } }