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