feat(traefik): a 4xx share is worth a look only once it is a lot of 4xx (#6105)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m29s
CI / integration (push) Successful in 1m42s
CI / publish (push) Successful in 1m9s

Three 404s on a quiet router read 7.5% and were raised as an anomaly; most of
the 4xx anomalies on review stood for 3 to 7 errors in 15 minutes. A new
Settings → Thresholds minimum, traefik.threshold.min_4xx (default 20, more
than one a minute held for the window), applies to both the router's grade
on its page and to anomaly detection, as min_5xx does for 5xx.

learn_normal takes `fewest`: for a share gated on a per-second rate, the
fewest events (rate × window × share) it must stand for to be judged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 16:02:17 -04:00
co-authored by Claude Opus 5.5
parent 4639ac6e52
commit b75b883326
11 changed files with 81 additions and 15 deletions
+9 -2
View File
@@ -38,11 +38,18 @@ MARKS = (NORMAL, REAL)
def _gate_open(learned, key: tuple[str, str, str], reported: dict) -> bool:
"""Whether `key` may be judged: ungated, or its resource's gate metric
averaged at least the gate's least over the window."""
averaged at least the gate's least over the window, and the share stands
for at least `fewest` events in it."""
if learned.gate is None:
return True
metric, least = learned.gate
return reported.get((key[0], key[1], metric), 0.0) >= (least() if callable(least) else least)
rate = reported.get((key[0], key[1], metric), 0.0)
if rate < (least() if callable(least) else least):
return False
if learned.fewest is None:
return True
fewest = learned.fewest() if callable(learned.fewest) else learned.fewest
return rate * WINDOW.total_seconds() * reported.get(key, 0.0) / 100.0 >= fewest
def departs(level: float, norm: Norm, floor: float) -> bool: