/** * 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.jmx.threadinfo; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import javax.management.MBeanNotificationInfo; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import org.helios.apmrouter.jmx.JMXHelper; import org.helios.apmrouter.util.SimpleLogger; import org.helios.apmrouter.util.SystemClock; /** * <p>Title: ExtendedThreadManager</p> * <p>Description: A drop in replacement for the standard {@link ThreadMXBean} that adds additional functionality and notifications.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.jmx.threadinfo.ExtendedThreadManager</code></p> */ public class ExtendedThreadManager extends NotificationBroadcasterSupport implements ExtendedThreadManagerMXBean { private static final MBeanNotificationInfo[] notificationInfo = createMBeanInfo(); /** The delegate ThreadMXBean */ protected final ThreadMXBean delegate; /** Indicates if the delegate is installed */ protected static final AtomicBoolean installed = new AtomicBoolean(false); /** The platform MBeanServer */ protected static final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); /** The ThreadMXBean object name */ protected static final ObjectName THREAD_MX_NAME = JMXHelper.objectName(ManagementFactory.THREAD_MXBEAN_NAME); /** The JMX notification type emitted when Thread Contention Monitoring is enabled */ public static final String NOTIF_TCM_ENABLED = "threadmxbean.tcm.enabled"; /** The JMX notification type emitted when Thread Contention Monitoring is disabled */ public static final String NOTIF_TCM_DISABLED = "threadmxbean.tcm.disabled"; /** The JMX notification type emitted when Thread Timing is enabled */ public static final String NOTIF_TCT_ENABLED = "threadmxbean.tct.enabled"; /** The JMX notification type emitted when Thread Timing is disabled */ public static final String NOTIF_TCT_DISABLED = "threadmxbean.tct.disabled"; /** The extended thread manager instance */ private static ExtendedThreadManager mxb = null; /** JMX notification serial number generator */ private static final AtomicLong serial = new AtomicLong(0L); /** The original ThreadMXBean */ public static final ThreadMXBean original = ManagementFactory.getThreadMXBean(); /** Indicates if thread contention monitoring is supported */ public static final boolean TCM_SUPPORTED = original.isThreadContentionMonitoringSupported(); /** Indicates if thread cpu timing monitoring is supported */ public static final boolean TCT_SUPPORTED = original.isThreadCpuTimeSupported(); /** The default max depth to get thread Infos with */ private int maxDepth = Integer.MAX_VALUE; // record initial tct and tcm states, store in statics public static void main(String[] args) { log("Installing Notifier"); install(); try { Thread.currentThread().join(); } catch (Exception ex) {}; } public static void log(Object msg) { System.out.println(msg); } /** * Installs and return the ExtendedThreadManager * @return the ExtendedThreadManager instance */ public static ExtendedThreadManager install() { if(!installed.get()) { mxb = new ExtendedThreadManager(ManagementFactory.getThreadMXBean()); try { server.unregisterMBean(THREAD_MX_NAME); server.registerMBean(mxb, THREAD_MX_NAME); installed.set(true); } catch (Exception ex) { ex.printStackTrace(System.err); throw new RuntimeException("Failed to install ExtendedThreadManager", ex); } } return mxb; } public static void remove() { if(installed.get()) { } } public static boolean isInstalled() { return installed.get(); } /** * Creates a new ExtendedThreadManager * @param delegate the ThreadMXBean delegate */ private ExtendedThreadManager(ThreadMXBean delegate) { super(Executors.newFixedThreadPool(1, new ThreadFactory(){ @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "ThreadMXBeanNotifier"); t.setDaemon(true); return t; } }), notificationInfo); this.delegate = delegate; } @Override public ObjectName getObjectName() { return delegate.getObjectName(); } @Override public int getThreadCount() { return delegate.getThreadCount(); } @Override public int getPeakThreadCount() { return delegate.getPeakThreadCount(); } @Override public long getTotalStartedThreadCount() { return delegate.getTotalStartedThreadCount(); } @Override public int getDaemonThreadCount() { return delegate.getDaemonThreadCount(); } @Override public int getNonDaemonThreadCount() { return delegate.getThreadCount() - delegate.getDaemonThreadCount(); } @Override public long[] getAllThreadIds() { return delegate.getAllThreadIds(); } @Override public ThreadInfo getThreadInfo(long id) { return delegate.getThreadInfo(id); } @Override public ThreadInfo[] getThreadInfo(long[] ids) { return delegate.getThreadInfo(ids); } @Override public ThreadInfo getThreadInfo(long id, int maxDepth) { return delegate.getThreadInfo(id, maxDepth); } @Override public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth) { return delegate.getThreadInfo(ids, maxDepth); } @Override public boolean isThreadContentionMonitoringSupported() { return delegate.isThreadContentionMonitoringSupported(); } @Override public boolean isThreadContentionMonitoringEnabled() { return delegate.isThreadContentionMonitoringEnabled(); } @Override public void setThreadContentionMonitoringEnabled(boolean enable) { delegate.setThreadContentionMonitoringEnabled(enable); if(enable) { sendNotification(new Notification(NOTIF_TCM_ENABLED, THREAD_MX_NAME, serial.incrementAndGet(), SystemClock.time(), "Thread Contention Monitoring Enabled")); } else { sendNotification(new Notification(NOTIF_TCM_DISABLED, THREAD_MX_NAME, serial.incrementAndGet(), SystemClock.time(), "Thread Contention Monitoring Disabled")); } } @Override public long getCurrentThreadCpuTime() { return delegate.getCurrentThreadCpuTime(); } @Override public long getCurrentThreadUserTime() { return delegate.getCurrentThreadUserTime(); } @Override public long getThreadCpuTime(long id) { return delegate.getThreadCpuTime(id); } @Override public long getThreadUserTime(long id) { return delegate.getThreadUserTime(id); } @Override public boolean isThreadCpuTimeSupported() { return delegate.isThreadCpuTimeSupported(); } @Override public boolean isCurrentThreadCpuTimeSupported() { return delegate.isCurrentThreadCpuTimeSupported(); } @Override public boolean isThreadCpuTimeEnabled() { return delegate.isThreadCpuTimeEnabled(); } @Override public void setThreadCpuTimeEnabled(boolean enable) { delegate.setThreadCpuTimeEnabled(enable); if(enable) { sendNotification(new Notification(NOTIF_TCT_ENABLED, THREAD_MX_NAME, serial.incrementAndGet(), SystemClock.time(), "Thread CPU Time Monitoring Enabled")); } else { sendNotification(new Notification(NOTIF_TCT_DISABLED, THREAD_MX_NAME, serial.incrementAndGet(), SystemClock.time(), "Thread CPU Time Monitoring Disabled")); } } @Override public long[] findMonitorDeadlockedThreads() { return delegate.findMonitorDeadlockedThreads(); } @Override public void resetPeakThreadCount() { delegate.resetPeakThreadCount(); } @Override public long[] findDeadlockedThreads() { return delegate.findDeadlockedThreads(); } @Override public boolean isObjectMonitorUsageSupported() { return delegate.isObjectMonitorUsageSupported(); } @Override public boolean isSynchronizerUsageSupported() { return delegate.isSynchronizerUsageSupported(); } @Override public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) { return delegate.getThreadInfo(ids, lockedMonitors, lockedSynchronizers); } /** * {@inheritDoc} * @see java.lang.management.ThreadMXBean#dumpAllThreads(boolean, boolean) */ @Override public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) { return delegate.dumpAllThreads(lockedMonitors, lockedSynchronizers); } /** * {@inheritDoc} * @see org.helios.apmrouter.jmx.threadinfo.ExtendedThreadManagerMXBean#getNonDaemonThreadNames() */ @Override public String[] getNonDaemonThreadNames() { try { Thread[] allThreads = new Thread[getThreadCount()]; Thread.enumerate(allThreads); Set<String> threadNames= new HashSet<String>(); for(Thread t: allThreads) { if(t==null) continue; if(!t.isDaemon()) { threadNames.add(t.toString()); } } return threadNames.toArray(new String[threadNames.size()]); } catch (Exception ex) { ex.printStackTrace(System.err); throw new RuntimeException("Failed to list non-daemon threads:" + ex); } } /* Thread[AWT-Shutdown,5,main] Thread[AWT-EventQueue-0,6,main] Thread[DestroyJavaVM,5,main] Thread[Thread-10,6,main] */ private static MBeanNotificationInfo[] createMBeanInfo() { return new MBeanNotificationInfo[]{ new MBeanNotificationInfo(new String[]{NOTIF_TCM_ENABLED, NOTIF_TCM_DISABLED}, Notification.class.getName(), "Notification indicating if ThreadContentionMonitoring (tcm) enablement has changed"), new MBeanNotificationInfo(new String[]{NOTIF_TCT_ENABLED, NOTIF_TCT_DISABLED}, Notification.class.getName(), "Notification indicating if ThreadContentionMonitoring (tcm) enablement has changed"), }; } /** * {@inheritDoc} * @see org.helios.apmrouter.jmx.threadinfo.ExtendedThreadManagerMXBean#getBusyThreads(long) */ @Override public String[] getBusyThreads(long sampleTime) { SimpleLogger.info("Starting BusyThreads"); ThreadInfo[] infos = delegate.getThreadInfo(delegate.getAllThreadIds()); Map<String, Long> init = new HashMap<String, Long>(infos.length); for(ThreadInfo ti: infos) { init.put(ti.getThreadName() + ":" + ti.getThreadId(), delegate.getThreadCpuTime(ti.getThreadId())); } SimpleLogger.info("BusyThreads Sampling Time [", sampleTime, "] ms."); SystemClock.sleep(sampleTime); SimpleLogger.info("Completed BusyThreads Sampling"); infos = delegate.getThreadInfo(delegate.getAllThreadIds()); Set<BusyThread> bthreads = new TreeSet<BusyThread>(); for(ThreadInfo ti: infos) { String key = ti.getThreadName() + ":" + ti.getThreadId(); if(!init.containsKey(key)) continue; long elapsedCpu = delegate.getThreadCpuTime(ti.getThreadId())-init.get(key); bthreads.add(new BusyThread(elapsedCpu, key)); } String[] out = new String[bthreads.size()]; int cnt = 0; for(BusyThread bt: bthreads) { out[cnt] = bt.toString(); cnt++; } return out; } /** * Returns the max depth used for getting thread infos * @return the max depth used for getting thread infos */ @Override public int getMaxDepth() { return maxDepth; } /** * Sets the max depth used for getting thread infos * @param maxDepth the max depth used for getting thread infos */ @Override public void setMaxDepth(int maxDepth) { this.maxDepth = maxDepth; } /** * {@inheritDoc} * @see org.helios.apmrouter.jmx.threadinfo.ExtendedThreadManagerMXBean#getThreadInfo() */ @Override public ExtendedThreadInfo[] getThreadInfo() { return ExtendedThreadInfo.wrapThreadInfos(delegate.getThreadInfo(delegate.getAllThreadIds(), maxDepth)); } }