diff --git a/core/java/src/net/i2p/stat/Frequency.java b/core/java/src/net/i2p/stat/Frequency.java
index 6be7dc3c9..facde2fc0 100644
--- a/core/java/src/net/i2p/stat/Frequency.java
+++ b/core/java/src/net/i2p/stat/Frequency.java
@@ -1,30 +1,41 @@
package net.i2p.stat;
/**
- * Manage the calculation of a moving event frequency over a certain period.
+ * Manage the calculation of a moving average event frequency over a certain period.
*
+ * This provides lifetime, and rolling average, frequency counts.
+ * Unlike Rate, it does not support "bucketed" averages.
+ * There is no tracking of the event frequency in the current or last bucket.
+ * There are no buckets at all.
+ *
+ * Depending on what you want, a rolling average might be better than buckets.
+ * Or not.
*/
public class Frequency {
private double _avgInterval;
private double _minAverageInterval;
- private long _period;
+ private final long _period;
private long _lastEvent;
- private long _start = now();
- private long _count = 0;
+ private final long _start = now();
+ private long _count;
private final Object _lock = this; // new Object(); // in case we want to do fancy sync later
+ /** @param period ms */
public Frequency(long period) {
- setPeriod(period);
+ _period = period;
+ _avgInterval = period + 1;
+ _minAverageInterval = _avgInterval;
}
- /** how long is this frequency averaged over? */
+ /** how long is this frequency averaged over? (ms) */
public long getPeriod() {
- synchronized (_lock) {
return _period;
- }
}
- /** when did the last event occur? */
+ /**
+ * when did the last event occur?
+ * @deprecated unused
+ */
public long getLastEvent() {
synchronized (_lock) {
return _lastEvent;
@@ -34,7 +45,7 @@ public class Frequency {
/**
* on average over the last $period, after how many milliseconds are events coming in,
* as calculated during the last event occurrence?
- *
+ * @return milliseconds; returns period + 1 if no events in previous period
*/
public double getAverageInterval() {
synchronized (_lock) {
@@ -42,14 +53,21 @@ public class Frequency {
}
}
- /** what is the lowest average interval (aka most frequent) we have seen? */
+ /**
+ * what is the lowest average interval (aka most frequent) we have seen? (ms)
+ * @return milliseconds; returns period + 1 if no events in previous period
+ * @deprecated unused
+ */
public double getMinAverageInterval() {
synchronized (_lock) {
return _minAverageInterval;
}
}
- /** calculate how many events would occur in a period given the current average */
+ /**
+ * Calculate how many events would occur in a period given the current (rolling) average.
+ * Use getStrictAverageInterval() for the real lifetime average.
+ */
public double getAverageEventsPerPeriod() {
synchronized (_lock) {
if (_avgInterval > 0) return _period / _avgInterval;
@@ -58,20 +76,26 @@ public class Frequency {
}
}
- /** calculate how many events would occur in a period given the maximum average */
+ /**
+ * Calculate how many events would occur in a period given the maximum rolling average.
+ * Use getStrictAverageEventsPerPeriod() for the real lifetime average.
+ */
public double getMaxAverageEventsPerPeriod() {
synchronized (_lock) {
- if (_minAverageInterval > 0) return _period / _minAverageInterval;
-
+ if (_minAverageInterval > 0 && _minAverageInterval <= _period) return _period / _minAverageInterval;
+
return 0;
}
}
- /** over the lifetime of this stat, without any decay or weighting, what was the average interval between events? */
+ /**
+ * Over the lifetime of this stat, without any decay or weighting, what was the average interval between events? (ms)
+ * @return milliseconds; returns Double.MAX_VALUE if no events ever
+ */
public double getStrictAverageInterval() {
synchronized (_lock) {
long duration = now() - _start;
- if ((duration <= 0) || (_count <= 0)) return 0;
+ if ((duration <= 0) || (_count <= 0)) return Double.MAX_VALUE;
return duration / (double) _count;
}
@@ -80,11 +104,8 @@ public class Frequency {
/** using the strict average interval, how many events occur within an average period? */
public double getStrictAverageEventsPerPeriod() {
double avgInterval = getStrictAverageInterval();
- synchronized (_lock) {
- if (avgInterval > 0) return _period / avgInterval;
-
- return 0;
- }
+ if (avgInterval > 0) return _period / avgInterval;
+ return 0;
}
/** how many events have occurred within the lifetime of this stat? */
@@ -115,18 +136,23 @@ public class Frequency {
*/
private void recalculate(boolean eventOccurred) {
synchronized (_lock) {
+ // This calculates something of a rolling average interval.
long now = now();
long interval = now - _lastEvent;
- if (interval >= _period)
- interval = _period - 1;
+ if (interval > _period)
+ interval = _period;
else if (interval <= 0) interval = 1;
- double oldWeight = 1 - (interval / (float) _period);
- double newWeight = (interval / (float) _period);
-
- double oldInterval = _avgInterval * oldWeight;
- double newInterval = interval * newWeight;
- _avgInterval = oldInterval + newInterval;
+ if (interval >= _period && !eventOccurred) {
+ // ensure getAverageEventsPerPeriod() will return 0
+ _avgInterval = _period + 1;
+ } else {
+ double oldWeight = 1 - (interval / (float) _period);
+ double newWeight = (interval / (float) _period);
+ double oldInterval = _avgInterval * oldWeight;
+ double newInterval = interval * newWeight;
+ _avgInterval = oldInterval + newInterval;
+ }
if ((_avgInterval < _minAverageInterval) || (_minAverageInterval <= 0)) _minAverageInterval = _avgInterval;
@@ -137,30 +163,6 @@ public class Frequency {
}
}
- private void setPeriod(long milliseconds) {
- synchronized (_lock) {
- _period = milliseconds;
- }
- }
-
- private void setLastEvent(long when) {
- synchronized (_lock) {
- _lastEvent = when;
- }
- }
-
- private void setAverageInterval(double msInterval) {
- synchronized (_lock) {
- _avgInterval = msInterval;
- }
- }
-
- private void setMinAverageInterval(double minAverageInterval) {
- synchronized (_lock) {
- _minAverageInterval = minAverageInterval;
- }
- }
-
private final static long now() {
return System.currentTimeMillis();
}
diff --git a/core/java/src/net/i2p/stat/FrequencyStat.java b/core/java/src/net/i2p/stat/FrequencyStat.java
index 994d26807..dfa7bcfe6 100644
--- a/core/java/src/net/i2p/stat/FrequencyStat.java
+++ b/core/java/src/net/i2p/stat/FrequencyStat.java
@@ -3,13 +3,13 @@ package net.i2p.stat;
/** coordinate an event frequency over various periods */
public class FrequencyStat {
/** unique name of the statistic */
- private String _statName;
+ private final String _statName;
/** grouping under which the stat is kept */
- private String _groupName;
+ private final String _groupName;
/** describe the stat */
- private String _description;
+ private final String _description;
/** actual frequency objects for this statistic */
- private Frequency _frequencies[];
+ private final Frequency _frequencies[];
public FrequencyStat(String name, String description, String group, long periods[]) {
_statName = name;
@@ -26,10 +26,12 @@ public class FrequencyStat {
_frequencies[i].eventOccurred();
}
- /** coalesce all the stats */
+ /**
+ * coalesce all the stats
+ */
public void coalesceStats() {
- //for (int i = 0; i < _frequencies.length; i++)
- // _frequencies[i].coalesceStats();
+ for (int i = 0; i < _frequencies.length; i++)
+ _frequencies[i].recalculate();
}
public String getName() {
@@ -58,9 +60,37 @@ public class FrequencyStat {
return null;
}
- /* FIXME missing equals() method FIXME */
+ /**
+ * @return lifetime event count
+ * @since 0.8.2
+ */
+ public long getEventCount() {
+ if ( (_frequencies == null) || (_frequencies.length <= 0) ) return 0;
+ return _frequencies[0].getEventCount();
+ }
+
+ /**
+ * @return lifetime average frequency in millisedonds, i.e. the average time between events, or Long.MAX_VALUE if no events ever
+ * @since 0.8.2
+ */
+ public long getFrequency() {
+ if ( (_frequencies == null) || (_frequencies.length <= 0) ) return Long.MAX_VALUE;
+ double d = _frequencies[0].getStrictAverageInterval();
+ if (d > _frequencies[0].getPeriod())
+ return Long.MAX_VALUE;
+ return Math.round(d);
+ }
+
@Override
public int hashCode() {
return _statName.hashCode();
}
+
+ /** @since 0.8.2 */
+ @Override
+ public boolean equals(Object obj) {
+ if ((obj == null) || (obj.getClass() != FrequencyStat.class)) return false;
+ return _statName.equals(((FrequencyStat)obj)._statName);
+ }
+
}
diff --git a/core/java/src/net/i2p/stat/Rate.java b/core/java/src/net/i2p/stat/Rate.java
index 25305bce7..fd0fae49f 100644
--- a/core/java/src/net/i2p/stat/Rate.java
+++ b/core/java/src/net/i2p/stat/Rate.java
@@ -10,6 +10,7 @@ import net.i2p.util.Log;
* average value over a period, the number of events in that period, the maximum number
* of events (using the interval between events), and lifetime data.
*
+ * If value is always a constant, you should be using Frequency instead.
*/
public class Rate {
private final static Log _log = new Log(Rate.class);
@@ -70,7 +71,10 @@ public class Rate {
return _extremeTotalValue;
}
- /** when the max(totalValue) was achieved, how many events occurred in that period? */
+ /**
+ * when the max(totalValue) was achieved, how many events occurred in that period?
+ * Note that this is not necesarily the highest event count; that isn't tracked.
+ */
public long getExtremeEventCount() {
return _extremeEventCount;
}
@@ -144,13 +148,50 @@ public class Rate {
load(props, prefix, treatAsCurrent);
}
- /** accrue the data in the current period as an instantaneous event */
+ /**
+ * Accrue the data in the current period as an instantaneous event.
+ * If value is always a constant, you should be using Frequency instead.
+ * If you always use this call, eventDuration is always zero,
+ * and the various get*Saturation*() and get*EventTime() methods will return zero.
+ */
public void addData(long value) {
- addData(value, 0);
+ synchronized (_lock) {
+ _currentTotalValue += value;
+ _currentEventCount++;
+ _lifetimeTotalValue += value;
+ _lifetimeEventCount++;
+ }
}
/**
* Accrue the data in the current period as if the event took the specified amount of time
+ * If value is always a constant, you should be using Frequency instead.
+ * If eventDuration is nonzero, then the various get*Saturation*() and get*EventTime()
+ * methods will also return nonzero.
+ *
+ *
+ * There are at least 4 possible strategies for eventDuration:
+ *
+ * 1) eventDuration is always zero.
+ * The various get*Saturation*() and get*EventTime() methods will return zero.
+ *
+ * 2) Each eventDuration is relatively small, and reflects processing time.
+ * This is probably the original meaning of "saturation", as it allows you
+ * to track how much time is spent gathering the stats.
+ * get*EventTime() will be close to 0.
+ * get*EventSaturation() will return values close to 0,
+ * get*SaturationLimit() will return adjusted values for the totals.
+ *
+ * 3) The total of the eventDurations are approximately equal to total elapsed time.
+ * get*EventTime() will be close to the period.
+ * get*EventSaturation() will return values close to 1,
+ * get*SaturationLimit() will return adjusted values for the totals.
+ *
+ * 4) Each eventDuration is not a duration at all, but someother independent data.
+ * get*EventTime() may be used to retrieve the data.
+ * get*EventSaturation() are probably useless.
+ * get*SaturationLimit() are probably useless.
+ *
*
* @param value value to accrue in the current period
* @param eventDuration how long it took to accrue this data (set to 0 if it was instantaneous)
@@ -195,7 +236,7 @@ public class Rate {
correctedTotalValue = _currentTotalValue *
(_lastEventCount / (double) _currentEventCount);
- if (_lastTotalValue > _extremeTotalValue) {
+ if (_lastTotalValue >= _extremeTotalValue) { // get the most recent if identical
_extremeTotalValue = _lastTotalValue;
_extremeEventCount = _lastEventCount;
_extremeTotalEventTime = _lastTotalEventTime;
@@ -220,7 +261,10 @@ public class Rate {
return 0.0D;
}
- /** what was the average value across the events in the most active period? */
+ /**
+ * During the extreme period (i.e. the period with the highest total value),
+ * what was the average value?
+ */
public double getExtremeAverageValue() {
if ((_extremeTotalValue != 0) && (_extremeEventCount > 0))
return _extremeTotalValue / _extremeEventCount;
@@ -240,7 +284,7 @@ public class Rate {
* During the last period, how much of the time was spent actually processing events in proportion
* to how many events could have occurred if there were no intervals?
*
- * @return percentage, or 0 if event times aren't used
+ * @return ratio, or 0 if event times aren't used
*/
public double getLastEventSaturation() {
if ((_lastEventCount > 0) && (_lastTotalEventTime > 0)) {
@@ -256,10 +300,11 @@ public class Rate {
}
/**
- * During the extreme period, how much of the time was spent actually processing events
+ * During the extreme period (i.e. the period with the highest total value),
+ * how much of the time was spent actually processing events
* in proportion to how many events could have occurred if there were no intervals?
*
- * @return percentage, or 0 if the statistic doesn't use event times
+ * @return ratio, or 0 if the statistic doesn't use event times
*/
public double getExtremeEventSaturation() {
if ((_extremeEventCount > 0) && (_extremeTotalEventTime > 0)) {
@@ -274,7 +319,7 @@ public class Rate {
* During the lifetime of this stat, how much of the time was spent actually processing events in proportion
* to how many events could have occurred if there were no intervals?
*
- * @return percentage, or 0 if event times aren't used
+ * @return ratio, or 0 if event times aren't used
*/
public double getLifetimeEventSaturation() {
if ((_lastEventCount > 0) && (_lifetimeTotalEventTime > 0)) {
@@ -311,7 +356,8 @@ public class Rate {
}
/**
- * using the extreme period's rate, what is the total value that could have been
+ * During the extreme period (i.e. the period with the highest total value),
+ * what is the total value that could have been
* sent if events were constant?
*
* @return event total at saturation, or 0 if no event times are measured
@@ -328,8 +374,9 @@ public class Rate {
}
/**
- * How large was the last period's value as compared to the largest period ever?
- *
+ * What was the total value, compared to the total value in
+ * the extreme period (i.e. the period with the highest total value),
+ * Warning- returns ratio, not percentage (i.e. it is not multiplied by 100 here)
*/
public double getPercentageOfExtremeValue() {
if ((_lastTotalValue != 0) && (_extremeTotalValue != 0))
@@ -340,7 +387,7 @@ public class Rate {
/**
* How large was the last period's value as compared to the lifetime average value?
- *
+ * Warning- returns ratio, not percentage (i.e. it is not multiplied by 100 here)
*/
public double getPercentageOfLifetimeValue() {
if ((_lastTotalValue != 0) && (_lifetimeTotalValue != 0)) {
@@ -500,6 +547,7 @@ public class Rate {
return System.currentTimeMillis(); //Clock.getInstance().now();
}
+/******
public static void main(String args[]) {
Rate rate = new Rate(1000);
for (int i = 0; i < 50; i++) {
@@ -532,4 +580,5 @@ public class Rate {
} catch (InterruptedException ie) { // nop
}
}
+******/
}
diff --git a/core/java/src/net/i2p/stat/StatManager.java b/core/java/src/net/i2p/stat/StatManager.java
index 0393d577a..cc47e0041 100644
--- a/core/java/src/net/i2p/stat/StatManager.java
+++ b/core/java/src/net/i2p/stat/StatManager.java
@@ -140,12 +140,17 @@ public class StatManager {
if (stat != null) stat.addData(data, eventDuration);
}
+ private int coalesceCounter;
+ /** every this many minutes for frequencies */
+ private static final int FREQ_COALESCE_RATE = 9;
+
public void coalesceStats() {
- synchronized (_frequencyStats) {
- for (Iterator
iter = _frequencyStats.values().iterator(); iter.hasNext();) {
- FrequencyStat stat = iter.next();
- if (stat != null) {
- stat.coalesceStats();
+ if (++coalesceCounter % FREQ_COALESCE_RATE == 0) {
+ synchronized (_frequencyStats) {
+ for (FrequencyStat stat : _frequencyStats.values()) {
+ if (stat != null) {
+ stat.coalesceStats();
+ }
}
}
}
diff --git a/router/java/src/net/i2p/router/StatisticsManager.java b/router/java/src/net/i2p/router/StatisticsManager.java
index 03afd73b0..5a00f154c 100644
--- a/router/java/src/net/i2p/router/StatisticsManager.java
+++ b/router/java/src/net/i2p/router/StatisticsManager.java
@@ -280,7 +280,7 @@ public class StatisticsManager implements Service {
stats.setProperty("stat_bandwidthReceiveBps.60m", str);
}
- private String getPeriod(Rate rate) { return DataHelper.formatDuration(rate.getPeriod()); }
+ private static String getPeriod(Rate rate) { return DataHelper.formatDuration(rate.getPeriod()); }
private final String num(double num) {
if (num < 0) num = 0;