@@ -46,6 +46,10 @@ class ErrorBudget:
4646
4747 Error Budget = 1 - SLO target
4848 Burn Rate = actual error rate / allowed error rate
49+
50+ Events are stored in a bounded ``deque(maxlen=max_events)`` to
51+ prevent unbounded memory growth in long-running SLOs. When the
52+ buffer is full, the oldest events are silently evicted on append.
4953 """
5054
5155 total : float = 0.0 # Set from SLO target
@@ -58,6 +62,11 @@ class ErrorBudget:
5862 _events : collections .deque = field (default_factory = lambda : collections .deque (maxlen = 100_000 ))
5963 _monotonic_offset : float = field (default_factory = lambda : time .time () - time .monotonic ())
6064
65+ def __post_init__ (self ) -> None :
66+ """Ensure _events is a bounded deque with the correct maxlen."""
67+ if not isinstance (self ._events , collections .deque ) or self ._events .maxlen != self .max_events :
68+ self ._events = collections .deque (self ._events , maxlen = self .max_events )
69+
6170 @property
6271 def remaining (self ) -> float :
6372 """Remaining error budget as a fraction (0.0 to 1.0)."""
@@ -76,11 +85,24 @@ def is_exhausted(self) -> bool:
7685 return self .consumed >= self .total
7786
7887 def record_event (self , good : bool ) -> None :
79- """Record a good or bad event against the budget."""
88+ """Record a good or bad event against the budget.
89+
90+ Events are stored in a bounded deque. When ``max_events`` is
91+ reached, the oldest event is silently evicted.
92+ """
8093 if not good :
8194 self .consumed += 1.0
8295 self ._events .append ({"good" : good , "timestamp" : time .monotonic ()})
8396
97+ def clear_events (self ) -> None :
98+ """Clear all recorded events (does **not** reset ``consumed``)."""
99+ self ._events .clear ()
100+
101+ @property
102+ def event_count (self ) -> int :
103+ """Number of events currently in the buffer."""
104+ return len (self ._events )
105+
84106 def burn_rate (self , window_seconds : int | None = None ) -> float :
85107 """Calculate current burn rate within a time window.
86108
0 commit comments