package net.i2p.stat;
/**
* Manage the calculation of a moving average event frequency over a certain period.
*
* This provides lifetime, and rolling average, frequency counts.
* Unlike Rate, it does not support "bucketed" averages.
* There is no tracking of the event frequency in the current or last bucket.
* There are no buckets at all.
*
* Depending on what you want, a rolling average might be better than buckets.
* Or not.
*/
public class Frequency {
private double _avgInterval;
private double _minAverageInterval;
private final long _period;
private long _lastEvent;
private final long _start = now();
private long _count;
/** @param period ms */
public Frequency(long period) {
_period = period;
_avgInterval = period + 1;
_minAverageInterval = _avgInterval;
}
/** how long is this frequency averaged over? (ms) */
public long getPeriod() {
return _period;
}
/**
* when did the last event occur?
* @deprecated unused
*/
@Deprecated
public synchronized long getLastEvent() {
return _lastEvent;
}
/**
* on average over the last $period, after how many milliseconds are events coming in,
* as calculated during the last event occurrence?
* @return milliseconds; returns period + 1 if no events in previous period
*/
public synchronized double getAverageInterval() {
return _avgInterval;
}
/**
* what is the lowest average interval (aka most frequent) we have seen? (ms)
* @return milliseconds; returns period + 1 if no events in previous period
* @deprecated unused
*/
@Deprecated
public synchronized double getMinAverageInterval() {
return _minAverageInterval;
}
/**
* Calculate how many events would occur in a period given the current (rolling) average.
* Use getStrictAverageInterval() for the real lifetime average.
*/
public synchronized double getAverageEventsPerPeriod() {
if (_avgInterval > 0) return _period / _avgInterval;
return 0;
}
/**
* Calculate how many events would occur in a period given the maximum rolling average.
* Use getStrictAverageEventsPerPeriod() for the real lifetime average.
*/
public synchronized double getMaxAverageEventsPerPeriod() {
if (_minAverageInterval > 0 && _minAverageInterval <= _period) return _period / _minAverageInterval;
return 0;
}
/**
* Over the lifetime of this stat, without any decay or weighting, what was the average interval between events? (ms)
* @return milliseconds; returns Double.MAX_VALUE if no events ever
*/
public synchronized double getStrictAverageInterval() {
long duration = now() - _start;
if ((duration <= 0) || (_count <= 0)) return Double.MAX_VALUE;
return duration / (double) _count;
}
/** using the strict average interval, how many events occur within an average period? */
public synchronized double getStrictAverageEventsPerPeriod() {
double avgInterval = getStrictAverageInterval();
if (avgInterval > 0) return _period / avgInterval;
return 0;
}
/** how many events have occurred within the lifetime of this stat? */
public synchronized long getEventCount() {
return _count;
}
/**
* Take note that a new event occurred, recalculating all the averages and frequencies
*
*/
public void eventOccurred() {
recalculate(true);
}
/**
* Recalculate the averages
*
*/
public void recalculate() {
recalculate(false);
}
/**
* Recalculate, but only update the lastEvent if eventOccurred
*/
private void recalculate(boolean eventOccurred) {
synchronized (this) {
// This calculates something of a rolling average interval.
long now = now();
long interval = now - _lastEvent;
if (interval > _period)
interval = _period;
else if (interval <= 0) interval = 1;
if (interval >= _period && !eventOccurred) {
// ensure getAverageEventsPerPeriod() will return 0
_avgInterval = _period + 1;
} else {
double oldWeight = 1 - (interval / (float) _period);
double newWeight = (interval / (float) _period);
double oldInterval = _avgInterval * oldWeight;
double newInterval = interval * newWeight;
_avgInterval = oldInterval + newInterval;
}
if ((_avgInterval < _minAverageInterval) || (_minAverageInterval <= 0)) _minAverageInterval = _avgInterval;
if (eventOccurred) {
_lastEvent = now;
_count++;
}
}
}
private final static long now() {
return System.currentTimeMillis();
}
/**
* Appends the data of this frequency to the specified StringBuilder
* @param dest to append data to
* @since 0.9.23
*/
synchronized void store(StringBuilder dest) {
dest.append("avgInterval:").append(_avgInterval).append(',');
dest.append("minAverageInterval").append(_minAverageInterval).append(',');
dest.append("lastEvent").append(_lastEvent).append(",");
dest.append("count").append(_count);
}
}