/**
*
*/
package edu.washington.cs.oneswarm.f2f.network;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
class SpeedManager {
private static Logger logger = Logger.getLogger(SpeedManager.class.getName());
/**
* These constants define startup cases for which queueing is immediate
* (i.e., we don't need to bother checking the queue size because we're
* uploading so slowly or there's so little data in-queue)
*/
public static final int MAX_SPEED0_QUEUE_LEN_BYTES = 100 * 1024;
public final static double SPEED0 = 500;
/**
* We keep a history of the amount of data added that extends this far into
* the past to compute the average upload speed. (This is the window of that
* average)
*/
public final static int NUM_MS_AVERAGE = 2000;
/**
* These support the average speed computation described above and store
* history bounded by NUM_MS_AVERAGE
*/
private int bytesSentLastAvgWindow = 0;
/**
* NOTE: --- timestamps is used to synchronize access to BOTH packetSizes
* and timestamps ---
*/
public final LinkedList<Integer> packetSizes = new LinkedList<Integer>();
public final LinkedList<Long> timestamps = new LinkedList<Long>();
/**
* The finest level of logging prints out data regarding every message,
* which is too fine-grained for many real-world debugging cases. For these,
* we support periodic logging at the FINER level. This defines the period.
*/
public static final long FINER_LOG_REPORTING_PERIOD = 5000;
private long lastPeriodicLog = 0;
// private final QueueManager queueManager;
private final boolean global;
public SpeedManager(QueueManager queueManager, boolean global) {
// this.queueManager = queueManager;
this.global = global;
}
/**
* @returns true if we can queue more packets, false if the queue is full
*/
boolean canQueuePacket(int currentQueueLengthBytes, int maxQueueLenMs) {
if (logger.isLoggable(Level.FINER)) {
if (lastPeriodicLog + FINER_LOG_REPORTING_PERIOD < System.currentTimeMillis()) {
printPeriodicLog(currentQueueLengthBytes);
lastPeriodicLog = System.currentTimeMillis();
}
}
double currentUploadSpeed = getCurrentUploadSpeed();
logger.finest("canQueue? speed=" + currentUploadSpeed + " queue length="
+ currentQueueLengthBytes);
/**
* If the queue length is tiny, immediately accept.
*/
if (currentQueueLengthBytes < QueueManager.MIN_GLOBAL_QUEUE_LEN_BYTES) {
logger.finest("canQueue: yes queue, " + currentQueueLengthBytes + "bytes < "
+ maxQueueLenMs + "bytes");
return true;
}
/**
* If we're uploading slowly and the queue length isn't too large
* (100k), also immediately accept.
*/
else if (currentUploadSpeed < SPEED0
&& currentQueueLengthBytes < MAX_SPEED0_QUEUE_LEN_BYTES) {
logger.finest("canQueue: yes, speed=" + currentUploadSpeed + " < " + SPEED0);
return true;
}
/**
* The previous cases are optimizations.
*
* THE COMMON CASE: We're uploading at a reasonable rate and the global
* queue has a fair bit of data. Here, we enforce our global queue
* resource consumption limits: ** no more than maxQueueLenMs worth of
* data ** This bounds the time data stays in the queue at our current
* upload speed.
*/
else {
double currentQueueLenMs = (currentQueueLengthBytes / currentUploadSpeed) * 1000.0;
if (currentQueueLenMs < maxQueueLenMs) {
logger.finest("yes queue, " + currentQueueLenMs + "ms < " + maxQueueLenMs + "ms");
return true;
} else {
logger.finest("no, queue len ms=" + currentQueueLenMs + " > " + maxQueueLenMs);
return false;
}
}
}
public void dataUploaded(int bytes) {
synchronized (timestamps) {
timestamps.addLast(System.currentTimeMillis());
packetSizes.addLast(bytes);
}
bytesSentLastAvgWindow += bytes;
}
/**
* This function is only used when the Java logging level at least FINER
* granularity.
*/
private void printPeriodicLog(int currentQueueLengthBytes) {
synchronized (timestamps) {
double currentUploadSpeed = getCurrentUploadSpeed();
double currentQueueLenMs = (currentQueueLengthBytes / currentUploadSpeed) * 1000.0;
String tstamp = "";
if (timestamps.size() > 0) {
/**
* If this has positive size, we can compute the actual average
* window (how closely does it match target?) Since we just
* called getCurrentUploadSpeed(), all of the old packet
* sizes/times have been pruned
*/
tstamp = " averager_window=" + (System.currentTimeMillis() - timestamps.get(0));
}
String bytesSuf = "";
if (packetSizes.size() > 0) {
bytesSuf = " oldest_bytes=" + packetSizes.get(0);
}
logger.finer("Speed manager log: global=" + global + " speed=" + currentUploadSpeed
+ " len(bytes)=" + currentQueueLengthBytes + " len(time)="
+ (Double.isNaN(currentQueueLenMs) ? "0" : currentQueueLenMs)
+ " timestamps.size()=" + timestamps.size() + tstamp + " packetSizes.size()="
+ packetSizes.size() + bytesSuf);
if (timestamps.size() != packetSizes.size()) {
logger.warning("** Timestmps and packetSizes have different sizes. (These should be coupled 1-1)");
}
}
}
public double getCurrentUploadSpeed() {
double currentSpeedBytesPerSecond = 0;
synchronized (timestamps) {
// first, remove the timestamps that are to old
while (timestamps.size() > 0
&& timestamps.getFirst() + NUM_MS_AVERAGE < System.currentTimeMillis()) {
timestamps.removeFirst();
bytesSentLastAvgWindow -= packetSizes.removeFirst();
}
// then calculate the speed
if (bytesSentLastAvgWindow > 0) {
if (timestamps.size() >= 1) {
long windowMs = System.currentTimeMillis() - timestamps.getFirst();
if (windowMs > 0) {
currentSpeedBytesPerSecond = ((double) 1000 * bytesSentLastAvgWindow)
/ (double) (windowMs);
if (logger.isLoggable(Level.FINEST)) {
logger.finest("current upload speed: " + currentSpeedBytesPerSecond
+ " B/s");
}
}
}
}
/**
* This should never happen, and is just error checking code.
*/
else if (bytesSentLastAvgWindow < 0) {
logger.warning("** Bytes sent last window was _negative_: "
+ bytesSentLastAvgWindow);
/**
* Bad juju, reset state.
*/
bytesSentLastAvgWindow = 0;
timestamps.clear();
packetSizes.clear();
}
}
return currentSpeedBytesPerSecond;
}
}