package net.i2p.android.router.service; import net.i2p.android.router.util.Util; import net.i2p.router.RouterContext; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.concurrent.CopyOnWriteArrayList; public class StatSummarizer implements Runnable { private final RouterContext _context; private final List<SummaryListener> _listeners; // TODO remove static instance private static StatSummarizer _instance; private volatile boolean _isRunning = true; private Thread _thread; public StatSummarizer() { _context = Util.getRouterContext(); _listeners = new CopyOnWriteArrayList<>(); _instance = this; if (_context != null) _context.addShutdownTask(new Shutdown()); } public static StatSummarizer instance() { return _instance; } public void run() { // We can't do anything without a RouterContext if (_context == null) return; _thread = Thread.currentThread(); String specs = ""; while (_isRunning && _context.router().isAlive()) { specs = adjustDatabases(specs); try { Thread.sleep(60 * 1000);} catch (InterruptedException ie) {} } } /** list of SummaryListener instances */ public List<SummaryListener> getListeners() { return _listeners; } public SummaryListener getListener(String rateName, long period) { for (SummaryListener lsnr : _listeners) { if (lsnr.getName().equals(rateName + "." + period)) return lsnr; } return null; } private static final String DEFAULT_DATABASES = "bw.sendRate.60000" + ",bw.recvRate.60000" + ",router.memoryUsed.60000" + ",router.activePeers.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; List<Rate> old = parseSpecs(oldSpecs); List<Rate> newSpecs = parseSpecs(spec); // remove old ones for (Rate r : old) { if (!newSpecs.contains(r)) removeDb(r); } // add new ones StringBuilder buf = new StringBuilder(); boolean comma = false; for (Rate r : newSpecs) { if (!old.contains(r)) addDb(r); if (comma) buf.append(','); else comma = true; buf.append(r.getRateStat().getName()).append(".").append(r.getPeriod()); } return buf.toString(); } private void removeDb(Rate r) { for (SummaryListener lsnr : _listeners) { if (lsnr.getRate().equals(r)) { // no iter.remove() in COWAL _listeners.remove(lsnr); lsnr.stopListening(); return; } } } private void addDb(Rate r) { SummaryListener lsnr = new SummaryListener(r); lsnr.startListening(); _listeners.add(lsnr); } /** * @param specs statName.period,statName.period,statName.period * @return list of Rate objects */ List<Rate> parseSpecs(String specs) { StringTokenizer tok = new StringTokenizer(specs, ","); List<Rate> rv = new ArrayList<>(); 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; 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; } private class Shutdown implements Runnable { public void run() { _isRunning = false; if (_thread != null) _thread.interrupt(); for (SummaryListener lsnr : _listeners) { lsnr.stopListening(); } _listeners.clear(); } } }