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 - 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(" ", " "); 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