package com.limegroup.gnutella.udpconnect;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Calculate and control the timing of data writing.
*/
public class WriteRegulator {
private static final Log LOG =
LogFactory.getLog(WriteRegulator.class);
/** Don't adjust the skipping of sleeps until the window has initialized */
private static final int MIN_START_WINDOW = 40;
/** When the window space hits this size, it is low */
private static final int LOW_WINDOW_SPACE = 4;
/** Cap the quick sending of blocks at this number */
private static final int MAX_SKIP_LIMIT = 14;
/** The expected failure rate at optimal throughput */
private static final float TARGET_FAILURE_RATE = 3f / 100f;
/** The low failure rate at optimal throughput */
private static final float LOW_FAILURE_RATE = 3f / 100f;
/** The high failure rate at optimal throughput */
private static final float HIGH_FAILURE_RATE = 4f / 100f;
private DataWindow _sendWindow;
private int _skipCount = 0;
private int _skipLimit = 2;
private boolean _limitHit = false;
private int _limitCount = 0;
private int _limitReset = 200;
private int _zeroCount = 0;
/** Keep track of how many successes/failures there are in
writing messages */
private FailureTracker _tracker;
public WriteRegulator( DataWindow sendWindow ) {
_sendWindow = sendWindow;
_tracker = new FailureTracker();
}
/**
* When a resend is required and the failure rate is too high,
* scale down activity.
*/
public void hitResendTimeout() {
if ( (!_limitHit || _limitCount >= 10) &&
_tracker.failureRate() > HIGH_FAILURE_RATE ) {
_limitHit = true;
_skipLimit /= 2;
_limitCount = 0;
if(LOG.isDebugEnabled())
LOG.debug("hitResendTimeout _skipLimit = "+_skipLimit+
" fR="+_tracker.failureRateAsString());
_tracker.clearOldFailures();
}
}
/**
* When the send window keeps getting hit, slow down activity.
*/
public void hitZeroWindow() {
_zeroCount++;
if ( (!_limitHit || _limitCount >= 10) && _zeroCount > 4) {
// Doing nothing for now since this is irrelevent to the skipping
//
//_limitHit = true;
//_skipLimit /= 2;
//_limitCount = 0;
_zeroCount = 0;
if(LOG.isDebugEnabled())
LOG.debug("hitZeroWindow _skipLimit = "+_skipLimit+
" fR="+_tracker.failureRateAsString());
}
}
/**
* Compute how long the sleep time should be before the next write.
*/
public long getSleepTime(long currTime, int receiverWindowSpace) {
//------------- Sleep ------------------------
// Sleep a fraction of rtt for specified window increment
int usedSpots = _sendWindow.getUsedSpots();
int windowSize = _sendWindow.getWindowSize();
long windowStart = _sendWindow.getWindowStart();
int rto = _sendWindow.getRTO();
float rttvar = _sendWindow.getRTTVar();
float srtt = _sendWindow.getSRTT();
int isrtt = (int) srtt;
int rtt;
int realRTT = isrtt;//_sendWindow.averageRoundTripTime();
int lowRTT = _sendWindow.lowRoundTripTime();
int smoothRTT = isrtt;//_sendWindow.smoothRoundTripTime();
int sentWait = isrtt;//_sendWindow.calculateWaitTime( currTime, 3);
rtt = sentWait + 1;
if (rtt == 0)
rtt = 10;
int baseWait = Math.min(realRTT, 2000)/4;
//
// Want to ideally achieve a steady state location in writing and
// reading window. Don't want to get too far ahead or too far behind
//
int sleepTime = ((usedSpots+1) * baseWait);
int minTime = 0;
int gettingSlow = 0;
// Ensure the sleep time is fairly distributed in the normal case
if ( sleepTime < windowSize ) {
double pct = (double) sleepTime / (double) windowSize;
if ( Math.random() < pct )
sleepTime = 1;
else
sleepTime = 0;
} else {
sleepTime = sleepTime / windowSize;
}
// Create a sleeptime specific to having almost no room left to send
// more data
if ( receiverWindowSpace <= LOW_WINDOW_SPACE ) {
// Scale up the sleep time to a full timeout as you approach
// zero space for writing
int multiple = LOW_WINDOW_SPACE / Math.max(1, receiverWindowSpace);
sleepTime = (((int)srtt) * multiple) / (LOW_WINDOW_SPACE + 1);
if ( receiverWindowSpace <= (LOW_WINDOW_SPACE/2) ) {
sleepTime = rto;
if(LOG.isDebugEnabled())
LOG.debug("LOW_WINDOW sT:"+sleepTime);
}
minTime = sleepTime;
}
if(LOG.isDebugEnabled())
LOG.debug(
"sleepTime:"+sleepTime+
" uS:"+usedSpots+
" RWS:"+receiverWindowSpace+
" smoothRTT:"+smoothRTT+
" realRTT:"+realRTT+
" rtt:"+rtt+
" RTO:"+rto+
" RTTVar:"+rttvar+
" srtt:"+srtt+
" sL:"+_skipLimit +
" fR="+_tracker.failureRateAsString());
if ( _skipLimit < 1 )
_skipLimit = 1;
// Reset Timing if you are going to wait less than rtt or
// RTT has elevated too much
// Compute a max target RTT given the bandwidth capacity
int maxRTT;
if ( smoothRTT > ((5*lowRTT)/2) ) { // If avg much greater than low
// Capacity is limited so kick in quickly
maxRTT = ((lowRTT*7) / 5);
} else {
// Capacity doesn't seem to be limited so only kick in if extreme
maxRTT = ((lowRTT*25) / 5);
}
// We want at least 2 round trips per full window time
// so find out how much you would wait for half a window
int windowDelay =
(((baseWait * windowSize) / _skipLimit) * 2) / 4;
// If our RTT time is going up, figure out what to do
if ( rtt != 0 && baseWait != 0 &&
receiverWindowSpace <= LOW_WINDOW_SPACE &&
(windowDelay < rtt || rtt > maxRTT) ) {
if(LOG.isDebugEnabled())
LOG.debug(
" -- MAX EXCEED "+
" RTT sL:"+_skipLimit + " w:"+ windowStart+
" Rrtt:"+realRTT+ " base :"+baseWait+
" uS:"+usedSpots+" RWS:"+receiverWindowSpace+
" lRTT:"+_sendWindow.lowRoundTripTime()+
" sWait:"+sentWait+
" mRTT:"+maxRTT+
" wDelay:"+windowDelay+
" sT:"+sleepTime);
// If we are starting to affect the RTT,
// then ratchet down the accelorator
/*
if ( realRTT > ((3*lowRTT)) || rtt > (3*lowRTT) ) {
_limitHit = true;
_skipLimit /= 2;
if(LOG.isDebugEnabled())
LOG.debug(
" -- LOWER SL "+
" rRTT:"+realRTT+
" lRTT:"+lowRTT+
" rtt:"+rtt+
" sL:"+_skipLimit);
}
*/
// If we are majorly affecting the RTT, then slow down right now
if ( rtt > maxRTT || realRTT > maxRTT ) {
minTime = lowRTT / 4;
if ( gettingSlow == 0 )
_skipLimit--;
gettingSlow = 50;
//sleepTime = (16*rtt) / 7;
if(LOG.isDebugEnabled())
LOG.debug(
" -- UP SLEEP "+
" rtt:"+rtt+
" mRTT:"+maxRTT+
" rRTT:"+realRTT+
" lRTT:"+lowRTT+
" sT:"+sleepTime);
}
}
// Cycle through the accelerator states and enforced backoff
if ( _skipLimit < 1 )
_skipLimit = 1;
_skipCount = (_skipCount + 1) % _skipLimit;
if ( !_limitHit ) {
// Bump up the skipLimit occasionally to see if we can handle it
if (_skipLimit < MAX_SKIP_LIMIT &&
windowStart%windowSize == 0 &&
gettingSlow == 0 &&
windowStart > MIN_START_WINDOW &&
_tracker.failureRate() < LOW_FAILURE_RATE ) {
if(LOG.isDebugEnabled())
LOG.debug("up _skipLimit = "+_skipLimit);
_skipLimit++;
if(LOG.isDebugEnabled())
LOG.debug(" -- UPP sL:"+_skipLimit);
}
} else {
// Wait before trying to be aggressive again
_limitCount++;
if (_limitCount >= _limitReset) {
if(LOG.isDebugEnabled())
LOG.debug(" -- UPP reset:"+_skipLimit);
_limitCount = 0;
_limitHit = false;
}
}
// Readjust the sleepTime to zero if the connection can handle it
if ( _skipCount != 0 &&
rtt < maxRTT &&
receiverWindowSpace > LOW_WINDOW_SPACE ) {
if(LOG.isDebugEnabled())
LOG.debug("_skipLimit = "+_skipLimit);
sleepTime = 0;
}
// Ensure that any minimum sleep time is enforced
sleepTime = Math.max(sleepTime, minTime);
// Reduce the gettingSlow indicator over time
if ( gettingSlow > 0 )
gettingSlow--;
return (long) sleepTime;
//------------- Sleep ------------------------
}
/**
* Record a message success
*/
public void addMessageSuccess() {
_tracker.addSuccess();
}
/**
* Record a message failure
*/
public void addMessageFailure() {
_tracker.addFailure();
}
/**
* Keep track of overall successes and failures
*/
private class FailureTracker {
private static final int HISTORY_SIZE=100;
private final byte [] _data = new byte[HISTORY_SIZE];
private boolean _rollover =false;
private int _index;
/**
* Add one to the successful count
*/
public void addSuccess() {
_data[_index++]=1;
if (_index>=HISTORY_SIZE-1){
LOG.debug("rolled over");
_index=0;
_rollover=true;
}
}
/**
* Add one to the failure count
*/
public void addFailure() {
_data[_index++]=0;
if (_index>=HISTORY_SIZE-1){
LOG.debug("rolled over");
_index=0;
_rollover=true;
}
}
/**
* Clear out old failures to give new rate a chance. This should clear
* out a clump of failures more quickly.
*/
public void clearOldFailures() {
for (int i = 0; i < HISTORY_SIZE/2; i++)
addSuccess();
}
/**
* Compute the failure rate of last HISTORY_SIZE blocks once up and running
*/
public float failureRate() {
int total=0;
for (int i=0;i < (_rollover ? HISTORY_SIZE : _index);i++)
total+=_data[i];
if (LOG.isDebugEnabled()) {
LOG.debug("failure rate from "+_index+
" measurements and rollover "+_rollover+
" total is "+total+
" and rate "+
(1- (float)total / (float)(_rollover ? HISTORY_SIZE : _index)));
}
return 1- ((float)total / (float)(_rollover ? HISTORY_SIZE : _index));
}
/**
* Report the failure rate as string for debugging.
*/
public String failureRateAsString() {
float rate = failureRate() * 1000;
int irate = ((int)rate) / 10 ;
int drate = (((int)rate) - (irate * 10));
return "" + irate + "." + drate;
}
}
}