/* * 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.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.MultiChoiceParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; /** * This class defines a simple stat tracker that can be used to track the time * that it takes for a particular event to complete. * * * @author Neil A. Wilson */ public class TimeTracker implements StatTracker { /** * The text that will be used to indicate that the comparison between multiple * jobs will be performed in parallel. */ public static final String COMPARE_IN_PARALLEL_STRING = "Compare in Parallel"; /** * The text that will be used to indicate that the comparison between multiple * jobs will be performed over time. */ public static final String COMPARE_OVER_TIME_STRING = "Compare over Time"; // Stores the number of occurrences of the tracked event over each interval. private ArrayList<Integer> countList; // Stores the sum of all the durations of the tracked events over each // interval. private ArrayList<Integer> durationList; // Indicates whether to enable real-time statistics collection. private boolean enableRealTimeStats; // Indicates whether this stat tracker has been started. private boolean hasBeenStarted; // Indicates whether this stat tracker is currently running. private boolean isRunning; // The formatter used to round off decimal values. private DecimalFormat decimalFormat; // The collection interval to use in seconds. private int collectionInterval; // The total length of time (in seconds) that this stat tracker was collecting // statistics. private int duration; // The number of times the tracked event has completed in the current // interval. private int intervalCount; // The current interval number used when reporting real-time statistics. private int intervalNum; // The total length of time (in nanoseconds) that the tracked operation has // been in progress over the current interval. private long intervalDuration; // The length of time in nanoseconds required to perform the last operation. private long lastOperationTime; // The maximum length of time in nanoseconds that it took for the tracked // event to complete. private long maxDuration; // The minimum length of time in nanoseconds that it took for the tracked // event to complete. private long minDuration; // The time as a nanosecond offset at which the current interval should stop // and the next should begin. private long intervalStopTime; // The time that the current occurrence of the tracked operation started as a // nanosecond offset. private long operationStartTime; // The time that this tracker started collecting statistics as a millisecond // offset. private long startTime; // The time that this tracker started collecting statistics as a nanosecond // offset. private long startTimeNanos; // The time that this tracker stopped collecting statistics as a millisecond // offset. private long stopTime; // The stat reporter used for reporting statistics in real time. private RealTimeStatReporter statReporter; // The client ID for the client that used this stat tracker. private String clientID; // The display name to use for this stat tracker. private String displayName; // The thread ID for the client thread that used this stat tracker. private String threadID; /** * Creates a new time tracker intended for use as a placeholder for decoding * purposes. This version of the constructor should not be used by job * classes. */ public TimeTracker() { this.clientID = ""; this.threadID = ""; this.displayName = ""; this.collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; decimalFormat = new DecimalFormat("0.000"); duration = 0; intervalCount = 0; intervalDuration = 0; lastOperationTime = -1; maxDuration = 0; minDuration = Integer.MAX_VALUE; intervalStopTime = 0; operationStartTime = 0; startTime = System.currentTimeMillis(); startTimeNanos = System.nanoTime(); stopTime = 0; countList = new ArrayList<Integer>(); durationList = new ArrayList<Integer>(); enableRealTimeStats = false; statReporter = null; intervalNum = 0; hasBeenStarted = false; isRunning = false; } /** * Creates a new time tracker with the specified information. * * @param clientID The client ID for the client that used this * stat tracker. * @param threadID The thread ID for the client 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 TimeTracker(String clientID, String threadID, String displayName, int collectionInterval) { this.clientID = clientID; this.threadID = threadID; this.displayName = displayName; this.collectionInterval = collectionInterval; if (collectionInterval <= 0) { this.collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; } decimalFormat = new DecimalFormat("0.000"); duration = 0; intervalCount = 0; intervalDuration = 0; lastOperationTime = -1; maxDuration = 0; minDuration = Integer.MAX_VALUE; intervalStopTime = 0; operationStartTime = 0; startTime = System.currentTimeMillis(); startTimeNanos = System.nanoTime(); stopTime = 0; countList = new ArrayList<Integer>(); durationList = new ArrayList<Integer>(); enableRealTimeStats = false; statReporter = null; intervalNum = 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() { return new TimeTracker(clientID, threadID, displayName, collectionInterval); } /** * Indicates that another occurrence of the tracked event has started. Note * that if there are consecutive calls to <CODE>startTimer</CODE> without a * call to <CODE>stopTimer</CODE> in between, then the earlier call to * <CODE>startTimer</CODE> will be discarded and the later will be used. */ public void startTimer() { operationStartTime = System.nanoTime(); } /** * Indicates that the current occurrence of the tracked event has completed. * If this is called without an earlier call to <CODE>startTimer</CODE>, then * the results will be unreliable. */ public void stopTimer() { long now = System.nanoTime(); lastOperationTime = now - operationStartTime; if (now < intervalStopTime) { // We are still in the same interval as the last operation, so just update // the statistics for this interval. intervalCount++; intervalDuration += lastOperationTime; } else { // The previous interval has completed so we need to save the stats for // that interval and start a new set. countList.add(intervalCount); durationList.add((int) (intervalDuration / 1000000L)); if (enableRealTimeStats) { statReporter.reportStatToAverage(this, intervalNum++, (intervalDuration / 1000000.0D / intervalCount)); } intervalStopTime += (1000000000L * collectionInterval); // There could have been no activity for quite some time, and multiple // intervals could have passed. If so, then fill in the blanks. while (intervalStopTime < now) { countList.add(0); durationList.add(0); if (enableRealTimeStats) { statReporter.reportStatToAverage(this, intervalNum++, 0.0); } intervalStopTime += (1000000000L * collectionInterval); } intervalCount = 1; intervalDuration = lastOperationTime; } // Finally, check to see if this is the new max or min. if (lastOperationTime > maxDuration) { maxDuration = lastOperationTime; } if (lastOperationTime < minDuration) { minDuration = lastOperationTime; } } /** * Updates the internal timer for this stat tracker to indicate that an * operation completed that required the specified length of time. The caller * is responsible for determining the length of time required to perform the * operation provided as the argument to this method. * * @param lastOperationTime The length of time in milliseconds required to * perform the operation. */ public void updateTimer(int lastOperationTime) { long now = System.nanoTime(); this.lastOperationTime = 1000000L * lastOperationTime; if (now < intervalStopTime) { // We are still in the same interval as the last operation, so just update // the statistics for this interval. intervalCount++; intervalDuration += this.lastOperationTime; } else { // The previous interval has completed so we need to save the stats for // that interval and start a new set. countList.add(intervalCount); durationList.add((int) (intervalDuration / 1000000L)); if (enableRealTimeStats) { statReporter.reportStatToAverage(this, intervalNum++, (intervalDuration / 1000000.0D / intervalCount)); } intervalStopTime += (1000000000L * collectionInterval); // There could have been no activity for quite some time, and multiple // intervals could have passed. If so, then fill in the blanks. while (intervalStopTime < now) { countList.add(0); durationList.add(0); if (enableRealTimeStats) { statReporter.reportStatToAverage(this, intervalNum++, 0.0); } intervalStopTime += (1000000000L * collectionInterval); } intervalCount = 1; intervalDuration = this.lastOperationTime; } // Finally, check to see if this is the new max or min. if (this.lastOperationTime > maxDuration) { maxDuration = this.lastOperationTime; } if (this.lastOperationTime < minDuration) { minDuration = this.lastOperationTime; } } /** * Retrieves the length of time in milliseconds required to perform the last * operation. That is, it returns the length of time between the last calls * to <CODE>startTimer</CODE> and <CODE>stopTimer</CODE>. * * @return The length of time in milliseconds required to perform the last * operation. */ public int getLastOperationTime() { return (int) (lastOperationTime / 1000000L); } /** * Removes the record of the last operation time associated with this time * tracker. That is, it undoes the effect of the last call to * <CODE>stopTimer()</CODE>. Note that this should not be used multiple times * between <CODE>startTimer()</CODE>/<CODE>stopTimer()</CODE> sequences. */ public void removeLastOperationTime() { if (intervalCount > 0) { intervalCount--; intervalDuration -= lastOperationTime; } } /** * Retrieves the maximum length of time in milliseconds that an occurrence of * the tracked event took to complete. * * @return The maximum length of time in milliseconds that an occurrence of * the tracked event took to complete. */ public int getMaxDuration() { return (int) (maxDuration / 1000000L); } /** * Retrieves the minimum length of time in milliseconds that an occurrence of * the tracked event took to complete. * * @return The minimum length of time in milliseconds that an occurrence of * the tracked event took to complete. */ public int getMinDuration() { return (int) (minDuration / 1000000L); } /** * Retrieves the total length of time in milliseconds that the tracked event * was in progress over the entire duration. * * @return The total length of time in milliseconds that the tracked event * was in progress over the entire duration. */ public int getTotalDuration() { int numIntervals = durationList.size(); int totalDuration = 0; for (int i=0; i < numIntervals; i++) { totalDuration += durationList.get(i); } return totalDuration; } /** * Retrieves the total length of time in milliseconds that the tracked event * was in progress over the entire duration. This method returns the value as * a long, which is less likely to overflow than an integer. * * @return The total length of time in milliseconds that the tracked event * was in progress over the entire duration. */ public long getTotalDurationAsLong() { int numIntervals = durationList.size(); long totalDuration = 0; for (int i=0; i < numIntervals; i++) { totalDuration += durationList.get(i); } return totalDuration; } /** * Retrieves the total number of times that the tracked event occurred over * the entire duration. * * @return The total number of times that the tracked event occurred over the * entire duration. */ public int getTotalCount() { int numIntervals = countList.size(); int totalCount = 0; for (int i=0; i < numIntervals; i++) { totalCount += countList.get(i); } return totalCount; } /** * Retrieves the total number of times that the tracked event occurred over * the entire duration. This method returns the value as a long, which is * less likely to overflow than an integer. * * @return The total number of times that the tracked event occurred over the * entire duration. */ public long getTotalCountAsLong() { int numIntervals = countList.size(); long totalCount = 0; for (int i=0; i < numIntervals; i++) { totalCount += countList.get(i); } return totalCount; } /** * Retrieves an array indicating the number of times the tracked event * occurred over each interval. * * @return An array indicating the number of times the tracked event occurred * over each interval. */ public int[] getIntervalCounts() { int[] returnArray = new int[countList.size()]; for (int i=0; i < returnArray.length; i++) { returnArray[i] = countList.get(i); } return returnArray; } /** * Retrieves an array indicating the total amount of time in milliseconds that * the tracked event was in progress over each interval. * * @return An array indicating the total amount of time in milliseconds that * the tracked event was in progress over each interval. */ public int[] getIntervalDurations() { int[] returnArray = new int[durationList.size()]; for (int i=0; i < returnArray.length; i++) { returnArray[i] = durationList.get(i); } return returnArray; } /** * Specifies the data associated with this tracker in terms of the total * duration per interval and the number of occurrences per interval. Note * that the number of elements in each array must be identical. * * @param intervalDurations An array containing the sums of all the * durations associated with this tracker over each * interval. * @param intervalCounts An array indicating the number of times the * tracked event occurred over each interval. */ public void setIntervalData(int[] intervalDurations, int[] intervalCounts) { durationList = new ArrayList<Integer>(); countList = new ArrayList<Integer>(); for (int i=0; i < intervalDurations.length; i++) { durationList.add(intervalDurations[i]); countList.add(intervalCounts[i]); } duration = collectionInterval * intervalDurations.length; } /** * Retrieves the average length of time in milliseconds that it took for an * occurrence of the tracked event to complete. * * @return The average length of time in milliseconds that it took for an * occurrence of the tracked event to complete. */ public double getAverageDuration() { long totalCount = getTotalCountAsLong(); if (totalCount > 0) { return (1.0 * getTotalDurationAsLong() / totalCount); } else { return 0.0; } } /** * Retrieves the average number of times that the tracked event completed over * a single interval. * * @return The average number of times that the tracked event completed over * a single interval. */ public double getAverageCountPerInterval() { if (! countList.isEmpty()) { return (1.0 * getTotalCountAsLong() / countList.size()); } else { return 0.0; } } /** * Retrieves the average length of time that occurrences of the tracked event * were in progress over a single interval. * * @return The average length of time that occurrences of the tracked event * were in progress over a single interval. */ public double getAverageDurationPerInterval() { if (! durationList.isEmpty()) { return (1.0 * getTotalDurationAsLong() / durationList.size()); } else { return 0.0; } } /** * Retrieves the standard deviation for this tracker, based on the average * duration. * * @return The standard deviation for this tracker, based on the average * duration. */ public double getStandardDeviation() { double avgDuration = getAverageDuration(); double sumOfDifferencesSquared = 0.0; int n = 0; for (int i=0; i < durationList.size(); i++) { int intervalDuration = durationList.get(i); int intervalCount = countList.get(i); if (intervalCount > 0) { double intervalAvgDuration = 1.0 * intervalDuration / intervalCount; double difference = (intervalAvgDuration - avgDuration); sumOfDifferencesSquared += (difference * difference); n++; } } return Math.sqrt(sumOfDifferencesSquared / (n - 1)); } /** * Retrieves the correlation coefficient for the data, which is a measure of * how strongly a change in one variable impacts a change in another. If the * regression line is perfectly horizontal (i.e., a change in one variable has * no impact whatsoever on the value of the other), then the correlation * coefficient will be zero. If the data is perfectly linear but not * horizontal (i.e., a change in one variable has a linear, proportional * change in the other), then it will be 1.0 or -1.0. If the data is not * linear, then the value will be somewhere between -1.0 and 0.0, or between * 0.0 and 1.0. * * @return The regression coefficient for the data. */ public double getCorrelationCoefficient() { int n = countList.size(); double avgX = getAverageDuration(); double avgY = (countList.size() * collectionInterval) / 2; double sumOfXDiffsSquared = 0.0; double sumOfYDiffsSquared = 0.0; double sumOfXYDiffs = 0.0; for (int i=0; i < countList.size(); i++) { int intervalDuration = durationList.get(i); int intervalCount = countList.get(i); if (intervalCount > 0) { double xValue = intervalDuration / intervalCount; int yValue = (collectionInterval * i); double xDiff = xValue - avgX; double yDiff = yValue - avgY; sumOfXDiffsSquared += (xDiff*xDiff); sumOfYDiffsSquared += (yDiff*yDiff); sumOfXYDiffs += (xDiff*yDiff); } } if (sumOfXDiffsSquared == 0) { return 0.0; } double stdDevX = Math.sqrt(sumOfXDiffsSquared / (n-1)); double stdDevY = Math.sqrt(sumOfYDiffsSquared / (n-1)); return (sumOfXYDiffs / ((n-1)*stdDevX*stdDevY)); } /** * Retrieves the linear regression coefficients for this tracker, based on the * average duration. The array returned will have two values, A and B, which * which can be used in the equation y = A + Bx to generate the line that most * closely approximates the set of results. * * @return The linear regression coefficients for this tracker, based on the * average duration. */ public double[] getRegressionCoefficients() { double sx = 0.0; double sy = 0.0; double sxx = 0.0; double syy = 0.0; double sxy = 0.0; int n = 0; for (int i=0; i < countList.size(); i++) { int intervalDuration = durationList.get(i); int intervalCount = countList.get(i); if (intervalCount > 0) { double intervalAvgDuration = 1.0 * intervalDuration / intervalCount; sx += (collectionInterval*i); sy += intervalAvgDuration; sxx += (collectionInterval*collectionInterval*i*i); syy += (intervalAvgDuration*intervalAvgDuration); sxy += (collectionInterval*i*intervalAvgDuration); n++; } } double b = (sxy - (sx*sy)/n) / (sxx - (sx*sx)/n); double a = (sy - b*sx) / n; return new double[] { a, b }; } /** * Retrieves a T score value that can be used to determine how confident we * are that the result set is approximately linear. It does this by dividing * the data set into two halves and performing a confidence test to determine * whether those two means can be considered equal. * * @return The calculated T score value. */ public double getHorizontalityTScore() { // First, get the counts and totals as integer arrays to make things easier. int[] timeArray = getIntervalDurations(); int[] countArray = getIntervalCounts(); // Break the data up into two equal halves. int n = countArray.length; int n1 = n / 2; int n2 = n - n1; // Now calculate the totals for each half. int time1 = 0; int time2 = 0; int count1 = 0; int count2 = 0; for (int i=0; i < n; i++) { if (i < n1) { time1 += timeArray[i]; count1 += countArray[i]; } else { time2 += timeArray[i]; count2 += countArray[i]; } } // If the count for both halves is zero, then the T score will automatically // be zero. if ((count1 == 0) && (count2 == 0)) { return 0.0; } // Find the average for each half. double mean1 = 1.0 * time1 / n1 / count1; double mean2 = 1.0 * time2 / n2 / count2; // Next, calculate the standard deviation for each half. double sumOfDifferencesSquared1 = 0.0; double sumOfDifferencesSquared2 = 0.0; for (int i=0; i < n; i++) { if (i < n1) { double intervalAvg = 1.0 * timeArray[i] / countArray[i]; double difference = (mean1 - intervalAvg); sumOfDifferencesSquared1 += (difference * difference); } else { double intervalAvg = 1.0 * timeArray[i] / countArray[i]; double difference = (mean2 - intervalAvg); sumOfDifferencesSquared2 += (difference * difference); } } double stdDev1 = Math.sqrt(sumOfDifferencesSquared1 / (n1 - 1)); double stdDev2 = Math.sqrt(sumOfDifferencesSquared2 / (n2 - 1)); // Finally, calculate the standard error and the T score. double stdError = Math.sqrt(stdDev1*stdDev1/n1 + stdDev2*stdDev2/n2); return Math.abs(mean1 - mean2) / stdError; } /** * 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 time " + "tracker " + displayName); } else { hasBeenStarted = true; isRunning = true; } // Just in case, reset all the counter info. duration = 0; intervalCount = 0; intervalDuration = 0; lastOperationTime = -1; maxDuration = 0; minDuration = Integer.MAX_VALUE; countList = new ArrayList<Integer>(); durationList = new ArrayList<Integer>(); // Register this tracker with the persistence thread. Client.registerPersistentStatistic(this); // Set the start time and the interval stop time. startTime = System.currentTimeMillis(); startTimeNanos = System.nanoTime(); long now = startTimeNanos; intervalStopTime = now + (1000000000L * collectionInterval); // Just as a safety blanket in case there is a call to stopTimer without // an earlier call to startTimer. The results would still be inaccurate // but not nearly to the same extent. operationStartTime = now; } /** * 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.nanoTime(); // 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 time 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) { countList.add(intervalCount); durationList.add((int) (intervalDuration / 1000000L)); if (enableRealTimeStats) { statReporter.reportStatToAverage(this, intervalNum++, (intervalDuration / 1000000.0D / intervalCount)); } intervalStopTime += (1000000000L * collectionInterval); } while (intervalStopTime < now) { countList.add(0); durationList.add(0); if (enableRealTimeStats) { statReporter.reportStatToAverage(this, intervalNum++, 0.0); } intervalStopTime += (1000000000L * collectionInterval); } // Update the stop time to be the time that the last complete interval // ended and calculate the duration. long stopTimeNanos = intervalStopTime - (1000000000L * collectionInterval); long durationNanos = stopTimeNanos - startTimeNanos; long durationMillis = durationNanos / 1000000L; stopTime = startTime + durationMillis; duration = (int) (durationMillis / 1000); if (enableRealTimeStats) { statReporter.doneReporting(this, intervalNum++); } } /** * 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) { if (statReporter != null) { enableRealTimeStats = true; this.statReporter = statReporter; statReporter.registerStat(jobID, this); } } /** * 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 * 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() { return true; } /** * 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 (getAverageDuration() >= value); } /** * 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 (getAverageDuration() <= value); } /** * 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 getAverageDuration(); } /** * 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 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) { duration = Integer.MAX_VALUE; maxDuration = Integer.MIN_VALUE; minDuration = Integer.MAX_VALUE; if (trackers.length > 0) { collectionInterval = trackers[0].getCollectionInterval(); } int min = Integer.MAX_VALUE; int[][] counts = new int[trackers.length][]; int[][] durations = new int[trackers.length][]; for (int i=0; i < counts.length; i++) { counts[i] = ((TimeTracker) trackers[i]).getIntervalCounts(); durations[i] = ((TimeTracker) trackers[i]).getIntervalDurations(); if (counts[i].length < min) { min = counts[i].length; } if (trackers[i].getDuration() < duration) { duration = trackers[i].getDuration(); } if (((TimeTracker) trackers[i]).getMaxDuration() > getMaxDuration()) { maxDuration = 1000000L * ((TimeTracker) trackers[i]).getMaxDuration(); } if (((TimeTracker) trackers[i]).getMinDuration() < getMinDuration()) { minDuration = 1000000L * ((TimeTracker) trackers[i]).getMinDuration(); } } int[] aggregateCounts = new int[min]; int[] aggregateDurations = new int[min]; for (int i=0; i < aggregateCounts.length; i++) { aggregateCounts[i] = 0; aggregateDurations[i] = 0; for (int j=0; j < counts.length; j++) { aggregateCounts[i] += counts[j][i]; aggregateDurations[i] += durations[j][i]; } } countList = new ArrayList<Integer>(aggregateCounts.length); durationList = new ArrayList<Integer>(aggregateCounts.length); for (int i=0; i < aggregateCounts.length; i++) { countList.add(aggregateCounts[i]); durationList.add(aggregateDurations[i]); } } /** * 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 + " -- Total Duration: " + getTotalDurationAsLong() + "; Total Count: " + getTotalCountAsLong() + "; Avg Duration: " + decimalFormat.format(getAverageDuration()) + "; Avg Count/Interval: " + decimalFormat.format(getAverageCountPerInterval()) + "; Std Dev: " + decimalFormat.format(getStandardDeviation()) + "; Corr Coeff: " + decimalFormat.format(getCorrelationCoefficient()); } /** * 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 returnBuffer = new StringBuilder(); double[] regressionCoefficients = getRegressionCoefficients(); String regressionEquation = "y = " + decimalFormat.format(regressionCoefficients[0]); if (regressionCoefficients[1] < 0) { regressionEquation += " - " + decimalFormat.format(Math.abs(regressionCoefficients[1])) + "x"; } else { regressionEquation += " + " + decimalFormat.format(regressionCoefficients[1]) + "x"; } returnBuffer.append(displayName + Constants.EOL); returnBuffer.append("Total Duration: " + getTotalDurationAsLong() + Constants.EOL); returnBuffer.append("Total Count: " + getTotalCountAsLong() + Constants.EOL); returnBuffer.append("Average Duration: " + decimalFormat.format(getAverageDuration()) + Constants.EOL); if (getMaxDuration() == Integer.MIN_VALUE) { returnBuffer.append("Maximum Duration: N/A" + Constants.EOL); } else { returnBuffer.append("Maximum Duration: " + getMaxDuration() + Constants.EOL); } if (getMinDuration() == Integer.MAX_VALUE) { returnBuffer.append("Minimum Duration: N/A" + Constants.EOL); } else { returnBuffer.append("Minimum Duration: " + getMinDuration() + Constants.EOL); } returnBuffer.append("Average Duration/Interval: " + decimalFormat.format(getAverageDurationPerInterval()) + Constants.EOL); returnBuffer.append("Average Count/Interval: " + decimalFormat.format(getAverageCountPerInterval()) + Constants.EOL); returnBuffer.append("Std Dev: " + decimalFormat.format(getStandardDeviation()) + Constants.EOL); returnBuffer.append("Correlation Coefficient: " + decimalFormat.format(getCorrelationCoefficient()) + Constants.EOL); returnBuffer.append("Regression Equation: " + regressionEquation + Constants.EOL); returnBuffer.append("Horizontality T Score: " + decimalFormat.format(getHorizontalityTScore()) + Constants.EOL); for (int i=0; i < countList.size(); i++) { int c = countList.get(i); int d = durationList.get(i); returnBuffer.append("Interval "); returnBuffer.append(i+1); returnBuffer.append(": "); returnBuffer.append("Duration=" + d); returnBuffer.append("; Count=" + c); if (c > 0) { returnBuffer.append("; Average=" + decimalFormat.format(1.0 * d / c)); } else { returnBuffer.append("; Average=0.000"); } returnBuffer.append(Constants.EOL); } return returnBuffer.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 html = new StringBuilder(); html.append("<TABLE BORDER=\"1\">" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD><B>Total Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Total Count</B></TD>" + Constants.EOL); html.append(" <TD><B>Avg Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Avg Count/Interval</B></TD>" + Constants.EOL); html.append(" <TD><B>Std Dev</B></TD>" + Constants.EOL); html.append(" <TD><B>Corr Coeff</B></TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD>" + getTotalDurationAsLong() + "</TD>" + Constants.EOL); html.append(" <TD>" + getTotalCountAsLong() + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getAverageDuration()) + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getAverageCountPerInterval()) + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getStandardDeviation()) + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getCorrelationCoefficient()) + "</TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); html.append("</TABLE>" + Constants.EOL); return html.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 html = new StringBuilder(); double[] regressionCoefficients = getRegressionCoefficients(); String regressionEquation = "y = " + decimalFormat.format(regressionCoefficients[0]); if (regressionCoefficients[1] < 0) { regressionEquation += " - " + decimalFormat.format(Math.abs(regressionCoefficients[1])) + "x"; } else { regressionEquation += " + " + decimalFormat.format(regressionCoefficients[1]) + "x"; } html.append("<TABLE BORDER=\"1\">" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD><B>Total Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Total Count</B></TD>" + Constants.EOL); html.append(" <TD><B>Avg Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Max Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Min Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Avg Count/Interval</B></TD>" + Constants.EOL); html.append(" <TD><B>Avg Duration/Interval</B></TD>" + Constants.EOL); html.append(" <TD><B>Std Dev</B></TD>" + Constants.EOL); html.append(" <TD><B>Correlation Coefficient</B></TD>" + Constants.EOL); html.append(" <TD><B>Regression Equation</B></TD>" + Constants.EOL); html.append(" <TD><B>Horizontality T Score</B></TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD>" + getTotalDurationAsLong() + "</TD>" + Constants.EOL); html.append(" <TD>" + getTotalCountAsLong() + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getAverageDuration()) + "</TD>" + Constants.EOL); if (getMaxDuration() == Integer.MIN_VALUE) { html.append(" <TD>N/A</TD>" + Constants.EOL); } else { html.append(" <TD>" + getMaxDuration() + "</TD>" + Constants.EOL); } if (getMinDuration() == Integer.MIN_VALUE) { html.append(" <TD>N/A</TD>" + Constants.EOL); } else { html.append(" <TD>" + getMinDuration() + "</TD>" + Constants.EOL); } html.append(" <TD>" + decimalFormat.format(getAverageCountPerInterval()) + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getAverageDurationPerInterval()) + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getStandardDeviation()) + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getCorrelationCoefficient()) + "</TD>" + Constants.EOL); html.append(" <TD>" + regressionEquation + "</TD>" + Constants.EOL); html.append(" <TD>" + decimalFormat.format(getHorizontalityTScore()) + "</TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); html.append("</TABLE>" + Constants.EOL); html.append("<BR><BR>" + Constants.EOL); html.append("<TABLE BORDER=\"1\">" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD><B>Interval</B></TD>" + Constants.EOL); html.append(" <TD><B>Total Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Count</B></TD>" + Constants.EOL); html.append(" <TD><B>Average Duration</B></TD>" + Constants.EOL); html.append(" <TD><B>Occurrences/Second</B></TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); for (int i=0; i < countList.size(); i++) { int c = countList.get(i); int d = durationList.get(i); html.append(" <TR>" + Constants.EOL); html.append(" <TD>" + (i+1) + "</TD>" + Constants.EOL); html.append(" <TD>" + d + "</TD>" + Constants.EOL); html.append(" <TD>" + c + "</TD>" + Constants.EOL); if (c > 0) { html.append(" <TD>" + decimalFormat.format(1.0 * d / c) + "</TD>" + Constants.EOL); } else { html.append(" <TD>0.000</TD>" + Constants.EOL); } html.append(" <TD>" + decimalFormat.format(1.0 * c / collectionInterval) + "</TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); } html.append("</TABLE>" + Constants.EOL); return html.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[] { displayName + " Total Duration", displayName + " Total Count", displayName + " Avg Duration", displayName + " Avg Count/Interval", displayName + " Std Dev", displayName + " Corr Coeff" }; } /** * 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(getTotalDurationAsLong()), String.valueOf(getTotalCountAsLong()), String.valueOf(decimalFormat.format(getAverageDuration())), String.valueOf(decimalFormat.format(getAverageCountPerInterval())), decimalFormat.format(getStandardDeviation()), decimalFormat.format(getCorrelationCoefficient()) }; } /** * 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[countList.size()+1][]; returnArray[0] = new String[] { "Interval", "Total Duration", "Count" }; for (int i=0; i < countList.size(); i++) { returnArray[i+1] = new String[] { String.valueOf(i+1), String.valueOf(durationList.get(i)), String.valueOf(countList.get(i)) }; } return returnArray; } else { String[][] returnArray = new String[countList.size()][]; for (int i=0; i < countList.size(); i++) { returnArray[i] = new String[] { String.valueOf(durationList.get(i)), 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() { // OK. This is cheating because it's not really being encoded in the // spirit of ASN.1. But the encoded representation is much smaller than // if I had used sequence of sequence, and this format isn't going to // be publicly documented anyway. ASN1Element[] elements = new ASN1Element[(2*countList.size())+2]; elements[0] = new ASN1Integer((int) (maxDuration / 1000000L)); elements[1] = new ASN1Integer((int) (minDuration / 1000000L)); for (int i=0,j=2; i < countList.size(); i++, j += 2) { elements[j] = new ASN1Integer(durationList.get(i)); elements[j+1] = new ASN1Integer(countList.get(i)); } return new ASN1Sequence(elements).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.decode(encodedData).decodeAsSequence().getElements(); countList = new ArrayList<Integer>(); durationList = new ArrayList<Integer>(); maxDuration = 1000000L * elements[0].decodeAsInteger().getIntValue(); minDuration = 1000000L * elements[1].decodeAsInteger().getIntValue(); for (int i=2; i < elements.length; i += 2) { int intervalDuration = elements[i].decodeAsInteger().getIntValue(); int intervalCount = elements[i+1].decodeAsInteger().getIntValue(); durationList.add(intervalDuration); countList.add(intervalCount); } } 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) { ArrayList<String> dataSetList = new ArrayList<String>(); dataSetList.add("Overall Summary for Job " + job.getJobID()); dataSetList.add("Summary Statistics Per Client"); String[] clientIDs = job.getStatTrackerClientIDs(); for (int i=0; i < clientIDs.length; i++) { dataSetList.add("Detail Statistics for Client " + clientIDs[i]); } String[] dataSets = new String[dataSetList.size()]; dataSetList.toArray(dataSets); 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.", true); BooleanParameter excludeFirstIntervalParameter = new BooleanParameter(Constants.SERVLET_PARAM_EXCLUDE_FIRST_INTERVAL, "Exclude First Interval", "Indicates whether information captured during " + "the first collection interval should be " + "ignored while generating the graph.", false); BooleanParameter excludeLastIntervalParameter = new BooleanParameter(Constants.SERVLET_PARAM_EXCLUDE_LAST_INTERVAL, "Exclude Last Interval", "Indicates whether information captured during " + "the last collection interval should be " + "ignored while generating the graph.", false); 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.", false); BooleanParameter includeRegressionParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_REGRESSION, "Include Regression Line", "Indicates whether the graph generated should " + "include a line that shows the calculated " + "linear regression for the displayed data set " + "(i.e., a trend line).", false); 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.", false); BooleanParameter includeHGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID, "Include Horizontal Grid Lines", "Indicates whether the graph generated should " + "include horizontal grid lines.", true); BooleanParameter includeVGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID, "Include Vertical Grid Lines", "Indicates whether the graph generated should " + "include vertical grid lines.", true); MultiChoiceParameter detailLevelParameter = new MultiChoiceParameter(Constants.SERVLET_PARAM_DETAIL_LEVEL, "Data Set to Display", "Indicates the data that should be " + "displayed in the graph (overall for the " + "job, a summary for the client, or detail " + "for a particular client thread.", dataSets, dataSets[0]); Parameter[] parameters = new Parameter[] { detailLevelParameter, includeLegendParameter, includeAverageParameter, includeRegressionParameter, excludeFirstIntervalParameter, excludeLastIntervalParameter, 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.", true); BooleanParameter excludeFirstIntervalParameter = new BooleanParameter(Constants.SERVLET_PARAM_EXCLUDE_FIRST_INTERVAL, "Exclude First Interval", "Indicates whether information captured during " + "the first collection interval should be " + "ignored while generating the graph.", false); BooleanParameter excludeLastIntervalParameter = new BooleanParameter(Constants.SERVLET_PARAM_EXCLUDE_LAST_INTERVAL, "Exclude Last Interval", "Indicates whether information captured during " + "the last collection interval should be " + "ignored while generating the graph.", false); 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.", false); BooleanParameter includeRegressionParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_REGRESSION, "Include Regression Line", "Indicates whether the graph generated should " + "include a line that shows the calculated " + "linear regression for the displayed data set " + "(i.e., a trend line).", false); 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.", false); BooleanParameter includeHGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID, "Include Horizontal Grid Lines", "Indicates whether the graph generated should " + "include horizontal grid lines.", true); BooleanParameter includeVGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID, "Include Vertical Grid Lines", "Indicates whether the graph generated should " + "include vertical grid lines.", true); Parameter[] parameters = new Parameter[] { includeLegendParameter, includeAverageParameter, includeRegressionParameter, excludeFirstIntervalParameter, excludeLastIntervalParameter, 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) { String[] choices = new String[] { COMPARE_IN_PARALLEL_STRING, COMPARE_OVER_TIME_STRING }; 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.", true); BooleanParameter drawAsBarParameter = new BooleanParameter(Constants.SERVLET_PARAM_DRAW_AS_BAR_GRAPH, "Draw as Bar Graph", "Indicates whether this graph should be drawn " + "as a bar graph instead of a line graph.", (jobs.length > 2)); BooleanParameter excludeFirstIntervalParameter = new BooleanParameter(Constants.SERVLET_PARAM_EXCLUDE_FIRST_INTERVAL, "Exclude First Interval", "Indicates whether information captured during " + "the first collection interval should be " + "ignored while generating the graph.", false); BooleanParameter excludeLastIntervalParameter = new BooleanParameter(Constants.SERVLET_PARAM_EXCLUDE_LAST_INTERVAL, "Exclude Last Interval", "Indicates whether information captured during " + "the last collection interval should be " + "ignored while generating the graph.", false); 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.", false); BooleanParameter includeHGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_HORIZ_GRID, "Include Horizontal Grid Lines", "Indicates whether the graph generated should " + "include horizontal grid lines.", true); BooleanParameter includeVGridParameter = new BooleanParameter(Constants.SERVLET_PARAM_INCLUDE_VERT_GRID, "Include Vertical Grid Lines", "Indicates whether the graph generated should " + "include vertical grid lines.", true); MultiChoiceParameter comparisonTypeParameter = new MultiChoiceParameter(Constants.SERVLET_PARAM_COMPARE_TYPE, "Comparison Type", "Indicates whether the job information " + "should be compared in parallel (with " + "statistics overlayed), or over time (with " + "each job comprising a single data point).", choices, choices[0]); Parameter[] parameters = new Parameter[] { // comparisonTypeParameter, drawAsBarParameter, includeLegendParameter, excludeFirstIntervalParameter, excludeLastIntervalParameter, 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() { int[] intervalDurations = getIntervalDurations(); int[] intervalCounts = getIntervalCounts(); double[] avgDurations = new double[intervalDurations.length]; for (int i=0; i < avgDurations.length; i++) { avgDurations[i] = 1.0 * intervalDurations[i] / intervalCounts[i]; } return avgDurations; } /** * 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 Duration (ms)"; } /** * 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 includeLegend = false; boolean includeAverage = false; boolean includeRegression = false; boolean excludeFirst = false; boolean excludeLast = false; boolean includeHGrid = false; boolean includeVGrid = false; boolean baseAtZero = false; String detailLevelStr = "Overall Summary for Job " + job.getJobID(); String graphTitle = displayName + " for Job " + job.getJobID(); String legendTitle = "Job ID"; BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter includeAverageParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_AVERAGE); if (includeAverageParameter != null) { includeAverage = includeAverageParameter.getBooleanValue(); } BooleanParameter includeRegressionParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_REGRESSION); if (includeRegressionParameter != null) { includeRegression = includeRegressionParameter.getBooleanValue(); } BooleanParameter excludeFirstParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_EXCLUDE_FIRST_INTERVAL); if (excludeFirstParameter != null) { excludeFirst = excludeFirstParameter.getBooleanValue(); } BooleanParameter excludeLastParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_EXCLUDE_LAST_INTERVAL); if (excludeLastParameter != null) { excludeLast = excludeLastParameter.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(); } MultiChoiceParameter detailLevelParameter = parameters.getMultiChoiceParameter( Constants.SERVLET_PARAM_DETAIL_LEVEL); if (detailLevelParameter != null) { detailLevelStr = detailLevelParameter.getStringValue(); } StatTracker[] trackersToGraph; int statCategory = Constants.STAT_CATEGORY_JOB_STATS; if (detailLevelStr.equals("Summary Statistics Per Client")) { statCategory = Constants.STAT_CATEGORY_CLIENT_STATS; graphTitle = displayName + " by Client"; legendTitle = "Client ID"; String[] clientIDs = job.getStatTrackerClientIDs(); ArrayList<StatTracker> trackerList = new ArrayList<StatTracker>(); for (int i=0; i < clientIDs.length; i++) { StatTracker[] trackers = job.getStatTrackers(displayName, clientIDs[i]); if ((trackers != null) && (trackers.length > 0)) { TimeTracker tracker = new TimeTracker(clientIDs[i], "", displayName, trackers[0].getCollectionInterval()); tracker.aggregate(trackers); trackerList.add(tracker); } } trackersToGraph = new StatTracker[trackerList.size()]; trackerList.toArray(trackersToGraph); } else if (detailLevelStr.startsWith("Detail Statistics for Client ")) { statCategory = Constants.STAT_CATEGORY_THREAD_STATS; String clientID = detailLevelStr.substring(29); graphTitle = displayName + " by Thread for Client " + clientID; legendTitle = "Thread ID"; trackersToGraph = job.getStatTrackers(displayName, clientID); } else { StatTracker[] trackers = job.getStatTrackers(displayName); TimeTracker tracker = new TimeTracker("", "", displayName, trackers[0].getCollectionInterval()); tracker.aggregate(trackers); trackersToGraph = new StatTracker[] { tracker }; } StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(baseAtZero); grapher.setIncludeLegend(includeLegend, legendTitle); grapher.setVerticalAxisTitle("Average Duration (ms)"); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIgnoreZeroValues(true); for (int i=0; i < trackersToGraph.length; i++) { TimeTracker tracker = (TimeTracker) trackersToGraph[i]; int[] intervalDurations = tracker.getIntervalDurations(); int[] intervalCounts = tracker.getIntervalCounts(); int numElements = intervalDurations.length; int j = 0; int subtractor = 0; int lastIndex = intervalDurations.length; if (excludeFirst) { numElements--; j = 1; subtractor = 1; } if (excludeLast) { numElements--; lastIndex--; } double[] avgDurations = new double[numElements]; for ( ; j < lastIndex; j++) { avgDurations[j-subtractor] = 1.0 * intervalDurations[j] / intervalCounts[j]; } String label; switch (statCategory) { case Constants.STAT_CATEGORY_CLIENT_STATS: label = trackersToGraph[i].getClientID(); break; case Constants.STAT_CATEGORY_THREAD_STATS: label = trackersToGraph[i].getThreadID(); break; default: label = "Job " + job.getJobID(); break; } grapher.addDataSet(avgDurations, job.getCollectionInterval(), label); } grapher.setIncludeAverage(includeAverage); grapher.setIncludeRegression(includeRegression); 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 includeAverage = false; boolean includeLegend = false; boolean includeRegression = false; boolean excludeFirst = false; boolean excludeLast = false; boolean includeHGrid = false; boolean includeVGrid = false; boolean baseAtZero = false; String graphTitle = displayName; String legendTitle = "Job ID"; BooleanParameter includeAverageParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_AVERAGE); if (includeAverageParameter != null) { includeAverage = includeAverageParameter.getBooleanValue(); } BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter includeRegressionParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_INCLUDE_REGRESSION); if (includeRegressionParameter != null) { includeRegression = includeRegressionParameter.getBooleanValue(); } BooleanParameter excludeFirstParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_EXCLUDE_FIRST_INTERVAL); if (excludeFirstParameter != null) { excludeFirst = excludeFirstParameter.getBooleanValue(); } BooleanParameter excludeLastParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_EXCLUDE_LAST_INTERVAL); if (excludeLastParameter != null) { excludeLast = excludeLastParameter.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[] trackers = job.getResourceStatTrackers(displayName); TimeTracker tracker = new TimeTracker("", "", displayName, trackers[0].getCollectionInterval()); tracker.aggregate(trackers); StatTracker[] trackersToGraph = new StatTracker[] { tracker }; StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(baseAtZero); grapher.setIncludeLegend(includeLegend, legendTitle); grapher.setVerticalAxisTitle("Average Duration (ms)"); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIgnoreZeroValues(true); for (int i=0; i < trackersToGraph.length; i++) { tracker = (TimeTracker) trackersToGraph[i]; int[] intervalDurations = tracker.getIntervalDurations(); int[] intervalCounts = tracker.getIntervalCounts(); int numElements = intervalDurations.length; int j = 0; int subtractor = 0; int lastIndex = intervalDurations.length; if (excludeFirst) { numElements--; j = 1; subtractor = 1; } if (excludeLast) { numElements--; lastIndex--; } double[] avgDurations = new double[numElements]; for ( ; j < lastIndex; j++) { avgDurations[j-subtractor] = 1.0 * intervalDurations[j] / intervalCounts[j]; } grapher.addDataSet(avgDurations, job.getCollectionInterval(), "Job " + job.getJobID()); } grapher.setIncludeAverage(includeAverage); grapher.setIncludeRegression(includeRegression); 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. * * @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 compareOverTime = false; boolean drawAsBarGraph = false; boolean includeLegend = false; boolean excludeFirst = false; boolean excludeLast = false; boolean includeHGrid = false; boolean includeVGrid = false; boolean baseAtZero = false; String graphTitle = "Comparison of " + displayName; BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter drawAsBarParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_DRAW_AS_BAR_GRAPH); if (drawAsBarParameter != null) { drawAsBarGraph = drawAsBarParameter.getBooleanValue(); } BooleanParameter excludeFirstParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_EXCLUDE_FIRST_INTERVAL); if (excludeFirstParameter != null) { excludeFirst = excludeFirstParameter.getBooleanValue(); } BooleanParameter excludeLastParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_EXCLUDE_LAST_INTERVAL); if (excludeLastParameter != null) { excludeLast = excludeLastParameter.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(); } MultiChoiceParameter compareOverTimeParameter = parameters.getMultiChoiceParameter( Constants.SERVLET_PARAM_COMPARE_TYPE); if (compareOverTimeParameter != null) { String compareStr = compareOverTimeParameter.getStringValue(); compareOverTime = compareStr.equalsIgnoreCase(COMPARE_OVER_TIME_STRING); } if (compareOverTime) { // NYI return null; } else { StatTracker[] trackersToGraph = new StatTracker[jobs.length]; for (int i=0; i < jobs.length; i++) { StatTracker[] jobTrackers = jobs[i].getStatTrackers(displayName); TimeTracker jobTracker = new TimeTracker(null, null, displayName, jobTrackers[0].getCollectionInterval()); jobTracker.aggregate(jobTrackers); trackersToGraph[i] = jobTracker; } StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setBaseAtZero(baseAtZero); grapher.setIncludeLegend(includeLegend, "Job"); grapher.setVerticalAxisTitle("Average Duration (ms)"); grapher.setIncludeHorizontalGrid(includeHGrid); grapher.setIncludeVerticalGrid(includeVGrid); grapher.setIgnoreZeroValues(true); for (int i=0; i < trackersToGraph.length; i++) { TimeTracker tracker = (TimeTracker) trackersToGraph[i]; int[] intervalDurations = tracker.getIntervalDurations(); int[] intervalCounts = tracker.getIntervalCounts(); int numElements = intervalDurations.length; int j = 0; int subtractor = 0; int lastIndex = intervalDurations.length; if (excludeFirst) { numElements--; j = 1; subtractor = 1; } if (excludeLast) { numElements--; lastIndex--; } double[] avgDurations = new double[numElements]; for ( ; j < lastIndex; j++) { avgDurations[j-subtractor] = 1.0 * intervalDurations[j] / intervalCounts[j]; } String label = jobs[i].getJobDescription(); if ((label == null) || (label.length() == 0)) { label = jobs[i].getJobID(); } grapher.addDataSet(avgDurations, jobs[i].getCollectionInterval(), label); } if (drawAsBarGraph) { return grapher.generateBarGraph(); } else { return grapher.generateLineGraph(); } } } /** * 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.setIncludeHorizontalGrid(true); grapher.setIncludeVerticalGrid(true); grapher.setVerticalAxisTitle("Average Duration (ms)"); grapher.setIncludeLegend(false, ""); grapher.setIgnoreZeroValues(false); int[] intervalCounts = getIntervalCounts(); int[] intervalDurations = getIntervalDurations(); double[] graphDurations = new double[intervalCounts.length]; for (int i=0; i< intervalCounts.length; i++) { graphDurations[i] = 1.0 * intervalDurations[i] / intervalCounts[i]; } grapher.addDataSet(graphDurations, collectionInterval, displayName); return grapher.generateLineGraph(); } }