/*
* 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 com.slamd.common.SLAMDException;
/**
* This class defines a load variance algorithm that may be used to increase or
* decrease the number of active threads at a constant rate. That is, the
* change in the number of active threads will appear to ramp up or ramp down
* at a fixed angle over time. The change in the number of active threads may
* be specified in one of the following ways:
* <UL>
* <LI>"+N", where N is a fixed number of threads to start over the course of
* this load variation.</LI>
* <LI>"-N", where N is a fixed number of threads to stop over the course of
* this load variation.</LI>
* <LI>"+N%", where N% is a percentage of the overall number of threads per
* client that should be started over the course of this load
* variation.</LI>
* <LI>"-N%", where N% is a percentage of the overall number of threads per
* client that should be stopped over the course of this load
* variation.</LI>
* <LI>"=N", where N is the fixed number of threads that should be running
* once this load variation completes.</LI>
* <LI>"=N%", where N% is a percentage of the overall number of threads per
* client that should be running once this load variation completes.</LI>
* </UL>
*
*
* @author Neil A. Wilson
*/
public class LinearLoadVarianceAlgorithm
extends LoadVarianceAlgorithm
{
/**
* The variation method that indicates that the number of active threads
* should be increased by a fixed amount.
*/
public static final int METHOD_INCREASE_BY_NUMBER = 1;
/**
* The variation method that indicates that the number of active threads
* should be decreased by a fixed amount.
*/
public static final int METHOD_DECREASE_BY_NUMBER = 2;
/**
* The variation method that indicates that the number of active threads
* should be increased by a percentage of the total number of threads.
*/
public static final int METHOD_INCREASE_BY_PERCENT = 3;
/**
* The variation method that indicates that the number of active threads
* should be decreased by a percentage of the total number of threads.
*/
public static final int METHOD_DECREASE_BY_PERCENT = 4;
/**
* The variation method that indicates that the number of active threads
* should be set to a fixed number.
*/
public static final int METHOD_SET_TO_NUMBER = 5;
/**
* The variation method that indicates that the number of active threads
* should be set to a percentage of the total number of threads.
*/
public static final int METHOD_SET_TO_PERCENT = 6;
// The variation method that should be used by this algorithm.
private int variationMethod;
// The value associated with the variation.
private int variationValue;
/**
* This constructor is used to create a new instance of this load variation
* algorithm through reflection. A default constructor must be provided in
* all subclasses, but the only thing that it needs to do is call
* <CODE>super()</CODE>.
*/
public LinearLoadVarianceAlgorithm()
{
super();
}
/**
* Initializes this load variation algorithm based on the provided list of
* arguments.
*
* @param arguments The arguments that may be used to customize the behavior
* of this load variation algorithm.
*
* @throws SLAMDException If a problem occurs while trying to initialize
* this load variation algorithm.
*/
@Override()
public void initializeVariationAlgorithm(String[] arguments)
throws SLAMDException
{
if ((arguments == null) || (arguments.length != 1))
{
throw new SLAMDException("There must be exactly one argument provided " +
"for the linear load variance algorithm.");
}
String value = arguments[0];
if ((value == null) || (value.length() == 0))
{
throw new SLAMDException("The argument to the linear load variance " +
"algorithm may not be blank.");
}
boolean endsWithPercent = value.endsWith("%");
String valueString = null;
switch (value.charAt(0))
{
case '+':
if (endsWithPercent)
{
variationMethod = METHOD_INCREASE_BY_PERCENT;
valueString = value.substring(1, value.length() - 1);
}
else
{
variationMethod = METHOD_INCREASE_BY_NUMBER;
valueString = value.substring(1);
}
break;
case '-':
if (endsWithPercent)
{
variationMethod = METHOD_DECREASE_BY_PERCENT;
valueString = value.substring(1, value.length() - 1);
}
else
{
variationMethod = METHOD_DECREASE_BY_NUMBER;
valueString = value.substring(1);
}
break;
case '=':
if (endsWithPercent)
{
variationMethod = METHOD_SET_TO_PERCENT;
valueString = value.substring(1, value.length() - 1);
}
else
{
variationMethod = METHOD_SET_TO_NUMBER;
valueString = value.substring(1);
}
break;
default:
throw new SLAMDException("The argument to the linear load variance " +
"algorithm must start with '+', '-', or '='.");
}
try
{
variationValue = Integer.parseInt(valueString);
}
catch (Exception e)
{
throw new SLAMDException("Unable to parse '" + valueString +
"' as an integer.");
}
if (variationValue < 0)
{
throw new SLAMDException("The load variance value may not be negative.");
}
if (endsWithPercent && (variationValue > 100))
{
throw new SLAMDException("Percentage values given may not exceed 100.");
}
}
/**
* Retrieves a two-dimensional array that provides information about the
* increase or decrease in active job threads that should be applied over
* time. Each element of the array returned should itself be a two-element
* array with the first element being the number of milliseconds since the
* start of this load variance instruction that the increase or decrease
* should occur, and the second is an integer value that indicates the number
* of threads that should be added at that time (may be negative if threads
* are to be removed).
*
* @param duration The length of time in seconds over which this load
* variance algorithm should operate.
* @param totalThreads The total number of threads that have been scheduled
* for the job with which this algorithm is to be used.
* @param activeThreads The number of threads that are already active at
* the time that this method is called.
*
* @return A two-dimensional array that provides information about the
* increase or decrease in active job threads that should be applied
* over time.
*
* @throws SLAMDException If a problem occurs while calculating the variance
* array.
*/
@Override()
public int[][] calculateVariance(int duration, int totalThreads,
int activeThreads)
throws SLAMDException
{
switch (variationMethod)
{
case METHOD_INCREASE_BY_NUMBER:
if ((activeThreads + variationValue) > totalThreads)
{
return handleFixedChange(true, (totalThreads-activeThreads),
duration);
}
else
{
return handleFixedChange(true, variationValue, duration);
}
case METHOD_DECREASE_BY_NUMBER:
return handleFixedChange(false, Math.min(activeThreads, variationValue),
duration);
case METHOD_INCREASE_BY_PERCENT:
int numThreads = totalThreads * variationValue / 100;
if ((activeThreads + numThreads) > totalThreads)
{
return handleFixedChange(true, (totalThreads-activeThreads),
duration);
}
else
{
return handleFixedChange(true, numThreads, duration);
}
case METHOD_DECREASE_BY_PERCENT:
numThreads = totalThreads * variationValue / 100;
return handleFixedChange(false, Math.min(activeThreads, numThreads),
duration);
case METHOD_SET_TO_NUMBER:
numThreads = Math.min(variationValue, totalThreads);
if (numThreads == activeThreads)
{
return new int[0][];
}
else if (numThreads > activeThreads)
{
return handleFixedChange(true, (numThreads-activeThreads), duration);
}
else
{
return handleFixedChange(false, (activeThreads-numThreads), duration);
}
case METHOD_SET_TO_PERCENT:
numThreads = totalThreads * variationValue / 100;
if (numThreads == activeThreads)
{
return new int[0][];
}
else if (numThreads > activeThreads)
{
return handleFixedChange(true, (numThreads-activeThreads), duration);
}
else
{
return handleFixedChange(false, (activeThreads-numThreads), duration);
}
default:
throw new SLAMDException("Invalid variation method: " +
variationMethod);
}
}
/**
* Handles the work of increasing or decreasing the number of active threads
* by a specified number at a constant rate over the given duration.
*
* @param increase Indicates whether the number of active threads should be
* increased or decreased.
* @param number The number of threads to increase or decrease.
* @param duration The length of time in seconds over which the increase or
* decrease should occur.
*
* @return A two-dimensional array that provides information about the
* increase or decrease in active job threads that should be applied
* over time.
*/
private int[][] handleFixedChange(boolean increase, int number,
int duration)
{
if (number == 0)
{
return new int[0][];
}
if (duration == 0)
{
if (increase)
{
return new int[][]
{
new int[] { 0, number }
};
}
else
{
return new int[][]
{
new int[] { 0, -number }
};
}
}
int durationMillis = duration * 1000;
int timeBetweenChanges = durationMillis / number;
int[][] varianceArray = new int[number][2];
for (int i=0; i < number; i++)
{
varianceArray[i][0] = (i+1) * timeBetweenChanges;
varianceArray[i][1] = (increase ? 1 : -1);
}
return varianceArray;
}
}