diff --git a/headroom/dashboard/templates/dashboard.html b/headroom/dashboard/templates/dashboard.html index 67a696f15..a4c9988f5 100644 --- a/headroom/dashboard/templates/dashboard.html +++ b/headroom/dashboard/templates/dashboard.html @@ -105,12 +105,12 @@
Proxy $ Saved
- +
- -
diff --git a/headroom/proxy/server.py b/headroom/proxy/server.py index f8b68a85a..5bb22ba84 100644 --- a/headroom/proxy/server.py +++ b/headroom/proxy/server.py @@ -3099,12 +3099,13 @@ def run_server( # sticky-session workaround. logger.warning( "Headroom is running with workers=%d. The in-memory CCR store, " - "compression cache, prefix tracker, and TOIN state are all " + "compression cache, prefix tracker, TOIN state, and CostTracker are all " "per-process; multi-worker deployments produce silent retrieval " - "failures and avoidable cache busts when sessions land on different " - "workers. Run --workers 1 (or place a sticky-session load balancer " - "in front of multiple --workers 1 processes). See RUST_DEV.md → " - "'Multi-worker deployment — CCR fragmentation'.", + "failures, avoidable cache busts, and an unstable dashboard 'Proxy $ Saved' " + "hero tile (each /stats poll hits a different worker's partial total) when " + "sessions land on different workers. Run --workers 1 (or place a " + "sticky-session load balancer in front of multiple --workers 1 processes). " + "See RUST_DEV.md → 'Multi-worker deployment — CCR fragmentation'.", workers, ) os.environ[_MULTI_WORKER_CONFIG_ENV] = json.dumps(_proxy_config_payload(config)) diff --git a/tests/test_proxy_savings_history.py b/tests/test_proxy_savings_history.py index e4874f5a6..8e95c05f4 100644 --- a/tests/test_proxy_savings_history.py +++ b/tests/test_proxy_savings_history.py @@ -211,6 +211,42 @@ def test_record_compression_savings_skips_empty_updates_and_normalizes_timestamp assert persisted["history"][-1]["timestamp"] == "2026-03-27T12:34:00Z" +def test_savings_tracker_save_does_not_flock_target_inode_before_replace(tmp_path, monkeypatch): + path = tmp_path / "proxy_savings.json" + tracker = SavingsTracker(path=str(path)) + + tracker.record_request( + model="gpt-4o", + input_tokens=120, + tokens_saved=10, + timestamp="2026-03-27T09:00:00Z", + ) + assert path.exists() + + flock_calls: list[int] = [] + + class _FcntlSpy: + LOCK_EX = 1 + LOCK_UN = 2 + + def flock(self, _fh, operation: int) -> None: + flock_calls.append(operation) + + monkeypatch.setattr(savings_tracker_module, "_HAS_FCNTL", True, raising=False) + monkeypatch.setattr(savings_tracker_module, "_fcntl", _FcntlSpy(), raising=False) + + tracker.record_request( + model="gpt-4o", + input_tokens=80, + tokens_saved=5, + timestamp="2026-03-27T09:10:00Z", + ) + + assert flock_calls == [] + persisted = json.loads(path.read_text(encoding="utf-8")) + assert persisted["lifetime"]["tokens_saved"] == 15 + + def test_litellm_resolution_and_savings_estimation_fallbacks(monkeypatch): def fake_cost_per_token(*, model, prompt_tokens, completion_tokens): if model in {"gpt-4o", "anthropic/claude-sonnet-4-6"}: