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 }