/*
* (C) Copyright IBM Corp. 2009
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.apps.sensordemo;
import java.io.FileInputStream;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.security.auth.login.LoginException;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import prefuse.action.assignment.ColorAction;
import prefuse.visual.VisualItem;
import com.ibm.gaiandb.draw.Chart;
import com.ibm.gaiandb.draw.ChartLegend;
import com.ibm.gaiandb.draw.DatabaseDiagram;
import com.ibm.gaiandb.draw.Graph;
import com.ibm.gaiandb.draw.NodeGraph;
import com.ibm.gaiandb.draw.ReservedColorAction;
import com.ibm.gaiandb.apps.DBConnector;
/**
* Draw sensor monitor data coming from any number of PCs.
* Data includes CPU usage, memory usage, temperature, acceleration,
* disk and network I/O and battery levels.
*
* This class will create a window which draws chart representations
* of the data, refreshing every three seconds.
*
* @author Samir Talwar - stalwar@uk.ibm.com
*/
public final class SensorDemo extends DBConnector {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009";
/** The configuration file. Used for custom graphs. */
private static final String configFile = "demoPCM.conf";
/** The directories to check for the config file. */
private static final String[] configDirectories = { ".", "demoPCM" };
/** The colors to use in each diagram. */
private static final int[] colorPalette = {
0xFF3333FF, // blue
0xFFFF0000, // red
0xFF00FF00, // green
0xFFFF6600, // orange
0xFFCC00CC, // purple
0xFFFFFF00, // yellow
0xFF9999FF, // cyan
0xFFFFAACC // pink
};
/** The maximum number of tries per diagram before we give up. */
private static final int maxTries = 3;
/** The window width. */
private static final int windowWidth = 640;
/** The window height. */
private static final int windowHeight = 480;
/** The height of each graph title. */
private static final int titleHeight = 20;
/** The width of each diagram. */
private int diagramWidth;
/** The height of each diagram. */
private int diagramHeight;
/** The diagrams. */
private DatabaseDiagram[] diagrams;
/** The original titles of the diagrams, used when we wish to alter them. */
private Map<DatabaseDiagram, String> titles = new Hashtable<DatabaseDiagram, String>();
/** The diagram title labels. */
private Map<DatabaseDiagram, JLabel> titleLabels = new Hashtable<DatabaseDiagram, JLabel>();
/** Tries left before we give up on the diagram. */
private Map<DatabaseDiagram, Integer> triesLeft = new Hashtable<DatabaseDiagram, Integer>();
/** The interval between refreshes, in milliseconds. */
private int refreshRateMs = 3000;
/**
* Creates a new <code>SensorDemo</code> and begins drawing graphs.
*
* @param args
* The program arguments.
* @throws InterruptedException
* if the thread is interrupted while sleeping.
*/
public static void main(String[] args) throws InterruptedException {
SensorDemo app;
try {
app = new SensorDemo(args);
}
catch (Exception e) {
terminate(e.getMessage());
return;
}
app.load();
app.createWindow();
app.updateDiagrams();
}
/**
* Initializes the demo by connecting to the database using the arguments
* provided.
*
* @param args
* An array containing, in order, the JDBC URL, username, and
* password.
*
* @throws ClassNotFoundException
* if the Derby client driver class cannot be found.
* @throws InterruptedException
* if an interrupt occurs while sleeping between connection
* attempts.
* @throws LoginException
*/
public SensorDemo(String[] args) throws ClassNotFoundException, InterruptedException, LoginException {
super(args);
connect();
}
/**
* Reads in the configuration file if there is one, then creates the graphs
* using the <code>GraphLoader</code>. Handles any custom settings.
*/
public void load() {
// Stop annoying prefuse log messages from showing up.
Logger.getLogger("prefuse").setLevel(Level.WARNING);
// Load the configuration file from wherever it might be.
Properties customProperties = new Properties();
for (String directory : configDirectories) {
try {
customProperties.load(new FileInputStream(directory + "/" + configFile));
break;
}
catch (Exception e) {
// Doesn't exist. Let's try the next one.
}
}
// Load the graphs. The GraphLoader does the heavy lifting here.
GraphLoader loader = new GraphLoader(conn, customProperties);
diagrams = loader.load();
if (diagrams.length == 0) {
System.err.println("No usable graphs. Terminating.");
System.exit(1);
}
// Initialise the diagrams.
for (DatabaseDiagram diagram : diagrams) {
titles.put(diagram, diagram.getTitle());
triesLeft.put(diagram, maxTries);
if (diagram instanceof ChartLegend) {
((Chart)diagram).setColorAction(new ReservedColorAction<String>("names",
DatabaseDiagram.GROUP, "NODE_NAME", VisualItem.FILLCOLOR, colorPalette));
}
else if (diagram instanceof Chart) {
((Chart)diagram).setColorAction(new ReservedColorAction<String>("names",
DatabaseDiagram.GROUP, "NODE", VisualItem.STROKECOLOR, colorPalette));
}
else if (diagram instanceof Graph) {
((Graph)diagram).setNodeColorAction(new ReservedColorAction<String>("names",
Graph.NODE_GROUP, "NODE_NAME", VisualItem.FILLCOLOR, colorPalette));
((Graph)diagram).setEdgeColorAction(new ColorAction(
Graph.EDGE_GROUP, VisualItem.STROKECOLOR, 0xFFCCCCCC));
}
}
// Process any custom settings.
String refreshRate = loader.get("refresh_rate");
if (refreshRate.length() > 0) {
try {
refreshRateMs = Integer.parseInt(refreshRate);
}
catch (NumberFormatException e) {
System.err.println("Could not process a refresh rate of " + refreshRate + ".");
// Go with the default.
}
}
}
/**
* Creates a window and adds each diagram to it, then initialises the
* diagrams.
*/
public void createWindow() {
// System look and feel would be nice, but it's not necessary.
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) {
System.err.println("Cannot use the system's native look and feel.");
}
// Calculate the grid size once.
final int rows, columns;
// Make it a square to begin with.
columns = (int)Math.ceil(Math.sqrt(diagrams.length));
// If we can drop a row, do it.
if ((columns - 1) * columns >= diagrams.length) {
rows = columns - 1;
}
else {
rows = columns;
}
// Set the size of each diagram.
diagramWidth = windowWidth / rows;
diagramHeight = windowHeight / columns - titleHeight;
// Create the window.
final JFrame frame = new JFrame("GaianDB Demo :: System Statistics");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(windowWidth, windowHeight));
frame.setLocationByPlatform(true);
// The default Java font looks a little poor. Let's use something native.
String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
Font labelFont;
if (Arrays.binarySearch(fonts, "Segoe UI") >= 0) {
labelFont = new Font("Segoe UI", Font.PLAIN, 12);
}
else if (Arrays.binarySearch(fonts, "Calibri") >= 0) {
labelFont = new Font("Calibri", Font.PLAIN, 12);
}
else {
labelFont = new Font("SansSerif", Font.PLAIN, 12);
}
frame.setFont(labelFont);
// JFrames can't have backgrounds, so we use a panel instead.
JPanel panel = new JPanel(new GridLayout(rows, columns, 10, 10));
panel.setBackground(Color.white);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
frame.add(panel);
// Add the diagrams to the panel.
for (DatabaseDiagram diagram : diagrams) {
panel.add(createPanel(diagram));
}
// Tell the diagrams to resize with the window.
frame.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
resizeDiagrams(frame, rows, columns);
}
});
for (final DatabaseDiagram diagram : diagrams) {
try {
if (diagram instanceof Chart) {
((Chart)diagram).populateTable(((Chart)diagram).getMaxDuration());
}
else if (diagram instanceof Graph) {
((Graph)diagram).populateGraph();
if (diagram instanceof NodeGraph) {
new Thread() {
public void run() {
while (true) {
String currentItem = ((NodeGraph)diagram).getCurrentItemName();
titleLabels.get(diagram).setText(
titles.get(diagram) +
" (" + ((NodeGraph)diagram).getNodeCount() + ")" +
(null != currentItem
? " Current Node: " + currentItem
: ""));
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
return;
}
}
}
}.start();
}
}
}
catch (SQLException e) {
System.err.println("Populating \"" + diagram.getTitle() + "\" failed due to a " + e.getClass().getName() + ": " + e.getMessage());
triesLeft.put(diagram, 0);
}
}
// Boom. Let's go.
frame.pack();
frame.validate();
frame.setVisible(true);
}
/**
* Resizes the diagrams to fit perfectly within the window.
*
* @param frame
* The window.
* @param rows
* The number of rows.
* @param columns
* The number of columns.
*/
public void resizeDiagrams(JFrame frame, int rows, int columns) {
diagramWidth = frame.getWidth() / rows;
diagramHeight = frame.getHeight() / columns - titleHeight;
for (DatabaseDiagram diagram : diagrams) {
diagram.setPreferredSize(new Dimension(diagramWidth, diagramHeight));
}
}
/**
* Repeatedly loops through the diagrams and updates them, pausing between
* each one.
*
* @throws InterruptedException if the thread is interrupted while sleeping.
*/
public void updateDiagrams() throws InterruptedException {
System.out.println("Executing SQL queries every " + refreshRateMs + "ms.\n");
// Start passing the prepared statements to the frame.
// The frame will execute it and update accordingly.
Thread.sleep(refreshRateMs);
for (int i = 0; ; i = (i + 1) % diagrams.length) {
final DatabaseDiagram diagram = diagrams[i];
if (triesLeft.get(diagram) > 0) {
// UI stuff goes in UI thread. It's easier that way.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
if (diagram instanceof Chart) {
((Chart)diagram).updateTable(refreshRateMs / 1000 + 1);
}
else if (diagram instanceof Graph) {
((Graph)diagram).updateGraph();
}
triesLeft.put(diagram, maxTries);
}
catch (SQLException e) {
System.err.println("Updating \"" + diagram.getTitle() + "\" failed due to a " + e.getClass().getName() + ": " + e.getMessage());
triesLeft.put(diagram, triesLeft.get(diagram) - 1);
}
}
});
}
Thread.sleep(refreshRateMs / diagrams.length);
}
}
/**
* Creates a panel for the diagram provided and its title to reside in.
*
* @param diagram
* The diagram to place within the panel.
* @param labelFont
* The font to use for the title.
*
* @return A new panel.
*/
private JPanel createPanel(DatabaseDiagram diagram) {
// Create the panel.
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.setBackground(Color.white);
panel.setPreferredSize(new Dimension(diagramWidth, diagramHeight + titleHeight));
// Create the title.
JLabel titleLabel = new JLabel(diagram.getTitle());
titleLabel.setSize(diagramWidth, titleHeight);
titleLabel.setHorizontalTextPosition(SwingConstants.CENTER);
titleLabels.put(diagram, titleLabel);
// Resize the diagram.
diagram.setPreferredSize(new Dimension(diagramWidth, diagramHeight));
panel.add(titleLabel);
panel.add(diagram);
panel.validate();
return panel;
}
}