/* * 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.loadvariance; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.StringTokenizer; import com.slamd.common.Constants; import com.slamd.job.JobClass; import com.slamd.job.UnableToRunException; import com.slamd.parameter.FileURLParameter; import com.slamd.parameter.InvalidValueException; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; /** * This class defines a SLAMD job that varies the load that it generates over * time based on input read from a file that controls the number of threads that * should be active at any given time. It works by creating and starting all * threads at the beginning of the job but making them remain inactive until * they are needed. * * * @author Neil A. Wilson */ public abstract class LoadVarianceJobClass extends JobClass { /** * The default length of time in milliseconds that an idle thread should sleep * between checks to determine whether it is time to start running. */ public static final int DEFAULT_IDLE_SLEEP_DURATION = 100; /** * The name of the parameter that will be used to specify the URL to the file * containing the load definition. */ public static final String PARAM_LOAD_DEFINITION = "load_definition"; // Indicates whether the job should loop through the load variance definition // once the end has been reached. public static boolean loopVarianceDefinition; // An array that is used to indicate whether each of the threads should // currently be active or inactive. public static boolean[] threadsActive; // The length of time in milliseconds that each thread should sleep while it // is idle before waking up to check to see if it should start running again. public static int idleSleepDuration; // An array that stores the duration between changes in the number of active // threads and the increase or decrease to make at those times. public static int[][] varianceData; // The reference to the thread that is used to keep track of starting and // stopping each of the individual job threads. public static LoadVarianceControlThread controlThread; /** * The default constructor used to create a new instance of the search thread. * The only thing it should do is to invoke the superclass constructor. All * other initialization should be performed in the <CODE>initialize</CODE> * method. */ public LoadVarianceJobClass() { super(); } /** * Retrieves the parameter that is used to specify the URL to the data file * containing the variable load definition. * * @return The parameter that is used to specify the URL to the data file * containing the variable load definition. */ public final Parameter getVariableLoadParameterStub() { return new FileURLParameter(PARAM_LOAD_DEFINITION, "Load Definition URL", "The URL to the file containing the load " + "definition to use for the job.", null, true); } /** * Validates the information in the load definition file both for its syntax * and for plausibility when used with the other provided information. * * @param threadsPerClient The number of threads per client that have been * scheduled for this job. * @param startTime The time that this job should start running. * @param stopTime The time that this job should stop running. * @param duration The maximum length of time in seconds that the * job should be allowed to run. * @param parameters The set of parameters associated with this job, * including the load definition URL parameter. * * @throws InvalidValueException If an error occurs during validation. */ public final void validateLoadDefinition(int threadsPerClient, Date startTime, Date stopTime, int duration, ParameterList parameters) throws InvalidValueException { // Get the contents of the load definition file. FileURLParameter loadFileParameter = parameters.getFileURLParameter(PARAM_LOAD_DEFINITION); if ((loadFileParameter == null) || (! loadFileParameter.hasValue())) { throw new InvalidValueException("No load definition URL was provided."); } String[] loadDefinitionLines; try { loadDefinitionLines = loadFileParameter.getNonBlankFileLines(); } catch (Exception e) { throw new InvalidValueException("Unable to retrieve the contents of " + "load definition file: " + e, e); } if ((loadDefinitionLines == null) || (loadDefinitionLines.length == 0)) { throw new InvalidValueException("No data was found in the load " + "definition file."); } // Parse each line to ensure that it is valid and that it does not attempt // to create a total number of threads greater than what has been scheduled. for (int i=0; i < loadDefinitionLines.length; i++) { // Each line should be tab-delimited using the following format: // 1. The length of time in seconds that should elapse between the end // of the previous instruction and the beginning of this one. // 2. The length of time over which this instruction should be carried // out. // 3. The fully-qualified name of the Java class that defines the // variation algorithm. // 4+ Any arguments that should be provided to the variation algorithm, // including the change in threads over time. try { StringTokenizer tokenizer = new StringTokenizer(loadDefinitionLines[i], "\t"); int delayBeforeExecution = Integer.parseInt(tokenizer.nextToken()); int durationOfVariance = Integer.parseInt(tokenizer.nextToken()); String algorithmName = tokenizer.nextToken(); ArrayList<String> argumentList = new ArrayList<String>(); while (tokenizer.hasMoreTokens()) { argumentList.add(tokenizer.nextToken()); } String[] arguments = new String[argumentList.size()]; argumentList.toArray(arguments); Class<?> algorithmClass = Constants.classForName(algorithmName); LoadVarianceAlgorithm loadVariationAlgorithm = (LoadVarianceAlgorithm) algorithmClass.newInstance(); loadVariationAlgorithm.initializeVariationAlgorithm(arguments); } catch (Exception e) { throw new InvalidValueException("Error while trying to parse line " + (i+1) + " of load definition file: " + e, e); } } } /** * Initializes the logic that will be used to generate load for this thread. * This should be called by the <CODE>initializeClient</CODE> method, as it * uses static variables to determine the logic to use for all threads. The * <CODE>runJob</CODE> method will be used to apply this definition on a * per-thread basis. * * @param parameters The parameter list containing the parameters for this * job, including the load definition URL parameter. * * @throws UnableToRunException If a problem occurs while parsing the load * definition file that would keep this job * from running properly. */ public final void initializeVariableLoad(ParameterList parameters) throws UnableToRunException { // Initialize variables that we will need to convert the contents of the // load definition file into actual numbers. ArrayList<int[]> varianceList = new ArrayList<int[]>(); int activeThreads = 0; int currentOffset = 0; int totalThreads = getClientSideJob().getThreadsPerClient(); // Set the default idle sleep time. idleSleepDuration = DEFAULT_IDLE_SLEEP_DURATION; // Indicate that the load variance definition should not be looped by // default. loopVarianceDefinition = false; // Get the contents of the load definition file. FileURLParameter loadFileParameter = parameters.getFileURLParameter(PARAM_LOAD_DEFINITION); if ((loadFileParameter == null) || (! loadFileParameter.hasValue())) { throw new UnableToRunException("No load definition URL was provided."); } String[] loadDefinitionLines; try { loadDefinitionLines = loadFileParameter.getNonBlankFileLines(); } catch (Exception e) { throw new UnableToRunException("Unable to retrieve the contents of " + "load definition file: " + e, e); } if ((loadDefinitionLines == null) || (loadDefinitionLines.length == 0)) { throw new UnableToRunException("No data was found in the load " + "definition file."); } // Parse each line to ensure that it is valid and that it does not attempt // to create a total number of threads greater than what has been scheduled. for (int i=0; i < loadDefinitionLines.length; i++) { // Each line should be tab-delimited using the following format: // 1. The length of time in seconds that should elapse between the end // of the previous instruction and the beginning of this one. // 2. The length of time over which this instruction should be carried // out. // 3. The fully-qualified name of the Java class that defines the // variation algorithm. // 4+ Any arguments that should be provided to the variation algorithm, // including the change in threads over time. try { StringTokenizer tokenizer = new StringTokenizer(loadDefinitionLines[i], "\t"); int delayBeforeExecution = Integer.parseInt(tokenizer.nextToken()); int durationOfVariance = Integer.parseInt(tokenizer.nextToken()); String algorithmName = tokenizer.nextToken(); ArrayList<String> argumentList = new ArrayList<String>(); while (tokenizer.hasMoreTokens()) { argumentList.add(tokenizer.nextToken()); } String[] arguments = new String[argumentList.size()]; argumentList.toArray(arguments); Class<?> algorithmClass = Constants.classForName(algorithmName); LoadVarianceAlgorithm varianceAlgorithm = (LoadVarianceAlgorithm) algorithmClass.newInstance(); varianceAlgorithm.initializeVariationAlgorithm(arguments); int[][] varianceInfo = varianceAlgorithm.calculateVariance(durationOfVariance, totalThreads, activeThreads); currentOffset += (1000 * delayBeforeExecution); for (int j=0; j < varianceInfo.length; j++) { int[] varianceElement = varianceInfo[j]; varianceElement[0] += currentOffset; activeThreads += varianceElement[1]; varianceList.add(varianceElement); } currentOffset += (1000 * durationOfVariance); } catch (Exception e) { e.printStackTrace(); throw new UnableToRunException("Error while trying to parse line " + (i+1) + " of load definition file: " + e, e); } } // Update the array containing the variance data. varianceData = new int[varianceList.size()][]; for (int i=0; i < varianceData.length; i++) { varianceData[i] = varianceList.get(i); } // Create the array that will be used to control the actions of each of the // threads. threadsActive = new boolean[totalThreads]; Arrays.fill(threadsActive, false); // Create the thread that will be used to keep track of all the actual job // threads. controlThread = new LoadVarianceControlThread(this); controlThread.start(); } /** * Retrieves the length of time in milliseconds that each thread will sleep * while it is idle before checking to determine whether it is time to start * running. * * @return The length of time in milliseconds that each thread should sleep * while it is idle before checking to determine if it is time to * start running. */ public int getIdleSleepDuration() { return idleSleepDuration; } /** * Specifies the length of time in milliseconds that each thread should sleep * while it is idle before checking to determine whether it is time to start * running. Note that this method should always be called after the call to * <CODE>initializeVariableLoad</CODE> or it will be overridden. * * @param idleSleepDuration The length of time in milliseconds that each * thread should sleep while it is idle before * checking to determine whether it is time to * start running. */ public void setIdleSleepDuration(int idleSleepDuration) { this.idleSleepDuration = idleSleepDuration; } /** * Indicates whether the job should loop back through the load variance * definition when the end is reached. If not, then only those threads that * were active at the end of the load variance definition will continue to be * used for the remainder of the job. * * @return <CODE>true</CODE> if the job should loop back through the load * variance definition when the end is reached, or <CODE>false</CODE> * if not. */ public boolean loopVarianceDefinition() { return loopVarianceDefinition; } /** * Specifies whether the job should loop back through the load variance * definition when the end is reached. * * @param loopVarianceDefinition Indicates whether the job should loop back * through the load variance definition when * the end is reached. */ public void setLoopVarianceDefinition(boolean loopVarianceDefinition) { this.loopVarianceDefinition = loopVarianceDefinition; } /** * Performs the processing associated with this job. In the generic case, it * completes the initialization and then enters a loop that waits until an * indication is received that the thread should start processing. If it is * notified to temporarily stop, then it will do so and wait for either a * permanent stop request or notification that it should start again. Once a * permanent stop request is received, then this method will perform * finalization and exit. */ @Override() public final void runJob() { doStartup(); controlThread.startRunning(); while (true) { if (threadsActive[getThreadNumber()] && (! shouldStop())) { doPreProcessing(); doProcessing(); doPostProcessing(); } else { if (shouldStop()) { break; } else { try { Thread.sleep(idleSleepDuration); } catch (Exception e) {} } } } controlThread.stopRunning(); doShutdown(); } /** * Indicates whether this thread should temporarily pause its execution or * stop altogether. This method should be periodically called by the * <CODE>doProcessing</CODE> method, and if it returns <CODE>true</CODE> then * <CODE>doProcessing</CODE> should exit. * * @return <CODE>true</CODE> if the thread should pause or stop its * execution, or <CODE>false</CODE> if not. */ public final boolean shouldPauseOrStop() { if (threadsActive[getThreadNumber()]) { if (shouldStop()) { threadsActive[getThreadNumber()] = false; return true; } else { return false; } } else { return true; } } /** * Performs any processing that should be done immediately before each call to * <CODE>doProcessing()</CODE>. By default, no action is performed. */ public void doPreProcessing() { // No implementation required by default. } /** * Performs any processing that should be done immediately after each call to * <CODE>doProcessing()</CODE>. By default, no action is performed. */ public void doPostProcessing() { // No implementation required by default. } /** * Performs any processing that should be done at the very beginning of * execution before any actual processing is performed. This should include * starting all stat trackers for the job. */ public abstract void doStartup(); /** * Performs any processing that should be done at the very end of execution * after all actual processing has completed. This should include stopping * all stat trackers for the job. */ public abstract void doShutdown(); /** * Performs the actual processing for this job. It should periodically call * the <CODE>shouldPauseOrStop</CODE> method and if it returns * <CODE>true</CODE> then this method should exit. */ public abstract void doProcessing(); }