mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(proxy): allow disabling periodic TOIN stats logging (#1265)
## Description
Add an explicit proxy configuration toggle for the periodic TOIN stats
logging loop.
Long-lived proxy workers currently schedule
`_log_toin_stats_periodically()` unconditionally at startup. This change
lets operators disable only that 5-minute stats logging loop via
`HEADROOM_PERIODIC_TOIN_STATS=0` when periodic stats collection creates
avoidable resource pressure. The default remains enabled, and this does
not disable TOIN learning or request-time feedback.
Closes #
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Added `ProxyConfig.periodic_toin_stats_enabled`, defaulting to `True`.
- Wired `HEADROOM_PERIODIC_TOIN_STATS` through
`_proxy_config_from_env()`.
- Guarded the proxy lifespan startup so `_log_toin_stats_periodically()`
is only scheduled when the config is enabled.
- Added tests for the default env behavior, disabled env values, and the
disabled lifespan behavior.
- Documented `HEADROOM_PERIODIC_TOIN_STATS` in the configuration
reference.
## Testing
- [x] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ uv sync --extra dev
Resolved 256 packages in 1m 05s
Built headroom-ai @ file:///C:/Users/wstcz/AppData/Local/Temp/headroom-main-20260622-073524
Installed 124 packages in 50.87s
$ .\.venv\Scripts\python.exe -c "import headroom._core; print('core ok')"
core ok
$ uv run pytest tests/test_proxy_telemetry_env.py -q
============================= test session starts =============================
platform win32 -- Python 3.11.15, pytest-9.0.3, pluggy-1.6.0
rootdir: C:\Users\wstcz\AppData\Local\Temp\headroom-main-20260622-073524
configfile: pyproject.toml
plugins: anyio-4.12.1, langsmith-0.8.0, asyncio-1.3.0, cov-7.0.0
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 9 items
tests\test_proxy_telemetry_env.py ......... [100%]
============================== warnings summary ===============================
.venv\Lib\site-packages\fastapi\testclient.py:1
C:\Users\wstcz\AppData\Local\Temp\headroom-main-20260622-073524\.venv\Lib\site-packages\fastapi\testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================== 9 passed, 1 warning in 4.30s =========================
$ python -m py_compile headroom\proxy\models.py headroom\proxy\server.py tests\test_proxy_telemetry_env.py
# no output; command exited 0
$ git diff --check
# no output; command exited 0
```
## Real Behavior Proof
- Environment: Windows, Python 3.11.15 uv-managed `.venv`, source
checkout at `bc12ace` plus this branch.
- Exact command / steps: synced dev dependencies with `uv sync --extra
dev`, verified `headroom._core` imports, then ran `uv run pytest
tests/test_proxy_telemetry_env.py -q`.
- Observed result: all 9 tests in `tests/test_proxy_telemetry_env.py`
passed. The new tests prove the env var defaults to enabled, `0` /
`false` / `off` / `no` disable it, and disabled config avoids requesting
the periodic TOIN stats coroutine during app lifespan startup.
- Not tested: full repository pytest, ruff, and mypy were not run for
this narrowly scoped proxy config change.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Screenshots (if applicable)
N/A.
## Additional Notes
- Default behavior is unchanged: periodic TOIN stats logging remains
enabled unless explicitly disabled.
- This only disables the periodic stats logging task. It does not
disable TOIN learning, request-time feedback, or stats retrieval
elsewhere.
- `My changes generate no new warnings` is left unchecked because the
focused pytest run emits a pre-existing Starlette/FastAPI TestClient
deprecation warning from dependencies.
- `CHANGELOG.md` is left unchecked because this is a small unreleased
configuration toggle and docs/tests cover the behavior.
This commit is contained in:
parent
d480c464e9
commit
b5f63d8fa9
4 changed files with 69 additions and 2 deletions
|
|
@ -235,6 +235,7 @@ headroom proxy --learn --min-evidence 3
|
|||
| `HEADROOM_TOIN_PATH` | Override TOIN telemetry file location. Always wins when set. | derived from `${HEADROOM_WORKSPACE_DIR}` |
|
||||
| `HEADROOM_SUBSCRIPTION_STATE_PATH` | Override subscription tracker state file. Always wins when set. | derived from `${HEADROOM_WORKSPACE_DIR}` |
|
||||
| `HEADROOM_TELEMETRY` | Set to `on` to opt in to anonymous telemetry | `off` |
|
||||
| `HEADROOM_PERIODIC_TOIN_STATS` | Controls periodic TOIN stats logging in long-lived proxy workers. Set to `0`, `false`, `off`, or `no` to disable the 5-minute stats loop without disabling TOIN learning or request-time feedback. | `true` |
|
||||
| `HEADROOM_MEMORY_INJECTION_MODE` | Memory-context routing mode: `live_zone_tail` (default) or `disabled`. The legacy `system_prompt` mode was retired by PR-A2; supplying it raises. | `live_zone_tail` |
|
||||
| `HEADROOM_PROXY_PYTHON_FORWARDER_MODE` | Python forwarder serialization mode. `byte_faithful` (default) forwards original request bytes verbatim when no transform mutated the body and re-serializes canonically only when needed — keeps Anthropic prompt-cache hit-rate intact. `legacy_json_kwarg` is an explicit operator opt-in for emergency rollback to the historical `httpx ... json=body` behavior. NOT a fallback — only flip on explicit operator decision. | `byte_faithful` |
|
||||
| `HEADROOM_STRIP_INTERNAL_HEADERS` | Python proxy: whether to strip internal `x-headroom-*` request headers (e.g. `x-headroom-bypass`, `x-headroom-mode`, `x-headroom-user-id`, `x-headroom-stack`, `x-headroom-base-url`) before every upstream forwarder call (PR-A5, fixes P5-49). `enabled` (default) stops fingerprinting / leakage. `disabled` is an explicit operator opt-in for diagnostic shadow tracing — NOT a fallback. Inbound reads of these headers (bypass gating, memory user-id resolution) are unaffected because they read `request.headers` directly. | `enabled` |
|
||||
|
|
|
|||
|
|
@ -294,6 +294,12 @@ class ProxyConfig:
|
|||
subscription_poll_interval_s: int = 300
|
||||
subscription_active_window_s: int = 60
|
||||
|
||||
# Periodic TOIN stats logging. Enabled by default for observability, but
|
||||
# operators of long-lived proxies can disable it if TOIN stats collection
|
||||
# causes avoidable memory pressure on their platform.
|
||||
# Env: HEADROOM_PERIODIC_TOIN_STATS=0.
|
||||
periodic_toin_stats_enabled: bool = True
|
||||
|
||||
# Stateless mode — disable all filesystem writes for read-only / container deployments
|
||||
stateless: bool = False
|
||||
|
||||
|
|
|
|||
|
|
@ -1810,7 +1810,8 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
|
|||
try:
|
||||
# Startup
|
||||
await proxy.startup()
|
||||
asyncio.create_task(_log_toin_stats_periodically())
|
||||
if config.periodic_toin_stats_enabled:
|
||||
asyncio.create_task(_log_toin_stats_periodically())
|
||||
if proxy.usage_reporter:
|
||||
await proxy.usage_reporter.start(proxy)
|
||||
if proxy.traffic_learner:
|
||||
|
|
@ -3740,6 +3741,7 @@ def _proxy_config_from_env() -> ProxyConfig:
|
|||
max_connections=_get_env_int("HEADROOM_MAX_CONNECTIONS", 500),
|
||||
max_keepalive_connections=_get_env_int("HEADROOM_MAX_KEEPALIVE", 100),
|
||||
http2=_get_env_bool("HEADROOM_HTTP2", True),
|
||||
periodic_toin_stats_enabled=_get_env_bool("HEADROOM_PERIODIC_TOIN_STATS", True),
|
||||
mode=normalize_proxy_mode(_get_env_str("HEADROOM_MODE", PROXY_MODE_TOKEN)),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
"""Tests for proxy telemetry environment variable handling."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("fastapi")
|
||||
|
||||
from headroom.proxy.server import ProxyConfig, create_app
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from headroom.proxy.server import ProxyConfig, _proxy_config_from_env, create_app
|
||||
|
||||
|
||||
class TestProxyTelemetrySDKEnv:
|
||||
|
|
@ -56,3 +59,58 @@ class TestProxyTelemetrySDKEnv:
|
|||
)
|
||||
|
||||
assert mock_beacon.call_args.kwargs["sdk"] == "proxy"
|
||||
|
||||
|
||||
class TestProxyPeriodicTOINStatsEnv:
|
||||
"""Test HEADROOM_PERIODIC_TOIN_STATS handling for long-lived proxy workers."""
|
||||
|
||||
def test_periodic_toin_stats_enabled_by_default(self, monkeypatch):
|
||||
"""Periodic TOIN stats logging remains enabled unless explicitly disabled."""
|
||||
monkeypatch.delenv("HEADROOM_PERIODIC_TOIN_STATS", raising=False)
|
||||
|
||||
config = _proxy_config_from_env()
|
||||
|
||||
assert config.periodic_toin_stats_enabled is True
|
||||
|
||||
@pytest.mark.parametrize("value", ["0", "false", "off", "no"])
|
||||
def test_periodic_toin_stats_can_be_disabled_by_env(self, monkeypatch, value):
|
||||
"""HEADROOM_PERIODIC_TOIN_STATS=0/false/off/no disables periodic logging."""
|
||||
monkeypatch.setenv("HEADROOM_PERIODIC_TOIN_STATS", value)
|
||||
|
||||
config = _proxy_config_from_env()
|
||||
|
||||
assert config.periodic_toin_stats_enabled is False
|
||||
|
||||
def test_lifespan_skips_periodic_toin_stats_when_disabled(self, monkeypatch):
|
||||
"""Disabling periodic TOIN stats avoids scheduling the stats loop."""
|
||||
monkeypatch.setenv("HEADROOM_SKIP_UPSTREAM_CHECK", "1")
|
||||
requested = False
|
||||
|
||||
def fake_periodic_toin_stats():
|
||||
nonlocal requested
|
||||
requested = True
|
||||
|
||||
async def noop():
|
||||
await asyncio.sleep(0)
|
||||
|
||||
return noop()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"headroom.proxy.server._log_toin_stats_periodically",
|
||||
fake_periodic_toin_stats,
|
||||
)
|
||||
|
||||
app = create_app(
|
||||
ProxyConfig(
|
||||
optimize=False,
|
||||
cache_enabled=False,
|
||||
rate_limit_enabled=False,
|
||||
cost_tracking_enabled=False,
|
||||
periodic_toin_stats_enabled=False,
|
||||
)
|
||||
)
|
||||
|
||||
with TestClient(app):
|
||||
pass
|
||||
|
||||
assert requested is False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue