IJPL-156272 Add freeze analyzer and stack trace viewer to text file that contains stack trace

(cherry picked from commit 24e9dff91ea035f5c0a50b8e64f4eaa81c2d0729)

GitOrigin-RevId: d31101f5fdf1e477fe41887957c9e22231822f68
This commit is contained in:
Maxim.Kolmakov
2024-06-21 16:25:13 +08:00
committed by intellij-monorepo-bot
parent 6e5be878d4
commit 4cea38b606
24 changed files with 495 additions and 101 deletions

1
.idea/modules.xml generated
View File

@@ -335,6 +335,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/intellij.devkit.plugin.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/intellij.devkit.plugin.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/runtimeModuleRepository/jps/intellij.devkit.runtimeModuleRepository.jps.iml" filepath="$PROJECT_DIR$/plugins/devkit/runtimeModuleRepository/jps/intellij.devkit.runtimeModuleRepository.jps.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml" filepath="$PROJECT_DIR$/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.themes/intellij.devkit.themes.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.themes/intellij.devkit.themes.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.uiDesigner/intellij.devkit.uiDesigner.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.uiDesigner/intellij.devkit.uiDesigner.iml" />

View File

@@ -46,7 +46,7 @@ public class UnscrambleDialog extends DialogWrapper {
private static final @NonNls String PROPERTY_LOG_FILE_HISTORY_URLS = "UNSCRAMBLE_LOG_FILE_URL";
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 final Project myProject;
private JPanel myEditorPanel;
@@ -311,84 +311,11 @@ public class UnscrambleDialog extends DialogWrapper {
String unscrambledTrace = unscrambleSupport == null ? textToUnscramble : unscrambleSupport.unscramble(project,textToUnscramble, logName, settings);
if (unscrambledTrace == null) return null;
List<ThreadState> threadStates = ThreadDumpParser.parse(unscrambledTrace);
return addConsole(project, threadStates, unscrambledTrace);
}
private static RunContentDescriptor addConsole(final Project project, final List<ThreadState> threadDump, String unscrambledTrace) {
Icon icon = null;
String message = JavaBundle.message("unscramble.unscrambled.stacktrace.tab");
if (!threadDump.isEmpty()) {
message = JavaBundle.message("unscramble.unscrambled.threaddump.tab");
icon = AllIcons.Actions.Dump;
}
else {
String name = getExceptionName(unscrambledTrace);
if (name != null) {
message = name;
icon = AllIcons.Actions.Lightning;
}
}
if (ContainerUtil.find(threadDump, DEADLOCK_CONDITION) != null) {
message = JavaBundle.message("unscramble.unscrambled.deadlock.tab");
icon = AllIcons.Debugger.KillProcess;
}
return AnalyzeStacktraceUtil.addConsole(project, threadDump.size() > 1 ? new ThreadDumpConsoleFactory(project, threadDump) : null, message, unscrambledTrace, icon);
return UnscrambleUtils.addConsole(project, threadStates, unscrambledTrace);
}
@Override
protected String getDimensionServiceKey(){
return "#com.intellij.unscramble.UnscrambleDialog";
}
private static @Nullable String getExceptionName(String unscrambledTrace) {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
BufferedReader reader = new BufferedReader(new StringReader(unscrambledTrace));
for (int i = 0; i < 3; i++) {
try {
String line = reader.readLine();
if (line == null) return null;
String name = getExceptionAbbreviation(line);
if (name != null) return name;
}
catch (IOException e) {
return null;
}
}
return null;
}
private static @Nullable String getExceptionAbbreviation(String line) {
line = StringUtil.trimStart(line.trim(), "Caused by: ");
int classNameStart = 0;
int classNameEnd = line.length();
for (int j = 0; j < line.length(); j++) {
char c = line.charAt(j);
if (c == '.' || c == '$') {
classNameStart = j + 1;
continue;
}
if (c == ':') {
classNameEnd = j;
break;
}
if (!StringUtil.isJavaIdentifierPart(c)) {
return null;
}
}
if (classNameStart >= classNameEnd) return null;
String clazz = line.substring(classNameStart, classNameEnd);
String abbreviate = abbreviate(clazz);
return abbreviate.length() > 1 ? abbreviate : clazz;
}
private static String abbreviate(String s) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isUpperCase(c)) {
builder.append(c);
}
}
return builder.toString();
}
}

