2006-03-16 21:45:17 +00:00
|
|
|
package net.i2p.router.web;
|
|
|
|
|
2008-07-16 13:42:54 +00:00
|
|
|
import java.awt.Color;
|
2011-03-17 02:07:08 +00:00
|
|
|
import java.awt.Graphics;
|
|
|
|
import java.awt.image.BufferedImage;
|
2008-07-16 13:42:54 +00:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.StringTokenizer;
|
2010-11-24 15:01:14 +00:00
|
|
|
import java.util.concurrent.Semaphore;
|
2006-03-16 21:45:17 +00:00
|
|
|
|
2011-03-17 02:07:08 +00:00
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
import javax.imageio.stream.ImageOutputStream;
|
|
|
|
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
|
|
|
|
2008-07-16 13:42:54 +00:00
|
|
|
import net.i2p.router.RouterContext;
|
|
|
|
import net.i2p.stat.Rate;
|
|
|
|
import net.i2p.stat.RateStat;
|
2006-04-10 05:37:28 +00:00
|
|
|
import net.i2p.util.Log;
|
|
|
|
|
2008-07-16 13:42:54 +00:00
|
|
|
import org.jrobin.core.RrdException;
|
2006-04-10 05:37:28 +00:00
|
|
|
import org.jrobin.graph.RrdGraph;
|
|
|
|
import org.jrobin.graph.RrdGraphDef;
|
2006-03-16 21:45:17 +00:00
|
|
|
|
|
|
|
/**
|
2011-03-17 17:16:28 +00:00
|
|
|
* A thread started by RouterConsoleRunner that
|
|
|
|
* checks the configuration for stats to be tracked via jrobin,
|
|
|
|
* and adds or deletes RRDs as necessary.
|
2006-03-16 21:45:17 +00:00
|
|
|
*
|
2011-03-17 17:16:28 +00:00
|
|
|
* This also contains methods to generate xml or png image output.
|
|
|
|
* The actual png rendering code is here for the special dual-rate graph;
|
|
|
|
* the rendering for standard graphs is in SummaryRenderer.
|
|
|
|
*
|
|
|
|
* To control memory, the number of simultaneous renderings is limited.
|
|
|
|
*
|
|
|
|
* @since 0.6.1.13
|
2006-03-16 21:45:17 +00:00
|
|
|
*/
|
|
|
|
public class StatSummarizer implements Runnable {
|
2010-11-24 15:01:14 +00:00
|
|
|
private final RouterContext _context;
|
|
|
|
private final Log _log;
|
2006-03-16 21:45:17 +00:00
|
|
|
/** list of SummaryListener instances */
|
2010-11-24 15:01:14 +00:00
|
|
|
private final List<SummaryListener> _listeners;
|
2006-03-16 21:45:17 +00:00
|
|
|
private static StatSummarizer _instance;
|
2010-11-24 15:01:14 +00:00
|
|
|
private static final int MAX_CONCURRENT_PNG = 3;
|
|
|
|
private final Semaphore _sem;
|
2006-03-16 21:45:17 +00:00
|
|
|
|
|
|
|
public StatSummarizer() {
|
|
|
|
_context = (RouterContext)RouterContext.listContexts().get(0); // fuck it, only summarize one per jvm
|
2006-04-10 05:37:28 +00:00
|
|
|
_log = _context.logManager().getLog(getClass());
|
2006-03-16 21:45:17 +00:00
|
|
|
_listeners = new ArrayList(16);
|
|
|
|
_instance = this;
|
2010-11-24 15:01:14 +00:00
|
|
|
_sem = new Semaphore(MAX_CONCURRENT_PNG, true);
|
2006-03-16 21:45:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static StatSummarizer instance() { return _instance; }
|
|
|
|
|
|
|
|
public void run() {
|
|
|
|
String specs = "";
|
|
|
|
while (_context.router().isAlive()) {
|
|
|
|
specs = adjustDatabases(specs);
|
|
|
|
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-19 00:23:23 +00:00
|
|
|
/** list of SummaryListener instances */
|
2010-11-24 15:01:14 +00:00
|
|
|
List<SummaryListener> getListeners() { return _listeners; }
|
2006-03-19 00:23:23 +00:00
|
|
|
|
2006-03-16 21:45:17 +00:00
|
|
|
private static final String DEFAULT_DATABASES = "bw.sendRate.60000" +
|
|
|
|
",bw.recvRate.60000" +
|
2007-07-16 20:47:57 +00:00
|
|
|
// ",tunnel.testSuccessTime.60000" +
|
|
|
|
// ",udp.outboundActiveCount.60000" +
|
|
|
|
// ",udp.receivePacketSize.60000" +
|
|
|
|
// ",udp.receivePacketSkew.60000" +
|
|
|
|
// ",udp.sendConfirmTime.60000" +
|
|
|
|
// ",udp.sendPacketSize.60000" +
|
2008-12-20 01:00:53 +00:00
|
|
|
",router.memoryUsed.60000" +
|
2007-07-16 20:47:57 +00:00
|
|
|
",router.activePeers.60000";
|
|
|
|
// ",router.activeSendPeers.60000" +
|
|
|
|
// ",tunnel.acceptLoad.60000" +
|
|
|
|
// ",tunnel.dropLoadProactive.60000" +
|
|
|
|
// ",tunnel.buildExploratorySuccess.60000" +
|
|
|
|
// ",tunnel.buildExploratoryReject.60000" +
|
|
|
|
// ",tunnel.buildExploratoryExpire.60000" +
|
|
|
|
// ",client.sendAckTime.60000" +
|
|
|
|
// ",client.dispatchNoACK.60000" +
|
|
|
|
// ",ntcp.sendTime.60000" +
|
|
|
|
// ",ntcp.transmitTime.60000" +
|
|
|
|
// ",ntcp.sendBacklogTime.60000" +
|
|
|
|
// ",ntcp.receiveTime.60000" +
|
|
|
|
// ",transport.sendMessageFailureLifetime.60000" +
|
|
|
|
// ",transport.sendProcessingTime.60000";
|
2006-03-16 21:45:17 +00:00
|
|
|
|
|
|
|
private String adjustDatabases(String oldSpecs) {
|
|
|
|
String spec = _context.getProperty("stat.summaries", DEFAULT_DATABASES);
|
|
|
|
if ( ( (spec == null) && (oldSpecs == null) ) ||
|
|
|
|
( (spec != null) && (oldSpecs != null) && (oldSpecs.equals(spec))) )
|
|
|
|
return oldSpecs;
|
|
|
|
|
2010-11-24 15:01:14 +00:00
|
|
|
List<Rate> old = parseSpecs(oldSpecs);
|
|
|
|
List<Rate> newSpecs = parseSpecs(spec);
|
2006-03-16 21:45:17 +00:00
|
|
|
|
|
|
|
// remove old ones
|
2010-11-24 15:01:14 +00:00
|
|
|
for (Rate r : old) {
|
2006-03-16 21:45:17 +00:00
|
|
|
if (!newSpecs.contains(r))
|
|
|
|
removeDb(r);
|
|
|
|
}
|
|
|
|
// add new ones
|
2009-07-01 16:00:43 +00:00
|
|
|
StringBuilder buf = new StringBuilder();
|
2010-11-24 15:01:14 +00:00
|
|
|
boolean comma = false;
|
|
|
|
for (Rate r : newSpecs) {
|
2006-03-16 21:45:17 +00:00
|
|
|
if (!old.contains(r))
|
|
|
|
addDb(r);
|
2010-11-24 15:01:14 +00:00
|
|
|
if (comma)
|
2006-03-16 21:45:17 +00:00
|
|
|
buf.append(',');
|
2010-11-24 15:01:14 +00:00
|
|
|
else
|
|
|
|
comma = true;
|
|
|
|
buf.append(r.getRateStat().getName()).append(".").append(r.getPeriod());
|
2006-03-16 21:45:17 +00:00
|
|
|
}
|
|
|
|
return buf.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void removeDb(Rate r) {
|
|
|
|
for (int i = 0; i < _listeners.size(); i++) {
|
2010-11-24 15:01:14 +00:00
|
|
|
SummaryListener lsnr = _listeners.get(i);
|
2006-03-16 21:45:17 +00:00
|
|
|
if (lsnr.getRate().equals(r)) {
|
|
|
|
_listeners.remove(i);
|
|
|
|
lsnr.stopListening();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private void addDb(Rate r) {
|
|
|
|
SummaryListener lsnr = new SummaryListener(r);
|
|
|
|
_listeners.add(lsnr);
|
|
|
|
lsnr.startListening();
|
|
|
|
//System.out.println("Start listening for " + r.getRateStat().getName() + ": " + r.getPeriod());
|
|
|
|
}
|
2010-11-24 15:01:14 +00:00
|
|
|
|
2006-03-17 23:46:00 +00:00
|
|
|
public boolean renderPng(Rate rate, OutputStream out) throws IOException {
|
2011-03-17 02:07:08 +00:00
|
|
|
return renderPng(rate, out, GraphHelper.DEFAULT_X, GraphHelper.DEFAULT_Y,
|
|
|
|
false, false, false, false, -1, true);
|
2006-03-17 23:46:00 +00:00
|
|
|
}
|
2010-11-24 15:01:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This does the single data graphs.
|
|
|
|
* For the two-data bandwidth graph see renderRatePng().
|
|
|
|
* Synchronized to conserve memory.
|
|
|
|
* @return success
|
|
|
|
*/
|
|
|
|
public boolean renderPng(Rate rate, OutputStream out, int width, int height, boolean hideLegend,
|
|
|
|
boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount,
|
|
|
|
boolean showCredit) throws IOException {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
_sem.acquire();
|
|
|
|
} catch (InterruptedException ie) {}
|
|
|
|
return locked_renderPng(rate, out, width, height, hideLegend, hideGrid, hideTitle, showEvents,
|
|
|
|
periodCount, showCredit);
|
|
|
|
} finally {
|
2011-03-17 17:16:28 +00:00
|
|
|
_sem.release();
|
2010-11-24 15:01:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean locked_renderPng(Rate rate, OutputStream out, int width, int height, boolean hideLegend,
|
|
|
|
boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount,
|
|
|
|
boolean showCredit) throws IOException {
|
2010-01-24 02:11:55 +00:00
|
|
|
if (width > GraphHelper.MAX_X)
|
|
|
|
width = GraphHelper.MAX_X;
|
2011-03-17 02:07:08 +00:00
|
|
|
else if (width <= 0)
|
|
|
|
width = GraphHelper.DEFAULT_X;
|
2010-01-24 02:11:55 +00:00
|
|
|
if (height > GraphHelper.MAX_Y)
|
|
|
|
height = GraphHelper.MAX_Y;
|
2011-03-17 02:07:08 +00:00
|
|
|
else if (height <= 0)
|
|
|
|
height = GraphHelper.DEFAULT_Y;
|
2006-03-16 21:45:17 +00:00
|
|
|
for (int i = 0; i < _listeners.size(); i++) {
|
2010-11-24 15:01:14 +00:00
|
|
|
SummaryListener lsnr = _listeners.get(i);
|
2006-03-16 21:45:17 +00:00
|
|
|
if (lsnr.getRate().equals(rate)) {
|
2006-03-20 05:31:09 +00:00
|
|
|
lsnr.renderPng(out, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, showCredit);
|
2006-03-16 21:45:17 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2010-11-24 15:01:14 +00:00
|
|
|
|
2011-03-17 02:07:08 +00:00
|
|
|
/** @deprecated unused */
|
2006-03-18 23:09:35 +00:00
|
|
|
public boolean renderPng(OutputStream out, String templateFilename) throws IOException {
|
|
|
|
SummaryRenderer.render(_context, out, templateFilename);
|
|
|
|
return true;
|
|
|
|
}
|
2010-11-24 15:01:14 +00:00
|
|
|
|
2006-03-16 21:45:17 +00:00
|
|
|
public boolean getXML(Rate rate, OutputStream out) throws IOException {
|
2011-03-17 17:16:28 +00:00
|
|
|
try {
|
|
|
|
try {
|
|
|
|
_sem.acquire();
|
|
|
|
} catch (InterruptedException ie) {}
|
|
|
|
return locked_getXML(rate, out);
|
|
|
|
} finally {
|
|
|
|
_sem.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean locked_getXML(Rate rate, OutputStream out) throws IOException {
|
2006-03-16 21:45:17 +00:00
|
|
|
for (int i = 0; i < _listeners.size(); i++) {
|
2010-11-24 15:01:14 +00:00
|
|
|
SummaryListener lsnr = _listeners.get(i);
|
2006-03-16 21:45:17 +00:00
|
|
|
if (lsnr.getRate().equals(rate)) {
|
|
|
|
lsnr.getData().exportXml(out);
|
|
|
|
out.write(("<!-- Rate: " + lsnr.getRate().getRateStat().getName() + " for period " + lsnr.getRate().getPeriod() + " -->\n").getBytes());
|
|
|
|
out.write(("<!-- Average data soure name: " + lsnr.getName() + " event count data source name: " + lsnr.getEventName() + " -->\n").getBytes());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-05-15 14:17:17 +00:00
|
|
|
/**
|
|
|
|
* This does the two-data bandwidth graph only.
|
|
|
|
* For all other graphs see SummaryRenderer
|
2010-11-24 15:01:14 +00:00
|
|
|
* Synchronized to conserve memory.
|
|
|
|
* @return success
|
2010-05-15 14:17:17 +00:00
|
|
|
*/
|
2010-11-24 15:01:14 +00:00
|
|
|
public boolean renderRatePng(OutputStream out, int width, int height, boolean hideLegend,
|
|
|
|
boolean hideGrid, boolean hideTitle, boolean showEvents,
|
|
|
|
int periodCount, boolean showCredit) throws IOException {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
_sem.acquire();
|
|
|
|
} catch (InterruptedException ie) {}
|
|
|
|
return locked_renderRatePng(out, width, height, hideLegend, hideGrid, hideTitle, showEvents,
|
|
|
|
periodCount, showCredit);
|
|
|
|
} finally {
|
2011-03-17 17:16:28 +00:00
|
|
|
_sem.release();
|
2010-11-24 15:01:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean locked_renderRatePng(OutputStream out, int width, int height, boolean hideLegend,
|
|
|
|
boolean hideGrid, boolean hideTitle, boolean showEvents,
|
|
|
|
int periodCount, boolean showCredit) throws IOException {
|
2007-07-14 18:44:11 +00:00
|
|
|
long end = _context.clock().now() - 60*1000;
|
2010-01-24 02:11:55 +00:00
|
|
|
if (width > GraphHelper.MAX_X)
|
|
|
|
width = GraphHelper.MAX_X;
|
2011-03-17 02:07:08 +00:00
|
|
|
else if (width <= 0)
|
|
|
|
width = GraphHelper.DEFAULT_X;
|
2010-01-24 02:11:55 +00:00
|
|
|
if (height > GraphHelper.MAX_Y)
|
|
|
|
height = GraphHelper.MAX_Y;
|
2011-03-17 02:07:08 +00:00
|
|
|
else if (height <= 0)
|
|
|
|
height = GraphHelper.DEFAULT_Y;
|
2006-04-10 05:37:28 +00:00
|
|
|
if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
|
|
|
|
if (periodCount > SummaryListener.PERIODS)
|
|
|
|
periodCount = SummaryListener.PERIODS;
|
|
|
|
long period = 60*1000;
|
|
|
|
long start = end - period*periodCount;
|
2007-07-14 18:44:11 +00:00
|
|
|
//long begin = System.currentTimeMillis();
|
2006-04-10 05:37:28 +00:00
|
|
|
try {
|
|
|
|
RrdGraphDef def = new RrdGraphDef();
|
2011-03-17 02:07:08 +00:00
|
|
|
def.setTimeSpan(start/1000, end/1000);
|
|
|
|
def.setMinValue(0d);
|
|
|
|
def.setBase(1024);
|
2010-06-02 18:16:43 +00:00
|
|
|
// Note to translators: all runtime zh translation disabled in this file, no font available in RRD
|
2010-05-15 14:17:17 +00:00
|
|
|
String title = _("Bandwidth usage");
|
2006-04-10 05:37:28 +00:00
|
|
|
if (!hideTitle)
|
|
|
|
def.setTitle(title);
|
|
|
|
String sendName = SummaryListener.createName(_context, "bw.sendRate.60000");
|
|
|
|
String recvName = SummaryListener.createName(_context, "bw.recvRate.60000");
|
|
|
|
def.datasource(sendName, sendName, sendName, "AVERAGE", "MEMORY");
|
|
|
|
def.datasource(recvName, recvName, recvName, "AVERAGE", "MEMORY");
|
2011-03-17 02:07:08 +00:00
|
|
|
def.area(sendName, Color.BLUE, _("Outbound Bytes/sec"));
|
2007-07-14 18:44:11 +00:00
|
|
|
//def.line(sendName, Color.BLUE, "Outbound bytes/sec", 3);
|
2011-03-17 02:07:08 +00:00
|
|
|
def.line(recvName, Color.RED, _("Inbound Bytes/sec") + "\\r", 3);
|
2007-07-14 18:44:11 +00:00
|
|
|
//def.area(recvName, Color.RED, "Inbound bytes/sec@r");
|
2006-04-10 05:37:28 +00:00
|
|
|
if (!hideLegend) {
|
2011-03-17 02:07:08 +00:00
|
|
|
def.gprint(sendName, "AVERAGE", _("Out average") + ": %.2f %s" + _("Bps"));
|
|
|
|
def.gprint(sendName, "MAX", ' ' + _("max") + ": %.2f %S" + _("Bps") + "\\r");
|
|
|
|
def.gprint(recvName, "AVERAGE", _("In average") + ": %.2f %S" + _("Bps"));
|
|
|
|
def.gprint(recvName, "MAX", ' ' + _("max") + ": %.2f %S" + _("Bps") + "\\r");
|
2006-04-10 05:37:28 +00:00
|
|
|
}
|
|
|
|
if (!showCredit)
|
|
|
|
def.setShowSignature(false);
|
|
|
|
if (hideLegend)
|
2011-03-17 02:07:08 +00:00
|
|
|
def.setNoLegend(true);
|
2006-04-10 05:37:28 +00:00
|
|
|
if (hideGrid) {
|
2011-03-17 02:07:08 +00:00
|
|
|
def.setDrawXGrid(false);
|
|
|
|
def.setDrawYGrid(false);
|
2006-04-10 05:37:28 +00:00
|
|
|
}
|
|
|
|
//System.out.println("rendering: path=" + path + " dsNames[0]=" + dsNames[0] + " dsNames[1]=" + dsNames[1] + " lsnr.getName=" + _listener.getName());
|
2009-07-30 17:44:08 +00:00
|
|
|
def.setAntiAliasing(false);
|
2006-04-10 05:37:28 +00:00
|
|
|
//System.out.println("Rendering: \n" + def.exportXmlTemplate());
|
|
|
|
//System.out.println("*****************\nData: \n" + _listener.getData().dump());
|
2011-03-17 02:07:08 +00:00
|
|
|
def.setWidth(width);
|
|
|
|
def.setHeight(height);
|
|
|
|
|
2006-04-10 05:37:28 +00:00
|
|
|
RrdGraph graph = new RrdGraph(def);
|
|
|
|
//System.out.println("Graph created");
|
2011-03-17 02:07:08 +00:00
|
|
|
int totalWidth = graph.getRrdGraphInfo().getWidth();
|
|
|
|
int totalHeight = graph.getRrdGraphInfo().getHeight();
|
|
|
|
BufferedImage img = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_USHORT_565_RGB);
|
|
|
|
Graphics gfx = img.getGraphics();
|
|
|
|
graph.render(gfx);
|
|
|
|
ImageOutputStream ios = new MemoryCacheImageOutputStream(out);
|
|
|
|
ImageIO.write(img, "png", ios);
|
|
|
|
|
2006-04-10 05:37:28 +00:00
|
|
|
//File t = File.createTempFile("jrobinData", ".xml");
|
|
|
|
//_listener.getData().dumpXml(new FileOutputStream(t));
|
|
|
|
//System.out.println("plotted: " + (data != null ? data.length : 0) + " bytes in " + timeToPlot
|
|
|
|
// ); // + ", data written to " + t.getAbsolutePath());
|
|
|
|
return true;
|
|
|
|
} catch (RrdException re) {
|
|
|
|
_log.error("Error rendering", re);
|
|
|
|
throw new IOException("Error plotting: " + re.getMessage());
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
_log.error("Error rendering", ioe);
|
|
|
|
throw ioe;
|
2007-07-14 18:44:11 +00:00
|
|
|
} catch (OutOfMemoryError oom) {
|
|
|
|
_log.error("Error rendering", oom);
|
|
|
|
throw new IOException("Error plotting: " + oom.getMessage());
|
2006-04-10 05:37:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-16 21:45:17 +00:00
|
|
|
/**
|
|
|
|
* @param specs statName.period,statName.period,statName.period
|
|
|
|
* @return list of Rate objects
|
|
|
|
*/
|
2010-11-24 15:01:14 +00:00
|
|
|
private List<Rate> parseSpecs(String specs) {
|
2006-03-16 21:45:17 +00:00
|
|
|
StringTokenizer tok = new StringTokenizer(specs, ",");
|
2010-11-24 15:01:14 +00:00
|
|
|
List<Rate> rv = new ArrayList();
|
2006-03-16 21:45:17 +00:00
|
|
|
while (tok.hasMoreTokens()) {
|
|
|
|
String spec = tok.nextToken();
|
|
|
|
int split = spec.lastIndexOf('.');
|
|
|
|
if ( (split <= 0) || (split + 1 >= spec.length()) )
|
|
|
|
continue;
|
|
|
|
String name = spec.substring(0, split);
|
|
|
|
String per = spec.substring(split+1);
|
|
|
|
long period = -1;
|
|
|
|
try {
|
|
|
|
period = Long.parseLong(per);
|
|
|
|
RateStat rs = _context.statManager().getRate(name);
|
|
|
|
if (rs != null) {
|
|
|
|
Rate r = rs.getRate(period);
|
|
|
|
if (r != null)
|
|
|
|
rv.add(r);
|
|
|
|
}
|
|
|
|
} catch (NumberFormatException nfe) {}
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
2010-05-15 14:17:17 +00:00
|
|
|
|
|
|
|
/** translate a string */
|
|
|
|
private String _(String s) {
|
|
|
|
// the RRD font doesn't have zh chars, at least on my system
|
|
|
|
if ("zh".equals(Messages.getLanguage(_context)))
|
|
|
|
return s;
|
|
|
|
return Messages.getString(s, _context);
|
|
|
|
}
|
2006-03-16 21:45:17 +00:00
|
|
|
}
|