package net.i2p.stat;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Note - if no filter is defined in stat.logFilters at startup, this class will not
* be instantiated - see StatManager.
*/
public class BufferedStatLog implements StatLog {
private final I2PAppContext _context;
private final Log _log;
private final StatEvent _events[];
private int _eventNext;
private int _lastWrite;
/** flush stat events to disk after this many events (or 30s)*/
private int _flushFrequency;
private final List<String> _statFilters;
private String _lastFilters;
private BufferedWriter _out;
private String _outFile;
/** short circuit for adding data, set to true if some filters are set, false if its empty (so we can skip the sync) */
private volatile boolean _filtersSpecified;
private static final int BUFFER_SIZE = 1024;
private static final boolean DISABLE_LOGGING = false;
public BufferedStatLog(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(BufferedStatLog.class);
_events = new StatEvent[BUFFER_SIZE];
if (DISABLE_LOGGING) return;
for (int i = 0; i < BUFFER_SIZE; i++)
_events[i] = new StatEvent();
_eventNext = 0;
_lastWrite = _events.length-1;
_statFilters = new ArrayList<String>(10);
_flushFrequency = 500;
updateFilters();
I2PThread writer = new I2PThread(new StatLogWriter(), "StatLogWriter");
writer.setDaemon(true);
writer.start();
}
public void addData(String scope, String stat, long value, long duration) {
if (DISABLE_LOGGING) return;
if (!shouldLog(stat)) return;
synchronized (_events) {
_events[_eventNext].init(scope, stat, value, duration);
_eventNext = (_eventNext + 1) % _events.length;
if (_eventNext == _lastWrite)
_lastWrite = (_lastWrite + 1) % _events.length; // drop an event
if (_log.shouldLog(Log.DEBUG))
_log.debug("AddData next=" + _eventNext + " lastWrite=" + _lastWrite);
if (_eventNext > _lastWrite) {
if (_eventNext - _lastWrite >= _flushFrequency)
_events.notifyAll();
} else {
if (_events.length - 1 - _lastWrite + _eventNext >= _flushFrequency)
_events.notifyAll();
}
}
}
private boolean shouldLog(String stat) {
if (!_filtersSpecified) return false;
synchronized (_statFilters) {
return _statFilters.contains(stat) || _statFilters.contains("*");
}
}
private void updateFilters() {
String val = _context.getProperty(StatManager.PROP_STAT_FILTER);
if (val != null) {
if ( (_lastFilters != null) && (_lastFilters.equals(val)) ) {
// noop
} else {
StringTokenizer tok = new StringTokenizer(val, ",");
synchronized (_statFilters) {
_statFilters.clear();
while (tok.hasMoreTokens())
_statFilters.add(tok.nextToken().trim());
_filtersSpecified = !_statFilters.isEmpty();
}
}
_lastFilters = val;
} else {
synchronized (_statFilters) {
_statFilters.clear();
_filtersSpecified = false;
}
}
String filename = _context.getProperty(StatManager.PROP_STAT_FILE, StatManager.DEFAULT_STAT_FILE);
File foo = new File(filename);
if (!foo.isAbsolute())
filename = (new File(_context.getRouterDir(), filename)).getAbsolutePath();
if ( (_outFile != null) && (_outFile.equals(filename)) ) {
// noop
} else {
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
_outFile = filename;
try {
_out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_outFile, true), "UTF-8"), 32*1024);
} catch (IOException ioe) { ioe.printStackTrace(); }
}
}
private class StatLogWriter implements Runnable {
private final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd HH:mm:ss.SSS");
public void run() {
int writeStart = -1;
int writeEnd = -1;
while (true) {
try {
synchronized (_events) {
if (_eventNext > _lastWrite) {
if (_eventNext - _lastWrite < _flushFrequency)
_events.wait(30*1000);
} else {
if (_events.length - 1 - _lastWrite + _eventNext < _flushFrequency)
_events.wait(30*1000);
}
writeStart = (_lastWrite + 1) % _events.length;
writeEnd = _eventNext;
_lastWrite = (writeEnd == 0 ? _events.length-1 : writeEnd - 1);
}
if (writeStart != writeEnd) {
try {
if (_log.shouldLog(Log.DEBUG))
_log.debug("writing " + writeStart +"->"+ writeEnd);
writeEvents(writeStart, writeEnd);
} catch (RuntimeException e) {
_log.error("error writing " + writeStart +"->"+ writeEnd, e);
}
}
} catch (InterruptedException ie) {}
}
}
private void writeEvents(int start, int end) {
try {
updateFilters();
int cur = start;
while (cur != end) {
//if (shouldLog(_events[cur].getStat())) {
String when = null;
synchronized (_fmt) {
when = _fmt.format(new Date(_events[cur].getTime()));
}
_out.write(when);
_out.write(" ");
if (_events[cur].getScope() == null)
_out.write("noScope");
else
_out.write(_events[cur].getScope());
_out.write(" ");
_out.write(_events[cur].getStat());
_out.write(" ");
_out.write(Long.toString(_events[cur].getValue()));
_out.write(" ");
_out.write(Long.toString(_events[cur].getDuration()));
_out.write("\n");
//}
cur = (cur + 1) % _events.length;
}
_out.flush();
} catch (IOException ioe) {
_log.error("Error writing out", ioe);
}
}
}
private class StatEvent {
private long _time;
private String _scope;
private String _stat;
private long _value;
private long _duration;
public long getTime() { return _time; }
public String getScope() { return _scope; }
public String getStat() { return _stat; }
public long getValue() { return _value; }
public long getDuration() { return _duration; }
public void init(String scope, String stat, long value, long duration) {
_scope = scope;
_stat = stat;
_value = value;
_duration = duration;
_time = _context.clock().now();
}
}
}