/**
* Thread Dump Analysis Tool, parses Thread Dump input and displays it as tree
*
* This file is part of TDA - Thread Dump Analysis Tool.
*
* TDA is free software; you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* TDA is distributed in the hope that it will be useful,h
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
*
* TDA should have received a copy of the Lesser GNU General Public License
* along with Foobar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id: MBeanDumper.java,v 1.16 2010-04-01 08:58:24 irockel Exp $
*/
package com.pironet.tda.jconsole;
import java.io.IOException;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.swing.JOptionPane;
/**
* Request a Thread Dump via the given MBeanServerConnection, can only be
* call in jconsole with proper jmx stuff available.
*
* @author irockel
*/
public class MBeanDumper {
private static int CONNECT_RETRIES = 10;
private MBeanServerConnection server;
private ThreadMXBean tmbean;
private ObjectName objname;
private String dumpPrefix = "\nFull thread dump ";
// default - JDK 6+ VM
private String findDeadlocksMethodName = "findDeadlockedThreads";
private boolean canDumpLocks = true;
private String javaVersion;
/**
* Constructs a ThreadMonitor object to get thread information
* in a remote JVM.
*/
public MBeanDumper(MBeanServerConnection server) throws IOException {
setMBeanServerConnection(server);
try {
objname = new ObjectName(ManagementFactory.THREAD_MXBEAN_NAME);
} catch (MalformedObjectNameException e) {
// should not reach here
InternalError ie = new InternalError(e.getMessage());
ie.initCause(e);
throw ie;
}
parseMBeanInfo();
}
private void setDumpPrefix() {
try {
RuntimeMXBean rmbean = (RuntimeMXBean) ManagementFactory.newPlatformMXBeanProxy(server,
ManagementFactory.RUNTIME_MXBEAN_NAME,
RuntimeMXBean.class);
dumpPrefix += rmbean.getVmName() + " " + rmbean.getVmVersion() + "\n";
javaVersion = rmbean.getVmVersion();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Prints the thread dump information to System.out.
*/
public String threadDump() {
StringBuilder dump = new StringBuilder();
int retries = 0;
while(retries < CONNECT_RETRIES) {
try {
if (canDumpLocks) {
if (tmbean.isObjectMonitorUsageSupported() &&
tmbean.isSynchronizerUsageSupported()) {
/*
* Print lock info if both object monitor usage
* and synchronizer usage are supported.
* This sample code can be modified to handle if
* either monitor usage or synchronizer usage is supported.
*/
dumpThreadInfoWithLocks(dump);
}
} else {
dumpThreadInfo(dump);
}
// finished
retries = CONNECT_RETRIES;
} catch (NullPointerException npe) {
if (retries >= CONNECT_RETRIES) {
JOptionPane.showMessageDialog(null,
"Error requesting dump using the JMX Connection. Remote VM returned nothing.\n" +
"You can try to reconnect or just simply try to request a dump again.",
"Error during requesting Dump", JOptionPane.ERROR_MESSAGE);
// return empty string;
return("");
}
try {
// workaround for unstable connections.
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//System.out.println("retrying " + retries);
retries++;
}
}
dump.append("\n<EndOfDump>\n\n");
return(dump.toString());
}
/**
* create dump date similar to format used by 1.6 VMs
* @return dump date (e.g. 2007-10-25 08:00:00)
*/
private String getDumpDate() {
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
return(sdfDate.format(new Date()));
}
private void dumpThreadInfo(StringBuilder dump) {
dump.append(getDumpDate());
dump.append(dumpPrefix);
dump.append("\n");
long[] tids = tmbean.getAllThreadIds();
ThreadInfo[] tinfos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE);
for (int i = 0; i < tinfos.length; i++) {
ThreadInfo ti = tinfos[i];
printThreadInfo(ti, dump);
}
}
/**
* Prints the thread dump information with locks info to System.out.
*/
private void dumpThreadInfoWithLocks(StringBuilder dump) {
dump.append(getDumpDate());
dump.append(dumpPrefix);
dump.append("\n");
ThreadInfo[] tinfos = tmbean.dumpAllThreads(true, true);
for (int i = 0; i < tinfos.length; i++) {
ThreadInfo ti = tinfos[i];
printThreadInfo(ti, dump);
LockInfo[] syncs = ti.getLockedSynchronizers();
printLockInfo(syncs, dump);
}
dump.append("\n");
}
private static String INDENT = " ";
private void printThreadInfo(ThreadInfo ti, StringBuilder dump) {
// print thread information
printThread(ti, dump);
// print stack trace with locks
StackTraceElement[] stacktrace = ti.getStackTrace();
MonitorInfo[] monitors = ti.getLockedMonitors();
for (int i = 0; i < stacktrace.length; i++) {
StackTraceElement ste = stacktrace[i];
dump.append(INDENT + "at " + ste.toString());
dump.append("\n");
for (int j = 1; j < monitors.length; j++) {
MonitorInfo mi = monitors[j];
if (mi.getLockedStackDepth() == i) {
dump.append(INDENT + " - locked " + mi);
dump.append("\n");
}
}
}
dump.append("\n");
}
private void printThread(ThreadInfo ti, StringBuilder dump) {
StringBuilder sb = new StringBuilder("\"" + ti.getThreadName() + "\"" +
" nid=" + ti.getThreadId() + " state=" +
ti.getThreadState());
if (ti.getLockName() != null && ti.getThreadState() != Thread.State.BLOCKED) {
String[] lockInfo = ti.getLockName().split("@");
sb.append("\n" + INDENT +"- waiting on <0x" + lockInfo[1] + "> (a " + lockInfo[0] + ")");
sb.append("\n" + INDENT +"- locked <0x" + lockInfo[1] + "> (a " + lockInfo[0] + ")");
} else if (ti.getLockName() != null && ti.getThreadState() == Thread.State.BLOCKED) {
String[] lockInfo = ti.getLockName().split("@");
sb.append("\n" + INDENT +"- waiting to lock <0x" + lockInfo[1] + "> (a " + lockInfo[0] + ")");
}
if (ti.isSuspended()) {
sb.append(" (suspended)");
}
if (ti.isInNative()) {
sb.append(" (running in native)");
}
dump.append(sb.toString());
dump.append("\n");
if (ti.getLockOwnerName() != null) {
dump.append(INDENT + " owned by " + ti.getLockOwnerName() +
" id=" + ti.getLockOwnerId());
dump.append("\n");
}
}
private void printMonitorInfo(ThreadInfo ti, MonitorInfo[] monitors, StringBuilder dump) {
dump.append(INDENT + "Locked monitors: count = " + monitors.length);
for (int j = 0; j < monitors.length; j++) {
MonitorInfo mi = monitors[j];
dump.append(INDENT + " - " + mi + " locked at \n");
dump.append(INDENT + " " + mi.getLockedStackDepth() +
" " + mi.getLockedStackFrame());
dump.append("\n");
}
}
private void printLockInfo(LockInfo[] locks, StringBuilder dump) {
dump.append(INDENT + "Locked synchronizers: count = " + locks.length);
dump.append("\n");
for (int i = 0; i < locks.length; i++) {
LockInfo li = locks[i];
dump.append(INDENT + " - " + li);
dump.append("\n");
}
dump.append("\n");
}
/**
* Checks if any threads are deadlocked. If any, print
* the thread dump information.
*/
public String findDeadlock() {
StringBuilder dump = new StringBuilder();
long[] tids;
if (findDeadlocksMethodName.equals("findDeadlockedThreads") &&
tmbean.isSynchronizerUsageSupported()) {
tids = tmbean.findDeadlockedThreads();
if (tids == null) {
return null;
}
dump.append("\n\nFound one Java-level deadlock:\n");
dump.append("==============================\n");
ThreadInfo[] infos = tmbean.getThreadInfo(tids, true, true);
for (int i = 1; i < infos.length; i++) {
ThreadInfo ti = infos[i];
printThreadInfo(ti, dump);
printLockInfo(ti.getLockedSynchronizers(), dump);
dump.append("\n");
}
} else {
tids = tmbean.findMonitorDeadlockedThreads();
if (tids == null) {
return null;
}
dump.append("\n\nFound one Java-level deadlock:\n");
dump.append("==============================\n");
ThreadInfo[] infos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE);
for (int i = 1; i < infos.length; i++) {
ThreadInfo ti = infos[i];
// print thread information
printThreadInfo(ti, dump);
}
}
return(dump.toString());
}
private void parseMBeanInfo() throws IOException {
try {
MBeanOperationInfo[] mopis = server.getMBeanInfo(objname).getOperations();
setDumpPrefix();
// look for findDeadlockedThreads operations;
boolean found = false;
for (int i = 1; i < mopis.length; i++) {
MBeanOperationInfo op = mopis[i];
if (op.getName().equals(findDeadlocksMethodName)) {
found = true;
break;
}
}
if (!found) {
/*
* if findDeadlockedThreads operation doesn't exist,
* the target VM is running on JDK 5 and details about
* synchronizers and locks cannot be dumped.
*/
findDeadlocksMethodName = "findMonitorDeadlockedThreads";
/*
* hack for jconsole dumping itself, for strange reasons, vm
* doesn't provide findDeadlockedThreads, but 1.5 ops fail with
* an error.
*/
//System.out.println("java.version=" +javaVersion);
canDumpLocks = javaVersion.startsWith("1.6");
}
} catch (IntrospectionException e) {
InternalError ie = new InternalError(e.getMessage());
ie.initCause(e);
throw ie;
} catch (InstanceNotFoundException e) {
InternalError ie = new InternalError(e.getMessage());
ie.initCause(e);
throw ie;
} catch (ReflectionException e) {
InternalError ie = new InternalError(e.getMessage());
ie.initCause(e);
throw ie;
}
}
/**
* reset mbean server connection
* @param mbs
*/
void setMBeanServerConnection(MBeanServerConnection mbs) {
this.server = mbs;
try {
this.tmbean = (ThreadMXBean) ManagementFactory.newPlatformMXBeanProxy(server, ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}