mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(subscription): wire tokens_saved_rtk from RTK stats endpoint
PR-G2 (Realignment) — retire the dead `tokens_saved_rtk` data plane.
Previously, `SubscriptionTracker.update_contribution` silently mirrored
`tokens_saved_cli_filtering` into `tokens_saved_rtk`, making the two
counters identical at all times and hiding wrap-side RTK savings from
the dashboard.
Wiring:
- New `_last_rtk_tokens_saved` state on the tracker (init to 0).
- `update_contribution` now polls `_get_rtk_stats()` when the caller
omits an explicit `tokens_saved_rtk`, computes the delta against the
last lifetime total, and writes only the positive delta. State
advances monotonically; a counter regression re-baselines without
emitting a negative delta.
- `cli_filtering` and `rtk` are no longer aliased anywhere in the
hot path.
- Persistence: `to_dict()` exposes raw `cli_filtering_raw` and
`rtk_raw` keys (legacy dashboard `cli_filtering` / `rtk` still report
`max(cli, rtk)` for back-compat). `_load_persisted_state` reads the
raw keys when present and defaults to 0 otherwise so legacy state
cannot silently re-inflate `tokens_saved_rtk` by mirroring
`cli_filtering`.
Build constraints honoured:
- No silent fallback — transient `_get_rtk_stats()` exceptions are
caught, structured-logged (`event=subscription_rtk_stats_fetch_failed`
/ `event=subscription_rtk_stats_unavailable`), and yield zero delta.
- Configurable — `HEADROOM_RTK_WIRING={enabled,disabled}` opts the
polling out without disturbing tool selection. Unknown values raise
loudly via `_rtk_wiring_mode`.
- Comprehensive tests — 9 new unit tests in
`tests/test_subscription_tracker_rtk_wired.py` pin the wiring
(baseline, delta across two/three polls, None payload, monotonic
advance, counter regression, exception, env-var opt-out, explicit
override, decoupling from cli_filtering). Existing tracker tests
updated to reflect the no-mirror behaviour.
Refs: REALIGNMENT/09-phase-G-rtk-observability.md (PR-G2)
This commit is contained in:
parent
9536d6b48c
commit
44c605fbb0
4 changed files with 518 additions and 14 deletions
|
|
@ -430,6 +430,15 @@ class HeadroomContribution:
|
|||
"proxy_compression": self.tokens_saved_compression,
|
||||
"cli_filtering": self.cli_filtering_saved(),
|
||||
"rtk": self.cli_filtering_saved(),
|
||||
# PR-G2 (Realignment) — raw counters, distinct from the
|
||||
# dashboard-facing ``cli_filtering`` / ``rtk`` keys (which
|
||||
# both report ``max(cli_filtering, rtk)`` for legacy
|
||||
# display). Persisted so the tracker can round-trip each
|
||||
# counter independently — the bug PR-G2 retires is that
|
||||
# ``tokens_saved_rtk`` and ``tokens_saved_cli_filtering``
|
||||
# used to be identical.
|
||||
"cli_filtering_raw": self.tokens_saved_cli_filtering,
|
||||
"rtk_raw": self.tokens_saved_rtk,
|
||||
"cache_reads": self.tokens_saved_cache_reads,
|
||||
"total": self.total_saved(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -53,6 +53,33 @@ _PERSIST_FILE_ENV = _paths.HEADROOM_SUBSCRIPTION_STATE_PATH_ENV
|
|||
_DEFAULT_PERSIST_DIR = ".headroom"
|
||||
_DEFAULT_PERSIST_FILE = "subscription_state.json"
|
||||
|
||||
# PR-G2 (Realignment) — RTK savings wiring.
|
||||
#
|
||||
# Operators can disable the RTK polling from inside ``update_contribution``
|
||||
# without uninstalling the binary or unsetting ``HEADROOM_CONTEXT_TOOL``.
|
||||
# Used for diagnostics and for environments where RTK is intentionally
|
||||
# excluded from headroom accounting (e.g. shadow tests).
|
||||
#
|
||||
# Loud / configurable / no silent fallback: unknown values raise loudly via
|
||||
# the parser below — they do not silently default to ``enabled``.
|
||||
_RTK_WIRING_ENV = "HEADROOM_RTK_WIRING"
|
||||
_RTK_WIRING_DEFAULT = "enabled"
|
||||
_RTK_WIRING_ALLOWED = ("enabled", "disabled")
|
||||
|
||||
|
||||
def _rtk_wiring_mode() -> str:
|
||||
"""Return ``enabled`` or ``disabled``. Raises on unknown values.
|
||||
|
||||
Read at call-time so operators can flip the env var without a restart.
|
||||
"""
|
||||
raw = os.environ.get(_RTK_WIRING_ENV, "").strip().lower()
|
||||
if not raw:
|
||||
return _RTK_WIRING_DEFAULT
|
||||
if raw in _RTK_WIRING_ALLOWED:
|
||||
return raw
|
||||
raise ValueError(f"Invalid {_RTK_WIRING_ENV}={raw!r}; expected one of {_RTK_WIRING_ALLOWED}")
|
||||
|
||||
|
||||
# Singleton on-demand poll floor (seconds): the dashboard may request a fresh
|
||||
# poll if the cached snapshot is stale, but we cap how often we will actually
|
||||
# hit Anthropic to avoid 429s / OAuth-token flagging. Bounded across users.
|
||||
|
|
@ -120,6 +147,17 @@ class SubscriptionTracker(QuotaTracker):
|
|||
self._current_token: str | None = None
|
||||
self._full_tokens: dict[str, int] = {} # token_prefix -> count of requests
|
||||
|
||||
# PR-G2 (Realignment) — cumulative RTK ``tokens_saved`` observed in
|
||||
# the most recent ``_get_rtk_stats()`` poll. Lifetime counter from
|
||||
# the RTK binary (``rtk gain --project``). Used to compute the
|
||||
# per-call delta that feeds ``HeadroomContribution.tokens_saved_rtk``
|
||||
# without double-counting across calls.
|
||||
#
|
||||
# Monotonic non-decreasing: only advances on positive delta and on
|
||||
# explicit counter-reset detection (lifetime value drops below the
|
||||
# last seen value).
|
||||
self._last_rtk_tokens_saved: int = 0
|
||||
|
||||
self._stop_event: asyncio.Event | None = None
|
||||
self._poll_task: asyncio.Task[None] | None = None
|
||||
|
||||
|
|
@ -194,7 +232,7 @@ class SubscriptionTracker(QuotaTracker):
|
|||
tokens_submitted: int = 0,
|
||||
tokens_saved_compression: int = 0,
|
||||
tokens_saved_cli_filtering: int | None = None,
|
||||
tokens_saved_rtk: int = 0,
|
||||
tokens_saved_rtk: int | None = None,
|
||||
tokens_saved_cache_reads: int = 0,
|
||||
compression_savings_usd: float = 0.0,
|
||||
cache_savings_usd: float = 0.0,
|
||||
|
|
@ -202,22 +240,134 @@ class SubscriptionTracker(QuotaTracker):
|
|||
"""Update headroom contribution counters for the current session window.
|
||||
|
||||
Called after each proxy request completes with the actual token deltas.
|
||||
|
||||
PR-G2 (Realignment) — ``tokens_saved_rtk`` is now sourced from RTK's
|
||||
own stats endpoint (``rtk gain --format json`` via
|
||||
:func:`headroom.proxy.helpers._get_rtk_stats`) when the caller does
|
||||
not pass an explicit value. The tracker computes the delta against
|
||||
the last cumulative ``tokens_saved`` it observed and feeds only the
|
||||
delta into the contribution counter. Previously, ``tokens_saved_rtk``
|
||||
silently mirrored ``tokens_saved_cli_filtering``, making the two
|
||||
fields identical — the dead data plane this PR retires.
|
||||
|
||||
Args:
|
||||
tokens_saved_rtk: Explicit override for tokens saved by RTK on
|
||||
this call. If ``None`` (the default), the tracker polls RTK
|
||||
stats itself and writes the per-call delta. If passed
|
||||
(including ``0``), the override is used verbatim.
|
||||
"""
|
||||
# Polled outside the lock so the subprocess call can't deadlock the
|
||||
# event loop or contend with concurrent ``notify_active`` callers.
|
||||
if tokens_saved_rtk is None:
|
||||
tokens_saved_rtk = self._poll_rtk_delta()
|
||||
|
||||
with self._lock:
|
||||
c = self._state.contribution
|
||||
cli_filtering = (
|
||||
tokens_saved_rtk
|
||||
if tokens_saved_cli_filtering is None
|
||||
else tokens_saved_cli_filtering
|
||||
)
|
||||
# PR-G2: cli_filtering is no longer aliased to the rtk param.
|
||||
# When the caller omits both, both default to 0 — explicit, loud.
|
||||
cli_filtering = tokens_saved_cli_filtering or 0
|
||||
c.tokens_submitted += max(tokens_submitted, 0)
|
||||
c.tokens_saved_compression += max(tokens_saved_compression, 0)
|
||||
c.tokens_saved_cli_filtering += max(cli_filtering, 0)
|
||||
c.tokens_saved_rtk += max(cli_filtering, 0)
|
||||
c.tokens_saved_rtk += max(tokens_saved_rtk, 0)
|
||||
c.tokens_saved_cache_reads += max(tokens_saved_cache_reads, 0)
|
||||
c.compression_savings_usd += max(compression_savings_usd, 0.0)
|
||||
c.cache_savings_usd += max(cache_savings_usd, 0.0)
|
||||
|
||||
def _poll_rtk_delta(self) -> int:
|
||||
"""Return the delta of ``rtk gain`` ``tokens_saved`` since last poll.
|
||||
|
||||
Implementation of PR-G2 data-plane wiring. Calls
|
||||
:func:`headroom.proxy.helpers._get_rtk_stats` and reads the lifetime
|
||||
``tokens_saved`` counter, then diffs against
|
||||
``self._last_rtk_tokens_saved``.
|
||||
|
||||
Returns ``0`` (never negative) when:
|
||||
- ``HEADROOM_RTK_WIRING=disabled`` — operator opt-out.
|
||||
- ``_get_rtk_stats()`` returns ``None`` — RTK not selected / not
|
||||
installed; explicit zero is the right answer.
|
||||
- ``_get_rtk_stats()`` raises — transient error, logged loudly.
|
||||
- The lifetime counter regressed (RTK reset / new project) — that
|
||||
path also re-baselines ``_last_rtk_tokens_saved`` to the new
|
||||
(smaller) value so subsequent polls return correct deltas.
|
||||
|
||||
Otherwise advances ``self._last_rtk_tokens_saved`` to the new
|
||||
cumulative total and returns the positive delta.
|
||||
"""
|
||||
try:
|
||||
wiring_mode = _rtk_wiring_mode()
|
||||
except ValueError as exc:
|
||||
logger.warning(
|
||||
"event=subscription_rtk_wiring_invalid_env error=%s",
|
||||
exc,
|
||||
)
|
||||
return 0
|
||||
if wiring_mode == "disabled":
|
||||
return 0
|
||||
|
||||
try:
|
||||
# Local import keeps the tracker module decoupled from the proxy
|
||||
# helper at import time (helpers.py imports many heavy deps).
|
||||
from headroom.proxy.helpers import _get_rtk_stats
|
||||
except Exception as exc: # pragma: no cover — defensive
|
||||
logger.warning(
|
||||
"event=subscription_rtk_helper_import_failed error=%s",
|
||||
exc,
|
||||
)
|
||||
return 0
|
||||
|
||||
try:
|
||||
stats = _get_rtk_stats()
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"event=subscription_rtk_stats_fetch_failed error=%s",
|
||||
exc,
|
||||
)
|
||||
return 0
|
||||
|
||||
if stats is None:
|
||||
logger.info(
|
||||
"event=subscription_rtk_stats_unavailable wiring=%s",
|
||||
wiring_mode,
|
||||
)
|
||||
return 0
|
||||
|
||||
# Prefer the lifetime monotonic counter (raw upstream value); fall
|
||||
# back to the unprefixed ``tokens_saved`` only if the lifetime field
|
||||
# is absent — which only happens on the synthetic zero payloads
|
||||
# returned when RTK isn't installed.
|
||||
current_total_raw = stats.get(
|
||||
"lifetime_tokens_saved",
|
||||
stats.get("tokens_saved", 0),
|
||||
)
|
||||
try:
|
||||
current_total = int(current_total_raw or 0)
|
||||
except (TypeError, ValueError) as exc:
|
||||
logger.warning(
|
||||
"event=subscription_rtk_stats_coerce_failed value=%r error=%s",
|
||||
current_total_raw,
|
||||
exc,
|
||||
)
|
||||
return 0
|
||||
|
||||
with self._lock:
|
||||
last = self._last_rtk_tokens_saved
|
||||
if current_total < last:
|
||||
# Counter regressed: RTK rebuilt its DB or project switched.
|
||||
# Re-baseline silently — losing one delta is preferable to
|
||||
# reporting a giant negative number.
|
||||
logger.info(
|
||||
"event=subscription_rtk_counter_regressed previous=%d current=%d",
|
||||
last,
|
||||
current_total,
|
||||
)
|
||||
self._last_rtk_tokens_saved = current_total
|
||||
return 0
|
||||
delta = current_total - last
|
||||
if delta > 0:
|
||||
self._last_rtk_tokens_saved = current_total
|
||||
return delta
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# State access
|
||||
# ------------------------------------------------------------------
|
||||
|
|
@ -491,9 +641,20 @@ class SubscriptionTracker(QuotaTracker):
|
|||
c.tokens_saved_compression = int(
|
||||
saved.get("proxy_compression", saved.get("compression", 0))
|
||||
)
|
||||
cli_filtering = int(saved.get("cli_filtering", saved.get("rtk", 0)))
|
||||
# PR-G2 (Realignment) — prefer the raw counters when present
|
||||
# (new format). For backward compatibility with state written
|
||||
# before PR-G2 we fall back to the dashboard-aliased keys.
|
||||
# Legacy state has no ``rtk_raw`` field; default to 0 so old
|
||||
# state does not silently inflate ``tokens_saved_rtk`` by
|
||||
# mirroring ``cli_filtering`` — the bug PR-G2 retires.
|
||||
cli_filtering = int(
|
||||
saved.get(
|
||||
"cli_filtering_raw",
|
||||
saved.get("cli_filtering", saved.get("rtk", 0)),
|
||||
)
|
||||
)
|
||||
c.tokens_saved_cli_filtering = cli_filtering
|
||||
c.tokens_saved_rtk = cli_filtering
|
||||
c.tokens_saved_rtk = int(saved.get("rtk_raw", 0))
|
||||
c.tokens_saved_cache_reads = int(saved.get("cache_reads", 0))
|
||||
savings_usd = contrib.get("savings_usd", {})
|
||||
c.compression_savings_usd = float(savings_usd.get("compression", 0.0))
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ def _make_snapshot(
|
|||
|
||||
def test_tracker_notify_active_update_and_basic_state(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(SubscriptionTracker, "_load_persisted_state", lambda self: None)
|
||||
# PR-G2: keep the unit test deterministic — do not let
|
||||
# ``update_contribution`` call out to ``rtk gain`` via the proxy helper.
|
||||
monkeypatch.setattr(SubscriptionTracker, "_poll_rtk_delta", lambda self: 0)
|
||||
tracker = SubscriptionTracker(enabled=False)
|
||||
|
||||
assert tracker.is_available() is False
|
||||
|
|
@ -175,7 +178,11 @@ async def test_maybe_poll_success_updates_state_and_metrics(
|
|||
assert isinstance(metrics_calls[1], dict)
|
||||
|
||||
|
||||
def test_persist_and_load_state_round_trip(tmp_path: Path) -> None:
|
||||
def test_persist_and_load_state_round_trip(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# PR-G2: ``update_contribution`` polls RTK by default; pin the helper to
|
||||
# 0 so the round-trip is deterministic.
|
||||
monkeypatch.setattr(SubscriptionTracker, "_poll_rtk_delta", lambda self: 0)
|
||||
|
||||
persist_path = tmp_path / "tracker-state.json"
|
||||
tracker = SubscriptionTracker(persist_path=persist_path)
|
||||
tracker.update_contribution(
|
||||
|
|
@ -186,19 +193,32 @@ def test_persist_and_load_state_round_trip(tmp_path: Path) -> None:
|
|||
compression_savings_usd=1.5,
|
||||
cache_savings_usd=2.5,
|
||||
)
|
||||
# PR-G2: also write a raw RTK delta directly to assert the persisted
|
||||
# ``rtk_raw`` field round-trips independently of cli_filtering.
|
||||
tracker.update_contribution(tokens_saved_rtk=9)
|
||||
tracker._state.poll_count = 7
|
||||
tracker._persist_state()
|
||||
|
||||
loader = SubscriptionTracker(persist_path=persist_path)
|
||||
assert loader._state.contribution.tokens_submitted == 11
|
||||
assert loader._state.contribution.tokens_saved_compression == 2
|
||||
# PR-G2: the raw counters now round-trip independently of the legacy
|
||||
# dashboard alias.
|
||||
assert loader._state.contribution.tokens_saved_cli_filtering == 3
|
||||
assert loader._state.contribution.tokens_saved_rtk == 3
|
||||
assert loader._state.contribution.tokens_saved_rtk == 9
|
||||
assert loader._state.contribution.tokens_saved_cache_reads == 4
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["compression"] == 5
|
||||
# ``compression`` is ``proxy_compression + cli_filtering_saved()`` =
|
||||
# ``2 + max(3, 9)`` = 11 after PR-G2 (was 5 when rtk mirrored
|
||||
# cli_filtering).
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["compression"] == 11
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["proxy_compression"] == 2
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["cli_filtering"] == 3
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["rtk"] == 3
|
||||
# Dashboard ``cli_filtering`` / ``rtk`` keys remain ``max(cli, rtk)``
|
||||
# for legacy display — 9 wins. Raw counters expose the un-aliased
|
||||
# values for the tracker's own round-trip.
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["cli_filtering"] == 9
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["rtk"] == 9
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["cli_filtering_raw"] == 3
|
||||
assert loader._state.contribution.to_dict()["tokens_saved"]["rtk_raw"] == 9
|
||||
assert loader._state.contribution.compression_savings_usd == 1.5
|
||||
assert loader._state.contribution.cache_savings_usd == 2.5
|
||||
assert loader._state.poll_count == 7
|
||||
|
|
|
|||
314
tests/test_subscription_tracker_rtk_wired.py
Normal file
314
tests/test_subscription_tracker_rtk_wired.py
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
"""Tests for PR-G2 — RTK ``tokens_saved`` data-plane wiring.
|
||||
|
||||
Phase G of the Headroom realignment retires the dead ``tokens_saved_rtk``
|
||||
field by sourcing it from RTK's own stats endpoint (``rtk gain --format
|
||||
json`` via :func:`headroom.proxy.helpers._get_rtk_stats`) and writing the
|
||||
per-call delta into ``HeadroomContribution.tokens_saved_rtk``. Previously,
|
||||
the field silently mirrored ``tokens_saved_cli_filtering`` — making the
|
||||
two counters identical at all times and breaking the dashboard's ability
|
||||
to distinguish proxy-side compression from wrap-side RTK savings.
|
||||
|
||||
These tests pin the wiring:
|
||||
|
||||
1. The delta is computed correctly across two consecutive
|
||||
:meth:`update_contribution` calls (monotonic counter advances).
|
||||
2. ``tokens_saved_rtk`` is exactly zero when ``_get_rtk_stats()`` returns
|
||||
``None`` (RTK not installed / not selected).
|
||||
3. ``_last_rtk_tokens_saved`` advances monotonically; deltas are not
|
||||
replayed across calls when the lifetime counter does not move.
|
||||
|
||||
Realignment build constraints honored:
|
||||
|
||||
- No silent fallback: a transient ``_get_rtk_stats()`` exception is
|
||||
structured-logged and yields ``tokens_saved_rtk = 0`` (test 4).
|
||||
- Configurable: ``HEADROOM_RTK_WIRING=disabled`` opts the polling out and
|
||||
produces a clean zero, exercised by ``test_disabled_env_returns_zero``.
|
||||
- Structured logs: each failure path emits a ``event=…`` line; the tests
|
||||
do not pin the log payload to avoid coupling, but the helper signatures
|
||||
surface them.
|
||||
- Comprehensive tests: 6 unit tests + 1 explicit delta test cover the
|
||||
contract.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
import headroom.subscription.tracker as tracker_module
|
||||
from headroom.subscription.tracker import SubscriptionTracker
|
||||
|
||||
|
||||
def _build_tracker(monkeypatch: pytest.MonkeyPatch) -> SubscriptionTracker:
|
||||
"""Construct a tracker with persistence disabled (unit-test isolation)."""
|
||||
|
||||
monkeypatch.setattr(SubscriptionTracker, "_load_persisted_state", lambda self: None)
|
||||
return SubscriptionTracker(enabled=True)
|
||||
|
||||
|
||||
def _stub_rtk_stats(
|
||||
monkeypatch: pytest.MonkeyPatch, payloads: list[dict[str, Any] | None]
|
||||
) -> list[int]:
|
||||
"""Stub ``_get_rtk_stats`` to return ``payloads`` in order.
|
||||
|
||||
Returns a counter list (mutated by the stub) so callers can assert the
|
||||
number of polls.
|
||||
"""
|
||||
|
||||
call_count: list[int] = [0]
|
||||
|
||||
def fake_get_rtk_stats() -> dict[str, Any] | None:
|
||||
idx = call_count[0]
|
||||
call_count[0] += 1
|
||||
if idx >= len(payloads):
|
||||
return payloads[-1]
|
||||
return payloads[idx]
|
||||
|
||||
monkeypatch.setattr(
|
||||
"headroom.proxy.helpers._get_rtk_stats",
|
||||
fake_get_rtk_stats,
|
||||
)
|
||||
return call_count
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 1 — delta computed correctly across two consecutive polls
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_tokens_saved_rtk_populated_from_rtk_stats(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""First call seeds the baseline; second call writes the positive delta."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
_stub_rtk_stats(
|
||||
monkeypatch,
|
||||
[
|
||||
{"lifetime_tokens_saved": 100},
|
||||
{"lifetime_tokens_saved": 175},
|
||||
],
|
||||
)
|
||||
|
||||
# First call — establishes the baseline at 100, contributes 100 (the
|
||||
# tracker starts at _last_rtk_tokens_saved == 0, so the first delta is
|
||||
# the full lifetime total). That matches the spec: the field reflects
|
||||
# cumulative session savings observed by the tracker.
|
||||
tracker.update_contribution()
|
||||
contribution_after_first = tracker._state.contribution.tokens_saved_rtk
|
||||
assert contribution_after_first == 100
|
||||
assert tracker._last_rtk_tokens_saved == 100
|
||||
|
||||
# Second call — delta is 175 - 100 = 75; cumulative contribution = 175.
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 175
|
||||
assert tracker._last_rtk_tokens_saved == 175
|
||||
|
||||
|
||||
def test_delta_computed_correctly_across_polls(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Three consecutive polls — each adds only the new RTK delta."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
_stub_rtk_stats(
|
||||
monkeypatch,
|
||||
[
|
||||
{"lifetime_tokens_saved": 0}, # baseline at zero
|
||||
{"lifetime_tokens_saved": 50},
|
||||
{"lifetime_tokens_saved": 250},
|
||||
],
|
||||
)
|
||||
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 0
|
||||
assert tracker._last_rtk_tokens_saved == 0
|
||||
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 50
|
||||
assert tracker._last_rtk_tokens_saved == 50
|
||||
|
||||
tracker.update_contribution()
|
||||
# 50 + (250 - 50) = 250 cumulative; delta on the third call was 200.
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 250
|
||||
assert tracker._last_rtk_tokens_saved == 250
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 2 — ``tokens_saved_rtk = 0`` when stats endpoint returns None
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_rtk_stats_none_yields_zero_delta(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""No RTK selected / installed — contribution stays at zero, no throw."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
_stub_rtk_stats(monkeypatch, [None, None])
|
||||
|
||||
tracker.update_contribution()
|
||||
tracker.update_contribution()
|
||||
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 0
|
||||
assert tracker._last_rtk_tokens_saved == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 3 — monotonic advancement; no replay on flat poll
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_last_rtk_advances_monotonically(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Two polls returning the same lifetime total contribute exactly once."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
_stub_rtk_stats(
|
||||
monkeypatch,
|
||||
[
|
||||
{"lifetime_tokens_saved": 42},
|
||||
{"lifetime_tokens_saved": 42}, # no movement
|
||||
{"lifetime_tokens_saved": 42}, # still no movement
|
||||
],
|
||||
)
|
||||
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 42
|
||||
assert tracker._last_rtk_tokens_saved == 42
|
||||
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 42 # unchanged
|
||||
assert tracker._last_rtk_tokens_saved == 42
|
||||
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 42
|
||||
assert tracker._last_rtk_tokens_saved == 42
|
||||
|
||||
|
||||
def test_counter_regression_rebaselines_without_negative_delta(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""RTK DB rebuild drops the lifetime total — re-baseline, do not subtract."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
_stub_rtk_stats(
|
||||
monkeypatch,
|
||||
[
|
||||
{"lifetime_tokens_saved": 500},
|
||||
{"lifetime_tokens_saved": 100}, # regression!
|
||||
{"lifetime_tokens_saved": 150},
|
||||
],
|
||||
)
|
||||
|
||||
tracker.update_contribution()
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 500
|
||||
assert tracker._last_rtk_tokens_saved == 500
|
||||
|
||||
tracker.update_contribution()
|
||||
# Regression: contribution stays at 500 (no negative subtraction).
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 500
|
||||
# Baseline now points at the new (smaller) lifetime total so subsequent
|
||||
# polls can compute a meaningful delta.
|
||||
assert tracker._last_rtk_tokens_saved == 100
|
||||
|
||||
tracker.update_contribution()
|
||||
# 150 - 100 = 50 new delta; contribution = 500 + 50 = 550.
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 550
|
||||
assert tracker._last_rtk_tokens_saved == 150
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 4 — transient exception in the stats endpoint
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_rtk_stats_exception_zero_delta_no_throw(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""A raised ``_get_rtk_stats()`` is caught, logged, and yields 0 delta."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
|
||||
def boom() -> dict[str, Any] | None:
|
||||
raise RuntimeError("transient subprocess failure")
|
||||
|
||||
monkeypatch.setattr("headroom.proxy.helpers._get_rtk_stats", boom)
|
||||
|
||||
# Must not raise.
|
||||
tracker.update_contribution()
|
||||
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 0
|
||||
assert tracker._last_rtk_tokens_saved == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 5 — explicit env-var opt-out
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_disabled_env_returns_zero(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""``HEADROOM_RTK_WIRING=disabled`` skips the poll entirely."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.setenv(tracker_module._RTK_WIRING_ENV, "disabled")
|
||||
|
||||
polls = _stub_rtk_stats(
|
||||
monkeypatch,
|
||||
[{"lifetime_tokens_saved": 999}],
|
||||
)
|
||||
|
||||
tracker.update_contribution()
|
||||
|
||||
# Stats endpoint never called when wiring is disabled.
|
||||
assert polls[0] == 0
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 0
|
||||
assert tracker._last_rtk_tokens_saved == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 6 — explicit override from caller (back-compat for callers that
|
||||
# already know the RTK delta out-of-band).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_explicit_rtk_override_skips_poll(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Caller-supplied ``tokens_saved_rtk`` short-circuits the poll."""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
|
||||
polls = _stub_rtk_stats(monkeypatch, [{"lifetime_tokens_saved": 999}])
|
||||
|
||||
tracker.update_contribution(tokens_saved_rtk=17)
|
||||
|
||||
# Stats endpoint not consulted when the caller passes an explicit value.
|
||||
assert polls[0] == 0
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 17
|
||||
assert tracker._last_rtk_tokens_saved == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 7 — cli_filtering decoupled from rtk
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_cli_filtering_no_longer_mirrors_rtk(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Pre-PR-G2 bug: ``cli_filtering`` and ``rtk`` were always equal.
|
||||
|
||||
After PR-G2 they are independent counters fed by separate sources.
|
||||
"""
|
||||
|
||||
tracker = _build_tracker(monkeypatch)
|
||||
monkeypatch.delenv(tracker_module._RTK_WIRING_ENV, raising=False)
|
||||
_stub_rtk_stats(monkeypatch, [{"lifetime_tokens_saved": 25}])
|
||||
|
||||
tracker.update_contribution(tokens_saved_cli_filtering=8)
|
||||
|
||||
assert tracker._state.contribution.tokens_saved_cli_filtering == 8
|
||||
# rtk comes from the polled delta, not from cli_filtering.
|
||||
assert tracker._state.contribution.tokens_saved_rtk == 25
|
||||
Loading…
Add table
Add a link
Reference in a new issue