Extract threadDump parser to a separate module

GitOrigin-RevId: c9d9890c5bf10e57b0aa77bb8f5c9ca7451c723e
This commit is contained in:
Maxim.Kolmakov
2024-10-09 23:35:30 +02:00
committed by intellij-monorepo-bot
parent 20c62c2d7e
commit e3d0d48018
21 changed files with 123 additions and 86 deletions

1
.idea/modules.xml generated
View File

@@ -789,6 +789,7 @@
<module fileurl="file://$PROJECT_DIR$/platform/testFramework/ui/intellij.platform.testFramework.ui.iml" filepath="$PROJECT_DIR$/platform/testFramework/ui/intellij.platform.testFramework.ui.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/testRunner/intellij.platform.testRunner.iml" filepath="$PROJECT_DIR$/platform/testRunner/intellij.platform.testRunner.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/platform-tests/intellij.platform.tests.iml" filepath="$PROJECT_DIR$/platform/platform-tests/intellij.platform.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/threadDumpParser/intellij.platform.threadDumpParser.iml" filepath="$PROJECT_DIR$/platform/threadDumpParser/intellij.platform.threadDumpParser.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/tips-of-the-day/intellij.platform.tips.iml" filepath="$PROJECT_DIR$/platform/tips-of-the-day/intellij.platform.tips.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/tracing-ide/intellij.platform.tracing.ide.iml" filepath="$PROJECT_DIR$/platform/tracing-ide/intellij.platform.tracing.ide.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/tracing/intellij.platform.tracing.rt.iml" filepath="$PROJECT_DIR$/platform/tracing/intellij.platform.tracing.rt.iml" />

View File

@@ -79,6 +79,7 @@
<orderEntry type="library" name="kotlinx-serialization-core" level="project" />
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
</component>
<component name="copyright">
<Base>

View File

@@ -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;

View File

@@ -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.*;

View File

@@ -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;

View File

@@ -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;

View File

@@ -43,6 +43,7 @@
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io.impl" />
<orderEntry type="module" module-name="intellij.platform.util.jdom" />
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
</component>
<component name="copyright">
<Base>

View File

@@ -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;

View File

@@ -80,6 +80,7 @@
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.markdown.utils" />
<orderEntry type="library" name="jetbrains.markdown" level="project" />
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
</component>
<component name="copyright">
<Base>

View File

@@ -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;

View File

@@ -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;

View File

@@ -16,5 +16,6 @@
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.java.impl" />
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
</component>
</module>

View File

@@ -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<ThreadState> 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()) {

View File

@@ -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();

View File

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
<orderEntry type="module" module-name="intellij.tools.ide.metrics.benchmark" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
</component>
</module>

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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;