001: /*
002: * Copyright 2004-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jconsole;
027:
028: import java.awt.*;
029: import java.awt.event.*;
030: import java.io.*;
031: import java.lang.management.*;
032: import java.lang.reflect.*;
033:
034: import javax.swing.*;
035: import javax.swing.border.*;
036: import javax.swing.event.*;
037:
038: import java.util.*;
039: import java.util.concurrent.*;
040: import java.util.List;
041:
042: import sun.awt.*;
043:
044: import static sun.tools.jconsole.OverviewPanel.*;
045: import static sun.tools.jconsole.Resources.*;
046: import static sun.tools.jconsole.Utilities.*;
047:
048: @SuppressWarnings("serial")
049: class ThreadTab extends Tab implements ActionListener,
050: DocumentListener, ListSelectionListener {
051: PlotterPanel threadMeter;
052: TimeComboBox timeComboBox;
053: JTabbedPane threadListTabbedPane;
054: DefaultListModel listModel;
055: JTextField filterTF;
056: JLabel messageLabel;
057: JSplitPane threadsSplitPane;
058: HashMap<Long, String> nameCache = new HashMap<Long, String>();
059:
060: private ThreadOverviewPanel overviewPanel;
061: private boolean plotterListening = false;
062:
063: private static final String threadCountKey = "threadCount";
064: private static final String peakKey = "peak";
065:
066: private static final String threadCountName = Resources
067: .getText("Live Threads");
068: private static final String peakName = Resources.getText("Peak");
069:
070: private static final Color threadCountColor = Plotter.defaultColor;
071: private static final Color peakColor = Color.red;
072:
073: private static final Border thinEmptyBorder = new EmptyBorder(2, 2,
074: 2, 2);
075:
076: private static final String infoLabelFormat = "ThreadTab.infoLabelFormat";
077:
078: /*
079: Hierarchy of panels and layouts for this tab:
080:
081: ThreadTab (BorderLayout)
082:
083: North: topPanel (BorderLayout)
084:
085: Center: controlPanel (FlowLayout)
086: timeComboBox
087:
088: Center: plotterPanel (BorderLayout)
089:
090: Center: plotter
091:
092: */
093:
094: public static String getTabName() {
095: return Resources.getText("Threads");
096: }
097:
098: public ThreadTab(VMPanel vmPanel) {
099: super (vmPanel, getTabName());
100:
101: setLayout(new BorderLayout(0, 0));
102: setBorder(new EmptyBorder(4, 4, 3, 4));
103:
104: JPanel topPanel = new JPanel(new BorderLayout());
105: JPanel plotterPanel = new JPanel(new VariableGridLayout(0, 1,
106: 4, 4, true, true));
107:
108: add(topPanel, BorderLayout.NORTH);
109: add(plotterPanel, BorderLayout.CENTER);
110:
111: JPanel controlPanel = new JPanel(new FlowLayout(
112: FlowLayout.CENTER, 20, 5));
113: topPanel.add(controlPanel, BorderLayout.CENTER);
114:
115: threadMeter = new PlotterPanel(Resources
116: .getText("Number of Threads"), Plotter.Unit.NONE, true);
117: threadMeter.plotter.createSequence(threadCountKey,
118: threadCountName, threadCountColor, true);
119: threadMeter.plotter.createSequence(peakKey, peakName,
120: peakColor, true);
121: setAccessibleName(threadMeter.plotter,
122: getText("ThreadTab.threadPlotter.accessibleName"));
123:
124: plotterPanel.add(threadMeter);
125:
126: timeComboBox = new TimeComboBox(threadMeter.plotter);
127: controlPanel.add(new LabeledComponent(Resources
128: .getText("Time Range:"), getMnemonicInt("Time Range:"),
129: timeComboBox));
130:
131: listModel = new DefaultListModel();
132:
133: JTextArea textArea = new JTextArea();
134: textArea.setBorder(thinEmptyBorder);
135: textArea.setEditable(false);
136: setAccessibleName(textArea,
137: getText("ThreadTab.threadInfo.accessibleName"));
138: JList list = new ThreadJList(listModel, textArea);
139:
140: Dimension di = new Dimension(super .getPreferredSize());
141: di.width = Math.min(di.width, 200);
142:
143: JScrollPane threadlistSP = new JScrollPane(list);
144: threadlistSP.setPreferredSize(di);
145: threadlistSP.setBorder(null);
146:
147: JScrollPane textAreaSP = new JScrollPane(textArea);
148: textAreaSP.setBorder(null);
149:
150: threadListTabbedPane = new JTabbedPane(JTabbedPane.TOP);
151: threadsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
152: threadlistSP, textAreaSP);
153: threadsSplitPane.setOneTouchExpandable(true);
154: threadsSplitPane.setBorder(null);
155:
156: JPanel firstTabPanel = new JPanel(new BorderLayout());
157: firstTabPanel.setOpaque(false);
158:
159: JPanel firstTabToolPanel = new JPanel(new FlowLayout(
160: FlowLayout.LEFT, 5, 2));
161: firstTabToolPanel.setOpaque(false);
162:
163: filterTF = new PromptingTextField("Filter", 20);
164: filterTF.getDocument().addDocumentListener(this );
165: firstTabToolPanel.add(filterTF);
166:
167: JSeparator separator = new JSeparator(JSeparator.VERTICAL);
168: separator.setPreferredSize(new Dimension(separator
169: .getPreferredSize().width,
170: filterTF.getPreferredSize().height));
171: firstTabToolPanel.add(separator);
172:
173: JButton detectDeadlockButton = new JButton(Resources
174: .getText("Detect Deadlock"));
175: detectDeadlockButton
176: .setMnemonic(getMnemonicInt("Detect Deadlock"));
177: detectDeadlockButton.setActionCommand("detectDeadlock");
178: detectDeadlockButton.addActionListener(this );
179: detectDeadlockButton
180: .setToolTipText(getText("Detect Deadlock.toolTip"));
181: firstTabToolPanel.add(detectDeadlockButton);
182:
183: messageLabel = new JLabel();
184: firstTabToolPanel.add(messageLabel);
185:
186: firstTabPanel.add(threadsSplitPane, BorderLayout.CENTER);
187: firstTabPanel.add(firstTabToolPanel, BorderLayout.SOUTH);
188: threadListTabbedPane.addTab(Resources.getText("Threads"),
189: firstTabPanel);
190:
191: plotterPanel.add(threadListTabbedPane);
192: }
193:
194: private long oldThreads[] = new long[0];
195:
196: public SwingWorker<?, ?> newSwingWorker() {
197: final ProxyClient proxyClient = vmPanel.getProxyClient();
198:
199: if (!plotterListening) {
200: proxyClient
201: .addWeakPropertyChangeListener(threadMeter.plotter);
202: plotterListening = true;
203: }
204:
205: return new SwingWorker<Boolean, Object>() {
206: private int tlCount;
207: private int tpCount;
208: private long ttCount;
209: private long[] threads;
210: private long timeStamp;
211:
212: public Boolean doInBackground() {
213: try {
214: ThreadMXBean threadMBean = proxyClient
215: .getThreadMXBean();
216:
217: tlCount = threadMBean.getThreadCount();
218: tpCount = threadMBean.getPeakThreadCount();
219: if (overviewPanel != null) {
220: ttCount = threadMBean
221: .getTotalStartedThreadCount();
222: } else {
223: ttCount = 0L;
224: }
225:
226: threads = threadMBean.getAllThreadIds();
227: for (long newThread : threads) {
228: if (nameCache.get(newThread) == null) {
229: ThreadInfo ti = threadMBean
230: .getThreadInfo(newThread);
231: if (ti != null) {
232: String name = ti.getThreadName();
233: if (name != null) {
234: nameCache.put(newThread, name);
235: }
236: }
237: }
238: }
239: timeStamp = System.currentTimeMillis();
240: return true;
241: } catch (IOException e) {
242: return false;
243: } catch (UndeclaredThrowableException e) {
244: return false;
245: }
246: }
247:
248: protected void done() {
249: try {
250: if (!get()) {
251: return;
252: }
253: } catch (InterruptedException ex) {
254: return;
255: } catch (ExecutionException ex) {
256: if (JConsole.isDebug()) {
257: ex.printStackTrace();
258: }
259: return;
260: }
261:
262: threadMeter.plotter.addValues(timeStamp, tlCount,
263: tpCount);
264: threadMeter.setValueLabel(tlCount + "");
265:
266: if (overviewPanel != null) {
267: overviewPanel.updateThreadsInfo(tlCount, tpCount,
268: ttCount, timeStamp);
269: }
270:
271: String filter = filterTF.getText().toLowerCase(
272: Locale.ENGLISH);
273: boolean doFilter = (filter.length() > 0);
274:
275: ArrayList<Long> l = new ArrayList<Long>();
276: for (long t : threads) {
277: l.add(t);
278: }
279: Iterator<Long> iterator = l.iterator();
280: while (iterator.hasNext()) {
281: long newThread = iterator.next();
282: String name = nameCache.get(newThread);
283: if (doFilter
284: && name != null
285: && name.toLowerCase(Locale.ENGLISH)
286: .indexOf(filter) < 0) {
287:
288: iterator.remove();
289: }
290: }
291: long[] newThreads = threads;
292: if (l.size() < threads.length) {
293: newThreads = new long[l.size()];
294: for (int i = 0; i < newThreads.length; i++) {
295: newThreads[i] = l.get(i);
296: }
297: }
298:
299: for (long oldThread : oldThreads) {
300: boolean found = false;
301: for (long newThread : newThreads) {
302: if (newThread == oldThread) {
303: found = true;
304: break;
305: }
306: }
307: if (!found) {
308: listModel.removeElement(oldThread);
309: if (!doFilter) {
310: nameCache.remove(oldThread);
311: }
312: }
313: }
314:
315: // Threads are in reverse chronological order
316: for (int i = newThreads.length - 1; i >= 0; i--) {
317: long newThread = newThreads[i];
318: boolean found = false;
319: for (long oldThread : oldThreads) {
320: if (newThread == oldThread) {
321: found = true;
322: break;
323: }
324: }
325: if (!found) {
326: listModel.addElement(newThread);
327: }
328: }
329: oldThreads = newThreads;
330: }
331: };
332: }
333:
334: long lastSelected = -1;
335:
336: public void valueChanged(ListSelectionEvent ev) {
337: ThreadJList list = (ThreadJList) ev.getSource();
338: final JTextArea textArea = list.textArea;
339:
340: Long selected = (Long) list.getSelectedValue();
341: if (selected == null) {
342: if (lastSelected != -1) {
343: selected = lastSelected;
344: }
345: } else {
346: lastSelected = selected;
347: }
348: textArea.setText("");
349: if (selected != null) {
350: final long threadID = selected;
351: workerAdd(new Runnable() {
352: public void run() {
353: ProxyClient proxyClient = vmPanel.getProxyClient();
354: StringBuilder sb = new StringBuilder();
355: try {
356: ThreadMXBean threadMBean = proxyClient
357: .getThreadMXBean();
358: ThreadInfo ti = null;
359: MonitorInfo[] monitors = null;
360: if (proxyClient.isLockUsageSupported()
361: && threadMBean
362: .isObjectMonitorUsageSupported()) {
363: // VMs that support the monitor usage monitoring
364: ThreadInfo[] infos = threadMBean
365: .dumpAllThreads(true, false);
366: for (ThreadInfo info : infos) {
367: if (info.getThreadId() == threadID) {
368: ti = info;
369: monitors = info.getLockedMonitors();
370: break;
371: }
372: }
373: } else {
374: // VM doesn't support monitor usage monitoring
375: ti = threadMBean.getThreadInfo(threadID,
376: Integer.MAX_VALUE);
377: }
378: if (ti != null) {
379: if (ti.getLockName() == null) {
380: sb.append(Resources.getText(
381: "Name State", ti
382: .getThreadName(), ti
383: .getThreadState()
384: .toString()));
385: } else if (ti.getLockOwnerName() == null) {
386: sb.append(Resources.getText(
387: "Name State LockName", ti
388: .getThreadName(), ti
389: .getThreadState()
390: .toString(), ti
391: .getLockName()));
392: } else {
393: sb
394: .append(Resources
395: .getText(
396: "Name State LockName LockOwner",
397: ti
398: .getThreadName(),
399: ti
400: .getThreadState()
401: .toString(),
402: ti
403: .getLockName(),
404: ti
405: .getLockOwnerName()));
406: }
407: sb.append(Resources.getText(
408: "BlockedCount WaitedCount", ti
409: .getBlockedCount(), ti
410: .getWaitedCount()));
411: sb.append(Resources.getText("Stack trace"));
412: int index = 0;
413: for (StackTraceElement e : ti
414: .getStackTrace()) {
415: sb.append(e.toString() + "\n");
416: if (monitors != null) {
417: for (MonitorInfo mi : monitors) {
418: if (mi.getLockedStackDepth() == index) {
419: sb
420: .append(Resources
421: .getText(
422: "Monitor locked",
423: mi
424: .toString()));
425: }
426: }
427: }
428: index++;
429: }
430: }
431: } catch (IOException ex) {
432: // Ignore
433: } catch (UndeclaredThrowableException e) {
434: proxyClient.markAsDead();
435: }
436: final String text = sb.toString();
437: SwingUtilities.invokeLater(new Runnable() {
438: public void run() {
439: textArea.setText(text);
440: textArea.setCaretPosition(0);
441: }
442: });
443: }
444: });
445: }
446: }
447:
448: private void doUpdate() {
449: workerAdd(new Runnable() {
450: public void run() {
451: update();
452: }
453: });
454: }
455:
456: private void detectDeadlock() {
457: workerAdd(new Runnable() {
458: public void run() {
459: try {
460: final Long[][] deadlockedThreads = getDeadlockedThreadIds();
461:
462: if (deadlockedThreads == null
463: || deadlockedThreads.length == 0) {
464: // Display message for 30 seconds. Do it on a separate thread so
465: // the sleep won't hold up the worker queue.
466: // This will be replaced later by separate statusbar logic.
467: new Thread() {
468: public void run() {
469: try {
470: SwingUtilities
471: .invokeAndWait(new Runnable() {
472: public void run() {
473: String msg = Resources
474: .getText("No deadlock detected");
475: messageLabel
476: .setText(msg);
477: threadListTabbedPane
478: .revalidate();
479: }
480: });
481: sleep(30 * 1000);
482: } catch (InterruptedException ex) {
483: // Ignore
484: } catch (InvocationTargetException ex) {
485: // Ignore
486: }
487: SwingUtilities
488: .invokeLater(new Runnable() {
489: public void run() {
490: messageLabel
491: .setText("");
492: }
493: });
494: }
495: }.start();
496: return;
497: }
498:
499: SwingUtilities.invokeLater(new Runnable() {
500: public void run() {
501: // Remove old deadlock tabs
502: while (threadListTabbedPane.getTabCount() > 1) {
503: threadListTabbedPane.removeTabAt(1);
504: }
505:
506: if (deadlockedThreads != null) {
507: for (int i = 0; i < deadlockedThreads.length; i++) {
508: DefaultListModel listModel = new DefaultListModel();
509: JTextArea textArea = new JTextArea();
510: textArea.setBorder(thinEmptyBorder);
511: textArea.setEditable(false);
512: setAccessibleName(
513: textArea,
514: getText("ThreadTab.threadInfo.accessibleName"));
515: JList list = new ThreadJList(
516: listModel, textArea);
517: JScrollPane threadlistSP = new JScrollPane(
518: list);
519: JScrollPane textAreaSP = new JScrollPane(
520: textArea);
521: threadlistSP.setBorder(null);
522: textAreaSP.setBorder(null);
523: JSplitPane splitPane = new JSplitPane(
524: JSplitPane.HORIZONTAL_SPLIT,
525: threadlistSP, textAreaSP);
526: splitPane
527: .setOneTouchExpandable(true);
528: splitPane.setBorder(null);
529: splitPane
530: .setDividerLocation(threadsSplitPane
531: .getDividerLocation());
532: String tabName;
533: if (deadlockedThreads.length > 1) {
534: tabName = Resources.getText(
535: "deadlockTabN", i + 1);
536: } else {
537: tabName = Resources
538: .getText("deadlockTab");
539: }
540: threadListTabbedPane.addTab(
541: tabName, splitPane);
542:
543: for (long t : deadlockedThreads[i]) {
544: listModel.addElement(t);
545: }
546: }
547: threadListTabbedPane
548: .setSelectedIndex(1);
549: }
550: }
551: });
552: } catch (IOException e) {
553: // Ignore
554: } catch (UndeclaredThrowableException e) {
555: vmPanel.getProxyClient().markAsDead();
556: }
557: }
558: });
559: }
560:
561: // Return deadlocked threads or null
562: public Long[][] getDeadlockedThreadIds() throws IOException {
563: ProxyClient proxyClient = vmPanel.getProxyClient();
564: ThreadMXBean threadMBean = proxyClient.getThreadMXBean();
565:
566: long[] ids = proxyClient.findDeadlockedThreads();
567: if (ids == null) {
568: return null;
569: }
570: ThreadInfo[] infos = threadMBean.getThreadInfo(ids,
571: Integer.MAX_VALUE);
572:
573: List<Long[]> dcycles = new ArrayList<Long[]>();
574: List<Long> cycle = new ArrayList<Long>();
575:
576: // keep track of which thread is visited
577: // one thread can only be in one cycle
578: boolean[] visited = new boolean[ids.length];
579:
580: int deadlockedThread = -1; // Index into arrays
581: while (true) {
582: if (deadlockedThread < 0) {
583: if (cycle.size() > 0) {
584: // a cycle found
585: dcycles.add(cycle.toArray(new Long[0]));
586: cycle = new ArrayList<Long>();
587: }
588: // start a new cycle from a non-visited thread
589: for (int j = 0; j < ids.length; j++) {
590: if (!visited[j]) {
591: deadlockedThread = j;
592: visited[j] = true;
593: break;
594: }
595: }
596: if (deadlockedThread < 0) {
597: // done
598: break;
599: }
600: }
601:
602: cycle.add(ids[deadlockedThread]);
603: long nextThreadId = infos[deadlockedThread]
604: .getLockOwnerId();
605: for (int j = 0; j < ids.length; j++) {
606: ThreadInfo ti = infos[j];
607: if (ti.getThreadId() == nextThreadId) {
608: if (visited[j]) {
609: deadlockedThread = -1;
610: } else {
611: deadlockedThread = j;
612: visited[j] = true;
613: }
614: break;
615: }
616: }
617: }
618: return dcycles.toArray(new Long[0][0]);
619: }
620:
621: // ActionListener interface
622: public void actionPerformed(ActionEvent evt) {
623: String cmd = ((AbstractButton) evt.getSource())
624: .getActionCommand();
625:
626: if (cmd == "detectDeadlock") {
627: messageLabel.setText("");
628: detectDeadlock();
629: }
630: }
631:
632: // DocumentListener interface
633:
634: public void insertUpdate(DocumentEvent e) {
635: doUpdate();
636: }
637:
638: public void removeUpdate(DocumentEvent e) {
639: doUpdate();
640: }
641:
642: public void changedUpdate(DocumentEvent e) {
643: doUpdate();
644: }
645:
646: private class ThreadJList extends JList {
647: private JTextArea textArea;
648:
649: ThreadJList(DefaultListModel listModel, JTextArea textArea) {
650: super (listModel);
651:
652: this .textArea = textArea;
653:
654: setBorder(thinEmptyBorder);
655:
656: addListSelectionListener(ThreadTab.this );
657: setCellRenderer(new DefaultListCellRenderer() {
658: public Component getListCellRendererComponent(
659: JList list, Object value, int index,
660: boolean isSelected, boolean cellHasFocus) {
661: super .getListCellRendererComponent(list, value,
662: index, isSelected, cellHasFocus);
663:
664: if (value != null) {
665: String name = nameCache.get(value);
666: if (name == null) {
667: name = value.toString();
668: }
669: setText(name);
670: }
671: return this ;
672: }
673: });
674: }
675:
676: public Dimension getPreferredSize() {
677: Dimension d = super .getPreferredSize();
678: d.width = Math.max(d.width, 100);
679: return d;
680: }
681: }
682:
683: private class PromptingTextField extends JTextField implements
684: FocusListener {
685: private String prompt;
686: boolean promptRemoved = false;
687: Color fg;
688:
689: public PromptingTextField(String prompt, int columns) {
690: super (prompt, columns);
691:
692: this .prompt = prompt;
693: updateForeground();
694: addFocusListener(this );
695: setAccessibleName(this , prompt);
696: }
697:
698: @Override
699: public void revalidate() {
700: super .revalidate();
701: updateForeground();
702: }
703:
704: private void updateForeground() {
705: this .fg = UIManager.getColor("TextField.foreground");
706: if (promptRemoved) {
707: setForeground(fg);
708: } else {
709: setForeground(Color.gray);
710: }
711: }
712:
713: public String getText() {
714: if (!promptRemoved) {
715: return "";
716: } else {
717: return super .getText();
718: }
719: }
720:
721: public void focusGained(FocusEvent e) {
722: if (!promptRemoved) {
723: setText("");
724: setForeground(fg);
725: promptRemoved = true;
726: }
727: }
728:
729: public void focusLost(FocusEvent e) {
730: if (promptRemoved && getText().equals("")) {
731: setText(prompt);
732: setForeground(Color.gray);
733: promptRemoved = false;
734: }
735: }
736:
737: }
738:
739: OverviewPanel[] getOverviewPanels() {
740: if (overviewPanel == null) {
741: overviewPanel = new ThreadOverviewPanel();
742: }
743: return new OverviewPanel[] { overviewPanel };
744: }
745:
746: private static class ThreadOverviewPanel extends OverviewPanel {
747: ThreadOverviewPanel() {
748: super (getText("Threads"), threadCountKey, threadCountName,
749: null);
750: }
751:
752: private void updateThreadsInfo(long tlCount, long tpCount,
753: long ttCount, long timeStamp) {
754: getPlotter().addValues(timeStamp, tlCount);
755: getInfoLabel()
756: .setText(
757: getText(infoLabelFormat, tlCount, tpCount,
758: ttCount));
759: }
760: }
761: }
|