/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support.math;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.Logger.LogLevel;
/**
* Exponential decay "running average".
*
* @author amphibian
*
* For the first <tt>maxReports</tt> reports, this is equivalent to a simple running average.
* After that it is a decaying running average with a <tt>decayFactor</tt> of <tt>1 / maxReports</tt>. We
* accomplish this by having <tt>decayFactor = 1/(Math.min(#reports, maxReports))</tt>. We can
* therefore:
* <ul>
* <li>Specify <tt>maxReports</tt> more easily than an arbitrary decay factor.</li>
* <li>We don't get big problems with influence of the initial value, which is usually not very reliable.</li>
* </ul>
*/
public final class BootstrappingDecayingRunningAverage implements RunningAverage, Cloneable {
private static final long serialVersionUID = -1;
@Override
public final BootstrappingDecayingRunningAverage clone() {
// Override clone() for locking; BDRAs are self-synchronized.
// Implement Cloneable to shut up findbugs.
return new BootstrappingDecayingRunningAverage(this);
}
private final double min;
private final double max;
private double currentValue;
private long reports;
private int maxReports;
private static volatile boolean logDEBUG;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
}
});
}
/**
* Constructor
*
* @param defaultValue
* default value
* @param min
* minimum value of input data
* @param max
* maxumum value of input data
* @param maxReports
* number of reports before bootstrapping period ends and decay begins
* @param fs
* {@link SimpleFieldSet} parameter for this object. Will
* override other parameters.
*/
public BootstrappingDecayingRunningAverage(double defaultValue, double min,
double max, int maxReports, SimpleFieldSet fs) {
this.min = min;
this.max = max;
reports = 0;
currentValue = defaultValue;
this.maxReports = maxReports;
assert(maxReports > 0);
if(fs != null) {
double d = fs.getDouble("CurrentValue", currentValue);
if(!(Double.isNaN(d) || d < min || d > max)) {
currentValue = d;
reports = fs.getLong("Reports", reports);
}
}
}
/**
* {@inheritDoc}
*
* @return
*/
@Override
public synchronized double currentValue() {
return currentValue;
}
/**
* <strong>Not a public method.</strong> Changes the internally stored <code>currentValue</code> and return the old one.
*
* Used by {@link DecayingKeyspaceAverage} to normalize the stored averages. Calling this function
* may (purposefully) destroy the utility of the average being kept.
*
* @param d
* @return
* @see DecayingKeyspaceAverage
*/
protected synchronized double setCurrentValue(double d) {
double old=currentValue;
currentValue=d;
return old;
}
/**
* {@inheritDoc}
*
* @param d
*/
@Override
public synchronized void report(double d) {
if(d < min) {
if(logDEBUG)
Logger.debug(this, "Too low: "+d, new Exception("debug"));
d = min;
}
if(d > max) {
if(logDEBUG)
Logger.debug(this, "Too high: "+d, new Exception("debug"));
d = max;
}
reports++;
double decayFactor = 1.0 / (Math.min(reports, maxReports));
currentValue = (d * decayFactor) + (currentValue * (1-decayFactor));
}
/**
* {@inheritDoc}
*
* @param d
*/
@Override
public void report(long d) {
report((double)d);
}
/**
* {@inheritDoc}
*
* @param d
*/
@Override
public synchronized double valueIfReported(double d) {
if(d < min) {
Logger.error(this, "Too low: "+d, new Exception("debug"));
d = min;
}
if(d > max) {
Logger.error(this, "Too high: "+d, new Exception("debug"));
d = max;
}
double decayFactor = 1.0 / (Math.min(reports + 1, maxReports));
return (d * decayFactor) + (currentValue * (1-decayFactor));
}
/**
* Change <code>maxReports</code>.
*
* @param maxReports
*/
public synchronized void changeMaxReports(int maxReports) {
this.maxReports=maxReports;
}
/**
* Copy constructor.
*/
private BootstrappingDecayingRunningAverage(BootstrappingDecayingRunningAverage a) {
synchronized (a) {
this.currentValue = a.currentValue;
this.max = a.max;
this.maxReports = a.maxReports;
this.min = a.min;
this.reports = a.reports;
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized long countReports() {
return reports;
}
/**
* Export this object as {@link SimpleFieldSet}.
*
* @param shortLived
* See {@link SimpleFieldSet#SimpleFieldSet(boolean)}.
* @return
*/
public synchronized SimpleFieldSet exportFieldSet(boolean shortLived) {
SimpleFieldSet fs = new SimpleFieldSet(shortLived);
fs.putSingle("Type", "BootstrappingDecayingRunningAverage");
fs.put("CurrentValue", currentValue);
fs.put("Reports", reports);
return fs;
}
}