/*
* 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 using curves based on various sections
* of the sine wave.
* <BR><BR>
* Each invocation of this load variance algorithm should consist of two
* tab-delimited components. The first should be one of the following strings:
* <UL>
* <LI>"concave" -- this indicates that the curve should open downward.</LI>
* <LI>"convex" -- this indicates that the curve should open upward.</LI>
* </UL>
* The second argument should be one of the following strings:
* <UL>
* <LI>"+N" -- this indicates that the actual number of active threads should
* be increased by "N".</LI>
* <LI>"-N" -- this indicates that the actual number of active threads should
* be decreased by "N".</LI>
* <LI>"+N%" -- this indicates that the actual number of active threads should
* be increased by "N" percent of the total number of threads.</LI>
* <LI>"-N%" -- this indicates that the actual number of active threads should
* be decreased by "N" percent of the total number of threads.</LI>
* <LI>"=N" -- this indicates that the actual number of active threads should
* be increased or decreased to "N".</LI>
* <LI>"=N%" -- this indicates that the actual number of active threads should
* be increased or decreased to "N" percent of the total number of
* threads.</LI>
* </UL>
*
*
* @author Neil A. Wilson
*/
public class SineLoadVarianceAlgorithm
extends LoadVarianceAlgorithm
{
/**
* A predefined constant that can make some of the formulas a little shorter.
*/
public static final double PI = Math.PI;
/**
* The shape value that indicates that the resulting curve should open
* downward.
*/
public static final int SHAPE_CONCAVE = 1;
/**
* The shape value that indicates that the resulting curve should open upward.
*/
public static final int SHAPE_CONVEX = 2;
/**
* 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 shape of the variation that should be used by this algorithm.
private int variationShape;
// 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 SineLoadVarianceAlgorithm()
{
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 != 2))
{
throw new SLAMDException("There must be exactly two arguments provided " +
"for the sine load variance algorithm.");
}
String shapeStr = arguments[0];
if (shapeStr.equalsIgnoreCase("concave"))
{
variationShape = SHAPE_CONCAVE;
}
else if (shapeStr.equalsIgnoreCase("convex"))
{
variationShape = SHAPE_CONVEX;
}
else
{
throw new SLAMDException("Invalid shape \"" + shapeStr +
"\" -- should be concave or convex.");
}
String value = arguments[1];
if ((value == null) || (value.length() == 0))
{
throw new SLAMDException("The second argument to the sine 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 second argument to the sine 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
{
// Figure out the change in the number of threads and whether there will be
// an increase or decrease.
boolean increase;
int numThreads;
switch (variationMethod)
{
case METHOD_INCREASE_BY_NUMBER:
increase = true;
numThreads = variationValue;
break;
case METHOD_DECREASE_BY_NUMBER:
increase = false;
numThreads = variationValue;
break;
case METHOD_INCREASE_BY_PERCENT:
increase = true;
numThreads = (totalThreads * variationValue / 100);
break;
case METHOD_DECREASE_BY_PERCENT:
increase = false;
numThreads = (totalThreads * variationValue / 100);
break;
case METHOD_SET_TO_NUMBER:
numThreads = variationValue - activeThreads;
if (numThreads > 0)
{
increase = true;
}
else
{
increase = false;
numThreads = Math.abs(numThreads);
}
break;
case METHOD_SET_TO_PERCENT:
numThreads = (totalThreads * variationValue / 100) - activeThreads;
if (numThreads > 0)
{
increase = true;
}
else
{
increase = false;
numThreads = Math.abs(numThreads);
}
break;
default:
throw new SLAMDException("Invalid load variance method " +
variationMethod);
}
// If there is no change in the number of threads, then we don't need to do
// anything.
if (numThreads == 0)
{
return new int[0][];
}
// If the duration is zero, then just make the change immediately.
if (duration == 0)
{
if (increase)
{
return new int[][]
{
new int[] { 0, numThreads }
};
}
else
{
return new int[][]
{
new int[] { 0, -numThreads }
};
}
}
// Make sure that the specified increase or decrease is still within the
// bounds of the number of available threads.
if (increase && ((activeThreads + numThreads) > totalThreads))
{
numThreads = totalThreads - activeThreads;
}
else if ((! increase) && ((activeThreads - numThreads) < 0))
{
numThreads = activeThreads;
}
// Call the appropriate method to make the calculation based on the variance
// shape.
switch (variationShape)
{
case SHAPE_CONCAVE:
if (increase)
{
return handleConcaveUp(numThreads, duration);
}
else
{
return handleConcaveDown(numThreads, duration);
}
case SHAPE_CONVEX:
if (increase)
{
return handleConvexUp(numThreads, duration);
}
else
{
return handleConvexDown(numThreads, duration);
}
default:
throw new SLAMDException("Invalid shape value " + variationShape);
}
}
/**
* Handle an increase in the number of active threads using a concave up
* shape. The shape will be based on the function f(x) = sin(x).
*
* @param numThreads The actual number of threads that should be activated
* during this process.
* @param duration The length of time in seconds over which the activation
* should be processed.
*
* @return The load variation data that indicates the number of threads to
* activate or deactivate over time.
*/
private int[][] handleConcaveUp(int numThreads, int duration)
{
// The equation solved for y is: y = H*sin((PI*x)/(2*W)).
// Solved for x is: x = (2*W*asin(y/H))/PI.
int durationMillis = duration * 1000;
int[][] returnArray = new int[numThreads][2];
for (int i=0,y=1; i < numThreads; i++,y++)
{
double x = (2.0*durationMillis*Math.asin(1.0*y/numThreads))/PI;
returnArray[i][0] = (int) x;
returnArray[i][1] = 1;
}
return returnArray;
}
/**
* Handle a decrease in the number of active threads using a concave down
* shape. The shape will be based on the function f(x) = sin(x + pi/2).
*
* @param numThreads The actual number of threads that should be deactivated
* during this process.
* @param duration The length of time in seconds over which the
* deactivation should be processed.
*
* @return The load variation data that indicates the number of threads to
* activate or deactivate over time.
*/
private int[][] handleConcaveDown(int numThreads, int duration)
{
// In this case, we'll use cosine which has a nicer period than sine for
// this particular calculation.
// The equation solved for y is: y = H*cos((PI*x)/(2*W)).
// Solved for x is: x = (2*W*acos(y/H))/PI.
int durationMillis = duration * 1000;
int[][] returnArray = new int[numThreads][2];
for (int i=0,y=numThreads; i < numThreads; i++,y--)
{
double x = (2.0*durationMillis*Math.acos(1.0*y/numThreads))/PI;
returnArray[i][0] = (int) x;
returnArray[i][1] = -1;
}
return returnArray;
}
/**
* Handle an increase int the number of active threads using a convex up
* shape. The shape will be based on the function f(x) = 1 + sin(x - pi/2).
*
* @param numThreads The actual number of threads that should be activated
* during this process.
* @param duration The length of time in seconds over which the activation
* should be processed.
*
* @return The load variation data that indicates the number of threads to
* activate or deactivate over time.
*/
private int[][] handleConvexUp(int numThreads, int duration)
{
// In this case, we'll use cosine which has a nicer period than sine for
// this particular calculation.
// The equation solved for y is: y = H*(1-cos((PI*x)/(2*W))).
// Solved for x is: x = (2*W*acos(1-(y/H)))/PI.
int durationMillis = duration * 1000;
int[][] returnArray = new int[numThreads][2];
for (int i=0,y=1; i < numThreads; i++,y++)
{
double x = (2.0*durationMillis*Math.acos(1.0-(1.0*y/numThreads)))/PI;
returnArray[i][0] = (int) x;
returnArray[i][1] = 1;
}
return returnArray;
}
/**
* Handle a decrease int the number of active threads using a convex down
* shape. The shape will be based on the function f(x) = 1 - sin(x).
*
* @param numThreads The actual number of threads that should be deactivated
* during this process.
* @param duration The length of time in seconds over which the
* deactivation should be processed.
*
* @return The load variation data that indicates the number of threads to
* activate or deactivate over time.
*/
private int[][] handleConvexDown(int numThreads, int duration)
{
// The equation solved for y is: y = H*(1-sin((PI*x)/(2*W))).
// Solved for x is: x = (2*W*asin(1-(y/H)))/PI.
int durationMillis = duration * 1000;
int[][] returnArray = new int[numThreads][2];
for (int i=0,y=numThreads; i < numThreads; i++,y--)
{
double x = (2.0*durationMillis*Math.asin(1.0-(1.0*y/numThreads)))/PI;
returnArray[i][0] = (int) x;
returnArray[i][1] = -1;
}
return returnArray;
}
}