/* 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; } }