- Persistent RRD

- Restart line in graphs
- Restore zh fonts in graphs
This commit is contained in:
zzz
2011-03-17 21:13:52 +00:00
parent 1324eaf056
commit 033dee0216
3 changed files with 115 additions and 36 deletions

View File

@ -8,6 +8,7 @@ import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -44,20 +45,24 @@ public class StatSummarizer implements Runnable {
private static StatSummarizer _instance; private static StatSummarizer _instance;
private static final int MAX_CONCURRENT_PNG = 3; private static final int MAX_CONCURRENT_PNG = 3;
private final Semaphore _sem; private final Semaphore _sem;
private volatile boolean _isRunning = true;
private Thread _thread;
public StatSummarizer() { public StatSummarizer() {
_context = (RouterContext)RouterContext.listContexts().get(0); // fuck it, only summarize one per jvm _context = (RouterContext)RouterContext.listContexts().get(0); // fuck it, only summarize one per jvm
_log = _context.logManager().getLog(getClass()); _log = _context.logManager().getLog(getClass());
_listeners = new ArrayList(16); _listeners = new CopyOnWriteArrayList();
_instance = this; _instance = this;
_sem = new Semaphore(MAX_CONCURRENT_PNG, true); _sem = new Semaphore(MAX_CONCURRENT_PNG, true);
_context.addShutdownTask(new Shutdown());
} }
public static StatSummarizer instance() { return _instance; } public static StatSummarizer instance() { return _instance; }
public void run() { public void run() {
_thread = Thread.currentThread();
String specs = ""; String specs = "";
while (_context.router().isAlive()) { while (_isRunning && _context.router().isAlive()) {
specs = adjustDatabases(specs); specs = adjustDatabases(specs);
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
} }
@ -236,6 +241,20 @@ public class StatSummarizer implements Runnable {
private boolean locked_renderRatePng(OutputStream out, int width, int height, boolean hideLegend, private boolean locked_renderRatePng(OutputStream out, int width, int height, boolean hideLegend,
boolean hideGrid, boolean hideTitle, boolean showEvents, boolean hideGrid, boolean hideTitle, boolean showEvents,
int periodCount, boolean showCredit) throws IOException { int periodCount, boolean showCredit) throws IOException {
// go to some trouble to see if we have the data for the combined bw graph
SummaryListener txLsnr = null;
SummaryListener rxLsnr = null;
for (SummaryListener lsnr : StatSummarizer.instance().getListeners()) {
String title = lsnr.getRate().getRateStat().getName();
if (title.equals("bw.sendRate"))
txLsnr = lsnr;
else if (title.equals("bw.recvRate"))
rxLsnr = lsnr;
}
if (txLsnr == null || rxLsnr == null)
throw new IOException("no rates for combined graph");
long end = _context.clock().now() - 60*1000; long end = _context.clock().now() - 60*1000;
if (width > GraphHelper.MAX_X) if (width > GraphHelper.MAX_X)
width = GraphHelper.MAX_X; width = GraphHelper.MAX_X;
@ -260,10 +279,13 @@ public class StatSummarizer implements Runnable {
String title = _("Bandwidth usage"); String title = _("Bandwidth usage");
if (!hideTitle) if (!hideTitle)
def.setTitle(title); def.setTitle(title);
long started = _context.router().getWhenStarted();
if (started > start && started < end)
def.vrule(started / 1000, Color.BLACK, null, 4.0f); // no room for legend
String sendName = SummaryListener.createName(_context, "bw.sendRate.60000"); String sendName = SummaryListener.createName(_context, "bw.sendRate.60000");
String recvName = SummaryListener.createName(_context, "bw.recvRate.60000"); String recvName = SummaryListener.createName(_context, "bw.recvRate.60000");
def.datasource(sendName, sendName, sendName, "AVERAGE", "MEMORY"); def.datasource(sendName, txLsnr.getData().getPath(), sendName, "AVERAGE", txLsnr.getBackendName());
def.datasource(recvName, recvName, recvName, "AVERAGE", "MEMORY"); def.datasource(recvName, rxLsnr.getData().getPath(), recvName, "AVERAGE", rxLsnr.getBackendName());
def.area(sendName, Color.BLUE, _("Outbound Bytes/sec")); def.area(sendName, Color.BLUE, _("Outbound Bytes/sec"));
//def.line(sendName, Color.BLUE, "Outbound bytes/sec", 3); //def.line(sendName, Color.BLUE, "Outbound bytes/sec", 3);
def.line(recvName, Color.RED, _("Inbound Bytes/sec") + "\\r", 3); def.line(recvName, Color.RED, _("Inbound Bytes/sec") + "\\r", 3);
@ -347,8 +369,25 @@ public class StatSummarizer implements Runnable {
/** translate a string */ /** translate a string */
private String _(String s) { private String _(String s) {
// the RRD font doesn't have zh chars, at least on my system // the RRD font doesn't have zh chars, at least on my system
if ("zh".equals(Messages.getLanguage(_context))) // Works on 1.5.9
return s; //if ("zh".equals(Messages.getLanguage(_context)))
// return s;
return Messages.getString(s, _context); return Messages.getString(s, _context);
} }
/**
* Make sure any persistent RRDs are closed
* @since 0.8.6
*/
private class Shutdown implements Runnable {
public void run() {
_isRunning = false;
if (_thread != null)
_thread.interrupt();
for (SummaryListener lsnr : _listeners) {
lsnr.stopListening();
}
_listeners.clear();
}
}
} }

View File

@ -1,5 +1,6 @@
package net.i2p.router.web; package net.i2p.router.web;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -9,12 +10,15 @@ import net.i2p.stat.Rate;
import net.i2p.stat.RateStat; import net.i2p.stat.RateStat;
import net.i2p.stat.RateSummaryListener; import net.i2p.stat.RateSummaryListener;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
import org.jrobin.core.RrdBackendFactory; import org.jrobin.core.RrdBackendFactory;
import org.jrobin.core.RrdDb; import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdDef; import org.jrobin.core.RrdDef;
import org.jrobin.core.RrdException; import org.jrobin.core.RrdException;
import org.jrobin.core.RrdMemoryBackendFactory; import org.jrobin.core.RrdMemoryBackendFactory;
import org.jrobin.core.RrdNioBackendFactory;
import org.jrobin.core.Sample; import org.jrobin.core.Sample;
import org.jrobin.graph.RrdGraph; import org.jrobin.graph.RrdGraph;
import org.jrobin.graph.RrdGraphDef; import org.jrobin.graph.RrdGraphDef;
@ -27,9 +31,16 @@ import org.jrobin.graph.RrdGraphDefTemplate;
* @since 0.6.1.13 * @since 0.6.1.13
*/ */
class SummaryListener implements RateSummaryListener { class SummaryListener implements RateSummaryListener {
private static final String PROP_PERSISTENT = "routerconsole.graphPersistent";
/** note that .jrb files are NOT compatible with .rrd files */
private static final String RRD_DIR = "rrd";
private static final String RRD_PREFIX = "rrd-";
private static final String RRD_SUFFIX = ".jrb";
private final I2PAppContext _context; private final I2PAppContext _context;
private final Log _log; private final Log _log;
private final Rate _rate; private final Rate _rate;
private final boolean _isPersistent;
private String _name; private String _name;
private String _eventName; private String _eventName;
private RrdDb _db; private RrdDb _db;
@ -39,18 +50,11 @@ class SummaryListener implements RateSummaryListener {
static final int PERIODS = 1440; static final int PERIODS = 1440;
static {
try {
RrdBackendFactory.setDefaultFactory("MEMORY");
} catch (RrdException re) {
re.printStackTrace();
}
}
public SummaryListener(Rate r) { public SummaryListener(Rate r) {
_context = I2PAppContext.getGlobalContext(); _context = I2PAppContext.getGlobalContext();
_rate = r; _rate = r;
_log = _context.logManager().getLog(SummaryListener.class); _log = _context.logManager().getLog(SummaryListener.class);
_isPersistent = _context.getBooleanProperty(PROP_PERSISTENT);
} }
public void add(double totalValue, long eventCount, double totalEventTime, long period) { public void add(double totalValue, long eventCount, double totalEventTime, long period) {
@ -99,7 +103,26 @@ class SummaryListener implements RateSummaryListener {
_name = createName(_context, baseName); _name = createName(_context, baseName);
_eventName = createName(_context, baseName + ".events"); _eventName = createName(_context, baseName + ".events");
try { try {
RrdDef def = new RrdDef(_name, now()/1000, period/1000); RrdBackendFactory factory = RrdBackendFactory.getFactory(getBackendName());
String rrdDefName;
if (_isPersistent) {
// generate full path for persistent RRD files
File rrdDir = new SecureFile(_context.getRouterDir(), RRD_DIR);
File rrdFile = new File(rrdDir, RRD_PREFIX + _name + RRD_SUFFIX);
rrdDefName = rrdFile.getAbsolutePath();
if (rrdFile.exists()) {
_db = new RrdDb(rrdDefName, factory);
if (_log.shouldLog(Log.INFO))
_log.info("Existing RRD " + baseName + " (" + rrdDefName + ") consuming " + _db.getRrdBackend().getLength() + " bytes");
} else {
rrdDir.mkdir();
}
} else {
rrdDefName = _name;
}
if (_db == null) {
// not persistent or not previously existing
RrdDef def = new RrdDef(rrdDefName, now()/1000, period/1000);
// for info on the heartbeat, xff, steps, etc, see the rrdcreate man page, aka // for info on the heartbeat, xff, steps, etc, see the rrdcreate man page, aka
// http://www.jrobin.org/support/man/rrdcreate.html // http://www.jrobin.org/support/man/rrdcreate.html
long heartbeat = period*10/1000; long heartbeat = period*10/1000;
@ -109,14 +132,15 @@ class SummaryListener implements RateSummaryListener {
int steps = 1; int steps = 1;
int rows = PERIODS; int rows = PERIODS;
def.addArchive("AVERAGE", xff, steps, rows); def.addArchive("AVERAGE", xff, steps, rows);
_factory = (RrdMemoryBackendFactory)RrdBackendFactory.getDefaultFactory(); _db = new RrdDb(def, factory);
_db = new RrdDb(def, _factory); if (_isPersistent)
SecureFileOutputStream.setPerms(new File(rrdDefName));
if (_log.shouldLog(Log.INFO))
_log.info("New RRD " + baseName + " (" + rrdDefName + ") consuming " + _db.getRrdBackend().getLength() + " bytes");
}
_sample = _db.createSample(); _sample = _db.createSample();
_renderer = new SummaryRenderer(_context, this); _renderer = new SummaryRenderer(_context, this);
_rate.setSummaryListener(this); _rate.setSummaryListener(this);
// Typical usage is 23456 bytes ~= 1440 * 16
if (_log.shouldLog(Log.INFO))
_log.info("New RRD " + baseName + " consuming " + _db.getRrdBackend().getLength() + " bytes");
} catch (RrdException re) { } catch (RrdException re) {
_log.error("Error starting", re); _log.error("Error starting", re);
} catch (IOException ioe) { } catch (IOException ioe) {
@ -132,7 +156,12 @@ class SummaryListener implements RateSummaryListener {
_log.error("Error closing", ioe); _log.error("Error closing", ioe);
} }
_rate.setSummaryListener(null); _rate.setSummaryListener(null);
_factory.delete(_db.getPath()); if (!_isPersistent) {
// close() does not release resources for memory backend
try {
((RrdMemoryBackendFactory)RrdBackendFactory.getFactory(RrdMemoryBackendFactory.NAME)).delete(_db.getPath());
} catch (RrdException re) {}
}
_db = null; _db = null;
} }
@ -150,6 +179,11 @@ class SummaryListener implements RateSummaryListener {
long now() { return _context.clock().now(); } long now() { return _context.clock().now(); }
/** @since 0.8.6 */
String getBackendName() {
return _isPersistent ? RrdNioBackendFactory.NAME : RrdMemoryBackendFactory.NAME;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return ((obj instanceof SummaryListener) && ((SummaryListener)obj)._rate.equals(_rate)); return ((obj instanceof SummaryListener) && ((SummaryListener)obj)._rate.equals(_rate));

View File

@ -13,6 +13,7 @@ import javax.imageio.stream.MemoryCacheImageOutputStream;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.stat.Rate; import net.i2p.stat.Rate;
import net.i2p.stat.RateStat; import net.i2p.stat.RateStat;
import net.i2p.stat.RateSummaryListener; import net.i2p.stat.RateSummaryListener;
@ -130,7 +131,10 @@ class SummaryRenderer {
// Strings.java // Strings.java
descr = _(_listener.getRate().getRateStat().getDescription()); descr = _(_listener.getRate().getRateStat().getDescription());
} }
def.datasource(plotName, path, plotName, "AVERAGE", "MEMORY"); long started = ((RouterContext)_context).router().getWhenStarted();
if (started > start && started < end)
def.vrule(started / 1000, Color.BLACK, _("Restart"), 4.0f);
def.datasource(plotName, path, plotName, "AVERAGE", _listener.getBackendName());
def.area(plotName, Color.BLUE, descr + "\\r"); def.area(plotName, Color.BLUE, descr + "\\r");
if (!hideLegend) { if (!hideLegend) {
def.gprint(plotName, "AVERAGE", _("avg") + ": %.2f %s"); def.gprint(plotName, "AVERAGE", _("avg") + ": %.2f %s");
@ -189,8 +193,9 @@ class SummaryRenderer {
/** translate a string */ /** translate a string */
private String _(String s) { private String _(String s) {
// the RRD font doesn't have zh chars, at least on my system // the RRD font doesn't have zh chars, at least on my system
if ("zh".equals(Messages.getLanguage(_context))) // Works on 1.5.9
return s; //if ("zh".equals(Messages.getLanguage(_context)))
// return s;
return Messages.getString(s, _context); return Messages.getString(s, _context);
} }
@ -199,8 +204,9 @@ class SummaryRenderer {
*/ */
private String _(String s, String o) { private String _(String s, String o) {
// the RRD font doesn't have zh chars, at least on my system // the RRD font doesn't have zh chars, at least on my system
if ("zh".equals(Messages.getLanguage(_context))) // Works on 1.5.9
return s.replace("{0}", o); //if ("zh".equals(Messages.getLanguage(_context)))
// return s.replace("{0}", o);
return Messages.getString(s, o, _context); return Messages.getString(s, o, _context);
} }
} }