/** * Thread Dump Analysis Tool, parses Thread Dump input and displays it as tree * * This file is part of TDA - Thread Dump Analysis Tool. * * TDA is free software; you can redistribute it and/or modify * it under the terms of the Lesser GNU General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * TDA is distributed in the hope that it will be useful,h * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Lesser GNU General Public License for more details. * * TDA should have received a copy of the Lesser GNU General Public License * along with Foobar; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id: TDA.java,v 1.190 2010-02-03 12:40:29 irockel Exp $ */ package com.pironet.tda; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Image; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetAdapter; import java.awt.dnd.DropTargetDropEvent; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.ProgressMonitorInputStream; import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.WindowConstants; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileFilter; import javax.swing.plaf.FontUIResource; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import com.pironet.tda.jconsole.MBeanDumper; import com.pironet.tda.parser.DumpParser; import com.pironet.tda.parser.DumpParserFactory; import com.pironet.tda.utils.AppInfo; import com.pironet.tda.utils.Browser; import com.pironet.tda.utils.HistogramTableModel; import com.pironet.tda.utils.MonitorComparator; import com.pironet.tda.utils.PrefManager; import com.pironet.tda.utils.ResourceManager; import com.pironet.tda.utils.StatusBar; import com.pironet.tda.utils.SwingWorker; import com.pironet.tda.utils.TableSorter; import com.pironet.tda.utils.ThreadsTableModel; import com.pironet.tda.utils.ThreadsTableSelectionModel; import com.pironet.tda.utils.TipOfDay; import com.pironet.tda.utils.ViewScrollPane; import com.pironet.tda.utils.jedit.JEditTextArea; import com.pironet.tda.utils.jedit.PopupMenu; import fr.loicmathieu.bobbin.gui.DumpTree; import fr.loicmathieu.bobbin.gui.LinesTableModel; /** * main class of the Thread Dump Analyzer. Start using static main method. * * @author irockel * @author lmathieu */ public class TDA extends JPanel implements ListSelectionListener, TreeSelectionListener, ActionListener, MenuListener { private static final String DEFAULT_FONT = "SansSerif"; private static final long serialVersionUID = -5202913524923361779L; private static final int FONT_SIZE = 12;//LMA bigger font to read easily private static JFileChooser fc; private static JFileChooser sessionFc; private static int DIVIDER_SIZE = 4; protected static JFrame frame; private static String dumpFile; private static String loggcFile; private static int fontSizeModifier = 0; private static TDA myTDA = null; private JEditorPane htmlPane; private JEditTextArea jeditPane; protected DumpTree tree; protected DefaultTreeModel treeModel; private JSplitPane splitPane; protected JSplitPane topSplitPane; private DumpStore dumpStore; private List<DefaultMutableTreeNode> topNodes; private ViewScrollPane htmlView; private ViewScrollPane tableView; private ViewScrollPane dumpView; private JTextField filter; private JCheckBox checkCase; private PreferencesDialog prefsDialog; private ThreadFilterDialog filterDialog; private CustomCategoriesDialog categoriesDialog; private JTable histogramTable; private DefaultMutableTreeNode logFile; private MainMenu pluginMainMenu; private DropTarget dt = null; private DropTarget hdt = null; private PopupListener catPopupListener = null; private StatusBar statusBar; private SearchDialog searchDialog; private MBeanDumper mBeanDumper; boolean runningAsJConsolePlugin; boolean runningAsVisualVMPlugin; private int dumpCounter; private boolean threadDisplay = false; private boolean isFoundClassHistogram = false; private static Object syncObject = new Object(); /** flag indicates if next file to open will be the first file (so fresh open) or if a add has to be performed. */ private boolean firstFile = true; private int rootNodeLevel = 0; /** trigger, if a file is opened */ private boolean fileOpen = false; /** * singleton access method for TDA */ public static TDA get(boolean setLF) { if (myTDA == null) { myTDA = new TDA(setLF); } return (myTDA); } /** * constructor (needs to be public for plugin) */ public TDA(boolean setLF) { super(new BorderLayout()); if (setLF) { // init L&F setupLookAndFeel(); } } /** * constructor (needs to be public for plugin) */ public TDA(boolean setLF, MBeanDumper mBeanDumper) { this(setLF); this.mBeanDumper = mBeanDumper; } public TDA(boolean setLF, String dumpFile) { this(setLF); TDA.dumpFile = dumpFile; } /** * initializes tda panel * * @param asVisualVMPlugin specifies if tda is running as a visualVM plugin * @param asJConsolePlugin specifies if tda is running as a jconsole plugin */ public void init(boolean asJConsolePlugin, boolean asVisualVMPlugin) { // init everything runningAsJConsolePlugin = asJConsolePlugin; runningAsVisualVMPlugin = asVisualVMPlugin; // Create the HTML viewing pane. if (!this.runningAsVisualVMPlugin && !this.runningAsJConsolePlugin) { InputStream is = TDA.class.getResourceAsStream("doc/welcome.html"); htmlPane = new JEditorPane(); String welcomeText = parseWelcomeURL(is); htmlPane.setContentType("text/html"); htmlPane.setText(welcomeText); } else if (asJConsolePlugin) { htmlPane = new JEditorPane("text/html", "<html><body bgcolor=\"ffffff\"><i>Press Button above to request a thread dump.</i></body></html>"); } else { htmlPane = new JEditorPane("text/html", "<html><body bgcolor=\"ffffff\"></body></html>"); } htmlPane.setEditable(false); if (!asJConsolePlugin && !asVisualVMPlugin) { hdt = new DropTarget(htmlPane, new FileDropTargetListener()); } JEditorPane emptyPane = new JEditorPane("text/html", ""); emptyPane.setEditable(false); htmlPane.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent evt) { // if a link was clicked if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { if (evt.getDescription().startsWith("monitor")) { DefaultMutableTreeNode monitorNode = tree.navigateToMonitor(evt.getDescription()); displayCategory(monitorNode.getUserObject()); } //LMA : add thread link else if (evt.getDescription().startsWith("thread") && ! evt.getDescription().startsWith("threaddump")){ DefaultMutableTreeNode threadNode = tree.navigateToThread(evt.getDescription()); displayCategory(threadNode.getUserObject()); } else if (evt.getDescription().startsWith("dump")) { tree.navigateToDump(); } else if (evt.getDescription().startsWith("wait")) { tree.navigateToChild("Threads waiting"); } else if (evt.getDescription().startsWith("sleep")) { tree.navigateToChild("Threads sleeping"); } else if (evt.getDescription().startsWith("dead")) { tree.navigateToChild("Deadlocks"); } else if (evt.getDescription().startsWith("threaddump")) { addMXBeanDump(); } else if (evt.getDescription().startsWith("openlogfile") && !evt.getDescription().endsWith("//")) { File[] files = { new File(evt.getDescription().substring(14)) }; openFiles(files, false); } else if (evt.getDescription().startsWith("openlogfile")) { chooseFile(); } else if (evt.getDescription().startsWith("opensession") && !evt.getDescription().endsWith("//")) { File file = new File(evt.getDescription().substring(14)); openSession(file, true); } else if (evt.getDescription().startsWith("opensession")) { openSession(); } else if (evt.getDescription().startsWith("preferences")) { showPreferencesDialog(); } else if (evt.getDescription().startsWith("filters")) { showFilterDialog(); } else if (evt.getDescription().startsWith("categories")) { showCategoriesDialog(); } else if (evt.getDescription().startsWith("overview")) { showHelp(); } else if (evt.getURL() != null) { try { // launch a browser with the appropriate URL Browser.open(evt.getURL().toString()); } catch (InterruptedException e) { System.out.println("Error launching external browser."); } catch (IOException e) { System.out.println("I/O error launching external browser." + e.getMessage()); e.printStackTrace(); } } } } }); htmlView = new ViewScrollPane(htmlPane, runningAsVisualVMPlugin); ViewScrollPane emptyView = new ViewScrollPane(emptyPane, runningAsVisualVMPlugin); // create the top split pane topSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); topSplitPane.setLeftComponent(emptyView); topSplitPane.setDividerSize(DIVIDER_SIZE); topSplitPane.setContinuousLayout(true); // Add the scroll panes to a split pane. splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setBottomComponent(htmlView); splitPane.setTopComponent(topSplitPane); splitPane.setDividerSize(DIVIDER_SIZE); splitPane.setContinuousLayout(true); if (this.runningAsVisualVMPlugin) { setOpaque(true); setBackground(Color.WHITE); setBorder(BorderFactory.createEmptyBorder(6, 0, 3, 0)); topSplitPane.setBorder(BorderFactory.createEmptyBorder()); topSplitPane.setOpaque(false); topSplitPane.setBackground(Color.WHITE); htmlPane.setBorder(BorderFactory.createEmptyBorder()); htmlPane.setOpaque(false); htmlPane.setBackground(Color.WHITE); splitPane.setBorder(BorderFactory.createEmptyBorder()); splitPane.setOpaque(false); splitPane.setBackground(Color.WHITE); } Dimension minimumSize = new Dimension(200, 50); htmlView.setMinimumSize(minimumSize); emptyView.setMinimumSize(minimumSize); // Add the split pane to this panel. add(htmlView, BorderLayout.CENTER); statusBar = new StatusBar(!(asJConsolePlugin || asVisualVMPlugin)); add(statusBar, BorderLayout.SOUTH); firstFile = true; setFileOpen(false); if (!runningAsVisualVMPlugin) { setShowToolbar(PrefManager.get().getShowToolbar()); } if (firstFile && runningAsVisualVMPlugin) { // init filechooser fc = new JFileChooser(); fc.setMultiSelectionEnabled(true); fc.setCurrentDirectory(PrefManager.get().getSelectedPath()); } } private void getLogfileFromClipboard() { Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); String text = null; try { if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) { text = (String) t.getTransferData(DataFlavor.stringFlavor); } } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } if (text != null) { if (topNodes == null) { initDumpDisplay(text); } else { addDumpStream(new ByteArrayInputStream(text.getBytes()), "Clipboard at " + new Date(System.currentTimeMillis()), false); addToLogfile(text); if (this.getRootPane() != null) { this.getRootPane().revalidate(); } displayContent(null); } if (!this.runningAsVisualVMPlugin) { getMainMenu().getFindLRThreadsToolBarButton().setEnabled(true); getMainMenu().getExpandButton().setEnabled(true); getMainMenu().getCollapseButton().setEnabled(true); } } } private String parseWelcomeURL(InputStream is) { String resultString = null; StringBuffer result = new StringBuffer(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is))){ while (br.ready()) { result.append(br.readLine()); result.append("\n"); } resultString = result.toString(); resultString = resultString.replaceFirst("./important.png", TDA.class.getResource("doc/important.png").toString()); resultString = resultString.replaceFirst("./logo.png", TDA.class.getResource("doc/logo.png").toString()); resultString = resultString.replaceFirst("./fileopen.png", TDA.class.getResource("doc/fileopen.png").toString()); resultString = resultString.replaceFirst("./settings.png", TDA.class.getResource("doc/settings.png").toString()); resultString = resultString.replaceFirst("./help.png", TDA.class.getResource("doc/help.png").toString()); resultString = resultString.replaceFirst("<!-- ##tipofday## -->", TipOfDay.getTipOfDay()); resultString = resultString.replaceFirst("<!-- ##recentlogfiles## -->", getAsTable("openlogfile://", PrefManager.get().getRecentFiles())); resultString = resultString.replaceFirst("<!-- ##recentsessions## -->", getAsTable("opensession://", PrefManager.get().getRecentSessions())); } catch (IllegalArgumentException ex) { // hack to prevent crashing of the app because off unparsed replacer. ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } // remove unparsed replacers. if (resultString != null) { resultString = resultString.replaceFirst("<!-- ##tipofday## -->", ""); resultString = resultString.replaceFirst("<!-- ##recentlogfiles## -->", ""); resultString = resultString.replaceFirst("<!-- ##recentsessions## -->", ""); } return resultString; } /** * convert the given elements into a href-table to be included into the welcome page. Only last four elements are * taken. * * @param prefix link prefix to use * @param elements list of elements. * @return given elements as table. */ private String getAsTable(String prefix, String[] elements) { StringBuffer result = new StringBuffer(); int from = elements.length > 4 ? elements.length - 4 : 0; for (int i = from; i < elements.length; i++) { if (elements[i].trim().length() > 0) { // remove backslashes as they confuse the html display. String elem = elements[i].replaceAll("\\\\", "/"); result.append("<tr><td width=\"20px\"></td><td><a href=\""); result.append(prefix); result.append(elem); result.append("\">"); result.append(cutLink(elem, 80)); result.append("</a></td></tr>\n"); } } return (result.toString()); } /** * cut the given link string to the specified length + three dots. * * @param link * @param len * @return cut link or original link if link.length() <= len */ private String cutLink(String link, int len) { if (link.length() > len) { String cut = link.substring(0, len / 2) + "..." + link.substring(link.length() - (len / 2)); return (cut); } return (link); } /** * request jmx dump */ public LogFileContent addMXBeanDump() { String dump = mBeanDumper.threadDump(); String locks = mBeanDumper.findDeadlock(); // if deadlocks were found, append them to dump output. if (locks != null && !"".equals(locks)) { dump += "\n" + locks; } // System.out.println(dump); if (topNodes == null) { initDumpDisplay(null); } addDumpStream(new ByteArrayInputStream(dump.getBytes()), "Logfile", false); dumpCounter++; LogFileContent lfc = addToLogfile(dump); if (this.getRootPane() != null) { this.getRootPane().revalidate(); } tree.setShowsRootHandles(false); displayContent(null); if (!this.runningAsVisualVMPlugin) { getMainMenu().getFindLRThreadsToolBarButton().setEnabled(true); getMainMenu().getExpandButton().setEnabled(true); getMainMenu().getCollapseButton().setEnabled(true); } return (lfc); } private LogFileContent addToLogfile(String dump) { ((LogFileContent) logFile.getUserObject()).appendToContentBuffer(dump); return (((LogFileContent) logFile.getUserObject())); } /** * create file filter for session files. * * @return file filter instance. */ private static FileFilter getSessionFilter() { FileFilter filter = new FileFilter() { @Override public boolean accept(File arg0) { return (arg0 != null && (arg0.isDirectory() || arg0.getName().endsWith("tsf"))); } @Override public String getDescription() { return ("TDA Session Files"); } }; return (filter); } /** * initializes session file chooser, if not already done. */ private static void initSessionFc() { sessionFc = new JFileChooser(); sessionFc.setMultiSelectionEnabled(true); sessionFc.setCurrentDirectory(PrefManager.get().getSelectedPath()); if ((PrefManager.get().getPreferredSizeFileChooser().height > 0)) { sessionFc.setPreferredSize(PrefManager.get().getPreferredSizeFileChooser()); } sessionFc.setFileFilter(getSessionFilter()); sessionFc.setSelectedFile(null); } /** * show help dialog. */ private void showHelp() { HelpViewer.show(getFrame()); } /** * sort monitors by thread amount */ private void sortCatByThreads() { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); ((TreeCategory) node.getUserObject()).sort(new MonitorComparator()); displayCategory(node.getUserObject()); } private void saveSession() { initSessionFc(); int returnVal = sessionFc.showSaveDialog(this.getRootPane()); sessionFc.setPreferredSize(sessionFc.getSize()); PrefManager.get().setPreferredSizeFileChooser(sessionFc.getSize()); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = sessionFc.getSelectedFile(); // check if file has a suffix if (file.getName().indexOf(".") < 0) { file = new File(file.getAbsolutePath() + ".tsf"); } int selectValue = 0; if (file.exists()) { Object[] options = { "Overwrite", "Cancel" }; selectValue = JOptionPane.showOptionDialog(null, "<html><body>File exists<br><b>" + file + "</b></body></html>", "Confirm overwrite", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); } if (selectValue == 0) { try (ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(file)))) { oos.writeObject(dumpFile); oos.writeObject(topNodes); oos.writeObject(dumpStore); } catch (IOException ex) { ex.printStackTrace(); } PrefManager.get().addToRecentSessions(file.getAbsolutePath()); } } } private void openSession() { initSessionFc(); int returnVal = sessionFc.showOpenDialog(this.getRootPane()); sessionFc.setPreferredSize(sessionFc.getSize()); PrefManager.get().setPreferredSizeFileChooser(sessionFc.getSize()); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = sessionFc.getSelectedFile(); int selectValue = 0; if ((selectValue == 0) && (file.exists())) { openSession(file, false); } } } /** * open the specified session * * @param file */ private void openSession(File file, boolean isRecent) { try { loadSession(file, isRecent); } catch (FileNotFoundException ex) { JOptionPane.showMessageDialog(this.getRootPane(), "Error opening " + ex.getMessage() + ".", "Error opening session", JOptionPane.ERROR_MESSAGE); } catch (IOException ex) { JOptionPane.showMessageDialog(this.getRootPane(), "Error opening " + ex.getMessage() + ".", "Error opening session", JOptionPane.ERROR_MESSAGE); } } private void loadSession(File file, boolean isRecent) throws IOException { final ObjectInputStream ois = new ObjectInputStream(new ProgressMonitorInputStream(this, "Opening session " + file, new GZIPInputStream( new FileInputStream(file)))); setFileOpen(true); firstFile = false; resetMainPanel(); initDumpDisplay(null); final SwingWorker worker = new SwingWorker() { @Override public Object construct() { synchronized (syncObject) { try { dumpFile = (String) ois.readObject(); topNodes = (ArrayList<DefaultMutableTreeNode>) ois.readObject(); dumpStore = (DumpStore) ois.readObject(); ois.close(); } catch (IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } finally { try { ois.close(); } catch (IOException ex) { ex.printStackTrace(); } } createTree(); } return null; } }; worker.start(); if (!isRecent) { PrefManager.get().addToRecentSessions(file.getAbsolutePath()); } } private void setShowToolbar(boolean state) { if (state) { add(getMainMenu().getToolBar(), BorderLayout.PAGE_START); } else { remove(getMainMenu().getToolBar()); } revalidate(); PrefManager.get().setShowToolbar(state); } /** * tries the native look and feel on mac and windows and metal on unix (gtk still isn't looking that nice, even in * 1.6) */ private void setupLookAndFeel() { setUIFont(new FontUIResource(DEFAULT_FONT, Font.PLAIN, FONT_SIZE)); try { for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { UIManager.setLookAndFeel(info.getClassName()); break; } } } catch (Exception e) { // If Nimbus is not available, you can set the GUI to another look and feel. e.printStackTrace(); } } /** * init the basic display for showing dumps * * @param content initial logfile content may also be parsed, can also be null. only used for clipboard operations. */ public void initDumpDisplay(String content) { // clear tree dumpStore = new DumpStore(); topNodes = new ArrayList<>(); if (!runningAsJConsolePlugin && !runningAsVisualVMPlugin) { getMainMenu().enableMenu(); } if (!runningAsJConsolePlugin || (dumpFile != null)) { if (dumpFile != null) { addDumpFile(); } else if (content != null) { addDumpStream(new ByteArrayInputStream(content.getBytes()), "Clipboard at " + new Date(System.currentTimeMillis()), false); addToLogfile(content); } } if (runningAsJConsolePlugin || runningAsVisualVMPlugin || isFileOpen()) { if (topSplitPane.getDividerLocation() <= 0) { topSplitPane.setDividerLocation(200); } // change from html view to split pane remove(0); revalidate(); htmlPane.setText(""); splitPane.setBottomComponent(htmlView); add(splitPane, BorderLayout.CENTER); if (PrefManager.get().getDividerPos() > 0) { splitPane.setDividerLocation(PrefManager.get().getDividerPos()); } else { // set default divider location splitPane.setDividerLocation(100); } revalidate(); } } /** * add the set dumpFileStream to the tree */ private void addDumpFile() { addDumpFile(dumpFile); } /** * add the set dumpFileStream to the tree */ public void addDumpFile(String filePath) { String[] file = new String[1]; file[0] = filePath; addDumpFiles(file); } private boolean isLogfileSizeOk(String fileName) { File file = new File(fileName); return (file.isFile() && ((PrefManager.get().getMaxLogfileSize() == 0) || (file.length() <= (PrefManager.get().getMaxLogfileSize() * 1024)))); } /** * add the set dumpFileStream to the tree */ private void addDumpFiles(String[] files) { for (String file : files) { try { dumpCounter = 1; addDumpStream(new FileInputStream(file), file, true); } catch (FileNotFoundException ex) { JOptionPane.showMessageDialog(this.getRootPane(), "Error opening " + ex.getMessage() + ".", "Error opening file", JOptionPane.ERROR_MESSAGE); } } } private void addDumpStream(InputStream inputStream, String file, boolean withLogfile) { final InputStream parseFileStream = new ProgressMonitorInputStream(this, "Parsing " + file, inputStream); // Create the nodes. if (!runningAsJConsolePlugin || topNodes.size() == 0) { topNodes.add(new DefaultMutableTreeNode(new Logfile(file))); } final DefaultMutableTreeNode top = topNodes.get(topNodes.size() - 1); if ((!withLogfile && logFile == null) || isLogfileSizeOk(file)) { logFile = new DefaultMutableTreeNode(new LogFileContent(file)); if (!runningAsVisualVMPlugin) { top.add(logFile); } } setFileOpen(true); final SwingWorker worker = new SwingWorker() { @Override public Object construct() { synchronized (syncObject) { int divider = topSplitPane.getDividerLocation(); addThreadDumps(top, parseFileStream); createTree(); tree.expandRow(1); topSplitPane.setDividerLocation(divider); } return null; } }; worker.start(); } /** * LMA : extract TREE GUI in separate file */ protected void createTree() { // Create a tree that allows multiple selection at a time. if (topNodes.size() == 1) { treeModel = new DefaultTreeModel(topNodes.get(0)); boolean rootVisible = !runningAsJConsolePlugin && !runningAsVisualVMPlugin; tree = new DumpTree(this, treeModel, rootVisible, runningAsJConsolePlugin, runningAsVisualVMPlugin, isFoundClassHistogram); addTreeListener(tree); if (!runningAsJConsolePlugin && !runningAsVisualVMPlugin) { frame.setTitle("TDA - Thread Dumps of " + dumpFile); } } else { DefaultMutableTreeNode root = new DefaultMutableTreeNode("Thread Dump Nodes"); treeModel = new DefaultTreeModel(root); for (int i = 0; i < topNodes.size(); i++) { root.add(topNodes.get(i)); } tree = new DumpTree(this, treeModel, false, runningAsJConsolePlugin, runningAsVisualVMPlugin, isFoundClassHistogram); addTreeListener(tree); if (!runningAsJConsolePlugin && !runningAsVisualVMPlugin) { if (!frame.getTitle().endsWith("...")) { frame.setTitle(frame.getTitle() + " ..."); } } } // Create the scroll pane and add the tree to it. ViewScrollPane treeView = new ViewScrollPane(tree, runningAsVisualVMPlugin); topSplitPane.setLeftComponent(treeView); Dimension minimumSize = new Dimension(200, 50); treeView.setMinimumSize(minimumSize); if (!runningAsJConsolePlugin && !runningAsVisualVMPlugin) { dt = new DropTarget(tree, new FileDropTargetListener()); } } /** * add a tree listener for enabling/disabling menu and toolbar icons. * * @param theTree */ private void addTreeListener(JTree theTree) { theTree.addTreeSelectionListener(new TreeSelectionListener() { ViewScrollPane emptyView = null; public void valueChanged(TreeSelectionEvent e) { getMainMenu().getCloseMenuItem().setEnabled(e.getPath() != null); if (getMainMenu().getCloseToolBarButton() != null) { getMainMenu().getCloseToolBarButton().setEnabled(e.getPath() != null); } // reset right pane of the top view: if (emptyView == null) { JEditorPane emptyPane = new JEditorPane("text/html", "<html><body bgcolor=\"ffffff\"> </body></html>"); emptyPane.setEditable(false); emptyView = new ViewScrollPane(emptyPane, runningAsVisualVMPlugin); } if (e.getPath() == null || !(((DefaultMutableTreeNode) e.getPath().getLastPathComponent()).getUserObject() instanceof Category)) { resetPane(); } } private void resetPane() { int dividerLocation = topSplitPane.getDividerLocation(); topSplitPane.setRightComponent(emptyView); topSplitPane.setDividerLocation(dividerLocation); } }); } private void setThreadDisplay(boolean value) { threadDisplay = value; /* * if(!value) { // clear thread pane topSplitPane.setRightComponent(null); } */ } public boolean isThreadDisplay() { return (threadDisplay); } /** * Required by TreeSelectionListener interface. */ public void valueChanged(TreeSelectionEvent e) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent(); if (node == null) { return; } Object nodeInfo = node.getUserObject(); if (nodeInfo instanceof ThreadInfo) { displayThreadInfo(nodeInfo); setThreadDisplay(true); } else if (nodeInfo instanceof ThreadDumpInfo) { displayThreadDumpInfo(nodeInfo); } else if (nodeInfo instanceof HistogramInfo) { HistogramInfo tdi = (HistogramInfo) nodeInfo; displayTable((HistogramTableModel) tdi.content); setThreadDisplay(false); } else if (nodeInfo instanceof LogFileContent) { displayLogFileContent(nodeInfo); } else if (nodeInfo instanceof Logfile && ((String) ((Logfile) nodeInfo).getContent()).startsWith("Thread Dumps")) { displayLogFile(); setThreadDisplay(false); } else if (nodeInfo instanceof Category) { displayCategory(nodeInfo); setThreadDisplay(true); } else { setThreadDisplay(false); displayContent(null); } } /** * process table selection events (thread display) * * @param e the event to process. */ public void valueChanged(ListSelectionEvent e) { // displayCategory(e.getFirstIndex()); ThreadsTableSelectionModel ttsm = (ThreadsTableSelectionModel) e.getSource(); TableSorter ts = (TableSorter) ttsm.getTable().getModel(); int[] rows = ttsm.getTable().getSelectedRows(); StringBuilder sb = new StringBuilder(); for (int row : rows) { //addon LMA : enable different display for threads and lines if(ts.getTableModel() instanceof LinesTableModel){ appendThreadInfo(sb, ((LinesTableModel) ts.getTableModel()).getInfoObjectAtRow(ts.modelIndex(row))); } else { appendThreadInfo(sb, ((ThreadsTableModel) ts.getTableModel()).getInfoObjectAtRow(ts.modelIndex(row))); } } displayContent(sb.toString()); setThreadDisplay(true); } private void displayThreadInfo(Object nodeInfo) { StringBuilder sb = new StringBuilder(""); appendThreadInfo(sb, nodeInfo); displayContent(sb.toString()); } private void appendThreadInfo(StringBuilder sb, Object nodeInfo) { ThreadInfo ti = (ThreadInfo) nodeInfo; if (ti.getInfo() != null) { sb.append(ti.getInfo()); } sb.append(ti.getContent()); } /** * display thread dump information for the give node object. * * @param nodeInfo */ private void displayThreadDumpInfo(Object nodeInfo) { ThreadDumpInfo ti = (ThreadDumpInfo) nodeInfo; displayContent(ti.getOverview()); } private void displayLogFile() { if (splitPane.getBottomComponent() != htmlView) { splitPane.setBottomComponent(htmlView); } htmlPane.setContentType("text/html"); htmlPane.setText(""); htmlPane.setCaretPosition(0); threadDisplay = false; statusBar.setInfoText(AppInfo.getStatusBarInfo()); } private void displayLogFileContent(Object nodeInfo) { int dividerLocation = splitPane.getDividerLocation(); if (splitPane.getBottomComponent() != jeditPane) { if (jeditPane == null) { initJeditView(); } splitPane.setBottomComponent(jeditPane); } LogFileContent lfc = (LogFileContent) nodeInfo; jeditPane.setText(lfc.getContent()); jeditPane.setCaretPosition(0); splitPane.setDividerLocation(dividerLocation); statusBar.setInfoText(AppInfo.getStatusBarInfo()); } /** * initialize the base components needed for the jedit view of the log file */ private void initJeditView() { jeditPane = new JEditTextArea(); jeditPane.setEditable(false); jeditPane.setCaretVisible(false); jeditPane.setCaretBlinkEnabled(false); jeditPane.setRightClickPopup(new PopupMenu(jeditPane, this, runningAsVisualVMPlugin)); jeditPane.getInputHandler().addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), (ActionListener) jeditPane.getRightClickPopup()); jeditPane.getInputHandler().addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), (ActionListener) jeditPane.getRightClickPopup()); } /** * display selected category in upper right frame */ private void displayCategory(Object nodeInfo) { Category cat = ((Category) nodeInfo); Dimension size = null; ((JScrollPane) topSplitPane.getLeftComponent()).setPreferredSize(topSplitPane.getLeftComponent().getSize()); boolean needDividerPos = false; if (topSplitPane.getRightComponent() != null) { size = topSplitPane.getRightComponent().getSize(); } else { needDividerPos = true; } setThreadDisplay(true); if (cat.getLastView() == null) { JComponent catComp = cat.getCatComponent(this); if (cat.getName().startsWith("Monitors") || cat.getName().startsWith("Threads blocked by Monitors")) { catComp.addMouseListener(getMonitorsPopupMenu()); } else { catComp.addMouseListener(getCatPopupMenu()); } dumpView = new ViewScrollPane(catComp, runningAsVisualVMPlugin); if (size != null) { dumpView.setPreferredSize(size); } topSplitPane.setRightComponent(dumpView); cat.setLastView(dumpView); } else { if (size != null) { cat.getLastView().setPreferredSize(size); } topSplitPane.setRightComponent(cat.getLastView()); } if (cat.getCurrentlySelectedUserObject() != null) { displayThreadInfo(cat.getCurrentlySelectedUserObject()); } else { displayContent(null); } if (needDividerPos) { topSplitPane.setDividerLocation(PrefManager.get().getTopDividerPos()); } if (cat.howManyFiltered() > 0) { statusBar.setInfoText("Filtered " + cat.howManyFiltered() + " elements in this category. Showing remaining " + cat.showing() + " elements."); } else { statusBar.setInfoText(AppInfo.getStatusBarInfo()); } displayContent(cat.getInfo()); } private void displayContent(String text) { if (splitPane.getBottomComponent() != htmlView) { splitPane.setBottomComponent(htmlView); } if (text != null) { htmlPane.setContentType("text/html"); htmlPane.setText(text); htmlPane.setCaretPosition(0); } else { htmlPane.setText(""); } } private void displayTable(HistogramTableModel htm) { setThreadDisplay(false); htm.setFilter(""); htm.setShowHotspotClasses(PrefManager.get().getShowHotspotClasses()); TableSorter ts = new TableSorter(htm); histogramTable = new JTable(ts); ts.setTableHeader(histogramTable.getTableHeader()); histogramTable.getColumnModel().getColumn(0).setPreferredWidth(700); tableView = new ViewScrollPane(histogramTable, runningAsVisualVMPlugin); JPanel histogramView = new JPanel(new BorderLayout()); JPanel histoStatView = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 0)); Font font = new Font(DEFAULT_FONT, Font.PLAIN, FONT_SIZE); JLabel infoLabel = new JLabel(NumberFormat.getInstance().format(htm.getRowCount()) + " classes and base types"); infoLabel.setFont(font); histoStatView.add(infoLabel); infoLabel = new JLabel(NumberFormat.getInstance().format(htm.getBytes()) + " bytes"); infoLabel.setFont(font); histoStatView.add(infoLabel); infoLabel = new JLabel(NumberFormat.getInstance().format(htm.getInstances()) + " live objects"); infoLabel.setFont(font); histoStatView.add(infoLabel); if (htm.isOOM()) { infoLabel = new JLabel("<html><b>OutOfMemory found!</b>"); infoLabel.setFont(font); histoStatView.add(infoLabel); } if (htm.isIncomplete()) { infoLabel = new JLabel("<html><b>Class Histogram is incomplete! (broken logfile?)</b>"); infoLabel.setFont(font); histoStatView.add(infoLabel); } JPanel filterPanel = new JPanel(new FlowLayout()); infoLabel = new JLabel("Filter-Expression"); infoLabel.setFont(font); filterPanel.add(infoLabel); filter = new JTextField(30); filter.setFont(font); filter.addCaretListener(new FilterListener(htm)); filterPanel.add(infoLabel); filterPanel.add(filter); checkCase = new JCheckBox(); checkCase.addChangeListener(new CheckCaseListener(htm)); infoLabel = new JLabel("Ignore Case"); infoLabel.setFont(font); filterPanel.add(infoLabel); filterPanel.add(checkCase); histoStatView.add(filterPanel); histogramView.add(histoStatView, BorderLayout.SOUTH); histogramView.add(tableView, BorderLayout.CENTER); histogramView.setPreferredSize(splitPane.getBottomComponent().getSize()); splitPane.setBottomComponent(histogramView); } private class FilterListener implements CaretListener { HistogramTableModel htm; String currentText = ""; FilterListener(HistogramTableModel htm) { this.htm = htm; } public void caretUpdate(CaretEvent event) { if (!filter.getText().equals(currentText)) { htm.setFilter(filter.getText()); histogramTable.revalidate(); } } } private class CheckCaseListener implements ChangeListener { HistogramTableModel htm; CheckCaseListener(HistogramTableModel htm) { this.htm = htm; } public void stateChanged(ChangeEvent e) { htm.setIgnoreCase(checkCase.isSelected()); histogramTable.revalidate(); } } private void addThreadDumps(DefaultMutableTreeNode top, InputStream dumpFileStream) { DumpParser dp = null; try { String fileName = top.getUserObject().toString(); Map<String, Map<String, String>> dumpMap = null; if (runningAsJConsolePlugin || runningAsVisualVMPlugin) { dumpMap = dumpStore.getFromDumpFiles(fileName); } if (dumpMap == null) { dumpMap = new HashMap<>(); dumpStore.addFileToDumpFiles(fileName, dumpMap); } dp = DumpParserFactory.get().getDumpParserForLogfile(dumpFileStream, dumpMap, runningAsJConsolePlugin, dumpCounter); ((Logfile) top.getUserObject()).setUsedParser(dp); while ((dp != null) && dp.hasMoreDumps()) { top.add(dp.parseNext()); if (!isFoundClassHistogram) { isFoundClassHistogram = dp.isFoundClassHistograms(); } } } finally { if (dp != null) { try { dp.close(); } catch (IOException ex) { ex.printStackTrace(); } } } } /** * navigate to the currently selected dump in logfile */ private void navigateToDumpInLogfile() { Object userObject = ((DefaultMutableTreeNode) tree.getSelectionPath().getLastPathComponent()).getUserObject(); if (userObject instanceof ThreadDumpInfo) { ThreadDumpInfo ti = (ThreadDumpInfo) userObject; int lineNumber = ti.getLogLine(); // find log file node. TreePath selPath = tree.getSelectionPath(); while (selPath != null && !checkNameFromNode((DefaultMutableTreeNode) selPath.getLastPathComponent(), File.separator)) { selPath = selPath.getParentPath(); } tree.setSelectionPath(selPath); tree.scrollPathToVisible(selPath); Enumeration childs = ((DefaultMutableTreeNode) selPath.getLastPathComponent()).children(); boolean found = false; DefaultMutableTreeNode logfileContent = null; while (!found && childs.hasMoreElements()) { logfileContent = (DefaultMutableTreeNode) childs.nextElement(); found = logfileContent.getUserObject() instanceof LogFileContent; } if (found) { tree.navigateToPath(logfileContent.getPath()); displayLogFileContent(logfileContent.getUserObject()); jeditPane.setFirstLine(lineNumber - 1); } } } protected MainMenu getMainMenu() { if ((frame != null) && (frame.getJMenuBar() != null)) { return ((MainMenu) frame.getJMenuBar()); } if (pluginMainMenu == null) { pluginMainMenu = new MainMenu(this); } return (pluginMainMenu); } /** * create a instance of this menu for a category */ private PopupListener getCatPopupMenu() { if (catPopupListener == null) { JMenuItem menuItem; // Create the popup menu. JPopupMenu popup = new JPopupMenu(); menuItem = new JMenuItem("Search..."); menuItem.addActionListener(this); popup.add(menuItem); // Add listener to the text area so the popup menu can come up. catPopupListener = new PopupListener(popup); } return (catPopupListener); } private PopupListener monitorsPopupListener = null; /** * create a instance of this menu for a category */ private PopupListener getMonitorsPopupMenu() { if (monitorsPopupListener == null) { JMenuItem menuItem; // Create the popup menu. JPopupMenu popup = new JPopupMenu(); menuItem = new JMenuItem("Search..."); menuItem.addActionListener(this); popup.add(menuItem); popup.addSeparator(); menuItem = new JMenuItem("Expand all nodes"); menuItem.addActionListener(this); popup.add(menuItem); menuItem = new JMenuItem("Collapse all nodes"); menuItem.addActionListener(this); popup.add(menuItem); popup.addSeparator(); menuItem = new JMenuItem("Sort by thread count"); menuItem.addActionListener(this); popup.add(menuItem); // Add listener to the text area so the popup menu can come up. monitorsPopupListener = new PopupListener(popup); } return (monitorsPopupListener); } class PopupListener extends MouseAdapter { JPopupMenu popup; PopupListener(JPopupMenu popupMenu) { popup = popupMenu; } @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } } } /** * check menu and button events. */ public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof JMenuItem) { JMenuItem source = (JMenuItem) (e.getSource()); if (source.getText().substring(1).startsWith(":\\") || source.getText().startsWith("/")) { if (source.getText().endsWith(".tsf")) { try { loadSession(new File(source.getText()), true); } catch (IOException ex) { ex.printStackTrace(); } } else { dumpFile = source.getText(); openFiles(new File[] { new File(dumpFile) }, true); } } else if ("Open...".equals(source.getText())) { chooseFile(); } else if ("Open loggc file...".equals(source.getText())) { openLoggcFile(); } else if ("Save Logfile...".equals(source.getText())) { saveLogFile(); } else if ("Save Session...".equals(source.getText())) { saveSession(); } else if ("Open Session...".equals(source.getText())) { openSession(); } else if ("Preferences".equals(source.getText())) { showPreferencesDialog(); } else if ("Thread Filters".equals(source.getText())) { showFilterDialog(); } else if ("Categories".equals(source.getText())) { showCategoriesDialog(); } else if ("Get Logfile from clipboard".equals(source.getText())) { getLogfileFromClipboard(); } else if ("Exit TDA".equals(source.getText())) { saveState(); frame.dispose(); } else if (ResourceManager.translate("help.contents").equals(source.getText())) { showHelp(); } else if ("Help".equals(source.getText())) { showHelp(); } else if ("Release Notes".equals(source.getText())) { showInfoFile("Release Notes", "doc/README", "Document.gif"); } else if ("License".equals(source.getText())) { showInfoFile("License Information", "doc/COPYING", "Document.gif"); } else if ("About TDA".equals(source.getText())) { showInfo(); } else if ("Search...".equals(source.getText())) { showSearchDialog(); } else if ("Parse loggc-logfile...".equals(source.getText())) { parseLoggcLogfile(); } else if ("Find long running threads...".equals(source.getText())) { findLongRunningThreads(); } else if (("Close logfile...".equals(source.getText())) || ("Close...".equals(source.getText()))) { closeCurrentDump(); } else if ("Close all...".equals(source.getText())) { closeAllDumps(); } else if ("Diff Selection".equals(source.getText())) { TreePath[] paths = tree.getSelectionPaths(); if ((paths != null) && (paths.length < 2)) { JOptionPane.showMessageDialog(this.getRootPane(), "You must select at least two dumps for getting a diff!\n", "Error", JOptionPane.ERROR_MESSAGE); } else { DefaultMutableTreeNode mergeRoot = fetchTop(tree.getSelectionPath()); Map<String, Map<String, String>> dumpMap = dumpStore.getFromDumpFiles(mergeRoot.getUserObject().toString()); ((Logfile) mergeRoot.getUserObject()).getUsedParser().mergeDumps(mergeRoot, dumpMap, paths, paths.length, null); createTree(); this.getRootPane().revalidate(); } } else if ("Show selected Dump in logfile".equals(source.getText())) { navigateToDumpInLogfile(); } else if ("Show Toolbar".equals(source.getText())) { setShowToolbar(((JCheckBoxMenuItem) source).getState()); } else if ("Request Thread Dump...".equals(source.getText())) { addMXBeanDump(); } else if ("Expand all nodes".equals(source.getText())) { tree.expandAllCatNodes(true); } else if ("Collapse all nodes".equals(source.getText())) { tree.expandAllCatNodes(false); } else if ("Sort by thread count".equals(source.getText())) { sortCatByThreads(); } else if ("Expand all Dump nodes".equals(source.getText())) { tree.expandAllDumpNodes(true); } else if ("Collapse all Dump nodes".equals(source.getText())) { tree.expandAllDumpNodes(false); } } else if (e.getSource() instanceof JButton) { JButton source = (JButton) e.getSource(); if ("Open Logfile".equals(source.getToolTipText())) { chooseFile(); } else if ("Close selected Logfile".equals(source.getToolTipText())) { closeCurrentDump(); } else if ("Preferences".equals(source.getToolTipText())) { showPreferencesDialog(); } else if ("Find long running threads".equals(source.getToolTipText())) { findLongRunningThreads(); } else if ("Expand all nodes".equals(source.getToolTipText())) { tree.expandAllDumpNodes(true); } else if ("Collapse all nodes".equals(source.getToolTipText())) { tree.expandAllDumpNodes(false); } else if ("Find long running threads".equals(source.getToolTipText())) { findLongRunningThreads(); } else if ("Thread Filters".equals(source.getToolTipText())) { showFilterDialog(); } else if ("Custom Categories".equals(source.getToolTipText())) { showCategoriesDialog(); } else if ("Request a Thread Dump".equals(source.getToolTipText())) { addMXBeanDump(); } else if ("Help".equals(source.getToolTipText())) { showHelp(); } source.setSelected(false); } } private void showInfo() { InfoDialog infoDialog = new InfoDialog(getFrame()); infoDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); // Display the window. infoDialog.pack(); infoDialog.setLocationRelativeTo(getFrame()); infoDialog.setVisible(true); } /** * set the ui font for all tda stuff (needs to be done for create of objects) * * @param f the font to user */ private void setUIFont(javax.swing.plaf.FontUIResource f) { Enumeration<Object> keys = UIManager.getDefaults().keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); Object value = UIManager.get(key); if (value instanceof javax.swing.plaf.FontUIResource) { UIManager.put(key, f); } } } /** * display the specified file in a info window. * * @param title title of the info window. * @param file the file to display. */ private void showInfoFile(String title, String file, String icon) { HelpOverviewDialog infoDialog = new HelpOverviewDialog(getFrame(), title, file, TDA.createImageIcon(icon).getImage()); infoDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); // Display the window. infoDialog.pack(); infoDialog.setLocationRelativeTo(getFrame()); infoDialog.setVisible(true); } private JFrame getFrame() { Container owner = this.getParent(); while (owner != null && !(owner instanceof JFrame)) { owner = owner.getParent(); } return (owner != null ? (JFrame) owner : null); } private void showPreferencesDialog() { // Create and set up the window. if (prefsDialog == null) { prefsDialog = new PreferencesDialog(getFrame()); prefsDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); } getFrame().setEnabled(false); // Display the window. prefsDialog.reset(); prefsDialog.pack(); prefsDialog.setLocationRelativeTo(getFrame()); prefsDialog.setVisible(true); } public void showFilterDialog() { // Create and set up the window. if (filterDialog == null) { filterDialog = new ThreadFilterDialog(getFrame()); filterDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); } getFrame().setEnabled(false); // Display the window. filterDialog.reset(); filterDialog.pack(); filterDialog.setLocationRelativeTo(getFrame()); filterDialog.setVisible(true); } /** * display categories settings. */ private void showCategoriesDialog() { // Create and set up the window. if (categoriesDialog == null) { categoriesDialog = new CustomCategoriesDialog(getFrame()); categoriesDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); } getFrame().setEnabled(false); // Display the window. categoriesDialog.reset(); categoriesDialog.pack(); categoriesDialog.setLocationRelativeTo(getFrame()); categoriesDialog.setVisible(true); } /** * save the current logfile (only used in plugin mode) */ public void saveLogFile() { if (fc == null) { fc = new JFileChooser(); fc.setMultiSelectionEnabled(true); fc.setCurrentDirectory(PrefManager.get().getSelectedPath()); } if (firstFile && (PrefManager.get().getPreferredSizeFileChooser().height > 0)) { fc.setPreferredSize(PrefManager.get().getPreferredSizeFileChooser()); } int returnVal = fc.showSaveDialog(this.getRootPane()); fc.setPreferredSize(fc.getSize()); PrefManager.get().setPreferredSizeFileChooser(fc.getSize()); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); int selectValue = 0; if (file.exists()) { Object[] options = { "Overwrite", "Cancel" }; selectValue = JOptionPane.showOptionDialog(null, "<html><body>File exists<br><b>" + file + "</b></body></html>", "Confirm overwrite", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); } if (selectValue == 0) { try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(((LogFileContent) logFile.getUserObject()).getContent().getBytes()); fos.flush(); } catch (IOException ex) { ex.printStackTrace(); } } } } /** * choose a log file. * * @param addFile check if a log file should be added or if tree should be cleared. */ private void chooseFile() { if (firstFile && (PrefManager.get().getPreferredSizeFileChooser().height > 0)) { fc.setPreferredSize(PrefManager.get().getPreferredSizeFileChooser()); } int returnVal = fc.showOpenDialog(this.getRootPane()); fc.setPreferredSize(fc.getSize()); PrefManager.get().setPreferredSizeFileChooser(fc.getSize()); if (returnVal == JFileChooser.APPROVE_OPTION) { File[] files = fc.getSelectedFiles(); openFiles(files, false); } } /** * open the provided files. If isRecent is set to true, passed files are not added to the recent file list. * * @param files the files array to open * @param isRecent true, if passed files are from recent file list. */ private void openFiles(File[] files, boolean isRecent) { for (File file : files) { dumpFile = file.getAbsolutePath(); if (dumpFile != null) { if (!firstFile) { // root nodes are moved down. setRootNodeLevel(1); // do direct add without re-init. addDumpFile(); } else { initDumpDisplay(null); if (isFileOpen()) { firstFile = false; } } } if (!isRecent) { PrefManager.get().addToRecentFiles(file.getAbsolutePath()); } } if (isFileOpen()) { this.getRootPane().revalidate(); displayContent(null); } } /** * Returns an ImageIcon, or null if the path was invalid. */ public static ImageIcon createImageIcon(String path) { java.net.URL imgURL = TDA.class.getResource("icons/" + path); if (imgURL != null) { return new ImageIcon(imgURL); } System.err.println("Couldn't find file: " + path); return null; } /** * load a loggc log file based on the current selected thread dump */ private void parseLoggcLogfile() { DefaultMutableTreeNode node = getDumpRootNode((DefaultMutableTreeNode) tree.getLastSelectedPathComponent()); if (node == null) { return; } // get pos of this node in the thread dump hierarchy. int pos = node.getParent().getIndex(node); ((Logfile) ((DefaultMutableTreeNode) node.getParent()).getUserObject()).getUsedParser().setDumpHistogramCounter(pos); openLoggcFile(); } /** * search for dump root node of for given node * * @param node starting to search for * @return root node returns null, if no root was found. */ private DefaultMutableTreeNode getDumpRootNode(DefaultMutableTreeNode node) { // search for starting node DefaultMutableTreeNode rootNode = node; while (rootNode != null && !(rootNode.getUserObject() instanceof ThreadDumpInfo)) { rootNode = (DefaultMutableTreeNode) rootNode.getParent(); } return rootNode; } /** * close the currently selected dump. */ private void closeCurrentDump() { TreePath selPath = tree.getSelectionPath(); while (selPath != null && !(checkNameFromNode((DefaultMutableTreeNode) selPath.getLastPathComponent(), File.separator) || checkNameFromNode( (DefaultMutableTreeNode) selPath.getLastPathComponent(), 2, File.separator))) { selPath = selPath.getParentPath(); } Object[] options = { "Close File", "Cancel close" }; String fileName = ((DefaultMutableTreeNode) selPath.getLastPathComponent()).getUserObject().toString(); fileName = fileName.substring(fileName.indexOf(File.separator)); int selectValue = JOptionPane.showOptionDialog(null, "<html><body>Are you sure, you want to close the currently selected dump file<br><b>" + fileName + "</b></body></html>", "Confirm closing...", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); // if first option "close file" is selected. if (selectValue == 0) { // remove stuff from the top nodes topNodes.remove(selPath.getLastPathComponent()); if (topNodes.size() == 0) { // simply do a reinit, as there isn't anything to display removeAll(); revalidate(); init(runningAsJConsolePlugin, runningAsVisualVMPlugin); getMainMenu().disableMenu(); } else { // rebuild jtree getMainMenu().getCloseMenuItem().setEnabled(false); getMainMenu().getCloseToolBarButton().setEnabled(false); createTree(); } revalidate(); } } /** * close all open dumps */ private void closeAllDumps() { Object[] options = { "Close all", "Cancel close" }; int selectValue = JOptionPane.showOptionDialog(null, "<html><body>Are you sure, you want to close all open dump files", "Confirm closing...", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); // if first option "close file" is selected. if (selectValue == 0) { // remove stuff from the top nodes topNodes = new ArrayList<>(); // simply do a reinit, as there is anything to display resetMainPanel(); } } /** * reset the main panel to start up */ private void resetMainPanel() { removeAll(); revalidate(); init(runningAsJConsolePlugin, runningAsVisualVMPlugin); revalidate(); getMainMenu().disableMenu(); } /** * check if name of node starts with passed string * * @param node the node name to check * @param startsWith the string to compare. * @return true if startsWith and beginning of node name matches. */ private boolean checkNameFromNode(DefaultMutableTreeNode node, String startsWith) { return (checkNameFromNode(node, 0, startsWith)); } /** * check if name of node starts with passed string * * @param node the node name to check * @param startIndex the index to start with comparing, 0 if comparing should happen from the beginning. * @param startsWith the string to compare. * @return true if startsWith and beginning of node name matches. */ private boolean checkNameFromNode(DefaultMutableTreeNode node, int startIndex, String startsWith) { Object info = node.getUserObject(); String result = null; if ((info != null) && (info instanceof AbstractInfo)) { result = ((AbstractInfo) info).getName(); } else if ((info != null) && (info instanceof String)) { result = (String) info; } if (startIndex > 0 && result != null) { result = result.substring(startIndex); } return (result != null && result.startsWith(startsWith)); } /** * open and parse loggc file */ private void openLoggcFile() { int returnVal = fc.showOpenDialog(this.getRootPane()); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); loggcFile = file.getAbsolutePath(); if (loggcFile != null) { try { final InputStream loggcFileStream = new ProgressMonitorInputStream(this, "Parsing " + loggcFile, new FileInputStream(loggcFile)); final SwingWorker worker = new SwingWorker() { @Override public Object construct() { try { DefaultMutableTreeNode top = fetchTop(tree.getSelectionPath()); ((Logfile) top.getUserObject()).getUsedParser().parseLoggcFile(loggcFileStream, top); addThreadDumps(top, loggcFileStream); createTree(); getRootPane().revalidate(); displayContent(null); } finally { if (loggcFileStream != null) { try { loggcFileStream.close(); } catch (IOException ex) { ex.printStackTrace(); } } } return null; } }; worker.start(); } catch (FileNotFoundException ex) { ex.printStackTrace(); } } } } /** * find long running threads either in all parsed thread dumps or in marked thread dump range. */ private void findLongRunningThreads() { TreePath[] paths = tree.getSelectionPaths(); if ((paths == null) || (paths.length < 2)) { JOptionPane.showMessageDialog(this.getRootPane(), "You must select at least two dumps for long thread run detection!\n", "Error", JOptionPane.ERROR_MESSAGE); } else { DefaultMutableTreeNode mergeRoot = fetchTop(tree.getSelectionPath()); Map<String, Map<String, String>> dumpMap = dumpStore.getFromDumpFiles(mergeRoot.getUserObject().toString()); LongThreadDialog longThreadDialog = new LongThreadDialog(this, paths, mergeRoot, dumpMap); if (frame != null) { frame.setEnabled(false); } // Display the window. longThreadDialog.reset(); longThreadDialog.pack(); longThreadDialog.setLocationRelativeTo(frame); longThreadDialog.setVisible(true); } } private int getRootNodeLevel() { return (rootNodeLevel); } private void setRootNodeLevel(int value) { rootNodeLevel = value; } private DefaultMutableTreeNode fetchTop(TreePath pathToRoot) { return ((DefaultMutableTreeNode) pathToRoot.getPathComponent(getRootNodeLevel())); } /** * save the application state to preferences. */ private void saveState() { PrefManager.get().setWindowState(frame.getExtendedState()); PrefManager.get().setSelectedPath(fc.getCurrentDirectory()); PrefManager.get().setPreferredSize(frame.getRootPane().getSize()); PrefManager.get().setWindowPos(frame.getX(), frame.getY()); if (isThreadDisplay()) { PrefManager.get().setTopDividerPos(topSplitPane.getDividerLocation()); PrefManager.get().setDividerPos(splitPane.getDividerLocation()); } PrefManager.get().flush(); } private boolean isFileOpen() { return fileOpen; } private void setFileOpen(boolean value) { fileOpen = value; } /** * Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread. */ private static void createAndShowGUI() { // Create and set up the window. frame = new JFrame("TDA - Thread Dump Analyzer"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Image image = Toolkit.getDefaultToolkit().getImage( "TDA.gif" ); Image image = TDA.createImageIcon("TDA.gif").getImage(); frame.setIconImage(image); frame.getRootPane().setPreferredSize(PrefManager.get().getPreferredSize()); frame.setJMenuBar(new MainMenu(TDA.get(true))); TDA.get(true).init(false, false); // Create and set up the content pane. if (dumpFile != null) { TDA.get(true).initDumpDisplay(null); } TDA.get(true).setOpaque(true); // content panes must be opaque frame.setContentPane(TDA.get(true)); // init filechooser fc = new JFileChooser(); fc.setMultiSelectionEnabled(true); fc.setCurrentDirectory(PrefManager.get().getSelectedPath()); /** * add window listener for persisting state of main frame */ frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { TDA.get(true).saveState(); } @Override public void windowClosed(WindowEvent e) { System.exit(0); } }); frame.setLocation(PrefManager.get().getWindowPos()); // Display the window. frame.pack(); // restore old window settings. frame.setExtendedState(PrefManager.get().getWindowState()); frame.setVisible(true); } /** * display search dialog for current category */ private void showSearchDialog() { // get the currently select category tree DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); JComponent catComp = ((Category) node.getUserObject()).getCatComponent(this); // Create and set up the window. searchDialog = new SearchDialog(getFrame(), catComp); getFrame().setEnabled(false); // Display the window. searchDialog.reset(); searchDialog.pack(); searchDialog.setLocationRelativeTo(getFrame()); searchDialog.setVisible(true); searchDialog.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { getFrame().setEnabled(true); } }); } /** * main startup method for TDA */ public static void main(String[] args) { if (args.length > 0) { dumpFile = args[0]; } // Schedule a job for the event-dispatching thread: // creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } /** * check file menu */ public void menuSelected(MenuEvent e) { JMenu source = (JMenu) e.getSource(); if ((source != null) && "File".equals(source.getText())) { // close menu item only active, if something is selected. getMainMenu().getCloseMenuItem().setEnabled(tree.getSelectionPath() != null); getMainMenu().getCloseToolBarButton().setEnabled(tree.getSelectionPath() != null); } } public void menuDeselected(MenuEvent e) { // nothing to do } public void menuCanceled(MenuEvent e) { // nothing to do } public static String getFontSizeModifier(int add) { String result = String.valueOf(fontSizeModifier + add); if ((fontSizeModifier + add) > 0) { result = "+" + (fontSizeModifier + add); } return (result); } public static void setFontSizeModifier(int value) { fontSizeModifier = value; } /** * handles dragging events for new files to open. */ private class FileDropTargetListener extends DropTargetAdapter { public void drop(DropTargetDropEvent dtde) { try { DataFlavor[] df = dtde.getTransferable().getTransferDataFlavors(); for (DataFlavor element : df) { if (element.isMimeTypeEqual("application/x-java-serialized-object")) { dtde.acceptDrop(dtde.getDropAction()); String[] fileStrings = ((String) dtde.getTransferable().getTransferData(element)).split("\n"); File[] files = new File[fileStrings.length]; for (int j = 0; j < fileStrings.length; j++) { files[j] = new File(fileStrings[j].substring(7)); } openFiles(files, false); dtde.dropComplete(true); } } } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); dtde.rejectDrop(); } catch (IOException ex) { ex.printStackTrace(); dtde.rejectDrop(); } } } }