forked from I2P_Developers/i2p.i2p
Console: Remove static StatSummarizer ref, hang off ClientAppManager
This commit is contained in:
@ -833,7 +833,7 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread t = new I2PAppThread(new StatSummarizer(), "StatSummarizer", true);
|
Thread t = new I2PAppThread(new StatSummarizer(_context), "StatSummarizer", true);
|
||||||
t.setPriority(Thread.NORM_PRIORITY - 1);
|
t.setPriority(Thread.NORM_PRIORITY - 1);
|
||||||
t.start();
|
t.start();
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@ import java.util.StringTokenizer;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.Semaphore;
|
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.data.DataHelper;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import static net.i2p.router.web.GraphConstants.*;
|
import static net.i2p.router.web.GraphConstants.*;
|
||||||
@ -32,29 +35,40 @@ import net.i2p.util.SystemVersion;
|
|||||||
*
|
*
|
||||||
* @since 0.6.1.13
|
* @since 0.6.1.13
|
||||||
*/
|
*/
|
||||||
public class StatSummarizer implements Runnable {
|
public class StatSummarizer implements Runnable, ClientApp {
|
||||||
private final RouterContext _context;
|
private final RouterContext _context;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
/** list of SummaryListener instances */
|
/** list of SummaryListener instances */
|
||||||
private final List<SummaryListener> _listeners;
|
private final List<SummaryListener> _listeners;
|
||||||
// TODO remove static instance
|
|
||||||
private static StatSummarizer _instance;
|
|
||||||
private static final int MAX_CONCURRENT_PNG = SystemVersion.isARM() ? 2 : 3;
|
private static final int MAX_CONCURRENT_PNG = SystemVersion.isARM() ? 2 : 3;
|
||||||
private final Semaphore _sem;
|
private final Semaphore _sem;
|
||||||
private volatile boolean _isRunning = true;
|
private volatile boolean _isRunning;
|
||||||
private boolean _isDisabled;
|
private volatile Thread _thread;
|
||||||
private Thread _thread;
|
private static final String NAME = "StatSummarizer";
|
||||||
|
|
||||||
public StatSummarizer() {
|
public StatSummarizer(RouterContext ctx) {
|
||||||
_context = RouterContext.listContexts().get(0); // only summarize one per jvm
|
_context = ctx;
|
||||||
_log = _context.logManager().getLog(getClass());
|
_log = _context.logManager().getLog(getClass());
|
||||||
_listeners = new CopyOnWriteArrayList<SummaryListener>();
|
_listeners = new CopyOnWriteArrayList<SummaryListener>();
|
||||||
_instance = this;
|
|
||||||
_sem = new Semaphore(MAX_CONCURRENT_PNG, true);
|
_sem = new Semaphore(MAX_CONCURRENT_PNG, true);
|
||||||
_context.addShutdownTask(new Shutdown());
|
_context.addShutdownTask(new Shutdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StatSummarizer instance() { return _instance; }
|
/**
|
||||||
|
* @return null if disabled
|
||||||
|
*/
|
||||||
|
public static StatSummarizer instance() {
|
||||||
|
return instance(I2PAppContext.getGlobalContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null if disabled
|
||||||
|
* @since 0.0.38
|
||||||
|
*/
|
||||||
|
public static StatSummarizer instance(I2PAppContext ctx) {
|
||||||
|
ClientApp app = ctx.clientAppManager().getRegisteredApp(NAME);
|
||||||
|
return (app != null) ? (StatSummarizer) app : null;
|
||||||
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
// JRobin 1.5.9 crashes these JVMs
|
// JRobin 1.5.9 crashes these JVMs
|
||||||
@ -65,24 +79,29 @@ public class StatSummarizer implements Runnable {
|
|||||||
System.getProperty("java.version") + " (" +
|
System.getProperty("java.version") + " (" +
|
||||||
System.getProperty("java.runtime.name") + ' ' +
|
System.getProperty("java.runtime.name") + ' ' +
|
||||||
System.getProperty("java.runtime.version") + ')');
|
System.getProperty("java.runtime.version") + ')');
|
||||||
_isDisabled = true;
|
|
||||||
_isRunning = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_isRunning = true;
|
||||||
boolean isPersistent = _context.getBooleanPropertyDefaultTrue(SummaryListener.PROP_PERSISTENT);
|
boolean isPersistent = _context.getBooleanPropertyDefaultTrue(SummaryListener.PROP_PERSISTENT);
|
||||||
if (!isPersistent)
|
if (!isPersistent)
|
||||||
deleteOldRRDs();
|
deleteOldRRDs();
|
||||||
_thread = Thread.currentThread();
|
_thread = Thread.currentThread();
|
||||||
|
_context.clientAppManager().register(this);
|
||||||
String specs = "";
|
String specs = "";
|
||||||
while (_isRunning && _context.router().isAlive()) {
|
try {
|
||||||
specs = adjustDatabases(specs);
|
while (_isRunning && _context.router().isAlive()) {
|
||||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
specs = adjustDatabases(specs);
|
||||||
|
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_isRunning = false;
|
||||||
|
_context.clientAppManager().unregister(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 0.8.7, public since 0.9.33, was package private */
|
/** @since 0.0.38 */
|
||||||
public static boolean isDisabled() {
|
public static boolean isDisabled(I2PAppContext ctx) {
|
||||||
return _instance == null || _instance._isDisabled;
|
return ctx.clientAppManager().getRegisteredApp(NAME) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,13 +109,57 @@ public class StatSummarizer implements Runnable {
|
|||||||
* See SummaryRenderer.render()
|
* See SummaryRenderer.render()
|
||||||
* @since 0.9.6
|
* @since 0.9.6
|
||||||
*/
|
*/
|
||||||
static void setDisabled() {
|
static void setDisabled(I2PAppContext ctx) {
|
||||||
if (_instance != null) {
|
StatSummarizer ss = instance(ctx);
|
||||||
_instance._isDisabled = true;
|
if (ss != null)
|
||||||
_instance._isRunning = false;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////// 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of SummaryListener instances
|
* List of SummaryListener instances
|
||||||
* @since public since 0.9.33, was package private
|
* @since public since 0.9.33, was package private
|
||||||
@ -206,8 +269,7 @@ public class StatSummarizer implements Runnable {
|
|||||||
// at java.lang.Class.forName0(Native Method)
|
// at java.lang.Class.forName0(Native Method)
|
||||||
// at java.lang.Class.forName(Class.java:270)
|
// at java.lang.Class.forName(Class.java:270)
|
||||||
// at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
|
// at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
|
||||||
_isDisabled = true;
|
setDisabled();
|
||||||
_isRunning = false;
|
|
||||||
String s = "Error rendering - disabling graph generation. Install ttf-dejavu font package?";
|
String s = "Error rendering - disabling graph generation. Install ttf-dejavu font package?";
|
||||||
_log.logAlways(Log.WARN, s);
|
_log.logAlways(Log.WARN, s);
|
||||||
IOException ioe = new IOException(s);
|
IOException ioe = new IOException(s);
|
||||||
@ -296,8 +358,7 @@ public class StatSummarizer implements Runnable {
|
|||||||
// at java.lang.Class.forName0(Native Method)
|
// at java.lang.Class.forName0(Native Method)
|
||||||
// at java.lang.Class.forName(Class.java:270)
|
// at java.lang.Class.forName(Class.java:270)
|
||||||
// at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
|
// at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
|
||||||
_isDisabled = true;
|
setDisabled();
|
||||||
_isRunning = false;
|
|
||||||
String s = "Error rendering - disabling graph generation. Install ttf-dejavu font package?";
|
String s = "Error rendering - disabling graph generation. Install ttf-dejavu font package?";
|
||||||
_log.logAlways(Log.WARN, s);
|
_log.logAlways(Log.WARN, s);
|
||||||
IOException ioe = new IOException(s);
|
IOException ioe = new IOException(s);
|
||||||
@ -316,7 +377,7 @@ public class StatSummarizer implements Runnable {
|
|||||||
// go to some trouble to see if we have the data for the combined bw graph
|
// go to some trouble to see if we have the data for the combined bw graph
|
||||||
SummaryListener txLsnr = null;
|
SummaryListener txLsnr = null;
|
||||||
SummaryListener rxLsnr = null;
|
SummaryListener rxLsnr = null;
|
||||||
for (SummaryListener lsnr : StatSummarizer.instance().getListeners()) {
|
for (SummaryListener lsnr : getListeners()) {
|
||||||
String title = lsnr.getRate().getRateStat().getName();
|
String title = lsnr.getRate().getRateStat().getName();
|
||||||
if (title.equals("bw.sendRate"))
|
if (title.equals("bw.sendRate"))
|
||||||
txLsnr = lsnr;
|
txLsnr = lsnr;
|
||||||
@ -396,9 +457,7 @@ public class StatSummarizer implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private class Shutdown implements Runnable {
|
private class Shutdown implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
_isRunning = false;
|
setDisabled();
|
||||||
if (_thread != null)
|
|
||||||
_thread.interrupt();
|
|
||||||
for (SummaryListener lsnr : _listeners) {
|
for (SummaryListener lsnr : _listeners) {
|
||||||
// FIXME could cause exceptions if rendering?
|
// FIXME could cause exceptions if rendering?
|
||||||
lsnr.stopListening();
|
lsnr.stopListening();
|
||||||
|
@ -289,7 +289,7 @@ class SummaryRenderer {
|
|||||||
graph = new RrdGraph(def);
|
graph = new RrdGraph(def);
|
||||||
} catch (NullPointerException npe) {
|
} catch (NullPointerException npe) {
|
||||||
_log.error("Error rendering", npe);
|
_log.error("Error rendering", npe);
|
||||||
StatSummarizer.setDisabled();
|
StatSummarizer.setDisabled(_context);
|
||||||
throw new IOException("Error rendering - disabling graph generation. Missing font? See http://trac.i2p2.i2p/ticket/915");
|
throw new IOException("Error rendering - disabling graph generation. Missing font? See http://trac.i2p2.i2p/ticket/915");
|
||||||
}
|
}
|
||||||
int totalWidth = graph.getRrdGraphInfo().getWidth();
|
int totalWidth = graph.getRrdGraphInfo().getWidth();
|
||||||
|
@ -147,10 +147,11 @@ public class GraphHelper extends FormHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getImages() {
|
public String getImages() {
|
||||||
if (StatSummarizer.isDisabled())
|
StatSummarizer ss = StatSummarizer.instance(_context);
|
||||||
|
if (ss == null)
|
||||||
return "";
|
return "";
|
||||||
try {
|
try {
|
||||||
List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
|
List<SummaryListener> listeners = ss.getListeners();
|
||||||
TreeSet<SummaryListener> ordered = new TreeSet<SummaryListener>(new AlphaComparator());
|
TreeSet<SummaryListener> ordered = new TreeSet<SummaryListener>(new AlphaComparator());
|
||||||
ordered.addAll(listeners);
|
ordered.addAll(listeners);
|
||||||
|
|
||||||
@ -235,9 +236,10 @@ public class GraphHelper extends FormHandler {
|
|||||||
* @since 0.9
|
* @since 0.9
|
||||||
*/
|
*/
|
||||||
public String getSingleStat() {
|
public String getSingleStat() {
|
||||||
|
StatSummarizer ss = StatSummarizer.instance(_context);
|
||||||
|
if (ss == null)
|
||||||
|
return "";
|
||||||
try {
|
try {
|
||||||
if (StatSummarizer.isDisabled())
|
|
||||||
return "";
|
|
||||||
if (_stat == null) {
|
if (_stat == null) {
|
||||||
_out.write("No stat specified");
|
_out.write("No stat specified");
|
||||||
return "";
|
return "";
|
||||||
@ -249,7 +251,7 @@ public class GraphHelper extends FormHandler {
|
|||||||
name = _stat;
|
name = _stat;
|
||||||
displayName = _t("Bandwidth usage");
|
displayName = _t("Bandwidth usage");
|
||||||
} else {
|
} else {
|
||||||
Set<Rate> rates = StatSummarizer.instance().parseSpecs(_stat);
|
Set<Rate> rates = ss.parseSpecs(_stat);
|
||||||
if (rates.size() != 1) {
|
if (rates.size() != 1) {
|
||||||
_out.write("Graphs not enabled for " + _stat);
|
_out.write("Graphs not enabled for " + _stat);
|
||||||
return "";
|
return "";
|
||||||
@ -376,7 +378,8 @@ public class GraphHelper extends FormHandler {
|
|||||||
private static final int[] times = { 15, 30, 60, 2*60, 5*60, 10*60, 30*60, 60*60, -1 };
|
private static final int[] times = { 15, 30, 60, 2*60, 5*60, 10*60, 30*60, 60*60, -1 };
|
||||||
|
|
||||||
public String getForm() {
|
public String getForm() {
|
||||||
if (StatSummarizer.isDisabled())
|
StatSummarizer ss = StatSummarizer.instance(_context);
|
||||||
|
if (ss == null)
|
||||||
return "";
|
return "";
|
||||||
// too hard to use the standard formhandler.jsi / FormHandler.java session nonces
|
// too hard to use the standard formhandler.jsi / FormHandler.java session nonces
|
||||||
// since graphs.jsp needs the refresh value in its <head>.
|
// since graphs.jsp needs the refresh value in its <head>.
|
||||||
@ -440,7 +443,7 @@ public class GraphHelper extends FormHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getAllMessages() {
|
public String getAllMessages() {
|
||||||
if (StatSummarizer.isDisabled()) {
|
if (StatSummarizer.isDisabled(_context)) {
|
||||||
addFormError("Graphing not supported with this JVM: " +
|
addFormError("Graphing not supported with this JVM: " +
|
||||||
System.getProperty("java.vendor") + ' ' +
|
System.getProperty("java.vendor") + ' ' +
|
||||||
System.getProperty("java.version") + " (" +
|
System.getProperty("java.version") + " (" +
|
||||||
|
@ -277,7 +277,7 @@ class SummaryBarRenderer {
|
|||||||
.append("</a>\n");
|
.append("</a>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StatSummarizer.isDisabled()) {
|
if (!StatSummarizer.isDisabled(_context)) {
|
||||||
buf.append("<a href=\"/graphs\" target=\"_top\" title=\"")
|
buf.append("<a href=\"/graphs\" target=\"_top\" title=\"")
|
||||||
.append(_t("Graph router performance"))
|
.append(_t("Graph router performance"))
|
||||||
.append("\">")
|
.append("\">")
|
||||||
@ -822,13 +822,14 @@ class SummaryBarRenderer {
|
|||||||
public String renderBandwidthGraphHTML() {
|
public String renderBandwidthGraphHTML() {
|
||||||
if (_helper == null) return "";
|
if (_helper == null) return "";
|
||||||
StringBuilder buf = new StringBuilder(512);
|
StringBuilder buf = new StringBuilder(512);
|
||||||
if (!StatSummarizer.isDisabled())
|
if (!StatSummarizer.isDisabled(_context)) {
|
||||||
buf.append("<div id=\"sb_graphcontainer\"><a href=\"/graphs\"><table id=\"sb_bandwidthgraph\">" +
|
buf.append("<div id=\"sb_graphcontainer\"><a href=\"/graphs\"><table id=\"sb_bandwidthgraph\">" +
|
||||||
"<tr title=\"")
|
"<tr title=\"")
|
||||||
.append(_t("Our inbound & outbound traffic for the last 20 minutes"))
|
.append(_t("Our inbound & outbound traffic for the last 20 minutes"))
|
||||||
.append("\"><td><span id=\"sb_graphstats\">")
|
.append("\"><td><span id=\"sb_graphstats\">")
|
||||||
.append(_helper.getSecondKBps())
|
.append(_helper.getSecondKBps())
|
||||||
.append("Bps</span></td></tr></table></a></div>\n");
|
.append("Bps</span></td></tr></table></a></div>\n");
|
||||||
|
}
|
||||||
buf.append("<script src=\"/js/refreshGraph.js\" type=\"text/javascript\" id=\"refreshGraph\" async></script>");
|
buf.append("<script src=\"/js/refreshGraph.js\" type=\"text/javascript\" id=\"refreshGraph\" async></script>");
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,19 @@
|
|||||||
*
|
*
|
||||||
* Do not tag this file for translation.
|
* Do not tag this file for translation.
|
||||||
*/
|
*/
|
||||||
|
net.i2p.I2PAppContext ctx = net.i2p.I2PAppContext.getGlobalContext();
|
||||||
|
net.i2p.router.web.StatSummarizer ss = net.i2p.router.web.StatSummarizer.instance(ctx);
|
||||||
|
if (ss == null) {
|
||||||
|
response.sendError(403, "Stats disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
boolean rendered = false;
|
boolean rendered = false;
|
||||||
/**** unused
|
/**** unused
|
||||||
String templateFile = request.getParameter("template");
|
String templateFile = request.getParameter("template");
|
||||||
if (templateFile != null) {
|
if (templateFile != null) {
|
||||||
java.io.OutputStream cout = response.getOutputStream();
|
java.io.OutputStream cout = response.getOutputStream();
|
||||||
response.setContentType("image/png");
|
response.setContentType("image/png");
|
||||||
rendered = net.i2p.router.web.StatSummarizer.instance().renderPng(cout, templateFile);
|
rendered = ss.renderPng(cout, templateFile);
|
||||||
}
|
}
|
||||||
****/
|
****/
|
||||||
net.i2p.stat.Rate rate = null;
|
net.i2p.stat.Rate rate = null;
|
||||||
@ -22,7 +27,7 @@ String period = request.getParameter("period");
|
|||||||
boolean fakeBw = (stat != null && ("bw.combined".equals(stat)));
|
boolean fakeBw = (stat != null && ("bw.combined".equals(stat)));
|
||||||
net.i2p.stat.RateStat rs = null;
|
net.i2p.stat.RateStat rs = null;
|
||||||
if (stat != null)
|
if (stat != null)
|
||||||
rs = net.i2p.I2PAppContext.getGlobalContext().statManager().getRate(stat);
|
rs = ctx.statManager().getRate(stat);
|
||||||
if ( !rendered && ((rs != null) || fakeBw) ) {
|
if ( !rendered && ((rs != null) || fakeBw) ) {
|
||||||
long per = -1;
|
long per = -1;
|
||||||
try {
|
try {
|
||||||
@ -39,12 +44,12 @@ if ( !rendered && ((rs != null) || fakeBw) ) {
|
|||||||
if ("xml".equals(format)) {
|
if ("xml".equals(format)) {
|
||||||
if (!fakeBw) {
|
if (!fakeBw) {
|
||||||
response.setContentType("text/xml");
|
response.setContentType("text/xml");
|
||||||
rendered = net.i2p.router.web.StatSummarizer.instance().getXML(rate, cout);
|
rendered = ss.getXML(rate, cout);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response.setContentType("image/png");
|
response.setContentType("image/png");
|
||||||
// very brief 45 sec expire
|
// very brief 45 sec expire
|
||||||
response.setDateHeader("Expires", net.i2p.I2PAppContext.getGlobalContext().clock().now() + (45*1000));
|
response.setDateHeader("Expires", ctx.clock().now() + (45*1000));
|
||||||
response.setHeader("Accept-Ranges", "none");
|
response.setHeader("Accept-Ranges", "none");
|
||||||
// http://jira.codehaus.org/browse/JETTY-1346
|
// http://jira.codehaus.org/browse/JETTY-1346
|
||||||
// This doesn't actually appear in the response, but it fixes the problem,
|
// This doesn't actually appear in the response, but it fixes the problem,
|
||||||
@ -70,9 +75,9 @@ if ( !rendered && ((rs != null) || fakeBw) ) {
|
|||||||
if (request.getParameter("showCredit") != null)
|
if (request.getParameter("showCredit") != null)
|
||||||
showCredit = Boolean.parseBoolean(request.getParameter("showCredit"));
|
showCredit = Boolean.parseBoolean(request.getParameter("showCredit"));
|
||||||
if (fakeBw)
|
if (fakeBw)
|
||||||
rendered = net.i2p.router.web.StatSummarizer.instance().renderRatePng(cout, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, end, showCredit);
|
rendered = ss.renderRatePng(cout, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, end, showCredit);
|
||||||
else
|
else
|
||||||
rendered = net.i2p.router.web.StatSummarizer.instance().renderPng(rate, cout, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, end, showCredit);
|
rendered = ss.renderPng(rate, cout, width, height, hideLegend, hideGrid, hideTitle, showEvents, periodCount, end, showCredit);
|
||||||
}
|
}
|
||||||
if (rendered)
|
if (rendered)
|
||||||
cout.close();
|
cout.close();
|
||||||
|
Reference in New Issue
Block a user