1 /*
2  * Copyright (c) 2000, 2019, 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 {
VM.registerVMInitializedObserver(o, a) -> initialize(VM.getVM().getTypeDataBase())64         VM.registerVMInitializedObserver(
65                             (o, a) -> initialize(VM.getVM().getTypeDataBase()));
66     }
67 
initialize(TypeDataBase db)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. */
JavaThreadsPanel()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 
ThreadPanel(JTable table)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          */
isInfoVisible()176         private boolean isInfoVisible() {
177             return (splitPane.getBottomComponent() != null);
178         }
179 
showOutputPane()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 
hideOutputPane()196         private void hideOutputPane()  {
197             dividerLocation = splitPane.getDividerLocation();
198             splitPane.remove(threadInfo);
199             splitPane.setDividerSize(0);
200         }
201 
showCurrentThreadInfo()202         private void showCurrentThreadInfo() {
203             int row = threadTable.getSelectedRow();
204             if (row >= 0) {
205                 threadInfo.setJavaThread(dataModel.getJavaThread(row));
206             }
207         }
208 
setActionsEnabled(boolean enabled)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 {
JavaThreadsToolBar(StatusBar status)222         public JavaThreadsToolBar(StatusBar status) {
223             super(HSDBActionManager.getInstance(), status);
224         }
225 
addComponents()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 
JavaThreadsColumnModel()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 
JavaThreadsTableModel(java.util.List threads)270         public JavaThreadsTableModel(java.util.List threads) {
271             this.elements = threads;
272         }
273 
getColumnCount()274         public int getColumnCount() {
275             return columnNames.length;
276         }
277 
getRowCount()278         public int getRowCount() {
279             return elements.size();
280         }
281 
getColumnName(int col)282         public String getColumnName(int col) {
283             return columnNames[col];
284         }
285 
getValueAt(int row, int col)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          */
getJavaThread(int index)301         public JavaThread getJavaThread(int index) {
302             return getRow(index).getThread();
303         }
304 
getRow(int row)305         private CachedThread getRow(int row) {
306             return (CachedThread)elements.get(row);
307         }
308 
threadIDAt(int index)309         private String threadIDAt(int index) {
310             return ((CachedThread) cachedThreads.get(index)).getThreadID();
311         }
312 
threadNameAt(int index)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 
actionPerformed(ActionEvent evt)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 
CachedThread(JavaThread thread)351         public CachedThread(JavaThread thread) {
352             this.thread = thread;
353         }
354 
getThread()355         public JavaThread getThread() {
356             return thread;
357         }
358 
getThreadID()359         public String getThreadID() {
360             if (!computed) {
361                 compute();
362             }
363 
364             return threadID;
365         }
366 
getThreadName()367         public String getThreadName() {
368             if (!computed) {
369                 compute();
370             }
371 
372             return threadName;
373         }
374 
compute()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 
registerActions()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 
registerAction(String actionName)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 
fireShowThreadOopInspector()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 
fireShowThreadStackMemory()420     private void fireShowThreadStackMemory() {
421         int i = threadTable.getSelectedRow();
422         if (i < 0) {
423             return;
424         }
425         showThreadStackMemory(dataModel.getJavaThread(i));
426     }
427 
fireShowJavaStackTrace()428     private void fireShowJavaStackTrace() {
429         int i = threadTable.getSelectedRow();
430         if (i < 0) {
431             return;
432         }
433         showJavaStackTrace(dataModel.getJavaThread(i));
434     }
435 
fireShowThreadInfo()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      */
fireShowThreadCrashes()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 
cache()461     private void cache() {
462       Threads threads = VM.getVM().getThreads();
463       for (int i = 0; i < threads.getNumberOfThreads(); i++) {
464         JavaThread t = threads.getJavaThreadAt(i);
465         if (t.isJavaThread()) {
466             cachedThreads.add(new CachedThread(t));
467         }
468       }
469     }
470 
decache()471     private void decache() {
472         cachedThreads.clear();
473     }
474 
475 }
476