From c4a3159b3399745db76cf20327a6f6e1e4401172 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Thu, 30 Aug 2012 14:06:06 +0000 Subject: [PATCH] Replace ident log with new, general-purpose event log. Use for stops, starts, and updates, and others. Mark all restarts on graphs using the event log. --- .../net/i2p/router/web/SummaryRenderer.java | 18 ++- router/java/src/net/i2p/router/Router.java | 28 ++-- .../net/i2p/router/tasks/RouterWatchdog.java | 2 + .../net/i2p/router/tasks/ShutdownHook.java | 3 + .../src/net/i2p/router/util/EventLog.java | 145 ++++++++++++++++++ 5 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 router/java/src/net/i2p/router/util/EventLog.java diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java index 3a1676ea23..494e93de83 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Map; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; @@ -15,6 +16,7 @@ import javax.imageio.stream.MemoryCacheImageOutputStream; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; +import net.i2p.router.util.EventLog; import net.i2p.util.Log; import org.jrobin.core.RrdException; @@ -137,9 +139,11 @@ class SummaryRenderer { // Strings.java descr = _(_listener.getRate().getRateStat().getDescription()); } - long started = ((RouterContext)_context).router().getWhenStarted(); - if (started > start && started < end) - def.vrule(started / 1000, RESTART_BAR_COLOR, _("Restart"), 4.0f); + + //long started = ((RouterContext)_context).router().getWhenStarted(); + //if (started > start && started < end) + // def.vrule(started / 1000, RESTART_BAR_COLOR, _("Restart"), 4.0f); + def.datasource(plotName, path, plotName, SummaryListener.CF, _listener.getBackendName()); if (descr.length() > 0) def.area(plotName, Color.BLUE, descr + "\\r"); @@ -151,6 +155,14 @@ class SummaryRenderer { def.gprint(plotName, "LAST", ' ' + _("now") + ": %.2f %S\\r"); // '07-Jul 21:09 UTC' with month name in the system locale SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM HH:mm"); + Map<Long, String> events = ((RouterContext)_context).router().eventLog().getEvents(EventLog.STARTED, start); + for (Map.Entry<Long, String> event : events.entrySet()) { + long started = event.getKey().longValue(); + if (started > start && started < end) { + String legend = _("Restart") + ' ' + sdf.format(new Date(started)) + " UTC " + event.getValue() + "\\r"; + def.vrule(started / 1000, RESTART_BAR_COLOR, legend, 4.0f); + } + } def.comment(sdf.format(new Date(start)) + " -- " + sdf.format(new Date(end)) + " UTC\\r"); } if (!showCredit) diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 442c8e78af..f270f18b75 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -40,6 +40,7 @@ import net.i2p.router.startup.WorkingDir; import net.i2p.router.tasks.*; import net.i2p.router.transport.FIFOBandwidthLimiter; import net.i2p.router.transport.udp.UDPTransport; +import net.i2p.router.util.EventLog; import net.i2p.stat.RateStat; import net.i2p.stat.StatManager; import net.i2p.util.ByteCache; @@ -77,6 +78,7 @@ public class Router implements RouterClock.ClockShiftListener { private I2PThread _gracefulShutdownDetector; private RouterWatchdog _watchdog; private Thread _watchdogThread; + private final EventLog _eventLog; public final static String PROP_CONFIG_FILE = "router.configLocation"; @@ -100,6 +102,7 @@ public class Router implements RouterClock.ClockShiftListener { public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys"; public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress"; public final static String DNS_CACHE_TIME = "" + (5*60); + private static final String EVENTLOG = "eventlog.txt"; private static final String originalTimeZoneID; static { @@ -219,12 +222,14 @@ public class Router implements RouterClock.ClockShiftListener { // i2p.dir.pid defaults to i2p.dir.router // i2p.dir.base defaults to user.dir == $CWD _context = new RouterContext(this, envProps); + _eventLog = new EventLog(_context, new File(_context.getRouterDir(), EVENTLOG)); // This is here so that we can get the directory location from the context // for the ping file // Check for other router but do not start a thread yet so the update doesn't cause // a NCDFE if (!isOnlyRouterRunning()) { + _eventLog.addEvent(EventLog.ABORTED, "Another router running"); System.err.println("ERROR: There appears to be another router already running!"); System.err.println(" Please make sure to shut down old instances before starting up"); System.err.println(" a new one. If you are positive that no other instance is running,"); @@ -410,6 +415,7 @@ public class Router implements RouterClock.ClockShiftListener { public void runRouter() { if (_isAlive) throw new IllegalStateException(); + _eventLog.addEvent(EventLog.STARTED, RouterVersion.FULL_VERSION); startupStuff(); _isAlive = true; _started = _context.clock().now(); @@ -631,6 +637,13 @@ public class Router implements RouterClock.ClockShiftListener { return Certificate.NULL_CERT; } + /** + * @since 0.9.3 + */ + public EventLog eventLog() { + return _eventLog; + } + /** * Ugly list of files that we need to kill if we are building a new identity * @@ -646,7 +659,6 @@ public class Router implements RouterClock.ClockShiftListener { "sessionKeys.dat" // no longer used }; - static final String IDENTLOG = "identlog.txt"; public void killKeys() { //new Exception("Clearing identity files").printStackTrace(); int remCount = 0; @@ -671,18 +683,10 @@ public class Router implements RouterClock.ClockShiftListener { } if (remCount > 0) { - FileOutputStream log = null; - try { - log = new FileOutputStream(new File(_context.getRouterDir(), IDENTLOG), true); - log.write((new Date() + ": Old router identity keys cleared\n").getBytes()); - } catch (IOException ioe) { - // ignore - } finally { - if (log != null) - try { log.close(); } catch (IOException ioe) {} - } + _eventLog.addEvent(EventLog.REKEYED); } } + /** * Rebuild a new identity the hard way - delete all of our old identity * files, then reboot the router. @@ -872,6 +876,7 @@ public class Router implements RouterClock.ClockShiftListener { } } _context.getFinalShutdownTasks().clear(); + _eventLog.addEvent(EventLog.STOPPED, Integer.toString(exitCode)); if (_killVMOnEnd) { try { Thread.sleep(1000); } catch (InterruptedException ie) {} @@ -1140,6 +1145,7 @@ public class Router implements RouterClock.ClockShiftListener { // Set the last version to the current version, since 0.8.13 _config.put("router.previousVersion", RouterVersion.VERSION); saveConfig(); + _eventLog.addEvent(EventLog.UPDATED); ok = FileUtil.extractZip(updateFile, _context.getBaseDir()); } diff --git a/router/java/src/net/i2p/router/tasks/RouterWatchdog.java b/router/java/src/net/i2p/router/tasks/RouterWatchdog.java index 7cc029b77a..213721f9f2 100644 --- a/router/java/src/net/i2p/router/tasks/RouterWatchdog.java +++ b/router/java/src/net/i2p/router/tasks/RouterWatchdog.java @@ -6,6 +6,7 @@ import net.i2p.data.DataHelper; import net.i2p.router.Job; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.util.EventLog; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; import net.i2p.util.ShellCommand; @@ -107,6 +108,7 @@ public class RouterWatchdog implements Runnable { _log.error("Memory: " + DataHelper.formatSize(used) + '/' + DataHelper.formatSize(max)); if (_consecutiveErrors == 1) { _log.log(Log.CRIT, "Router appears hung, or there is severe network congestion. Watchdog starts barking!"); + _context.router().eventLog().addEvent(EventLog.WATCHDOG); // This works on linux... // It won't on windows, and we can't call i2prouter.bat either, it does something // completely different... diff --git a/router/java/src/net/i2p/router/tasks/ShutdownHook.java b/router/java/src/net/i2p/router/tasks/ShutdownHook.java index ae8e38d7a4..d3db8f8765 100644 --- a/router/java/src/net/i2p/router/tasks/ShutdownHook.java +++ b/router/java/src/net/i2p/router/tasks/ShutdownHook.java @@ -10,6 +10,8 @@ package net.i2p.router.tasks; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.RouterVersion; +import net.i2p.router.util.EventLog; import net.i2p.util.Log; /** @@ -35,6 +37,7 @@ public class ShutdownHook extends Thread { // Needed to make the wrapper happy, otherwise it gets confused // and thinks we haven't shut down, possibly because it // prevents other shutdown hooks from running + _context.router().eventLog().addEvent(EventLog.CRASHED, RouterVersion.FULL_VERSION); _context.router().setKillVMOnEnd(false); _context.router().shutdown2(Router.EXIT_HARD); } diff --git a/router/java/src/net/i2p/router/util/EventLog.java b/router/java/src/net/i2p/router/util/EventLog.java new file mode 100644 index 0000000000..9a2d9d20e1 --- /dev/null +++ b/router/java/src/net/i2p/router/util/EventLog.java @@ -0,0 +1,145 @@ +package net.i2p.router.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import net.i2p.I2PAppContext; +import net.i2p.util.SecureFileOutputStream; + +/** + * Simple event logger for occasional events, + * with caching for reads. + * Does not keep the file open. + * @since 0.9.3 + */ +public class EventLog { + + private final I2PAppContext _context; + private final File _file; + /** event to cached map */ + private final Map<String, SortedMap<Long, String>> _cache; + /** event to starting time of cached map */ + private final Map<String, Long> _cacheTime; + + /** for convenience, not required */ + public static final String ABORTED = "aborted"; + public static final String CHANGE_IP = "changeIP"; + public static final String CHANGE_PORT = "changePort"; + public static final String CLOCK_SHIFT = "clockShift"; + public static final String CRASHED = "crashed"; + public static final String INSTALLED = "installed"; + public static final String INSTALL_FAILED = "intallFailed"; + public static final String NEW_IDENT = "newIdent"; + public static final String REKEYED = "rekeyed"; + public static final String SOFT_RESTART = "softRestart"; + public static final String STARTED = "started"; + public static final String STOPPED = "stopped"; + public static final String UPDATED = "updated"; + public static final String WATCHDOG = "watchdog"; + + /** + * @param file must be absolute + * @throws IllegalArgumentException if not absolute + */ + public EventLog(I2PAppContext ctx, File file) { + if (!file.isAbsolute()) + throw new IllegalArgumentException(); + _context = ctx; + _file = file; + _cache = new HashMap(4); + _cacheTime = new HashMap(4); + } + + /** + * Append an event. Fails silently. + * @param event no spaces, e.g. "started" + * @throws IllegalArgumentException if event contains a space or newline + */ + public void addEvent(String event) { + addEvent(event, null); + } + + /** + * Append an event. Fails silently. + * @param event no spaces or newlines, e.g. "started" + * @param info no newlines, may be blank or null + * @throws IllegalArgumentException if event contains a space or either contains a newline + */ + public synchronized void addEvent(String event, String info) { + if (event.contains(" ") || event.contains("\n") || + (info != null && info.contains("\n"))) + throw new IllegalArgumentException(); + _cache.remove(event); + _cacheTime.remove(event); + OutputStream out = null; + try { + out = new SecureFileOutputStream(_file, true); + StringBuilder buf = new StringBuilder(128); + buf.append(_context.clock().now()).append(' ').append(event); + if (info != null && info.length() > 0) + buf.append(' ').append(info); + buf.append('\n'); + out.write(buf.toString().getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + } + + /** + * Caches. + * Fails silently. + * @param event matching this event only, case sensitive + * @param since since this time, 0 for all + * @return non-null, Map of times to (possibly empty) info strings, sorted, earliest first, unmodifiable + */ + public synchronized SortedMap<Long, String> getEvents(String event, long since) { + SortedMap<Long, String> rv = _cache.get(event); + if (rv != null) { + Long cacheTime = _cacheTime.get(event); + if (cacheTime != null) { + if (since >= cacheTime.longValue()) + return rv.tailMap(Long.valueOf(since)); + } + } + rv = new TreeMap(); + InputStream in = null; + try { + in = new FileInputStream(_file); + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + String line = null; + while ( (line = br.readLine()) != null) { + try { + String[] s = line.split(" ", 3); + if (!s[1].equals(event)) + continue; + long time = Long.parseLong(s[0]); + if (time <= since) + continue; + Long ltime = Long.valueOf(time); + String info = s.length > 2 ? s[2] : ""; + rv.put(time, info); + } catch (IndexOutOfBoundsException ioobe) { + } catch (NumberFormatException nfe) { + } + } + rv = Collections.unmodifiableSortedMap(rv); + _cache.put(event, rv); + _cacheTime.put(event, Long.valueOf(since)); + } catch (IOException ioe) { + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + return rv; + } +} -- GitLab