2004-10-03 jrandom
* Add a new stat logging component to optionally dump the raw stats to disk as they are generated, rather than rely upon the summarized data. By default, this is off, but the router property "stat.logFilters" can be set to a comma delimited list of stats (e.g. "client.sendAckTime") which will be written to the file "stats.log" (or whatever the property "stat.logFile" is set to). This can also log profile related stats, such as "dbResponseTime" or "tunnelTestResponseTime".
This commit is contained in:
146
core/java/src/net/i2p/stat/BufferedStatLog.java
Normal file
146
core/java/src/net/i2p/stat/BufferedStatLog.java
Normal file
@ -0,0 +1,146 @@
|
||||
package net.i2p.stat;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BufferedStatLog implements StatLog {
|
||||
private I2PAppContext _context;
|
||||
private List _events;
|
||||
/** flush stat events to disk after this many events (or 30s)*/
|
||||
private int _flushFrequency;
|
||||
private List _statFilters;
|
||||
private BufferedWriter _out;
|
||||
private String _outFile;
|
||||
|
||||
public BufferedStatLog(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_events = new ArrayList(1000);
|
||||
_statFilters = new ArrayList(10);
|
||||
_flushFrequency = 1000;
|
||||
I2PThread writer = new I2PThread(new StatLogWriter(), "StatLogWriter");
|
||||
writer.setDaemon(true);
|
||||
writer.start();
|
||||
}
|
||||
|
||||
public void addData(String scope, String stat, long value, long duration) {
|
||||
synchronized (_events) {
|
||||
_events.add(new StatEvent(scope, stat, value, duration));
|
||||
if (_events.size() > _flushFrequency)
|
||||
_events.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldLog(String stat) {
|
||||
synchronized (_statFilters) {
|
||||
return _statFilters.contains(stat);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFilters() {
|
||||
String val = _context.getProperty("stat.logFilters");
|
||||
if (val != null) {
|
||||
StringTokenizer tok = new StringTokenizer(val, ",");
|
||||
synchronized (_statFilters) {
|
||||
_statFilters.clear();
|
||||
while (tok.hasMoreTokens())
|
||||
_statFilters.add(tok.nextToken().trim());
|
||||
}
|
||||
} else {
|
||||
synchronized (_statFilters) { _statFilters.clear(); }
|
||||
}
|
||||
|
||||
String filename = _context.getProperty("stat.logFile");
|
||||
if (filename == null)
|
||||
filename = "stats.log";
|
||||
if ( (_outFile != null) && (_outFile.equals(filename)) ) {
|
||||
// noop
|
||||
} else {
|
||||
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
|
||||
_outFile = filename;
|
||||
try {
|
||||
_out = new BufferedWriter(new FileWriter(_outFile));
|
||||
} catch (IOException ioe) { ioe.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
|
||||
private class StatLogWriter implements Runnable {
|
||||
private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd hh:mm:ss.SSS");
|
||||
public void run() {
|
||||
List cur = new ArrayList(1000);
|
||||
while (true) {
|
||||
synchronized (_events) {
|
||||
if (_events.size() < _flushFrequency) {
|
||||
try { _events.wait(30*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
cur.addAll(_events);
|
||||
_events.clear();
|
||||
}
|
||||
if (cur.size() > 0) {
|
||||
writeEvents(cur);
|
||||
cur.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeEvents(List events) {
|
||||
try {
|
||||
updateFilters();
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
StatEvent evt = (StatEvent)events.get(i);
|
||||
if (!shouldLog(evt.getStat())) continue;
|
||||
String when = null;
|
||||
synchronized (_fmt) {
|
||||
when = _fmt.format(new Date(evt.getTime()));
|
||||
}
|
||||
_out.write(when);
|
||||
_out.write(" ");
|
||||
if (evt.getScope() == null)
|
||||
_out.write("noScope ");
|
||||
else
|
||||
_out.write(evt.getScope() + " ");
|
||||
_out.write(evt.getStat()+" ");
|
||||
_out.write(evt.getValue()+" ");
|
||||
_out.write(evt.getDuration()+"\n");
|
||||
}
|
||||
_out.flush();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StatEvent {
|
||||
private long _time;
|
||||
private String _scope;
|
||||
private String _stat;
|
||||
private long _value;
|
||||
private long _duration;
|
||||
|
||||
public StatEvent(String scope, String stat, long value, long duration) {
|
||||
_scope = scope;
|
||||
_stat = stat;
|
||||
_value = value;
|
||||
_duration = duration;
|
||||
_time = _context.clock().now();
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ public class RateStat {
|
||||
private String _description;
|
||||
/** actual rate objects for this statistic */
|
||||
private Rate _rates[];
|
||||
/** component we tell about events as they occur */
|
||||
private StatLog _statLog;
|
||||
|
||||
public RateStat(String name, String description, String group, long periods[]) {
|
||||
_statName = name;
|
||||
@ -28,11 +30,13 @@ public class RateStat {
|
||||
for (int i = 0; i < periods.length; i++)
|
||||
_rates[i] = new Rate(periods[i]);
|
||||
}
|
||||
|
||||
public void setStatLog(StatLog sl) { _statLog = sl; }
|
||||
|
||||
/**
|
||||
* update all of the rates for the various periods with the given value.
|
||||
*/
|
||||
public void addData(long value, long eventDuration) {
|
||||
if (_statLog != null) _statLog.addData(_groupName, _statName, value, eventDuration);
|
||||
for (int i = 0; i < _rates.length; i++)
|
||||
_rates[i].addData(value, eventDuration);
|
||||
}
|
||||
|
8
core/java/src/net/i2p/stat/StatLog.java
Normal file
8
core/java/src/net/i2p/stat/StatLog.java
Normal file
@ -0,0 +1,8 @@
|
||||
package net.i2p.stat;
|
||||
|
||||
/**
|
||||
* Component to be notified when a particular event occurs
|
||||
*/
|
||||
public interface StatLog {
|
||||
public void addData(String scope, String stat, long value, long duration);
|
||||
}
|
@ -27,6 +27,7 @@ public class StatManager {
|
||||
private Map _frequencyStats;
|
||||
/** stat name to RateStat */
|
||||
private Map _rateStats;
|
||||
private StatLog _statLog;
|
||||
|
||||
/**
|
||||
* The stat manager should only be constructed and accessed through the
|
||||
@ -39,6 +40,18 @@ public class StatManager {
|
||||
_context = context;
|
||||
_frequencyStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_rateStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_statLog = new BufferedStatLog(context);
|
||||
}
|
||||
|
||||
public StatLog getStatLog() { return _statLog; }
|
||||
public void setStatLog(StatLog log) {
|
||||
_statLog = log;
|
||||
synchronized (_rateStats) {
|
||||
for (Iterator iter = _rateStats.values().iterator(); iter.hasNext(); ) {
|
||||
RateStat rs = (RateStat)iter.next();
|
||||
rs.setStatLog(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +77,9 @@ public class StatManager {
|
||||
*/
|
||||
public void createRateStat(String name, String description, String group, long periods[]) {
|
||||
if (_rateStats.containsKey(name)) return;
|
||||
_rateStats.put(name, new RateStat(name, description, group, periods));
|
||||
RateStat rs = new RateStat(name, description, group, periods);
|
||||
if (_statLog != null) rs.setStatLog(_statLog);
|
||||
_rateStats.put(name, rs);
|
||||
}
|
||||
|
||||
/** update the given frequency statistic, taking note that an event occurred (and recalculating all frequencies) */
|
||||
|
Reference in New Issue
Block a user