diff --git a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java
index 55a85490085576688a2df2d6b2e88e82836c10c4..ef81a92d265083086479aaf922a3aeaa902af802 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java
@@ -17,6 +17,7 @@ public class GraphHelper extends FormHandler {
     private int _width;
     private int _height;
     private int _refreshDelaySeconds;
+    private boolean _persistent;
 
     private static final String PROP_X = "routerconsole.graphX";
     private static final String PROP_Y = "routerconsole.graphY";
@@ -39,9 +40,21 @@ public class GraphHelper extends FormHandler {
         _height = _context.getProperty(PROP_Y, DEFAULT_Y);
         _periodCount = _context.getProperty(PROP_PERIODS, DEFAULT_PERIODS);
         _refreshDelaySeconds = _context.getProperty(PROP_REFRESH, DEFAULT_REFRESH);
-        _showEvents = Boolean.valueOf(_context.getProperty(PROP_EVENTS)).booleanValue();
+        _showEvents = _context.getBooleanProperty(PROP_EVENTS);
     }
     
+    /**
+     *  This must be output in the jsp since <meta> must be in the <head>
+     *  @since 0.8.6
+     */
+    public String getRefreshMeta() {
+        if (_refreshDelaySeconds <= 8 ||
+            ConfigRestartBean.getRestartTimeRemaining() < (1000 * (_refreshDelaySeconds + 30)))
+            return "";
+        // shorten the refresh by 3 seconds so we beat the iframe
+        return "<meta http-equiv=\"refresh\" content=\"" + (_refreshDelaySeconds - 3) + "\">";
+    }
+
     /**
      *  This was a HelperBase but now it's a FormHandler
      *  @since 0.8.2
@@ -51,13 +64,17 @@ public class GraphHelper extends FormHandler {
     public void setPeriodCount(String str) { 
         try { _periodCount = Integer.parseInt(str); } catch (NumberFormatException nfe) {}
     }
+
     public void setShowEvents(boolean b) { _showEvents = b; }
+
     public void setHeight(String str) {
         try { _height = Math.min(Integer.parseInt(str), MAX_Y); } catch (NumberFormatException nfe) {}
     }
+
     public void setWidth(String str) {
         try { _width = Math.min(Integer.parseInt(str), MAX_X); } catch (NumberFormatException nfe) {}
     }
+
     public void setRefreshDelay(String str) {
         try {
             int rds = Integer.parseInt(str);
@@ -67,6 +84,9 @@ public class GraphHelper extends FormHandler {
                 _refreshDelaySeconds = -1;
         } catch (NumberFormatException nfe) {}
     }
+
+    /** @since 0.8.6 */
+    public void setPersistent(String foo) { _persistent = true; }
     
     public String getImages() { 
         try {
@@ -123,13 +143,9 @@ public class GraphHelper extends FormHandler {
                            + "\" alt=\"" + title 
                            + "\" title=\"" + title + "\"></a>\n");
             }
-            // FIXME <meta> not allowed inside <div>, move to the .jsp
-            if (_refreshDelaySeconds > 0)
-                // shorten the refresh by 3 seconds so we beat the iframe
-                _out.write("<meta http-equiv=\"refresh\" content=\"" + (_refreshDelaySeconds - 3) + "\">\n");
 
             // FIXME jrobin doesn't support setting the timezone, will have to mod TimeAxis.java
-            _out.write("<p<i>" + _("All times are UTC.") + "</i></p>\n");
+            _out.write("<p><i>" + _("All times are UTC.") + "</i></p>\n");
         } catch (IOException ioe) {
             ioe.printStackTrace();
         }
@@ -148,11 +164,11 @@ public class GraphHelper extends FormHandler {
             _out.write("<form action=\"graphs\" method=\"POST\">\n" +
                        "<input type=\"hidden\" name=\"action\" value=\"foo\">\n" +
                        "<input type=\"hidden\" name=\"nonce\" value=\"" + nonce + "\" >\n");
-            _out.write(_("Periods") + ": <input size=\"3\" type=\"text\" name=\"periodCount\" value=\"" + _periodCount + "\"><br>\n");
+            _out.write(_("Periods") + ": <input size=\"5\" style=\"text-align: right;\" type=\"text\" name=\"periodCount\" value=\"" + _periodCount + "\"><br>\n");
             _out.write(_("Plot averages") + ": <input type=\"radio\" class=\"optbox\" name=\"showEvents\" value=\"false\" " + (_showEvents ? "" : "checked=\"true\" ") + "> ");
             _out.write(_("or")+ " " +_("plot events") + ": <input type=\"radio\" class=\"optbox\" name=\"showEvents\" value=\"true\" "+ (_showEvents ? "checked=\"true\" " : "") + "><br>\n");
-            _out.write(_("Image sizes") + ": " + _("width") + ": <input size=\"4\" type=\"text\" name=\"width\" value=\"" + _width 
-                       + "\"> " + _("pixels") + ", " + _("height") + ": <input size=\"4\" type=\"text\" name=\"height\" value=\"" + _height  
+            _out.write(_("Image sizes") + ": " + _("width") + ": <input size=\"4\" style=\"text-align: right;\" type=\"text\" name=\"width\" value=\"" + _width 
+                       + "\"> " + _("pixels") + ", " + _("height") + ": <input size=\"4\" style=\"text-align: right;\" type=\"text\" name=\"height\" value=\"" + _height  
                        + "\"> " + _("pixels") + "<br>\n");
             _out.write(_("Refresh delay") + ": <select name=\"refreshDelay\">");
             for (int i = 0; i < times.length; i++) {
@@ -169,7 +185,13 @@ public class GraphHelper extends FormHandler {
                 _out.write("</option>\n");
             }
             _out.write("</select><br>\n" +
-                       "<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>");
+                       _("Store graph data on disk?") +
+                       " <input type=\"checkbox\" class=\"optbox\" value=\"true\" name=\"persistent\"");
+            boolean persistent = _context.getBooleanPropertyDefaultTrue(SummaryListener.PROP_PERSISTENT);
+            if (persistent)
+                _out.write(" checked=\"true\"");
+            _out.write(">" +
+                       "<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Save settings and redraw graphs") + "\"></div></form>");
         } catch (IOException ioe) {
             ioe.printStackTrace();
         }
@@ -194,26 +216,27 @@ public class GraphHelper extends FormHandler {
             _height != _context.getProperty(PROP_Y, DEFAULT_Y) ||
             _periodCount != _context.getProperty(PROP_PERIODS, DEFAULT_PERIODS) ||
             _refreshDelaySeconds != _context.getProperty(PROP_REFRESH, DEFAULT_REFRESH) ||
-            _showEvents != Boolean.valueOf(_context.getProperty(PROP_EVENTS)).booleanValue()) {
+            _showEvents != _context.getBooleanProperty(PROP_EVENTS) ||
+            _persistent != _context.getBooleanPropertyDefaultTrue(SummaryListener.PROP_PERSISTENT)) {
             _context.router().setConfigSetting(PROP_X, "" + _width);
             _context.router().setConfigSetting(PROP_Y, "" + _height);
             _context.router().setConfigSetting(PROP_PERIODS, "" + _periodCount);
             _context.router().setConfigSetting(PROP_REFRESH, "" + _refreshDelaySeconds);
             _context.router().setConfigSetting(PROP_EVENTS, "" + _showEvents);
+            _context.router().setConfigSetting(SummaryListener.PROP_PERSISTENT, "" + _persistent);
             _context.router().saveConfig();
             addFormNotice(_("Graph settings saved"));
         }
     }
 
