/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.stat;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.slamd.asn1.ASN1Element;
import com.slamd.asn1.ASN1Reader;
import com.slamd.asn1.ASN1Sequence;
import com.slamd.common.SLAMDException;
/**
* This class defines a program that may be used to extract and view information
* contained in encoded persistent stat files.
*
*
* @author Neil A. Wilson
*/
public class PersistentStatViewer
implements ItemListener
{
// The stat trackers with the information to view.
protected StatTracker[] statTrackers;
// The variables related to the GUI.
protected JComboBox statListBox;
private JFrame appWindow;
private JPanel graphPanel;
/**
* Parses the command line arguments to figure out what the user wants to do
* and then attempts to carry that out.
*
* @param args The command-line arguments provided to this program.
*
* @throws Exception If a problem occurs.
*/
public static void main(String[] args)
throws Exception
{
// Set default values for the configurable options.
boolean aggregateThreads = false;
boolean guiMode = false;
boolean verboseOutput = false;
String filename = null;
// Iterate through the command-line parameters and process them.
for (int i=0; i < args.length; i++)
{
if (args[i].equals("-a"))
{
aggregateThreads = true;
}
else if (args[i].equals("-g"))
{
guiMode = true;
}
else if (args[i].equals("-v"))
{
verboseOutput = true;
}
else if (args[i].equals("-f"))
{
filename = args[++i];
}
else if (args[i].equals("-H"))
{
displayUsage();
System.exit(0);
}
else
{
System.err.println("ERROR: Unrecognized argument \"" + args[i] + '"');
displayUsage();
System.exit(1);
}
}
// Make sure that a filename was provided.
if (filename == null)
{
System.err.println("ERROR: No stat file was provided (use -f)");
displayUsage();
System.exit(1);
}
// Get the stat trackers from that file.
StatTracker[] trackers = decodeStats(filename);
if ((trackers == null) || (trackers.length == 0))
{
System.err.println("ERROR: No stat tracker data was found in the " +
"specified file.");
System.exit(1);
}
if (guiMode)
{
PersistentStatViewer statViewer = new PersistentStatViewer(trackers);
return;
}
// Create a list of the unique display names for all the stats.
ArrayList<String> statNames = new ArrayList<String>();
for (int i=0; i < trackers.length; i++)
{
if (! statNames.contains(trackers[i].getDisplayName()))
{
statNames.add(trackers[i].getDisplayName());
}
}
// Iterate through the stat names and display information for each.
for (int i=0; i < statNames.size(); i++)
{
String name = statNames.get(i);
ArrayList<StatTracker> trackerList = new ArrayList<StatTracker>();
for (int j=0; j < trackers.length; j++)
{
if (trackers[j].getDisplayName().equals(name))
{
trackerList.add(trackers[j]);
}
}
StatTracker[] trackersToDisplay;
if (aggregateThreads)
{
StatTracker[] trackersToAggregate = new StatTracker[trackerList.size()];
trackerList.toArray(trackersToAggregate);
if (trackersToAggregate.length <= 1)
{
trackersToDisplay = trackersToAggregate;
}
else
{
StatTracker aggregateTracker = trackersToAggregate[0].newInstance();
aggregateTracker.aggregate(trackersToAggregate);
trackersToDisplay = new StatTracker[] { aggregateTracker };
}
}
else
{
trackersToDisplay = new StatTracker[trackerList.size()];
trackerList.toArray(trackersToDisplay);
}
for (int j=0; j < trackersToDisplay.length; j++)
{
if (verboseOutput)
{
System.out.println(trackersToDisplay[j].getDetailString());
}
else
{
System.out.println(trackersToDisplay[j].getSummaryString());
}
System.out.println();
}
}
}
/**
* Displays usage information for this program.
*/
public static void displayUsage()
{
String EOL = System.getProperty("line.separator");
System.err.println(
"USAGE: java com.slamd.stat.PersistentStatViewer {options}" + EOL +
" where {options} include:" + EOL +
"-f {filename} -- Specifies the path to the persistent stat file to use" + EOL +
"-a -- Indicates that all threads should be aggregated" + EOL +
"-v -- Display verbose statistics" + EOL +
"-g -- Display graphs (in a GUI) rather than values" + EOL +
"-H -- Displays usage information"
);
}
/**
* Creates a new instance of the graphical persistent stat viewer that will be
* used to display information about the provided stat trackers.
*
* @param statTrackers The stat trackers to view.
*/
public PersistentStatViewer(StatTracker[] statTrackers)
{
// Set the stat trackers for this viewer and get the unique names.
this.statTrackers = statTrackers;
// Get the unique names of the stat trackers.
ArrayList<String> nameList = new ArrayList<String>();
for (int i=0; i < statTrackers.length; i++)
{
if (! nameList.contains(statTrackers[i].getDisplayName()))
{
nameList.add(statTrackers[i].getDisplayName());
}
}
String[] statNames = new String[nameList.size()];
nameList.toArray(statNames);
// Create the GUI.
appWindow = new JFrame("SLAMD Persistent Stat Viewer");
appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
appWindow.getContentPane().setLayout(new BorderLayout());
appWindow.setResizable(false);
statListBox = new JComboBox(statNames);
statListBox.setEditable(false);
statListBox.addItemListener(this);
appWindow.getContentPane().add(statListBox, BorderLayout.NORTH);
graphPanel = new PersistentStatGraphPanel(this);
graphPanel.setPreferredSize(new Dimension(640, 480));
appWindow.getContentPane().add(graphPanel, BorderLayout.CENTER);
appWindow.pack();
appWindow.setVisible(true);
graphPanel.repaint();
}
/**
* Indicates that an item change event has occurred and should be processed.
* In this case, it will simply trigger the graph to be redrawn.
*
* @param event The item change event that occurred.
*/
public void itemStateChanged(ItemEvent event)
{
graphPanel.repaint();
}
/**
* Reads the provided data file and decodes it as a set of stat trackers.
*
* @param filename The path to the file containing the encoded stat tracker
* information.
*
* @return The set of stat trackers that have been decoded from the specified
* file.
*
* @throws IOException If a problem occurs while attempting to read from the
* specified file.
*
* @throws SLAMDException If a problem occurs while attempting to decode the
* information as a set of stat trackers.
*/
public static StatTracker[] decodeStats(String filename)
throws IOException, SLAMDException
{
File statFile = new File(filename);
if (! (statFile.exists() && statFile.isFile()))
{
throw new IOException('"' + filename + "\" either does not exist or " +
"is not a regular file.");
}
FileInputStream inputStream = new FileInputStream(filename);
ASN1Reader asn1Reader = new ASN1Reader(inputStream);
ASN1Element element;
try
{
element = asn1Reader.readElement();
}
catch (IOException ioe)
{
try
{
inputStream.close();
} catch (Exception e2) {}
throw ioe;
}
catch (Exception e)
{
try
{
inputStream.close();
} catch (Exception e2) {}
throw new SLAMDException("Unable to decode the contents of the file \"" +
filename + "\" as an ASN.1 element -- " + e, e);
}
asn1Reader.close();
inputStream.close();
ASN1Sequence sequence;
try
{
sequence = element.decodeAsSequence();
}
catch (Exception e)
{
throw new SLAMDException("Unable to decode the ASN.1 element read from " +
"the input file as a sequence -- " + e, e);
}
return StatEncoder.sequenceToTrackers(sequence);
}
}
/**
* This class defines a custom panel that will be used to display graphs of the
* results. This is necessary to ensure that the graphs that are displayed stay
* displayed whenever a repaint event occurs.
*/
class PersistentStatGraphPanel
extends JPanel
{
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 7284912589440159947L;
// The stat viewer with which this panel is associated.
PersistentStatViewer statViewer;
/**
* Creates a new graph panel that will be used with the provided stat viewer.
*
* @param statViewer The stat viewer with which this panel is associated.
*/
public PersistentStatGraphPanel(PersistentStatViewer statViewer)
{
super(true);
this.statViewer = statViewer;
}
/**
* Draws the graph of the statistics based on the selected tracker name.
*
* @param g The graphics context to use to draw the graph.
*/
public void paint(Graphics g)
{
try
{
// Figure out which stat is selected and get the associated trackers.
String statName = (String) statViewer.statListBox.getSelectedItem();
ArrayList<StatTracker> trackerList = new ArrayList<StatTracker>();
for (int i=0; i < statViewer.statTrackers.length; i++)
{
if (statViewer.statTrackers[i].getDisplayName().equals(statName))
{
trackerList.add(statViewer.statTrackers[i]);
}
}
if (trackerList.isEmpty())
{
return;
}
StatTracker[] trackers = new StatTracker[trackerList.size()];
trackerList.toArray(trackers);
StatTracker aggregateTracker = trackers[0].newInstance();
aggregateTracker.aggregate(trackers);
BufferedImage image = aggregateTracker.createGraph(640, 480);
g.drawImage(image, 0, 0, null);
} catch (Exception e) {}
}
}