96e6d015f02628e16fba5794891f5496500ff064
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / actions / ThreadDumpAction.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * class ExportThreadsAction
19  * @author Eugene Zhuravlev
20  * @author Sascha Weinreuter
21  */
22 package com.intellij.debugger.actions;
23
24 import com.intellij.debugger.DebuggerBundle;
25 import com.intellij.debugger.DebuggerManagerEx;
26 import com.intellij.debugger.engine.DebugProcessImpl;
27 import com.intellij.debugger.engine.events.DebuggerCommandImpl;
28 import com.intellij.debugger.impl.DebuggerContextImpl;
29 import com.intellij.debugger.impl.DebuggerSession;
30 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
31 import com.intellij.debugger.ui.DebuggerSessionTab;
32 import com.intellij.openapi.actionSystem.AnAction;
33 import com.intellij.openapi.actionSystem.AnActionEvent;
34 import com.intellij.openapi.actionSystem.CommonDataKeys;
35 import com.intellij.openapi.actionSystem.Presentation;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.application.ModalityState;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.unscramble.ThreadDumpParser;
40 import com.intellij.unscramble.ThreadState;
41 import com.intellij.util.SmartList;
42 import com.intellij.xdebugger.XDebugSession;
43 import com.sun.jdi.*;
44 import gnu.trove.TIntObjectHashMap;
45
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.Map;
50
51 public class ThreadDumpAction extends AnAction implements AnAction.TransparentUpdate {
52
53   public void actionPerformed(AnActionEvent e) {
54     final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
55     if (project == null) {
56       return;
57     }
58     DebuggerContextImpl context = (DebuggerManagerEx.getInstanceEx(project)).getContext();
59
60     final DebuggerSession session = context.getDebuggerSession();
61     if(session != null && session.isAttached()) {
62       final DebugProcessImpl process = context.getDebugProcess();
63       process.getManagerThread().invoke(new DebuggerCommandImpl() {
64         protected void action() throws Exception {
65           final VirtualMachineProxyImpl vm = process.getVirtualMachineProxy();
66           vm.suspend();
67           try {
68             final List<ThreadState> threads = buildThreadStates(vm);
69             ApplicationManager.getApplication().invokeLater(new Runnable() {
70               public void run() {
71                 XDebugSession xSession = session.getXDebugSession();
72                 if (xSession != null) {
73                   DebuggerSessionTab.addThreadDump(project, threads, xSession.getUI(), session);
74                 }
75               }
76             }, ModalityState.NON_MODAL);
77           }
78           finally {
79             vm.resume();
80           }
81         }
82       });
83     }
84   }
85
86   static List<ThreadState> buildThreadStates(VirtualMachineProxyImpl vmProxy) {
87     final List<ThreadReference> threads = vmProxy.getVirtualMachine().allThreads();
88     final List<ThreadState> result = new ArrayList<ThreadState>();
89     final Map<String, ThreadState> nameToThreadMap = new HashMap<String, ThreadState>();
90     final Map<String, String> waitingMap = new HashMap<String, String>(); // key 'waits_for' value
91     for (ThreadReference threadReference : threads) {
92       final StringBuilder buffer = new StringBuilder();
93       boolean hasEmptyStack = true;
94       final int threadStatus = threadReference.status();
95       if (threadStatus == ThreadReference.THREAD_STATUS_ZOMBIE) {
96         continue;
97       }
98       final String threadName = threadName(threadReference);
99       final ThreadState threadState = new ThreadState(threadName, threadStatusToState(threadStatus));
100       nameToThreadMap.put(threadName, threadState);
101       result.add(threadState);
102       threadState.setJavaThreadState(threadStatusToJavaThreadState(threadStatus));
103
104       buffer.append("\"").append(threadName).append("\"");
105       ReferenceType referenceType = threadReference.referenceType();
106       if (referenceType != null) {
107         //noinspection HardCodedStringLiteral
108         Field daemon = referenceType.fieldByName("daemon");
109         if (daemon != null) {
110           Value value = threadReference.getValue(daemon);
111           if (value instanceof BooleanValue && ((BooleanValue)value).booleanValue()) {
112             buffer.append(" ").append(DebuggerBundle.message("threads.export.attribute.label.daemon"));
113             threadState.setDaemon(true);
114           }
115         }
116
117         //noinspection HardCodedStringLiteral
118         Field priority = referenceType.fieldByName("priority");
119         if (priority != null) {
120           Value value = threadReference.getValue(priority);
121           if (value instanceof IntegerValue) {
122             buffer.append(" ").append(DebuggerBundle.message("threads.export.attribute.label.priority", ((IntegerValue)value).intValue()));
123           }
124         }
125
126         Field tid = referenceType.fieldByName("tid");
127         if (tid != null) {
128           Value value = threadReference.getValue(tid);
129           if (value instanceof LongValue) {
130             buffer.append(" ").append(DebuggerBundle.message("threads.export.attribute.label.tid", Long.toHexString(((LongValue)value).longValue())));
131             buffer.append(" nid=NA");
132           }
133         }
134       }
135       //ThreadGroupReference groupReference = threadReference.threadGroup();
136       //if (groupReference != null) {
137       //  buffer.append(", ").append(DebuggerBundle.message("threads.export.attribute.label.group", groupReference.name()));
138       //}
139       final String state = threadState.getState();
140       if (state != null) {
141         buffer.append(" ").append(state);
142       }
143
144       buffer.append("\n  java.lang.Thread.State: ").append(threadState.getJavaThreadState());
145       
146       try {
147         if (vmProxy.canGetOwnedMonitorInfo() && vmProxy.canGetMonitorInfo()) {
148           List<ObjectReference> list = threadReference.ownedMonitors();
149           for (ObjectReference reference : list) {
150             if (!vmProxy.canGetMonitorFrameInfo()) { // java 5 and earlier
151               buffer.append("\n\t ").append(renderLockedObject(reference));
152             }
153             final List<ThreadReference> waiting = reference.waitingThreads();
154             for (ThreadReference thread : waiting) {
155               final String waitingThreadName = threadName(thread);
156               waitingMap.put(waitingThreadName, threadName);
157               buffer.append("\n\t ").append(DebuggerBundle.message("threads.export.attribute.label.blocks.thread", waitingThreadName));
158             }
159           }
160         }
161
162         ObjectReference waitedMonitor = vmProxy.canGetCurrentContendedMonitor() ? threadReference.currentContendedMonitor() : null;
163         if (waitedMonitor != null) {
164           if (vmProxy.canGetMonitorInfo()) {
165             ThreadReference waitedMonitorOwner = waitedMonitor.owningThread();
166             if (waitedMonitorOwner != null) {
167               final String monitorOwningThreadName = threadName(waitedMonitorOwner);
168               waitingMap.put(threadName, monitorOwningThreadName);
169               buffer.append("\n\t ")
170                 .append(DebuggerBundle.message("threads.export.attribute.label.waiting.for.thread", monitorOwningThreadName, renderObject(waitedMonitor)));
171             }
172           }
173         }
174
175         final List<StackFrame> frames = threadReference.frames();
176         hasEmptyStack = frames.size() == 0;
177
178         final TIntObjectHashMap<List<ObjectReference>> lockedAt = new TIntObjectHashMap<List<ObjectReference>>();
179         if (vmProxy.canGetMonitorFrameInfo()) {
180           for (MonitorInfo info : threadReference.ownedMonitorsAndFrames()) {
181             final int stackDepth = info.stackDepth();
182             List<ObjectReference> monitors;
183             if ((monitors = lockedAt.get(stackDepth)) == null) {
184               lockedAt.put(stackDepth, monitors = new SmartList<ObjectReference>());
185             }
186             monitors.add(info.monitor());
187           }
188         }
189
190         for (int i = 0, framesSize = frames.size(); i < framesSize; i++) {
191           final StackFrame stackFrame = frames.get(i);
192           try {
193             final Location location = stackFrame.location();
194             buffer.append("\n\t  ").append(renderLocation(location));
195
196             final List<ObjectReference> monitors = lockedAt.get(i);
197             if (monitors != null) {
198               for (ObjectReference monitor : monitors) {
199                 buffer.append("\n\t  - ").append(renderLockedObject(monitor));
200               }
201             }
202           }
203           catch (InvalidStackFrameException e) {
204             buffer.append("\n\t  Invalid stack frame: ").append(e.getMessage());
205           }
206         }
207       }
208       catch (IncompatibleThreadStateException e) {
209         buffer.append("\n\t ").append(DebuggerBundle.message("threads.export.attribute.error.incompatible.state"));
210       }
211       threadState.setStackTrace(buffer.toString(), hasEmptyStack);
212       ThreadDumpParser.inferThreadStateDetail(threadState);
213     }
214
215     for (String waiting : waitingMap.keySet()) {
216       final ThreadState waitingThread = nameToThreadMap.get(waiting);
217       final ThreadState awaitedThread = nameToThreadMap.get(waitingMap.get(waiting));
218       awaitedThread.addWaitingThread(waitingThread);
219     }
220
221     // detect simple deadlocks
222     for (ThreadState thread : result) {
223       for (ThreadState awaitingThread : thread.getAwaitingThreads()) {
224         if (awaitingThread.isAwaitedBy(thread)) {
225           thread.addDeadlockedThread(awaitingThread);
226           awaitingThread.addDeadlockedThread(thread);
227         }
228       }
229     }
230
231     ThreadDumpParser.sortThreads(result);
232     return result;
233   }
234
235   private static String renderLockedObject(ObjectReference monitor) {
236     return DebuggerBundle.message("threads.export.attribute.label.locked", renderObject(monitor));
237   }
238
239   public static String renderObject(ObjectReference monitor) {
240     String monitorTypeName;
241     try {
242       monitorTypeName = monitor.referenceType().name();
243     }
244     catch (Throwable e) {
245       monitorTypeName = "Error getting object type: '" + e.getMessage() + "'";
246     }
247     return DebuggerBundle.message("threads.export.attribute.label.object-id", Long.toHexString(monitor.uniqueID()), monitorTypeName);
248   }
249
250   private static String threadStatusToJavaThreadState(int status) {
251     switch (status) {
252       case ThreadReference.THREAD_STATUS_MONITOR:
253         return Thread.State.BLOCKED.name();
254       case ThreadReference.THREAD_STATUS_NOT_STARTED:
255         return Thread.State.NEW.name();
256       case ThreadReference.THREAD_STATUS_RUNNING:
257         return Thread.State.RUNNABLE.name();
258       case ThreadReference.THREAD_STATUS_SLEEPING:
259         return Thread.State.TIMED_WAITING.name();
260       case ThreadReference.THREAD_STATUS_WAIT:
261         return Thread.State.WAITING.name();
262       case ThreadReference.THREAD_STATUS_ZOMBIE:
263         return Thread.State.TERMINATED.name();
264       case ThreadReference.THREAD_STATUS_UNKNOWN:
265         return "unknown";
266       default:
267         return "undefined";
268     }
269   }
270
271   private static String threadStatusToState(int status) {
272     switch (status) {
273       case ThreadReference.THREAD_STATUS_MONITOR:
274         return "waiting for monitor entry";
275       case ThreadReference.THREAD_STATUS_NOT_STARTED:
276         return "not started";
277       case ThreadReference.THREAD_STATUS_RUNNING:
278         return "runnable";
279       case ThreadReference.THREAD_STATUS_SLEEPING:
280         return "sleeping";
281       case ThreadReference.THREAD_STATUS_WAIT:
282         return "waiting";
283       case ThreadReference.THREAD_STATUS_ZOMBIE:
284         return "zombie";
285       case ThreadReference.THREAD_STATUS_UNKNOWN:
286         return "unknown";
287       default:
288         return "undefined";
289     }
290   }
291
292   public static String renderLocation(final Location location) {
293     String sourceName;
294     try {
295       sourceName = location.sourceName();
296     }
297     catch (Throwable e) {
298       sourceName = "Unknown Source";
299     }
300
301     final StringBuilder methodName = new StringBuilder();
302     try {
303       methodName.append(location.declaringType().name());
304     }
305     catch (Throwable e) {
306       methodName.append(e.getMessage());
307     }
308     methodName.append(".");
309     try {
310       methodName.append(location.method().name());
311     }
312     catch (Throwable e) {
313       methodName.append(e.getMessage());
314     }
315
316     int lineNumber;
317     try {
318       lineNumber = location.lineNumber();
319     }
320     catch (Throwable e) {
321       lineNumber = -1;
322     }
323     return DebuggerBundle.message("export.threads.stackframe.format", methodName.toString(), sourceName, lineNumber);
324   }
325
326   private static String threadName(ThreadReference threadReference) {
327     return threadReference.name() + "@" + threadReference.uniqueID();
328   }
329
330
331   public void update(AnActionEvent event){
332     Presentation presentation = event.getPresentation();
333     Project project = CommonDataKeys.PROJECT.getData(event.getDataContext());
334     if (project == null) {
335       presentation.setEnabled(false);
336       return;
337     }
338     DebuggerSession debuggerSession = (DebuggerManagerEx.getInstanceEx(project)).getContext().getDebuggerSession();
339     presentation.setEnabled(debuggerSession != null && debuggerSession.isAttached());
340   }
341 }