IDEA-55813 Thread dump analyzer to merge threads with equal stack traces

This commit is contained in:
Vassiliy.Kudryashov
2015-09-08 12:39:56 +03:00
parent 8463342b69
commit 8e7d266063
3 changed files with 163 additions and 18 deletions
@@ -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);