From 487e04ef3c2347edb7c4cef8e44b9774a23019b0 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 3 May 2017 16:48:42 +0200 Subject: [PATCH] IDEA-147000 Support YourKit thread dump format in Analyze stacktrace --- .../intellij/unscramble/ThreadDumpParser.java | 12 ++++- .../filters/ExceptionWorkerTest.java | 26 ++++++---- .../unscramble/ThreadDumpParserTest.groovy | 23 +++++++++ .../execution/filters/ExceptionWorker.java | 47 ++++++++++++------- 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java b/java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java index 563af08d1707..d8f04d49d1d8 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java +++ b/java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java @@ -21,7 +21,6 @@ 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; @@ -31,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 ourYourkitThreadStartPattern = Pattern.compile("(.+) \\[([^\\[]*)]"); 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) <(.+)>"); @@ -197,6 +197,16 @@ public class ThreadDumpParser { } return state; } + + String noDaemon = StringUtil.trimEnd(line, " [DAEMON]"); + m = ourYourkitThreadStartPattern.matcher(noDaemon); + if (m.find()) { + ThreadState state = new ThreadState(m.group(1), m.group(2)); + if (noDaemon.length() < line.length()) { + state.setDaemon(true); + } + return state; + } return null; } diff --git a/java/java-tests/testSrc/com/intellij/execution/filters/ExceptionWorkerTest.java b/java/java-tests/testSrc/com/intellij/execution/filters/ExceptionWorkerTest.java index 49082f98939e..365cd3a113bc 100644 --- a/java/java-tests/testSrc/com/intellij/execution/filters/ExceptionWorkerTest.java +++ b/java/java-tests/testSrc/com/intellij/execution/filters/ExceptionWorkerTest.java @@ -62,18 +62,28 @@ public class ExceptionWorkerTest extends LightCodeInsightFixtureTestCase { public void testAnomalyParenthesisParsing() { String[][] data = new String[][]{ - {"at youtrack.jetbrains.com.Issue.IDEA_125137()(FooTest.groovy:2)", "youtrack.jetbrains.com.Issue", "IDEA_125137()", + {"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)", "youtrack.jetbrains.com.Issue", "IDEA_125137()Hmm", + {"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)", "p1.Cl", "mee", "p1.Cl.java:87"} + {"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) { - Trinity trinity = ExceptionWorker.parseExceptionLine(datum[0]); - assertNotNull(trinity); - assertEquals(datum[1], trinity.first.subSequence(datum[0])); - assertEquals(datum[2], trinity.second.subSequence(datum[0])); - assertEquals(datum[3], trinity.third.subSequence(datum[0])); + assertParsed(datum[0], datum[1], datum[2], datum[3]); } } + + private static void assertParsed(String line, String className, String methodName, String fileLine) { + assertTrue(line.endsWith("\n")); + Trinity trinity = ExceptionWorker.parseExceptionLine(line); + assertNotNull(trinity); + assertEquals(className, trinity.first.subSequence(line)); + assertEquals(methodName, trinity.second.subSequence(line)); + assertEquals(fileLine, trinity.third.subSequence(line)); + } + + public void testYourKitFormat() { + assertParsed("com.intellij.util.concurrency.Semaphore.waitFor(long) Semaphore.java:89\n", + "com.intellij.util.concurrency.Semaphore", "waitFor", "Semaphore.java:89"); + } } diff --git a/java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.groovy b/java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.groovy index 68bbb6e804af..6c5811bc4ee3 100644 --- a/java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.groovy +++ b/java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.groovy @@ -96,5 +96,28 @@ class ThreadDumpParserTest extends TestCase { assert threads[0].isAwaitedBy(threads[1]) } + void "test YourKit format"() { + def text = """ +Stacks at 2017-05-03 01:07:25 PM (uptime 4h 21m 28s) Threads shown: 38 of 46 + +ApplicationImpl pooled thread 228 [WAITING] +java.lang.Thread.run() Thread.java:745 + +ApplicationImpl pooled thread 234 [WAITING] [DAEMON] +java.lang.Thread.run() Thread.java:745 + +ApplicationImpl pooled thread 6 [RUNNABLE, IN_NATIVE] +java.net.DatagramSocket.receive(DatagramPacket) DatagramSocket.java:812 +com.intellij.a.f.a.c.a() c.java:60 +com.intellij.a.f.a.d.run() d.java:20 +java.lang.Thread.run() Thread.java:745 +""" + def threads = ThreadDumpParser.parse(text) + assert threads.collect { it.name } == ['ApplicationImpl pooled thread 228', 'ApplicationImpl pooled thread 234', 'ApplicationImpl pooled thread 6'] + assert threads.collect { it.state } == ['WAITING', 'WAITING', 'RUNNABLE, IN_NATIVE'] + assert threads.collect { it.daemon } == [false, true, false] + assert threads.collect { it.stackTrace.readLines().size() } == [2, 2, 5] // thread name is included into stack trace + } + } diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java index 259dcde3d5a1..448e9bb47af0 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java @@ -167,24 +167,14 @@ public class ExceptionWorker { return myInfo; } - //todo [roma] regexp - @Nullable - static Trinity parseExceptionLine(final String line) { - int startIdx; - if (line.startsWith(AT_PREFIX)){ - startIdx = 0; - } - else{ - startIdx = line.indexOf(STANDALONE_AT); - if (startIdx < 0) { - startIdx = line.indexOf(AT_PREFIX); - } + private static int findAtPrefix(String line) { + if (line.startsWith(AT_PREFIX)) return 0; - if (startIdx < 0) { - startIdx = -1; - } - } + int startIdx = line.indexOf(STANDALONE_AT); + return startIdx < 0 ? line.indexOf(AT_PREFIX) : startIdx; + } + private static int findFirstRParenAfterDigit(String line) { int rParenIdx = -1; int rParenCandidate = line.lastIndexOf(')'); //Looking for minimal position for ')' after a digit @@ -194,6 +184,15 @@ public class ExceptionWorker { } rParenCandidate = line.lastIndexOf(')', rParenCandidate - 1); } + return rParenIdx; + } + + @Nullable + static Trinity 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); @@ -207,7 +206,21 @@ public class ExceptionWorker { // class, method, link return Trinity.create(new TextRange(classNameIdx, handleSpaces(line, dotIdx, -1)), new TextRange(handleSpaces(line, dotIdx + 1, 1), handleSpaces(line, lParenIdx, -1)), - new TextRange(lParenIdx + 1, rParenIdx)); + yourKitLink != null ? yourKitLink : new TextRange(lParenIdx + 1, rParenIdx)); + } + + @Nullable + private static TextRange getYourKitLinkRange(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); + } + } + return null; } private static int handleSpaces(String line, int pos, int delta) {