mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-55813 Thread dump analyzer to merge threads with equal stack traces
This commit is contained in:
@@ -20,6 +20,7 @@ import com.intellij.execution.ui.ConsoleView;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.ide.ExporterToTextFile;
|
||||
import com.intellij.ide.ui.UISettings;
|
||||
import com.intellij.notification.NotificationGroup;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
@@ -70,6 +71,7 @@ public class ThreadDumpPanel extends JPanel implements DataProvider {
|
||||
private static final Icon IO_ICON_DAEMON = new LayeredIcon(IO, Daemon_sign);
|
||||
private final JBList myThreadList;
|
||||
private final List<ThreadState> myThreadDump;
|
||||
private final List<ThreadState> myMergedThreadDump;
|
||||
private final JPanel myFilterPanel;
|
||||
private final SearchTextField myFilterField;
|
||||
private final ExporterToTextFile myExporterToTextFile;
|
||||
@@ -77,6 +79,20 @@ public class ThreadDumpPanel extends JPanel implements DataProvider {
|
||||
public ThreadDumpPanel(final Project project, final ConsoleView consoleView, final DefaultActionGroup toolbarActions, final List<ThreadState> threadDump) {
|
||||
super(new BorderLayout());
|
||||
myThreadDump = threadDump;
|
||||
myMergedThreadDump = new ArrayList<ThreadState>();
|
||||
List<ThreadState> copy = new ArrayList<ThreadState>(myThreadDump);
|
||||
for (int i = 0; i < copy.size(); i++) {
|
||||
ThreadState state = copy.get(i);
|
||||
ThreadState.CompoundThreadState compound = new ThreadState.CompoundThreadState(state);
|
||||
myMergedThreadDump.add(compound);
|
||||
for (int j = i+1; j < copy.size(); j++) {
|
||||
ThreadState toAdd = copy.get(j);
|
||||
if (compound.add(toAdd)) {
|
||||
copy.remove(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
myFilterField = new SearchTextField();
|
||||
myFilterField.addDocumentListener(new DocumentAdapter() {
|
||||
@@ -116,6 +132,7 @@ public class ThreadDumpPanel extends JPanel implements DataProvider {
|
||||
toolbarActions.add(new CopyToClipboardAction(threadDump, project));
|
||||
toolbarActions.add(new SortThreadsAction());
|
||||
toolbarActions.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EXPORT_TO_TEXT_FILE));
|
||||
toolbarActions.add(new MergeStacktracesAction());
|
||||
add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false).getComponent(), BorderLayout.WEST);
|
||||
|
||||
JPanel leftPanel = new JPanel(new BorderLayout());
|
||||
@@ -156,16 +173,24 @@ public class ThreadDumpPanel extends JPanel implements DataProvider {
|
||||
|
||||
private void updateThreadList() {
|
||||
String text = myFilterPanel.isVisible() ? myFilterField.getText() : "";
|
||||
Object selection = myThreadList.getSelectedValue();
|
||||
DefaultListModel model = (DefaultListModel)myThreadList.getModel();
|
||||
model.clear();
|
||||
for (ThreadState state : myThreadDump) {
|
||||
int selectedIndex = 0;
|
||||
int index = 0;
|
||||
List<ThreadState> threadStates = UISettings.getInstance().MERGE_EQUAL_STACKTRACES ? myMergedThreadDump : myThreadDump;
|
||||
for (ThreadState state : threadStates) {
|
||||
if (StringUtil.containsIgnoreCase(state.getStackTrace(), text) || StringUtil.containsIgnoreCase(state.getName(), text)) {
|
||||
//noinspection unchecked
|
||||
model.addElement(state);
|
||||
if (selection == state) {
|
||||
selectedIndex = index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
if (!model.isEmpty()) {
|
||||
myThreadList.setSelectedIndex(0);
|
||||
myThreadList.setSelectedIndex(selectedIndex);
|
||||
}
|
||||
myThreadList.revalidate();
|
||||
myThreadList.repaint();
|
||||
@@ -301,22 +326,9 @@ public class ThreadDumpPanel extends JPanel implements DataProvider {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
final DefaultListModel model = (DefaultListModel)myThreadList.getModel();
|
||||
final ThreadState selected = (ThreadState)myThreadList.getSelectedValue();
|
||||
ArrayList<ThreadState> states = new ArrayList<ThreadState>();
|
||||
for (int i = 0; i < model.getSize(); i++) {
|
||||
states.add((ThreadState)model.getElementAt(i));
|
||||
}
|
||||
Collections.sort(states, COMPARATOR);
|
||||
int selectedIndex = 0;
|
||||
for (int i = 0; i < states.size(); i++) {
|
||||
final ThreadState state = states.get(i);
|
||||
model.setElementAt(state, i);
|
||||
if (state == selected) {
|
||||
selectedIndex = i;
|
||||
}
|
||||
}
|
||||
myThreadList.setSelectedIndex(selectedIndex);
|
||||
Collections.sort(myThreadDump, COMPARATOR);
|
||||
Collections.sort(myMergedThreadDump, COMPARATOR);
|
||||
updateThreadList();
|
||||
COMPARATOR = COMPARATOR == BY_TYPE ? BY_NAME : BY_TYPE;
|
||||
update(e);
|
||||
}
|
||||
@@ -372,6 +384,23 @@ public class ThreadDumpPanel extends JPanel implements DataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private class MergeStacktracesAction extends ToggleAction implements DumbAware {
|
||||
MergeStacktracesAction() {
|
||||
super("Merge Identical Stacktraces", "Group threads with identical stacktraces", AllIcons.General.CollapseAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected(AnActionEvent e) {
|
||||
return UISettings.getInstance().MERGE_EQUAL_STACKTRACES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(AnActionEvent e, boolean state) {
|
||||
UISettings.getInstance().MERGE_EQUAL_STACKTRACES = state;
|
||||
updateThreadList();
|
||||
}
|
||||
}
|
||||
|
||||
public static ExporterToTextFile createToFileExporter(Project project, List<ThreadState> threadStates) {
|
||||
return new MyToFileExporter(project, threadStates);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.unscramble;
|
||||
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -159,4 +160,118 @@ public class ThreadState {
|
||||
public void setDaemon(boolean daemon) {
|
||||
isDaemon = daemon;
|
||||
}
|
||||
|
||||
public static class CompoundThreadState extends ThreadState {
|
||||
private final ThreadState myOriginalState;
|
||||
private int myCounter = 1;
|
||||
|
||||
public CompoundThreadState(ThreadState state) {
|
||||
super(state.myName, state.myState);
|
||||
myOriginalState = state;
|
||||
}
|
||||
|
||||
public boolean add(ThreadState state) {
|
||||
if (myOriginalState.isEDT()) return false;
|
||||
if (!Comparing.equal(state.myState, myOriginalState.myState)) return false;
|
||||
if (state.myEmptyStackTrace != myOriginalState.myEmptyStackTrace) return false;
|
||||
if (state.isDaemon != myOriginalState.isDaemon) return false;
|
||||
if (!Comparing.equal(state.myJavaThreadState, myOriginalState.myJavaThreadState)) return false;
|
||||
if (!Comparing.equal(state.myThreadStateDetail, myOriginalState.myThreadStateDetail)) return false;
|
||||
if (!Comparing.equal(state.myExtraState, myOriginalState.myExtraState)) return false;
|
||||
if (!Comparing.haveEqualElements(state.myThreadsWaitingForMyLock, myOriginalState.myThreadsWaitingForMyLock)) return false;
|
||||
if (!Comparing.haveEqualElements(state.myDeadlockedThreads, myOriginalState.myDeadlockedThreads)) return false;
|
||||
if (!Comparing.equal(getMergeableStackTrace(state.myStackTrace), getMergeableStackTrace(myOriginalState.myStackTrace))) return false;
|
||||
myCounter++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String getMergeableStackTrace(String stackTrace) {
|
||||
if (stackTrace == null) return null;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String[] lines = stackTrace.split("\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
if (i == 0) continue;//first line has unique details
|
||||
line = line.replaceAll("<0x.+>\\s", "<merged>");
|
||||
builder.append(line).append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return (myCounter == 1) ? myOriginalState.getName() : myCounter + " similar threads";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return myOriginalState.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStackTrace() {
|
||||
return myCounter == 1 ? myOriginalState.getStackTrace() : getMergeableStackTrace(myOriginalState.getStackTrace());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ThreadState> getAwaitingThreads() {
|
||||
return myOriginalState.getAwaitingThreads();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJavaThreadState() {
|
||||
return myOriginalState.getJavaThreadState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getThreadStateDetail() {
|
||||
return myOriginalState.getThreadStateDetail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmptyStackTrace() {
|
||||
return myOriginalState.isEmptyStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtraState() {
|
||||
return myOriginalState.getExtraState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAwaitedBy(ThreadState thread) {
|
||||
return myOriginalState.isAwaitedBy(thread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeadlocked() {
|
||||
return myOriginalState.isDeadlocked();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ThreadOperation getOperation() {
|
||||
return myOriginalState.getOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaiting() {
|
||||
return myOriginalState.isWaiting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEDT() {
|
||||
return myOriginalState.isEDT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDaemon() {
|
||||
return myOriginalState.isDaemon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSleeping() {
|
||||
return myOriginalState.isSleeping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ public class UISettings extends SimpleModificationTracker implements PersistentS
|
||||
public boolean SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES = true;
|
||||
public boolean NAVIGATE_TO_PREVIEW = false;
|
||||
public boolean SORT_BOOKMARKS = false;
|
||||
public boolean MERGE_EQUAL_STACKTRACES = false;
|
||||
|
||||
private final EventDispatcher<UISettingsListener> myDispatcher = EventDispatcher.create(UISettingsListener.class);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user