View File

@@ -36,21 +36,7 @@ class UnscrambleListener extends ClipboardAnalyzeListener {
@Override
public boolean canHandle(@NotNull String value) {
value = ThreadDumpParser.normalizeText(value);
int linesCount = 0;
for (String line : value.split("\n")) {
line = line.trim();
if (line.length() == 0) continue;
line = StringUtil.trimEnd(line, "\r");
if (STACKTRACE_LINE.matcher(line).matches()) {
linesCount++;
}
else {
linesCount = 0;
}
if (linesCount > 2) return true;
}
return false;
return UnscrambleUtils.isStackTrace(value);
}
@Override

View File

@@ -0,0 +1,126 @@
// 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;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.icons.AllIcons;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.threadDumpParser.ThreadDumpParser;
import com.intellij.threadDumpParser.ThreadState;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.regex.Pattern;
final public class UnscrambleUtils {
private static final Condition<ThreadState> DEADLOCK_CONDITION = state -> state.isDeadlocked();
private static final Pattern STACKTRACE_LINE =
Pattern.compile(
"[\t]*at [[_a-zA-Z0-9/]+\\.]+[_a-zA-Z$0-9/]+\\.[a-zA-Z0-9_/]+\\([A-Za-z0-9_/]+\\.(java|kt):[\\d]+\\)+[ [~]*\\[[a-zA-Z0-9\\.\\:/]\\]]*");
public static @Nullable String getExceptionName(String unscrambledTrace) {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
BufferedReader reader = new BufferedReader(new StringReader(unscrambledTrace));
for (int i = 0; i < 3; i++) {
try {
String line = reader.readLine();
if (line == null) return null;
String name = getExceptionAbbreviation(line);
if (name != null) return name;
}
catch (IOException e) {
return null;
}
}
return null;
}
private static @Nullable String getExceptionAbbreviation(String line) {
line = StringUtil.trimStart(line.trim(), "Caused by: ");
int classNameStart = 0;
int classNameEnd = line.length();
for (int j = 0; j < line.length(); j++) {
char c = line.charAt(j);
if (c == '.' || c == '$') {
classNameStart = j + 1;
continue;
}
if (c == ':') {
classNameEnd = j;
break;
}
if (!StringUtil.isJavaIdentifierPart(c)) {
return null;
}
}
if (classNameStart >= classNameEnd) return null;
String clazz = line.substring(classNameStart, classNameEnd);
String abbreviate = abbreviate(clazz);
return abbreviate.length() > 1 ? abbreviate : clazz;
}
private static String abbreviate(String s) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isUpperCase(c)) {
builder.append(c);
}
}
return builder.toString();
}
public static RunContentDescriptor addConsole(final Project project,
final List<ThreadState> threadDump,
String unscrambledTrace,
Boolean withExecutor) {
Icon icon = null;
String message = JavaBundle.message("unscramble.unscrambled.stacktrace.tab");
if (!threadDump.isEmpty()) {
message = JavaBundle.message("unscramble.unscrambled.threaddump.tab");
icon = AllIcons.Actions.Dump;
}
else {
String name = getExceptionName(unscrambledTrace);
if (name != null) {
message = name;
icon = AllIcons.Actions.Lightning;
}
}
if (ContainerUtil.find(threadDump, DEADLOCK_CONDITION) != null) {
message = JavaBundle.message("unscramble.unscrambled.deadlock.tab");
icon = AllIcons.Debugger.KillProcess;
}
return AnalyzeStacktraceUtil.addConsole(project, threadDump.size() > 1 ? new ThreadDumpConsoleFactory(project, threadDump) : null,
message, unscrambledTrace, icon, withExecutor);
}
public static RunContentDescriptor addConsole(final Project project, final List<ThreadState> threadDump, String unscrambledTrace) {
return addConsole(project, threadDump, unscrambledTrace, true);
}
public static boolean isStackTrace(String text) {
text = ThreadDumpParser.normalizeText(text);
int linesCount = 0;
for (String line : text.split("\n")) {
line = line.trim();
if (line.length() == 0) continue;
line = StringUtil.trimEnd(line, "\r");
if (STACKTRACE_LINE.matcher(line).matches()) {
linesCount++;
}
else {
linesCount = 0;
}
if (linesCount > 2) return true;
}
return false;
}
}

