/* * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 David Berkman * * This file is part of the SmallMind Code Project. * * The SmallMind Code Project is free software, you can redistribute * it and/or modify it under either, at your discretion... * * 1) The terms of GNU Affero General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * ...or... * * 2) The terms of the Apache License, Version 2.0. * * The SmallMind Code Project 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 * General Public License or Apache License for more details. * * You should have received a copy of the GNU Affero General Public License * and the Apache License along with the SmallMind Code Project. If not, see * <http://www.gnu.org/licenses/> or <http://www.apache.org/licenses/LICENSE-2.0>. * * Additional permission under the GNU Affero GPL version 3 section 7 * ------------------------------------------------------------------ * If you modify this Program, or any covered work, by linking or * combining it with other code, such other code is not for that reason * alone subject to any of the requirements of the GNU Affero GPL * version 3. */ package org.smallmind.instrument; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.Thread.State; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.LockInfo; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.lang.management.MonitorInfo; import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.ObjectName; public class MachineVitals { private static final MachineVitals INSTANCE = new MachineVitals(ManagementFactory.getMemoryMXBean(), ManagementFactory.getMemoryPoolMXBeans(), ManagementFactory.getOperatingSystemMXBean(), ManagementFactory.getThreadMXBean(), ManagementFactory.getGarbageCollectorMXBeans(), ManagementFactory.getRuntimeMXBean(), ManagementFactory.getPlatformMBeanServer()); private static final int MAX_STACK_TRACE_DEPTH = 100; private final MBeanServer mBeanServer; private final MemoryMXBean memory; private final OperatingSystemMXBean os; private final ThreadMXBean threads; private final List<MemoryPoolMXBean> memoryPools; private final List<GarbageCollectorMXBean> garbageCollectors; private final RuntimeMXBean runtime; MachineVitals (MemoryMXBean memory, List<MemoryPoolMXBean> memoryPools, OperatingSystemMXBean os, ThreadMXBean threads, List<GarbageCollectorMXBean> garbageCollectors, RuntimeMXBean runtime, MBeanServer mBeanServer) { this.memory = memory; this.memoryPools = memoryPools; this.os = os; this.threads = threads; this.garbageCollectors = garbageCollectors; this.runtime = runtime; this.mBeanServer = mBeanServer; } public static MachineVitals getInstance () { return INSTANCE; } public String getVersion () { return System.getProperty("java.runtime.version"); } public String getName () { return System.getProperty("java.vm.name"); } public long getUptime () { return TimeUnit.MILLISECONDS.toSeconds(runtime.getUptime()); } public int getThreadCount () { return threads.getThreadCount(); } public int getDaemonThreadCount () { return threads.getDaemonThreadCount(); } public double getTotalInit () { return memory.getHeapMemoryUsage().getInit() + memory.getNonHeapMemoryUsage().getInit(); } public double getTotalUsed () { return memory.getHeapMemoryUsage().getUsed() + memory.getNonHeapMemoryUsage().getUsed(); } public double getTotalMax () { return memory.getHeapMemoryUsage().getMax() + memory.getNonHeapMemoryUsage().getMax(); } public double getTotalCommitted () { return memory.getHeapMemoryUsage().getCommitted() + memory.getNonHeapMemoryUsage().getCommitted(); } public double getHeapInit () { return memory.getHeapMemoryUsage().getInit(); } public double getHeapUsed () { return memory.getHeapMemoryUsage().getUsed(); } public double getHeapMax () { return memory.getHeapMemoryUsage().getMax(); } public double getHeapCommitted () { return memory.getHeapMemoryUsage().getCommitted(); } public double getHeapUsage () { final MemoryUsage usage = memory.getHeapMemoryUsage(); return usage.getUsed() / (double)usage.getMax(); } public double getNonHeapUsage () { final MemoryUsage usage = memory.getNonHeapMemoryUsage(); return usage.getUsed() / (double)usage.getMax(); } public Map<String, Double> getMemoryPoolUsage () { final Map<String, Double> pools = new TreeMap<String, Double>(); for (MemoryPoolMXBean pool : memoryPools) { final double max = pool.getUsage().getMax() == -1 ? pool.getUsage().getCommitted() : pool.getUsage().getMax(); pools.put(pool.getName(), pool.getUsage().getUsed() / max); } return Collections.unmodifiableMap(pools); } public double getFileDescriptorUsage () { try { final Method getOpenFileDescriptorCount = os.getClass().getDeclaredMethod("getOpenFileDescriptorCount"); final Long openFds = (Long)getOpenFileDescriptorCount.invoke(os); final Method getMaxFileDescriptorCount = os.getClass().getDeclaredMethod("getMaxFileDescriptorCount"); final Long maxFds = (Long)getMaxFileDescriptorCount.invoke(os); getOpenFileDescriptorCount.setAccessible(true); getMaxFileDescriptorCount.setAccessible(true); return openFds.doubleValue() / maxFds.doubleValue(); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) { return Double.NaN; } } public Map<String, GarbageCollectorStats> getGarbageCollectors () { final Map<String, GarbageCollectorStats> stats = new HashMap<String, GarbageCollectorStats>(); for (GarbageCollectorMXBean gc : garbageCollectors) { stats.put(gc.getName(), new GarbageCollectorStats(gc.getCollectionCount(), gc.getCollectionTime())); } return Collections.unmodifiableMap(stats); } public Set<String> getDeadlockedThreads () { final long[] threadIds = threads.findDeadlockedThreads(); if (threadIds != null) { final Set<String> threads = new HashSet<String>(); for (ThreadInfo info : this.threads.getThreadInfo(threadIds, MAX_STACK_TRACE_DEPTH)) { final StringBuilder stackTrace = new StringBuilder(); for (StackTraceElement element : info.getStackTrace()) { stackTrace.append("\t at ").append(element.toString()).append('\n'); } threads.add(String.format("%s locked on %s (owned by %s):\n%s", info.getThreadName(), info.getLockName(), info.getLockOwnerName(), stackTrace.toString())); } return Collections.unmodifiableSet(threads); } return Collections.emptySet(); } public Map<State, Double> getThreadStatePercentages () { final Map<State, Double> conditions = new HashMap<State, Double>(); for (State state : State.values()) { conditions.put(state, 0.0); } final long[] allThreadIds = threads.getAllThreadIds(); final ThreadInfo[] allThreads = threads.getThreadInfo(allThreadIds); int liveCount = 0; for (ThreadInfo info : allThreads) { if (info != null) { final State state = info.getThreadState(); conditions.put(state, conditions.get(state) + 1); liveCount++; } } for (State state : new ArrayList<State>(conditions.keySet())) { conditions.put(state, conditions.get(state) / liveCount); } return Collections.unmodifiableMap(conditions); } public void getThreadDump (OutputStream out) { final ThreadInfo[] threads = this.threads.dumpAllThreads(true, true); final PrintWriter writer = new PrintWriter(out, true); for (int ti = threads.length - 1; ti >= 0; ti--) { final ThreadInfo t = threads[ti]; writer.printf("%s id=%d state=%s", t.getThreadName(), t.getThreadId(), t.getThreadState()); final LockInfo lock = t.getLockInfo(); if (lock != null && t.getThreadState() != Thread.State.BLOCKED) { writer.printf("\n - waiting on <0x%08x> (a %s)", lock.getIdentityHashCode(), lock.getClassName()); writer.printf("\n - locked <0x%08x> (a %s)", lock.getIdentityHashCode(), lock.getClassName()); } else if (lock != null && t.getThreadState() == Thread.State.BLOCKED) { writer.printf("\n - waiting to lock <0x%08x> (a %s)", lock.getIdentityHashCode(), lock.getClassName()); } if (t.isSuspended()) { writer.print(" (suspended)"); } if (t.isInNative()) { writer.print(" (running in native)"); } writer.println(); if (t.getLockOwnerName() != null) { writer.printf(" owned by %s id=%d\n", t.getLockOwnerName(), t.getLockOwnerId()); } final StackTraceElement[] elements = t.getStackTrace(); final MonitorInfo[] monitors = t.getLockedMonitors(); for (int i = 0; i < elements.length; i++) { final StackTraceElement element = elements[i]; writer.printf(" at %s\n", element); for (int j = 1; j < monitors.length; j++) { final MonitorInfo monitor = monitors[j]; if (monitor.getLockedStackDepth() == i) { writer.printf(" - locked %s\n", monitor); } } } writer.println(); final LockInfo[] locks = t.getLockedSynchronizers(); if (locks.length > 0) { writer.printf(" Locked synchronizers: count = %d\n", locks.length); for (LockInfo l : locks) { writer.printf(" - %s\n", l); } writer.println(); } } writer.println(); writer.flush(); } public Map<String, BufferPoolStats> getBufferPoolStats () { try { final String[] attributes = {"Count", "MemoryUsed", "TotalCapacity"}; final ObjectName direct = new ObjectName("java.nio:type=BufferPool,name=direct"); final ObjectName mapped = new ObjectName("java.nio:type=BufferPool,name=mapped"); final AttributeList directAttributes = mBeanServer.getAttributes(direct, attributes); final AttributeList mappedAttributes = mBeanServer.getAttributes(mapped, attributes); final Map<String, BufferPoolStats> stats = new TreeMap<String, BufferPoolStats>(); final BufferPoolStats directStats = new BufferPoolStats((Long)((Attribute)directAttributes.get(0)).getValue(), (Long)((Attribute)directAttributes.get(1)).getValue(), (Long)((Attribute)directAttributes.get(2)).getValue()); stats.put("direct", directStats); final BufferPoolStats mappedStats = new BufferPoolStats((Long)((Attribute)mappedAttributes.get(0)).getValue(), (Long)((Attribute)mappedAttributes.get(1)).getValue(), (Long)((Attribute)mappedAttributes.get(2)).getValue()); stats.put("mapped", mappedStats); return Collections.unmodifiableMap(stats); } catch (JMException e) { return Collections.emptyMap(); } } // Per-GC statistics. public static class GarbageCollectorStats { private final long runs, timeMS; private GarbageCollectorStats (long runs, long timeMS) { this.runs = runs; this.timeMS = timeMS; } public long getRuns () { return runs; } public long getTime (TimeUnit unit) { return unit.convert(timeMS, TimeUnit.MILLISECONDS); } } public static class BufferPoolStats { private final long count, memoryUsed, totalCapacity; private BufferPoolStats (long count, long memoryUsed, long totalCapacity) { this.count = count; this.memoryUsed = memoryUsed; this.totalCapacity = totalCapacity; } public long getCount () { return count; } public long getMemoryUsed () { return memoryUsed; } public long getTotalCapacity () { return totalCapacity; } } }