blob: 8a811fbf951a8c75d9efdc7be2917d379e906d22 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.jmx.plugin;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import javax.swing.ToolTipManager;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import sun.tools.jconsole.PlotterPanel;
import sun.tools.jconsole.TimeComboBox;
import sun.tools.jconsole.Plotter.Unit;
public abstract class Stats extends JPanel {
private static final long serialVersionUID = 6041540234044035106L;
private static final String STATS_COLLECTOR = "JEMonitor Stats Collector";
private boolean hideZeroValue = false;
private boolean doLog = false;
private int mBeansNum;
private int selectedRow = -1;
private long statsIntervalMillis = 10000;
/* Collection variables used in this file. */
private Map<ObjectName, LogObject> logMap;
private Map<String, Boolean> shownStats;
private Map<ObjectName, Map<String, String>> valueStore;
private List<GraphFrame> frameList;
protected TreeMap<String, ObjectName> comboToObjects;
/*
* Collections used to save Stats and ObjectNames for showing in tab.
* These values are set by the statsCollector Timer thread, and may
* be concurrently read by other threads, such as the JConsole refresh
* thread.
*/
private Map<ObjectName, Map<String, String>> savedStats;
private volatile List<ObjectName> savedObjectNames;
/* Combo box for selecting a MBean. */
private final int mBeanComboBoxLength = 50;
private JComboBox mBeansComboBox;
private ActionListener mBeanComboBoxListener;
/* Stats table settings. */
private JCheckBox hideZeroValueBox;
private JCheckBox cumulativeStatsBox;
private JTextField statsIntervalText;
/* Stats logging settings. */
private JButton saveLogButton;
private JButton startLogButton;
private JButton stopLogButton;
private SaveLogFileChooser fileChooser;
/* Stats table components. */
private JTable statsTable;
private StatsTableModel statsModel;
private JPopupMenu popup;
private JCheckBoxMenuItem logMenuItem;
private JMenuItem graphMenuItem;
/* MBean related parameters, customized in subclasses. */
private final Object[] envStatParams =
new Object[] {true /* setClear */, true /* setFast */ };
private final String[] signature =
new String[] {"java.lang.boolean", "java.lang.boolean" };
protected static MBeanServerConnection connection;
protected String[] statsTitles;
protected String opName;
protected String mBeanNamePrefix;
protected ObjectName objName;
protected Map<String, String> tips;
private Timer statsCollector;
private final TimerTask drawNewStats = new StatsCollectionTask(false);
public Stats(MBeanServerConnection connection) {
Stats.connection = connection;
setLayout(new FlowLayout());
ToolTipManager.sharedInstance().setDismissDelay(10000);
initVariables();
/* Initiate those containers. */
initContainers();
/* Create GUI components. */
initGUIs();
/* Start collecting stats. */
statsCollector = new Timer(STATS_COLLECTOR);
statsCollector.scheduleAtFixedRate(new StatsCollectionTask(),
0, statsIntervalMillis);
/* Draw the stats tab when the plugin is first started. */
drawNewStats.run();
}
public StatsTableModel getTModel() {
return statsModel;
}
protected abstract void initVariables();
protected abstract void generateTips();
private void initContainers() {
valueStore = new HashMap<ObjectName, Map<String, String>>();
logMap = new HashMap<ObjectName, LogObject>();
shownStats = new HashMap<String, Boolean>();
comboToObjects = new TreeMap<String, ObjectName>();
frameList = new ArrayList<GraphFrame>();
savedStats = new ConcurrentHashMap<ObjectName, Map<String, String>>();
savedObjectNames = new ArrayList<ObjectName>();
for (ObjectName name : getBeansNames()) {
Map<String, String> storeMap =
new LinkedHashMap<String, String>();
Map<String, String> map = generateStats(name);
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
if (key.contains(":")) {
storeMap.put(key.substring(0, key.indexOf(":")), "0");
} else {
storeMap.put(key, "0");
}
}
valueStore.put(name, storeMap);
logMap.put(name, new LogObject());
comboToObjects.put(getMBeanComboBoxString(name), name);
}
for (int i = 0; i < statsTitles.length; i++) {
shownStats.put(statsTitles[i], true);
}
this.objName = getFirstObjectName();
this.mBeansNum = logMap.size();
generateTips();
}
private ObjectName getFirstObjectName() {
return comboToObjects.firstEntry().getValue();
}
/* Update the tips to html format suitable. */
protected void updateTips() {
for (Map.Entry<String, String> entry : tips.entrySet()) {
String value = entry.getValue();
String formatStr = new String();
boolean stop = false;
while (!stop) {
if (value.length() <= 80) {
stop = true;
formatStr += value;
} else {
int endIndex = 79;
String phrase = value.substring(0, endIndex);
if (!phrase.endsWith(" ")) {
endIndex = phrase.lastIndexOf(" ");
}
formatStr += value.substring(0, endIndex) + "<br>";
value = value.substring(endIndex, value.length());
}
}
formatStr = "<html>" + formatStr + "</html>";
tips.put(entry.getKey(), formatStr);
}
}
private void initGUIs() {
/* panel for selecting a specific environment. */
JPanel mBeanPanel = createMBeanPanel();
/* panel for stat settings. */
JPanel setPanel = createSettingPanel();
/* controls for logging stats to a file. */
JPanel logPanel = createLogPanel();
/* display of stats values. */
JPanel statsPanel = createStatsPanel();
createMenu();
add(mBeanPanel);
add(setPanel);
add(logPanel);
add(statsPanel);
}
/* Create the GUI Setting part. */
private JPanel createMBeanPanel() {
mBeansComboBox = new JComboBox();
mBeansComboBox.setPrototypeDisplayValue(generateString());
mBeanComboBoxListener = new BeanComboBoxListener();
initMBeanComboBox(getBeansNames());
return generatePanel(new JComponent[] {
new JLabel("Choose JE Environment:"), mBeansComboBox },
new FlowLayout(), "Choose JE MBean");
}
/* Fill MBeanComboBox with blank spaces if MBean name is too short. */
private String generateString() {
String length = new String();
for (int i = 0; i < mBeanComboBoxLength; i++) {
length += "X";
}
return length;
}
/* Initialize the contents of the mbean combobox. */
private void initMBeanComboBox(List<ObjectName> names) {
/* If the number of MBeans changes, update the map also. */
if (names.size() != comboToObjects.size()) {
comboToObjects = new TreeMap<String, ObjectName>();
for (ObjectName name : names) {
comboToObjects.put(getMBeanComboBoxString(name), name);
}
}
for (Map.Entry<String, ObjectName> entry : comboToObjects.entrySet()) {
mBeansComboBox.addItem(entry.getKey());
}
mBeansComboBox.setSelectedIndex(0);
mBeansComboBox.addActionListener(mBeanComboBoxListener);
}
/* Return the MBean name to display in the mBeanComboBox. */
private String getMBeanComboBoxString(ObjectName name) {
String envHome = name.toString().substring
(name.toString().indexOf("(") + 1, name.toString().length() - 1);
if (envHome.length() > 40) {
envHome = envHome.substring(0, 19) + "..." +
envHome.substring(envHome.length() - 20, envHome.length());
}
return envHome;
}
/* Create the stats setting panel. */
private JPanel createSettingPanel() {
cumulativeStatsBox = new JCheckBox("Display cumulative stats", false);
cumulativeStatsBox.addActionListener(new ClearStatsBoxListener());
hideZeroValueBox = new JCheckBox("Hide zero values", false);
hideZeroValueBox.addActionListener(new HideZeroValueBoxListener());
statsIntervalText = new JTextField("10", 4);
statsIntervalText.addKeyListener(new StatsIntervalListener());
return generatePanel(new JComponent[] {
new JLabel("Collection interval (secs):"),
statsIntervalText,
cumulativeStatsBox,
hideZeroValueBox} ,
new FlowLayout(), "Settings");
}
/* Create the log setting panel. */
private JPanel createLogPanel() {
startLogButton =
createButton("Start Recording", new StartLogListener());
startLogButton.setEnabled(false);
stopLogButton = createButton("Stop Recording", new StopLogListener());
stopLogButton.setEnabled(false);
saveLogButton = createButton("Record Statistics To ...",
new SaveLogListener(this));
return generatePanel(new JComponent[] {
startLogButton, stopLogButton, saveLogButton },
new FlowLayout(), "Record Statistics");
}
/* Create Table Panel. */
private JPanel createStatsPanel() {
/* Create the stats type choosen panel. */
JPanel leftPanel = new JPanel();
leftPanel.setLayout(new GridLayout(15, 1));
leftPanel.add(new JLabel(" Stats to Display"));
for (int i = 0; i < statsTitles.length; i++) {
createCheckBox(statsTitles[i], leftPanel);
}
JScrollPane left = new JScrollPane(leftPanel);
/* Add JE stats table. */
statsModel = new StatsTableModel();
statsTable = new JTable(statsModel);
statsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
statsTable.setDefaultRenderer(String.class, new StringRenderer());
statsTable.setIntercellSpacing(new Dimension(6, 3));
statsTable.setRowHeight(statsTable.getRowHeight() + 4);
statsTable.addMouseListener(new TableMouseListener());
statsTable.addMouseMotionListener(new TableMouseMotionListener());
JScrollPane right = new JScrollPane(statsTable);
right.setAlignmentX(Component.CENTER_ALIGNMENT);
JSplitPane splitPane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
splitPane.setOneTouchExpandable(true);
splitPane.setResizeWeight(0.3);
/* Add table to a panel and set the layout. */
return generatePanel(new JComponent[] { splitPane },
new FlowLayout(), "JE Stats Table");
}
/* Create the pop up menu for the stats shown table. */
private void createMenu() {
popup = new JPopupMenu();
logMenuItem = new JCheckBoxMenuItem("Log This Stat");
logMenuItem.addActionListener(new LogMenuListener());
popup.add(logMenuItem);
graphMenuItem = new JMenuItem("Graph This Stat");
graphMenuItem.addActionListener(new GraphMenuListener());
popup.add(graphMenuItem);
}
/* Create CheckBox for choosing shown stats. */
private void createCheckBox(String title, JPanel container) {
JCheckBox checkBox = new JCheckBox(title, true);
checkBox.addActionListener(new StatsTypeListener());
container.add(checkBox);
}
private JButton createButton(String text, ActionListener listener) {
JButton button = new JButton(text);
button.addActionListener(listener);
return button;
}
/*
* Add provided components to a panel using assigned layout
* with a broder surrounded.
*/
private JPanel generatePanel(JComponent[] components,
LayoutManager layout,
String panelName) {
JPanel panel = new JPanel(layout);
for (JComponent component : components) {
panel.add(component);
}
/* If the panelName is none, then no border would be added. */
if (!"none".equals(panelName)) {
panel.setBorder
(BorderFactory.createCompoundBorder
(BorderFactory.createTitledBorder(panelName),
BorderFactory.createEmptyBorder(0, 0, 0, 0)));
}
return panel;
}
/* Set the connection for this plugin. */
public void setConnection(MBeanServerConnection connection) {
Stats.connection = connection;
}
/* Get the connection to the MBeanServer. */
public static MBeanServerConnection getConnection() {
return connection;
}
/* Remove a GraphFrame from the list and release the resouces. */
public void removeGraphFrame(ObjectName beanName,
String graphStats) {
CopyOnWriteArrayList<GraphFrame> list =
new CopyOnWriteArrayList<GraphFrame>(frameList);
for (GraphFrame frame : list) {
if (frame.getBeanName().equals(beanName) &&
frame.getStatsName().equals(graphStats)) {
frameList.remove(frame);
frame = null;
}
}
}
/* Get results for table stats. */
public synchronized List<Map.Entry<String, String>> getResultsList() {
if (savedObjectNames == null || savedObjectNames.size() == 0) {
return null;
}
/* Refresh the table if there is an MBean change. */
repaintComboBox(savedObjectNames);
Map<String, String> displayStats = savedStats.get(objName);
if (displayStats == null || displayStats.size() == 0) {
return null;
}
/* Remove the those stats we don't want to show on table. */
removeUnShownTypeStats(displayStats);
/* Hide zero values if we choose to hide them. */
if (hideZeroValue) {
hideZeroValues(displayStats);
}
/* If no stats are removed from shown, add a null stats on table. */
if (displayStats.size() == 0) {
displayStats.put("No Stats", "");
}
List<Map.Entry<String, String>> list =
new ArrayList<Map.Entry<String, String>>(displayStats.entrySet());
return list;
}
/*
* Go through the stats map to remove those stats which belongs to the
* unshown types chosen by users.
*/
private void removeUnShownTypeStats(Map<String, String> map) {
Object[] keys = map.keySet().toArray();
for (Map.Entry<String, Boolean> stats : shownStats.entrySet()) {
/* If the stats is not chosen, remove it from map. */
if (!stats.getValue()) {
for (Object key : keys) {
if (key.toString().contains(stats.getKey())) {
map.remove(key.toString());
}
}
}
}
emptyArray(keys);
keys = null;
}
/* Empty the array to release the resources it requires. */
private static void emptyArray(Object[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = null;
}
}
/*
* Hide zero values from the stats table, if all stats belongs to a type
* are all zero values, then the whole type would remove from table,
* including the stats title, like: Compression stats, etc.
*/
private void hideZeroValues(Map<String, String> map) {
Object[] keys = map.keySet().toArray();
for (Map.Entry<String, Boolean> entry : shownStats.entrySet()) {
boolean deleteAll = true;
for (Object key : keys) {
String value = map.get(key.toString());
/* Ensure we are operating on a non-deleted stat. */
if (key.toString().contains(entry.getKey()) && value != null) {
if (!(value.equals(""))) {
if (value.equals("0")) {
map.remove(key.toString());
} else {
deleteAll = false;
}
}
}
}
if (deleteAll) {
map.remove(entry.getKey());
}
}
emptyArray(keys);
keys = null;
}
/* Get results of the invoked MBean operation. */
private synchronized Map<String, String> generateStats(ObjectName name) {
Map<String, String> map = new LinkedHashMap<String, String>();
try {
if (connection != null &&
connection.queryNames(name, null) != null) {
String status = (String)
connection.invoke(name, opName, envStatParams, signature);
StringTokenizer st1 = new StringTokenizer(status, "\n");
String title = null;
while (st1.hasMoreTokens()) {
String expression = st1.nextToken();
if (expression != null) {
if (expression.indexOf("=") < 0) {
StringTokenizer st2 =
new StringTokenizer(expression, ":");
title = st2.nextToken();
map.put(title, "");
} else {
StringTokenizer st2 =
new StringTokenizer(expression, "=");
String stats = " " + st2.nextToken();
String value = st2.nextToken().trim();
map.put(stats + ":" + title, value);
}
}
}
}
} catch (javax.management.InstanceNotFoundException e) {
/*
* If the connection is broken while it is trying to invoke, close
* and release all resources.
*/
forceClose();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/* Stop and null the log and graph thread, close all the file writers. */
private void forceClose() {
setConnection(null);
for (Map.Entry<ObjectName, LogObject> item : logMap.entrySet()) {
item.getValue().closeFileWriter();
}
}
/* Return the JEMonitor MBean names in this application. */
private synchronized ArrayList<ObjectName> getBeansNames() {
if (connection == null) {
return null;
}
ArrayList<ObjectName> names = null;
try {
ObjectName name = new ObjectName(mBeanNamePrefix);
if (connection.queryNames(name, null).size() != 0) {
names = new ArrayList<ObjectName>
(connection.queryNames(name, null));
}
} catch (java.rmi.RemoteException e) {
/*
* Because the interval of plugin and the logging thread is not the
* same, sometimes logging thread would try to invoke a non-active
* connection and throws out RemoteException. We need to stop the
* thread and release the resources.
*/
forceClose();
} catch (Exception e) {
e.printStackTrace();
}
return names;
}
/* Repaint mbeans combobox if the number of MBeans changes. */
private void repaintComboBox(List<ObjectName> names) {
if (names == null) {
return;
}
try {
if (names.size() != mBeansNum && names.size() != 0) {
mBeansComboBox.removeActionListener(mBeanComboBoxListener);
mBeansComboBox.removeAllItems();
initMBeanComboBox(names);
/*
* Remove it from the logging map, release the resources
* acquired by this MBean.
*/
for (Map.Entry<ObjectName, LogObject> entry :
logMap.entrySet()) {
boolean find = false;
for (ObjectName name : names) {
if (name.equals(entry.getKey())) {
find = true;
break;
}
}
if (!find) {
logMap.get(entry.getKey()).closeFileWriter();
logMap.remove(entry.getKey());
valueStore.remove(entry.getKey());
break;
}
}
objName = getFirstObjectName();
mBeansNum = names.size();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/* Write chosen stats to the log. */
private void writeToLog(Map<String, String> map,
ObjectName currentName) {
try {
if (map == null)
return;
/* If the log name is not set, return. */
LogObject item = logMap.get(currentName);
if (item.getLogName() == null)
return;
StringBuilder buffer = new StringBuilder();
getCSVOutput(buffer, map, false, currentName);
buffer.append("\n");
item.writeLog(buffer.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/* Make the stats output in CSV format. */
private void getCSVOutput(StringBuilder buffer,
Map<String, String> map,
boolean init,
ObjectName name) {
LogObject item = logMap.get(name);
if (map == null || map.size() == 0) {
return;
}
/* Before write to log, remove those unlog stats. */
if (item != null) {
Object[] keys = map.keySet().toArray();
for (String title : item.getTurnOff()) {
for (Object key : keys) {
if (key.toString().contains(title)) {
map.remove(key.toString());
}
}
}
emptyArray(keys);
keys = null;
}
item = null;
if (!init) {
buffer.append(System.currentTimeMillis());
} else {
buffer.append("TIME");
}
for (int i = 0; i < statsTitles.length; i++) {
map.remove(statsTitles[i]);
}
if (map.size() > 0) {
buffer.append(",");
int count = 1;
for (Map.Entry<String, String> entry : map.entrySet()) {
String title = entry.getKey().substring
(0, entry.getKey().indexOf(":")).trim();
if (!init) {
title = "\"" + entry.getValue() + "\"";
}
if (count < map.size())
title = title + ",";
buffer.append(title);
count++;
}
}
}
/* Return a new SwingWorker for UI update. */
private SwingWorker<?,?> newSwingWorker() {
ArrayList<Stats> list = new ArrayList<Stats>();
list.add(this);
return new StatsSwingWorker(list);
}
/* Writing and graphing stats according to the specified interval. */
private void writeLogAndGraphing(ObjectName objectName,
Map<String, String> values) {
if ((values == null) || (values.size() == 0)) {
return;
}
/* Graphing stats. */
if (frameList.size() > 0) {
for (GraphFrame frame : frameList) {
if (frame.getBeanName().equals(objectName)) {
frame.writeData(values);
}
}
}
/* Write stats to csv file. */
if (doLog) {
writeToLog(values, objectName);
}
}
/* A utility class for recording the environment-related information. */
private class LogObject {
private String logName;
private FileWriter csvOutput = null;
private ArrayList<String> turnOffIndex = new ArrayList<String>();
public void setLogName(String logName) {
if (logName.contains(".csv")) {
this.logName = logName;
} else {
this.logName = logName + ".csv";
}
}
public String getLogName() {
return logName;
}
public void addTurnOff(String title) {
turnOffIndex.add(title);
}
public ArrayList<String> getTurnOff() {
return turnOffIndex;
}
/* Paint the CSV file header. */
public void initCSVOutput(ObjectName objectName) {
if (logName != null) {
try {
csvOutput = new FileWriter(new File(logName), true);
StringBuilder buffer = new StringBuilder();
Map<String, String> map = generateStats(objectName);
getCSVOutput(buffer, map, true, objectName);
csvOutput.append(buffer.toString() + "\n");
csvOutput.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/* Write stats to log. */
public void writeLog(String stats) {
try {
if (csvOutput != null) {
csvOutput.append(stats);
csvOutput.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/* Close the file writer. */
public void closeFileWriter() {
try {
if (csvOutput != null)
csvOutput.close();
csvOutput = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}
/* The table model for stats table. */
public class StatsTableModel extends AbstractTableModel {
private static final long serialVersionUID = -2478788160419123718L;
private String[] columnNames = { "Stat Name", "Value" };
private List<Map.Entry<String, String>> list =
new ArrayList<Map.Entry<String, String>>();
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return (list == null) ? 0 : list.size();
}
@Override
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
Map.Entry<String, String> value = list.get(row);
switch (col) {
case 0 :
/* Column 0 shows the stats name.*/
if (value.getKey().indexOf(":") < 0) {
return value.getKey().trim();
} else {
return value.getKey().
substring(0, value.getKey().indexOf(":"));
}
case 1 :
return value.getValue();
default:
return null;
}
}
@Override
@SuppressWarnings("unchecked")
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
public void setList(List<Map.Entry<String, String>> list) {
this.list = list;
}
}
/*
* If a stat's value has changed since last time, then marked it in red.
*/
private class StringRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 480362177240428265L;
public StringRenderer() {
super();
setHorizontalAlignment(JLabel.LEFT);
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
Component cell =
super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
/*
* If the value is the same, the font color is black, if the value
* is different, then change the color to red, and replace the
* value in the valueStore to the new one.
*/
if (column == 1) {
String newValue =
valueStore.get(objName).get(table.getValueAt(row, 0));
if (newValue != null &&
!newValue.equals(table.getValueAt(row, 1))) {
valueStore.get(objName).
put(table.getValueAt(row, 0).toString(),
table.getValueAt(row, 1).toString().trim());
cell.setForeground(Color.RED);
}
} else {
cell.setForeground(Color.BLACK);
}
return cell;
}
}
/* The file chooser for setting log file. */
private class SaveLogFileChooser extends JFileChooser {
private static final long serialVersionUID = -3035086973026766211L;
public SaveLogFileChooser() {
setFileFilter(new FileNameExtensionFilter("CSV files", "csv"));
}
@Override
public void approveSelection() {
File file = getSelectedFile();
if (file != null) {
FileFilter filter = getFileFilter();
if (filter != null &&
filter instanceof FileNameExtensionFilter) {
String[] extensions =
((FileNameExtensionFilter) filter).getExtensions();
boolean goodExt = (extensions.length > 0) ? true: false;
if (!goodExt) {
file = new File(file.getParent(), file.getName() +
"." + extensions[0]);
}
}
if (file.exists()) {
String okStr = "ok";
String cancelStr = "cancel";
int ret =
JOptionPane.showOptionDialog(this,
"File " + file.getName() + " already exists!",
"Save File",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
new Object[] {okStr, cancelStr}, okStr);
if (ret != JOptionPane.OK_OPTION) {
return;
}
}
setSelectedFile(file);
}
super.approveSelection();
}
}
/* Following classes are listeners for GUI events. */
/* Listener for clear stats button. */
private class ClearStatsBoxListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
envStatParams[0] = !(cumulativeStatsBox.isSelected());
/* Repain the tab. */
drawNewStats.run();
}
}
/* Listener for display non-zero checkbox. */
private class HideZeroValueBoxListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
hideZeroValue = hideZeroValueBox.isSelected();
/* Repaint the tab. */
drawNewStats.run();
}
}
/* Listener for start logging button. */
private class StartLogListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
/* Paint the CSV file header for each MBean. */
for (Map.Entry<ObjectName, LogObject> entry : logMap.entrySet()) {
entry.getValue().initCSVOutput(entry.getKey());
}
enableComponent(new JComponent[] { saveLogButton,
startLogButton,
logMenuItem },
false);
doLog = true;
}
}
/* Listener for stop logging button. */
private class StopLogListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
enableComponent
(new JComponent[] { saveLogButton, logMenuItem }, true);
doLog = false;
startLogButton.setEnabled(false);
stopLogButton.setEnabled(false);
saveLogButton.setEnabled(true);
}
}
/* Set provided component enabled or not. */
private void enableComponent(JComponent[] components, boolean enabled) {
for (JComponent component : components) {
component.setEnabled(enabled);
}
}
/* Listener for save log files button. */
private class SaveLogListener implements ActionListener {
JPanel shownPanel;
public SaveLogListener(JPanel shownPanel) {
this.shownPanel = shownPanel;
}
public void actionPerformed(ActionEvent e) {
if (fileChooser == null) {
fileChooser = new SaveLogFileChooser();
}
int ret = fileChooser.showSaveDialog(shownPanel);
if (ret == JFileChooser.APPROVE_OPTION) {
logMap.get(objName).setLogName
(fileChooser.getSelectedFile().getAbsolutePath());
startLogButton.setEnabled(true);
stopLogButton.setEnabled(true);
}
}
}
/* Listener for choosing a MBean ComboBox. */
private class BeanComboBoxListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
/* If choose another environment, need to update the table. */
objName =
comboToObjects.get(mBeansComboBox.getSelectedItem());
/* Repaint the tab. */
drawNewStats.run();
}
}
/* Listener for stats type checkbox. */
private class StatsTypeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
shownStats.put(((JCheckBox) e.getSource()).getText(),
((JCheckBox) e.getSource()).isSelected());
/* Repaint the tab. */
drawNewStats.run();
}
}
/* Listener for log menu item on JEStats table. */
private class LogMenuListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
LogObject currentBean = logMap.get(objName);
if (selectedRow > -1) {
String title =
statsTable.getValueAt(selectedRow, 0).toString().trim();
if (!logMenuItem.getState() &&
!currentBean.getTurnOff().contains(title)) {
currentBean.addTurnOff(title);
}
if (logMenuItem.getState() &&
currentBean.getTurnOff().contains(title)) {
currentBean.getTurnOff().remove(title);
}
}
}
}
/* Listener for graph menu item on JEStats table. */
private class GraphMenuListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (selectedRow > -1) {
String graphStats =
statsTable.getValueAt(selectedRow, 0).toString().trim();
if (!isTitleEqual(graphStats)) {
boolean initialized = false;
for (GraphFrame frame : frameList) {
if (frame.getBeanName().equals(objName) &&
frame.getStatsName().equals(graphStats)) {
initialized = true;
break;
}
}
if (!initialized) {
frameList.add(new GraphFrame(objName, graphStats));
}
}
}
}
}
/* If the string equals to one of those stats types. */
private boolean isTitleEqual(String title) {
boolean equal = false;
for (int i = 0; i < statsTitles.length; i++) {
if (statsTitles[i].equals(title)) {
equal = true;
break;
}
}
return equal;
}
/* These two classes are listeners for mouse actions on the Stats table. */
private class TableMouseListener implements MouseListener {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
int row = statsTable.rowAtPoint(e.getPoint());
if (row >= 0) {
statsTable.setRowSelectionInterval(row, row);
final String statName =
statsTable.getValueAt(row, 0).toString().trim();
if (logMap.get(objName).getTurnOff().contains(statName)) {
logMenuItem.setState(false);
} else {
logMenuItem.setState(true);
}
/* Check which stats shouldn't be graphed. */
try {
String statValue = getParsedValue
(statsTable.getValueAt(row, 1).toString().trim());
/*
* If the stats value can't be converted to a number,
* disable graphing.
*/
Long.parseLong(statValue);
graphMenuItem.setEnabled(true);
} catch (NumberFormatException exception) {
/* If the value can't be converted to long, disable the
* graphing menu.
*/
graphMenuItem.setEnabled(false);
}
selectedRow = row;
}
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
}
/*
* Parse the number format ***,***,*** to *********.
*/
private String getParsedValue(String value) {
if (value.indexOf(",") > 0) {
StringTokenizer st = new StringTokenizer(value, ",");
value = new String();
while (st.hasMoreTokens()) {
value = value + st.nextToken();
}
}
return value;
}
/* The MouseMotionListener to the table. */
private class TableMouseMotionListener implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
int row = statsTable.rowAtPoint(e.getPoint());
if (row >= 0) {
String stats = ((String) statsTable.getValueAt(row, 0)).trim();
statsTable.setToolTipText(tips.get(stats));
}
}
}
/* KeyListener for setting graph interval text field. */
private class StatsIntervalListener implements KeyListener {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
String newIntervalText = statsIntervalText.getText();
try {
long statsIntervalSeconds = new Long(newIntervalText);
statsIntervalMillis = statsIntervalSeconds * 1000;
} catch (Exception exception) {
System.err.println("\"" + newIntervalText +
"\" is not a valid interval. " +
exception);
}
statsCollector.cancel();
statsCollector = new Timer(STATS_COLLECTOR);
statsCollector.scheduleAtFixedRate(new StatsCollectionTask(),
0,
statsIntervalMillis);
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
/**
* Frame for showing stats.
*
* Note: stats whose type is float are not supported currently.
*/
private class GraphFrame extends JFrame {
private static final long serialVersionUID = 8921577524698094123L;
/* Set the color of line in graphing. */
private final Color statsColor = Color.blue.darker();
/* The panel doing the graphing work. */
private PlotterPanel plotterPanel;
private final ObjectName bean;
private final String stats;
public GraphFrame(ObjectName beanName, String statsName) {
super(statsName.trim() + " for " + beanName.toString());
bean = beanName;
stats = statsName.trim();
setLayout(new BorderLayout(0, 0));
setSize(800, 400);
/* Add TimeComboBox and PlotterPanel to the frame. */
JPanel topPanel = new JPanel(new BorderLayout());
JPanel controlPanel =
new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 5));
controlPanel.add(new JLabel("Time Range:"));
plotterPanel = new PlotterPanel(stats, Unit.NONE, false);
plotterPanel.getPlotter().createSequence
(stats, stats, statsColor, true);
TimeComboBox timeComboBox =
new TimeComboBox(plotterPanel.getPlotter());
controlPanel.add(timeComboBox);
topPanel.add(controlPanel, BorderLayout.CENTER);
add(topPanel, BorderLayout.NORTH);
add(plotterPanel, BorderLayout.CENTER);
/*
* When we click the close button, it would dispose the frame and
* if the connection is alive, remove the frame in the frame list.
*/
addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent evt) {
setVisible(false);
dispose();
if (JEStats.getConnection() != null) {
removeGraphFrame(bean, stats);
}
}
});
setVisible(true);
}
public ObjectName getBeanName() {
return bean;
}
public String getStatsName() {
return stats;
}
/*
* When use the PlotterPanel, we only need to write data into it.
*/
public void writeData(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if (entry.getKey().indexOf(":") > 0) {
String realKey = entry.getKey().substring
(0, entry.getKey().indexOf(":")).trim();
/*
* The following three stats can't convert to long, so
* ignore them. Also, some values have "," inside, need to
* remove it away.
*/
if (stats.equals(realKey)) {
String value = getParsedValue(entry.getValue());
plotterPanel.getPlotter().addValues
(System.currentTimeMillis(), Long.valueOf(value));
}
}
}
}
}
private class StatsCollectionTask extends TimerTask {
private final boolean writeLogAndGraphing;
public StatsCollectionTask() {
this(true);
}
public StatsCollectionTask(boolean writeLogAndGraphing) {
this.writeLogAndGraphing = writeLogAndGraphing;
}
@Override
public void run() {
List<ObjectName> objectNames = getBeansNames();
if (objectNames == null || objectNames.size() == 0) {
return;
}
/*
* Reset the object names. Make sure to do ths assignment
* atomically, because other threads may read savedObjectNames.
*/
savedObjectNames = objectNames;
for (ObjectName objectName : objectNames) {
Map<String, String> statValues = generateStats(objectName);
/* Copy and save stats and ObjectNames for tab refreshing. */
Map<String, String> copiedStats =
new LinkedHashMap<String, String>();
copiedStats.putAll(statValues);
savedStats.put(objectName, copiedStats);
/* Write log and update graphing for this MBean. */
if (writeLogAndGraphing) {
writeLogAndGraphing(objectName, statValues);
}
}
newSwingWorker().execute();
}
}
}