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