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