"""Frequency — rolling average event frequency tracker. Ported from net.i2p.stat.Frequency. """ import threading import time as _time def _now_ms() -> int: return int(_time.time() * 1000) class Frequency: """Track the average interval between events using a rolling weighted average.""" def __init__(self, period: int) -> None: if period <= 0: raise ValueError("period must be positive") self._period = period self._lock = threading.Lock() now = _now_ms() self._last_event: int = now self._start: int = now self._count: int = 0 self._avg_interval: float = float(period + 1) self._min_avg_interval: float = float(period + 1) @property def period(self) -> int: return self._period def event_occurred(self) -> None: """Record that an event has occurred and update rolling average.""" now = _now_ms() with self._lock: interval = now - self._last_event self._last_event = now self._count += 1 if interval >= self._period: # No recent events — reset to "no events" self._avg_interval = float(self._period + 1) else: # Weighted rolling average old_weight = 1.0 - (interval / self._period) new_weight = interval / self._period self._avg_interval = ( old_weight * self._avg_interval + new_weight * interval ) if self._avg_interval < self._min_avg_interval: self._min_avg_interval = self._avg_interval def recalculate(self) -> None: """Recalculate rolling average without recording an event.""" now = _now_ms() with self._lock: interval = now - self._last_event if interval >= self._period: self._avg_interval = float(self._period + 1) def get_average_interval(self) -> float: with self._lock: return self._avg_interval def get_min_average_interval(self) -> float: with self._lock: return self._min_avg_interval def get_average_events_per_period(self) -> float: with self._lock: if self._avg_interval <= 0: return 0.0 return self._period / self._avg_interval def get_max_average_events_per_period(self) -> float: with self._lock: if self._min_avg_interval <= 0: return 0.0 return self._period / self._min_avg_interval def get_strict_average_interval(self) -> float: """Lifetime average: (now - start) / event_count.""" now = _now_ms() with self._lock: if self._count <= 0: return float(self._period + 1) return (now - self._start) / self._count def get_event_count(self) -> int: with self._lock: return self._count