blob: c1d48cc37165da6796c2cc26550267f48339cf59 [file] [log] [blame]
/*
* Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
/*
*
* Example of using the java.lang.management API to sort threads
* by CPU usage.
*
* JTop class can be run as a standalone application.
* It first establishs a connection to a target VM specified
* by the given hostname and port number where the JMX agent
* to be connected. It then polls for the thread information
* and the CPU consumption of each thread to display every 2
* seconds.
*
* It is also used by JTopPlugin which is a JConsolePlugin
* that can be used with JConsole (see README.txt). The JTop
* GUI will be added as a JConsole tab by the JTop plugin.
*
* @see com.sun.tools.jconsole.JConsolePlugin
*
* @author Mandy Chung
*/
import java.lang.management.*;
import javax.management.*;
import javax.management.remote.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.text.NumberFormat;
import java.net.MalformedURLException;
import static java.lang.management.ManagementFactory.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
/**
* JTop is a JPanel to display thread's name, CPU time, and its state
* in a table.
*/
public class JTop extends JPanel {
private static class StatusBar extends JPanel {
private static final long serialVersionUID = -6483392381797633018L;
private final JLabel statusText;
public StatusBar(boolean defaultVisible) {
super(new GridLayout(1, 1));
statusText = new JLabel();
statusText.setVisible(defaultVisible);
add(statusText);
}
@Override
public Dimension getMaximumSize() {
Dimension maximum = super.getMaximumSize();
Dimension minimum = getMinimumSize();
return new Dimension(maximum.width, minimum.height);
}
public void setMessage(String text) {
statusText.setText(text);
statusText.setVisible(true);
}
}
private static final long serialVersionUID = -1499762160973870696L;
private MBeanServerConnection server;
private ThreadMXBean tmbean;
private MyTableModel tmodel;
private final StatusBar statusBar;
public JTop() {
super(new GridBagLayout());
tmodel = new MyTableModel();
JTable table = new JTable(tmodel);
table.setPreferredScrollableViewportSize(new Dimension(500, 300));
// Set the renderer to format Double
table.setDefaultRenderer(Double.class, new DoubleRenderer());
// Add some space
table.setIntercellSpacing(new Dimension(6,3));
table.setRowHeight(table.getRowHeight() + 4);
// Create the scroll pane and add the table to it.
JScrollPane scrollPane = new JScrollPane(table);
// Add the scroll pane to this panel.
GridBagConstraints c1 = new GridBagConstraints();
c1.fill = GridBagConstraints.BOTH;
c1.gridy = 0;
c1.gridx = 0;
c1.weightx = 1;
c1.weighty = 1;
add(scrollPane, c1);
statusBar = new StatusBar(false);
GridBagConstraints c2 = new GridBagConstraints();
c2.fill = GridBagConstraints.HORIZONTAL;
c2.gridy = 1;
c2.gridx = 0;
c2.weightx = 1.0;
c2.weighty = 0.0;
add(statusBar, c2);
}
// Set the MBeanServerConnection object for communicating
// with the target VM
public void setMBeanServerConnection(MBeanServerConnection mbs) {
this.server = mbs;
try {
this.tmbean = newPlatformMXBeanProxy(server,
THREAD_MXBEAN_NAME,
ThreadMXBean.class);
} catch (IOException e) {
e.printStackTrace();
}
if (!tmbean.isThreadCpuTimeSupported()) {
statusBar.setMessage("Monitored VM does not support thread CPU time measurement");
} else {
try {
tmbean.setThreadCpuTimeEnabled(true);
} catch (SecurityException e) {
statusBar.setMessage("Monitored VM does not have permission for enabling thread cpu time measurement");
}
}
}
class MyTableModel extends AbstractTableModel {
private static final long serialVersionUID = -7877310288576779514L;
private String[] columnNames = {"ThreadName",
"CPU(sec)",
"State"};
// List of all threads. The key of each entry is the CPU time
// and its value is the ThreadInfo object with no stack trace.
private List<Map.Entry<Long, ThreadInfo>> threadList =
Collections.emptyList();
public MyTableModel() {
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public int getRowCount() {
return threadList.size();
}
@Override
public String getColumnName(int col) {
return columnNames[col];
}
@Override
public Object getValueAt(int row, int col) {
Map.Entry<Long, ThreadInfo> me = threadList.get(row);
switch (col) {
case 0 :
// Column 0 shows the thread name
return me.getValue().getThreadName();
case 1 :
// Column 1 shows the CPU usage
long ns = me.getKey().longValue();
double sec = ns / 1000000000;
return new Double(sec);
case 2 :
// Column 2 shows the thread state
return me.getValue().getThreadState();
default:
return null;
}
}
@Override
public Class<?> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
void setThreadList(List<Map.Entry<Long, ThreadInfo>> list) {
threadList = list;
}
}
/**
* Get the thread list with CPU consumption and the ThreadInfo
* for each thread sorted by the CPU time.
*/
private List<Map.Entry<Long, ThreadInfo>> getThreadList() {
// Get all threads and their ThreadInfo objects
// with no stack trace
long[] tids = tmbean.getAllThreadIds();
ThreadInfo[] tinfos = tmbean.getThreadInfo(tids);
// build a map with key = CPU time and value = ThreadInfo
SortedMap<Long, ThreadInfo> map = new TreeMap<Long, ThreadInfo>();
for (int i = 0; i < tids.length; i++) {
long cpuTime = tmbean.getThreadCpuTime(tids[i]);
// filter out threads that have been terminated
if (cpuTime != -1 && tinfos[i] != null) {
map.put(new Long(cpuTime), tinfos[i]);
}
}
// build the thread list and sort it with CPU time
// in decreasing order
Set<Map.Entry<Long, ThreadInfo>> set = map.entrySet();
List<Map.Entry<Long, ThreadInfo>> list =
new ArrayList<Map.Entry<Long, ThreadInfo>>(set);
Collections.reverse(list);
return list;
}
/**
* Format Double with 4 fraction digits
*/
class DoubleRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1704639497162584382L;
NumberFormat formatter;
public DoubleRenderer() {
super();
setHorizontalAlignment(JLabel.RIGHT);
}
@Override
public void setValue(Object value) {
if (formatter==null) {
formatter = NumberFormat.getInstance();
formatter.setMinimumFractionDigits(4);
}
setText((value == null) ? "" : formatter.format(value));
}
}
// SwingWorker responsible for updating the GUI
//
// It first gets the thread and CPU usage information as a
// background task done by a worker thread so that
// it will not block the event dispatcher thread.
//
// When the worker thread finishes, the event dispatcher
// thread will invoke the done() method which will update
// the UI.
class Worker extends SwingWorker<List<Map.Entry<Long, ThreadInfo>>,Object> {
private MyTableModel tmodel;
Worker(MyTableModel tmodel) {
this.tmodel = tmodel;
}
// Get the current thread info and CPU time
@Override
public List<Map.Entry<Long, ThreadInfo>> doInBackground() {
return getThreadList();
}
// fire table data changed to trigger GUI update
// when doInBackground() is finished
@Override
protected void done() {
try {
// Set table model with the new thread list
tmodel.setThreadList(get());
// refresh the table model
tmodel.fireTableDataChanged();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
}
}
// Return a new SwingWorker for UI update
public SwingWorker<?,?> newSwingWorker() {
return new Worker(tmodel);
}
public static void main(String[] args) throws Exception {
// Validate the input arguments
if (args.length != 1) {
usage();
}
String[] arg2 = args[0].split(":");
if (arg2.length != 2) {
usage();
}
String hostname = arg2[0];
int port = -1;
try {
port = Integer.parseInt(arg2[1]);
} catch (NumberFormatException x) {
usage();
}
if (port < 0) {
usage();
}
// Create the JTop Panel
final JTop jtop = new JTop();
// Set up the MBeanServerConnection to the target VM
MBeanServerConnection server = connect(hostname, port);
jtop.setMBeanServerConnection(server);
// A timer task to update GUI per each interval
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
// Schedule the SwingWorker to update the GUI
jtop.newSwingWorker().execute();
}
};
// Create the standalone window with JTop panel
// by the event dispatcher thread
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
createAndShowGUI(jtop);
}
});
// refresh every 2 seconds
Timer timer = new Timer("JTop Sampling thread");
timer.schedule(timerTask, 0, 2000);
}
// Establish a connection with the remote application
//
// You can modify the urlPath to the address of the JMX agent
// of your application if it has a different URL.
//
// You can also modify the following code to take
// username and password for client authentication.
private static MBeanServerConnection connect(String hostname, int port) {
// Create an RMI connector client and connect it to
// the RMI connector server
String urlPath = "/jndi/rmi://" + hostname + ":" + port + "/jmxrmi";
MBeanServerConnection server = null;
try {
JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
JMXConnector jmxc = JMXConnectorFactory.connect(url);
server = jmxc.getMBeanServerConnection();
} catch (MalformedURLException e) {
// should not reach here
} catch (IOException e) {
System.err.println("\nCommunication error: " + e.getMessage());
System.exit(1);
}
return server;
}
private static void usage() {
System.out.println("Usage: java JTop <hostname>:<port>");
System.exit(1);
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI(JPanel jtop) {
// Create and set up the window.
JFrame frame = new JFrame("JTop");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// Create and set up the content pane.
JComponent contentPane = (JComponent) frame.getContentPane();
contentPane.add(jtop, BorderLayout.CENTER);
contentPane.setOpaque(true); //content panes must be opaque
contentPane.setBorder(new EmptyBorder(12, 12, 12, 12));
frame.setContentPane(contentPane);
// Display the window.
frame.pack();
frame.setVisible(true);
}
}