-/** inner class, don't bother reindenting */
-private static class AlphaComparator implements Comparator {
-    public int compare(Object lhs, Object rhs) {
-        SummaryListener l = (SummaryListener)lhs;
-        SummaryListener r = (SummaryListener)rhs;
-        String lName = l.getRate().getRateStat().getName() + "." + l.getRate().getPeriod();
-        String rName = r.getRate().getRateStat().getName() + "." + r.getRate().getPeriod();
-        return lName.compareTo(rName);
+    private static class AlphaComparator implements Comparator<SummaryListener> {
+        public int compare(SummaryListener l, SummaryListener r) {
+            String lName = l.getRate().getRateStat().getName();
+            String rName = r.getRate().getRateStat().getName();
+            int rv = lName.compareTo(rName);
+            if (rv != 0)
+                return rv;
+            return (int) (l.getRate().getPeriod() - r.getRate().getPeriod());
+        }
     }
 }
-
-}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java b/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java
index ca009084ae35679f50029e79c41e58acc819862f..d08bdc3a03353bd3a3e83f157cd3608d8eb92fb9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java
@@ -3,6 +3,7 @@ package net.i2p.router.web;
 import java.awt.Color;
 import java.awt.Graphics;
 import java.awt.image.BufferedImage;
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
@@ -18,6 +19,7 @@ import javax.imageio.stream.MemoryCacheImageOutputStream;
 import net.i2p.router.RouterContext;
 import net.i2p.stat.Rate;
 import net.i2p.stat.RateStat;
