package com.yahoo.dtf.actions.flowcontrol;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.regex.Pattern;
import com.yahoo.dtf.actions.Action;
import com.yahoo.dtf.exception.DTFException;
import com.yahoo.dtf.exception.ParseException;
/**
* @dtf.tag choices
*
* @dtf.since 1.0
* @dtf.author Rodney Gomes
*
* @dtf.tag.desc Choices tag can be used at any point you want to execute a
* group of different actions with a probability associated with
* executing each of the actions. This way you can generate a load
* pattern that is a bit more complex than just the number of
* operations per second.
*
* @dtf.tag.example
* <distribute workers="3" iterations="1..10">
* <choices>
* <choose howoften="25%">
* <event name="test.event2"/>
* </choose>
* <choose howoften="25%">
* <event name="test.event1"/>
* </choose>
* <choose howoften="50%">
* <event name="test.event3"/>
* </choose>
* </choices>
* </distribute>
*
* @dtf.tag.example
* <for property="i" range="1..1000">
* <choices>
* <choose howoften="5%">
* <event name="test.event2"/>
* </choose>
* <choose howoften="5%">
* <event name="test.event1"/>
* </choose>
* <choose howoften="90%">
* <event name="test.event3"/>
* </choose>
* </choices>
* </for>
*
*/
public class Choices extends Action {
private static final String CHOICESTATE = "dtf.choices.state";
private static class Chosen {
public Choose choose = null;
public double chosen = 0;
public double howoften = 0;
}
private static class ChoiceState {
public long totalchoices = 1;
public ArrayList<Chosen> chosen = null;
}
private static Pattern pattern = Pattern.compile("[0-9]?[0-9]?[0-9]%");
private Integer percToInt(String value) throws ParseException {
if (!pattern.matcher(value).matches())
throw new ParseException("Percentage value expected for ["
+ value + "] should match " + pattern.toString());
try {
value = value.replace("%","");
return new Integer(value);
} catch (NumberFormatException e) {
throw new ParseException("Value is not a Integer [" + value + "]",e);
}
}
private static Object _csLock = new Object();
private HashMap<String, ChoiceState> getChoiceStates() {
synchronized (_csLock) {
HashMap<String, ChoiceState> cs =
(HashMap<String, ChoiceState>)getGlobalContext(CHOICESTATE);
if (cs == null) {
cs = new HashMap<String, ChoiceState>();
registerGlobalContext(CHOICESTATE, cs);
}
return cs;
}
}
public void execute() throws DTFException {
HashMap<String, ChoiceState> css = getChoiceStates();
ChoiceState cs = null;
// synchronize on the Choices list for all threads to find if we already
// have the ChoiceState object created otherwise create it just once.
synchronized (css) {
cs = css.get(""+ this.hashCode());
if (cs == null) {
cs = new ChoiceState();
ArrayList<Choose> choices = findActions(Choose.class);
// sort the choices by the percentage number ;)
Collections.sort(choices, new Comparator<Choose>() {
public int compare(Choose o1, Choose o2) {
try {
Integer thisWhen = percToInt(o1.getHowoften());
Integer otherWhen = percToInt(o2.getHowoften());
// reverse order
return otherWhen.compareTo(thisWhen);
} catch (ParseException e) {
return 0;
}
}
});
ArrayList<Chosen> chosen = new ArrayList<Chosen>();
for(int i = 0; i < choices.size(); i++) {
Chosen aux = new Chosen();
aux.choose = choices.get(i);
aux.howoften = percToInt(aux.choose.getHowoften())/100.0f;
chosen.add(aux);
}
cs = new ChoiceState();
cs.chosen = chosen;
css.put(""+this.hashCode(), cs);
}
}
Chosen chosen = null;
synchronized (cs) {
int whonext = -1;
double maxdist = Long.MIN_VALUE;
int total = 0;
for (int i = 0; i < cs.chosen.size(); i++) {
Chosen aux = cs.chosen.get(i);
/*
* Calculate the percentage of times I have been chosen and then
* calculate the distance between that and the actual percentage of
* tiem I should have been chosen.
*/
double got = (aux.chosen/cs.totalchoices);
double distance = aux.howoften-got;
total += aux.howoften * 100;
/*
* The next choice should be the one with the biggest distance left
* to go to be at the percentage that we are requiring.
*/
if (distance > maxdist) {
whonext = i;
maxdist = distance;
}
}
if ( total > 100 )
throw new ParseException("Adding up choices yields more than 100% at [" + total + "]");
chosen = cs.chosen.get(whonext);
if ((chosen.chosen/cs.totalchoices) <= chosen.howoften) {
chosen.chosen++;
} else {
chosen = null;
}
cs.totalchoices++;
}
if ( chosen != null )
chosen.choose.execute();
}
}