package hk.hku.cecid.edi.sfrm.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import hk.hku.cecid.edi.sfrm.handler.MessageStatusQueryHandler;
import hk.hku.cecid.edi.sfrm.spa.SFRMProcessor;
import hk.hku.cecid.piazza.commons.module.ActiveTaskModule;
/**
* @author Patrick Yip
* To regulate the execution interval of the collector, to improve the performance and use as least resource as possible to reach
* the maximum speed
*/
public class BandWidthOptimizer{
private int curRound = 0;
private int maxRound = 20;
private long eiInc = 500;
private long minEI = 500;
private long maxEI = 8000;
private TreeMap<Long, List> intervalSpeedMap = new TreeMap<Long, List>();
private TreeMap<Long, Integer> intervalRetriedMap = new TreeMap<Long, Integer>();
private TreeMap<Long, Double> intervalAvgSpeedMap = new TreeMap<Long, Double>();
private ActiveTaskModule collector;
private long curEI;
private boolean initialized = false;
//The tolerance when the speed will concerned as stable in each round of trial run
private double speedGradientTolerance = 100;
//The tolerance when the transition point will determine
private double eiSpeedGradientTolerance = 50;
private long optimizedEI = -1;
private double optimizedSpeed = -1.0;
private boolean foundOptimized = false;
public BandWidthOptimizer(ActiveTaskModule collector){
this.collector = collector;
}
public void setMaxRound(int maxRound){
this.maxRound = maxRound;
}
public void setEIIncrement(long increment){
this.eiInc = increment;
}
public void setMinExecutionInterval(long minEI){
this.minEI = minEI;
}
public void setMaxExecutionInterval(long maxEI){
this.maxEI = maxEI;
}
public void setSpeedGradientTolerance(double tolerance){
this.speedGradientTolerance = tolerance;
}
public void setEISpeedGradientTolerance(double tolerance){
this.eiSpeedGradientTolerance = tolerance;
}
/**
* Reset the bandwidth optimizer
*/
public void reset(){
intervalSpeedMap.clear();
intervalRetriedMap.clear();
intervalAvgSpeedMap.clear();
initialized = false;
foundOptimized = false;
optimizedSpeed = -1.0;
optimizedEI = -1;
}
public boolean findMaxSpeed(){
if(!initialized){
curEI = minEI;
initialized = true;
//Initialize the interval retried map
incrementIntervalRetriedMap(curEI, intervalRetriedMap);
}
//Mark the speed and interval
double totalSpeed = getTotalSpeed();
if(totalSpeed <= 0)
return false;
updateIntervalSpeed(intervalSpeedMap, curEI, totalSpeed);
//Stop to increment the EI, since the max EI was reached
//Then set the EI to the min EI to reach the maximum speed in the rest of transmition
if(curEI >= maxEI){
foundOptimized = true;
optimizedEI = minEI;
optimizedSpeed = intervalAvgSpeedMap.get(optimizedEI);
collector.setExecutionInterval(minEI);
return true;
}
if(curRound < maxRound - 1){
curRound ++;
}else{
curRound = 0;
if(validateSpeedGradient(curEI, intervalSpeedMap, speedGradientTolerance)){
updateIntervalAvgSpeedMap(curEI, intervalSpeedMap, intervalAvgSpeedMap);
// printAvgSpeedMap(intervalAvgSpeedMap);
foundOptimized = determineOptimizedValue(intervalAvgSpeedMap);
curEI += eiInc;
}else{
intervalSpeedMap.get(curEI).clear();
}
incrementIntervalRetriedMap(curEI, intervalRetriedMap);
}
collector.setExecutionInterval(curEI);
return foundOptimized;
}
private void incrementIntervalRetriedMap(long EI, TreeMap<Long, Integer> retriedMap){
//If exist, increment it
if(retriedMap.containsKey(EI)){
Integer retried = 0;
retried = retriedMap.get(EI);
retried++;
retriedMap.put(EI, retried);
//If not exist, initialize it
}else{
retriedMap.put(EI, 1);
}
}
/**
* Validate whether this round of the test is valid by examine the gradient of this round of test
* On the other hand it is test whether the speed was stabilized for the given EI to the network speed
* @param ei
* @param intervalSpeed
* @param tolerance
* @return whether the current EI is valid (stabilized)
*/
private boolean validateSpeedGradient(long ei, TreeMap<Long, List> intervalSpeed, double tolerance){
List<Double> speedList = intervalSpeed.get(ei);
//Use the head tail gradient approach
double gradient = Math.abs(speedList.get(speedList.size() - 1) - speedList.get(0));
//Use the average gradient approach
// double totalGradient = 0.0;
// for(int i=1; speedList.size() > i;i++){
// totalGradient += Math.abs(speedList.get(i) - speedList.get(i-1));
// }
// double gradient = totalGradient/(speedList.size()-1);
if(tolerance >= gradient){
return true;
}else{
return false;
}
}
private void updateIntervalSpeed(TreeMap<Long, List> eISpeed, long ei, double totalSpeed){
if(!eISpeed.containsKey(ei)){
List<Double> speedList = new ArrayList<Double>();
speedList.add(totalSpeed);
eISpeed.put(ei, speedList);
}else{
List<Double> speedList = eISpeed.get(ei);
speedList.add(totalSpeed);
}
}
private double getTotalSpeed(){
MessageStatusQueryHandler statusQueryHandler = SFRMProcessor.getInstance().getMessageSpeedQueryHandler();
Iterator<String> msgIter = statusQueryHandler.getMessageList();
double totalSpeed = 0.0;
int counter = 0;
while(msgIter.hasNext()){
String msgID = msgIter.next();
StatusQuery query = statusQueryHandler.getMessageSpeedQuery(msgID);
totalSpeed += query.getCurrentSpeed();
counter++;
}
if(counter == 0)
return -1;
return totalSpeed;
}
public void printIntervalSpeedMap(){
Set eiSet = intervalSpeedMap.keySet();
Iterator<Long> eiIter = eiSet.iterator();
SFRMProcessor.getInstance().getLogger().debug(",SegISList,EI,Speeds,");
while(eiIter.hasNext()){
Long ei = eiIter.next();
List speedList = intervalSpeedMap.get(ei);
String speedStr = "";
for(int i=0; speedList.size() > i;i++){
speedStr += speedList.get(i) + ",";
}
SFRMProcessor.getInstance().getLogger().debug(",SegISList," + ei + "," + speedStr + ",");
}
}
public void printIntervalRetriedMap(){
Set eiSet = intervalRetriedMap.keySet();
Iterator<Long> eiIter = eiSet.iterator();
SFRMProcessor.getInstance().getLogger().debug(",IntervalRetried,EI,Retried,");
while(eiIter.hasNext()){
Long ei = eiIter.next();
int retried = intervalRetriedMap.get(ei);
SFRMProcessor.getInstance().getLogger().debug(",IntervalRetried," + ei + "," + retried);
}
}
private void updateIntervalAvgSpeedMap(long ei, TreeMap<Long, List> intervalSpeedMap, TreeMap<Long, Double> intervalAvgSpeedMap){
if(!intervalSpeedMap.containsKey(ei)){
return;
}
List<Double> speeds = intervalSpeedMap.get(ei);
double totalSpeed = 0.0;
for(int i=0; speeds.size()>i;i++){
totalSpeed += speeds.get(i);
}
double avgSpeed = totalSpeed / speeds.size();
intervalAvgSpeedMap.put(ei, avgSpeed);
}
private void printAvgSpeedMap(TreeMap<Long, Double> intervalAvgSpeedMap){
Set<Long> eiSet = intervalAvgSpeedMap.keySet();
Iterator <Long> eiIter = eiSet.iterator();
SFRMProcessor.getInstance().getLogger().debug(",IntervalAvgSpeed,EI,Avg Speed,");
while(eiIter.hasNext()){
long ei = eiIter.next();
double avgSpeed = intervalAvgSpeedMap.get(ei);
SFRMProcessor.getInstance().getLogger().debug(",IntervalAvgSpeed," + ei + "," + avgSpeed + ",");
}
}
/**
* Determine the optimized execution interval from the previous statistics
* @param intervalAvgSpeedMap statistics for execution interval and average speed
* @return execution interval that is corrspending to optimized speed
*/
private boolean determineOptimizedValue(TreeMap<Long, Double> intervalAvgSpeedMap){
Set<Long> eiSet = intervalAvgSpeedMap.keySet();
Iterator<Long> eiIter = eiSet.iterator();
double preSpeed = -1;
long preEI = -1;
while(eiIter.hasNext()){
long ei = eiIter.next();
double curSpeed = intervalAvgSpeedMap.get(ei);
if(preSpeed != -1){
//It is expected that the speed is decreasing, so the curSpeed should be less than preSpeed
if(preSpeed - curSpeed > eiSpeedGradientTolerance){
// SFRMProcessor.getInstance().getLogger().debug(",Optimized," + preEI + "," + preSpeed + ",");
optimizedEI = preEI;
optimizedSpeed = preSpeed;
return true;
}
}
preSpeed = curSpeed;
preEI = ei;
}
// SFRMProcessor.getInstance().getLogger().debug(",Optimized,didn't have optimized,");
return false;
}
//Code for adjusting speed
private double adjustSpeedTolerance = 500.0;
private long adjustEIDelta = 1000;
private long adjustEIMin = 100;
/**
* Adjust the current speed to the specified speed
* @param currentSpeed current speed
* @param expectedSpeed expected speed
* @return the adjusted execution interval
*/
public long adjustSpeed(double currentSpeed, double expectedSpeed, long currentExecutionInterval){
//If the current speed is within the tolerance level, don't adjust the execution interval
if(expectedSpeed - adjustSpeedTolerance <= currentSpeed && expectedSpeed + adjustSpeedTolerance >= currentSpeed){
return currentExecutionInterval;
}else{
long newExecutionInterval = currentExecutionInterval;
if(currentSpeed > expectedSpeed){
newExecutionInterval += adjustEIDelta;
}else{
newExecutionInterval -= adjustEIDelta;
if(newExecutionInterval < 0){
newExecutionInterval = adjustEIMin;
}
}
collector.setExecutionInterval(newExecutionInterval);
curEI = newExecutionInterval;
return newExecutionInterval;
}
}
public double getOptimizedSpeed(){
return optimizedSpeed;
}
public long getOptimizedEI(){
return optimizedEI;
}
public boolean isFoundOptimized(){
return this.foundOptimized;
}
public long getCurrentEI(){
return curEI;
}
public void setAdjustSpeedTolerance(double tolerance){
this.adjustSpeedTolerance = tolerance;
}
public void setAdjustEIDelta(long delta){
this.adjustEIDelta = delta;
}
public void setAdjustEIMin(long min){
this.adjustEIMin = min;
}
}