forked from I2P_Developers/i2p.i2p
- Persistent RRD
- Restart line in graphs - Restore zh fonts in graphs
This commit is contained in:
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user