package com.twitter.common.stats;
import com.google.common.base.Supplier;
import com.google.common.base.Function;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.util.Clock;
/**
* Keep track of statistics over a set of value in a sliding window.
* WARNING: The computation of the statistics needs to be explicitly requested with
* {@code refresh()} before reading any statistics.
*
* @see Windowed class for more details about how the window is parametrized.
*/
public class WindowedStatistics extends Windowed<Statistics> implements StatisticsInterface {
private int lastIndex = -1;
private double variance = 0.0;
private double mean = 0.0;
private long sum = 0L;
private long min = Long.MAX_VALUE;
private long max = Long.MIN_VALUE;
private long populationSize = 0L;
public WindowedStatistics(Amount<Long, Time> window, int slices, Clock clock) {
super(Statistics.class, window, slices,
new Supplier<Statistics>() {
@Override public Statistics get() { return new Statistics(); }
},
new Function<Statistics, Statistics>() {
@Override public Statistics apply(Statistics s) { s.clear(); return s; }
},
clock);
}
/**
* Construct a Statistics sliced over time in {@code slices + 1} windows.
* The {@code window} parameter represents the total window, that will be sliced into
* {@code slices + 1} parts.
*
* Ex: WindowedStatistics(Amount.of(1L, Time.MINUTES), 3) will be sliced like this:
* <pre>
* 20s 20s 20s 20s
* [----A-----][-----B----][-----C----][-----D----]
* </pre>
* The current window is 'D' (the one you insert elements into) and the tenured windows
* are 'A', 'B', 'C' (the ones you read elements from).
*/
public WindowedStatistics(Amount<Long, Time> window, int slices) {
this(window, slices, Clock.SYSTEM_CLOCK);
}
/**
* Equivalent to calling {@link #WindowedStatistics(Amount, int)} with a 1 minute window
* and 3 slices.
*/
public WindowedStatistics() {
this(Amount.of(1L, Time.MINUTES), 3, Clock.SYSTEM_CLOCK);
}
public void accumulate(long value) {
getCurrent().accumulate(value);
}
/**
* Compute all the statistics in one pass.
*/
public void refresh() {
int currentIndex = getCurrentIndex();
if (lastIndex != currentIndex) {
lastIndex = currentIndex;
double x = 0.0;
variance = 0.0;
mean = 0.0;
sum = 0L;
populationSize = 0L;
min = Long.MAX_VALUE;
max = Long.MIN_VALUE;
for (Statistics s : getTenured()) {
if (s.populationSize() == 0) {
continue;
}
x += s.populationSize() * (s.variance() + s.mean() * s.mean());
sum += s.sum();
populationSize += s.populationSize();
min = Math.min(min, s.min());
max = Math.max(max, s.max());
}
if (populationSize != 0) {
mean = ((double) sum) / populationSize;
variance = x / populationSize - mean * mean;
}
}
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the variance of the aggregated windows
*/
public double variance() {
return variance;
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the standard deviation of the aggregated windows
*/
public double standardDeviation() {
return Math.sqrt(variance());
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the mean of the aggregated windows
*/
public double mean() {
return mean;
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the sum of the aggregated windows
*/
public long sum() {
return sum;
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the min of the aggregated windows
*/
public long min() {
return min;
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the max of the aggregated windows
*/
public long max() {
return max;
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the range of the aggregated windows
*/
public long range() {
return max - min;
}
/**
* WARNING: You need to call refresh() to recompute the variance
* @return the population size of the aggregated windows
*/
public long populationSize() {
return populationSize;
}
}