blob: 57d45b1a759ffa930e5c04e382202a2028b0ab46 [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.
*/
package com.intellij.unscramble;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author yole
*/
public class ThreadDumpParser {
private static final Pattern ourThreadStartPattern = Pattern.compile("^\\s*\"(.+)\".+(prio=\\d+ (?:os_prio=[^\\s]+ )?tid=[^\\s]+ nid=[^\\s]+|[Ii][Dd]=\\d+) ([^\\[]+)");
private static final Pattern ourThreadStatePattern = Pattern.compile("java\\.lang\\.Thread\\.State: (.+) \\((.+)\\)");
private static final Pattern ourThreadStatePattern2 = Pattern.compile("java\\.lang\\.Thread\\.State: (.+)");
private static final Pattern ourWaitingForLockPattern = Pattern.compile("- waiting (on|to lock) <(.+)>");
private static final Pattern ourParkingToWaitForLockPattern = Pattern.compile("- parking to wait for <(.+)>");
@NonNls private static final String PUMP_EVENT = "java.awt.EventDispatchThread.pumpOneEventForFilters";
private static final Pattern ourIdleTimerThreadPattern = Pattern.compile("java.lang.Object.wait\\([^()]+\\)\\s+at java.util.TimerThread.mainLoop");
private static final Pattern ourIdleSwingTimerThreadPattern = Pattern.compile("java.lang.Object.wait\\([^()]+\\)\\s+at javax.swing.TimerQueue.run");
private ThreadDumpParser() {
}
public static List<ThreadState> parse(String threadDump) {
List<ThreadState> result = new ArrayList<ThreadState>();
StringBuilder lastThreadStack = new StringBuilder();
ThreadState lastThreadState = null;
boolean expectingThreadState = false;
boolean haveNonEmptyStackTrace = false;
for(@NonNls String line: StringUtil.tokenize(threadDump, "\r\n")) {
if (line.startsWith("============") || line.contains("Java-level deadlock")) {
break;
}
ThreadState state = tryParseThreadStart(line);
if (state != null) {
if (lastThreadState != null) {
lastThreadState.setStackTrace(lastThreadStack.toString(), !haveNonEmptyStackTrace);
}
lastThreadState = state;
result.add(lastThreadState);
lastThreadStack.setLength(0);
haveNonEmptyStackTrace = false;
lastThreadStack.append(line).append("\n");
expectingThreadState = true;
}
else {
boolean parsedThreadState = false;
if (expectingThreadState) {
expectingThreadState = false;
parsedThreadState = tryParseThreadState(line, lastThreadState);
}
lastThreadStack.append(line).append("\n");
if (!parsedThreadState && line.trim().startsWith("at")) {
haveNonEmptyStackTrace = true;
}
}
}
if (lastThreadState != null) {
lastThreadState.setStackTrace(lastThreadStack.toString(), !haveNonEmptyStackTrace);
}
for(ThreadState threadState: result) {
inferThreadStateDetail(threadState);
final String s = findWaitingForLock(threadState.getStackTrace());
if (s != null) {
for(ThreadState lockOwner : result) {
if (lockOwner == threadState) {
continue;
}
final String marker = "- locked <" + s + ">";
if (lockOwner.getStackTrace().contains(marker)) {
if (threadState.isAwaitedBy(lockOwner)) {
threadState.addDeadlockedThread(lockOwner);
lockOwner.addDeadlockedThread(threadState);
}
lockOwner.addWaitingThread(threadState);
break;
}
}
}
}
sortThreads(result);
return result;
}
public static void sortThreads(List<ThreadState> result) {
Collections.sort(result, new Comparator<ThreadState>() {
public int compare(final ThreadState o1, final ThreadState o2) {
return getInterestLevel(o2) - getInterestLevel(o1);
}
});
}
@Nullable
private static String findWaitingForLock(final String stackTrace) {
Matcher m = ourWaitingForLockPattern.matcher(stackTrace);
if (m.find()) {
return m.group(2);
}
m = ourParkingToWaitForLockPattern.matcher(stackTrace);
if (m.find()) {
return m.group(1);
}
return null;
}
private static int getInterestLevel(final ThreadState state) {
if (state.isEmptyStackTrace()) return -10;
if (isKnownJdkThread(state)) return -5;
if (state.isSleeping()) {
return -2;
}
if (state.getOperation() == ThreadOperation.Socket) {
return -1;
}
return state.getStackTrace().split("\n").length;
}
public static boolean isKnownJdkThread(final ThreadState state) {
@NonNls String stackTrace = state.getStackTrace();
return stackTrace.contains("java.lang.ref.Reference$ReferenceHandler.run") ||
stackTrace.contains("java.lang.ref.Finalizer$FinalizerThread.run") ||
stackTrace.contains("sun.awt.AWTAutoShutdown.run") ||
stackTrace.contains("sun.java2d.Disposer.run") ||
stackTrace.contains("sun.awt.windows.WToolkit.eventLoop") ||
ourIdleTimerThreadPattern.matcher(stackTrace).find() ||
ourIdleSwingTimerThreadPattern.matcher(stackTrace).find();
}
public static void inferThreadStateDetail(final ThreadState threadState) {
@NonNls String stackTrace = threadState.getStackTrace();
if (stackTrace.contains("at java.net.PlainSocketImpl.socketAccept") ||
stackTrace.contains("at java.net.PlainDatagramSocketImpl.receive") ||
stackTrace.contains("at java.net.SocketInputStream.socketRead") ||
stackTrace.contains("at java.net.PlainSocketImpl.socketConnect")) {
threadState.setOperation(ThreadOperation.Socket);
}
else if (stackTrace.contains("at java.io.FileInputStream.readBytes")) {
threadState.setOperation(ThreadOperation.IO);
}
else if (stackTrace.contains("at java.lang.Thread.sleep")) {
final String javaThreadState = threadState.getJavaThreadState();
if (!Thread.State.RUNNABLE.name().equals(javaThreadState)) {
threadState.setThreadStateDetail("sleeping"); // JDK 1.6 sets this explicitly, but JDK 1.5 does not
}
}
if (threadState.isEDT()) {
if (stackTrace.contains("java.awt.EventQueue.getNextEvent")) {
threadState.setThreadStateDetail("idle");
}
int modality = 0;
int pos = 0;
while(true) {
pos = stackTrace.indexOf(PUMP_EVENT, pos);
if (pos < 0) break;
modality++;
pos += PUMP_EVENT.length();
}
threadState.setExtraState("modality level " + modality);
}
}
@Nullable
private static ThreadState tryParseThreadStart(final String line) {
Matcher m = ourThreadStartPattern.matcher(line);
if (m.find()) {
final ThreadState state = new ThreadState(m.group(1), m.group(3));
if (line.contains(" daemon ")) {
state.setDaemon(true);
}
return state;
}
return null;
}
private static boolean tryParseThreadState(final String line, final ThreadState threadState) {
Matcher m = ourThreadStatePattern.matcher(line);
if (m.find()) {
threadState.setJavaThreadState(m.group(1));
threadState.setThreadStateDetail(m.group(2).trim());
return true;
}
m = ourThreadStatePattern2.matcher(line);
if (m.find()) {
threadState.setJavaThreadState(m.group(1));
return true;
}
return false;
}
}