diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java b/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java index f477247181c0..fe60dab4beca 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java +++ b/java/java-impl/src/com/intellij/unscramble/ThreadDumpPanel.java @@ -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 myThreadDump; + private final List 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 threadDump) { super(new BorderLayout()); myThreadDump = threadDump; + myMergedThreadDump = new ArrayList(); + List copy = new ArrayList(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 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 states = new ArrayList(); - 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 threadStates) { return new MyToFileExporter(project, threadStates); } diff --git a/java/java-impl/src/com/intellij/unscramble/ThreadState.java b/java/java-impl/src/com/intellij/unscramble/ThreadState.java index 51f705944bcb..d88a63715eb5 100644 --- a/java/java-impl/src/com/intellij/unscramble/ThreadState.java +++ b/java/java-impl/src/com/intellij/unscramble/ThreadState.java @@ -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", ""); + 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 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(); + } + } } diff --git a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java index 73306727aa5f..b8cf3cebb390 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java +++ b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java @@ -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 myDispatcher = EventDispatcher.create(UISettingsListener.class);