diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java b/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java index c6f878418efb442e5d8d6c666469e7270d288c18..632baae09d8069a8778f4b4ad10bad49fce0a89b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java @@ -11,6 +11,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import net.i2p.app.ClientAppManager; +import net.i2p.app.ClientAppState; +import static net.i2p.app.ClientAppState.*; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -20,6 +23,7 @@ import net.i2p.data.router.RouterInfo; import net.i2p.data.router.RouterKeyGenerator; import net.i2p.router.RouterContext; import net.i2p.router.TunnelPoolSettings; +import net.i2p.router.app.RouterApp; import net.i2p.router.crypto.FamilyKeyCrypto; import net.i2p.router.peermanager.DBHistory; import net.i2p.router.peermanager.PeerProfile; @@ -36,10 +40,20 @@ import net.i2p.util.ObjectCounter; * @since 0.9.38 split out from SybilRenderer * */ -public class Analysis { +public class Analysis implements RouterApp { private final RouterContext _context; + private final ClientAppManager _cmgr; + private final PersistSybil _persister; + private volatile ClientAppState _state = UNINITIALIZED; private final DecimalFormat fmt = new DecimalFormat("#0.00"); + /** + * The name we register with the ClientAppManager + */ + public static final String APP_NAME = "sybil"; + public static final String PROP_FREQUENCY = "router.sybilFrequency"; + private static final long MIN_FREQUENCY = 60*60*1000L; + private static final long MIN_UPTIME = 75*60*1000L; public static final int PAIRMAX = 20; public static final int MAX = 10; @@ -64,8 +78,88 @@ public class Analysis { private static final double POINTS_NEW = 4.0; private static final double POINTS_BANLIST = 25.0; - public Analysis(RouterContext ctx) { + /** Get via getInstance() */ + private Analysis(RouterContext ctx, ClientAppManager mgr, String[] args) { _context = ctx; + _cmgr = mgr; + _persister = new PersistSybil(ctx); + } + + /** + * @return non-null, creates new if not already registered + */ + public synchronized static Analysis getInstance(RouterContext ctx) { + ClientAppManager cmgr = ctx.clientAppManager(); + if (cmgr == null) + return null; + Analysis rv = (Analysis) cmgr.getRegisteredApp(APP_NAME); + if (rv == null) { + rv = new Analysis(ctx, cmgr, null); + rv.startup(); + } + return rv; + } + + public PersistSybil getPersister() { return _persister; } + + /** + * ClientApp interface + */ + public synchronized void startup() { + changeState(STARTING); + changeState(RUNNING); + _cmgr.register(this); + schedule(); + } + + /** + * ClientApp interface + * @param args ignored + */ + public synchronized void shutdown(String[] args) { + if (_state == STOPPED) + return; + changeState(STOPPING); + changeState(STOPPED); + } + + public ClientAppState getState() { + return _state; + } + + public String getName() { + return APP_NAME; + } + + public String getDisplayName() { + return "Sybil Analyzer"; + } + + /////// end ClientApp methods + + private synchronized void changeState(ClientAppState state) { + _state = state; + if (_cmgr != null) + _cmgr.notify(this, state, null, null); + } + + public void schedule() { + long freq = _context.getProperty(PROP_FREQUENCY, 0L); + if (freq > 0) { + List<Long> previous = _persister.load(); + long now = _context.clock().now(); + long when; + if (!previous.isEmpty()) { + if (freq < MIN_FREQUENCY) + freq = MIN_FREQUENCY; + when = Math.max(previous.get(0).longValue() + freq, now); + } else { + when = now; + } + long up = _context.router().getUptime(); + when = Math.max(when, now + MIN_UPTIME - up); + // TODO schedule for when + } } private static class RouterInfoRoutingKeyComparator implements Comparator<RouterInfo>, Serializable { diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java b/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java index 7efa5a5cde8e8bbe383e71617ba63b4b76e2ca2b..23ad7f3b055c31b44ab44bdbf71d06f1da718cb0 100644 --- a/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java @@ -44,7 +44,8 @@ public class PersistSybil { private static final String PFX = "sybil-"; private static final String SFX = ".txt.gz"; - public PersistSybil(I2PAppContext ctx) { + /** access via Analysis.getPersister() */ + PersistSybil(I2PAppContext ctx) { _context = ctx; _log = ctx.logManager().getLog(PersistSybil.class); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 7410e7140d78a0a5eb5b0f914f29ed7794f03fde..59881aa72b1b9d1d190194d317941ca3dca166ec 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -34,6 +34,7 @@ import net.i2p.jetty.I2PLogger; import net.i2p.router.RouterContext; import net.i2p.router.app.RouterApp; import net.i2p.router.news.NewsManager; +import net.i2p.router.sybil.Analysis; import net.i2p.router.update.ConsoleUpdateManager; import net.i2p.util.Addresses; import net.i2p.util.FileSuffixFilter; @@ -865,6 +866,13 @@ public class RouterConsoleRunner implements RouterApp { if (_mgr == null) _context.addShutdownTask(new ServerShutdown()); ConfigServiceHandler.registerSignalHandler(_context); + + if (_mgr != null && + _context.getBooleanProperty(HelperBase.PROP_ADVANCED) && + _context.getProperty(Analysis.PROP_FREQUENCY, 0L) > 0) { + // registers and starts itself + Analysis.getInstance(_context); + } } /** diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java index 4b18a85418b7fdc4ba7f93cf895f21713bb4f2af..d7a96a5d0f66c32771ed6ef734b2294e65968f3e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java @@ -125,7 +125,7 @@ public class SybilRenderer { */ private void renderRouterInfoHTML(Writer out, int mode, long date) throws IOException { Hash us = _context.routerHash(); - Analysis analysis = new Analysis(_context); + Analysis analysis = Analysis.getInstance(_context); List<RouterInfo> ris = analysis.getFloodfills(us); if (ris.isEmpty()) { out.write("<h3 class=\"sybils\">No known floodfills</h3>"); @@ -180,7 +180,7 @@ public class SybilRenderer { } else if (mode == 11) { renderDestSummary(out, buf, analysis, avgMinDist, ris, points); } else if (mode == 12) { - PersistSybil ps = new PersistSybil(_context); + PersistSybil ps = analysis.getPersister(); try { points = ps.load(date); } catch (IOException ioe) { @@ -196,7 +196,7 @@ public class SybilRenderer { long now = _context.clock().now(); points = analysis.backgroundAnalysis(); if (!points.isEmpty()) { - PersistSybil ps = new PersistSybil(_context); + PersistSybil ps = analysis.getPersister(); try { ps.store(now, points); } catch (IOException ioe) { @@ -214,7 +214,7 @@ public class SybilRenderer { * @since 0.9.38 */ private void renderOverview(Writer out, StringBuilder buf, Analysis analysis) throws IOException { - PersistSybil ps = new PersistSybil(_context); + PersistSybil ps = analysis.getPersister(); List<Long> dates = ps.load(); if (dates.isEmpty()) { out.write("No stored analysis");