org.carrot2.util.RollingWindowAverage Maven / Gradle / Ivy
Show all versions of carrot2-core Show documentation
/*
* Carrot2 project.
*
* Copyright (C) 2002-2018, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.util;
import java.util.LinkedList;
/**
* Calculates an average of values showing up in a given time window. To keep processing
* efficient, bucketing is performed.
*
* This class is not thread-safe.
*/
public class RollingWindowAverage
{
/** Helpful constant for one millisecond. */
public static final int MILLIS = 1;
/** Helpful constant for one second. */
public static final int SECOND = 1000 * MILLIS;
/** Helpful constant for one minute. */
public static final int MINUTE = 60 * SECOND;
/**
* An entry in {@link #buckets}.
*/
private final static class Bucket
{
public final long deadline;
public long elements;
public long sum;
public Bucket(long deadline)
{
this.deadline = deadline;
}
}
/**
* Each entry in this list is a partial sum of all objects that fell into the same
* time period.
*/
final LinkedList buckets = new LinkedList();
/**
* Up-to-date sum of elements in all buckets.
*/
private long currentSum;
/**
* Up-to-date count of all elements in all buckets.
*/
private long currentCount;
private long bucketSizeMillis;
private long windowSizeMillis;
/**
*
*/
public RollingWindowAverage(long windowSizeMillis, long bucketSizeMillis)
{
if (bucketSizeMillis <= 0 || windowSizeMillis <= 0
|| windowSizeMillis <= bucketSizeMillis)
{
throw new IllegalArgumentException("Bucket size must be smaller than window size.");
}
this.bucketSizeMillis = bucketSizeMillis;
this.windowSizeMillis = windowSizeMillis;
}
/**
* Adds a new entry.
*/
public final void add(long timestamp, long value)
{
final long now = getNow();
removeOldBuckets(now);
Bucket bucket;
if (buckets.isEmpty() || (bucket = buckets.getFirst()).deadline < timestamp)
{
// Create new bucket.
bucket = new Bucket(now + bucketSizeMillis);
buckets.addFirst(bucket);
}
bucket.elements++;
currentCount++;
bucket.sum += value;
currentSum += value;
}
/**
*
*/
public final double getCurrentAverage()
{
removeOldBuckets(getNow());
if (this.currentCount == 0)
{
return 0;
}
else
{
return ((double) this.currentSum) / this.currentCount;
}
}
/**
* Returns the number of updates kept in the rolling window's scope.
*/
public final long getUpdatesInWindow()
{
removeOldBuckets(getNow());
return this.currentCount;
}
/**
* Returns the size of the rolling window.
*/
public final long getWindowSizeMillis()
{
return windowSizeMillis;
}
/**
* Returns System.currentTimeMillise()
, but moved to a separate method to speed
* up JUnit tests and make them independent of actual wall time.
*/
long getNow()
{
return System.currentTimeMillis();
}
/**
* Remove buckets that fall outside the rolling window's scope.
*/
private void removeOldBuckets(long now)
{
final long purgeTimestamp = now - this.windowSizeMillis;
Bucket bucket;
while (!buckets.isEmpty()
&& (bucket = buckets.getLast()).deadline < purgeTimestamp)
{
currentSum -= bucket.sum;
currentCount -= bucket.elements;
buckets.removeLast();
}
}
}