diff --git a/.idea/modules.xml b/.idea/modules.xml index f532749a71e6..8b50d33e321e 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -789,6 +789,7 @@ + diff --git a/java/debugger/impl/intellij.java.debugger.impl.iml b/java/debugger/impl/intellij.java.debugger.impl.iml index d5c0275f1c3f..254f1474bd61 100644 --- a/java/debugger/impl/intellij.java.debugger.impl.iml +++ b/java/debugger/impl/intellij.java.debugger.impl.iml @@ -79,6 +79,7 @@ + diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/ExportThreadsAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/ExportThreadsAction.java index dc461d627f52..dba88648de7e 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/ExportThreadsAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/ExportThreadsAction.java @@ -16,7 +16,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.unscramble.ThreadDumpPanel; -import com.intellij.unscramble.ThreadState; +import com.intellij.threadDumpParser.ThreadState; import org.jetbrains.annotations.NotNull; import java.util.List; diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java index 306a5bcbadd5..7575c64c6f8f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java @@ -17,8 +17,8 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.unscramble.ThreadDumpParser; -import com.intellij.unscramble.ThreadState; +import com.intellij.threadDumpParser.ThreadDumpParser; +import com.intellij.threadDumpParser.ThreadState; import com.intellij.util.SmartList; import com.intellij.xdebugger.XDebugSession; import com.sun.jdi.*; diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java index fcfaad5f2808..3c839068321f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java @@ -45,7 +45,7 @@ import com.intellij.ui.classFilter.ClassFilter; import com.intellij.ui.content.Content; import com.intellij.ui.viewModel.extraction.ToolWindowContentExtractor; import com.intellij.unscramble.ThreadDumpPanel; -import com.intellij.unscramble.ThreadState; +import com.intellij.threadDumpParser.ThreadState; import com.intellij.util.DocumentUtil; import com.intellij.util.SmartList; import com.intellij.util.ThreeState; diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/ReloadClassesWorker.java b/java/debugger/impl/src/com/intellij/debugger/impl/ReloadClassesWorker.java index 43c2ac6ee047..de803d127fba 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/ReloadClassesWorker.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/ReloadClassesWorker.java @@ -15,7 +15,7 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; -import com.intellij.unscramble.ThreadState; +import com.intellij.threadDumpParser.ThreadState; import com.intellij.util.concurrency.Semaphore; import com.intellij.util.ui.MessageCategory; import com.intellij.xdebugger.XDebugSession; diff --git a/java/execution/impl/intellij.java.execution.impl.iml b/java/execution/impl/intellij.java.execution.impl.iml index 80da0c0f8ac8..20786f97e650 100644 --- a/java/execution/impl/intellij.java.execution.impl.iml +++ b/java/execution/impl/intellij.java.execution.impl.iml @@ -43,6 +43,7 @@ + diff --git a/java/execution/impl/src/com/intellij/execution/impl/DefaultJavaProgramRunner.java b/java/execution/impl/src/com/intellij/execution/impl/DefaultJavaProgramRunner.java index a93bc16949b4..9d39146062fd 100644 --- a/java/execution/impl/src/com/intellij/execution/impl/DefaultJavaProgramRunner.java +++ b/java/execution/impl/src/com/intellij/execution/impl/DefaultJavaProgramRunner.java @@ -36,8 +36,8 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.unscramble.AnalyzeStacktraceUtil; import com.intellij.unscramble.ThreadDumpConsoleFactory; -import com.intellij.unscramble.ThreadDumpParser; -import com.intellij.unscramble.ThreadState; +import com.intellij.threadDumpParser.ThreadDumpParser; +import com.intellij.threadDumpParser.ThreadState; import com.intellij.util.ArrayUtil; import com.intellij.util.TimeoutUtil; import com.intellij.util.concurrency.AppExecutorUtil; diff --git a/java/java-impl/intellij.java.impl.iml b/java/java-impl/intellij.java.impl.iml index 33d39be627e2..d6bec7a7e296 100644 --- a/java/java-impl/intellij.java.impl.iml +++ b/java/java-impl/intellij.java.impl.iml @@ -80,6 +80,7 @@ + diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadDumpConsoleFactory.java b/java/java-impl/src/com/intellij/unscramble/ThreadDumpConsoleFactory.java index efa7b49cc1c4..2ab9036e4269 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadDumpConsoleFactory.java +++ b/java/java-impl/src/com/intellij/unscramble/ThreadDumpConsoleFactory.java @@ -18,6 +18,7 @@ package com.intellij.unscramble; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.project.Project; +import com.intellij.threadDumpParser.ThreadState; import javax.swing.*; import java.util.List; diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java b/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java index 6d4cae9e9aca..a2dd2276ad43 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java +++ b/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java @@ -26,6 +26,8 @@ import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.threadDumpParser.ThreadOperation; +import com.intellij.threadDumpParser.ThreadState; import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.util.PlatformIcons; diff --git a/java/unscramble/intellij.java.unscramble.iml b/java/unscramble/intellij.java.unscramble.iml index 96c99b1b9223..afbfa244a001 100644 --- a/java/unscramble/intellij.java.unscramble.iml +++ b/java/unscramble/intellij.java.unscramble.iml @@ -16,5 +16,6 @@ + \ No newline at end of file diff --git a/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java b/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java index d57b513e33de..ef2bb37e7e12 100644 --- a/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java +++ b/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java @@ -20,6 +20,8 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.configurable.VcsContentAnnotationConfigurable; +import com.intellij.threadDumpParser.ThreadDumpParser; +import com.intellij.threadDumpParser.ThreadState; import com.intellij.ui.GuiUtils; import com.intellij.ui.SimpleListCellRenderer; import com.intellij.ui.TextFieldWithHistory; @@ -45,7 +47,6 @@ public class UnscrambleDialog extends DialogWrapper { private static final @NonNls String PROPERTY_LOG_FILE_LAST_URL = "UNSCRAMBLE_LOG_FILE_LAST_URL"; private static final @NonNls String PROPERTY_UNSCRAMBLER_NAME_USED = "UNSCRAMBLER_NAME_USED"; private static final Condition DEADLOCK_CONDITION = state -> state.isDeadlocked(); - private static final String[] IMPORTANT_THREAD_DUMP_WORDS = ContainerUtil.ar("tid", "nid", "wait", "parking", "prio", "os_prio", "java"); private final Project myProject; private JPanel myEditorPanel; @@ -279,77 +280,10 @@ public class UnscrambleDialog extends DialogWrapper { @Override public void actionPerformed(ActionEvent e){ String text = myStacktraceEditorPanel.getText(); - myStacktraceEditorPanel.setText(normalizeText(text)); + myStacktraceEditorPanel.setText(ThreadDumpParser.normalizeText(text)); } } - public static String normalizeText(@NonNls String text) { - StringBuilder builder = new StringBuilder(text.length()); - - text = text.replaceAll("(\\S[ \\t\\x0B\\f\\r]+)(at\\s+)", "$1\n$2"); - text = text.replaceAll("(\\\\n|\\\\r|\\\\t)+(at\\s+)", "\n$2"); - String[] lines = text.split("\n"); - - boolean first = true; - boolean inAuxInfo = false; - for (final String line : lines) { - //noinspection HardCodedStringLiteral - if (!inAuxInfo && (line.startsWith("JNI global references") || line.trim().equals("Heap"))) { - builder.append("\n"); - inAuxInfo = true; - } - if (inAuxInfo) { - builder.append(trimSuffix(line)).append("\n"); - continue; - } - if (line.startsWith("at breakpoint")) { // possible thread status mixed with "at ..." - builder.append(" ").append(trimSuffix(line)); - continue; - } - if (!first && (mustHaveNewLineBefore(line) || StringUtil.endsWith(builder, ")"))) { - if (!StringUtil.endsWith(builder, "\n")) builder.append("\n"); - if (line.startsWith("\"")) builder.append("\n"); // Additional line break for thread names - } - first = false; - int i = builder.lastIndexOf("\n"); - CharSequence lastLine = i == -1 ? builder : builder.subSequence(i + 1, builder.length()); - if (!line.matches("\\s+.*") && lastLine.length() > 0) { - if (lastLine.toString().matches("\\s*at") //separate 'at' from filename - || ContainerUtil.or(IMPORTANT_THREAD_DUMP_WORDS, word -> line.startsWith(word))) { - builder.append(" "); - } - } - builder.append(trimSuffix(line)); - } - return builder.toString(); - } - - private static String trimSuffix(final String line) { - int len = line.length(); - - while ((0 < len) && (line.charAt(len-1) <= ' ')) { - len--; - } - return (len < line.length()) ? line.substring(0, len) : line; - } - - private static boolean mustHaveNewLineBefore(String line) { - final int nonWs = CharArrayUtil.shiftForward(line, 0, " \t"); - if (nonWs < line.length()) { - line = line.substring(nonWs); - } - - if (line.startsWith("at")) return true; // Start of the new stack frame entry - if (line.startsWith("Caused")) return true; // Caused by message - if (line.startsWith("- locked")) return true; // "Locked a monitor" logging - if (line.startsWith("- waiting")) return true; // "Waiting for monitor" logging - if (line.startsWith("- parking to wait")) return true; - if (line.startsWith("java.lang.Thread.State")) return true; - if (line.startsWith("\"")) return true; // Start of the new thread (thread name) - - return false; - } - @Override protected void doOKAction() { if (myConfigurable != null && myConfigurable.isModified()) { diff --git a/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java b/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java index ef9ccd2b51b5..d84253a4ecaa 100644 --- a/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java +++ b/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java @@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFrame; +import com.intellij.threadDumpParser.ThreadDumpParser; import org.jetbrains.annotations.NotNull; import java.util.regex.Pattern; @@ -35,7 +36,7 @@ class UnscrambleListener extends ClipboardAnalyzeListener { @Override public boolean canHandle(@NotNull String value) { - value = UnscrambleDialog.normalizeText(value); + value = ThreadDumpParser.normalizeText(value); int linesCount = 0; for (String line : value.split("\n")) { line = line.trim(); diff --git a/platform/threadDumpParser/api-dump.txt b/platform/threadDumpParser/api-dump.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/platform/threadDumpParser/intellij.platform.threadDumpParser.iml b/platform/threadDumpParser/intellij.platform.threadDumpParser.iml new file mode 100644 index 000000000000..a5c8ed2433ab --- /dev/null +++ b/platform/threadDumpParser/intellij.platform.threadDumpParser.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java b/platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadDumpParser.java similarity index 77% rename from java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java rename to platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadDumpParser.java index b4f437ad6cc6..9f98f3e2263b 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadDumpParser.java +++ b/platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadDumpParser.java @@ -1,8 +1,11 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.unscramble; +package com.intellij.threadDumpParser; import com.intellij.diagnostic.EventCountDumper; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,7 +17,7 @@ import java.util.regex.Pattern; import static com.intellij.diagnostic.CoroutineDumperKt.isCoroutineDumpHeader; - +@ApiStatus.Internal public final class ThreadDumpParser { private static final Pattern ourThreadStartPattern = Pattern.compile("^\"(.+)\".+(prio=\\d+ (?:os_prio=[^\\s]+ )?.*tid=[^\\s]+ nid=[^\\s]+|[Ii][Dd]=\\d+) ([^\\[]+)"); private static final Pattern ourForcedThreadStartPattern = Pattern.compile("^Thread (\\d+): \\(state = (.+)\\)"); @@ -30,6 +33,8 @@ public final class ThreadDumpParser { private static final String AT_JAVA_LANG_OBJECT_WAIT = "at java.lang.Object.wait("; private static final Pattern ourLockedOwnableSynchronizersPattern = Pattern.compile("- <(0x[\\da-f]+)> \\(.*\\)"); + private static final String[] IMPORTANT_THREAD_DUMP_WORDS = ContainerUtil.ar("tid", "nid", "wait", "parking", "prio", "os_prio", "java"); + private ThreadDumpParser() { } @@ -263,4 +268,72 @@ public final class ThreadDumpParser { } return false; } + + public static String normalizeText(@NonNls String text) { + StringBuilder builder = new StringBuilder(text.length()); + + text = text.replaceAll("(\\S[ \\t\\x0B\\f\\r]+)(at\\s+)", "$1\n$2"); + text = text.replaceAll("(\\\\n|\\\\r|\\\\t)+(at\\s+)", "\n$2"); + String[] lines = text.split("\n"); + + boolean first = true; + boolean inAuxInfo = false; + for (final String line : lines) { + //noinspection HardCodedStringLiteral + if (!inAuxInfo && (line.startsWith("JNI global references") || line.trim().equals("Heap"))) { + builder.append("\n"); + inAuxInfo = true; + } + if (inAuxInfo) { + builder.append(trimSuffix(line)).append("\n"); + continue; + } + if (line.startsWith("at breakpoint")) { // possible thread status mixed with "at ..." + builder.append(" ").append(trimSuffix(line)); + continue; + } + if (!first && (mustHaveNewLineBefore(line) || StringUtil.endsWith(builder, ")"))) { + if (!StringUtil.endsWith(builder, "\n")) builder.append("\n"); + if (line.startsWith("\"")) builder.append("\n"); // Additional line break for thread names + } + first = false; + int i = builder.lastIndexOf("\n"); + CharSequence lastLine = i == -1 ? builder : builder.subSequence(i + 1, builder.length()); + if (!line.matches("\\s+.*") && lastLine.length() > 0) { + if (lastLine.toString().matches("\\s*at") //separate 'at' from filename + || ContainerUtil.or(IMPORTANT_THREAD_DUMP_WORDS, word -> line.startsWith(word))) { + builder.append(" "); + } + } + builder.append(trimSuffix(line)); + } + return builder.toString(); + } + + private static boolean mustHaveNewLineBefore(String line) { + final int nonWs = CharArrayUtil.shiftForward(line, 0, " \t"); + if (nonWs < line.length()) { + line = line.substring(nonWs); + } + + if (line.startsWith("at")) return true; // Start of the new stack frame entry + if (line.startsWith("Caused")) return true; // Caused by message + if (line.startsWith("- locked")) return true; // "Locked a monitor" logging + if (line.startsWith("- waiting")) return true; // "Waiting for monitor" logging + if (line.startsWith("- parking to wait")) return true; + if (line.startsWith("java.lang.Thread.State")) return true; + if (line.startsWith("\"")) return true; // Start of the new thread (thread name) + + return false; + } + + private static String trimSuffix(final String line) { + int len = line.length(); + + while ((0 < len) && (line.charAt(len - 1) <= ' ')) { + len--; + } + return (len < line.length()) ? line.substring(0, len) : line; + } + } diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadOperation.java b/platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadOperation.java similarity index 77% rename from java/java-impl/src/com/intellij/unscramble/ThreadOperation.java rename to platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadOperation.java index 35c7675d03ad..4079fdebb539 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadOperation.java +++ b/platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadOperation.java @@ -1,7 +1,9 @@ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.unscramble; +package com.intellij.threadDumpParser; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal public enum ThreadOperation { Socket("socket operation"), IO("I/O"); diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadState.java b/platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadState.java similarity index 98% rename from java/java-impl/src/com/intellij/unscramble/ThreadState.java rename to platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadState.java index af2153af2846..cc7a8f0577de 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadState.java +++ b/platform/threadDumpParser/src/com/intellij/threadDumpParser/ThreadState.java @@ -1,17 +1,18 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.unscramble; +package com.intellij.threadDumpParser; import com.intellij.diagnostic.ThreadDumper; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.NlsSafe; import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; - +@ApiStatus.Internal public class ThreadState { private final String myName; private final String myState; diff --git a/java/java-tests/testSrc/com/intellij/unscramble/NormalizeTextTest.java b/platform/threadDumpParser/testSrc/com/intellij/threadDumpParser/NormalizeTextTest.java similarity index 98% rename from java/java-tests/testSrc/com/intellij/unscramble/NormalizeTextTest.java rename to platform/threadDumpParser/testSrc/com/intellij/threadDumpParser/NormalizeTextTest.java index 78deb81b032b..99ed57574a93 100644 --- a/java/java-tests/testSrc/com/intellij/unscramble/NormalizeTextTest.java +++ b/platform/threadDumpParser/testSrc/com/intellij/threadDumpParser/NormalizeTextTest.java @@ -1,5 +1,5 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.unscramble; +package com.intellij.threadDumpParser; import junit.framework.TestCase; import org.jetbrains.annotations.NonNls; @@ -165,7 +165,7 @@ public class NormalizeTextTest extends TestCase { at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)"""); - assertEquals(2, ThreadDumpParser.parse(UnscrambleDialog.normalizeText(text)).size()); + assertEquals(2, ThreadDumpParser.parse(ThreadDumpParser.normalizeText(text)).size()); } public void testJsonEscapes() { @@ -182,6 +182,6 @@ public class NormalizeTextTest extends TestCase { } private static void doTest(@NonNls String stackTrace, @NonNls String expected) { - assertEquals(expected, UnscrambleDialog.normalizeText(stackTrace)); + assertEquals(expected, ThreadDumpParser.normalizeText(stackTrace)); } } diff --git a/java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.java b/platform/threadDumpParser/testSrc/com/intellij/threadDumpParser/ThreadDumpParserTest.java similarity index 99% rename from java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.java rename to platform/threadDumpParser/testSrc/com/intellij/threadDumpParser/ThreadDumpParserTest.java index bfa19366bd69..e8ca3999e924 100644 --- a/java/java-tests/testSrc/com/intellij/unscramble/ThreadDumpParserTest.java +++ b/platform/threadDumpParser/testSrc/com/intellij/threadDumpParser/ThreadDumpParserTest.java @@ -1,5 +1,5 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.unscramble; +package com.intellij.threadDumpParser; import com.intellij.openapi.util.text.StringUtil; import com.intellij.tools.ide.metrics.benchmark.Benchmark;