blob: e8eb1a9025049d3f09250a229449dee83509c78d [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.
*/
/**
* class ExportThreadsAction
* @author Eugene Zhuravlev
* @author Sascha Weinreuter
*/
package com.intellij.debugger.actions;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerManagerEx;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.ui.DebuggerSessionTab;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.Project;
import com.intellij.unscramble.ThreadDumpParser;
import com.intellij.unscramble.ThreadState;
import com.intellij.util.SmartList;
import com.intellij.xdebugger.XDebugSession;
import com.sun.jdi.*;
import gnu.trove.TIntObjectHashMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ThreadDumpAction extends AnAction implements AnAction.TransparentUpdate {
public void actionPerformed(AnActionEvent e) {
final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
if (project == null) {
return;
}
DebuggerContextImpl context = (DebuggerManagerEx.getInstanceEx(project)).getContext();
final DebuggerSession session = context.getDebuggerSession();
if(session != null && session.isAttached()) {
final DebugProcessImpl process = context.getDebugProcess();
process.getManagerThread().invoke(new DebuggerCommandImpl() {
protected void action() throws Exception {
final VirtualMachineProxyImpl vm = process.getVirtualMachineProxy();
vm.suspend();
try {
final List<ThreadState> threads = buildThreadStates(vm);
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
XDebugSession xSession = session.getXDebugSession();
if (xSession != null) {
DebuggerSessionTab.addThreadDump(project, threads, xSession.getUI(), session);
}
}
}, ModalityState.NON_MODAL);
}
finally {
vm.resume();
}
}
});
}
}
private static List<ThreadState> buildThreadStates(VirtualMachineProxyImpl vmProxy) {
final List<ThreadReference> threads = vmProxy.getVirtualMachine().allThreads();
final List<ThreadState> result = new ArrayList<ThreadState>();
final Map<String, ThreadState> nameToThreadMap = new HashMap<String, ThreadState>();
final Map<String, String> waitingMap = new HashMap<String, String>(); // key 'waits_for' value
for (ThreadReference threadReference : threads) {
final StringBuilder buffer = new StringBuilder();
boolean hasEmptyStack = true;
final int threadStatus = threadReference.status();
if (threadStatus == ThreadReference.THREAD_STATUS_ZOMBIE) {
continue;
}
final String threadName = threadName(threadReference);
final ThreadState threadState = new ThreadState(threadName, threadStatusToState(threadStatus));
nameToThreadMap.put(threadName, threadState);
result.add(threadState);
threadState.setJavaThreadState(threadStatusToJavaThreadState(threadStatus));
buffer.append("\"").append(threadName).append("\"");
ReferenceType referenceType = threadReference.referenceType();
if (referenceType != null) {
//noinspection HardCodedStringLiteral
Field daemon = referenceType.fieldByName("daemon");
if (daemon != null) {
Value value = threadReference.getValue(daemon);
if (value instanceof BooleanValue && ((BooleanValue)value).booleanValue()) {
buffer.append(" ").append(DebuggerBundle.message("threads.export.attribute.label.daemon"));
threadState.setDaemon(true);
}
}
//noinspection HardCodedStringLiteral
Field priority = referenceType.fieldByName("priority");
if (priority != null) {
Value value = threadReference.getValue(priority);
if (value instanceof IntegerValue) {
buffer.append(" ").append(DebuggerBundle.message("threads.export.attribute.label.priority", ((IntegerValue)value).intValue()));
}
}
Field tid = referenceType.fieldByName("tid");
if (tid != null) {
Value value = threadReference.getValue(tid);
if (value instanceof LongValue) {
buffer.append(" ").append(DebuggerBundle.message("threads.export.attribute.label.tid", Long.toHexString(((LongValue)value).longValue())));
buffer.append(" nid=NA");
}
}
}
//ThreadGroupReference groupReference = threadReference.threadGroup();
//if (groupReference != null) {
// buffer.append(", ").append(DebuggerBundle.message("threads.export.attribute.label.group", groupReference.name()));
//}
final String state = threadState.getState();
if (state != null) {
buffer.append(" ").append(state);
}
buffer.append("\n java.lang.Thread.State: ").append(threadState.getJavaThreadState());
try {
if (vmProxy.canGetOwnedMonitorInfo() && vmProxy.canGetMonitorInfo()) {
List<ObjectReference> list = threadReference.ownedMonitors();
for (ObjectReference reference : list) {
if (!vmProxy.canGetMonitorFrameInfo()) { // java 5 and earlier
buffer.append("\n\t ").append(renderLockedObject(reference));
}
final List<ThreadReference> waiting = reference.waitingThreads();
for (ThreadReference thread : waiting) {
final String waitingThreadName = threadName(thread);
waitingMap.put(waitingThreadName, threadName);
buffer.append("\n\t ").append(DebuggerBundle.message("threads.export.attribute.label.blocks.thread", waitingThreadName));
}
}
}
ObjectReference waitedMonitor = vmProxy.canGetCurrentContendedMonitor() ? threadReference.currentContendedMonitor() : null;
if (waitedMonitor != null) {
if (vmProxy.canGetMonitorInfo()) {
ThreadReference waitedMonitorOwner = waitedMonitor.owningThread();
if (waitedMonitorOwner != null) {
final String monitorOwningThreadName = threadName(waitedMonitorOwner);
waitingMap.put(threadName, monitorOwningThreadName);
buffer.append("\n\t ")
.append(DebuggerBundle.message("threads.export.attribute.label.waiting.for.thread", monitorOwningThreadName, renderObject(waitedMonitor)));
}
}
}
final List<StackFrame> frames = threadReference.frames();
hasEmptyStack = frames.size() == 0;
final TIntObjectHashMap<List<ObjectReference>> lockedAt = new TIntObjectHashMap<List<ObjectReference>>();
if (vmProxy.canGetMonitorFrameInfo()) {
for (MonitorInfo info : threadReference.ownedMonitorsAndFrames()) {
final int stackDepth = info.stackDepth();
List<ObjectReference> monitors;
if ((monitors = lockedAt.get(stackDepth)) == null) {
lockedAt.put(stackDepth, monitors = new SmartList<ObjectReference>());
}
monitors.add(info.monitor());
}
}
for (int i = 0, framesSize = frames.size(); i < framesSize; i++) {
final StackFrame stackFrame = frames.get(i);
try {
final Location location = stackFrame.location();
buffer.append("\n\t ").append(renderLocation(location));
final List<ObjectReference> monitors = lockedAt.get(i);
if (monitors != null) {
for (ObjectReference monitor : monitors) {
buffer.append("\n\t - ").append(renderLockedObject(monitor));
}
}
}
catch (InvalidStackFrameException e) {
buffer.append("\n\t Invalid stack frame: ").append(e.getMessage());
}
}
}
catch (IncompatibleThreadStateException e) {
buffer.append("\n\t ").append(DebuggerBundle.message("threads.export.attribute.error.incompatible.state"));
}
threadState.setStackTrace(buffer.toString(), hasEmptyStack);
ThreadDumpParser.inferThreadStateDetail(threadState);
}
for (String waiting : waitingMap.keySet()) {
final ThreadState waitingThread = nameToThreadMap.get(waiting);
final ThreadState awaitedThread = nameToThreadMap.get(waitingMap.get(waiting));
awaitedThread.addWaitingThread(waitingThread);
}
// detect simple deadlocks
for (ThreadState thread : result) {
for (ThreadState awaitingThread : thread.getAwaitingThreads()) {
if (awaitingThread.isAwaitedBy(thread)) {
thread.addDeadlockedThread(awaitingThread);
awaitingThread.addDeadlockedThread(thread);
}
}
}
ThreadDumpParser.sortThreads(result);
return result;
}
private static String renderLockedObject(ObjectReference monitor) {
return DebuggerBundle.message("threads.export.attribute.label.locked", renderObject(monitor));
}
public static String renderObject(ObjectReference monitor) {
String monitorTypeName;
try {
monitorTypeName = monitor.referenceType().name();
}
catch (Throwable e) {
monitorTypeName = "Error getting object type: '" + e.getMessage() + "'";
}
return DebuggerBundle.message("threads.export.attribute.label.object-id", Long.toHexString(monitor.uniqueID()), monitorTypeName);
}
private static String threadStatusToJavaThreadState(int status) {
switch (status) {
case ThreadReference.THREAD_STATUS_MONITOR:
return Thread.State.BLOCKED.name();
case ThreadReference.THREAD_STATUS_NOT_STARTED:
return Thread.State.NEW.name();
case ThreadReference.THREAD_STATUS_RUNNING:
return Thread.State.RUNNABLE.name();
case ThreadReference.THREAD_STATUS_SLEEPING:
return Thread.State.TIMED_WAITING.name();
case ThreadReference.THREAD_STATUS_WAIT:
return Thread.State.WAITING.name();
case ThreadReference.THREAD_STATUS_ZOMBIE:
return Thread.State.TERMINATED.name();
case ThreadReference.THREAD_STATUS_UNKNOWN:
return "unknown";
default:
return "undefined";
}
}
private static String threadStatusToState(int status) {
switch (status) {
case ThreadReference.THREAD_STATUS_MONITOR:
return "waiting for monitor entry";
case ThreadReference.THREAD_STATUS_NOT_STARTED:
return "not started";
case ThreadReference.THREAD_STATUS_RUNNING:
return "runnable";
case ThreadReference.THREAD_STATUS_SLEEPING:
return "sleeping";
case ThreadReference.THREAD_STATUS_WAIT:
return "waiting";
case ThreadReference.THREAD_STATUS_ZOMBIE:
return "zombie";
case ThreadReference.THREAD_STATUS_UNKNOWN:
return "unknown";
default:
return "undefined";
}
}
public static String renderLocation(final Location location) {
String sourceName;
try {
sourceName = location.sourceName();
}
catch (Throwable e) {
sourceName = "Unknown Source";
}
final StringBuilder methodName = new StringBuilder();
try {
methodName.append(location.declaringType().name());
}
catch (Throwable e) {
methodName.append(e.getMessage());
}
methodName.append(".");
try {
methodName.append(location.method().name());
}
catch (Throwable e) {
methodName.append(e.getMessage());
}
int lineNumber;
try {
lineNumber = location.lineNumber();
}
catch (Throwable e) {
lineNumber = -1;
}
return DebuggerBundle.message("export.threads.stackframe.format", methodName.toString(), sourceName, lineNumber);
}
private static String threadName(ThreadReference threadReference) {
return threadReference.name() + "@" + threadReference.uniqueID();
}
public void update(AnActionEvent event){
Presentation presentation = event.getPresentation();
Project project = CommonDataKeys.PROJECT.getData(event.getDataContext());
if (project == null) {
presentation.setEnabled(false);
return;
}
DebuggerSession debuggerSession = (DebuggerManagerEx.getInstanceEx(project)).getContext().getDebuggerSession();
presentation.setEnabled(debuggerSession != null && debuggerSession.isAttached());
}
}