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