+import net.i2p.util.FileUtil;
 import net.i2p.util.Log;
 
 import org.jrobin.core.RrdException;
@@ -60,6 +62,9 @@ public class StatSummarizer implements Runnable {
     public static StatSummarizer instance() { return _instance; }
     
     public void run() {
+        boolean isPersistent = _context.getBooleanPropertyDefaultTrue(SummaryListener.PROP_PERSISTENT);
+        if (!isPersistent)
+            deleteOldRRDs();
         _thread = Thread.currentThread();
         String specs = "";
         while (_isRunning && _context.router().isAlive()) {
@@ -126,10 +131,10 @@ public class StatSummarizer implements Runnable {
     }
     
     private void removeDb(Rate r) {
-        for (int i = 0; i < _listeners.size(); i++) {
-            SummaryListener lsnr = _listeners.get(i);
+        for (SummaryListener lsnr : _listeners) {
             if (lsnr.getRate().equals(r)) {
-                _listeners.remove(i);
+                // no iter.remove() in COWAL
+                _listeners.remove(lsnr);
                 lsnr.stopListening();
                 return;
             }
@@ -178,8 +183,7 @@ public class StatSummarizer implements Runnable {
             height = GraphHelper.MAX_Y;
         else if (height <= 0)
             height = GraphHelper.DEFAULT_Y;
-        for (int i = 0; i < _listeners.size(); i++) {
-            SummaryListener lsnr = _listeners.get(i);
+        for (SummaryListener lsnr : _listeners) {
             if (lsnr.getRate().equals(rate)) {
                 lsnr.renderPng(out, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, showCredit);
                 return true;
@@ -206,8 +210,7 @@ public class StatSummarizer implements Runnable {
     }
 
     private boolean locked_getXML(Rate rate, OutputStream out) throws IOException {
-        for (int i = 0; i < _listeners.size(); i++) {
-            SummaryListener lsnr = _listeners.get(i);
+        for (SummaryListener lsnr : _listeners) {
             if (lsnr.getRate().equals(rate)) {
                 lsnr.getData().exportXml(out);
                 out.write(("<!-- Rate: " + lsnr.getRate().getRateStat().getName() + " for period " + lsnr.getRate().getPeriod() + " -->\n").getBytes());
@@ -274,7 +277,6 @@ public class StatSummarizer implements Runnable {
             def.setTimeSpan(start/1000, end/1000);
             def.setMinValue(0d);
             def.setBase(1024);
-            // Note to translators: all runtime zh translation disabled in this file, no font available in RRD
             String title = _("Bandwidth usage");
             if (!hideTitle)
                 def.setTitle(title);
@@ -365,6 +367,15 @@ public class StatSummarizer implements Runnable {
         return rv;
     }
 
+    /**
+     *  Delete the old rrd dir if we are no longer persistent
+     *  @since 0.8.6
+     */
+    private void deleteOldRRDs() {
+        File rrdDir = new File(_context.getRouterDir(), SummaryListener.RRD_DIR);
+        FileUtil.rmdir(rrdDir, false);
+    }
+
     /** translate a string */
     private String _(String s) {
         // the RRD font doesn't have zh chars, at least on my system
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java
index c66c2fbccfdafcad11bc8e9c7b08ab664fa33b98..b5721ff5fe0f8f0933ec66c0b3338b6dc0003210 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java
@@ -32,9 +32,9 @@ import org.jrobin.graph.RrdGraphDefTemplate;
  *  @since 0.6.1.13
  */
 class SummaryListener implements RateSummaryListener {
-    private static final String PROP_PERSISTENT = "routerconsole.graphPersistent";
+    static final String PROP_PERSISTENT = "routerconsole.graphPersistent";
     /** note that .jrb files are NOT compatible with .rrd files */
-    private static final String RRD_DIR = "rrd";
+    static final String RRD_DIR = "rrd";
     private static final String RRD_PREFIX = "rrd-";
     private static final String RRD_SUFFIX = ".jrb";
     static final String CF = "AVERAGE";
@@ -62,7 +62,7 @@ class SummaryListener implements RateSummaryListener {
         _context = I2PAppContext.getGlobalContext();
         _rate = r;
         _log = _context.logManager().getLog(SummaryListener.class);
-        _isPersistent = _context.getBooleanProperty(PROP_PERSISTENT);
+        _isPersistent = _context.getBooleanPropertyDefaultTrue(PROP_PERSISTENT);
     }
     
     public void add(double totalValue, long eventCount, double totalEventTime, long period) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
index 9aaa83ecd3daa6958e8ee67ef5dab8176b830638..0d9693c2b0122cb6078f43ad4e3900c2d5b63a22 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
@@ -101,12 +101,11 @@ class SummaryRenderer {
                 String title;
                 String p;
                 // we want the formatting and translation of formatDuration2(), except not zh, and not the &nbsp;
-                if ("zh".equals(Messages.getLanguage(_context)))
-                    p = DataHelper.formatDuration(_listener.getRate().getPeriod());
-                else
+                //if ("zh".equals(Messages.getLanguage(_context)))
+                //    p = DataHelper.formatDuration(_listener.getRate().getPeriod());
+                //else
                     p = DataHelper.formatDuration2(_listener.getRate().getPeriod()).replace("&nbsp;", " ");
                 if (showEvents)
-                    // Note to translators: all runtime zh translation disabled in this file, no font available in RRD
                     title = name + ' ' + _("events in {0}", p);
                 else
                     title = name + ' ' + _("averaged for {0}", p);
@@ -133,7 +132,10 @@ class SummaryRenderer {
             if (started > start && started < end)
                 def.vrule(started / 1000, Color.BLACK, _("Restart"), 4.0f);
             def.datasource(plotName, path, plotName, SummaryListener.CF, _listener.getBackendName());
-            def.area(plotName, Color.BLUE, descr + "\\r");
+            if (descr.length() > 0)
+                def.area(plotName, Color.BLUE, descr + "\\r");
+            else
+                def.area(plotName, Color.BLUE);
             if (!hideLegend) {
                 def.gprint(plotName, SummaryListener.CF, _("avg") + ": %.2f %s");
                 def.gprint(plotName, "MAX", ' ' + _("max") + ": %.2f %S");
diff --git a/apps/routerconsole/jsp/graphs.jsp b/apps/routerconsole/jsp/graphs.jsp
index bbda259441a9564c05abfcd7bc3a193e23c389fc..e54133250a7233399a9c3e696fae2a2daf3f3324 100644
--- a/apps/routerconsole/jsp/graphs.jsp
+++ b/apps/routerconsole/jsp/graphs.jsp
@@ -5,19 +5,26 @@
 <html><head>
 <%@include file="css.jsi" %>
 <%=intl.title("graphs")%>
+ <jsp:useBean class="net.i2p.router.web.GraphHelper" id="graphHelper" scope="request" />
+ <% graphHelper.storeMethod(request.getMethod()); %>
+ <jsp:setProperty name="graphHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+<% /* GraphHelper sets the defaults in setContextId, so setting the properties must be after the context */ %>
+ <jsp:setProperty name="graphHelper" property="*" />
+<%
+    graphHelper.storeWriter(out);
+    graphHelper.storeMethod(request.getMethod());
+    // meta must be inside the head
+    boolean allowRefresh = intl.allowIFrame(request.getHeader("User-Agent"));
+    if (allowRefresh) {
+        out.print(graphHelper.getRefreshMeta());
+    }
+%>
 </head><body>
-
 <%@include file="summary.jsi" %>
 <h1><%=intl._("I2P Performance Graphs")%></h1>
 <div class="main" id="main">
  <div class="graphspanel">
  <div class="widepanel">
- <jsp:useBean class="net.i2p.router.web.GraphHelper" id="graphHelper" scope="request" />
- <% graphHelper.storeMethod(request.getMethod()); %>
- <jsp:setProperty name="graphHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
-<% /* GraphHelper sets the defaults in setContextId, so setting the properties must be after the context */ %>
- <jsp:setProperty name="graphHelper" property="*" />
- <% graphHelper.storeWriter(out); %>
  <jsp:getProperty name="graphHelper" property="allMessages" />
  <jsp:getProperty name="graphHelper" property="images" />
  <jsp:getProperty name="graphHelper" property="form" />
diff --git a/core/java/src/net/i2p/stat/Rate.java b/core/java/src/net/i2p/stat/Rate.java
index 8473d58ebbbfdcf2a2ca68e67980036a5db28a4f..21e496739a5121aa15af6300db6579e44e1d23ba 100644
--- a/core/java/src/net/i2p/stat/Rate.java
+++ b/core/java/src/net/i2p/stat/Rate.java
@@ -3,6 +3,7 @@ package net.i2p.stat;
 import java.io.IOException;
 import java.util.Properties;
 
+import net.i2p.data.DataHelper;
 import net.i2p.util.Log;
 
 /**
@@ -471,48 +472,28 @@ public class Rate {
         coalesce();
     }
 
+    /**
+     * This is used in StatSummarizer and SummaryListener.
+     * We base it on the stat we are tracking, not the stored data.
+     */
     @Override
     public boolean equals(Object obj) {
         if ((obj == null) || !(obj instanceof Rate)) return false;
         if (obj == this) return true;
         Rate r = (Rate) obj;
         return _period == r.getPeriod() && _creationDate == r.getCreationDate() &&
-        //_lastCoalesceDate == r.getLastCoalesceDate() &&
-               _currentTotalValue == r.getCurrentTotalValue() && _currentEventCount == r.getCurrentEventCount()
-               && _currentTotalEventTime == r.getCurrentTotalEventTime() && _lastTotalValue == r.getLastTotalValue()
-               && _lastEventCount == r.getLastEventCount() && _lastTotalEventTime == r.getLastTotalEventTime()
-               && _extremeTotalValue == r.getExtremeTotalValue() && _extremeEventCount == r.getExtremeEventCount()
-               && _extremeTotalEventTime == r.getExtremeTotalEventTime()
-               && _lifetimeTotalValue == r.getLifetimeTotalValue() && _lifetimeEventCount == r.getLifetimeEventCount()
-               && _lifetimeTotalEventTime == r.getLifetimeTotalEventTime();
+               // do this the easy way to avoid NPEs.
+               // Alternative: compare name and group name (very carefully to avoid NPEs)
+               _stat == r._stat;
     }
 
     /**
      * It doesn't appear that Rates are ever stored in a Set or Map
      * (RateStat stores in an array) so let's make this easy.
-     * We can always make something faster if it's actually used.
      */
     @Override
     public int hashCode() {
-/*****
-        int hash = 5;
-        hash = 67 * hash + (int)(Double.doubleToLongBits(this._currentTotalValue) ^ (Double.doubleToLongBits(this._currentTotalValue) >>> 32));
-        hash = 67 * hash + (int)(this._currentEventCount ^ (this._currentEventCount >>> 32));
-        hash = 67 * hash + (int)(this._currentTotalEventTime ^ (this._currentTotalEventTime >>> 32));
-        hash = 67 * hash + (int)(Double.doubleToLongBits(this._lastTotalValue) ^ (Double.doubleToLongBits(this._lastTotalValue) >>> 32));
-        hash = 67 * hash + (int)(this._lastEventCount ^ (this._lastEventCount >>> 32));
-        hash = 67 * hash + (int)(this._lastTotalEventTime ^ (this._lastTotalEventTime >>> 32));
-        hash = 67 * hash + (int)(Double.doubleToLongBits(this._extremeTotalValue) ^ (Double.doubleToLongBits(this._extremeTotalValue) >>> 32));
-        hash = 67 * hash + (int)(this._extremeEventCount ^ (this._extremeEventCount >>> 32));
-        hash = 67 * hash + (int)(this._extremeTotalEventTime ^ (this._extremeTotalEventTime >>> 32));
-        hash = 67 * hash + (int)(Double.doubleToLongBits(this._lifetimeTotalValue) ^ (Double.doubleToLongBits(this._lifetimeTotalValue) >>> 32));
-        hash = 67 * hash + (int)(this._lifetimeEventCount ^ (this._lifetimeEventCount >>> 32));
-        hash = 67 * hash + (int)(this._lifetimeTotalEventTime ^ (this._lifetimeTotalEventTime >>> 32));
-        hash = 67 * hash + (int)(this._creationDate ^ (this._creationDate >>> 32));
-        hash = 67 * hash + (int)(this._period ^ (this._period >>> 32));
-        return hash;
-******/
-        return toString().hashCode();
+        return DataHelper.hashCode(_stat) ^ ((int)_period) ^ ((int) _creationDate);
     }
 
     @Override