package net.i2p.stat;
import java.io.IOException;
import java.io.OutputStream;
import java.text.Collator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
/**
* Coordinate the management of various frequencies and rates within I2P components,
* both allowing central update and retrieval, as well as distributed creation and
* use. This does not provide any persistence, but the data structures exposed can be
* read and updated to manage the complete state.
*
*/
public class StatManager {
private final I2PAppContext _context;
/** stat name to FrequencyStat */
private final ConcurrentHashMap<String, FrequencyStat> _frequencyStats;
/** stat name to RateStat */
private final ConcurrentHashMap<String, RateStat> _rateStats;
/** may be null */
private StatLog _statLog;
/**
* Comma-separated stats or * for all.
* This property must be set at startup, or
* logging is disabled.
*/
public static final String PROP_STAT_FILTER = "stat.logFilters";
public static final String PROP_STAT_FILE = "stat.logFile";
public static final String DEFAULT_STAT_FILE = "stats.log";
/** default false */
public static final String PROP_STAT_FULL = "stat.full";
/**
* The stat manager should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public StatManager(I2PAppContext context) {
_context = context;
_frequencyStats = new ConcurrentHashMap<String,FrequencyStat>(8);
_rateStats = new ConcurrentHashMap<String,RateStat>(128);
String filter = getStatFilter();
if (filter != null && filter.length() > 0)
_statLog = new BufferedStatLog(context);
}
/** @since 0.8.8 */
public void shutdown() {
_frequencyStats.clear();
_rateStats.clear();
}
/** may be null */
public StatLog getStatLog() { return _statLog; }
public void setStatLog(StatLog log) {
_statLog = log;
for (RateStat rs : _rateStats.values()) {
rs.setStatLog(log);
}
}
/**
* Create a new statistic to monitor the frequency of some event.
* The stat is ONLY created if the stat.full property is true or we are not in the router context.
*
* @param name unique name of the statistic
* @param description simple description of the statistic
* @param group used to group statistics together
* @param periods array of period lengths (in milliseconds)
*/
public void createFrequencyStat(String name, String description, String group, long periods[]) {
if (ignoreStat(name)) return;
createRequiredFrequencyStat(name, description, group, periods);
}
/**
* Create a new statistic to monitor the frequency of some event.
* The stat is always created, independent of the stat.full setting or context.
*
* @param name unique name of the statistic
* @param description simple description of the statistic
* @param group used to group statistics together
* @param periods array of period lengths (in milliseconds)
* @since 0.8.7
*/
public void createRequiredFrequencyStat(String name, String description, String group, long periods[]) {
if (_frequencyStats.containsKey(name)) return;
_frequencyStats.putIfAbsent(name, new FrequencyStat(name, description, group, periods));
}
/**
* Create a new statistic to monitor the average value and confidence of some action.
* The stat is ONLY created if the stat.full property is true or we are not in the router context.
*
* @param name unique name of the statistic
* @param description simple description of the statistic
* @param group used to group statistics together
* @param periods array of period lengths (in milliseconds)
*/
public void createRateStat(String name, String description, String group, long periods[]) {
if (ignoreStat(name)) return;
createRequiredRateStat(name, description, group, periods);
}
/**
* Create a new statistic to monitor the average value and confidence of some action.
* The stat is always created, independent of the stat.full setting or context.
*
* @param name unique name of the statistic
* @param description simple description of the statistic
* @param group used to group statistics together
* @param periods array of period lengths (in milliseconds)
* @since 0.8.7
*/
public void createRequiredRateStat(String name, String description, String group, long periods[]) {
if (_rateStats.containsKey(name)) return;
RateStat rs = new RateStat(name, description, group, periods);
if (_statLog != null) rs.setStatLog(_statLog);
_rateStats.putIfAbsent(name, rs);
}
// Hope this doesn't cause any problems with unsynchronized accesses like addRateData() ...
public void removeRateStat(String name) {
_rateStats.remove(name);
}
/** update the given frequency statistic, taking note that an event occurred (and recalculating all frequencies) */
public void updateFrequency(String name) {
FrequencyStat freq = _frequencyStats.get(name);
if (freq != null) freq.eventOccurred();
}
/** update the given rate statistic, taking note that the given data point was received (and recalculating all rates) */
public void addRateData(String name, long data, long eventDuration) {
RateStat stat = _rateStats.get(name); // unsynchronized
if (stat != null) stat.addData(data, eventDuration);
}
/**
* Update the given rate statistic, taking note that the given data point was received (and recalculating all rates).
* Zero duration.
* @since 0.8.10
*/
public void addRateData(String name, long data) {
RateStat stat = _rateStats.get(name); // unsynchronized
if (stat != null) stat.addData(data);
}
private int coalesceCounter;
/** every this many minutes for frequencies */
private static final int FREQ_COALESCE_RATE = 9;
public void coalesceStats() {
if (++coalesceCounter % FREQ_COALESCE_RATE == 0) {
for (FrequencyStat stat : _frequencyStats.values()) {
if (stat != null) {
stat.coalesceStats();
}
}
}
for (RateStat stat : _rateStats.values()) {
if (stat != null) {
stat.coalesceStats();
}
}
}
/**
* Misnamed, as it returns a FrequenceyStat, not a Frequency.
*/
public FrequencyStat getFrequency(String name) {
return _frequencyStats.get(name);
}
/**
* Misnamed, as it returns a RateStat, not a Rate.
*/
public RateStat getRate(String name) {
return _rateStats.get(name);
}
public Set<String> getFrequencyNames() {
return new HashSet<String>(_frequencyStats.keySet());
}
public Set<String> getRateNames() {
return new HashSet<String>(_rateStats.keySet());
}
/** is the given stat a monitored rate? */
public boolean isRate(String statName) {
return _rateStats.containsKey(statName);
}
/** is the given stat a monitored frequency? */
public boolean isFrequency(String statName) {
return _frequencyStats.containsKey(statName);
}
/**
* Group name (untranslated String) to a SortedSet of untranslated stat names.
* Map is unsorted.
*/
public Map<String, SortedSet<String>> getStatsByGroup() {
Map<String, SortedSet<String>> groups = new HashMap<String, SortedSet<String>>(32);
for (FrequencyStat stat : _frequencyStats.values()) {
String gname = stat.getGroupName();
SortedSet<String> names = groups.get(gname);
if (names == null) {
names = new TreeSet<String>(Collator.getInstance());
groups.put(gname, names);
}
names.add(stat.getName());
}
for (RateStat stat : _rateStats.values()) {
String gname = stat.getGroupName();
SortedSet<String> names = groups.get(gname);
if (names == null) {
names = new TreeSet<String>(Collator.getInstance());
groups.put(gname, names);
}
names.add(stat.getName());
}
return groups;
}
public String getStatFilter() { return _context.getProperty(PROP_STAT_FILTER); }
public String getStatFile() { return _context.getProperty(PROP_STAT_FILE, DEFAULT_STAT_FILE); }
/**
* Save memory by not creating stats unless they are required for router operation.
* For backward compatibility of any external clients, always returns false if not in router context.
*
* @param statName ignored
* @return true if the stat should be ignored.
*/
public boolean ignoreStat(String statName) {
return _context.isRouterContext() && !_context.getBooleanProperty(PROP_STAT_FULL);
}
/**
* Serializes all Frequencies and Rates to the provided OutputStream
* @param out to write to
* @param prefix to use when serializing
* @throws IOException if something goes wrong
* @since 0.9.23
*/
public void store(OutputStream out, String prefix) throws IOException {
for (FrequencyStat fs : _frequencyStats.values())
fs.store(out, prefix);
for (RateStat rs : _rateStats.values())
rs.store(out,prefix);
}
}