/* * 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.image.BufferedImage; import java.text.DecimalFormat; import java.util.ArrayList; import com.slamd.asn1.ASN1Element; import com.slamd.asn1.ASN1Integer; import com.slamd.asn1.ASN1OctetString; import com.slamd.asn1.ASN1Sequence; import com.slamd.client.Client; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.job.Job; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; /** * This class defines a stat tracker that can be used to report the relative * ratios of multiple related statistics. * * * @author Neil A. Wilson */ public class StackedValueTracker implements StatTracker { // The array list that will hold the number of values in each interval. private ArrayList<Integer> countList; // The array list that will hold the set of values for this tracker. private ArrayList<double[]> valueList; // Indicates whether the generated graph should be stacked or overlay. private boolean drawAsStackedGraph; // Indicates whether this stat tracker has been started. private boolean hasBeenStarted; // Indicates whether to include the legend in the generated graph. private boolean includeLegend; // Indicates whether to include horizontal grid lines in the generated graph. private boolean includeHorizontalGrid; // Indicates whether to include vertical grid lines in the generated graph. private boolean includeVerticalGrid; // Indicates whether this stat tracker is currently running. private boolean isRunning; // The formatter used to round off decimal values. private DecimalFormat decimalFormat; // The categorized totals maintained for the current interval. private double[] intervalTotals; // The collection interval for this stat tracker. private int collectionInterval; // The length of time this tracker was active. private int duration; // The number of data points collected for the current interval. private int intervalCount; // The time that the current interval should end and the next should start. private long intervalStopTime; // The time that this tracker started. private long startTime; // The time that this tracker stopped. private long stopTime; // The client ID for this tracker. private String clientID; // The display name for this tracker. private String displayName; // The thread ID for this tracker. private String threadID; // The names of the categories associated with this tracker. private String[] categoryNames; /** * Creates a new stacked value tracker intended for use as a placeholder for * decoding purposes. This version of the constructor should not be used * by job classes. */ public StackedValueTracker() { this.clientID = ""; this.threadID = ""; this.displayName = ""; this.collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; drawAsStackedGraph = false; includeLegend = false; includeHorizontalGrid = true; includeVerticalGrid = true; decimalFormat = new DecimalFormat("0.000"); countList = new ArrayList<Integer>(); valueList = new ArrayList<double[]>(); intervalTotals = new double[0]; intervalCount = 0; intervalStopTime = 0; startTime = System.currentTimeMillis(); stopTime = 0; duration = 0; categoryNames = new String[0]; hasBeenStarted = false; isRunning = false; } /** * Creates a new stacked value tracker with the specified information. * * @param clientID The client ID of the client that used this * stat tracker. * @param threadID The thread ID of the thread that used this * stat tracker. * @param displayName The display name to use for this stat tracker. * @param collectionInterval The collection interval in seconds that * should be used for this stat tracker. * @param categoryNames The names of the categories associated with * this stat tracker. */ public StackedValueTracker(String clientID, String threadID, String displayName, int collectionInterval, String[] categoryNames) { this.clientID = clientID; this.threadID = threadID; this.displayName = displayName; this.collectionInterval = collectionInterval; this.categoryNames = categoryNames; drawAsStackedGraph = false; includeLegend = false; includeHorizontalGrid = true; includeVerticalGrid = true; decimalFormat = new DecimalFormat("0.000"); countList = new ArrayList<Integer>(); valueList = new ArrayList<double[]>(); intervalTotals = new double[categoryNames.length]; intervalCount = 0; intervalStopTime = 0; startTime = System.currentTimeMillis(); stopTime = 0; duration = 0; hasBeenStarted = false; isRunning = false; } /** * Creates a new instance of this stat tracker. The new instance should have * the same type, display name, client ID, thread ID, and collection interval * as the stat tracker used to create it. * * @return The new instance of this stat tracker. */ public StatTracker newInstance() { StackedValueTracker tracker = new StackedValueTracker(clientID, threadID, displayName, collectionInterval, categoryNames); tracker.setDrawAsStackedGraph(drawAsStackedGraph); tracker.setIncludeLegend(includeLegend); tracker.setIncludeHorizontalGrid(includeHorizontalGrid); tracker.setIncludeVerticalGrid(includeVerticalGrid); return tracker; } /** * Adds data to this stat tracker. * * @param values The array of values to use for this tracker, with one * element per category name. */ public void addData(double[] values) { long now = System.currentTimeMillis(); // We're in the same interval as the last time the counter was incremented. // Just increment it again. if (now < intervalStopTime) { for (int i=0; i < values.length; i++) { intervalTotals[i] += values[i]; } intervalCount++; } // The previous interval has stopped and a new one has started. Close out // the old one and start the new. Note that if this is an event that // happens infrequently, then multiple intervals could have passed, so make // sure that the appropriate number of intervals are added. else { countList.add(intervalCount); valueList.add(intervalTotals); intervalTotals = values; intervalCount = 1; intervalStopTime += (1000 * collectionInterval); while (intervalStopTime < now) { valueList.add(new double[categoryNames.length]); countList.add(0); intervalStopTime += (1000 * collectionInterval); } } } /** * Indicates that the stat tracker is to start maintaining statistics and that * it should start its internal timer. */ public void startTracker() { // If the tracker has already been started, then print an error message. // Otherwise, indicate that it is started and running. if (hasBeenStarted) { System.err.println("***** WARNING: Multiple calls to start " + "stacked value stat tracker " + displayName); } else { hasBeenStarted = true; isRunning = true; } // Just in case, reset all the counter info. countList = new ArrayList<Integer>(); valueList = new ArrayList<double[]>(); intervalTotals = new double[categoryNames.length]; intervalCount = 0; // Register this tracker with the persistence thread. Client.registerPersistentStatistic(this); // Set the start time and the interval stop time. long now = System.currentTimeMillis(); startTime = now; intervalStopTime = now + (1000 * collectionInterval); } /** * Indicates that the stat tracker that there will not be any more statistics * collection done and that it should stop its internal timer. */ public void stopTracker() { long now = System.currentTimeMillis(); // If the tracker was never started, then print an error message. // Otherwise, indicate that it is no longer running. if (! hasBeenStarted) { System.err.println("***** WARNING: Attempting to stop stacked value " + "stat tracker " + displayName + " without having started it"); } else { isRunning = false; } // If the previous interval had passed since the last update, make sure // that we add the appropriate number of empty intervals. if (intervalStopTime < now) { valueList.add(intervalTotals); countList.add(intervalCount); } while (intervalStopTime < now) { countList.add(0); valueList.add(new double[categoryNames.length]); intervalStopTime += (1000 * collectionInterval); } // Update the stop time to be the time that the last complete interval // ended and calculate the duration. stopTime = intervalStopTime - (1000 * collectionInterval); duration = (int) ((stopTime - startTime) / 1000); } /** * Indicates whether this stat tracker has been started, regardless of whether * it is currently running. * * @return <CODE>true</CODE> if this stat tracker has been started, or * <CODE>false</CODE> if it has not yet been started. */ public boolean hasBeenStarted() { return hasBeenStarted; } /** * Indicates whether this stat tracker is currently running. * * @return <CODE>true</CODE> if this stat tracker is currently running, or * <CODE>false</CODE> if not. */ public boolean isRunning() { return isRunning; } /** * Indicates that the stat tracker should enable real-time statistics * collection. Note that some stat trackers may not support real-time * statistics collection, in which case this method may be ignored. * * @param statReporter The stat-reporter that should be used to report * real-time statistics to the SLAMD server. * @param jobID The job ID of the job that will be reporting the * data. */ public void enableRealTimeStats(RealTimeStatReporter statReporter, String jobID) { // No implementation required. Categorical trackers do not support // real-time statistics collection. } /** * Indicates whether the values in the generated graph should be stacked. * * @param drawAsStackedGraph Indicates whether the values in the generated * graph should be stacked. */ public void setDrawAsStackedGraph(boolean drawAsStackedGraph) { this.drawAsStackedGraph = drawAsStackedGraph; } /** * Indicates the generated graph should include a legend. * * @param includeLegend Indicates whether the generated graph should include * a legend. */ public void setIncludeLegend(boolean includeLegend) { this.includeLegend = includeLegend; } /** * Indicates the generated graph should include horizontal grid lines. * * @param includeHorizontalGrid Indicates whether the generated graph should * include horizontal grid lines. */ public void setIncludeHorizontalGrid(boolean includeHorizontalGrid) { this.includeHorizontalGrid = includeHorizontalGrid; } /** * Indicates the generated graph should include vertical grid lines. * * @param includeVerticalGrid Indicates whether the generated graph should * include vertical grid lines. */ public void setIncludeVerticalGrid(boolean includeVerticalGrid) { this.includeVerticalGrid = includeVerticalGrid; } /** * Retrieves the data associated with this tracker. * * @return The data associated with this tracker. */ public double[][] getIntervalTotals() { double[][] values = new double[valueList.size()][]; valueList.toArray(values); return values; } /** * Specifies the data to use for this tracker. Note that the set of interval * totals and interval counts must have the same number of elements. * * @param intervalTotals The sum of all the values for each interval broken * down by category. * @param intervalCounts The number of occurrences of the tracked event * for each interval. */ public void setIntervalTotals(double[][] intervalTotals, int[] intervalCounts) { valueList = new ArrayList<double[]>(); countList = new ArrayList<Integer>(); for (int i=0; i < intervalTotals.length; i++) { valueList.add(intervalTotals[i]); countList.add(intervalCounts[i]); } duration = intervalTotals.length * collectionInterval; } /** * Retrieves the average values for each interval. * * @return The average values for each interval. */ public double[][] getIntervalAverages() { double[][] values = new double[valueList.size()][categoryNames.length]; for (int i=0; i < valueList.size(); i++) { int count = countList.get(i); double[] data = valueList.get(i); for (int j=0; j < categoryNames.length; j++) { values[i][j] = data[j] / count; } } return values; } /** * Retrieves the number of values in each interval. * * @return The number of values in each interval. */ public int[] getIntervalCounts() { int[] counts = new int[countList.size()]; for (int i=0; i < counts.length; i++) { counts[i] = countList.get(i); } return counts; } /** * Retrieves the average number of data points added per collection interval. * * @return The average number of data points added per collection interval. */ public double getAverageCountPerInterval() { int totalCount = 0; for (int i=0; i < countList.size(); i++) { totalCount += countList.get(i); } return (1.0 * totalCount / countList.size()); } /** * Retrieves the total values for this tracker. * * @return The total values for this tracker. */ public double[] getTotalValues() { double[] data = new double[categoryNames.length]; for (int i=0; i < valueList.size(); i++) { double[] intervalData = valueList.get(i); for (int j=0; j < intervalData.length; j++) { data[j] += intervalData[j]; } } return data; } /** * Retrieves the total value for the specified category for this tracker. * * @param categoryName The name of the category for which to retrieve the * total value. * * @return The total value for the specified category, or * <CODE>Double.NaN</CODE> if no such category exists. */ public double getTotalValue(String categoryName) { int categoryNum = -1; for (int i=0; i < categoryNames.length; i++) { if (categoryNames[i].equalsIgnoreCase(categoryName)) { categoryNum = i; break; } } if (categoryNum < 0) { return Double.NaN; } double totalValue = 0.0; for (int i=0; i < valueList.size(); i++) { totalValue += valueList.get(i)[categoryNum]; } return totalValue; } /** * Retrieves the average values for this tracker. * * @return The average values for this tracker. */ public double[] getAverageValues() { double[] data = new double[categoryNames.length]; int count = 0; for (int i=0; i < valueList.size(); i++) { double[] intervalData = valueList.get(i); for (int j=0; j < intervalData.length; j++) { data[j] += intervalData[j]; } count += countList.get(i); } for (int i=0; i < data.length; i++) { data[i] = (data[i] / count); } return data; } /** * Retrieves the average value for the specified category for this tracker. * * @param categoryName The name of the category for which to retrieve the * average value. * * @return The average value for the specified category, or * <CODE>Double.NaN</CODE> if no such category exists. */ public double getAverageValue(String categoryName) { int categoryNum = -1; for (int i=0; i < categoryNames.length; i++) { if (categoryNames[i].equalsIgnoreCase(categoryName)) { categoryNum = i; break; } } if (categoryNum < 0) { return Double.NaN; } double totalValue = 0.0; int count = 0; for (int i=0; i < valueList.size(); i++) { totalValue += valueList.get(i)[categoryNum]; count += countList.get(i); } return (totalValue / count); } /** * Retrieves the names of the categories used by this tracker. * * @return The names of the categories used by this tracker. */ public String[] getCategoryNames() { return categoryNames; } /** * Retrieves the client ID of the client that used this stat tracker. * * @return The client ID of the client that used this stat tracker. */ public String getClientID() { return clientID; } /** * Specifies the client ID of the client that used this stat tracker. Note * that this should only be used when creating a new stat tracker based on * encoded data and not when using it to collect statistics. * * @param clientID The client ID of the client that used this stat tracker. */ public void setClientID(String clientID) { this.clientID = clientID; } /** * Retrieves the thread ID of the client thread that used this stat tracker. * * @return The thread ID of the client thread that used this stat tracker. */ public String getThreadID() { return threadID; } /** * Specifies the thread ID of the client thread that used this stat tracker. * Note that this should only be used when creating a new stat tracker based * on encoded data and not when using it to collect statistics. * * @param threadID The thread ID of the client thread that used this stat * tracker. */ public void setThreadID(String threadID) { this.threadID = threadID; } /** * Retrieves the user-friendly name associated with this stat tracker. * * @return The user-friendly name associated with this stat tracker. */ public String getDisplayName() { return displayName; } /** * Specifies the display name for this stat tracker. Note that this should * only be used when creating a new stat tracker based on encoded data and not * when using it to collect statistics. * * @param displayName The display name for this stat tracker. */ public void setDisplayName(String displayName) { this.displayName = displayName; } /** * Retrieves the collection interval (in seconds) that will be used for this * stat tracker. * * @return The collection interval (in seconds) that will be used for this * stat tracker. */ public int getCollectionInterval() { return collectionInterval; } /** * Specifies the collection interval in seconds to use fort this stat tracker. * This should not be used while the stat tracker is actively collecting * statistics. * * @param collectionInterval The collection interval in seconds to use for * this stat tracker. */ public void setCollectionInterval(int collectionInterval) { if (collectionInterval > 0) { this.collectionInterval = collectionInterval; } else { this.collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; } } /** * Retrieves the total length of time in seconds that this stat tracker was * capturing statistics. * * @return The total length of time in seconds that this stat tracker was * */ public int getDuration() { return duration; } /** * Specifies the duration for this stat tracker. Note that this should only * be used when creating a new stat tracker based on encoded data and not when * using it to collect statistics. * * @param duration The duration for this stat tracker. */ public void setDuration(int duration) { this.duration = duration; } /** * Indicates whether the user may search for jobs with statistics collected by * this stat tracker. The search will be "greater than" and "less than" some * user-specified value. * * @return <CODE>true</CODE> if statistics collected by this stat tracker * should be searchable, or <CODE>false</CODE> if not. */ public boolean isSearchable() { return false; } /** * Indicates whether the value associated with this stat tracker is greater * than or equal to the provided value. This is only applicable if * <CODE>isSearchable</CODE> returns <CODE>true</CODE>, and what exactly * "the value of this stat tracker" means will be left up to those stat * trackers that are searchable. * * @param value The value against which the value of this stat tracker is to * be compared. * * @return <CODE>true</CODE> if the value of this stat tracker is greater * than or equal to the provided value, or <CODE>false</CODE> if not. */ public boolean isAtLeast(double value) { return false; } /** * Indicates whether the value associated with this stat tracker is less than * or equal to the provided value. This is only applicable if * <CODE>isSearchable</CODE> returns <CODE>true</CODE>, and what exactly * "the value of this stat tracker" means will be left up to those stat * trackers that are searchable. * * @param value The value against which the value of this stat tracker is to * be compared. * * @return <CODE>true</CODE> if the value of this stat tracker is less than * or equal to the provided value, or <CODE>false</CODE> if not. */ public boolean isAtMost(double value) { return false; } /** * Retrieves the value associated with this stat tracker. This is only * applicable if <CODE>isSearchable</CODE> returns <CODE>true</CODE>, and what * exactly "the value associated with this stat tracker" means will be left up * to those stat trackers that are searchable. * * @return The value associated with this stat tracker. */ public double getSummaryValue() { return 0.0; } /** * Retrieves the number of intervals for which data is available for this stat * tracker. * * @return The number of intervals for which data is available for this stat * tracker. */ public int getNumIntervals() { return countList.size(); } /** * Aggregates the information collected by the provided set of stat trackers * into a single tracker that represents the information gathered from the * entire set of data. All of the stat trackers in the provided array must be * of the same type and have the same collection interval and categories as * the instance into which the information will be aggregated. * * @param trackers The set of stat trackers whose data is to be aggregated. */ public void aggregate(StatTracker[] trackers) { if ((trackers == null) || (trackers.length == 0)) { return; } int minIntervals = Integer.MAX_VALUE; for (int i=0; i < trackers.length; i++) { int numIntervals = ((StackedValueTracker) trackers[i]).valueList.size(); if (numIntervals < minIntervals) { minIntervals = numIntervals; } } StackedValueTracker tracker = (StackedValueTracker) trackers[0]; categoryNames = tracker.categoryNames; collectionInterval = tracker.collectionInterval; startTime = tracker.startTime; duration = collectionInterval * minIntervals; stopTime = startTime + (1000 * duration); for (int i=0; i < minIntervals; i++) { double[] trackerValues = tracker.valueList.get(i); Integer trackerCount = tracker.countList.get(i); double[] aggregateValues = new double[trackerValues.length]; System.arraycopy(trackerValues, 0, aggregateValues, 0, trackerValues.length); valueList.add(aggregateValues); countList.add(trackerCount); } for (int i=1; i < trackers.length; i++) { tracker = (StackedValueTracker) trackers[i]; for (int j=0; j < minIntervals; j++) { double[] currentTotals = valueList.get(j); double[] trackerTotals = tracker.valueList.get(j); for (int k=0; k < currentTotals.length; k++) { currentTotals[k] += trackerTotals[k]; } int currentCount = countList.get(j); int trackerCount = tracker.countList.get(j); countList.set(j, (currentCount + trackerCount)); } } } /** * Retrieves brief one-line summary string with cumulative information about * this stat tracker. * * @return A brief one-line summary string containing cumulative information * about this stat tracker. */ public String getSummaryString() { double[] averageValues = getAverageValues(); StringBuilder buf = new StringBuilder(); buf.append(displayName); buf.append(" -- "); for (int i=0; i < categoryNames.length; i++) { if (i > 0) { buf.append("; "); } buf.append("Average["); buf.append(categoryNames[i]); buf.append("]: "); buf.append(decimalFormat.format(averageValues[i])); } return buf.toString(); } /** * Retrieves a detailed (potentially multi-line) string with verbose * information about the data collected by this stat tracker. * * @return A detailed string with verbose information about the data * collected by this stat tracker. */ public String getDetailString() { StringBuilder buf = new StringBuilder(); buf.append(displayName); buf.append(Constants.EOL); double[] averageValues = getAverageValues(); for (int i=0; i < categoryNames.length; i++) { buf.append(" Average["); buf.append(categoryNames[i]); buf.append("]: "); buf.append(decimalFormat.format(averageValues[i])); buf.append(Constants.EOL); } for (int i=0; i < valueList.size(); i++) { double[] values = valueList.get(i); for (int j=0; j < categoryNames.length; j++) { buf.append(" Total["); buf.append(categoryNames[j]); buf.append(", "); buf.append(i); buf.append("]: "); buf.append(decimalFormat.format(values[j])); buf.append(Constants.EOL); } buf.append(" Count["); buf.append(i); buf.append("]: "); buf.append(countList.get(i)); buf.append(Constants.EOL); } return buf.toString(); } /** * Retrieves a version of the summary information for this stat tracker * formatted for display in an HTML document. * * @return An HTML version of the summary data for this stat tracker. */ public String getSummaryHTML() { StringBuilder buf = new StringBuilder(); double[] averageValues = getAverageValues(); for (int i=0; i < categoryNames.length; i++) { buf.append("<TABLE BORDER=\"1\">"); buf.append(Constants.EOL); buf.append(" <TR>"); buf.append(Constants.EOL); buf.append(" <TD><B>Average "); buf.append(categoryNames[i]); buf.append("</B></TD>"); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); buf.append(" <TR>"); buf.append(Constants.EOL); buf.append(" <TD>"); buf.append(decimalFormat.format(averageValues[i])); buf.append("</TD>"); buf.append(Constants.EOL); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); buf.append("</TABLE>"); buf.append(Constants.EOL); } buf.append("<TABLE BORDER=\"1\">"); buf.append(Constants.EOL); buf.append(" <TR>"); buf.append(Constants.EOL); buf.append(" <TD><B>Average Count/Interval</TD>"); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); buf.append(" <TR>"); buf.append(Constants.EOL); buf.append(" <TD>"); buf.append(decimalFormat.format(getAverageCountPerInterval())); buf.append("</TD>"); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); buf.append("</TABLE>"); buf.append(Constants.EOL); return buf.toString(); } /** * Retrieves a version of the verbose information for this stat tracker, * formatted for display in an HTML document. * * @return An HTML version of the verbose data for this stat tracker. */ public String getDetailHTML() { StringBuilder buf = new StringBuilder(); buf.append("<TABLE BORDER=\"1\">"); buf.append(Constants.EOL); buf.append(" <TR>"); buf.append(Constants.EOL); buf.append(" <TD><B>Interval</B></TD>"); buf.append(Constants.EOL); for (int i=0; i < categoryNames.length; i++) { buf.append(" <TD><B>"); buf.append(categoryNames[i]); buf.append("</B></TD>"); buf.append(Constants.EOL); } buf.append(" <TD><B>Count</B></TD>"); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); for (int i=0; i < valueList.size(); i++) { buf.append(" <TD>"); buf.append(i); buf.append("</TD>"); buf.append(Constants.EOL); double[] values = valueList.get(i); for (int j=0; j < values.length; j++) { buf.append(" <TD>"); buf.append(decimalFormat.format(values[j])); buf.append("</TD>"); buf.append(Constants.EOL); } buf.append(" <TD>"); buf.append(countList.get(i)); buf.append("</TD>"); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); } buf.append(" <TR>"); buf.append(Constants.EOL); buf.append(" <TD>Average</TD>"); buf.append(Constants.EOL); double[] averageValues = getAverageValues(); for (int i=0; i < averageValues.length; i++) { buf.append(" <TD>"); buf.append(decimalFormat.format(averageValues[i])); buf.append("</TD>"); buf.append(Constants.EOL); } buf.append(" <TD>"); buf.append(decimalFormat.format(getAverageCountPerInterval())); buf.append("</TD>"); buf.append(Constants.EOL); buf.append(" </TR>"); buf.append(Constants.EOL); buf.append("</TABLE>"); buf.append(Constants.EOL); return buf.toString(); } /** * Retrieves a string array with the labels corresponding to the values * returned from the <CODE>getSummaryData</CODE> method. * * @return A string array with the labels corresponding to the values * returned from the <CODE>getSummaryData</CODE> method. */ public String[] getSummaryLabels() { String[] returnArray = new String[categoryNames.length+1]; for (int i=0; i < categoryNames.length; i++) { returnArray[i] = "Avg " + categoryNames[i]; } returnArray[categoryNames.length] = "Avg Count/Interval"; return returnArray; } /** * Retrieves the summary string data for this stat tracker as separate values. * * @return The summary string data for this stat tracker as separate values. */ public String[] getSummaryData() { String[] returnArray = new String[categoryNames.length+1]; double[] values = getAverageValues(); for (int i=0; i < values.length; i++) { returnArray[i] = decimalFormat.format(values[i]); } returnArray[values.length] = decimalFormat.format(getAverageCountPerInterval()); return returnArray; } /** * Retrieves the raw data associated with this stat tracker in a form that * can be easily converted for export to CSV, tab-delimited text, or some * other format for use in an external application. There should be one value * per "cell". * * @param includeLabels Indicates whether the information being exported * should contain labels. * * @return The raw data associated with this stat tracker in a form that can * be exported to some external form. */ public String[][] getDataForExport(boolean includeLabels) { if (includeLabels) { String[][] returnArray = new String[valueList.size()+1][]; returnArray[0] = new String[categoryNames.length + 2]; returnArray[0][0] = "Interval"; for (int i=0; i < categoryNames.length; i++) { returnArray[0][i+1] = categoryNames[i]; } returnArray[0][categoryNames.length+1] = "Count"; for (int i=0; i < valueList.size(); i++) { returnArray[i+1] = new String[categoryNames.length+2]; returnArray[i+1][0] = String.valueOf(i+1); double[] values = valueList.get(i); for (int j=0; j < values.length; j++) { returnArray[i+1][j+1] = decimalFormat.format(values[j]); } returnArray[i+1][values.length+1] = String.valueOf(countList.get(i)); } return returnArray; } else { String[][] returnArray = new String[valueList.size()][]; for (int i=0; i < valueList.size(); i++) { returnArray[i] = new String[categoryNames.length+2]; returnArray[i][0] = String.valueOf(i+1); double[] values = valueList.get(i); for (int j=0; j < values.length; j++) { returnArray[i][j+1] = decimalFormat.format(values[j]); } returnArray[i][values.length+1] = String.valueOf(countList.get(i)); } return returnArray; } } /** * Encodes the data collected by this tracker into a byte array that may be * transferred over the network or written out to persistent storage. * * @return The data collected by this tracker encoded as a byte array. */ public byte[] encode() { // The format used for stacked value trackers: // StackedValueTracker ::= SEQUENCE { // categoryNames SEQUENCE OF OCTET STRING, // categoryData SEQUENCE OF StackedValueData // graphConfig SEQUENCE OF OCTET STRING } // // StackedValueData ::= SEQUENCE { // dataValues SEQUENCE OF OCTET STRING, // count INTEGER } ASN1Element[] nameElements = new ASN1Element[categoryNames.length]; for (int i=0; i < nameElements.length; i++) { nameElements[i] = new ASN1OctetString(categoryNames[i]); } ASN1Element[] dataElements = new ASN1Element[valueList.size()]; for (int i=0; i < dataElements.length; i++) { double[] values = valueList.get(i); int count = countList.get(i); ASN1Element[] valueElements = new ASN1Element[values.length]; for (int j=0; j < values.length; j++) { valueElements[j] = new ASN1OctetString(String.valueOf(values[j])); } ASN1Element[] sequenceElements = new ASN1Element[] { new ASN1Sequence(valueElements), new ASN1Integer(count) }; dataElements[i] = new ASN1Sequence(sequenceElements); } ASN1Element[] graphConfigElements = new ASN1Element[] { new ASN1OctetString(Constants.SERVLET_PARAM_DRAW_AS_STACKED_GRAPH + "=" + String.valueOf(drawAsStackedGraph)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_LABELS + "=" + String.valueOf(includeLegend)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID + "=" + String.valueOf(includeHorizontalGrid)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID + "=" + String.valueOf(includeVerticalGrid)) }; ASN1Element[] trackerElements = new ASN1Element[] { new ASN1Sequence(nameElements), new ASN1Sequence(dataElements), new ASN1Sequence(graphConfigElements) }; return (new ASN1Sequence(trackerElements)).encode(); } /** * Decodes the provided data and uses it as the data for this stat tracker. * * @param encodedData The encoded version of the data to use for this * stat tracker. * * @throws SLAMDException If the provided data cannot be decoded and used * with this stat tracker. */ public void decode(byte[] encodedData) throws SLAMDException { try { ASN1Element[] trackerElements = ASN1Element.decodeAsSequence(encodedData).getElements(); ASN1Element[] nameElements = trackerElements[0].decodeAsSequence().getElements(); categoryNames = new String[nameElements.length]; for (int i=0; i < nameElements.length; i++) { categoryNames[i] = nameElements[i].decodeAsOctetString().getStringValue(); } ASN1Element[] sequenceElements = trackerElements[1].decodeAsSequence().getElements(); for (int i=0; i < sequenceElements.length; i++) { ASN1Element[] dataElements = sequenceElements[i].decodeAsSequence().getElements(); ASN1Element[] valueElements = dataElements[0].decodeAsSequence().getElements(); double[] values = new double[valueElements.length]; for (int j=0; j < valueElements.length; j++) { values[j] = Double.parseDouble( valueElements[j].decodeAsOctetString().getStringValue()); } int count = dataElements[1].decodeAsInteger().getIntValue(); valueList.add(values); countList.add(count); } if (trackerElements.length > 2) { ASN1Element[] configElements = trackerElements[2].decodeAsSequence().getElements(); for (int i=0; i < configElements.length; i++) { String elementStr = configElements[i].decodeAsOctetString().getStringValue(); int equalPos = elementStr.indexOf('='); String name = elementStr.substring(0, equalPos); boolean value = Boolean.valueOf(elementStr.substring(equalPos+1)); if (name.equals(Constants.SERVLET_PARAM_DRAW_AS_STACKED_GRAPH)) { drawAsStackedGraph = value; } else if (name.equals(Constants.SERVLET_PARAM_INCLUDE_LABELS)) { includeLegend = value; } else if (name.equals(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID)) { includeHorizontalGrid = value; } else if (name.equals(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID)) { includeVerticalGrid = value; } } } } catch (Exception e) { throw new SLAMDException("Unable to decode data: " + e, e); } } /** * Retrieves the set of parameters that may be specified to customize the * graph that is generated based on the statistical information in the stat * trackers. * * @param job The job containing the statistical information to be graphed. * * @return The set of parameters that may be used to customize the graph that * is generated. */ public ParameterList getGraphParameterStubs(Job job) { BooleanParameter drawAsStackedGraphParameter = new BooleanParameter(Constants.SERVLET_PARAM_DRAW_AS_STACKED_GRAPH, "Draw as Stacked Area Graph", "Indicates whether the data should be graphed " + "as lines for each category or as a stacked " + "area graph", drawAsStackedGraph); BooleanParameter includeLegendParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS, "Include Legend", "Indicates whether the graph generated should " + "include a legend that shows the categories " + "included in the graph.", includeLegend); BooleanParameter includeHGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID, "Include Horizontal Grid Lines", "Indicates whether the graph generated should " + "include horizontal grid lines.", includeHorizontalGrid); BooleanParameter includeVGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID, "Include Vertical Grid Lines", "Indicates whether the graph generated should " + "include vertical grid lines.", includeVerticalGrid); Parameter[] parameters = new Parameter[] { drawAsStackedGraphParameter, includeLegendParameter, includeHGridParameter, includeVGridParameter }; return new ParameterList(parameters); } /** * Retrieves the set of parameters that may be specified to customize the * graph that is generated based on the resource monitor information in the * stat trackers. * * @param job The job containing the resource monitor information to be * graphed. * * @return The set of parameters that may be used to customize the graph that * is generated. */ public ParameterList getMonitorGraphParameterStubs(Job job) { BooleanParameter drawAsStackedGraphParameter = new BooleanParameter(Constants.SERVLET_PARAM_DRAW_AS_STACKED_GRAPH, "Draw as Stacked Area Graph", "Indicates whether the data should be graphed " + "as lines for each category or as a stacked " + "area graph", true); BooleanParameter includeLegendParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS, "Include Legend", "Indicates whether the graph generated should " + "include a legend that shows the categories " + "included in the graph.", includeLegend); BooleanParameter includeHGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID, "Include Horizontal Grid Lines", "Indicates whether the graph generated should " + "include horizontal grid lines.", includeHorizontalGrid); BooleanParameter includeVGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID, "Include Vertical Grid Lines", "Indicates whether the graph generated should " + "include vertical grid lines.", includeVerticalGrid); Parameter[] parameters = new Parameter[] { drawAsStackedGraphParameter, includeLegendParameter, includeHGridParameter, includeVGridParameter }; return new ParameterList(parameters); } /** * Retrieves the set of parameters that may be specified to customize the * graph that is generated based on the statistical information in the stat * trackers. * * @param jobs The job containing the statistical information to be compared * and graphed. * * @return The set of parameters that may be used to customize the graph that * is generated. */ public ParameterList getGraphParameterStubs(Job[] jobs) { BooleanParameter includeLegendParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS, "Include Legend", "Indicates whether the graph generated should " + "include a legend that shows the categories " + "included in the graph.", includeLegend); BooleanParameter includeHGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID, "Include Horizontal Grid Lines", "Indicates whether the graph generated should " + "include horizontal grid lines.", includeHorizontalGrid); Parameter[] parameters = new Parameter[] { includeLegendParameter, includeHGridParameter, }; return new ParameterList(parameters); } /** * Retrieves the data that represents the points in a line graph for this * stat tracker. This is only applicable if <CODE>isSearchable</CODE> * returns <CODE>true</CODE>. * * @return The data that represents the points in a line graph for this stat * tracker, or <CODE>null</CODE> if that data is not available. */ public double[] getGraphData() { return null; } /** * Retrieves the label that should be included along the vertical axis in a * line graph for this stat tracker. This is only applicable if * <CODE>isSearchable</CODE> returns <CODE>true</CODE>. * * @return The label that should be included along the vertical axis in a * line graph for this stat tracker, or <CODE>null</CODE> if that * data is not applicable. */ public String getAxisLabel() { return "Average Value"; } /** * Creates a graph that visually depicts the information in the provided set * of stat trackers. The provided stat trackers must be of the same type as * this stat tracker. * * @param job The job containing the statistical information to be * graphed. * @param width The width in pixels of the graph to create. * @param height The height in pixels of the graph to create. * @param parameters The set of parameters that may be used to customize * the graph that is generated. * * @return The graph created from the statistical information in the provided * job. */ public BufferedImage createGraph(Job job, int width, int height, ParameterList parameters) { boolean stackedGraph = drawAsStackedGraph; boolean includeHGrid = includeHorizontalGrid; boolean includeVGrid = includeVerticalGrid; String graphTitle = displayName + " for Job " + job.getJobID(); String legendTitle = "Category"; BooleanParameter drawAsStackedGraphParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_DRAW_AS_STACKED_GRAPH); if (drawAsStackedGraphParameter != null) { stackedGraph = drawAsStackedGraphParameter.getBooleanValue(); } BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter includeHGridParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID); if (includeHGridParameter != null) { includeHGrid = includeHGridParameter.getBooleanValue(); } BooleanParameter includeVGridParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_VERT_GRID); if (includeVGridParameter != null) { includeVGrid = includeVGridParameter.getBooleanValue(); } StatTracker[] trackers = job.getStatTrackers(displayName); StackedValueTracker tracker = new StackedValueTracker("", "", displayName, trackers[0].getCollectionInterval(), ((StackedValueTracker) trackers[0]).categoryNames); tracker.aggregate(trackers); double[][] averageValues = tracker.getIntervalAverages(); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(true); grapher.setIncludeLegend(includeLegend, legendTitle); grapher.setVerticalAxisTitle("Average Value"); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIgnoreZeroValues(false); for (int i=0; i < categoryNames.length; i++) { double[] values = new double[averageValues.length]; for (int j=0; j < values.length; j++) { values[j] = averageValues[j][i]; } grapher.addDataSet(values, job.getCollectionInterval(), categoryNames[i]); } grapher.setIncludeAverage(false); grapher.setIncludeRegression(false); if (stackedGraph) { return grapher.generateStackedAreaGraph(); } else { return grapher.generateLineGraph(); } } /** * Creates a graph that visually depicts the information collected by resource * monitors associated with the provided job. * * @param job The job containing the statistical information to be * graphed. * @param width The width in pixels of the graph to create. * @param height The height in pixels of the graph to create. * @param parameters The set of parameters that may be used to customize * the graph that is generated. * * @return The graph created from the statistical information in the provided * job. */ public BufferedImage createMonitorGraph(Job job, int width, int height, ParameterList parameters) { boolean stackedGraph = drawAsStackedGraph; boolean includeHGrid = includeHorizontalGrid; boolean includeVGrid = includeVerticalGrid; String graphTitle = displayName; String legendTitle = "Category"; BooleanParameter stackedParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_DRAW_AS_STACKED_GRAPH); if (stackedParameter != null) { stackedGraph = stackedParameter.getBooleanValue(); } BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter includeHGridParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID); if (includeHGridParameter != null) { includeHGrid = includeHGridParameter.getBooleanValue(); } BooleanParameter includeVGridParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_VERT_GRID); if (includeVGridParameter != null) { includeVGrid = includeVGridParameter.getBooleanValue(); } StatTracker[] trackers = job.getResourceStatTrackers(displayName); StackedValueTracker tracker = new StackedValueTracker("", "", displayName, trackers[0].getCollectionInterval(), ((StackedValueTracker) trackers[0]).categoryNames); tracker.aggregate(trackers); double[][] averageValues = tracker.getIntervalAverages(); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(true); grapher.setIncludeLegend(includeLegend, legendTitle); grapher.setVerticalAxisTitle("Average Value"); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIgnoreZeroValues(false); for (int i=0; i < categoryNames.length; i++) { double[] values = new double[averageValues.length]; for (int j=0; j < values.length; j++) { values[j] = averageValues[j][i]; } grapher.addDataSet(values, job.getCollectionInterval(), categoryNames[i]); } grapher.setIncludeAverage(false); grapher.setIncludeRegression(false); if (stackedGraph) { return grapher.generateStackedAreaGraph(); } else { return grapher.generateLineGraph(); } } /** * Creates a graph that visually depicts the information in the provided set * of stat trackers. The provided stat trackers must be the of the same type * as this stat tracker and must have exactly the same set of categories in * the same order. * * @param jobs The job containing the statistical information to be * compared and graphed. * @param width The width in pixels of the graph to create. * @param height The height in pixels of the graph to create. * @param parameters The set of parameters that may be used to customize the * graph that is generated. * * @return The graph created from the statistical information in the provided * job. */ public BufferedImage createGraph(Job[] jobs, int width, int height, ParameterList parameters) { boolean includeHGrid = includeHorizontalGrid; BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter includeHGridParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID); if (includeHGridParameter != null) { includeHGrid = includeHGridParameter.getBooleanValue(); } String graphTitle = "Comparison of " + displayName; String legendTitle = "Category"; StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(true); grapher.setIncludeLegend(includeLegend, legendTitle); grapher.setVerticalAxisTitle("Average Value"); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(false); grapher.setIgnoreZeroValues(false); for (int i=0; i < jobs.length; i++) { StatTracker[] trackers = jobs[i].getStatTrackers(displayName); StackedValueTracker tracker = new StackedValueTracker("", "", displayName, trackers[0].getCollectionInterval(), ((StackedValueTracker) trackers[0]).categoryNames); tracker.aggregate(trackers); double[] averageValues = tracker.getAverageValues(); String dataSetName = jobs[i].getJobDescription(); if ((dataSetName == null) || (dataSetName.length() == 0)) { dataSetName = jobs[i].getJobID(); } grapher.addStackedBarGraphDataSet(dataSetName, tracker.categoryNames, averageValues); } return grapher.generateStackedBarGraph(); } /** * Creates a graph that visually depicts the information in this stat tracker * using all the default settings. * * @param width The width in pixels of the graph to create. * @param height The height in pixels of the graph to create. * * @return The graph created from this stat tracker. */ public BufferedImage createGraph(int width, int height) { StatGrapher grapher = new StatGrapher(width, height, displayName); grapher.setBaseAtZero(true); grapher.setIncludeLegend(true, "Category"); grapher.setVerticalAxisTitle("Average Value"); grapher.setIncludeHorizontalGrid(true); grapher.setIncludeVerticalGrid(true); grapher.setIgnoreZeroValues(false); double[][] averageValues = getIntervalAverages(); for (int i=0; i < categoryNames.length; i++) { double[] values = new double[averageValues.length]; for (int j=0; j < values.length; j++) { values[j] = averageValues[j][i]; } grapher.addDataSet(values, collectionInterval, categoryNames[i]); } return grapher.generateStackedAreaGraph(); } }