diff --git a/apps/routerconsole/java/src/net/i2p/router/web/OldConsoleHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/OldConsoleHelper.java
index b88187681d38c31ec29596f2240e9188a7b390ab..100d7140a79bd6cc0192eeedebf95c97def305a2 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/OldConsoleHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/OldConsoleHelper.java
@@ -6,8 +6,14 @@ import java.io.OutputStreamWriter;
 
 
 public class OldConsoleHelper extends HelperBase {
+    private boolean _full;
+
     public OldConsoleHelper() {}
     
+    public void setFull(String f) {
+        _full = f != null && f.length() > 0;
+    }
+
     public String getConsole() {
         try {
             if (_out != null) {
@@ -27,11 +33,11 @@ public class OldConsoleHelper extends HelperBase {
         StatsGenerator gen = new StatsGenerator(_context);
         try {
             if (_out != null) {
-                gen.generateStatsPage(_out);
+                gen.generateStatsPage(_out, _full);
                 return "";
             } else {
                 ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
-                gen.generateStatsPage(new OutputStreamWriter(baos));
+                gen.generateStatsPage(new OutputStreamWriter(baos), _full);
                 return baos.toString();
             }
         } catch (IOException ioe) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java b/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java
index e2aee86be8db01f043a3ebc2b77a4e6d3d0991bd..b229e8483925a10a4846031afbcaa9bcded7fa34 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java
@@ -22,12 +22,13 @@ import net.i2p.util.Log;
 public class StatsGenerator {
     private Log _log;
     private RouterContext _context;
+
     public StatsGenerator(RouterContext context) {
         _context = context;
         _log = context.logManager().getLog(StatsGenerator.class);
     }
     
-    public void generateStatsPage(Writer out) throws IOException {
+    public void generateStatsPage(Writer out, boolean showAll) throws IOException {
         StringBuilder buf = new StringBuilder(16*1024);
         buf.append("<div class=\"joblog\"><form action=\"/stats.jsp\">");
         buf.append("<select name=\"go\" onChange='location.href=this.value'>");
@@ -58,8 +59,9 @@ public class StatsGenerator {
         
         buf.append(_("Statistics gathered during this router's uptime")).append(" (");
         long uptime = _context.router().getUptime();
-        buf.append(DataHelper.formatDuration(uptime));
+        buf.append(DataHelper.formatDuration2(uptime));
         buf.append(").  ").append( _("The data gathered is quantized over a 1 minute period, so should just be used as an estimate."));
+        buf.append(' ').append( _("These statistics are primarily used for development and debugging."));
 
         out.write(buf.toString());
         buf.setLength(0);
@@ -85,7 +87,7 @@ public class StatsGenerator {
                 if (_context.statManager().isFrequency(stat))
                     renderFrequency(stat, buf);
                 else
-                    renderRate(stat, buf);
+                    renderRate(stat, buf, showAll);
                 out.write(buf.toString());
                 buf.setLength(0);
             }
@@ -99,38 +101,50 @@ public class StatsGenerator {
         buf.append("<i>");
         buf.append(freq.getDescription());
         buf.append("</i><br>");
+        if (freq.getEventCount() <= 0) {
+            buf.append(_("No lifetime events")).append("<br>\n");
+            return;
+        }
         long uptime = _context.router().getUptime();
         long periods[] = freq.getPeriods();
         Arrays.sort(periods);
+        buf.append("<ul>");
         for (int i = 0; i < periods.length; i++) {
             if (periods[i] > uptime)
                 break;
+            buf.append("<li>");
             renderPeriod(buf, periods[i], _("frequency"));
             Frequency curFreq = freq.getFrequency(periods[i]);
-            buf.append(" <i>avg per period:</i> (");
+            buf.append(DataHelper.formatDuration2(Math.round(curFreq.getAverageInterval())));
+            buf.append("; ");
+            buf.append(_("Rolling average events per period"));
+            buf.append(": ");
             buf.append(num(curFreq.getAverageEventsPerPeriod()));
-            buf.append(", max ");
+            buf.append("; ");
+            buf.append(_("Highest events per period"));
+            buf.append(": ");
             buf.append(num(curFreq.getMaxAverageEventsPerPeriod()));
-            if ( (curFreq.getMaxAverageEventsPerPeriod() > 0) && (curFreq.getAverageEventsPerPeriod() > 0) ) {
-                buf.append(", current is ");
-                buf.append(pct(curFreq.getAverageEventsPerPeriod()/curFreq.getMaxAverageEventsPerPeriod()));
-                buf.append(" of max");
-            }
-            buf.append(")");
+            buf.append("; ");
+            //if (showAll && (curFreq.getMaxAverageEventsPerPeriod() > 0) && (curFreq.getAverageEventsPerPeriod() > 0) ) {
+            //    buf.append("(current is ");
+            //    buf.append(pct(curFreq.getAverageEventsPerPeriod()/curFreq.getMaxAverageEventsPerPeriod()));
+            //    buf.append(" of max)");
+            //}
             //buf.append(" <i>avg interval between updates:</i> (").append(num(curFreq.getAverageInterval())).append("ms, min ");
             //buf.append(num(curFreq.getMinAverageInterval())).append("ms)");
-            buf.append(" <i>strict average per period:</i> ");
+            buf.append(_("Lifetime average events per period")).append(": ");
             buf.append(num(curFreq.getStrictAverageEventsPerPeriod()));
-            buf.append(" events (averaged ");
-            buf.append(" using the lifetime of ");
-            buf.append(curFreq.getEventCount());
-            buf.append(" events)");
-            buf.append("<br>\n");
+            buf.append("</li>\n");
         }
-        buf.append("<br>\n");
+        // Display the strict average
+        buf.append("<li><b>").append(_("Lifetime average frequency")).append(":</b> ");
+        buf.append(DataHelper.formatDuration2(freq.getFrequency()));
+        buf.append(" (");
+        buf.append(ngettext((int) freq.getEventCount(), "1 event", "{0} events"));
+        buf.append(")</li></ul><br>\n");
     }
     
-    private void renderRate(String name, StringBuilder buf) {
+    private void renderRate(String name, StringBuilder buf, boolean showAll) {
         RateStat rate = _context.statManager().getRate(name);
         String d = rate.getDescription();
         if (! "".equals(d)) {
@@ -153,40 +167,39 @@ public class StatsGenerator {
             buf.append("<li>");
             renderPeriod(buf, periods[i], _("rate"));
             if (curRate.getLastEventCount() > 0) {
-                buf.append( "<i>").append(_("avg value")).append(":</i> (");
+                buf.append(_("Average")).append(":</i> ");
                 buf.append(num(curRate.getAverageValue()));
-                buf.append(" peak ");
+                buf.append("; ");
+                buf.append(_("Highest average"));
+                buf.append(": ");
                 buf.append(num(curRate.getExtremeAverageValue()));
-                buf.append(", [");
-                buf.append(pct(curRate.getPercentageOfExtremeValue()));
-                buf.append(" of max");
-                buf.append(", and ");
-                buf.append(pct(curRate.getPercentageOfLifetimeValue()));
-                buf.append(" of lifetime average]");
-                
-                buf.append(")");
-                buf.append(" <i>highest total period value:</i> (");
-                buf.append(num(curRate.getExtremeTotalValue()));
-                buf.append(")");
-                if (curRate.getLifetimeTotalEventTime() > 0) {
-                    buf.append(" <i>saturation:</i> (");
+                buf.append("; ");
+
+                // This is rarely interesting
+                // Don't bother to translate
+                if (showAll) {
+                    buf.append("Highest total in a period: ");
+                    buf.append(num(curRate.getExtremeTotalValue()));
+                    buf.append("; ");
+                }
+
+                // Saturation stats, which nobody understands, even when it isn't meaningless
+                // Don't bother to translate
+                if (showAll && curRate.getLifetimeTotalEventTime() > 0) {
+                    buf.append("Saturation: ");
                     buf.append(pct(curRate.getLastEventSaturation()));
-                    buf.append(")");
-                    buf.append(" <i>saturated limit:</i> (");
+                    buf.append("; Saturated limit: ");
                     buf.append(num(curRate.getLastSaturationLimit()));
-                    buf.append(")");
-                    buf.append(" <i>peak saturation:</i> (");
+                    buf.append("; Peak saturation: ");
                     buf.append(pct(curRate.getExtremeEventSaturation()));
-                    buf.append(")");
-                    buf.append(" <i>peak saturated limit:</i> (");
+                    buf.append("; Peak saturated limit: ");
                     buf.append(num(curRate.getExtremeSaturationLimit()));
-                    buf.append(")");
+                    buf.append("; ");
                 }
-                buf.append(" <i>").append(_("events")).append(":</i> ");
-                buf.append(curRate.getLastEventCount());
-                buf.append(" <i>in this period which ended:</i> ");
-                buf.append(DataHelper.formatDuration(now - curRate.getLastCoalesceDate()));
-                buf.append(" ago ");
+
+                buf.append(ngettext((int) curRate.getLastEventCount(), "There was 1 event", "There were {0} events"));
+                buf.append(' ');
+                buf.append(_("in this period which ended {0} ago.", DataHelper.formatDuration2(now - curRate.getLastCoalesceDate())));
             } else {
                 buf.append(" <i>").append(_("No events")).append("</i> ");
             }
@@ -194,38 +207,38 @@ public class StatsGenerator {
             if (numPeriods > 0) {
                 double avgFrequency = curRate.getLifetimeEventCount() / (double)numPeriods;
                 double peakFrequency = curRate.getExtremeEventCount();
-                buf.append(" (").append(_("lifetime average")).append(": ");
+                buf.append(" (").append(_("Average event count")).append(": ");
                 buf.append(num(avgFrequency));
-                buf.append(", ").append(_("peak average")).append(": ");
+                buf.append("; ").append(_("Events in peak period")).append(": ");
+                // This isn't really the highest event count, but the event count during the period with the highest total value.
                 buf.append(curRate.getExtremeEventCount());
                 buf.append(")");
             }
             if (curRate.getSummaryListener() != null) {
                 buf.append(" <a href=\"viewstat.jsp?stat=").append(name);
                 buf.append("&amp;period=").append(periods[i]);
-                buf.append("\" title=\"Render summarized data\">render</a>");
+                buf.append("\">").append(_("Graph Data")).append("</a> - ");
                 buf.append(" <a href=\"viewstat.jsp?stat=").append(name);
-                buf.append("&amp;period=").append(periods[i]).append("&amp;showEvents=true\" title=\"Render summarized event counts\">events</a>");
-                buf.append(" (as <a href=\"viewstat.jsp?stat=").append(name);
+                buf.append("&amp;period=").append(periods[i]).append("&amp;showEvents=true\">").append(_("Graph Event Count")).append("</a> - ");
+                buf.append("<a href=\"viewstat.jsp?stat=").append(name);
                 buf.append("&amp;period=").append(periods[i]);
-                buf.append("&amp;format=xml\" title=\"Dump stat history as XML\">XML</a>");
-                buf.append(" in a format <a href=\"http://people.ee.ethz.ch/~oetiker/webtools/rrdtool\">RRDTool</a> understands)");
+                buf.append("&amp;format=xml\">").append(_("Export Data as XML")).append("</a>");
             }
             buf.append("</li>\n");
         }
         // Display the strict average
-        buf.append("<li><b>").append(_("lifetime average value")).append(":</b> ");
+        buf.append("<li><b>").append(_("Lifetime average value")).append(":</b> ");
         buf.append(num(rate.getLifetimeAverageValue()));
-        buf.append(" over ");
-        buf.append(rate.getLifetimeEventCount());
-        buf.append(" events<br></li>");
-        buf.append("</ul>");
-        buf.append("<br>\n");
+        buf.append(" (");
+        buf.append(ngettext((int) rate.getLifetimeEventCount(), "1 event", "{0} events"));
+        buf.append(")<br></li>" +
+                   "</ul>" +
+                   "<br>\n");
     }
     
     private static void renderPeriod(StringBuilder buf, long period, String name) {
         buf.append("<b>");
-        buf.append(DataHelper.formatDuration(period));
+        buf.append(DataHelper.formatDuration2(period));
         buf.append(" ");
         buf.append(name);
         buf.append(":</b> ");
@@ -246,4 +259,9 @@ public class StatsGenerator {
     private String _(String s, Object o) {
         return Messages.getString(s, o, _context);
     }
+
+    /** translate a string */
+    private String ngettext(int n, String s, String p) {
+        return Messages.getString(n, s, p, _context);
+    }
 }
diff --git a/apps/routerconsole/jsp/stats.jsp b/apps/routerconsole/jsp/stats.jsp
index d140d7c2cce6dbc1e017204ee1799ace9888c812..61466d98617fb8b4a1b05991938148824b691c77 100644
--- a/apps/routerconsole/jsp/stats.jsp
+++ b/apps/routerconsole/jsp/stats.jsp
@@ -10,6 +10,7 @@
 <jsp:useBean class="net.i2p.router.web.OldConsoleHelper" id="oldhelper" scope="request" />
 <jsp:setProperty name="oldhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
 <jsp:setProperty name="oldhelper" property="writer" value="<%=out%>" />
+<jsp:setProperty name="oldhelper" property="full" value="<%=request.getParameter("f")%>" />
  <h1><%=intl._("I2P Router Statistics")%></h1>
 <div class="main" id="main">
  <jsp:getProperty name="oldhelper" property="stats" />
diff --git a/core/java/src/net/i2p/stat/Frequency.java b/core/java/src/net/i2p/stat/Frequency.java
index 6be7dc3c908223df3d9e3079c66864ad53f15def..facde2fc006dc7283322d04cf2eb32a96e4a728a 100644
--- a/core/java/src/net/i2p/stat/Frequency.java
+++ b/core/java/src/net/i2p/stat/Frequency.java
@@ -1,30 +1,41 @@
 package net.i2p.stat;
 
 /**
- * Manage the calculation of a moving event frequency over a certain period.
+ * Manage the calculation of a moving average event frequency over a certain period.
  *
+ * This provides lifetime, and rolling average, frequency counts.
+ * Unlike Rate, it does not support "bucketed" averages.
+ * There is no tracking of the event frequency in the current or last bucket.
+ * There are no buckets at all.
+ *
+ * Depending on what you want, a rolling average might be better than buckets.
+ * Or not.
  */
 public class Frequency {
     private double _avgInterval;
     private double _minAverageInterval;
-    private long _period;
+    private final long _period;
     private long _lastEvent;
-    private long _start = now();
-    private long _count = 0;
+    private final long _start = now();
+    private long _count;
     private final Object _lock = this; // new Object(); // in case we want to do fancy sync later
 
+    /** @param period ms */
     public Frequency(long period) {
-        setPeriod(period);
+        _period = period;
+        _avgInterval = period + 1;
+        _minAverageInterval = _avgInterval;
     }
 
-    /** how long is this frequency averaged over? */
+    /** how long is this frequency averaged over? (ms) */
     public long getPeriod() {
-        synchronized (_lock) {
             return _period;
-        }
     }
 
-    /** when did the last event occur? */
+    /**
+     * when did the last event occur?
+     * @deprecated unused
+     */
     public long getLastEvent() {
         synchronized (_lock) {
             return _lastEvent;
@@ -34,7 +45,7 @@ public class Frequency {
     /** 
      * on average over the last $period, after how many milliseconds are events coming in, 
      * as calculated during the last event occurrence? 
-     *
+     * @return milliseconds; returns period + 1 if no events in previous period
      */
     public double getAverageInterval() {
         synchronized (_lock) {
@@ -42,14 +53,21 @@ public class Frequency {
         }
     }
 
-    /** what is the lowest average interval (aka most frequent) we have seen? */
+    /**
+     * what is the lowest average interval (aka most frequent) we have seen? (ms)
+     * @return milliseconds; returns period + 1 if no events in previous period
+     * @deprecated unused
+     */
     public double getMinAverageInterval() {
         synchronized (_lock) {
             return _minAverageInterval;
         }
     }
 
-    /** calculate how many events would occur in a period given the current average */
+    /**
+     * Calculate how many events would occur in a period given the current (rolling) average.
+     * Use getStrictAverageInterval() for the real lifetime average.
+     */
     public double getAverageEventsPerPeriod() {
         synchronized (_lock) {
             if (_avgInterval > 0) return _period / _avgInterval;
@@ -58,20 +76,26 @@ public class Frequency {
         }
     }
 
-    /** calculate how many events would occur in a period given the maximum average */
+    /**
+     * Calculate how many events would occur in a period given the maximum rolling average.
+     * Use getStrictAverageEventsPerPeriod() for the real lifetime average.
+     */
     public double getMaxAverageEventsPerPeriod() {
         synchronized (_lock) {
-            if (_minAverageInterval > 0) return _period / _minAverageInterval;
-            
+            if (_minAverageInterval > 0 && _minAverageInterval <= _period) return _period / _minAverageInterval;
+
             return 0;
         }
     }
 
-    /** over the lifetime of this stat, without any decay or weighting, what was the average interval between events? */
+    /**
+     * Over the lifetime of this stat, without any decay or weighting, what was the average interval between events? (ms)
+     * @return milliseconds; returns Double.MAX_VALUE if no events ever
+     */
     public double getStrictAverageInterval() {
         synchronized (_lock) {
             long duration = now() - _start;
-            if ((duration <= 0) || (_count <= 0)) return 0;
+            if ((duration <= 0) || (_count <= 0)) return Double.MAX_VALUE;
            
             return duration / (double) _count;
         }
@@ -80,11 +104,8 @@ public class Frequency {
     /** using the strict average interval, how many events occur within an average period? */
     public double getStrictAverageEventsPerPeriod() {
         double avgInterval = getStrictAverageInterval();
-        synchronized (_lock) {
-            if (avgInterval > 0) return _period / avgInterval;
-           
-            return 0;
-        }
+        if (avgInterval > 0) return _period / avgInterval;
+        return 0;
     }
 
     /** how many events have occurred within the lifetime of this stat? */
@@ -115,18 +136,23 @@ public class Frequency {
      */
     private void recalculate(boolean eventOccurred) {
         synchronized (_lock) {
+            // This calculates something of a rolling average interval.
             long now = now();
             long interval = now - _lastEvent;
-            if (interval >= _period)
-                interval = _period - 1;
+            if (interval > _period)
+                interval = _period;
             else if (interval <= 0) interval = 1;
 
-            double oldWeight = 1 - (interval / (float) _period);
-            double newWeight = (interval / (float) _period);
-
-            double oldInterval = _avgInterval * oldWeight;
-            double newInterval = interval * newWeight;
-            _avgInterval = oldInterval + newInterval;
+            if (interval >= _period && !eventOccurred) {
+                // ensure getAverageEventsPerPeriod() will return 0
+                _avgInterval = _period + 1;
+            } else {
+                double oldWeight = 1 - (interval / (float) _period);
+                double newWeight = (interval / (float) _period);
+                double oldInterval = _avgInterval * oldWeight;
+                double newInterval = interval * newWeight;
+                _avgInterval = oldInterval + newInterval;
+            }
 
             if ((_avgInterval < _minAverageInterval) || (_minAverageInterval <= 0)) _minAverageInterval = _avgInterval;
 
@@ -137,30 +163,6 @@ public class Frequency {
         }
     }
 
-    private void setPeriod(long milliseconds) {
-        synchronized (_lock) {
-            _period = milliseconds;
-        }
-    }
-
-    private void setLastEvent(long when) {
-        synchronized (_lock) {
-            _lastEvent = when;
-        }
-    }
-
-    private void setAverageInterval(double msInterval) {
-        synchronized (_lock) {
-            _avgInterval = msInterval;
-        }
-    }
-
-    private void setMinAverageInterval(double minAverageInterval) {
-        synchronized (_lock) {
-            _minAverageInterval = minAverageInterval;
-        }
-    }
-
     private final static long now() {
         return System.currentTimeMillis();
     }
diff --git a/core/java/src/net/i2p/stat/FrequencyStat.java b/core/java/src/net/i2p/stat/FrequencyStat.java
index 994d26807a5c4af8e7d6d0ff08ba3ff5d5ac4bf2..dfa7bcfe675298c564d2169f4d8d95372c19e4ad 100644
--- a/core/java/src/net/i2p/stat/FrequencyStat.java
+++ b/core/java/src/net/i2p/stat/FrequencyStat.java
@@ -3,13 +3,13 @@ package net.i2p.stat;
 /** coordinate an event frequency over various periods */
 public class FrequencyStat {
     /** unique name of the statistic */
-    private String _statName;
+    private final String _statName;
     /** grouping under which the stat is kept */
-    private String _groupName;
+    private final String _groupName;
     /** describe the stat */
-    private String _description;
+    private final String _description;
     /** actual frequency objects for this statistic */
-    private Frequency _frequencies[];
+    private final Frequency _frequencies[];
 
     public FrequencyStat(String name, String description, String group, long periods[]) {
         _statName = name;
@@ -26,10 +26,12 @@ public class FrequencyStat {
             _frequencies[i].eventOccurred();
     }
 
-    /** coalesce all the stats */
+    /**
+     * coalesce all the stats
+     */
     public void coalesceStats() {
-        //for (int i = 0; i < _frequencies.length; i++)
-        //	_frequencies[i].coalesceStats();
+        for (int i = 0; i < _frequencies.length; i++)
+            _frequencies[i].recalculate();
     }
 
     public String getName() {
@@ -58,9 +60,37 @@ public class FrequencyStat {
         return null;
     }
 
-    /* FIXME missing equals() method FIXME */
+    /**
+     * @return lifetime event count
+     * @since 0.8.2
+     */	
+    public long getEventCount() {
+        if ( (_frequencies == null) || (_frequencies.length <= 0) ) return 0;
+        return _frequencies[0].getEventCount();
+    }
+
+    /**
+     * @return lifetime average frequency in millisedonds, i.e. the average time between events, or Long.MAX_VALUE if no events ever
+     * @since 0.8.2
+     */	
+    public long getFrequency() {
+        if ( (_frequencies == null) || (_frequencies.length <= 0) ) return Long.MAX_VALUE;
+        double d = _frequencies[0].getStrictAverageInterval();
+        if (d > _frequencies[0].getPeriod())
+            return Long.MAX_VALUE;
+        return Math.round(d);
+    }
+
     @Override
     public int hashCode() {
         return _statName.hashCode();
     }
+
+    /** @since 0.8.2 */
+    @Override
+    public boolean equals(Object obj) {
+        if ((obj == null) || (obj.getClass() != FrequencyStat.class)) return false;
+        return _statName.equals(((FrequencyStat)obj)._statName);
+    }
+
 }
diff --git a/core/java/src/net/i2p/stat/Rate.java b/core/java/src/net/i2p/stat/Rate.java
index 25305bce742c51587d41a88f36139327a29d3898..fd0fae49f00d197c575c866c87cc21aca98bfdd2 100644
--- a/core/java/src/net/i2p/stat/Rate.java
+++ b/core/java/src/net/i2p/stat/Rate.java
@@ -10,6 +10,7 @@ import net.i2p.util.Log;
  * average value over a period, the number of events in that period, the maximum number
  * of events (using the interval between events), and lifetime data.
  *
+ * If value is always a constant, you should be using Frequency instead.
  */
 public class Rate {
     private final static Log _log = new Log(Rate.class);
@@ -70,7 +71,10 @@ public class Rate {
         return _extremeTotalValue;
     }
 
-    /** when the max(totalValue) was achieved, how many events occurred in that period? */
+    /**
+     * 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() {
         return _extremeEventCount;
     }
@@ -144,13 +148,50 @@ public class Rate {
         load(props, prefix, treatAsCurrent);
     }
 
-    /** accrue the data in the current period as an instantaneous event */
+    /**
+     * Accrue the data in the current period as an instantaneous event.
+     * If value is always a constant, you should be using Frequency instead.
+     * 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) {
-        addData(value, 0);
+        synchronized (_lock) {
+            _currentTotalValue += value;
+            _currentEventCount++;
+            _lifetimeTotalValue += value;
+            _lifetimeEventCount++;
+        }
     }
 
     /**
      * Accrue the data in the current period as if the event took the specified amount of time
+     * If value is always a constant, you should be using Frequency instead.
+     * If eventDuration is nonzero, then the various get*Saturation*() and get*EventTime()
+     * methods will also return nonzero.
+     *
+     * <pre>
+     * There are at least 4 possible strategies for eventDuration:
+     *
+     *   1) eventDuration is always zero.
+     *      The various get*Saturation*() and get*EventTime() methods will return zero.
+     *
+     *   2) Each eventDuration is relatively small, and reflects processing time.
+     *      This is probably the original meaning of "saturation", as it allows you
+     *      to track how much time is spent gathering the stats.
+     *      get*EventTime() will be close to 0.
+     *      get*EventSaturation() will return values close to 0,
+     *      get*SaturationLimit() will return adjusted values for the totals.
+     *
+     *   3) The total of the eventDurations are approximately equal to total elapsed time.
+     *      get*EventTime() will be close to the period.
+     *      get*EventSaturation() will return values close to 1,
+     *      get*SaturationLimit() will return adjusted values for the totals.
+     *
+     *   4) Each eventDuration is not a duration at all, but someother independent data.
+     *      get*EventTime() may be used to retrieve the data.
+     *      get*EventSaturation() are probably useless.
+     *      get*SaturationLimit() are probably useless.
+     * </pre>
      *
      * @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)
@@ -195,7 +236,7 @@ public class Rate {
                 correctedTotalValue = _currentTotalValue *
                                       (_lastEventCount / (double) _currentEventCount);
 
-            if (_lastTotalValue > _extremeTotalValue) {
+            if (_lastTotalValue >= _extremeTotalValue) {  // get the most recent if identical
                 _extremeTotalValue = _lastTotalValue;
                 _extremeEventCount = _lastEventCount;
                 _extremeTotalEventTime = _lastTotalEventTime;
@@ -220,7 +261,10 @@ public class Rate {
         return 0.0D;
     }
 
-    /** what was the average value across the events in the most active period? */
+    /**
+     * During the extreme period (i.e. the period with the highest total value),
+     * what was the average value?
+     */
     public double getExtremeAverageValue() {
         if ((_extremeTotalValue != 0) && (_extremeEventCount > 0))
             return _extremeTotalValue / _extremeEventCount;
@@ -240,7 +284,7 @@ public class Rate {
      * 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 percentage, or 0 if event times aren't used
+     * @return ratio, or 0 if event times aren't used
      */
     public double getLastEventSaturation() {
         if ((_lastEventCount > 0) && (_lastTotalEventTime > 0)) {
@@ -256,10 +300,11 @@ public class Rate {
     }
 
     /** 
-     * During the extreme period, how much of the time was spent actually processing events
+     * During the extreme period (i.e. the period with the highest total value),
+     * 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 percentage, or 0 if the statistic doesn't use event times
+     * @return ratio, or 0 if the statistic doesn't use event times
      */
     public double getExtremeEventSaturation() {
         if ((_extremeEventCount > 0) && (_extremeTotalEventTime > 0)) {
@@ -274,7 +319,7 @@ 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? 
      *
-     * @return percentage, or 0 if event times aren't used
+     * @return ratio, or 0 if event times aren't used
      */
     public double getLifetimeEventSaturation() {
         if ((_lastEventCount > 0) && (_lifetimeTotalEventTime > 0)) {
@@ -311,7 +356,8 @@ public class Rate {
     }
 
     /** 
-     * using the extreme period's rate, what is the total value that could have been 
+     * During the extreme period (i.e. the period with the highest total value),
+     * what is the total value that could have been 
      * sent if events were constant?
      *
      * @return event total at saturation, or 0 if no event times are measured
@@ -328,8 +374,9 @@ public class Rate {
     }
 
     /**
-     * How large was the last period's value as compared to the largest period ever?
-     *
+     * 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)
      */
     public double getPercentageOfExtremeValue() {
         if ((_lastTotalValue != 0) && (_extremeTotalValue != 0))
@@ -340,7 +387,7 @@ 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)
      */
     public double getPercentageOfLifetimeValue() {
         if ((_lastTotalValue != 0) && (_lifetimeTotalValue != 0)) {
@@ -500,6 +547,7 @@ public class Rate {
         return System.currentTimeMillis(); //Clock.getInstance().now();
     }
 
+/******
     public static void main(String args[]) {
         Rate rate = new Rate(1000);
         for (int i = 0; i < 50; i++) {
@@ -532,4 +580,5 @@ public class Rate {
         } catch (InterruptedException ie) { // nop
         }
     }
+******/
 }
diff --git a/core/java/src/net/i2p/stat/StatManager.java b/core/java/src/net/i2p/stat/StatManager.java
index 0393d577a339e312e67a6d5dba4f5c1dbfee541c..cc47e00414f5c62607505f135c1893b1429efb00 100644
--- a/core/java/src/net/i2p/stat/StatManager.java
+++ b/core/java/src/net/i2p/stat/StatManager.java
@@ -140,12 +140,17 @@ public class StatManager {
         if (stat != null) stat.addData(data, eventDuration);
     }
 
+    private int coalesceCounter;
+    /** every this many minutes for frequencies */
+    private static final int FREQ_COALESCE_RATE = 9;
+
     public void coalesceStats() {
-        synchronized (_frequencyStats) {
-            for (Iterator<FrequencyStat> iter = _frequencyStats.values().iterator(); iter.hasNext();) {
-                FrequencyStat stat = iter.next();
-                if (stat != null) {
-                    stat.coalesceStats();
+        if (++coalesceCounter % FREQ_COALESCE_RATE == 0) {
+            synchronized (_frequencyStats) {
+                for (FrequencyStat stat : _frequencyStats.values()) {
+                    if (stat != null) {
+                        stat.coalesceStats();
+                    }
                 }
             }
         }
diff --git a/router/java/src/net/i2p/router/StatisticsManager.java b/router/java/src/net/i2p/router/StatisticsManager.java
index 03afd73b0591a21862d8d5641af5283c9233af48..5a00f154c84bbb299408be373b5eb789532676b7 100644
--- a/router/java/src/net/i2p/router/StatisticsManager.java
+++ b/router/java/src/net/i2p/router/StatisticsManager.java
@@ -280,7 +280,7 @@ public class StatisticsManager implements Service {
         stats.setProperty("stat_bandwidthReceiveBps.60m", str);
     }
 
-    private String getPeriod(Rate rate) { return DataHelper.formatDuration(rate.getPeriod()); }
+    private static String getPeriod(Rate rate) { return DataHelper.formatDuration(rate.getPeriod()); }
 
     private final String num(double num) { 
         if (num < 0) num = 0;