1 /*
2  * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  *   - Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  *
11  *   - Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *
15  *   - Neither the name of Oracle nor the names of its
16  *     contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * This source code is provided to illustrate the usage of a given feature
34  * or technique and has been deliberately simplified. Additional steps
35  * required for a production-quality application, such as security checks,
36  * input validation and proper error handling, might not be present in
37  * this sample code.
38  */
39 
40 
41 /*
42  *
43  * Example of using the java.lang.management API to sort threads
44  * by CPU usage.
45  *
46  * JTop class can be run as a standalone application.
47  * It first establishs a connection to a target VM specified
48  * by the given hostname and port number where the JMX agent
49  * to be connected.  It then polls for the thread information
50  * and the CPU consumption of each thread to display every 2
51  * seconds.
52  *
53  * It is also used by JTopPlugin which is a JConsolePlugin
54  * that can be used with JConsole (see README.txt). The JTop
55  * GUI will be added as a JConsole tab by the JTop plugin.
56  *
57  * @see com.sun.tools.jconsole.JConsolePlugin
58  *
59  * @author Mandy Chung
60  */
61 import java.lang.management.*;
62 import javax.management.*;
63 import javax.management.remote.*;
64 import java.io.IOException;
65 import java.util.ArrayList;
66 import java.util.Collections;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.SortedMap;
71 import java.util.Timer;
72 import java.util.TimerTask;
73 import java.util.TreeMap;
74 import java.util.concurrent.ExecutionException;
75 import java.text.NumberFormat;
76 import java.net.MalformedURLException;
77 import static java.lang.management.ManagementFactory.*;
78 
79 import java.awt.*;
80 import javax.swing.*;
81 import javax.swing.border.*;
82 import javax.swing.table.*;
83 
84 /**
85  * JTop is a JPanel to display thread's name, CPU time, and its state
86  * in a table.
87  */
88 public class JTop extends JPanel {
89 
90     private static class StatusBar extends JPanel {
91         private static final long serialVersionUID = -6483392381797633018L;
92         private final JLabel statusText;
93 
StatusBar(boolean defaultVisible)94         public StatusBar(boolean defaultVisible) {
95             super(new GridLayout(1, 1));
96             statusText = new JLabel();
97             statusText.setVisible(defaultVisible);
98             add(statusText);
99         }
100 
101         @Override
getMaximumSize()102         public Dimension getMaximumSize() {
103             Dimension maximum = super.getMaximumSize();
104             Dimension minimum = getMinimumSize();
105             return new Dimension(maximum.width, minimum.height);
106         }
107 
setMessage(String text)108         public void setMessage(String text) {
109             statusText.setText(text);
110             statusText.setVisible(true);
111         }
112     }
113     private static final long serialVersionUID = -1499762160973870696L;
114     private MBeanServerConnection server;
115     private ThreadMXBean tmbean;
116     private MyTableModel tmodel;
117     private final StatusBar statusBar;
JTop()118     public JTop() {
119         super(new GridBagLayout());
120 
121         tmodel = new MyTableModel();
122         JTable table = new JTable(tmodel);
123         table.setPreferredScrollableViewportSize(new Dimension(500, 300));
124 
125         // Set the renderer to format Double
126         table.setDefaultRenderer(Double.class, new DoubleRenderer());
127         // Add some space
128         table.setIntercellSpacing(new Dimension(6,3));
129         table.setRowHeight(table.getRowHeight() + 4);
130 
131         // Create the scroll pane and add the table to it.
132         JScrollPane scrollPane = new JScrollPane(table);
133 
134         // Add the scroll pane to this panel.
135         GridBagConstraints c1 = new GridBagConstraints();
136         c1.fill = GridBagConstraints.BOTH;
137         c1.gridy = 0;
138         c1.gridx = 0;
139         c1.weightx = 1;
140         c1.weighty = 1;
141         add(scrollPane, c1);
142 
143         statusBar = new StatusBar(false);
144         GridBagConstraints c2 = new GridBagConstraints();
145         c2.fill = GridBagConstraints.HORIZONTAL;
146         c2.gridy = 1;
147         c2.gridx = 0;
148         c2.weightx = 1.0;
149         c2.weighty = 0.0;
150         add(statusBar, c2);
151     }
152 
153     // Set the MBeanServerConnection object for communicating
154     // with the target VM
setMBeanServerConnection(MBeanServerConnection mbs)155     public void setMBeanServerConnection(MBeanServerConnection mbs) {
156         this.server = mbs;
157         try {
158             this.tmbean = newPlatformMXBeanProxy(server,
159                                                  THREAD_MXBEAN_NAME,
160                                                  ThreadMXBean.class);
161         } catch (IOException e) {
162             e.printStackTrace();
163         }
164         if (!tmbean.isThreadCpuTimeSupported()) {
165             statusBar.setMessage("Monitored VM does not support thread CPU time measurement");
166         } else {
167             try {
168                 tmbean.setThreadCpuTimeEnabled(true);
169             } catch (SecurityException e) {
170                 statusBar.setMessage("Monitored VM does not have permission for enabling thread cpu time measurement");
171             }
172         }
173     }
174 
175     class MyTableModel extends AbstractTableModel {
176         private static final long serialVersionUID = -7877310288576779514L;
177         private String[] columnNames = {"ThreadName",
178                                         "CPU(sec)",
179                                         "State"};
180         // List of all threads. The key of each entry is the CPU time
181         // and its value is the ThreadInfo object with no stack trace.
182         private List<Map.Entry<Long, ThreadInfo>> threadList =
183             Collections.emptyList();
184 
MyTableModel()185         public MyTableModel() {
186         }
187 
188         @Override
getColumnCount()189         public int getColumnCount() {
190             return columnNames.length;
191         }
192 
193         @Override
getRowCount()194         public int getRowCount() {
195             return threadList.size();
196         }
197 
198         @Override
getColumnName(int col)199         public String getColumnName(int col) {
200             return columnNames[col];
201         }
202 
203         @Override
getValueAt(int row, int col)204         public Object getValueAt(int row, int col) {
205             Map.Entry<Long, ThreadInfo> me = threadList.get(row);
206             switch (col) {
207                 case 0 :
208                     // Column 0 shows the thread name
209                     return me.getValue().getThreadName();
210                 case 1 :
211                     // Column 1 shows the CPU usage
212                     long ns = me.getKey().longValue();
213                     double sec = ns / 1000000000;
214                     return new Double(sec);
215                 case 2 :
216                     // Column 2 shows the thread state
217                     return me.getValue().getThreadState();
218                 default:
219                     return null;
220             }
221         }
222 
223         @Override
getColumnClass(int c)224         public Class<?> getColumnClass(int c) {
225             return getValueAt(0, c).getClass();
226         }
227 
setThreadList(List<Map.Entry<Long, ThreadInfo>> list)228         void setThreadList(List<Map.Entry<Long, ThreadInfo>> list) {
229             threadList = list;
230         }
231     }
232 
233     /**
234      * Get the thread list with CPU consumption and the ThreadInfo
235      * for each thread sorted by the CPU time.
236      */
getThreadList()237     private List<Map.Entry<Long, ThreadInfo>> getThreadList() {
238         // Get all threads and their ThreadInfo objects
239         // with no stack trace
240         long[] tids = tmbean.getAllThreadIds();
241         ThreadInfo[] tinfos = tmbean.getThreadInfo(tids);
242 
243         // build a map with key = CPU time and value = ThreadInfo
244         SortedMap<Long, ThreadInfo> map = new TreeMap<Long, ThreadInfo>();
245         for (int i = 0; i < tids.length; i++) {
246             long cpuTime = tmbean.getThreadCpuTime(tids[i]);
247             // filter out threads that have been terminated
248             if (cpuTime != -1 && tinfos[i] != null) {
249                 map.put(new Long(cpuTime), tinfos[i]);
250             }
251         }
252 
253         // build the thread list and sort it with CPU time
254         // in decreasing order
255         Set<Map.Entry<Long, ThreadInfo>> set = map.entrySet();
256         List<Map.Entry<Long, ThreadInfo>> list =
257             new ArrayList<Map.Entry<Long, ThreadInfo>>(set);
258         Collections.reverse(list);
259         return list;
260     }
261 
262 
263     /**
264      * Format Double with 4 fraction digits
265      */
266     class DoubleRenderer extends DefaultTableCellRenderer {
267         private static final long serialVersionUID = 1704639497162584382L;
268         NumberFormat formatter;
DoubleRenderer()269         public DoubleRenderer() {
270             super();
271             setHorizontalAlignment(JLabel.RIGHT);
272         }
273 
274         @Override
setValue(Object value)275         public void setValue(Object value) {
276             if (formatter==null) {
277                 formatter = NumberFormat.getInstance();
278                 formatter.setMinimumFractionDigits(4);
279             }
280             setText((value == null) ? "" : formatter.format(value));
281         }
282     }
283 
284     // SwingWorker responsible for updating the GUI
285     //
286     // It first gets the thread and CPU usage information as a
287     // background task done by a worker thread so that
288     // it will not block the event dispatcher thread.
289     //
290     // When the worker thread finishes, the event dispatcher
291     // thread will invoke the done() method which will update
292     // the UI.
293     class Worker extends SwingWorker<List<Map.Entry<Long, ThreadInfo>>,Object> {
294         private MyTableModel tmodel;
Worker(MyTableModel tmodel)295         Worker(MyTableModel tmodel) {
296             this.tmodel = tmodel;
297         }
298 
299         // Get the current thread info and CPU time
300         @Override
doInBackground()301         public List<Map.Entry<Long, ThreadInfo>> doInBackground() {
302             return getThreadList();
303         }
304 
305         // fire table data changed to trigger GUI update
306         // when doInBackground() is finished
307         @Override
done()308         protected void done() {
309             try {
310                 // Set table model with the new thread list
311                 tmodel.setThreadList(get());
312                 // refresh the table model
313                 tmodel.fireTableDataChanged();
314             } catch (InterruptedException e) {
315             } catch (ExecutionException e) {
316             }
317         }
318     }
319 
320     // Return a new SwingWorker for UI update
newSwingWorker()321     public SwingWorker<?,?> newSwingWorker() {
322         return new Worker(tmodel);
323     }
324 
main(String[] args)325     public static void main(String[] args) throws Exception {
326         // Validate the input arguments
327         if (args.length != 1) {
328             usage();
329         }
330 
331         String[] arg2 = args[0].split(":");
332         if (arg2.length != 2) {
333             usage();
334         }
335         String hostname = arg2[0];
336         int port = -1;
337         try {
338             port = Integer.parseInt(arg2[1]);
339         } catch (NumberFormatException x) {
340             usage();
341         }
342         if (port < 0) {
343             usage();
344         }
345 
346         // Create the JTop Panel
347         final JTop jtop = new JTop();
348         // Set up the MBeanServerConnection to the target VM
349         MBeanServerConnection server = connect(hostname, port);
350         jtop.setMBeanServerConnection(server);
351 
352         // A timer task to update GUI per each interval
353         TimerTask timerTask = new TimerTask() {
354             @Override
355             public void run() {
356                 // Schedule the SwingWorker to update the GUI
357                 jtop.newSwingWorker().execute();
358             }
359         };
360 
361         // Create the standalone window with JTop panel
362         // by the event dispatcher thread
363         SwingUtilities.invokeAndWait(new Runnable() {
364             @Override
365             public void run() {
366                 createAndShowGUI(jtop);
367             }
368         });
369 
370         // refresh every 2 seconds
371         Timer timer = new Timer("JTop Sampling thread");
372         timer.schedule(timerTask, 0, 2000);
373 
374     }
375 
376     // Establish a connection with the remote application
377     //
378     // You can modify the urlPath to the address of the JMX agent
379     // of your application if it has a different URL.
380     //
381     // You can also modify the following code to take
382     // username and password for client authentication.
connect(String hostname, int port)383     private static MBeanServerConnection connect(String hostname, int port) {
384         // Create an RMI connector client and connect it to
385         // the RMI connector server
386         String urlPath = "/jndi/rmi://" + hostname + ":" + port + "/jmxrmi";
387         MBeanServerConnection server = null;
388         try {
389             JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
390             JMXConnector jmxc = JMXConnectorFactory.connect(url);
391             server = jmxc.getMBeanServerConnection();
392         } catch (MalformedURLException e) {
393             // should not reach here
394         } catch (IOException e) {
395             System.err.println("\nCommunication error: " + e.getMessage());
396             System.exit(1);
397         }
398         return server;
399     }
400 
usage()401     private static void usage() {
402         System.out.println("Usage: java JTop <hostname>:<port>");
403         System.exit(1);
404     }
405     /**
406      * Create the GUI and show it.  For thread safety,
407      * this method should be invoked from the
408      * event-dispatching thread.
409      */
createAndShowGUI(JPanel jtop)410     private static void createAndShowGUI(JPanel jtop) {
411         // Create and set up the window.
412         JFrame frame = new JFrame("JTop");
413         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
414 
415         // Create and set up the content pane.
416         JComponent contentPane = (JComponent) frame.getContentPane();
417         contentPane.add(jtop, BorderLayout.CENTER);
418         contentPane.setOpaque(true); //content panes must be opaque
419         contentPane.setBorder(new EmptyBorder(12, 12, 12, 12));
420         frame.setContentPane(contentPane);
421 
422         // Display the window.
423         frame.pack();
424         frame.setVisible(true);
425     }
426 
427 }
428