/* * 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.text.SimpleDateFormat; import java.util.Date; import com.slamd.asn1.ASN1Element; 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 track events that occur * periodically. It records the time and magnitude of each occurrence. * * * @author Neil A. Wilson */ public class PeriodicEventTracker implements StatTracker { /** * The initial size that will be used for the value arrays for this stat * tracker, as well as the size increment that will be used when the arrays * get full. */ public static final int ARRAY_SIZE_INCREMENT = 1000; // Indicates whether the generated graph should be based at zero or at the // lowest actual value. private boolean baseAtZero; // Indicates whether the generated graph should be flat between data points. private boolean flatBetweenPoints; // Indicates whether this stat tracker has been started. private boolean hasBeenStarted; // Indicates whether the generated graph should include an average line. private boolean includeAverage; // Indicates whether the generated graph should include a horizontal grid. private boolean includeHorizontalGrid; // Indicates whether the generated graph should include a legend. private boolean includeLegend; // Indicates whether the generated graph should include a vertical grid. private boolean includeVerticalGrid; // Indicates whether this stat tracker is currently running. private boolean isRunning; // The decimal format that will be used to format floating-point values. private DecimalFormat decimalFormat; // The total of all values provided. private double totalValue; // The magnitudes for the tracked event. private double[] eventValues; // The length of time in seconds that this tracker was active. private int duration; // The statistics collection interval to use for this stat tracker. This // isn't important for normal operations, but it may be needed for other // things like real-time reporting. private int collectionInterval; // The number of times that the tracked event has occurred. private int numOccurrences; // The time that this stat tracker started collecting statistics. private long startTime; // The time that this stat tracker stopped collecting statistics. private long stopTime; // The times that the tracked event occurred. private long[] eventTimes; // The date formatter that will be used to format dates. private SimpleDateFormat dateFormat; // The ID assigned to the client running this stat tracker. private String clientID; // The display name for this stat tracker; private String displayName; // The ID assigned to the thread running this stat tracker. private String threadID; /** * Creates a new instance of this stat intended for use as a placeholder for * decoding purposes. This version of the constructor should not be used * by job classes. */ public PeriodicEventTracker() { this.clientID = ""; this.threadID = ""; this.displayName = ""; this.collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; baseAtZero = true; flatBetweenPoints = true; includeAverage = false; includeHorizontalGrid = true; includeVerticalGrid = true; includeLegend = false; totalValue = 0.0; eventValues = new double[ARRAY_SIZE_INCREMENT]; eventTimes = new long[ARRAY_SIZE_INCREMENT]; numOccurrences = 0; startTime = System.currentTimeMillis(); stopTime = startTime; duration = 0; decimalFormat = new DecimalFormat("0.000"); dateFormat = new SimpleDateFormat(Constants.DISPLAY_DATE_FORMAT); hasBeenStarted = false; isRunning = false; } /** * Creates a new instance of this stat tracker based on the provided * 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. */ public PeriodicEventTracker(String clientID, String threadID, String displayName, int collectionInterval) { this.clientID = clientID; this.threadID = threadID; this.displayName = displayName; this.collectionInterval = collectionInterval; baseAtZero = true; flatBetweenPoints = true; includeAverage = false; includeHorizontalGrid = true; includeVerticalGrid = true; includeLegend = false; totalValue = 0.0; eventValues = new double[ARRAY_SIZE_INCREMENT]; eventTimes = new long[ARRAY_SIZE_INCREMENT]; numOccurrences = 0; startTime = System.currentTimeMillis(); stopTime = startTime; duration = 0; decimalFormat = new DecimalFormat("0.000"); dateFormat = new SimpleDateFormat(Constants.DISPLAY_DATE_FORMAT); 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() { PeriodicEventTracker tracker = new PeriodicEventTracker(clientID, threadID, displayName, collectionInterval); tracker.setFlatBetweenPoints(flatBetweenPoints); tracker.setBaseAtZero(baseAtZero); tracker.setIncludeAverage(includeAverage); tracker.setIncludeHorizontalGrid(includeHorizontalGrid); tracker.setIncludeVerticalGrid(includeVerticalGrid); return tracker; } /** * 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 " + "periodic event stat tracker " + displayName); } else { hasBeenStarted = true; isRunning = true; } // Register this tracker with the persistence thread. Client.registerPersistentStatistic(this); startTime = System.currentTimeMillis(); eventTimes = new long[ARRAY_SIZE_INCREMENT]; eventValues = new double[ARRAY_SIZE_INCREMENT]; numOccurrences = 0; } /** * 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() { stopTime = 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 periodic event " + "stat tracker " + displayName + " without having started it"); } else { isRunning = false; } long[] newTimes = new long[numOccurrences]; System.arraycopy(eventTimes, 0, newTimes, 0, numOccurrences); eventTimes = newTimes; double[] newValues = new double[numOccurrences]; System.arraycopy(eventValues, 0, newValues, 0, numOccurrences); eventValues = newValues; 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 action required. Real-time stat reporting is currently not supported // for this tracker. } /** * Updates the tracker with the provided value. The current time will be used * as the time of the update. * * @param value The magnitude for the update. */ public void update(double value) { update(System.currentTimeMillis(), value); } /** * Updates the tracker with the provided time and value. * * @param time The time for the update. * @param value The magnitude for the update. */ public void update(long time, double value) { if (numOccurrences >= eventTimes.length) { long[] newTimes = new long[eventTimes.length + ARRAY_SIZE_INCREMENT]; System.arraycopy(eventTimes, 0, newTimes, 0, numOccurrences); eventTimes = newTimes; double[] newValues = new double[eventValues.length + ARRAY_SIZE_INCREMENT]; System.arraycopy(eventValues, 0, newValues, 0, numOccurrences); eventValues = newValues; } eventTimes[numOccurrences] = time; eventValues[numOccurrences] = value; numOccurrences++; totalValue += value; } /** * Specifies the data associated with this tracker. Note that the number of * values in the time array must equal the number of elements in the value * array. * * @param eventTimes The times of the event occurrences. * @param eventValues The values associated with the event occurrences. */ public void setEventData(long[] eventTimes, double[] eventValues) { this.eventTimes = eventTimes; this.eventValues = eventValues; numOccurrences = eventTimes.length; totalValue = 0.0; for (int i=0; i < numOccurrences; i++) { totalValue += eventValues[i]; } } /** * Indicates whether graphs generated from this tracker should have a lower * range based at zero or the actual collected values. * * @param baseAtZero Indicates whether graphs generated from this tracker * should have a lower range based at zero. */ public void setBaseAtZero(boolean baseAtZero) { this.baseAtZero = baseAtZero; } /** * Indicates whether graphs generated from this tracker should have lines that * are flat between data points rather than directly connecting the points. * * @param flatBetweenPoints Indicates whether graphs generated from this * tracker should have lines that are flat between * data points. */ public void setFlatBetweenPoints(boolean flatBetweenPoints) { this.flatBetweenPoints = flatBetweenPoints; } /** * Indicates whether graphs generated from this tracker should include an * average line. * * @param includeAverage Indicates whether graphs generated from this * tracker should include an average line. */ public void setIncludeAverage(boolean includeAverage) { this.includeAverage = includeAverage; } /** * Indicates whether graphs generated from this tracker should include * horizontal grid lines. * * @param includeHorizontalGrid Indicates whether graphs generated from this * tracker should include horizontal grid * lines. */ public void setIncludeHorizontalGrid(boolean includeHorizontalGrid) { this.includeHorizontalGrid = includeHorizontalGrid; } /** * Indicates whether graphs generated from this tracker should include * vertical grid lines. * * @param includeVerticalGrid Indicates whether graphs generated from this * tracker should include vertical grid lines. */ public void setIncludeVerticalGrid(boolean includeVerticalGrid) { this.includeVerticalGrid = includeVerticalGrid; } /** * Indicates whether graphs generated from this tracker should include a * legend. * * @param includeLegend Indicates whether graphs generated from this tracker * should include a legend. */ public void setIncludeLegend(boolean includeLegend) { this.includeLegend = includeLegend; } /** * Retrieves the number of times the tracked event occurred. * * @return The number of times the tracked event occurred. */ public int getNumOccurrences() { return numOccurrences; } /** * Retrieves the sum of all the values of the tracked event. * * @return The sum of all the values of the tracked event. */ public double getTotalValue() { return totalValue; } /** * Retrieves the average of all the values of the tracked event. * * @return The average of all the values of the tracked event. */ public double getAverageValue() { if (numOccurrences > 0) { return (totalValue / numOccurrences); } else { return 0.0; } } /** * Retrieves the maximum value of the tracked event. * * @return The maximum value of the tracked event. */ public double getMaxValue() { if (numOccurrences > 0) { double maxValue = eventValues[0]; for (int i=1; i < numOccurrences; i++) { if (eventValues[i] > maxValue) { maxValue = eventValues[i]; } } return maxValue; } else { return 0.0; } } /** * Retrieves the maximum value of the tracked event. * * @return The maximum value of the tracked event. */ public double getMinValue() { if (numOccurrences > 0) { double minValue = eventValues[0]; for (int i=1; i < numOccurrences; i++) { if (eventValues[i] < minValue) { minValue = eventValues[i]; } } return minValue; } else { return 0.0; } } /** * Retrieves the average length of time in milliseconds between occurrences * of the tracked event. * * @return The average length of time in milliseconds between occurrences of * the tracked event. */ public double getAverageTimeBetweenOccurrences() { if (numOccurrences == 0) { return 0.0; } return (1.0 * (eventTimes[numOccurrences-1] - eventTimes[0]) / (numOccurrences-1)); } /** * Retrieves an array containing the time that each occurrence of the tracked * event took place. * * @return An array containing the time that each occurrence of the tracked * event took place. */ public long[] getEventTimes() { long[] times = new long[numOccurrences]; System.arraycopy(eventTimes, 0, times, 0, numOccurrences); return times; } /** * Retrieves an array whose elements are the values associated with each * occurrence of the tracked event. * * @return An array whose elements are the values associated with each * occurrence of the tracked event. */ public double[] getEventValues() { double[] values = new double[numOccurrences]; System.arraycopy(eventValues, 0, values, 0, numOccurrences); return values; } /** * 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 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 collectionInterval The collection interval in seconds to use for * this stat tracker. */ public void setCollectionInterval(int collectionInterval) { this.collectionInterval = collectionInterval; } /** * 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 * capturing statistics. */ 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() { // Searching will not be allowed because this tracker is not interval-based. 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 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() { // This tracker doesn't really work on intervals, but this is close enough. return numOccurrences; } /** * 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 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; } else if (trackers.length == 1) { PeriodicEventTracker tracker = (PeriodicEventTracker) trackers[0]; numOccurrences = tracker.numOccurrences; startTime = tracker.startTime; stopTime = tracker.stopTime; duration = (int) ((stopTime - startTime) / 1000); totalValue = tracker.totalValue; eventTimes = new long[numOccurrences]; System.arraycopy(tracker.eventTimes, 0, eventTimes, 0, numOccurrences); eventValues = new double[numOccurrences]; System.arraycopy(tracker.eventValues, 0, eventValues, 0, numOccurrences); clientID = tracker.clientID; threadID = tracker.threadID; displayName = tracker.displayName; collectionInterval = tracker.collectionInterval; } else { PeriodicEventTracker[] pTrackers = new PeriodicEventTracker[trackers.length]; for (int i=0; i < trackers.length; i++) { pTrackers[i] = (PeriodicEventTracker) trackers[i]; } numOccurrences = pTrackers[0].numOccurrences; startTime = pTrackers[0].startTime; stopTime = pTrackers[0].stopTime; totalValue = pTrackers[0].totalValue; for (int i=1; i < trackers.length; i++) { numOccurrences += pTrackers[i].numOccurrences; totalValue += pTrackers[i].totalValue; if (pTrackers[i].startTime < startTime) { startTime = pTrackers[i].startTime; } if (pTrackers[i].stopTime > stopTime) { stopTime = pTrackers[i].stopTime; } } duration = (int) ((stopTime - startTime) / 1000); eventTimes = new long[numOccurrences]; eventValues = new double[numOccurrences]; int[] positions = new int[pTrackers.length]; for (int i=0; i < numOccurrences; i++) { double eventValue = 0.0; int pos = -1; long eventTime = -1; for (int j=0; j < pTrackers.length; j++) { if (positions[j] < pTrackers[j].numOccurrences) { if ((eventTime < 0) || (pTrackers[j].eventTimes[positions[j]] < eventTime)) { eventTime = pTrackers[j].eventTimes[positions[j]]; eventValue = pTrackers[j].eventValues[positions[j]]; pos = j; } } } eventTimes[i] = eventTime; eventValues[i] = eventValue; positions[pos]++; } } } /** * 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() { if (numOccurrences == 0) { return 0.0; } return (totalValue / numOccurrences); } /** * 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() { return displayName + " -- Occurrences: " + numOccurrences + "; Avg Value: " + decimalFormat.format(getAverageValue()) + "; Total Value: " + decimalFormat.format(totalValue) + "; Avg Time Between Occurrences (ms): " + decimalFormat.format(getAverageTimeBetweenOccurrences()); } /** * 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 buffer = new StringBuilder(); buffer.append("<TABLE BORDER=\"1\">" + Constants.EOL); buffer.append(" <TR>" + Constants.EOL); buffer.append(" <TD><B>Occurrences</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Avg Value</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Total Value</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Avg Time Between Occurrences</B></TD>" + Constants.EOL); buffer.append(" </TR>" + Constants.EOL); buffer.append(" <TR>" + Constants.EOL); buffer.append(" <TD>" + numOccurrences + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(getAverageValue()) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(totalValue) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(getAverageTimeBetweenOccurrences()) + "</TD>" + Constants.EOL); buffer.append(" </TR>" + Constants.EOL); buffer.append("</TABLE>" + Constants.EOL); return buffer.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 buffer = new StringBuilder(); buffer.append(displayName + Constants.EOL); buffer.append("Occurrences: " + numOccurrences + Constants.EOL); buffer.append("Average Value: " + decimalFormat.format(getAverageValue()) + Constants.EOL); buffer.append("Total Value: " + decimalFormat.format(totalValue) + Constants.EOL); buffer.append("Maximum Value: " + decimalFormat.format(getMaxValue()) + Constants.EOL); buffer.append("Minimum Value: " + decimalFormat.format(getMinValue()) + Constants.EOL); buffer.append("Average Time Between Occurrences (ms): " + decimalFormat.format(getAverageTimeBetweenOccurrences()) + Constants.EOL); for (int i=0; i < numOccurrences; i++) { buffer.append(dateFormat.format(new Date(eventTimes[i])) + ": " + decimalFormat.format(eventValues[i]) + Constants.EOL); } return buffer.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 buffer = new StringBuilder(); buffer.append("<TABLE BORDER=\"1\">" + Constants.EOL); buffer.append(" <TR>" + Constants.EOL); buffer.append(" <TD><B>Occurrences</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Avg Value</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Total Value</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Max Value</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Min Value</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Avg Time Between Occurrences</B></TD>" + Constants.EOL); buffer.append(" </TR>" + Constants.EOL); buffer.append(" <TR>" + Constants.EOL); buffer.append(" <TD>" + numOccurrences + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(getAverageValue()) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(totalValue) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(getMaxValue()) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(getMinValue()) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(getAverageTimeBetweenOccurrences()) + "</TD>" + Constants.EOL); buffer.append(" </TR>" + Constants.EOL); buffer.append("</TABLE>" + Constants.EOL); buffer.append("<BR><BR>" + Constants.EOL); buffer.append("<TABLE BORDER=\"1\">" + Constants.EOL); buffer.append(" <TR>" + Constants.EOL); buffer.append(" <TD><B>Event Time</B></TD>" + Constants.EOL); buffer.append(" <TD><B>Event Value</B></TD>" + Constants.EOL); buffer.append(" </TR>" + Constants.EOL); for (int i=0; i < numOccurrences; i++) { buffer.append(" <TR>" + Constants.EOL); buffer.append(" <TD>" + dateFormat.format(new Date(eventTimes[i])) + "</TD>" + Constants.EOL); buffer.append(" <TD>" + decimalFormat.format(eventValues[i]) + "</TD>" + Constants.EOL); buffer.append(" </TR>" + Constants.EOL); } buffer.append("</TABLE>" + Constants.EOL); return buffer.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() { return new String[] { "Occurrences", "Average Value", "Total Value", "Average Time Between Occurrences" }; } /** * 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() { return new String[] { String.valueOf(numOccurrences), decimalFormat.format(getAverageValue()), decimalFormat.format(totalValue), decimalFormat.format(getAverageTimeBetweenOccurrences()) }; } /** * 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) { String[][] returnArray; int arrayPos; if (includeLabels) { returnArray = new String[numOccurrences+1][2]; arrayPos = 1; returnArray[0][0] = "Event Time"; returnArray[0][1] = "Event Value"; } else { returnArray = new String[numOccurrences][2]; arrayPos = 0; } for (int i=0; i < numOccurrences; i++,arrayPos++) { returnArray[arrayPos][0] = dateFormat.format(new Date(eventTimes[i])); returnArray[arrayPos][1] = decimalFormat.format(eventValues[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() { ASN1Element[] sequenceElements = new ASN1Element[(numOccurrences*2)+3]; sequenceElements[0] = new ASN1OctetString(String.valueOf(startTime)); sequenceElements[1] = new ASN1OctetString(String.valueOf(stopTime)); ASN1Element[] configElements = new ASN1Element[] { new ASN1OctetString(Constants.SERVLET_PARAM_BASE_AT_ZERO + "=" + String.valueOf(baseAtZero)), new ASN1OctetString(Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS + "=" + String.valueOf(flatBetweenPoints)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_AVERAGE + "=" + String.valueOf(includeAverage)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID + "=" + String.valueOf(includeHorizontalGrid)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID + "=" + String.valueOf(includeVerticalGrid)), new ASN1OctetString(Constants.SERVLET_PARAM_INCLUDE_LABELS + "=" + String.valueOf(includeLegend)), }; sequenceElements[2] = new ASN1Sequence(configElements); int pos = 3; for (int i=0; i < numOccurrences; i++) { sequenceElements[pos++] = new ASN1OctetString(String.valueOf(eventTimes[i])); sequenceElements[pos++] = new ASN1OctetString(String.valueOf(eventValues[i])); } return new ASN1Sequence(sequenceElements).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[] elements = ASN1Element.decodeAsSequence(encodedData).getElements(); numOccurrences = (elements.length-3) / 2; eventTimes = new long[numOccurrences]; eventValues = new double[numOccurrences]; totalValue = 0.0; startTime = Long.parseLong(elements[0].decodeAsOctetString().getStringValue()); stopTime = Long.parseLong(elements[1].decodeAsOctetString().getStringValue()); duration = (int) ((stopTime - startTime) / 1000); ASN1Element[] configElements = elements[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_BASE_AT_ZERO)) { baseAtZero = value; } else if (name.equals(Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS)) { flatBetweenPoints = value; } else if (name.equals(Constants.SERVLET_PARAM_INCLUDE_AVERAGE)) { includeAverage = 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; } else if (name.equals(Constants.SERVLET_PARAM_INCLUDE_LABELS)) { includeLegend = value; } } for (int i=0,j=3; i < numOccurrences; i++) { eventTimes[i] = Long.parseLong( elements[j++].decodeAsOctetString().getStringValue()); eventValues[i] = Double.parseDouble( elements[j++].decodeAsOctetString().getStringValue()); totalValue += eventValues[i]; } } catch (Exception e) { throw new SLAMDException("Unable to decode data for periodic event " + "tracker: " + 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 baseAtZeroParameter = new BooleanParameter(Constants.SERVLET_PARAM_BASE_AT_ZERO, "Base at Zero", "Indicates whether the lower bound for the " + "graph should be based at zero rather than " + "dynamically calculated from the information " + "contained in the data provided.", baseAtZero); BooleanParameter showPointsParameter = new BooleanParameter(Constants.SERVLET_PARAM_SHOW_POINTS, "Show Data Points", "Indicates whether the individual data points " + "should be depicted on the graph.", true); BooleanParameter flatBetweenPointsParameter = new BooleanParameter(Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS, "Flat Between Data Points", "Indicates whether the data points should be " + "connected using only horizontal and vertical " + "lines or if they should be directly connected.", flatBetweenPoints); BooleanParameter includeAverageParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_AVERAGE, "Include Average Line", "Indicates whether the graph generated should " + "include a line that shows the average value " + "for the displayed data set.", includeAverage); 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[] { includeAverageParameter, showPointsParameter, flatBetweenPointsParameter, includeHGridParameter, includeVGridParameter, baseAtZeroParameter }; 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 baseAtZeroParameter = new BooleanParameter(Constants.SERVLET_PARAM_BASE_AT_ZERO, "Base at Zero", "Indicates whether the lower bound for the " + "graph should be based at zero rather than " + "dynamically calculated from the information " + "contained in the data provided.", baseAtZero); BooleanParameter showPointsParameter = new BooleanParameter(Constants.SERVLET_PARAM_SHOW_POINTS, "Show Data Points", "Indicates whether the individual data points " + "should be depicted on the graph.", true); BooleanParameter flatBetweenPointsParameter = new BooleanParameter(Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS, "Flat Between Data Points", "Indicates whether the data points should be " + "connected using only horizontal and vertical " + "lines or if they should be directly connected.", flatBetweenPoints); BooleanParameter includeAverageParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_AVERAGE, "Include Average Line", "Indicates whether the graph generated should " + "include a line that shows the average value " + "for the displayed data set.", includeAverage); 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[] { includeAverageParameter, showPointsParameter, flatBetweenPointsParameter, includeHGridParameter, includeVGridParameter, baseAtZeroParameter }; 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 baseAtZeroParameter = new BooleanParameter(Constants.SERVLET_PARAM_BASE_AT_ZERO, "Base at Zero", "Indicates whether the lower bound for the " + "graph should be based at zero rather than " + "dynamically calculated from the information " + "contained in the data provided.", baseAtZero); BooleanParameter showPointsParameter = new BooleanParameter(Constants.SERVLET_PARAM_SHOW_POINTS, "Show Data Points", "Indicates whether the individual data points " + "should be depicted on the graph.", true); BooleanParameter flatBetweenPointsParameter = new BooleanParameter(Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS, "Flat Between Data Points", "Indicates whether the data points should be " + "connected using only horizontal and vertical " + "lines or if they should be directly connected.", flatBetweenPoints); 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 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 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[] { includeLegendParameter, showPointsParameter, flatBetweenPointsParameter, includeHGridParameter, includeVGridParameter, baseAtZeroParameter }; 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 null; } /** * 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. * * @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 showPoints = true; boolean includeHGrid = false; boolean includeVGrid = false; BooleanParameter includeAverageParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_AVERAGE); if (includeAverageParameter != null) { includeAverage = includeAverageParameter.getBooleanValue(); } BooleanParameter showPointsParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_SHOW_POINTS); if (showPointsParameter != null) { showPoints = showPointsParameter.getBooleanValue(); } BooleanParameter flatBetweenPointsParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS); if (flatBetweenPointsParameter != null) { flatBetweenPoints = flatBetweenPointsParameter.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(); } BooleanParameter baseAtZeroParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_BASE_AT_ZERO); if (baseAtZeroParameter != null) { baseAtZero = baseAtZeroParameter.getBooleanValue(); } StatTracker[] trackersToGraph = job.getStatTrackers(displayName); if ((trackersToGraph == null) || (trackersToGraph.length == 0)) { return null; } PeriodicEventTracker tracker = (PeriodicEventTracker) trackersToGraph[0].newInstance(); tracker.aggregate(trackersToGraph); double[][] xCoordiantes = new double[1][tracker.numOccurrences]; for (int i=0; i < tracker.numOccurrences; i++) { xCoordiantes[0][i] = (tracker.eventTimes[i] - tracker.startTime) / 1000.0; } double[][] yCoordinates = new double[1][tracker.numOccurrences]; yCoordinates[0] = tracker.eventValues; String graphTitle = displayName + " for job " + job.getJobID(); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(baseAtZero); grapher.setFlatBetweenPoints(flatBetweenPoints); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIncludeAverage(includeAverage); return grapher.generateXYLineGraph(xCoordiantes, yCoordinates, new String[] { job.getJobID() }, showPoints, true); } /** * 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 showPoints = true; boolean includeHGrid = false; boolean includeVGrid = false; BooleanParameter includeAverageParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_AVERAGE); if (includeAverageParameter != null) { includeAverage = includeAverageParameter.getBooleanValue(); } BooleanParameter showPointsParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_SHOW_POINTS); if (showPointsParameter != null) { showPoints = showPointsParameter.getBooleanValue(); } BooleanParameter flatBetweenPointsParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS); if (flatBetweenPointsParameter != null) { flatBetweenPoints = flatBetweenPointsParameter.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(); } BooleanParameter baseAtZeroParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_BASE_AT_ZERO); if (baseAtZeroParameter != null) { baseAtZero = baseAtZeroParameter.getBooleanValue(); } String graphTitle = displayName + " for job " + job.getJobID(); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(baseAtZero); grapher.setFlatBetweenPoints(flatBetweenPoints); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIncludeAverage(includeAverage); StatTracker[] trackersToGraph = job.getResourceStatTrackers(displayName); if ((trackersToGraph == null) || (trackersToGraph.length == 0)) { return null; } PeriodicEventTracker tracker = (PeriodicEventTracker) trackersToGraph[0].newInstance(); tracker.aggregate(trackersToGraph); double[][] xCoordiantes = new double[1][tracker.numOccurrences]; for (int i=0; i < tracker.numOccurrences; i++) { xCoordiantes[0][i] = (tracker.eventTimes[i] - tracker.startTime) / 1000.0; } double[][] yCoordinates = new double[1][tracker.numOccurrences]; yCoordinates[0] = tracker.eventValues; return grapher.generateXYLineGraph(xCoordiantes, yCoordinates, new String[] { job.getJobID() }, showPoints, true); } /** * 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. * * @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 showPoints = true; boolean includeHGrid = false; boolean includeVGrid = false; BooleanParameter includeLegendParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter showPointsParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_SHOW_POINTS); if (showPointsParameter != null) { showPoints = showPointsParameter.getBooleanValue(); } BooleanParameter flatBetweenPointsParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_FLAT_BETWEEN_POINTS); if (flatBetweenPointsParameter != null) { flatBetweenPoints = flatBetweenPointsParameter.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(); } BooleanParameter baseAtZeroParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_BASE_AT_ZERO); if (baseAtZeroParameter != null) { baseAtZero = baseAtZeroParameter.getBooleanValue(); } String graphTitle = "Comparison of " + displayName + " Values"; StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(baseAtZero); grapher.setFlatBetweenPoints(flatBetweenPoints); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIncludeLegend(includeLegend, "Job IDs"); double[][] xCoordinates = new double[jobs.length][]; double[][] yCoordinates = new double[jobs.length][]; String[] jobIDs = new String[jobs.length]; for (int i=0; i < jobs.length; i++) { StatTracker[] trackers = jobs[i].getStatTrackers(displayName); if ((trackers == null) || (trackers.length == 0)) { return null; } PeriodicEventTracker tracker = (PeriodicEventTracker) trackers[0].newInstance(); tracker.aggregate(trackers); jobIDs[i] = jobs[i].getJobID(); yCoordinates[i] = tracker.eventValues; long startTime = tracker.startTime; xCoordinates[i] = new double[tracker.eventTimes.length]; for (int j=0; j < xCoordinates[i].length; j++) { xCoordinates[i][j] = (tracker.eventTimes[j] - startTime) / 1000.0; } } return grapher.generateXYLineGraph(xCoordinates, yCoordinates, jobIDs, showPoints, true); } /** * 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) { double[][] xCoordiantes = new double[1][numOccurrences]; for (int i=0; i < numOccurrences; i++) { xCoordiantes[0][i] = (eventTimes[i] - startTime) / 1000.0; } double[][] yCoordinates = new double[1][numOccurrences]; yCoordinates[0] = eventValues; String graphTitle = displayName; StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(true); grapher.setFlatBetweenPoints(true); grapher.setIncludeHorizontalGrid(true); grapher.setIncludeVerticalGrid(true); return grapher.generateXYLineGraph(xCoordiantes, yCoordinates, new String[] { displayName }, true, true); } }