View File

@@ -82,7 +82,7 @@ class FreezeAnalyzerTest {
fun testGeneralLockFreeze() {
val threadDump = File(this::class.java.classLoader.getResource("freezes/generalLock/generalLock.txt")!!.path).toPath().readText()
FreezeAnalyzer.analyzeFreeze(threadDump)?.message.shouldBe("EDT is blocked on com.intellij.codeInsight.completion.CompletionProgressIndicator.blockingWaitForFinish")
FreezeAnalyzer.analyzeFreeze(threadDump)?.threads?.joinToString { it -> it.stackTrace }.shouldStartWith("Possibly locked by com.intellij.codeInsight.completion.JavaMethodCallElement.<init> in DefaultDispatcher-worker-55")
FreezeAnalyzer.analyzeFreeze(threadDump)?.additionalMessage?.shouldBe("Possibly locked by com.intellij.codeInsight.completion.JavaMethodCallElement.<init> in DefaultDispatcher-worker-55")
}
@Test

View File

@@ -18919,6 +18919,7 @@ f:com.intellij.unscramble.AnalyzeStacktraceUtil
- sf:EP_NAME:com.intellij.openapi.extensions.ProjectExtensionPointName
- s:addConsole(com.intellij.openapi.project.Project,com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory,java.lang.String,java.lang.String):V
- s:addConsole(com.intellij.openapi.project.Project,com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory,java.lang.String,java.lang.String,javax.swing.Icon):com.intellij.execution.ui.RunContentDescriptor
- s:addConsole(com.intellij.openapi.project.Project,com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Boolean):com.intellij.execution.ui.RunContentDescriptor
- s:createEditorPanel(com.intellij.openapi.project.Project,com.intellij.openapi.Disposable):com.intellij.unscramble.AnalyzeStacktraceUtil$StacktraceEditorPanel
- s:printStacktrace(com.intellij.execution.ui.ConsoleView,java.lang.String):V
com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory

View File

@@ -59,7 +59,8 @@ public final class AnalyzeStacktraceUtil {
@Nullable ConsoleFactory consoleFactory,
final @NlsContexts.TabTitle String tabTitle,
String text,
@Nullable Icon icon) {
@Nullable Icon icon,
Boolean withExecutor) {
final TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
builder.filters(EP_NAME.getExtensions(project));
final ConsoleView consoleView = builder.getConsole();
@@ -70,14 +71,13 @@ public final class AnalyzeStacktraceUtil {
: new MyConsolePanel(consoleView, toolbarActions);
final RunContentDescriptor descriptor =
new RunContentDescriptor(consoleView, null, consoleComponent, tabTitle, icon) {
@Override
public boolean isContentReuseProhibited() {
return true;
}
};
@Override
public boolean isContentReuseProhibited() {
return true;
}
};
final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
for (AnAction action: consoleView.createConsoleActions()) {
for (AnAction action : consoleView.createConsoleActions()) {
toolbarActions.add(action);
}
final ConsoleViewImpl console = (ConsoleViewImpl)consoleView;
@@ -85,7 +85,10 @@ public final class AnalyzeStacktraceUtil {
console.getEditor().getSettings().setCaretRowShown(true);
toolbarActions.add(ActionManager.getInstance().getAction("AnalyzeStacktraceToolbar"));
RunContentManager.getInstance(project).showRunContent(executor, descriptor);
if (withExecutor) {
final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
RunContentManager.getInstance(project).showRunContent(executor, descriptor);
}
consoleView.allowHeavyFilters();
if (consoleFactory == null) {
printStacktrace(consoleView, text);
@@ -93,6 +96,14 @@ public final class AnalyzeStacktraceUtil {
return descriptor;
}
public static RunContentDescriptor addConsole(Project project,
@Nullable ConsoleFactory consoleFactory,
final @NlsContexts.TabTitle String tabTitle,
String text,
@Nullable Icon icon) {
return addConsole(project, consoleFactory, tabTitle, text, icon, true);
}
private static final class MyConsolePanel extends JPanel {
MyConsolePanel(ExecutionConsole consoleView, ActionGroup toolbarActions) {
super(new BorderLayout());

View File

@@ -18,6 +18,7 @@ public final class DevKitIcons {
return IconManager.getInstance().loadRasterizedIcon(path, expUIPath, DevKitIcons.class.getClassLoader(), cacheKey, flags);
}
/** 16x16 */ public static final @NotNull Icon Add_sdk = load("icons/expui/addSDK.svg", "icons/add_sdk.svg", 641117830, 2);
/** 16x16 */ public static final @NotNull Icon Freeze = load("icons/expui/freeze.svg", "icons/freeze.svg", 754396711, 2);
public static final class Gutter {
/** 12x12 */ public static final @NotNull Icon DescriptionFile = load("icons/expui/gutter/descriptionFile@14x14.svg", "icons/gutter/descriptionFile.svg", 1318760137, 2);

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.49971 1C7.77586 1 7.99971 1.22386 7.99971 1.5V2.56574L8.72236 2.08397C8.95213 1.9308 9.26256 1.99289 9.41574 2.22265C9.56892 2.45241 9.50683 2.76285 9.27706 2.91603L7.99971 3.76759V7.13413L10.9154 5.45076L11.0142 3.91876C11.032 3.64319 11.2698 3.43421 11.5453 3.45198C11.8209 3.46975 12.0299 3.70755 12.0121 3.98312L11.9562 4.84984L12.8792 4.31697C13.1183 4.1789 13.4241 4.26083 13.5622 4.49998C13.7003 4.73913 13.6183 5.04492 13.3792 5.18299L12.4562 5.71586L13.2348 6.10081C13.4823 6.22321 13.5837 6.52309 13.4614 6.77063C13.339 7.01817 13.0391 7.11962 12.7915 6.99722L11.4154 6.31679L8.49965 8.00019L11.4147 9.68321L12.7909 9.00278C13.0384 8.88038 13.3383 8.98183 13.4607 9.22937C13.5831 9.4769 13.4817 9.77679 13.2341 9.89919L12.4556 10.2841L13.3785 10.817C13.6177 10.9551 13.6996 11.2609 13.5615 11.5C13.4235 11.7392 13.1177 11.8211 12.8785 11.683L11.9556 11.1502L12.0115 12.0169C12.0292 12.2924 11.8203 12.5302 11.5447 12.548C11.2691 12.5658 11.0313 12.3568 11.0135 12.0812L10.9147 10.5492L7.99971 8.86625V12.2324L9.27706 13.084C9.50683 13.2372 9.56892 13.5476 9.41574 13.7774C9.26256 14.0071 8.95213 14.0692 8.72236 13.916L7.99971 13.4343V14.5C7.99971 14.7761 7.77586 15 7.49971 15C7.22357 15 6.99971 14.7761 6.99971 14.5V13.4343L6.27706 13.916C6.0473 14.0692 5.73687 14.0071 5.58369 13.7774C5.43051 13.5476 5.4926 13.2372 5.72236 13.084L6.99971 12.2324V8.86618L4.08464 10.5492L3.98583 12.0812C3.96806 12.3568 3.73026 12.5658 3.45469 12.548C3.17912 12.5302 2.97013 12.2924 2.98791 12.0168L3.0438 11.1501L2.12085 11.683C1.8817 11.8211 1.5759 11.7391 1.43783 11.5C1.29976 11.2608 1.3817 10.955 1.62085 10.817L2.5438 10.2841L1.76526 9.89915C1.51772 9.77675 1.41627 9.47687 1.53867 9.22933C1.66106 8.98179 1.96095 8.88034 2.20848 9.00274L3.58464 9.68317L6.49965 8.00019L3.58399 6.31683L2.20784 6.99726C1.9603 7.11966 1.66041 7.01821 1.53802 6.77067C1.41563 6.52313 1.51708 6.22325 1.76461 6.10085L2.54316 5.7159L1.6202 5.18303C1.38105 5.04496 1.29912 4.73917 1.43719 4.50002C1.57526 4.26087 1.88105 4.17894 2.1202 4.31701L3.04316 4.84988L2.98726 3.98316C2.96949 3.70759 3.17848 3.46979 3.45405 3.45202C3.72962 3.43425 3.96742 3.64323 3.98519 3.9188L4.08399 5.4508L6.99971 7.1342V3.76759L5.72236 2.91603C5.4926 2.76285 5.43051 2.45241 5.58369 2.22265C5.73687 1.99289 6.0473 1.9308 6.27706 2.08397L6.99971 2.56574V1.5C6.99971 1.22386 7.22357 1 7.49971 1Z" fill="#3574F0"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7133_52115)">
<path d="M7.49971 1C7.77586 1 7.99971 1.22386 7.99971 1.5V2.56574L8.72236 2.08397C8.95213 1.9308 9.26256 1.99289 9.41574 2.22265C9.56892 2.45241 9.50683 2.76285 9.27706 2.91603L7.99971 3.76759V7.13413L10.9154 5.45076L11.0142 3.91876C11.032 3.64319 11.2698 3.43421 11.5453 3.45198C11.8209 3.46975 12.0299 3.70755 12.0121 3.98312L11.9562 4.84984L12.8792 4.31697C13.1183 4.1789 13.4241 4.26083 13.5622 4.49998C13.7003 4.73913 13.6183 5.04492 13.3792 5.18299L12.4562 5.71586L13.2348 6.10081C13.4823 6.22321 13.5837 6.52309 13.4614 6.77063C13.339 7.01817 13.0391 7.11962 12.7915 6.99722L11.4154 6.31679L8.49965 8.00019L11.4147 9.68321L12.7909 9.00278C13.0384 8.88038 13.3383 8.98183 13.4607 9.22937C13.5831 9.4769 13.4817 9.77679 13.2341 9.89919L12.4556 10.2841L13.3785 10.817C13.6177 10.9551 13.6996 11.2609 13.5615 11.5C13.4235 11.7392 13.1177 11.8211 12.8785 11.683L11.9556 11.1502L12.0115 12.0169C12.0292 12.2924 11.8203 12.5302 11.5447 12.548C11.2691 12.5658 11.0313 12.3568 11.0135 12.0812L10.9147 10.5492L7.99971 8.86625V12.2324L9.27706 13.084C9.50683 13.2372 9.56892 13.5476 9.41574 13.7774C9.26256 14.0071 8.95213 14.0692 8.72236 13.916L7.99971 13.4343V14.5C7.99971 14.7761 7.77586 15 7.49971 15C7.22357 15 6.99971 14.7761 6.99971 14.5V13.4343L6.27706 13.916C6.0473 14.0692 5.73687 14.0071 5.58369 13.7774C5.43051 13.5476 5.4926 13.2372 5.72236 13.084L6.99971 12.2324V8.86618L4.08464 10.5492L3.98583 12.0812C3.96806 12.3568 3.73026 12.5658 3.45469 12.548C3.17912 12.5302 2.97013 12.2924 2.98791 12.0168L3.0438 11.1501L2.12085 11.683C1.8817 11.8211 1.5759 11.7391 1.43783 11.5C1.29976 11.2608 1.3817 10.955 1.62085 10.817L2.5438 10.2841L1.76526 9.89915C1.51772 9.77675 1.41627 9.47687 1.53867 9.22933C1.66106 8.98179 1.96095 8.88034 2.20848 9.00274L3.58464 9.68317L6.49965 8.00019L3.58399 6.31683L2.20784 6.99726C1.9603 7.11966 1.66041 7.01821 1.53802 6.77067C1.41563 6.52313 1.51708 6.22325 1.76461 6.10085L2.54316 5.7159L1.6202 5.18303C1.38105 5.04496 1.29912 4.73917 1.43719 4.50002C1.57526 4.26087 1.88105 4.17894 2.1202 4.31701L3.04316 4.84988L2.98726 3.98316C2.96949 3.70759 3.17848 3.46979 3.45405 3.45202C3.72962 3.43425 3.96742 3.64323 3.98519 3.9188L4.08399 5.4508L6.99971 7.1342V3.76759L5.72236 2.91603C5.4926 2.76285 5.43051 2.45241 5.58369 2.22265C5.73687 1.99289 6.0473 1.9308 6.27706 2.08397L6.99971 2.56574V1.5C6.99971 1.22386 7.22357 1 7.49971 1Z" fill="#548AF7"/>
</g>
<defs>
<clipPath id="clip0_7133_52115">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.00003 9.73202V15H9.00003V9.73206L13.5622 12.366L14.5622 10.634L9.99999 7.99999L14.5622 5.36602L13.5622 3.63397L9.00003 6.26792V1H7.00003V6.26796L2.43783 3.63398L1.43783 5.36603L5.99999 7.99999L1.43781 10.634L2.43781 12.366L7.00003 9.73202Z" fill="#389FD6"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8253_2564)">
<path d="M7.00003 9.73202V15H9.00003V9.73206L13.5622 12.366L14.5622 10.634L9.99999 7.99999L14.5622 5.36602L13.5622 3.63397L9.00003 6.26792V1H7.00003V6.26796L2.43783 3.63398L1.43783 5.36603L5.99999 7.99999L1.43781 10.634L2.43781 12.366L7.00003 9.73202Z" fill="#3592C4"/>
</g>
<defs>
<clipPath id="clip0_8253_2564">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -17,5 +17,6 @@
<orderEntry type="module" module-name="intellij.devkit.jps" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.runtimeModuleRepository.jps" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.devkit.java.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.devkit.stacktrace" scope="RUNTIME" />
</component>
</module>

View File

@@ -0,0 +1,25 @@
<?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$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.devkit.core" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.editor" />
<orderEntry type="module" module-name="intellij.platform.ide.core" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.execution" />
<orderEntry type="module" module-name="intellij.java" />
<orderEntry type="module" module-name="intellij.java.impl" />
<orderEntry type="module" module-name="intellij.java.unscramble" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
<orderEntry type="module" module-name="intellij.platform.diagnostic.freezeAnalyzer" />
</component>
</module>

View File

@@ -0,0 +1,12 @@
<idea-plugin package="org.jetbrains.idea.devkit.stacktrace">
<dependencies>
<module name="intellij.devkit.core"/>
<module name="intellij.java.unscramble"/>
<module name="intellij.platform.diagnostic.freezeAnalyzer"/>
<plugin id="com.intellij.java"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<fileEditorProvider id="java-stacktrace-editor"
implementation="org.jetbrains.idea.devkit.stacktrace.editor.StackTraceEditorProvider"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,4 @@
progress.title.freeze.analysis=Analyzing freeze\u2026
progress.title.parsing.thread.dump=Parsing thread dump\u2026
stack.trace=Stack Trace
tab.title.freeze.analyzer=Freeze Analyzer

View File

@@ -0,0 +1,14 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.stacktrace
import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey
object DevKitStackTraceBundle {
private const val BUNDLE_FQN: @NonNls String = "messages.DevKitStackTraceBundle"
private val BUNDLE = DynamicBundle(DevKitStackTraceBundle::class.java, BUNDLE_FQN)
fun message(key: @PropertyKey(resourceBundle = BUNDLE_FQN) String, vararg params: Any): @Nls String = BUNDLE.getMessage(key, *params)
}

View File

@@ -0,0 +1,15 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.idea.devkit.stacktrace.editor
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.TextEditorWithPreviewProvider
internal class StackTraceEditorProvider : TextEditorWithPreviewProvider(StackTraceFileEditorProvider()) {
override fun createSplitEditor(firstEditor: TextEditor, secondEditor: FileEditor): FileEditor {
require(secondEditor is StackTraceFileEditor) { "Secondary editor should be StackTraceFileEditor" }
return StackTraceEditorWithPreview(firstEditor, secondEditor)
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.idea.devkit.stacktrace.editor
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.TextEditorWithPreview
import org.jetbrains.idea.devkit.stacktrace.DevKitStackTraceBundle
/**
* Text and stacktrace preview editor.
*/
internal class StackTraceEditorWithPreview(editor: TextEditor, preview: StackTraceFileEditor)
: TextEditorWithPreview(editor, preview, DevKitStackTraceBundle.message("stack.trace"), Layout.SHOW_EDITOR_AND_PREVIEW, false) {
init {
preview.setMainEditor(editor.getEditor())
}
override fun onLayoutChange(oldValue: Layout?, newValue: Layout?) {
if (newValue == Layout.SHOW_PREVIEW) {
myPreview.preferredFocusedComponent?.requestFocus() ?: myPreview.component.requestFocus()
}
}
}

View File

@@ -0,0 +1,173 @@
package org.jetbrains.idea.devkit.stacktrace.editor
import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.application.EDT
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorState
import com.intellij.openapi.options.advanced.AdvancedSettings
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.ToolWindow
import com.intellij.platform.diagnostic.freezeAnalyzer.FreezeAnalyzer
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.threadDumpParser.ThreadDumpParser.parse
import com.intellij.ui.content.Content
import com.intellij.ui.content.ContentFactory.getInstance
import com.intellij.ui.content.ContentManager
import com.intellij.ui.content.TabbedPaneContentUI
import com.intellij.unscramble.AnalyzeStacktraceUtil
import com.intellij.unscramble.UnscrambleUtils.addConsole
import com.intellij.util.concurrency.annotations.RequiresEdt
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import org.jetbrains.idea.devkit.DevKitIcons
import org.jetbrains.idea.devkit.stacktrace.DevKitStackTraceBundle
import org.jetbrains.idea.devkit.stacktrace.util.StackTracePluginScope
import java.awt.BorderLayout
import java.beans.PropertyChangeListener
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.SwingConstants.TOP
import kotlin.time.Duration.Companion.milliseconds
class StackTraceFileEditor(private val project: Project, private val file: VirtualFile) : FileEditor, UserDataHolderBase() {
private val document = FileDocumentManager.getInstance().getDocument(file) ?: error("Document not found for file: $file")
private val mainPanelWrapper = JPanel(BorderLayout())
private val mainEditor = MutableStateFlow<Editor?>(null)
private val coroutineScope = StackTracePluginScope.createChildScope(project)
private var myContentManager: ContentManager? = null
init {
document.addDocumentListener(ReparseContentDocumentListener(), this)
coroutineScope.launch(Dispatchers.EDT) {
myContentManager = getInstance().createContentManager(TabbedPaneContentUI(TOP), false, project)
updateStacktracePane()
}
}
fun setMainEditor(editor: Editor) {
check(mainEditor.value == null) { "Main editor already set" }
mainEditor.value = editor
}
override fun getComponent(): JComponent = mainPanelWrapper
override fun getPreferredFocusedComponent(): JComponent? = myContentManager?.component ?: mainPanelWrapper
override fun getName(): String = DevKitStackTraceBundle.message("stack.trace")
override fun setState(state: FileEditorState) {}
override fun isModified(): Boolean = false
override fun isValid(): Boolean = true
override fun addPropertyChangeListener(listener: PropertyChangeListener) {}
override fun removePropertyChangeListener(listener: PropertyChangeListener) {}
override fun getFile(): VirtualFile = file
override fun dispose() {
myContentManager?.removeAllContents(true)
myContentManager?.let {
Disposer.dispose(it)
}
myContentManager = null
coroutineScope.cancel()
}
@RequiresEdt
private suspend fun updateStacktracePane() {
if (!file.isValid) return
val contentManager = myContentManager ?: return
myContentManager?.removeAllContents(true)
addThreadContent(contentManager) ?: return
addFreezeAnalysisContent(contentManager)
mainPanelWrapper.add(contentManager.component, BorderLayout.CENTER)
if (mainPanelWrapper.isShowing) mainPanelWrapper.validate()
mainPanelWrapper.repaint()
}
private suspend fun addThreadContent(contentManager: ContentManager): Unit? = withContext(Dispatchers.Default) {
withBackgroundProgress(project, DevKitStackTraceBundle.message("progress.title.parsing.thread.dump")) {
val threadStates = parse(document.text)
withContext(Dispatchers.EDT) {
addConsole(project, threadStates, document.text, false)
}
}
}?.let { descriptor ->
contentManager.addContent(createNewContent(descriptor).apply {
executionId = descriptor.executionId
component = descriptor.component
setPreferredFocusedComponent(descriptor.preferredFocusComputable)
putUserData(RunContentDescriptor.DESCRIPTOR_KEY, descriptor)
displayName = descriptor.displayName
descriptor.setAttachedContent(this)
})
}
private suspend fun addFreezeAnalysisContent(contentManager: ContentManager) {
withContext(Dispatchers.Default) {
withBackgroundProgress(project, DevKitStackTraceBundle.message("progress.title.freeze.analysis")) {
FreezeAnalyzer.analyzeFreeze(document.text)
}
}?.let { result ->
val freezeDescriptor = AnalyzeStacktraceUtil.addConsole(
project, null,
DevKitStackTraceBundle.message("tab.title.freeze.analyzer"),
"${result.message}\n${result.additionalMessage ?: ""}\n======= Stack Trace: ========= \n${result.threads.joinToString { it -> it.stackTrace }}",
DevKitIcons.Freeze, false
)
contentManager.addContent(createNewContent(freezeDescriptor).apply {
executionId = freezeDescriptor.executionId
component = freezeDescriptor.component
setPreferredFocusedComponent(freezeDescriptor.preferredFocusComputable)
putUserData(RunContentDescriptor.DESCRIPTOR_KEY, freezeDescriptor)
displayName = freezeDescriptor.displayName
})
}
}
private fun createNewContent(descriptor: RunContentDescriptor): Content {
val content = getInstance().createContent(
descriptor.component, descriptor.displayName, true
).apply {
putUserData(ToolWindow.SHOW_CONTENT_ICON, true)
isPinned = AdvancedSettings.getBoolean("start.run.configurations.pinned")
icon = descriptor.icon
}
return content
}
private inner class ReparseContentDocumentListener : DocumentListener {
@OptIn(FlowPreview::class)
private val documentChangedRequests = MutableSharedFlow<Unit>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
).apply {
coroutineScope.launch(Dispatchers.EDT) {
debounce(500.milliseconds)
.collectLatest {
updateStacktracePane()
}
}
}
override fun documentChanged(event: DocumentEvent) {
documentChangedRequests.tryEmit(Unit)
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.idea.devkit.stacktrace.editor
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorPolicy
import com.intellij.openapi.fileEditor.WeighedFileEditorProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtilCore.loadText
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.unscramble.UnscrambleUtils
/**
* Java stacktrace weighed file editor provider.
*/
class StackTraceFileEditorProvider : WeighedFileEditorProvider() {
override fun accept(project: Project, file: VirtualFile): Boolean {
return file.extension == "txt" && UnscrambleUtils.isStackTrace(loadText(file))
}
override fun createEditor(project: Project, file: VirtualFile): FileEditor = StackTraceFileEditor(project, file)
override fun getEditorTypeId(): String = "stacktrace-preview-editor"
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR
}

View File

@@ -0,0 +1,21 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.idea.devkit.stacktrace.util
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.platform.util.coroutines.childScope
import kotlinx.coroutines.CoroutineScope
@Service(Service.Level.PROJECT)
internal class StackTracePluginScope(private val coroutineScope: CoroutineScope) {
companion object {
fun createChildScope(project: Project): CoroutineScope {
return scope(project).childScope("DevKitStackTracePlugin")
}
fun scope(project: Project): CoroutineScope {
return project.service<StackTracePluginScope>().coroutineScope
}
}
}

View File

@@ -23,4 +23,5 @@
- name: intellij.devkit.uiDesigner
- name: intellij.devkit.workspaceModel
- name: intellij.kotlin.devkit
- name: intellij.devkit.debugger
- name: intellij.devkit.debugger
- name: intellij.devkit.stacktrace

View File

@@ -28,6 +28,7 @@
<module name="intellij.devkit.workspaceModel"/>
<module name="intellij.kotlin.devkit"/>
<module name="intellij.devkit.debugger"/>
<module name="intellij.devkit.stacktrace"/>
</content>
<extensions defaultExtensionNs="com.intellij">