/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.stat; import java.awt.image.BufferedImage; import java.text.DecimalFormat; import java.util.ArrayList; import com.slamd.asn1.ASN1Element; import com.slamd.asn1.ASN1Integer; import com.slamd.asn1.ASN1OctetString; import com.slamd.asn1.ASN1Sequence; import com.slamd.client.Client; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.job.Job; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.MultiChoiceParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; /** * This class defines a stat tracker that can be used to count the number * of times a given event occurs, as well as divide those occurrences into * separate categories. * * * @author Neil A. Wilson */ public class CategoricalTracker implements StatTracker { // 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 in seconds. private int collectionInterval; // The length of time in seconds that this tracker was collecting statistics. private int duration; // The total number of elements across all categories. private int totalCount; // The number of times each category of event has occurred during the current // interval. private int[] intervalCounts; // The number of times each category of event has occurred over the total // interval. private int[] totalCounts; // The time that the current collection interval should stop. private long intervalStopTime; // The time that startTracker was called. private long startTime; // The time that stopTracker was called. private long stopTime; // The client ID of 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 of the client thread that used this stat tracker. private String threadID; // The names associated with each of the categories. private String[] categoryNames; // The list that contains the data collected by this tracker, broken up into // intervals. private ArrayList<int[]> countList; /** * Creates a new categorical tracker intended for use as a placeholder for * decoding purposes. This version of the constructor should not be used * by job classes. */ public CategoricalTracker() { this.clientID = ""; this.threadID = ""; this.displayName = ""; this.collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; decimalFormat = new DecimalFormat("0.000"); intervalCounts = new int[0]; totalCounts = new int[0]; categoryNames = new String[0]; totalCount = 0; intervalStopTime = 0; startTime = System.currentTimeMillis(); stopTime = 0; duration = 0; countList = new ArrayList<int[]>(); hasBeenStarted = false; isRunning = false; } /** * Creates a new categorical tracker with the specified information. * * @param clientID The client ID of the client that used this * stat tracker. * @param threadID The thread ID of the thread that used this * stat tracker. * @param displayName The display name to use for this stat tracker. * @param collectionInterval The collection interval in seconds that * should be used for this stat tracker. */ public CategoricalTracker(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"); intervalCounts = new int[0]; totalCounts = new int[0]; categoryNames = new String[0]; totalCount = 0; intervalStopTime = 0; startTime = System.currentTimeMillis(); stopTime = 0; duration = 0; countList = new ArrayList<int[]>(); 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 CategoricalTracker(clientID, threadID, displayName, collectionInterval); } /** * Increments the count for the specified category. * * @param category The category in which to increment the count. */ public void increment(String category) { long now = System.currentTimeMillis(); totalCount++; // We're in the same interval as the last time the counter was incremented. // Just increment it again. if (now < intervalStopTime) { // See if the specified category is one we already know about. If so, // then update the counter for that category. for (int i=0; i < categoryNames.length; i++) { if (category.equals(categoryNames[i])) { intervalCounts[i]++; totalCounts[i]++; return; } } // We don't have any information about this category, so create a new one. int currentCategories = categoryNames.length; String[] newCategoryNames = new String[currentCategories+1]; int[] newIntervalCounts = new int[currentCategories+1]; int[] newTotalCounts = new int[currentCategories+1]; System.arraycopy(categoryNames, 0, newCategoryNames, 0, categoryNames.length); System.arraycopy(intervalCounts, 0, newIntervalCounts, 0, intervalCounts.length); System.arraycopy(totalCounts, 0, newTotalCounts, 0, totalCounts.length); newCategoryNames[currentCategories] = category; newIntervalCounts[currentCategories] = 1; newTotalCounts[currentCategories] = 1; intervalCounts = newIntervalCounts; totalCounts = newTotalCounts; categoryNames = newCategoryNames; } // The previous interval has stopped and a new one has started. Close out // the old one and start the new. Note that if this is an event that // happens infrequently, then multiple intervals could have passed, so make // sure that the appropriate number of intervals are added. else { // Make a copy of the current interval counts and add it to the list. int[] tmpCounts = new int[intervalCounts.length]; System.arraycopy(intervalCounts, 0, tmpCounts, 0, tmpCounts.length); countList.add(tmpCounts); intervalStopTime += (1000 * collectionInterval); while (intervalStopTime < now) { int[] counts = new int[intervalCounts.length]; for (int i=0; i < counts.length; i++) { counts[i] = 0; } countList.add(counts); intervalStopTime += (1000 * collectionInterval); } boolean matchFound = false; for (int i=0; i < categoryNames.length; i++) { if (category.equals(categoryNames[i])) { intervalCounts[i] = 1; totalCounts[i]++; matchFound = true; } else { intervalCounts[i] = 0; } } if (! matchFound) { int currentCategories = categoryNames.length; String[] newCategoryNames = new String[currentCategories+1]; int[] newIntervalCounts = new int[currentCategories+1]; int[] newTotalCounts = new int[currentCategories+1]; System.arraycopy(categoryNames, 0, newCategoryNames, 0, categoryNames.length); System.arraycopy(intervalCounts, 0, newIntervalCounts, 0, intervalCounts.length); System.arraycopy(totalCounts, 0, newTotalCounts, 0, totalCounts.length); newCategoryNames[currentCategories] = category; newIntervalCounts[currentCategories] = 1; newTotalCounts[currentCategories] = 1; intervalCounts = newIntervalCounts; totalCounts = newTotalCounts; categoryNames = newCategoryNames; } } } /** * 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 " + "categorical stat tracker " + displayName); } else { hasBeenStarted = true; isRunning = true; } // Just in case, reset all the counter info. categoryNames = new String[0]; intervalCounts = new int[0]; totalCounts = new int[0]; countList = new ArrayList<int[]>(); totalCount = 0; // Register this tracker with the persistence thread. Client.registerPersistentStatistic(this); // Set the start time and the interval stop time. long now = System.currentTimeMillis(); startTime = now; intervalStopTime = now + (1000 * collectionInterval); } /** * Indicates that the stat tracker that there will not be any more statistics * collection done and that it should stop its internal timer. */ public void stopTracker() { long now = System.currentTimeMillis(); // If the tracker was never started, then print an error message. // Otherwise, indicate that it is no longer running. if (! hasBeenStarted) { System.err.println("***** WARNING: Attempting to stop categorical " + "stat tracker " + displayName + " without having started it"); } else { isRunning = false; } // If the previous interval had passed since the last update, make sure // that we add the appropriate number of empty intervals. while (intervalStopTime < now) { int[] counts = new int[intervalCounts.length]; for (int i=0; i < counts.length; i++) { counts[i] = 0; } countList.add(counts); intervalStopTime += (1000 * collectionInterval); } // Update the stop time to be the time that the last complete interval // ended and calculate the duration. stopTime = intervalStopTime - (1000 * collectionInterval); duration = (int) ((stopTime - startTime) / 1000); } /** * Indicates whether this stat tracker has been started, regardless of whether * it is currently running. * * @return <CODE>true</CODE> if this stat tracker has been started, or * <CODE>false</CODE> if it has not yet been started. */ public boolean hasBeenStarted() { return hasBeenStarted; } /** * Indicates whether this stat tracker is currently running. * * @return <CODE>true</CODE> if this stat tracker is currently running, or * <CODE>false</CODE> if not. */ public boolean isRunning() { return isRunning; } /** * Indicates that the stat tracker should enable real-time statistics * collection. Note that some stat trackers may not support real-time * statistics collection, in which case this method may be ignored. * * @param statReporter The stat-reporter that should be used to report * real-time statistics to the SLAMD server. * @param jobID The job ID of the job that will be reporting the * data. */ public void enableRealTimeStats(RealTimeStatReporter statReporter, String jobID) { // No implementation required. Categorical trackers do not support // real-time statistics collection. } /** * Retrieves the total number of times that the tracked event occurred over * the total duration. * * @return The total number of times that the tracked event occurred over the * total duration. */ public int getTotalCount() { return totalCount; } /** * Retrieves the total number of times that the tracked event occurred for the * specified category over the total duration. * * @param categoryName The name of the category for which to determine the * total. * * @return The total number of times that the tracked event occurred for the * specified category over the total duration. */ public int getTotalCount(String categoryName) { for (int i=0; i < categoryNames.length; i++) { if (categoryName.equalsIgnoreCase(categoryNames[i])) { int total = 0; for (int j=0; j < countList.size(); j++) { total += countList.get(j)[i]; } return total; } } return 0; } /** * Retrieves the total number of times that the tracked event occurred for * each category over the total duration. The order of the elements in the * returned array will correspond to the order of the elements returned from * the <CODE>getCategoryNames()</CODE> method. * * @return The total number of times that the tracked event occurred for each * category over the total duration. */ public int[] getTotalCounts() { int[] totalCounts = new int[categoryNames.length]; for (int i=0; i < countList.size(); i++) { for (int j=0; j < categoryNames.length; j++) { totalCounts[j] += countList.get(i)[j]; } } return totalCounts; } /** * Retrieves the category names associated with this stat tracker. * * @return The category names associated with this stat tracker. */ public String[] getCategoryNames() { return categoryNames; } /** * Retrieves the set of interval counts by category for this stat tracker. * Note that each element of the array will itself be an array of the counts, * with the elements in the same order as the category names. * * @return The set of interval counts by category for this stat tracker. */ public int[][] getIntervalCounts() { int[][] countsArray = new int[countList.size()][categoryNames.length]; for (int i=0; i < countsArray.length; i++) { int[] counts = countList.get(i); int j; for (j=0; j < counts.length; j++) { countsArray[i][j] = counts[j]; } for ( ; j < categoryNames.length; j++) { countsArray[i][j] = 0; } } return countsArray; } /** * Retrieves the set of interval counts for the specified category. * * @param categoryName The name of the category for which to retrieve the * interval counts. * * @return The interval counts for the specified category, or an empty array * if the specified category is not defined for this tracker. */ public int[] getIntervalCounts(String categoryName) { for (int i=0; i < categoryNames.length; i++) { if (categoryName.equalsIgnoreCase(categoryNames[i])) { int[] counts = new int[countList.size()]; for (int j=0; j < counts.length; j++) { counts[j] = countList.get(j)[i]; } return counts; } } return new int[0]; } /** * Specifies the number of occurrences of the tracked event for each interval, * separated by category names. * * @param categoryNames The names of the categories corresponding to the * values in the interval counts. * @param intervalCounts The number of occurrences of the tracked event * by category by interval. */ public void setIntervalCounts(String[] categoryNames, int[][] intervalCounts) { totalCount = 0; totalCounts = new int[categoryNames.length]; this.categoryNames = categoryNames; countList = new ArrayList<int[]>(); for (int i=0; i < intervalCounts.length; i++) { countList.add(intervalCounts[i]); for (int j=0; j < intervalCounts[i].length; j++) { totalCounts[j] += intervalCounts[i][j]; totalCount += intervalCounts[i][j]; } } duration = collectionInterval * intervalCounts.length; } /** * 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() { return false; } /** * Indicates whether the value associated with this stat tracker is greater * than or equal to the provided value. This is only applicable if * <CODE>isSearchable</CODE> returns <CODE>true</CODE>, and what exactly * "the value of this stat tracker" means will be left up to those stat * trackers that are searchable. * * @param value The value against which the value of this stat tracker is to * be compared. * * @return <CODE>true</CODE> if the value of this stat tracker is greater * than or equal to the provided value, or <CODE>false</CODE> if not. */ public boolean isAtLeast(double value) { return false; } /** * Indicates whether the value associated with this stat tracker is less than * or equal to the provided value. This is only applicable if * <CODE>isSearchable</CODE> returns <CODE>true</CODE>, and what exactly * "the value of this stat tracker" means will be left up to those stat * trackers that are searchable. * * @param value The value against which the value of this stat tracker is to * be compared. * * @return <CODE>true</CODE> if the value of this stat tracker is less than * or equal to the provided value, or <CODE>false</CODE> if not. */ public boolean isAtMost(double value) { return false; } /** * Retrieves the value associated with this stat tracker. This is only * applicable if <CODE>isSearchable</CODE> returns <CODE>true</CODE>, and what * exactly "the value associated with this stat tracker" means will be left up * to those stat trackers that are searchable. * * @return The value associated with this stat tracker. */ public double getSummaryValue() { return 0.0; } /** * Retrieves the number of intervals for which data is available for this stat * tracker. * * @return The number of intervals for which data is available for this stat * tracker. */ public int getNumIntervals() { return countList.size(); } /** * Aggregates the information collected by the provided set of stat trackers * into a single tracker that represents the information gathered from the * entire set of data. All of the stat trackers in the provided array must be * of the same type 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.length == 0) { // If there aren't any trackers in the array, then this tracker will be // empty. clientID = ""; threadID = ""; displayName = ""; collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL; totalCounts = new int[0]; categoryNames = new String[0]; totalCount = 0; countList = new ArrayList<int[]>(); } else if (trackers.length == 1) { // If there was only one tracker provided, then make this tracker look // like it. CategoricalTracker tracker = (CategoricalTracker) trackers[0]; clientID = tracker.clientID; threadID = tracker.threadID; displayName = tracker.displayName; collectionInterval = tracker.collectionInterval; totalCounts = tracker.totalCounts; categoryNames = tracker.categoryNames; totalCount = tracker.totalCount; countList = tracker.countList; } else { // There were multiple trackers provided, so we need to do a little // preliminary work. Iterate through all the trackers and put together // a list of all the tracker names and figure out the maximum number of // intervals. ArrayList<String> categoryNameList = new ArrayList<String>(); int maxIntervals = 0; for (int i=0; i < trackers.length; i++) { CategoricalTracker tracker = (CategoricalTracker) trackers[i]; if (tracker.countList.size() > maxIntervals) { maxIntervals = tracker.countList.size(); } for (int j=0; j < tracker.categoryNames.length; j++) { boolean matchFound = false; for (int k=0; k < categoryNameList.size(); k++) { if (tracker.categoryNames[j].equals(categoryNameList.get(k))) { matchFound = true; break; } } if (! matchFound) { categoryNameList.add(tracker.categoryNames[j]); } } } // Set the category names based on the list compiled from the trackers categoryNames = new String[categoryNameList.size()]; categoryNameList.toArray(categoryNames); // Start with a blank tracker. clientID = trackers[0].getClientID(); threadID = trackers[0].getThreadID(); displayName = trackers[0].getDisplayName(); collectionInterval = trackers[0].getCollectionInterval(); totalCount = 0; countList = new ArrayList<int[]>(maxIntervals); totalCounts = new int[categoryNames.length]; for (int i=0; i < totalCounts.length; i++) { totalCounts[i] = 0; } // Create a new array of ints to hold the global counts int[][] intervalCounts = new int[maxIntervals][categoryNames.length]; for (int i=0; i < intervalCounts.length; i++) { for (int j=0; j < intervalCounts[i].length; j++) { intervalCounts[i][j] = 0; } } // Iterate through all of the trackers and update the data in this tracker // with data from the other trackers. for (int i=0; i < trackers.length; i++) { CategoricalTracker tracker = (CategoricalTracker) trackers[i]; String[] trackerNames = tracker.getCategoryNames(); int[][] trackerCounters = tracker.getIntervalCounts(); // Go through each of the categories in this tracker and figure out // where they are in the global category list. for (int j=0; j < trackerNames.length; j++) { for (int k=0; k < categoryNames.length; k++) { if (trackerNames[j].equals(categoryNames[k])) { // This is the right category, so add all the information from // the current tracker to this tracker. for (int l=0; l < trackerCounters.length; l++) { int numInCategory = trackerCounters[l][j]; intervalCounts[l][k] += numInCategory; totalCounts[k] += numInCategory; totalCount += numInCategory; } break; } } } } // Finally, convert the interval counts array into a list. for (int i=0; i < intervalCounts.length; i++) { countList.add(intervalCounts[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() { StringBuilder returnBuffer = new StringBuilder(); returnBuffer.append(displayName); returnBuffer.append(" -- "); String separator = ""; for (int i=0; i < categoryNames.length; i++) { returnBuffer.append(separator); returnBuffer.append(categoryNames[i]); returnBuffer.append(": "); returnBuffer.append(totalCounts[i]); returnBuffer.append(" ("); returnBuffer.append(decimalFormat.format(100.0 * totalCounts[i] / totalCount)); returnBuffer.append("%)"); separator = "; "; } return returnBuffer.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 returnBuffer = new StringBuilder(); returnBuffer.append(displayName + Constants.EOL); for (int i=0; i < categoryNames.length; i++) { returnBuffer.append("Total "); returnBuffer.append(categoryNames[i]); returnBuffer.append(": "); returnBuffer.append(totalCounts[i]); returnBuffer.append(" ("); returnBuffer.append(decimalFormat.format(100.0 * totalCounts[i] / totalCount)); returnBuffer.append("%)"); returnBuffer.append(Constants.EOL); } int[][] counts = getIntervalCounts(); for (int i=0; i < counts.length; i++) { returnBuffer.append("Interval " + i); for (int j=0; j < counts[i].length; j++) { returnBuffer.append("; "); returnBuffer.append(categoryNames[j]); returnBuffer.append(": "); returnBuffer.append(counts[i][j]); returnBuffer.append(decimalFormat.format(100.0 * counts[i][j] / totalCounts[j])); returnBuffer.append(" ("); returnBuffer.append("%)"); } 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(); for (int i=0; i < categoryNames.length; i++) { html.append("<TABLE BORDER=\"1\">" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD>" + categoryNames[i] + "</TD>" + Constants.EOL); html.append(" </TR>" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD>" + totalCounts[i] + " (" + decimalFormat.format(100.0 * totalCounts[i] / totalCount) + "%)</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(); html.append("<TABLE BORDER=\"1\">" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD>Interval</TD>" + Constants.EOL); for (int i=0; i < categoryNames.length; i++) { html.append(" <TD>" + categoryNames[i] + "</TD>" + Constants.EOL); } html.append(" </TR>" + Constants.EOL); html.append(" <TR>" + Constants.EOL); html.append(" <TD>Total</TD>" + Constants.EOL); for (int i=0; i < categoryNames.length; i++) { html.append(" <TD>" + totalCounts[i] + " (" + decimalFormat.format(100.0 * totalCounts[i] / totalCount) + "%)</TD>" + Constants.EOL); } html.append(" </TR>" + Constants.EOL); int[][] counts = getIntervalCounts(); for (int i=0; i < counts.length; i++) { int intervalTotal = 0; for (int j=0; j < counts[i].length; j++) { intervalTotal += counts[i][j]; } html.append(" <TR>" + Constants.EOL); html.append(" <TD>" + i + "</TD>" + Constants.EOL); for (int j=0; j < counts[i].length; j++) { html.append(" <TD>" + counts[i][j] + " (" + decimalFormat.format(100.0 * counts[i][j] / intervalTotal) + ")</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[] { getDisplayName() }; } /** * 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() { StringBuilder returnBuffer = new StringBuilder(); String separator = ""; for (int i=0; i < categoryNames.length; i++) { returnBuffer.append(separator); returnBuffer.append(categoryNames[i]); returnBuffer.append(": "); returnBuffer.append(totalCounts[i]); returnBuffer.append(" ("); returnBuffer.append(decimalFormat.format(100.0 * totalCounts[i] / totalCount)); returnBuffer.append("%)"); separator = "; "; } return new String[] { returnBuffer.toString() }; } /** * 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) { int[][] countArray = getIntervalCounts(); if (includeLabels) { String[][] returnArray = new String[countArray.length+1][categoryNames.length+1]; returnArray[0][0] = "Interval"; for (int i=0; i < categoryNames.length; i++) { returnArray[0][i+1] = categoryNames[i]; } for (int i=0; i < countArray.length; i++) { returnArray[i+1][0] = String.valueOf(i+1); for (int j=0; j < countArray[i].length; j++) { returnArray[i+1][j+1] = String.valueOf(countArray[i][j]); } } return returnArray; } else { String[][] returnArray = new String[countArray.length][categoryNames.length]; for (int i=0; i < countArray.length; i++) { for (int j=0; j < countArray[i].length; j++) { returnArray[i][j] = String.valueOf(countArray[i][j]); } } 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() { // This data is encoded as a sequence of sequences. The first sequence is // the names of all the categories. The second is the count for each // category for one interval. int numCategories = categoryNames.length; ASN1Element[] elements = new ASN1Element[countList.size() + 1]; ASN1Element[] nameElements = new ASN1Element[numCategories]; for (int i=0; i < numCategories; i++) { nameElements[i] = new ASN1OctetString(categoryNames[i]); } elements[0] = new ASN1Sequence(nameElements); int[][] countArray = getIntervalCounts(); for (int i=0; i < countArray.length; i++) { ASN1Element[] intervalElements = new ASN1Element[numCategories]; for (int j=0; j < intervalElements.length; j++) { intervalElements[j] = new ASN1Integer(countArray[i][j]); } elements[i+1] = new ASN1Sequence(intervalElements); } 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(); ASN1Element[] categoryNameElements = elements[0].decodeAsSequence().getElements(); categoryNames = new String[categoryNameElements.length]; for (int i=0; i < categoryNames.length; i++) { categoryNames[i] = categoryNameElements[i].decodeAsOctetString().getStringValue(); } countList = new ArrayList<int[]>(); totalCount = 0; totalCounts = new int[categoryNames.length]; for (int i=1; i < elements.length; i++) { ASN1Element[] countElements = elements[i].decodeAsSequence().getElements(); int[] counts = new int[countElements.length]; for (int j=0; j< counts.length; j++) { counts[j] = countElements[j].decodeAsInteger().getIntValue(); totalCounts[j] += counts[j]; totalCount += counts[j]; } countList.add(counts); } } 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()); String[] clientIDs = job.getStatTrackerClientIDs(); for (int i=0; i < clientIDs.length; i++) { dataSetList.add("Summary for Client " + clientIDs[i]); } for (int i=0; i < clientIDs.length; i++) { StatTracker[] threadTrackers = job.getStatTrackers(displayName, clientIDs[i]); for (int j=0; j < threadTrackers.length; j++) { dataSetList.add("Detail for Thread " + threadTrackers[j].getThreadID()); } } String[] dataSets = new String[dataSetList.size()]; dataSetList.toArray(dataSets); 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.", 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.", true); BooleanParameter showPercentagesParameter = new BooleanParameter(Constants.SERVLET_PARAM_SHOW_PERCENTAGES, "Show Percentages in Legend", "Indicates whether the graph legend will show " + "the percentage for each category by the " + "category name.", 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, drawAsBarParameter, includeLegendParameter, showPercentagesParameter }; 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 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.", 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.", true); BooleanParameter showPercentagesParameter = new BooleanParameter(Constants.SERVLET_PARAM_SHOW_PERCENTAGES, "Show Percentages in Legend", "Indicates whether the graph legend will show " + "the percentage for each category by the " + "category name.", true); Parameter[] parameters = new Parameter[] { drawAsBarParameter, includeLegendParameter, showPercentagesParameter }; 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 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.", 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 showPercentagesParameter = new BooleanParameter(Constants.SERVLET_PARAM_SHOW_PERCENTAGES, "Show Percentages in Legend", "Indicates whether the graph legend will show " + "the percentage for each category by the " + "category name.", false); Parameter[] parameters = new Parameter[] { drawAsBarParameter, includeLegendParameter, showPercentagesParameter }; return new ParameterList(parameters); } /** * 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 drawAsBarGraph = false; boolean includeLegend = false; boolean showPercentages = false; String detailLevelStr = "Overall Summary for Job " + job.getJobID(); String graphTitle = displayName + " for Job " + job.getJobID(); BooleanParameter drawAsBarParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_DRAW_AS_BAR_GRAPH); if (drawAsBarParameter != null) { drawAsBarGraph = drawAsBarParameter.getBooleanValue(); } BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter showPercentagesParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_SHOW_PERCENTAGES); if (showPercentagesParameter != null) { showPercentages = showPercentagesParameter.getBooleanValue(); } MultiChoiceParameter detailLevelParameter = parameters.getMultiChoiceParameter( Constants.SERVLET_PARAM_DETAIL_LEVEL); if (detailLevelParameter != null) { detailLevelStr = detailLevelParameter.getStringValue(); } if (detailLevelStr.startsWith("Summary for Client ")) { String clientID = detailLevelStr.substring(19); graphTitle = displayName + " for Client " + clientID; StatTracker[] trackers = job.getStatTrackers(displayName, clientID); CategoricalTracker tracker = new CategoricalTracker(clientID, "", displayName, collectionInterval); tracker.aggregate(trackers); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setIncludeLegend(includeLegend, "Category Name"); grapher.setShowPercentages(showPercentages); if (drawAsBarGraph) { return createBarGraph(grapher, tracker); } else { return grapher.generatePieGraph(tracker.categoryNames, tracker.totalCounts); } } else if (detailLevelStr.startsWith("Detail for Thread ")) { String threadID = detailLevelStr.substring(18); graphTitle = displayName + " for Client Thread " + threadID; StatTracker[] trackers = job.getStatTrackers(displayName, clientID); CategoricalTracker tracker = null; for (int i=0; i < trackers.length; i++) { if (trackers[i].getThreadID().equals(threadID)) { tracker = (CategoricalTracker) trackers[i]; break; } } if (tracker == null) { return null; } StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setIncludeLegend(includeLegend, "Category Name"); grapher.setShowPercentages(showPercentages); if (drawAsBarGraph) { return createBarGraph(grapher, tracker); } else { return grapher.generatePieGraph(tracker.categoryNames, tracker.totalCounts); } } else { StatTracker[] trackers = job.getStatTrackers(displayName); CategoricalTracker tracker = new CategoricalTracker("", "", displayName, collectionInterval); tracker.aggregate(trackers); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setIncludeLegend(includeLegend, "Category Name"); grapher.setShowPercentages(showPercentages); if (drawAsBarGraph) { return createBarGraph(grapher, tracker); } else { return grapher.generatePieGraph(tracker.categoryNames, tracker.totalCounts); } } } /** * 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 drawAsBarGraph = false; boolean includeLegend = true; boolean showPercentages = true; String graphTitle = displayName; BooleanParameter drawAsBarParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_DRAW_AS_BAR_GRAPH); if (drawAsBarParameter != null) { drawAsBarGraph = drawAsBarParameter.getBooleanValue(); } BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter showPercentagesParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_SHOW_PERCENTAGES); if (showPercentagesParameter != null) { showPercentages = showPercentagesParameter.getBooleanValue(); } StatTracker[] trackers = job.getResourceStatTrackers(displayName); CategoricalTracker tracker = new CategoricalTracker("", "", displayName, collectionInterval); tracker.aggregate(trackers); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setIncludeLegend(includeLegend, "Category Name"); grapher.setShowPercentages(showPercentages); if (drawAsBarGraph) { return createBarGraph(grapher, tracker); } else { return grapher.generatePieGraph(tracker.categoryNames, tracker.totalCounts); } } /** * 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() { // Categorical trackers are not searchable. 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() { // Categorical trackers are not searchable. 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 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 drawAsBarGraph = false; boolean includeLegend = false; boolean showPercentages = false; String graphTitle = "Comparison of " + displayName; BooleanParameter drawAsBarParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_DRAW_AS_BAR_GRAPH); if (drawAsBarParameter != null) { drawAsBarGraph = drawAsBarParameter.getBooleanValue(); } BooleanParameter includeLegendParameter = parameters.getBooleanParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS); if (includeLegendParameter != null) { includeLegend = includeLegendParameter.getBooleanValue(); } BooleanParameter showPercentagesParameter = parameters.getBooleanParameter( Constants.SERVLET_PARAM_SHOW_PERCENTAGES); if (showPercentagesParameter != null) { showPercentages = showPercentagesParameter.getBooleanValue(); } StatTracker[] trackers = new StatTracker[jobs.length]; for (int i=0; i < jobs.length; i++) { trackers[i] = new CategoricalTracker(null, null, displayName, jobs[i].getCollectionInterval()); StatTracker[] jobTrackers = jobs[i].getStatTrackers(displayName); trackers[i].aggregate(jobTrackers); } CategoricalTracker tracker = new CategoricalTracker(null, null, displayName, collectionInterval); tracker.aggregate(trackers); StatGrapher grapher = new StatGrapher(width, height, graphTitle); grapher.setIncludeLegend(includeLegend, "Category Name"); grapher.setShowPercentages(showPercentages); if (drawAsBarGraph) { return createBarGraph(grapher, tracker); } else { return grapher.generatePieGraph(tracker.categoryNames, tracker.totalCounts); } } /** * Generates a bar graph representation of the information in this categorical * tracker. * * @param grapher The stat grapher that will be used to generate the graph. * @param tracker The categorical tracker containing the data to be graphed. * * @return The bar graph generated from the provided stat tracker. */ public BufferedImage createBarGraph(StatGrapher grapher, CategoricalTracker tracker) { DecimalFormat percentFormat = new DecimalFormat("0.00"); String[] names = tracker.categoryNames; int[] counts = tracker.totalCounts; for (int i=0; i < names.length; i++) { double[] values = new double[] { counts[i] }; String name = names[i]; if (grapher.showPercentages) { name += " (" + percentFormat.format(100.0 * counts[i] / tracker.totalCount) + "%)"; } grapher.addDataSet(values, collectionInterval, name); } return grapher.generateBarGraph(); } /** * 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.setIncludeLegend(true, "Category Name"); grapher.setShowPercentages(true); return grapher.generatePieGraph(categoryNames, totalCounts); } }