IDEA-186244 Analyse Stack Trace does not understand jstack -F traces

This commit is contained in:
peter
2018-05-02 18:23:06 +02:00
parent 318ece93f4
commit 90877751c9
6 changed files with 191 additions and 72 deletions
@@ -21,7 +21,6 @@ import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
@@ -65,8 +64,8 @@ public class ExceptionExFilterFactory implements ExceptionFilterFactory {
final int startOffset,
int startLineNumber,
@NotNull final Consumer<AdditionalHighlight> consumer) {
Map<String, Trinity<TextRange, TextRange, TextRange>> visited = new THashMap<>();
final Trinity<TextRange, TextRange, TextRange> emptyInfo = Trinity.create(null, null, null);
Map<String, ExceptionWorker.ParsedLine> visited = new THashMap<>();
ExceptionWorker.ParsedLine emptyInfo = new ExceptionWorker.ParsedLine(TextRange.EMPTY_RANGE, TextRange.EMPTY_RANGE, TextRange.EMPTY_RANGE, null, -1);
final ExceptionWorker worker = new ExceptionWorker(myCache);
for (int i = 0; i < copiedFragment.getLineCount(); i++) {
@@ -75,11 +74,11 @@ public class ExceptionExFilterFactory implements ExceptionFilterFactory {
String lineText = copiedFragment.getText(new TextRange(lineStartOffset, lineEndOffset));
if (!lineText.contains(".java:")) continue;
Trinity<TextRange, TextRange, TextRange> info = visited.get(lineText);
ExceptionWorker.ParsedLine info = visited.get(lineText);
if (info == emptyInfo) continue;
if (info == null) {
info = ReadAction.compute(() -> doparse(emptyInfo, worker, lineEndOffset, lineText));
info = ReadAction.compute(() -> doParse(worker, lineEndOffset, lineText));
visited.put(lineText, info == null ? emptyInfo : info);
if (info == null) {
continue;
@@ -87,7 +86,7 @@ public class ExceptionExFilterFactory implements ExceptionFilterFactory {
}
int off = startOffset + lineStartOffset;
final Color color = UIUtil.getInactiveTextColor();
consumer.consume(new AdditionalHighlight(off + info.first.getStartOffset(), off + info.second.getEndOffset()) {
consumer.consume(new AdditionalHighlight(off + info.classFqnRange.getStartOffset(), off + info.methodNameRange.getEndOffset()) {
@NotNull
@Override
public TextAttributes getTextAttributes(@Nullable TextAttributes source) {
@@ -97,9 +96,7 @@ public class ExceptionExFilterFactory implements ExceptionFilterFactory {
}
}
private Trinity<TextRange, TextRange, TextRange> doparse(Trinity<TextRange, TextRange, TextRange> emptyInfo,
ExceptionWorker worker,
int lineEndOffset, String lineText) {
private static ExceptionWorker.ParsedLine doParse(ExceptionWorker worker, int lineEndOffset, String lineText) {
Result result = worker.execute(lineText, lineEndOffset);
if (result == null) return null;
HyperlinkInfo hyperlinkInfo = result.getHyperlinkInfo();
@@ -117,8 +114,7 @@ public class ExceptionExFilterFactory implements ExceptionFilterFactory {
PsiTryStatement parent = PsiTreeUtil.getParentOfType(element, PsiTryStatement.class, true, PsiClass.class);
PsiCodeBlock tryBlock = parent != null? parent.getTryBlock() : null;
if (tryBlock == null || !tryBlock.getTextRange().contains(offset)) return null;
Trinity<TextRange, TextRange, TextRange> info = worker.getInfo();
return info;
return worker.getInfo();
}
@NotNull
@@ -127,9 +127,9 @@ public class VcsContentAnnotationExceptionFilter implements Filter, FilterMixin
final Document document = getDocumentForFile(worker);
if (document == null) return;
int startFileOffset = worker.getInfo().getThird().getStartOffset();
int startFileOffset = worker.getInfo().fileLineRange.getStartOffset();
int idx = lineText.indexOf(':', startFileOffset);
int endIdx = idx == -1 ? worker.getInfo().getThird().getEndOffset() : idx;
int endIdx = idx == -1 ? worker.getInfo().fileLineRange.getEndOffset() : idx;
consumer.consume(new MyAdditionalHighlight(startOffset + lineStartOffset + startFileOffset + 1, startOffset + lineStartOffset + endIdx));
if (worker.getPsiClass() != null) {
@@ -149,8 +149,8 @@ public class VcsContentAnnotationExceptionFilter implements Filter, FilterMixin
}
}
if (methodChanged) {
consumer.consume(new MyAdditionalHighlight(startOffset + lineStartOffset + worker.getInfo().getSecond().getStartOffset(),
startOffset + lineStartOffset + worker.getInfo().getSecond().getEndOffset()));
consumer.consume(new MyAdditionalHighlight(startOffset + lineStartOffset + worker.getInfo().methodNameRange.getStartOffset(),
startOffset + lineStartOffset + worker.getInfo().methodNameRange.getEndOffset()));
}
}
}
@@ -30,6 +30,7 @@ import java.util.regex.Pattern;
*/
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 ourForcedThreadStartPattern = Pattern.compile("^\\s*Thread (\\d+): \\(state = (.+)\\)");
private static final Pattern ourYourkitThreadStartPattern = Pattern.compile("(?:\\s)*(.+) \\[([A-Z_, ]*)]");
private static final Pattern ourYourkitThreadStartPattern2 = Pattern.compile("(.+) State: (.+) CPU usage on sample: .+");
private static final Pattern ourThreadStatePattern = Pattern.compile("java\\.lang\\.Thread\\.State: (.+) \\((.+)\\)");
@@ -198,7 +199,12 @@ public class ThreadDumpParser {
}
return state;
}
m = ourForcedThreadStartPattern.matcher(line);
if (m.matches()) {
return new ThreadState(m.group(1), m.group(2));
}
boolean daemon = line.contains(" [DAEMON]");
if (daemon) {
line = StringUtil.replace(line, " [DAEMON]", "");
@@ -21,7 +21,6 @@ import com.intellij.execution.filters.FilterMixin;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
@@ -79,29 +78,29 @@ public class ExceptionWorkerTest extends LightCodeInsightFixtureTestCase {
}
public void testAnomalyParenthesisParsing() {
String[][] data = new String[][]{
{"at youtrack.jetbrains.com.Issue.IDEA_125137()(FooTest.groovy:2)\n", "youtrack.jetbrains.com.Issue", "IDEA_125137()",
"FooTest.groovy:2"},
{"at youtrack.jetbrains.com.Issue.IDEA_125137()Hmm(FooTest.groovy:2)\n", "youtrack.jetbrains.com.Issue", "IDEA_125137()Hmm",
"FooTest.groovy:2"},
{"p1.Cl.mee(p1.Cl.java:87) (A MESSAGE) IDEA-133794 (BUG START WITH 1)\n", "p1.Cl", "mee", "p1.Cl.java:87"}
};
for (String[] datum : data) {
assertParsed(datum[0], datum[1], datum[2], datum[3]);
}
assertParsed("at youtrack.jetbrains.com.Issue.IDEA_125137()(FooTest.groovy:2)\n", "youtrack.jetbrains.com.Issue", "IDEA_125137()", "FooTest.groovy", 2);
assertParsed("at youtrack.jetbrains.com.Issue.IDEA_125137()Hmm(FooTest.groovy:2)\n", "youtrack.jetbrains.com.Issue", "IDEA_125137()Hmm", "FooTest.groovy", 2);
assertParsed("p1.Cl.mee(p1.Cl.java:87) (A MESSAGE) IDEA-133794 (BUG START WITH 1)\n", "p1.Cl", "mee", "p1.Cl.java", 87);
}
private static void assertParsed(String line, String className, String methodName, String fileLine) {
private static void assertParsed(String line, String className, String methodName, String fileName, int lineIndex) {
assertTrue(line.endsWith("\n"));
Trinity<TextRange, TextRange, TextRange> trinity = ExceptionWorker.parseExceptionLine(line);
ExceptionWorker.ParsedLine trinity = ExceptionWorker.parseExceptionLine(line);
assertNotNull(trinity);
assertEquals(className, trinity.first.subSequence(line));
assertEquals(methodName, trinity.second.subSequence(line));
assertEquals(fileLine, trinity.third.subSequence(line));
assertEquals(className, trinity.classFqnRange.subSequence(line));
assertEquals(methodName, trinity.methodNameRange.subSequence(line));
assertEquals(fileName, trinity.fileName);
assertEquals(lineIndex, trinity.lineNumber);
}
public void testYourKitFormat() {
assertParsed("com.intellij.util.concurrency.Semaphore.waitFor(long) Semaphore.java:89\n",
"com.intellij.util.concurrency.Semaphore", "waitFor", "Semaphore.java:89");
"com.intellij.util.concurrency.Semaphore", "waitFor", "Semaphore.java", 89);
}
public void testForcedJstackFormat() {
assertParsed(" - java.lang.ref.ReferenceQueue.remove(long) @bci=151, line=143 (Compiled frame)\n",
"java.lang.ref.ReferenceQueue", "remove", null, 143);
}
}
@@ -228,4 +228,44 @@ com.intellij.openapi.application.impl.ApplicationImpl.runWriteAction(Runnable) A
'AWT-EventQueue-0 2017.3#IC-173.SNAPSHOT IDEA, eap:true, os:Linux 3.13.0-117-generic, java-version:JetBrains s.r.o 1.8.0_152-release-867-b1']
}
void "test jstack -F format"() {
String text = '''
Attaching to process ID 7370, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
Deadlock Detection:
No deadlocks found.
Thread 8393: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.parkNanos(java.lang.Object, long) @bci=63, line=215 (Compiled frame)
- java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(java.util.concurrent.SynchronousQueue$TransferStack$SNode, boolean, long) @bci=283, line=460 (Compiled frame)
- java.util.concurrent.SynchronousQueue$TransferStack.transfer(java.lang.Object, boolean, long) @bci=175, line=362 (Compiled frame)
- java.util.concurrent.SynchronousQueue.poll(long, java.util.concurrent.TimeUnit) @bci=49, line=941 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=247, line=1073 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=74, line=1134 (Interpreted frame)
- java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=28, line=624 (Interpreted frame)
- java.lang.Thread.run() @bci=34, line=748 (Interpreted frame)
Thread 7399: (state = IN_NATIVE)
- sun.awt.X11.XToolkit.$$YJP$$waitForEvents(long) @bci=0 (Compiled frame; information may be imprecise)
- sun.awt.X11.XToolkit.waitForEvents(long) @bci=14 (Compiled frame)
- sun.awt.X11.XToolkit.run(boolean) @bci=298, line=568 (Interpreted frame)
- sun.awt.X11.XToolkit.run() @bci=38, line=532 (Interpreted frame)
- java.lang.Thread.run() @bci=34, line=748 (Interpreted frame)
Thread 7381: (state = BLOCKED)
'''
def threads = ThreadDumpParser.parse(text)
assert threads.collect { it.name } == ['8393', '7399', '7381']
assert threads[0].stackTrace.contains('ThreadPoolExecutor')
assert threads[1].stackTrace.contains('XToolkit')
assert threads[2].emptyStackTrace
}
}
@@ -23,7 +23,6 @@ import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
@@ -48,7 +47,7 @@ public class ExceptionWorker {
private PsiClass[] myClasses = PsiClass.EMPTY_ARRAY;
private PsiFile[] myFiles = PsiFile.EMPTY_ARRAY;
private String myMethod;
private Trinity<TextRange, TextRange, TextRange> myInfo;
private ParsedLine myInfo;
private final ExceptionInfoCache myCache;
public ExceptionWorker(@NotNull ExceptionInfoCache cache) {
@@ -63,23 +62,15 @@ public class ExceptionWorker {
return null;
}
myMethod = myInfo.getSecond().substring(line);
myMethod = myInfo.methodNameRange.substring(line);
final String fileAndLine = myInfo.third.substring(line).trim();
final int colonIndex = fileAndLine.lastIndexOf(':');
if (colonIndex < 0) return null;
final int lineNumber = getLineNumber(fileAndLine.substring(colonIndex + 1));
if (lineNumber < 0) return null;
Pair<PsiClass[], PsiFile[]> pair = myCache.resolveClass(myInfo.first.substring(line).trim());
Pair<PsiClass[], PsiFile[]> pair = myCache.resolveClass(myInfo.classFqnRange.substring(line).trim());
myClasses = pair.first;
myFiles = pair.second;
if (myFiles.length == 0) {
if (myFiles.length == 0 && myInfo.fileName != null) {
// try find the file with the required name
//todo[nik] it would be better to use FilenameIndex here to honor the scope by it isn't accessible in Open API
myFiles = PsiShortNamesCache.getInstance(myProject).getFilesByName(fileAndLine.substring(0, colonIndex).trim());
myFiles = PsiShortNamesCache.getInstance(myProject).getFilesByName(myInfo.fileName);
}
if (myFiles.length == 0) return null;
@@ -93,8 +84,8 @@ public class ExceptionWorker {
final int textStartOffset = textEndOffset - line.length();
final int highlightStartOffset = textStartOffset + myInfo.third.getStartOffset();
final int highlightEndOffset = textStartOffset + myInfo.third.getEndOffset();
int highlightStartOffset = textStartOffset + myInfo.fileLineRange.getStartOffset();
int highlightEndOffset = textStartOffset + myInfo.fileLineRange.getEndOffset();
ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex();
List<VirtualFile> virtualFilesInLibraries = new ArrayList<>();
@@ -122,7 +113,7 @@ public class ExceptionWorker {
else {
virtualFiles = virtualFilesInContent;
}
HyperlinkInfo linkInfo = HyperlinkInfoFactory.getInstance().createMultipleFilesHyperlinkInfo(virtualFiles, lineNumber - 1, myProject);
HyperlinkInfo linkInfo = HyperlinkInfoFactory.getInstance().createMultipleFilesHyperlinkInfo(virtualFiles, myInfo.lineNumber - 1, myProject);
Filter.Result result = new Filter.Result(highlightStartOffset, highlightEndOffset, linkInfo, attributes);
myResult = result;
return result;
@@ -158,7 +149,7 @@ public class ExceptionWorker {
return ArrayUtil.getFirstElement(myFiles);
}
public Trinity<TextRange, TextRange, TextRange> getInfo() {
public ParsedLine getInfo() {
return myInfo;
}
@@ -183,41 +174,94 @@ public class ExceptionWorker {
}
@Nullable
public static Trinity<TextRange, TextRange, TextRange> parseExceptionLine(final String line) {
int startIdx = findAtPrefix(line);
TextRange yourKitLink = startIdx < 0 ? getYourKitLinkRange(line) : null;
int rParenIdx = yourKitLink != null ? yourKitLink.getEndOffset() - 2 : findFirstRParenAfterDigit(line);
if (rParenIdx < 0) return null;
final int lParenIdx = line.lastIndexOf('(', rParenIdx);
if (lParenIdx < 0) return null;
final int dotIdx = line.lastIndexOf('.', lParenIdx);
if (dotIdx < 0 || dotIdx < startIdx) return null;
int moduleIdx = line.indexOf('/');
int classNameIdx = moduleIdx > -1 && moduleIdx < lParenIdx && moduleIdx < dotIdx ? moduleIdx + 1 : startIdx + 1 + (startIdx >= 0 ? AT.length() : 0);
// class, method, link
return Trinity.create(new TextRange(classNameIdx, handleSpaces(line, dotIdx, -1)),
new TextRange(handleSpaces(line, dotIdx + 1, 1), handleSpaces(line, lParenIdx, -1)),
yourKitLink != null ? yourKitLink : new TextRange(lParenIdx + 1, rParenIdx));
public static ParsedLine parseExceptionLine(final String line) {
ParsedLine result = parseNormalStackTraceLine(line);
if (result == null) result = parseYourKitLine(line);
if (result == null) result = parseForcedLine(line);
return result;
}
@Nullable
private static TextRange getYourKitLinkRange(String line) {
private static ParsedLine parseNormalStackTraceLine(String line) {
int startIdx = findAtPrefix(line);
int rParenIdx = findFirstRParenAfterDigit(line);
if (rParenIdx < 0) return null;
TextRange methodName = findMethodNameCandidateBefore(line, startIdx, rParenIdx);
if (methodName == null) return null;
int lParenIdx = methodName.getEndOffset();
int dotIdx = methodName.getStartOffset() - 1;
int moduleIdx = line.indexOf('/');
int classNameIdx = moduleIdx > -1 && moduleIdx < dotIdx ? moduleIdx + 1 : startIdx + 1 + (startIdx >= 0 ? AT.length() : 0);
return ParsedLine.createFromFileAndLine(new TextRange(classNameIdx, handleSpaces(line, dotIdx, -1)),
trimRange(line, methodName),
lParenIdx + 1, rParenIdx, line);
}
private static TextRange trimRange(String line, TextRange range) {
int start = handleSpaces(line, range.getStartOffset(), 1);
int end = handleSpaces(line, range.getEndOffset(), -1);
if (start != range.getStartOffset() || end != range.getEndOffset()) {
return TextRange.create(start, end);
}
return range;
}
@Nullable
private static ParsedLine parseYourKitLine(String line) {
int lineEnd = line.length() - 1;
if (lineEnd > 0 && line.charAt(lineEnd) == '\n') lineEnd--;
if (lineEnd > 0 && Character.isDigit(line.charAt(lineEnd))) {
int spaceIndex = line.lastIndexOf(' ');
int rParenIdx = line.lastIndexOf(')');
if (rParenIdx > 0 && spaceIndex == rParenIdx + 1) {
return new TextRange(spaceIndex + 1, lineEnd + 1);
TextRange methodName = findMethodNameCandidateBefore(line, 0, rParenIdx);
if (methodName != null) {
return ParsedLine.createFromFileAndLine(new TextRange(0, methodName.getStartOffset() - 1),
methodName,
spaceIndex + 1, lineEnd + 1,
line);
}
}
}
return null;
}
@Nullable
private static ParsedLine parseForcedLine(String line) {
String dash = "- ";
if (!line.trim().startsWith(dash)) return null;
String linePrefix = "line=";
int lineNumberStart = line.indexOf(linePrefix);
if (lineNumberStart < 0) return null;
int lineNumberEnd = line.indexOf(' ', lineNumberStart);
if (lineNumberEnd < 0) return null;
TextRange methodName = findMethodNameCandidateBefore(line, 0, lineNumberStart);
if (methodName == null) return null;
int lineNumber = getLineNumber(line.substring(lineNumberStart + linePrefix.length(), lineNumberEnd));
if (lineNumber < 0) return null;
return new ParsedLine(trimRange(line, TextRange.create(line.indexOf(dash) + dash.length(), methodName.getStartOffset() - 1)),
methodName,
TextRange.create(lineNumberStart, lineNumberEnd), null, lineNumber);
}
private static TextRange findMethodNameCandidateBefore(String line, int start, int end) {
int lParenIdx = line.lastIndexOf('(', end);
if (lParenIdx < 0) return null;
int dotIdx = line.lastIndexOf('.', lParenIdx);
if (dotIdx < 0 || dotIdx < start) return null;
return TextRange.create(dotIdx + 1, lParenIdx);
}
private static int handleSpaces(String line, int pos, int delta) {
int len = line.length();
while (pos >= 0 && pos < len) {
@@ -227,4 +271,38 @@ public class ExceptionWorker {
}
return pos;
}
public static class ParsedLine {
@NotNull public final TextRange classFqnRange;
@NotNull public final TextRange methodNameRange;
@NotNull public final TextRange fileLineRange;
@Nullable public final String fileName;
public final int lineNumber;
ParsedLine(@NotNull TextRange classFqnRange,
@NotNull TextRange methodNameRange,
@NotNull TextRange fileLineRange, @Nullable String fileName, int lineNumber) {
this.classFqnRange = classFqnRange;
this.methodNameRange = methodNameRange;
this.fileLineRange = fileLineRange;
this.fileName = fileName;
this.lineNumber = lineNumber;
}
@Nullable
private static ParsedLine createFromFileAndLine(@NotNull TextRange classFqnRange,
@NotNull TextRange methodNameRange,
int fileLineStart, int fileLineEnd, String line) {
TextRange fileLineRange = TextRange.create(fileLineStart, fileLineEnd);
String fileAndLine = fileLineRange.substring(line);
int colonIndex = fileAndLine.lastIndexOf(':');
if (colonIndex < 0) return null;
int lineNumber = getLineNumber(fileAndLine.substring(colonIndex + 1));
if (lineNumber < 0) return null;
return new ParsedLine(classFqnRange, methodNameRange, fileLineRange, fileAndLine.substring(0, colonIndex).trim(), lineNumber);
}
}
}