diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
index aeb3639030cb55d35d76c63a2b0253f77e1e71d3..edd7b31dc9c584f860a2c988f2584aa75d3467ad 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
@@ -16,6 +16,7 @@ import net.i2p.router.peermanager.DBHistory;
 import net.i2p.router.peermanager.PeerProfile;
 import net.i2p.router.peermanager.ProfileOrganizer;
 import net.i2p.stat.Rate;
+import net.i2p.stat.RateAverages;
 import net.i2p.stat.RateStat;
 
 /**
@@ -171,11 +172,12 @@ class ProfileOrganizerRenderer {
             if (_context.banlist().isBanlisted(peer)) buf.append(_("Banned"));
             if (prof.getIsFailing()) buf.append(' ').append(_("Failing"));
             if (_context.commSystem().wasUnreachable(peer)) buf.append(' ').append(_("Unreachable"));
+            RateAverages ra = RateAverages.getTemp();
             Rate failed = prof.getTunnelHistory().getFailedRate().getRate(30*60*1000);
-            long fails = failed.getCurrentEventCount() + failed.getLastEventCount();
+            long fails = failed.computeAverages(ra, false).getTotalEventCount();
             if (fails > 0) {
                 Rate accepted = prof.getTunnelCreateResponseTime().getRate(30*60*1000);
-                long total = fails + accepted.getCurrentEventCount() + accepted.getLastEventCount();
+                long total = fails + accepted.computeAverages(ra, false).getTotalEventCount();
                 if (total / fails <= 10)   // hide if < 10%
                     buf.append(' ').append(fails).append('/').append(total).append(' ').append(_("Test Fails"));
             }
@@ -218,6 +220,7 @@ class ProfileOrganizerRenderer {
         buf.append("<th class=\"smallhead\">").append(_("1h Fail Rate")).append("</th>");
         buf.append("<th class=\"smallhead\">").append(_("1d Fail Rate")).append("</th>");
         buf.append("</tr>");
+        RateAverages ra = RateAverages.getTemp();
         for (Iterator<PeerProfile> iter = integratedPeers.iterator(); iter.hasNext();) {
             PeerProfile prof = iter.next();
             Hash peer = prof.getPeer();
@@ -240,9 +243,9 @@ class ProfileOrganizerRenderer {
             buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
             time = now - prof.getLastSendFailed();
             buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
-            buf.append("<td align=\"right\">").append(avg(prof, 10*60*1000l)).append("</td>");
-            buf.append("<td align=\"right\">").append(avg(prof, 60*60*1000l)).append("</td>");
-            buf.append("<td align=\"right\">").append(avg(prof, 24*60*60*1000l)).append("</td>");
+            buf.append("<td align=\"right\">").append(avg(prof, 10*60*1000l, ra)).append("</td>");
+            buf.append("<td align=\"right\">").append(avg(prof, 60*60*1000l, ra)).append("</td>");
+            buf.append("<td align=\"right\">").append(avg(prof, 24*60*60*1000l, ra)).append("</td>");
             DBHistory dbh = prof.getDBHistory();
             if (dbh != null) {
                 time = now - dbh.getLastLookupSuccessful();
@@ -253,8 +256,8 @@ class ProfileOrganizerRenderer {
                 buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
                 time = now - dbh.getLastStoreFailed();
                 buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
-                buf.append("<td align=\"right\">").append(davg(dbh, 60*60*1000l)).append("</td>");
-                buf.append("<td align=\"right\">").append(davg(dbh, 24*60*60*1000l)).append("</td>");
+                buf.append("<td align=\"right\">").append(davg(dbh, 60*60*1000l, ra)).append("</td>");
+                buf.append("<td align=\"right\">").append(davg(dbh, 24*60*60*1000l, ra)).append("</td>");
             } else {
                 for (int i = 0; i < 6; i++)
                     buf.append("<td align=\"right\">").append(_(NA));
@@ -340,31 +343,30 @@ class ProfileOrganizerRenderer {
     private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } }
     private final static String NA = HelperBase._x("n/a");
 
-    private String avg (PeerProfile prof, long rate) {
+    private String avg (PeerProfile prof, long rate, RateAverages ra) {
             RateStat rs = prof.getDbResponseTime();
             if (rs == null)
                 return _(NA);
             Rate r = rs.getRate(rate);
             if (r == null)
                 return _(NA);
-            long c = r.getCurrentEventCount() + r.getLastEventCount();
-            if (c == 0)
+            r.computeAverages(ra, false);
+            if (ra.getTotalEventCount() == 0)
                 return _(NA);
-            double d = r.getCurrentTotalValue() + r.getLastTotalValue();
-            return DataHelper.formatDuration2(Math.round(d/c));
+            return DataHelper.formatDuration2(Math.round(ra.getAverage()));
     }
 
-    private String davg (DBHistory dbh, long rate) {
+    private String davg (DBHistory dbh, long rate, RateAverages ra) {
             RateStat rs = dbh.getFailedLookupRate();
             if (rs == null)
                 return "0%";
             Rate r = rs.getRate(rate);
             if (r == null)
                 return "0%";
-            long c = r.getCurrentEventCount() + r.getLastEventCount();
-            if (c <= 0)
+            r.computeAverages(ra, false);
+            if (ra.getTotalEventCount() <= 0)
                 return "0%";
-            double avg = 0.5 + 100 * (r.getCurrentTotalValue() + r.getLastTotalValue()) / c;
+            double avg = 0.5 + 100 * ra.getAverage();
             return ((int) avg) + "%";
     }
 
diff --git a/core/java/src/net/i2p/stat/Rate.java b/core/java/src/net/i2p/stat/Rate.java
index f2542a10b2c5ead08a62a1847d074b5930e57bd0..26778cd0732a734925e3aadb474c4a49a89b7591 100644
--- a/core/java/src/net/i2p/stat/Rate.java
+++ b/core/java/src/net/i2p/stat/Rate.java
@@ -14,25 +14,25 @@ import net.i2p.data.DataHelper;
  */
 public class Rate {
     //private final static Log _log = new Log(Rate.class);
-    private volatile double _currentTotalValue;
+    private double _currentTotalValue;
     // was long, save space
-    private volatile int _currentEventCount;
-    private volatile long _currentTotalEventTime;
-    private volatile double _lastTotalValue;
+    private int _currentEventCount;
+    private long _currentTotalEventTime;
+    private double _lastTotalValue;
     // was long, save space
-    private volatile int _lastEventCount;
-    private volatile long _lastTotalEventTime;
-    private volatile double _extremeTotalValue;
+    private int _lastEventCount;
+    private long _lastTotalEventTime;
+    private double _extremeTotalValue;
     // was long, save space
-    private volatile int _extremeEventCount;
-    private volatile long _extremeTotalEventTime;
-    private volatile double _lifetimeTotalValue;
-    private volatile long _lifetimeEventCount;
-    private volatile long _lifetimeTotalEventTime;
+    private int _extremeEventCount;
+    private long _extremeTotalEventTime;
+    private double _lifetimeTotalValue;
+    private long _lifetimeEventCount;
+    private long _lifetimeTotalEventTime;
     private RateSummaryListener _summaryListener;
     private RateStat _stat;
 
-    private volatile long _lastCoalesceDate;
+    private long _lastCoalesceDate;
     private long _creationDate;
     // was long, save space
     private int _period;
@@ -41,37 +41,37 @@ public class Rate {
     // private final Object _lock = new Object();
 
     /** in the current (partial) period, what is the total value acrued through all events? */
-    public double getCurrentTotalValue() {
+    public synchronized double getCurrentTotalValue() {
         return _currentTotalValue;
     }
 
     /** in the current (partial) period, how many events have occurred? */
-    public long getCurrentEventCount() {
+    public synchronized long getCurrentEventCount() {
         return _currentEventCount;
     }
 
     /** in the current (partial) period, how much of the time has been spent doing the events? */
-    public long getCurrentTotalEventTime() {
+    public synchronized long getCurrentTotalEventTime() {
         return _currentTotalEventTime;
     }
 
     /** in the last full period, what was the total value acrued through all events? */
-    public double getLastTotalValue() {
+    public synchronized double getLastTotalValue() {
         return _lastTotalValue;
     }
 
     /** in the last full period, how many events occurred? */
-    public long getLastEventCount() {
+    public synchronized long getLastEventCount() {
         return _lastEventCount;
     }
 
     /** in the last full period, how much of the time was spent doing the events? */
-    public long getLastTotalEventTime() {
+    public synchronized long getLastTotalEventTime() {
         return _lastTotalEventTime;
     }
 
     /** what was the max total value acrued in any period?  */
-    public double getExtremeTotalValue() {
+    public synchronized double getExtremeTotalValue() {
         return _extremeTotalValue;
     }
 
@@ -79,42 +79,42 @@ public class Rate {
      * when the max(totalValue) was achieved, how many events occurred in that period?
      * Note that this is not necesarily the highest event count; that isn't tracked.
      */
-    public long getExtremeEventCount() {
+    public synchronized long getExtremeEventCount() {
         return _extremeEventCount;
     }
 
     /** when the max(totalValue) was achieved, how much of the time was spent doing the events? */
-    public long getExtremeTotalEventTime() {
+    public synchronized long getExtremeTotalEventTime() {
         return _extremeTotalEventTime;
     }
 
     /** since rate creation, what was the total value acrued through all events?  */
-    public double getLifetimeTotalValue() {
+    public synchronized double getLifetimeTotalValue() {
         return _lifetimeTotalValue;
     }
 
     /** since rate creation, how many events have occurred? */
-    public long getLifetimeEventCount() {
+    public synchronized long getLifetimeEventCount() {
         return _lifetimeEventCount;
     }
 
     /** since rate creation, how much of the time was spent doing the events? */
-    public long getLifetimeTotalEventTime() {
+    public synchronized long getLifetimeTotalEventTime() {
         return _lifetimeTotalEventTime;
     }
 
     /** when was the rate last coalesced? */
-    public long getLastCoalesceDate() {
+    public synchronized long getLastCoalesceDate() {
         return _lastCoalesceDate;
     }
 
     /** when was this rate created? */
-    public long getCreationDate() {
+    public synchronized long getCreationDate() {
         return _creationDate;
     }
 
     /** how large should this rate's cycle be? */
-    public long getPeriod() {
+    public synchronized long getPeriod() {
         return _period;
     }
     
@@ -160,13 +160,11 @@ public class Rate {
      * If you always use this call, eventDuration is always zero,
      * and the various get*Saturation*() and get*EventTime() methods will return zero.
      */
-    public void addData(long value) {
-        synchronized (this) {
-            _currentTotalValue += value;
-            _currentEventCount++;
-            _lifetimeTotalValue += value;
-            _lifetimeEventCount++;
-        }
+    public synchronized void addData(long value) {
+        _currentTotalValue += value;
+        _currentEventCount++;
+        _lifetimeTotalValue += value;
+        _lifetimeEventCount++;
     }
 
     /**
@@ -202,16 +200,14 @@ public class Rate {
      * @param value value to accrue in the current period
      * @param eventDuration how long it took to accrue this data (set to 0 if it was instantaneous)
      */
-    public void addData(long value, long eventDuration) {
-        synchronized (this) {
-            _currentTotalValue += value;
-            _currentEventCount++;
-            _currentTotalEventTime += eventDuration;
+    public synchronized void addData(long value, long eventDuration) {
+        _currentTotalValue += value;
+        _currentEventCount++;
+        _currentTotalEventTime += eventDuration;
 
-            _lifetimeTotalValue += value;
-            _lifetimeEventCount++;
-            _lifetimeTotalEventTime += eventDuration;
-        }
+        _lifetimeTotalValue += value;
+        _lifetimeEventCount++;
+        _lifetimeTotalEventTime += eventDuration;
     }
 
     /** 2s is plenty of slack to deal with slow coalescing (across many stats) */
@@ -261,10 +257,8 @@ public class Rate {
     
     /**
      * What was the average value across the events in the last period?
-     *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
      */
-    public double getAverageValue() {
+    public synchronized double getAverageValue() {
         int lec = _lastEventCount;  // avoid race NPE
         if ((_lastTotalValue != 0) && (lec > 0))
             return _lastTotalValue / lec;
@@ -275,10 +269,8 @@ public class Rate {
     /**
      * During the extreme period (i.e. the period with the highest total value),
      * what was the average value?
-     *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
      */
-    public double getExtremeAverageValue() {
+    public synchronized double getExtremeAverageValue() {
         if ((_extremeTotalValue != 0) && (_extremeEventCount > 0))
             return _extremeTotalValue / _extremeEventCount;
 
@@ -287,23 +279,31 @@ public class Rate {
 
     /**
      * What was the average value across the events since the stat was created?
-     *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
      */
-    public double getLifetimeAverageValue() {
+    public synchronized double getLifetimeAverageValue() {
         if ((_lifetimeTotalValue != 0) && (_lifetimeEventCount > 0))
             return _lifetimeTotalValue / _lifetimeEventCount;
        
         return 0.0D;
     }
 
+    /**
+     * @return the average or lifetime average depending on last event count
+     * @since 0.9.4
+     */
+    public synchronized double getAvgOrLifetimeAvg() {
+        if (getLastEventCount() > 0)
+            return getAverageValue();
+        return getLifetimeAverageValue();
+    }
+    
     /** 
      * During the last period, how much of the time was spent actually processing events in proportion 
      * to how many events could have occurred if there were no intervals?
      *
      * @return ratio, or 0 if event times aren't used
      */
-    public double getLastEventSaturation() {
+    public synchronized double getLastEventSaturation() {
         if ((_lastEventCount > 0) && (_lastTotalEventTime > 0)) {
             /*double eventTime = (double) _lastTotalEventTime / (double) _lastEventCount;
             double maxEvents = _period / eventTime;
@@ -321,11 +321,9 @@ public class Rate {
      * how much of the time was spent actually processing events
      * in proportion to how many events could have occurred if there were no intervals? 
      *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
-     *
      * @return ratio, or 0 if the statistic doesn't use event times
      */
-    public double getExtremeEventSaturation() {
+    public synchronized double getExtremeEventSaturation() {
         if ((_extremeEventCount > 0) && (_extremeTotalEventTime > 0)) {
             double eventTime = (double) _extremeTotalEventTime / (double) _extremeEventCount;
             double maxEvents = _period / eventTime;
@@ -338,11 +336,9 @@ public class Rate {
      * During the lifetime of this stat, how much of the time was spent actually processing events in proportion 
      * to how many events could have occurred if there were no intervals? 
      *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
-     *
      * @return ratio, or 0 if event times aren't used
      */
-    public double getLifetimeEventSaturation() {
+    public synchronized double getLifetimeEventSaturation() {
         if ((_lastEventCount > 0) && (_lifetimeTotalEventTime > 0)) {
             double eventTime = (double) _lifetimeTotalEventTime / (double) _lifetimeEventCount;
             double maxEvents = _period / eventTime;
@@ -354,7 +350,7 @@ public class Rate {
     }
 
     /** how many periods have we already completed? */
-    public long getLifetimePeriods() {
+    public synchronized long getLifetimePeriods() {
         long lifetime = now() - _creationDate;
         double periods = lifetime / (double) _period;
         return (long) Math.floor(periods);
@@ -364,11 +360,9 @@ public class Rate {
      * using the last period's rate, what is the total value that could have been sent 
      * if events were constant?
      *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
-     *
      * @return max total value, or 0 if event times aren't used
      */
-    public double getLastSaturationLimit() {
+    public synchronized double getLastSaturationLimit() {
         if ((_lastTotalValue != 0) && (_lastEventCount > 0) && (_lastTotalEventTime > 0)) {
             double saturation = getLastEventSaturation();
             if (saturation != 0.0D) return _lastTotalValue / saturation;
@@ -383,11 +377,9 @@ public class Rate {
      * what is the total value that could have been 
      * sent if events were constant?
      *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
-     *
      * @return event total at saturation, or 0 if no event times are measured
      */
-    public double getExtremeSaturationLimit() {
+    public synchronized double getExtremeSaturationLimit() {
         if ((_extremeTotalValue != 0) && (_extremeEventCount > 0) && (_extremeTotalEventTime > 0)) {
             double saturation = getExtremeEventSaturation();
             if (saturation != 0.0d) return _extremeTotalValue / saturation;
@@ -402,10 +394,8 @@ public class Rate {
      * What was the total value, compared to the total value in
      * the extreme period (i.e. the period with the highest total value),
      * Warning- returns ratio, not percentage (i.e. it is not multiplied by 100 here)
-     *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
      */
-    public double getPercentageOfExtremeValue() {
+    public synchronized double getPercentageOfExtremeValue() {
         if ((_lastTotalValue != 0) && (_extremeTotalValue != 0))
             return _lastTotalValue / _extremeTotalValue;
         
@@ -415,10 +405,8 @@ public class Rate {
     /**
      * How large was the last period's value as compared to the lifetime average value?
      * Warning- returns ratio, not percentage (i.e. it is not multiplied by 100 here)
-     *
-     * Warning - unsynchronized, might glitch during coalesce, caller may prevent by synchronizing on this.
      */
-    public double getPercentageOfLifetimeValue() {
+    public synchronized double getPercentageOfLifetimeValue() {
         if ((_lastTotalValue != 0) && (_lifetimeTotalValue != 0)) {
             double lifetimePeriodValue = _period * (_lifetimeTotalValue / (now() - _creationDate));
             return _lastTotalValue / lifetimePeriodValue;
@@ -426,8 +414,45 @@ public class Rate {
   
         return 0.0D;
     }
+    
+    /**
+     * @return a thread-local temp object containing computed averages.
+     * @since 0.9.4
+     */
+    public RateAverages computeAverages() {
+        return computeAverages(RateAverages.getTemp(),false);
+    }
+    
+    /**
+     * @param out where to store the computed averages.  
+     * @param useLifetime whether the lifetime average should be used if
+     * there are no events.
+     * @return the same RateAverages object for chaining
+     * @since 0.9.4
+     */
+    public synchronized RateAverages computeAverages(RateAverages out, boolean useLifetime) {
+        out.reset();
+        
+        final long total = _currentEventCount + _lastEventCount;
+        out.setTotalEventCount(total);
+        
+        if (total <= 0) {
+            final double avg = useLifetime ? getLifetimeAverageValue() : getAverageValue();
+            out.setAverage(avg);
+        } else {
+
+            if (_currentEventCount > 0)
+                out.setCurrent( getCurrentTotalValue() / _currentEventCount );
+            if (_lastEventCount > 0)
+                out.setLast( getLastTotalValue() / _lastEventCount );
+
+            out.setTotalValues(getCurrentTotalValue() + getLastTotalValue());
+            out.setAverage( out.getTotalValues()  / total );
+        }
+        return out;
+    }
 
-    public void store(String prefix, StringBuilder buf) throws IOException {
+    public synchronized void store(String prefix, StringBuilder buf) throws IOException {
         PersistenceHelper.addTime(buf, prefix, ".period", "Length of the period:", _period);
         PersistenceHelper.addDate(buf, prefix, ".creationDate",
                               "When was this rate created?", _creationDate);
@@ -476,7 +501,7 @@ public class Rate {
      *                       treat the data with as much freshness (or staleness) as appropriate.
      * @throws IllegalArgumentException if the data was formatted incorrectly
      */
-    public void load(Properties props, String prefix, boolean treatAsCurrent) throws IllegalArgumentException {
+    public synchronized void load(Properties props, String prefix, boolean treatAsCurrent) throws IllegalArgumentException {
         _period = PersistenceHelper.getInt(props, prefix, ".period");
         _creationDate = PersistenceHelper.getLong(props, prefix, ".creationDate");
         _lastCoalesceDate = PersistenceHelper.getLong(props, prefix, ".lastCoalesceDate");
@@ -504,7 +529,7 @@ public class Rate {
      * We base it on the stat we are tracking, not the stored data.
      */
     @Override
-    public boolean equals(Object obj) {
+    public synchronized boolean equals(Object obj) {
         if ((obj == null) || !(obj instanceof Rate)) return false;
         if (obj == this) return true;
         Rate r = (Rate) obj;
@@ -519,12 +544,12 @@ public class Rate {
      * (RateStat stores in an array) so let's make this easy.
      */
     @Override
-    public int hashCode() {
+    public synchronized int hashCode() {
         return DataHelper.hashCode(_stat) ^ _period ^ ((int) _creationDate);
     }
 
     @Override
-    public String toString() {
+    public synchronized String toString() {
         StringBuilder buf = new StringBuilder(2048);
         buf.append("\n\t total value: ").append(getLastTotalValue());
         buf.append("\n\t highest total value: ").append(getExtremeTotalValue());
@@ -554,39 +579,4 @@ public class Rate {
         // skew periodically
         return System.currentTimeMillis(); //Clock.getInstance().now();
     }
-
-/******
-    public static void main(String args[]) {
-        Rate rate = new Rate(1000);
-        for (int i = 0; i < 50; i++) {
-            try {
-                Thread.sleep(20);
-            } catch (InterruptedException ie) { // nop
-            }
-            rate.addData(i * 100, 20);
-        }
-        rate.coalesce();
-        StringBuilder buf = new StringBuilder(1024);
-        try {
-            rate.store("rate.test", buf);
-            byte data[] = buf.toString().getBytes();
-            _log.error("Stored rate: size = " + data.length + "\n" + buf.toString());
-
-            Properties props = new Properties();
-            props.load(new java.io.ByteArrayInputStream(data));
-
-            //_log.error("Properties loaded: \n" + props);
-
-            Rate r = new Rate(props, "rate.test", true);
-
-            _log.error("Comparison after store/load: " + r.equals(rate));
-        } catch (Throwable t) {
-            _log.error("b0rk", t);
-        }
-        try {
-            Thread.sleep(5000);
-        } catch (InterruptedException ie) { // nop
-        }
-    }
-******/
 }
diff --git a/core/java/src/net/i2p/stat/RateAverages.java b/core/java/src/net/i2p/stat/RateAverages.java
new file mode 100644
index 0000000000000000000000000000000000000000..375bf0af8608e42b41e32ffc6837673fe2622711
--- /dev/null
+++ b/core/java/src/net/i2p/stat/RateAverages.java
@@ -0,0 +1,102 @@
+package net.i2p.stat;
+
+/**
+ * Storage space for computations of various averages.
+ * 
+ * @author zab
+ * @since 0.9.4
+ */
+public class RateAverages {
+    
+    /** thread-local temp instance */
+    private static final ThreadLocal<RateAverages> TEMP =
+            new ThreadLocal<RateAverages>() {
+        public RateAverages initialValue() {
+            return new RateAverages();
+        }
+    };
+    
+    /**
+     * @since 0.9.4
+     * @return thread-local temp instance.
+     */
+    public static RateAverages getTemp() {
+        return TEMP.get();
+    }
+    
+    private double average, current, last, totalValues;
+    private long totalEventCount;
+    
+    void reset() {
+        average = 0;
+        current = 0;
+        last = 0;
+        totalEventCount = 0;
+        totalValues = 0;
+    }
+
+    /**
+     * @since 0.9.4
+     * @return one of several things:
+     * if there are any events (current or last) => weighted average
+     * otherwise if the useLifetime parameter to Rate.computeAverages was:
+     * true => the lifetime average value
+     * false => zero
+     */
+    public double getAverage() {
+        return average;
+    }
+
+    void setAverage(double average) {
+        this.average = average;
+    }
+
+    /**
+     * @since 0.9.4
+     * @return the current average == current value / current event count
+     */
+    public double getCurrent() {
+        return current;
+    }
+
+    void setCurrent(double current) {
+        this.current = current;
+    }
+
+    /**
+     * @since 0.9.4
+     * @return the last average == last value / last event count
+     */
+    public double getLast() {
+        return last;
+    }
+
+    void setLast(double last) {
+        this.last = last;
+    }
+    
+    /**
+     * @since 0.9.4
+     * @return the total event count == current + last event counts
+     */
+    public long getTotalEventCount() {
+        return totalEventCount;
+    }
+    
+    void setTotalEventCount(long totalEventCount) {
+        this.totalEventCount = totalEventCount;
+    }
+    
+    /**
+     * @since 0.9.4
+     * @return the total values == current + last values
+     */
+    public double getTotalValues() {
+        return totalValues;
+    }
+    
+    void setTotalValues(double totalValues) {
+        this.totalValues = totalValues;
+    }
+    
+}
diff --git a/router/java/src/net/i2p/router/RouterThrottleImpl.java b/router/java/src/net/i2p/router/RouterThrottleImpl.java
index 54ef7a9f1ddb73ad29652f569c815851e9fc9531..b7237c559fc829b967570f0114db07552f4d07bf 100644
--- a/router/java/src/net/i2p/router/RouterThrottleImpl.java
+++ b/router/java/src/net/i2p/router/RouterThrottleImpl.java
@@ -3,9 +3,9 @@ package net.i2p.router;
 import net.i2p.data.Hash;
 import net.i2p.router.peermanager.TunnelHistory;
 import net.i2p.stat.Rate;
+import net.i2p.stat.RateAverages;
 import net.i2p.stat.RateStat;
 import net.i2p.util.Log;
-import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -40,7 +40,7 @@ class RouterThrottleImpl implements RouterThrottle {
     private static final int PREPROCESSED_SIZE = 1024;
 
     private static final long REJECT_STARTUP_TIME = 20*60*1000;
-
+    
     public RouterThrottleImpl(RouterContext context) {
         _context = context;
         _log = context.logManager().getLog(RouterThrottleImpl.class);
@@ -119,6 +119,8 @@ class RouterThrottleImpl implements RouterThrottle {
         //long lag = _context.jobQueue().getMaxLag();
         // reject here if lag too high???
         
+        RateAverages ra = RateAverages.getTemp();
+        
         // TODO
         // This stat is highly dependent on transport mix.
         // For NTCP, it is queueing delay only, ~25ms
@@ -133,37 +135,19 @@ class RouterThrottleImpl implements RouterThrottle {
 
         //Reject tunnels if the time to process messages and send them is too large. Too much time implies congestion.
         if(r != null) {
-            long current = r.getCurrentEventCount();
-            long last = r.getLastEventCount();
-            long total = current + last;
-            double avgSendProcessingTime = 0;
-            double currentSendProcessingTime = 0;
-            double lastSendProcessingTime = 0;
+            r.computeAverages(ra,false);
             
-            //Calculate times
-            if(total > 0) {
-                if(current > 0)
-                    currentSendProcessingTime = r.getCurrentTotalValue() / current;
-                if(last > 0)
-                    lastSendProcessingTime = r.getLastTotalValue() / last;
-                avgSendProcessingTime =  (r.getCurrentTotalValue() + r.getLastTotalValue()) / total;
-            } else {
-                avgSendProcessingTime = r.getAverageValue();
-                //if(_log.shouldLog(Log.WARN))
-                //    _log.warn("No events occurred. Using 1 minute average to look at message delay.");
-            }
-
             int maxProcessingTime = _context.getProperty(PROP_MAX_PROCESSINGTIME, DEFAULT_MAX_PROCESSINGTIME);
 
             //Set throttling if necessary
-            if((avgSendProcessingTime > maxProcessingTime*0.9 
-                    || currentSendProcessingTime > maxProcessingTime
-                    || lastSendProcessingTime > maxProcessingTime)) {
+            if((ra.getAverage() > maxProcessingTime*0.9 
+                    || ra.getCurrent() > maxProcessingTime
+                    || ra.getLast() > maxProcessingTime)) {
                 if(_log.shouldLog(Log.WARN)) {
                     _log.warn("Refusing tunnel request due to sendProcessingTime " +
-                              ((int)currentSendProcessingTime) + " / " +
-                              ((int)lastSendProcessingTime) + " / " +
-                              ((int)avgSendProcessingTime) + " / " +
+                              ((int)ra.getCurrent()) + " / " +
+                              ((int)ra.getLast()) + " / " +
+                              ((int)ra.getAverage()) + " / " +
                               maxProcessingTime +
                               " current/last/avg/max ms");
                 }
@@ -181,11 +165,9 @@ class RouterThrottleImpl implements RouterThrottle {
             double tunnelGrowthFactor = getTunnelGrowthFactor();
             Rate avgTunnels = _context.statManager().getRate("tunnel.participatingTunnels").getRate(10*60*1000);
             if (avgTunnels != null) {
-                double avg = 0;
-                if (avgTunnels.getLastEventCount() > 0) 
-                    avg = avgTunnels.getAverageValue();
-                else
-                    avg = avgTunnels.getLifetimeAverageValue();
+                
+                double avg = avgTunnels.getAvgOrLifetimeAvg();
+                
                 int min = getMinThrottleTunnels();
                 if (avg < min)
                     avg = min;
@@ -222,11 +204,7 @@ class RouterThrottleImpl implements RouterThrottle {
         Rate tunnelTestTime10m = _context.statManager().getRate("tunnel.testSuccessTime").getRate(10*60*1000);
         if ( (tunnelTestTime1m != null) && (tunnelTestTime10m != null) && (tunnelTestTime1m.getLastEventCount() > 0) ) {
             double avg1m = tunnelTestTime1m.getAverageValue();
-            double avg10m = 0;
-            if (tunnelTestTime10m.getLastEventCount() > 0)
-                avg10m = tunnelTestTime10m.getAverageValue();
-            else
-                avg10m = tunnelTestTime10m.getLifetimeAverageValue();
+            double avg10m = tunnelTestTime10m.getAvgOrLifetimeAvg();
 
             if (avg10m < 5000)
                 avg10m = 5000; // minimum before complaining
@@ -272,13 +250,8 @@ class RouterThrottleImpl implements RouterThrottle {
         double messagesPerTunnel = DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE;
         if (rs != null) {
             r = rs.getRate(60*1000);
-            if (r != null) {
-                long count = r.getLastEventCount() + r.getCurrentEventCount();
-                if (count > 0)
-                    messagesPerTunnel = (r.getLastTotalValue() + r.getCurrentTotalValue()) / count;
-                else
-                    messagesPerTunnel = r.getLifetimeAverageValue();
-            }
+            if (r != null) 
+                messagesPerTunnel = r.computeAverages(ra, true).getAverage();
         }
         if (messagesPerTunnel < DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE)
             messagesPerTunnel = DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE;
diff --git a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
index 5c3894e1a600156a8d10e9490e3cd5a3dc0871b6..6750c025de700376a6de82a493302e10aa66fdfd 100644
--- a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
+++ b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
@@ -2,6 +2,7 @@ package net.i2p.router.peermanager;
 
 import net.i2p.I2PAppContext;
 import net.i2p.stat.Rate;
+import net.i2p.stat.RateAverages;
 import net.i2p.stat.RateStat;
 
 /**
@@ -122,15 +123,16 @@ class CapacityCalculator {
         Rate curAccepted = acceptStat.getRate(period);
         Rate curRejected = rejectStat.getRate(period);
         Rate curFailed = failedStat.getRate(period);
+        RateAverages ra = RateAverages.getTemp();
 
         double eventCount = 0;
         if (curAccepted != null) {
-            eventCount = curAccepted.getCurrentEventCount() + curAccepted.getLastEventCount();
+            eventCount = curAccepted.computeAverages(ra, false).getTotalEventCount();
             // Punish for rejections.
             // We don't want to simply do eventCount -= rejected or we get to zero with 50% rejection,
             // and we don't want everybody to be at zero during times of congestion.
             if (eventCount > 0 && curRejected != null) {
-                long rejected = curRejected.getCurrentEventCount() + curRejected.getLastEventCount();
+                long rejected = curRejected.computeAverages(ra,false).getTotalEventCount();
                 if (rejected > 0)
                     eventCount *= eventCount / (eventCount + (2 * rejected));
             }
@@ -144,7 +146,7 @@ class CapacityCalculator {
         // fast pool, for example, you have a 1/7 chance of being falsely blamed.
         // We also don't want to drive everybody's capacity to zero, that isn't helpful.
         if (curFailed != null) {
-            double failed = curFailed.getCurrentTotalValue() + curFailed.getLastTotalValue();
+            double failed = curFailed.computeAverages(ra, false).getTotalValues();
             if (failed > 0) {
                 //if ( (period <= 10*60*1000) && (curFailed.getCurrentEventCount() > 0) )
                 //    return 0.0d; // their tunnels have failed in the last 0-10 minutes
diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java
index ed3837ec449b5f737f861d24710959b437128332..d5a7ecd62e22ce246f7b25647c58fde6511c7828 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java
@@ -178,6 +178,6 @@ class ExploratoryPeerSelector extends TunnelPeerSelector {
         Rate r = rs.getRate(period);
         if (r == null)
             return 0;
-        return (int) (r.getLastEventCount() + r.getCurrentEventCount());
+        return (int) (r.computeAverages().getTotalEventCount());
     }
 }
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
index d14c32420daf765bf757f1b4cae5ede94fde037f..4a5e346e856002ddd71da33666da413664c175b7 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
@@ -19,6 +19,7 @@ import net.i2p.router.TunnelInfo;
 import net.i2p.router.TunnelPoolSettings;
 import net.i2p.router.tunnel.HopConfig;
 import net.i2p.stat.Rate;
+import net.i2p.stat.RateAverages;
 import net.i2p.stat.RateStat;
 import net.i2p.util.Log;
 
@@ -331,9 +332,10 @@ public class TunnelPool {
                 Rate rr = r.getRate(10*60*1000);
                 Rate sr = s.getRate(10*60*1000);
                 if (er != null && rr != null && sr != null) {
-                    long ec = er.getCurrentEventCount() + er.getLastEventCount();
-                    long rc = rr.getCurrentEventCount() + rr.getLastEventCount();
-                    long sc = sr.getCurrentEventCount() + sr.getLastEventCount();
+                    RateAverages ra = RateAverages.getTemp();
+                    long ec = er.computeAverages(ra, false).getTotalEventCount();
+                    long rc = rr.computeAverages(ra, false).getTotalEventCount();
+                    long sc = sr.computeAverages(ra, false).getTotalEventCount();
                     long tot = ec + rc + sc;
                     if (tot >= BUILD_TRIES_QUANTITY_OVERRIDE) {
                         if (1000 * sc / tot <=  1000 / BUILD_TRIES_QUANTITY_OVERRIDE)
@@ -366,9 +368,10 @@ public class TunnelPool {
                 Rate rr = r.getRate(10*60*1000);
                 Rate sr = s.getRate(10*60*1000);
                 if (er != null && rr != null && sr != null) {
-                    long ec = er.getCurrentEventCount() + er.getLastEventCount();
-                    long rc = rr.getCurrentEventCount() + rr.getLastEventCount();
-                    long sc = sr.getCurrentEventCount() + sr.getLastEventCount();
+                    RateAverages ra = RateAverages.getTemp();
+                    long ec = er.computeAverages(ra, false).getTotalEventCount();
+                    long rc = rr.computeAverages(ra, false).getTotalEventCount();
+                    long sc = sr.computeAverages(ra, false).getTotalEventCount();
                     long tot = ec + rc + sc;
                     if (tot >= BUILD_TRIES_LENGTH_OVERRIDE) {
                         if (1000 * sc / tot <=  1000 / BUILD_TRIES_LENGTH_OVERRIDE)