// (c) Copyright 2000 Justin F. Chapweske // (c) Copyright 2000 Ry4an C. Brase package com.onionnetworks.util; /** * This class allows you to easily compute rate for various events. A weighted * floating average is used and all parameters can be tweaked to fine tune * your rate calculations. * * @author Justin F. Chapweske */ public class RateCalculator { public static final int DEFAULT_INTERVAL_LENGTH = 300; public static final int DEFAULT_HISTORY_SIZE = 100; public static final float DEFAULT_HISTORY_WEIGHT = .8f; protected int intervalLength; // Number of milliseconds in interval. protected int historySize; // Number of intervals to keep track of. protected float historyWeight; // Multipler/weight of old intervals. protected double[] history; // old intervals. protected int historyPos; // history is circular, this is index. protected long lastIntervalTime = -1; // last interval cutoff. protected double currentIntervalEvents; // Num events this interval. protected double totalEvents; protected double lastEstimatedEventCount; // monotincally increasing. protected long lastPositiveUpdateTime = -1; protected long pauseTime = -1; // for pausing /** * Construct a new RateCalculator using the default values. */ public RateCalculator() { this(DEFAULT_INTERVAL_LENGTH,DEFAULT_HISTORY_SIZE, DEFAULT_HISTORY_WEIGHT); } /** * Construct a new RateCalculator. * @param intervalLength The number of millis in each interval. * @param historySize The number of intervals to keep track of. * @param historyWeight The multipler used to determine the relevence of * older intervals. * * Using a small historySize and/or a low historyWeight will cause the rate * to fit tightly against the current rate. Using higher values allows * the rate to be much smoother. If this is being used for direct UI it * is usually nice to have a frequently updated smooth rate. */ public RateCalculator(int intervalLength, int historySize, float historyWeight) { this.intervalLength = intervalLength; this.historySize = historySize; this.historyWeight = historyWeight; history = new double[historySize]; for (int i=0; i<history.length;i++) { history[i]=-1; } } public void pause() { pause(System.currentTimeMillis()); } /** * Pauses the RateCalculator. The RateCalculator will be resumed with the * next call to resume(). It is not advised to update events during the * paused period. */ public void pause(long time) { if (pauseTime != -1) { throw new IllegalStateException("RateCalculator already paused"); } pauseTime = time; } /** * @return true if the RateCalculator is paused */ public boolean isPaused() { return pauseTime != -1; } /** * Resumes the paused RateCalculator. */ public void resume() { resume(System.currentTimeMillis()); } public void resume(long time) { if (pauseTime == -1) { throw new IllegalStateException("RateCalculator not paused"); } update(0,time); pauseTime = -1; } /** * This is the preferred way to update the number of events for rate * calculation, it will simply use the time of the call to keep track of * when the events occured. */ public void update(double numEvents) { update(numEvents,System.currentTimeMillis()); } /** * If you wish to specify exactly the time at which the events occured you * can use this method. It is very important that subsequent calls to * update have eventTime's monotonically increasing. */ public void update(double numEvents, long eventTime) { currentIntervalEvents += numEvents; totalEvents += numEvents; // paused if (pauseTime != -1) { if (lastIntervalTime != -1) { lastIntervalTime += (eventTime-pauseTime); } if (lastPositiveUpdateTime != -1) { lastPositiveUpdateTime += (eventTime-pauseTime); } pauseTime = eventTime; } // first interval. if (lastIntervalTime == -1) { lastIntervalTime = eventTime; lastPositiveUpdateTime = eventTime; return; } if (numEvents > 0) { lastPositiveUpdateTime = eventTime; } long deltaTime = eventTime-lastIntervalTime; if (deltaTime >= intervalLength) { history[historyPos] = currentIntervalEvents / (double) deltaTime; historyPos = (historyPos+1) % history.length; // circular lastIntervalTime = eventTime; currentIntervalEvents = 0; } } public double getRate() { return getRate(System.currentTimeMillis()); } /** * If you are specifying a time with update(long time) then you must * specify the time at which you wish to view the rate for, this time * must be greater than the last time that was passed to the update * call. This method DOES NOT allow you to see what the rate was at * during a previous interval. */ public double getRate(long time) { update(0,time); double rate=0,total=0,weight=1; for (int i=history.length-1;i>=0;i--) { double intervalRate = history[(historyPos + i) % history.length]; if (intervalRate == -1) { continue; } rate += intervalRate*weight; total += weight; weight *= historyWeight; } if (total ==0 && rate == 0) { return currentIntervalEvents / ((double)(time-lastIntervalTime+1)); } return rate/total; } public double getEstimatedEventCount(double maxEvents) { return getEstimatedEventCount(maxEvents,System.currentTimeMillis()); } public double getEstimatedEventCount(double maxEvents, long time) { double rate = getRate(time); long deltaTime = time-lastPositiveUpdateTime; double estimatedEventCount = totalEvents + (deltaTime * rate); return estimatedEventCount; } public long getEstimatedTimeRemaining(double maxEvents) { return getEstimatedTimeRemaining(maxEvents, System.currentTimeMillis()); } public long getEstimatedTimeRemaining(double maxEvents, long time) { double rate = getRate(time); double estimatedEventCount = getEstimatedEventCount(maxEvents,time); return (long) ((maxEvents-estimatedEventCount)/rate); } }