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.
This commit is contained in:
zzz
2012-08-30 14:06:06 +00:00
parent b01cf32321
commit c4a3159b33
5 changed files with 182 additions and 14 deletions

View File

@ -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)

View File

@ -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());
}

View File

@ -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...

View File

@ -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);
}

View File

@ -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;
}
}