1 /* 2 * Copyright (c) 2000, 2004, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 package sun.jvm.hotspot.ui; 26 27 import java.awt.BorderLayout; 28 import java.awt.Dimension; 29 30 import java.awt.event.*; 31 32 import java.io.*; 33 import java.util.*; 34 35 import javax.swing.*; 36 import javax.swing.event.ListSelectionEvent; 37 import javax.swing.event.ListSelectionListener; 38 import javax.swing.table.*; 39 40 import sun.jvm.hotspot.debugger.*; 41 import sun.jvm.hotspot.runtime.*; 42 import sun.jvm.hotspot.types.*; 43 44 import sun.jvm.hotspot.ui.action.*; 45 46 import com.sun.java.swing.ui.*; 47 import com.sun.java.swing.action.*; 48 49 /** 50 * This panel contains a JTable which displays the list of Java 51 * threads as their native thread identifiers combined with their 52 * Java names. It allows selection and examination of any of the 53 * threads. 54 */ 55 public class JavaThreadsPanel extends SAPanel implements ActionListener { 56 private JavaThreadsTableModel dataModel; 57 private StatusBar statusBar; 58 private JTable threadTable; 59 private java.util.List<CachedThread> cachedThreads = new ArrayList(); 60 private static AddressField crashThread; 61 62 63 static { 64 VM.registerVMInitializedObserver( 65 (o, a) -> initialize(VM.getVM().getTypeDataBase())); 66 } 67 68 private static void initialize(TypeDataBase db) { 69 crashThread = db.lookupType("VMError").getAddressField("_thread"); 70 } 71 72 /** Constructor assumes the threads panel is created while the VM is 73 suspended. Subsequent resume and suspend operations of the VM 74 will cause the threads panel to clear and fill itself back in, 75 respectively. */ 76 public JavaThreadsPanel() { 77 VM.getVM().registerVMResumedObserver(new Observer() { 78 public void update(Observable o, Object data) { 79 decache(); 80 } 81 }); 82 83 VM.getVM().registerVMSuspendedObserver(new Observer() { 84 public void update(Observable o, Object data) { 85 cache(); 86 } 87 }); 88 89 cache(); 90 91 setLayout(new BorderLayout()); 92 93 dataModel = new JavaThreadsTableModel(cachedThreads); 94 statusBar = new StatusBar(); 95 96 threadTable = new JTable(dataModel, new JavaThreadsColumnModel()); 97 threadTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 98 threadTable.addMouseListener(new MouseAdapter() { 99 public void mouseClicked(MouseEvent evt) { 100 if (evt.getClickCount() == 2) { 101 // double clicking will display the oop inspector. 102 fireShowThreadOopInspector(); 103 } 104 } 105 }); 106 107 add(new JavaThreadsToolBar(statusBar), BorderLayout.NORTH); 108 add(new ThreadPanel(threadTable), BorderLayout.CENTER); 109 add(statusBar, BorderLayout.SOUTH); 110 111 registerActions(); 112 } 113 114 /** 115 * A splitpane panel which contains the thread table and the Thread Info. 116 * the thread info is toggleable 117 */ 118 private class ThreadPanel extends JPanel { 119 120 private JSplitPane splitPane; 121 private JTable threadTable; 122 private ThreadInfoPanel threadInfo; 123 private int dividerSize; 124 private int dividerLocation = -1; 125 private boolean actionsEnabled = false; 126 127 public ThreadPanel(JTable table) { 128 setLayout(new BorderLayout()); 129 this.threadInfo = new ThreadInfoPanel(); 130 this.threadTable = table; 131 132 splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 133 splitPane.setOneTouchExpandable(true); 134 splitPane.setTopComponent(new JScrollPane(table)); 135 136 // Set the size of the divider to 0 but save it so it can be restored 137 dividerSize = splitPane.getDividerSize(); 138 splitPane.setDividerSize(0); 139 140 add(splitPane, BorderLayout.CENTER); 141 142 // Register an ItemListener on the LogViewerAction which toggles 143 // the apearance of the ThreadInfoPanel 144 ActionManager manager = HSDBActionManager.getInstance(); 145 StateChangeAction action = manager.getStateChangeAction(ThreadInfoAction.VALUE_COMMAND); 146 if (action != null) { 147 action.setItemListener(new ItemListener() { 148 public void itemStateChanged(ItemEvent evt) { 149 if (evt.getStateChange() == ItemEvent.SELECTED) { 150 showOutputPane(); 151 } else { 152 hideOutputPane(); 153 } 154 } 155 }); 156 } 157 158 // A listener is added to listen to changes in row selection 159 // and changes the contents of the ThreadInfoPanel. 160 ListSelectionModel selModel = table.getSelectionModel(); 161 selModel.addListSelectionListener(new ListSelectionListener() { 162 public void valueChanged(ListSelectionEvent evt) { 163 if (evt.getValueIsAdjusting() == false) { 164 setActionsEnabled(true); 165 if (isInfoVisible()) { 166 showCurrentThreadInfo(); 167 } 168 } 169 } 170 }); 171 } 172 173 /** 174 * Returns a flag to indicate if the thread info is visible 175 */ 176 private boolean isInfoVisible() { 177 return (splitPane.getBottomComponent() != null); 178 } 179 180 private void showOutputPane() { 181 if (splitPane.getBottomComponent() == null) { 182 splitPane.setBottomComponent(threadInfo); 183 184 if (dividerLocation == -1) { 185 // Calculate the divider location from the pref size. 186 Dimension pSize = this.getSize(); 187 dividerLocation = pSize.height / 2; 188 } 189 190 splitPane.setDividerSize(dividerSize); 191 splitPane.setDividerLocation(dividerLocation); 192 showCurrentThreadInfo(); 193 } 194 } 195 196 private void hideOutputPane() { 197 dividerLocation = splitPane.getDividerLocation(); 198 splitPane.remove(threadInfo); 199 splitPane.setDividerSize(0); 200 } 201 202 private void showCurrentThreadInfo() { 203 int row = threadTable.getSelectedRow(); 204 if (row >= 0) { 205 threadInfo.setJavaThread(dataModel.getJavaThread(row)); 206 } 207 } 208 209 private void setActionsEnabled(boolean enabled) { 210 if (actionsEnabled != enabled) { 211 ActionManager manager = ActionManager.getInstance(); 212 manager.setActionEnabled(InspectAction.VALUE_COMMAND, enabled); 213 manager.setActionEnabled(MemoryAction.VALUE_COMMAND, enabled); 214 manager.setActionEnabled(JavaStackTraceAction.VALUE_COMMAND, enabled); 215 actionsEnabled = enabled; 216 } 217 } 218 219 } // end ThreadPanel 220 221 private class JavaThreadsToolBar extends CommonToolBar { 222 public JavaThreadsToolBar(StatusBar status) { 223 super(HSDBActionManager.getInstance(), status); 224 } 225 226 protected void addComponents() { 227 addButton(manager.getAction(InspectAction.VALUE_COMMAND)); 228 addButton(manager.getAction(MemoryAction.VALUE_COMMAND)); 229 addButton(manager.getAction(JavaStackTraceAction.VALUE_COMMAND)); 230 231 addToggleButton(manager.getStateChangeAction(ThreadInfoAction.VALUE_COMMAND)); 232 addButton(manager.getAction(FindCrashesAction.VALUE_COMMAND)); 233 } 234 } 235 236 private class JavaThreadsColumnModel extends DefaultTableColumnModel { 237 private String[] columnNames = { "OS Thread ID", "Java Thread Name" }; 238 239 public JavaThreadsColumnModel() { 240 // Should actually get the line metrics for 241 int PREF_WIDTH = 80; 242 int MAX_WIDTH = 100; 243 int HUGE_WIDTH = 140; 244 245 TableColumn column; 246 247 // Thread ID 248 column = new TableColumn(0, MAX_WIDTH); 249 column.setHeaderValue(columnNames[0]); 250 column.setMaxWidth(MAX_WIDTH); 251 column.setResizable(false); 252 addColumn(column); 253 254 // Thread name 255 column = new TableColumn(1, HUGE_WIDTH); 256 column.setHeaderValue(columnNames[1]); 257 column.setResizable(false); 258 addColumn(column); 259 } 260 } // end class JavaThreadsColumnModel 261 262 /** 263 * Encapsulates the set of threads in a table model 264 */ 265 private class JavaThreadsTableModel extends AbstractTableModel { 266 private String[] columnNames = { "OS Thread ID", "Java Thread Name" }; 267 268 private java.util.List elements; 269 270 public JavaThreadsTableModel(java.util.List threads) { 271 this.elements = threads; 272 } 273 274 public int getColumnCount() { 275 return columnNames.length; 276 } 277 278 public int getRowCount() { 279 return elements.size(); 280 } 281 282 public String getColumnName(int col) { 283 return columnNames[col]; 284 } 285 286 public Object getValueAt(int row, int col) { 287 CachedThread thread = getRow(row); 288 switch (col) { 289 case 0: 290 return thread.getThreadID(); 291 case 1: 292 return thread.getThreadName(); 293 default: 294 throw new RuntimeException("Index (" + col + ", " + row + ") out of bounds"); 295 } 296 } 297 298 /** 299 * Returns the selected Java Thread indexed by the row or null. 300 */ 301 public JavaThread getJavaThread(int index) { 302 return getRow(index).getThread(); 303 } 304 305 private CachedThread getRow(int row) { 306 return (CachedThread)elements.get(row); 307 } 308 309 private String threadIDAt(int index) { 310 return ((CachedThread) cachedThreads.get(index)).getThreadID(); 311 } 312 313 private String threadNameAt(int index) { 314 try { 315 return ((CachedThread) cachedThreads.get(index)).getThreadName(); 316 } catch (AddressException e) { 317 return "<Error: AddressException>"; 318 } catch (NullPointerException e) { 319 return "<Error: NullPointerException>"; 320 } 321 } 322 } // end class JavaThreadsTableModel 323 324 public void actionPerformed(ActionEvent evt) { 325 String command = evt.getActionCommand(); 326 327 if (command.equals(InspectAction.VALUE_COMMAND)) { 328 fireShowThreadOopInspector(); 329 } else if (command.equals(MemoryAction.VALUE_COMMAND)) { 330 fireShowThreadStackMemory(); 331 } else if (command.equals(ThreadInfoAction.VALUE_COMMAND)) { 332 fireShowThreadInfo(); 333 } else if (command.equals(FindCrashesAction.VALUE_COMMAND)) { 334 if (fireShowThreadCrashes()) { 335 statusBar.setMessage("Some thread crashes were encountered"); 336 } else { 337 statusBar.setMessage("No thread crashes encountered"); 338 } 339 } else if (command.equals(JavaStackTraceAction.VALUE_COMMAND)) { 340 fireShowJavaStackTrace(); 341 } 342 } 343 344 // Cached data for a thread 345 private class CachedThread { 346 private JavaThread thread; 347 private String threadID; 348 private String threadName; 349 private boolean computed; 350 351 public CachedThread(JavaThread thread) { 352 this.thread = thread; 353 } 354 355 public JavaThread getThread() { 356 return thread; 357 } 358 359 public String getThreadID() { 360 if (!computed) { 361 compute(); 362 } 363 364 return threadID; 365 } 366 367 public String getThreadName() { 368 if (!computed) { 369 compute(); 370 } 371 372 return threadName; 373 } 374 375 private void compute() { 376 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 377 thread.printThreadIDOn(new PrintStream(bos)); 378 threadID = bos.toString(); 379 threadName = thread.getThreadName(); 380 381 computed = true; 382 } 383 } 384 385 //-------------------------------------------------------------------------------- 386 // Internals only below this point 387 // 388 389 protected void registerActions() { 390 registerAction(InspectAction.VALUE_COMMAND); 391 registerAction(MemoryAction.VALUE_COMMAND); 392 registerAction(FindCrashesAction.VALUE_COMMAND); 393 registerAction(JavaStackTraceAction.VALUE_COMMAND); 394 395 // disable Inspector, Memory and Java Stack trace action until a thread is selected 396 ActionManager manager = ActionManager.getInstance(); 397 manager.setActionEnabled(InspectAction.VALUE_COMMAND, false); 398 manager.setActionEnabled(MemoryAction.VALUE_COMMAND, false); 399 manager.setActionEnabled(JavaStackTraceAction.VALUE_COMMAND, false); 400 } 401 402 private void registerAction(String actionName) { 403 ActionManager manager = ActionManager.getInstance(); 404 DelegateAction action = manager.getDelegateAction(actionName); 405 action.addActionListener(this); 406 } 407 408 409 410 private void fireShowThreadOopInspector() { 411 int i = threadTable.getSelectedRow(); 412 if (i < 0) { 413 return; 414 } 415 416 JavaThread t = dataModel.getJavaThread(i); 417 showThreadOopInspector(t); 418 } 419 420 private void fireShowThreadStackMemory() { 421 int i = threadTable.getSelectedRow(); 422 if (i < 0) { 423 return; 424 } 425 showThreadStackMemory(dataModel.getJavaThread(i)); 426 } 427 428 private void fireShowJavaStackTrace() { 429 int i = threadTable.getSelectedRow(); 430 if (i < 0) { 431 return; 432 } 433 showJavaStackTrace(dataModel.getJavaThread(i)); 434 } 435 436 private void fireShowThreadInfo() { 437 int i = threadTable.getSelectedRow(); 438 if (i < 0) { 439 return; 440 } 441 showThreadInfo(dataModel.getJavaThread(i)); 442 } 443 444 /** 445 * Shows stack memory for threads which have crashed (defined as 446 * having taken a signal above a Java frame) 447 * 448 * @return a flag which indicates if crashes were encountered. 449 */ 450 private boolean fireShowThreadCrashes() { 451 Optional<JavaThread> crashed = 452 cachedThreads.stream() 453 .map(t -> t.getThread()) 454 .filter(t -> t.getAddress().equals( 455 crashThread.getValue())) 456 .findAny(); 457 crashed.ifPresent(this::showThreadStackMemory); 458 return crashed.isPresent(); 459 } 460 461 private void cache() { 462 Threads threads = VM.getVM().getThreads(); 463 for (JavaThread t = threads.first(); t != null; t = t.next()) { 464 if (t.isJavaThread()) { 465 cachedThreads.add(new CachedThread(t)); 466 } 467 } 468 } 469 470 private void decache() { 471 cachedThreads.clear(); 472 } 473 474 }