Newer
Older
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.Semaphore;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppState;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;

zzz
committed
import static net.i2p.router.web.GraphConstants.*;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.util.SystemVersion;
* A thread started by RouterConsoleRunner that
* checks the configuration for stats to be tracked via jrobin,
* and adds or deletes RRDs as necessary.
* This also contains methods to generate xml or png image output.
* The rendering for graphs is in SummaryRenderer.
*
* To control memory, the number of simultaneous renderings is limited.
*
* @since 0.6.1.13
public class StatSummarizer implements Runnable, ClientApp {
private final RouterContext _context;
private final Log _log;
private final List<SummaryListener> _listeners;
private static final int MAX_CONCURRENT_PNG = SystemVersion.isARM() ? 2 : 3;
private final Semaphore _sem;
private volatile boolean _isRunning;
private volatile Thread _thread;
private static final String NAME = "StatSummarizer";
public StatSummarizer(RouterContext ctx) {
_context = ctx;
_listeners = new CopyOnWriteArrayList<SummaryListener>();
_sem = new Semaphore(MAX_CONCURRENT_PNG, true);
/**
* @return null if disabled
*/
public static StatSummarizer instance() {
return instance(I2PAppContext.getGlobalContext());
}
/**
* @return null if disabled
*/
public static StatSummarizer instance(I2PAppContext ctx) {
ClientApp app = ctx.clientAppManager().getRegisteredApp(NAME);
return (app != null) ? (StatSummarizer) app : null;
}
if (SystemVersion.isApache() || // Harmony
SystemVersion.isGNU()) { // JamVM or gij
_log.logAlways(Log.WARN, "Graphing not supported with this JVM: " +
System.getProperty("java.vendor") + ' ' +
System.getProperty("java.version") + " (" +
System.getProperty("java.runtime.name") + ' ' +
System.getProperty("java.runtime.version") + ')');
return;
}
_isRunning = true;
boolean isPersistent = _context.getBooleanPropertyDefaultTrue(SummaryListener.PROP_PERSISTENT);
if (!isPersistent)
deleteOldRRDs();
_context.clientAppManager().register(this);
try {
while (_isRunning && _context.router().isAlive()) {
specs = adjustDatabases(specs);
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
}
} finally {
_isRunning = false;
_context.clientAppManager().unregister(this);
public static boolean isDisabled(I2PAppContext ctx) {
return ctx.clientAppManager().getRegisteredApp(NAME) == null;
/**
* Disable graph generation until restart
* See SummaryRenderer.render()
* @since 0.9.6
*/
static void setDisabled(I2PAppContext ctx) {
StatSummarizer ss = instance(ctx);
if (ss != null)
ss.setDisabled();
}
/**
* Disable graph generation until restart
* See SummaryRenderer.render()
* @since 0.9.38
*/
synchronized void setDisabled() {
if (_isRunning) {
_isRunning = false;
Thread t = _thread;
if (t != null)
t.interrupt();
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/////// ClientApp methods
/**
* Does nothing, we aren't tracked
* @since 0.9.38
*/
public void startup() {}
/**
* Does nothing, we aren't tracked
* @since 0.9.38
*/
public void shutdown(String[] args) {}
/** @since 0.9.38 */
public ClientAppState getState() {
return ClientAppState.RUNNING;
}
/** @since 0.9.38 */
public String getName() {
return NAME;
}
/** @since 0.9.38 */
public String getDisplayName() {
return "Console stats summarizer";
}
/////// End ClientApp methods

zzz
committed
/**
* List of SummaryListener instances
* @since public since 0.9.33, was package private
*/
public List<SummaryListener> getListeners() { return _listeners; }

zzz
committed
/** @since public since 0.9.33, was package private */
public static final String DEFAULT_DATABASES = "bw.sendRate.60000" +
// ",tunnel.testSuccessTime.60000" +
// ",udp.outboundActiveCount.60000" +
// ",udp.receivePacketSize.60000" +
// ",udp.receivePacketSkew.60000" +
// ",udp.sendConfirmTime.60000" +
// ",udp.sendPacketSize.60000" +
",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";
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;
Set<Rate> old = parseSpecs(oldSpecs);
Set<Rate> newSpecs = parseSpecs(spec);
if (!newSpecs.contains(r))
removeDb(r);
}
// add new ones
StringBuilder buf = new StringBuilder();
boolean comma = false;
for (Rate r : newSpecs) {
else
comma = true;
buf.append(r.getRateStat().getName()).append(".").append(r.getPeriod());
}
return buf.toString();
}
private void removeDb(Rate r) {
// no iter.remove() in COWAL
_listeners.remove(lsnr);
lsnr.stopListening();
return;
}
}
}
private void addDb(Rate r) {
SummaryListener lsnr = new SummaryListener(r);
boolean success = lsnr.startListening();
if (success)
_listeners.add(lsnr);
else
_log.error("Failed to add RRD for rate " + r.getRateStat().getName() + '.' + r.getPeriod());
//System.out.println("Start listening for " + r.getRateStat().getName() + ": " + r.getPeriod());
}
public boolean renderPng(Rate rate, OutputStream out) throws IOException {

zzz
committed
return renderPng(rate, out, DEFAULT_X, DEFAULT_Y,
/**
* 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,
try {
try {
_sem.acquire();
} catch (InterruptedException ie) {}
try {
return locked_renderPng(rate, out, width, height, hideLegend, hideGrid, hideTitle, showEvents,
} catch (NoClassDefFoundError ncdfe) {
// java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
// at java.lang.Class.forName0(Native Method)
// at java.lang.Class.forName(Class.java:270)
// at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
setDisabled();
String s = "Error rendering - disabling graph generation. Install fonts-dejavu font package?";
_log.logAlways(Log.WARN, s);
IOException ioe = new IOException(s);
ioe.initCause(ncdfe);
throw ioe;
}
private boolean locked_renderPng(Rate rate, OutputStream out, int width, int height, boolean hideLegend,
boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount,

zzz
committed
if (width > MAX_X)
width = MAX_X;

zzz
committed
width = DEFAULT_X;
if (height > MAX_Y)
height = MAX_Y;

zzz
committed
height = DEFAULT_Y;
lsnr.renderPng(out, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, end, showCredit);
public boolean renderPng(OutputStream out, String templateFilename) throws IOException {
SummaryRenderer.render(_context, out, templateFilename);
return true;
}
public boolean getXML(Rate rate, OutputStream out) throws IOException {
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 {
if (lsnr.getRate().equals(rate)) {
lsnr.getData().exportXml(out);
out.write(DataHelper.getUTF8("<!-- Rate: " + lsnr.getRate().getRateStat().getName() + " for period " + lsnr.getRate().getPeriod() + " -->\n"));
out.write(DataHelper.getUTF8("<!-- Average data source name: " + lsnr.getName() + " event count data source name: " + lsnr.getEventName() + " -->\n"));
* For all other graphs see renderPng() above.
* Synchronized to conserve memory.
*
* @param end number of periods before now
public boolean renderRatePng(OutputStream out, int width, int height, boolean hideLegend,
boolean hideGrid, boolean hideTitle, boolean showEvents,
int periodCount, int end, boolean showCredit) throws IOException {
try {
try {
_sem.acquire();
} catch (InterruptedException ie) {}
try {
return locked_renderRatePng(out, width, height, hideLegend, hideGrid, hideTitle, showEvents,
periodCount, end, showCredit);
} catch (NoClassDefFoundError ncdfe) {
// java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
// at java.lang.Class.forName0(Native Method)
// at java.lang.Class.forName(Class.java:270)
// at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
setDisabled();
String s = "Error rendering - disabling graph generation. Install fonts-dejavu font package?";
_log.logAlways(Log.WARN, s);
IOException ioe = new IOException(s);
ioe.initCause(ncdfe);
throw ioe;
}
}
}
private boolean locked_renderRatePng(OutputStream out, int width, int height, boolean hideLegend,
boolean hideGrid, boolean hideTitle, boolean showEvents,
int periodCount, int end, 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 : 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");

zzz
committed
if (width > MAX_X)
width = MAX_X;

zzz
committed
width = DEFAULT_X;
if (height > MAX_Y)
height = MAX_Y;

zzz
committed
height = DEFAULT_Y;
txLsnr.renderPng(out, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount,
end, showCredit, rxLsnr, _t("Bandwidth usage"));
/**
* @param specs statName.period,statName.period,statName.period
* @return list of Rate objects

zzz
committed
* @since public since 0.9.33, was package private

zzz
committed
public Set<Rate> parseSpecs(String specs) {
Set<Rate> rv = new HashSet<Rate>();
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;
}
/**
* Delete the old rrd dir if we are no longer persistent
*/
private void deleteOldRRDs() {
File rrdDir = new File(_context.getRouterDir(), SummaryListener.RRD_DIR);
FileUtil.rmdir(rrdDir, false);
}
private static final boolean IS_WIN = SystemVersion.isWindows();
private String _t(String s) {
// Works on 1.5.9 except on windows
if (IS_WIN && "zh".equals(Messages.getLanguage(_context)))
return s;
*/
private class Shutdown implements Runnable {
public void run() {
setDisabled();