Commit 5fe4e7b
authored
fix(proxy): expose persistent savings metrics (#1647)
## Description
Closes #1616
Expose the proxy's durable `persistent_savings.lifetime` totals through
`/metrics` so Prometheus/Grafana scrapes can read the same lifetime
savings counters already visible in `/stats` and `/stats-history`.
The existing runtime counters remain process-local:
`headroom_tokens_saved_total` still resets with the proxy process. New
`headroom_persistent_savings_*` counters are sourced from the
`SavingsTracker` lifetime block.
## 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
- Export durable lifetime savings counters from
`PrometheusMetrics.export()`:
- `headroom_persistent_savings_requests_total`
- `headroom_persistent_savings_tokens_saved_total`
- `headroom_persistent_savings_input_tokens_total`
- `headroom_persistent_savings_input_cost_usd_total`
- `headroom_persistent_savings_compression_savings_usd_total`
- Add a restart regression proving runtime counters reset while
persistent savings counters remain available from the same savings file.
- Extend the existing `/stats-history` restart test with `/metrics`
endpoint assertions.
- Update metrics docs to distinguish runtime
`headroom_tokens_saved_total` from lifetime
`headroom_persistent_savings_tokens_saved_total`.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
Local focused checks:
$ rtk /usr/bin/env HEADROOM_REQUIRE_RUST_CORE=false PYTHONPATH=. /tmp/headroom-1616-testenv/bin/python -m pytest tests/test_proxy_cache_ttl_metrics.py::test_prometheus_metrics_export_includes_extended_fields tests/test_proxy_cache_ttl_metrics.py::test_prometheus_export_includes_persistent_savings_after_restart
2 passed, 1 warning in 0.19s
$ rtk /tmp/headroom-1616-testenv/bin/python -m ruff check headroom/proxy/prometheus_metrics.py tests/test_proxy_cache_ttl_metrics.py tests/test_proxy_savings_history.py
All checks passed!
$ rtk /tmp/headroom-1616-testenv/bin/python -m ruff format --check headroom/proxy/prometheus_metrics.py tests/test_proxy_cache_ttl_metrics.py tests/test_proxy_savings_history.py
3 files already formatted
$ rtk git diff --check
# no output
GitHub Actions:
All non-skipped checks passed on PR #1647, including lint, build, build-wheel, test (1-4), test-agno, test-extras, test-dashboard-ui, docker-native-e2e, docker-init-e2e, docker-wrap-e2e, security checks, merge-conflicts, and PR governance.
```
## Real Behavior Proof
- Environment: local macOS worktree, throwaway Python env at
`/tmp/headroom-1616-testenv`, `PYTHONPATH=.`.
- Exact command / steps: recorded a compressed request through
`PrometheusMetrics.record_request()`, re-created `PrometheusMetrics`
with the same `SavingsTracker` path, then exported `/metrics` text.
- Observed result: runtime counters are zero after re-creating the
metrics object, while `headroom_persistent_savings_tokens_saved_total`
and related persistent counters still expose the durable lifetime
values.
- Not tested: full server-level pytest locally, because the local build
is blocked by the known native `headroom._core`/`esaxx-rs` build issue
(`fatal error: 'cstdint' file not found`). The app-level `/metrics`
assertions passed in GitHub Actions.
## 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
- [ ] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] 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
This intentionally does not rename or hydrate the existing runtime
`headroom_tokens_saved_total` counter. That preserves the current
process-local semantics and gives external dashboards a dedicated
lifetime series that maps directly to `/stats.persistent_savings`.
`mypy headroom` was not run as a standalone local command. CHANGELOG is
N/A for this narrow proxy metrics fix unless maintainers prefer an
entry.1 parent c600e31 commit 5fe4e7b
5 files changed
Lines changed: 102 additions & 4 deletions
File tree
- docs/content/docs
- headroom/proxy
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
75 | 78 | | |
76 | 79 | | |
77 | 80 | | |
| |||
253 | 256 | | |
254 | 257 | | |
255 | 258 | | |
256 | | - | |
| 259 | + | |
| 260 | + | |
257 | 261 | | |
258 | 262 | | |
259 | 263 | | |
| |||
265 | 269 | | |
266 | 270 | | |
267 | 271 | | |
268 | | - | |
| 272 | + | |
| 273 | + | |
269 | 274 | | |
270 | 275 | | |
271 | 276 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
| 154 | + | |
154 | 155 | | |
155 | 156 | | |
156 | 157 | | |
157 | 158 | | |
158 | 159 | | |
| 160 | + | |
| 161 | + | |
159 | 162 | | |
160 | 163 | | |
161 | 164 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
825 | 825 | | |
826 | 826 | | |
827 | 827 | | |
| 828 | + | |
828 | 829 | | |
829 | 830 | | |
830 | 831 | | |
| |||
896 | 897 | | |
897 | 898 | | |
898 | 899 | | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
899 | 938 | | |
900 | 939 | | |
901 | 940 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
79 | 80 | | |
80 | 81 | | |
81 | 82 | | |
82 | | - | |
83 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
84 | 87 | | |
85 | 88 | | |
86 | 89 | | |
| |||
105 | 108 | | |
106 | 109 | | |
107 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
108 | 116 | | |
109 | 117 | | |
110 | 118 | | |
| |||
113 | 121 | | |
114 | 122 | | |
115 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
116 | 149 | | |
117 | 150 | | |
118 | 151 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
866 | 866 | | |
867 | 867 | | |
868 | 868 | | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
869 | 875 | | |
870 | 876 | | |
871 | 877 | | |
| |||
907 | 913 | | |
908 | 914 | | |
909 | 915 | | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
910 | 922 | | |
911 | 923 | | |
912 | 924 | | |
| |||
922 | 934 | | |
923 | 935 | | |
924 | 936 | | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
925 | 943 | | |
926 | 944 | | |
927 | 945 | | |
| |||
0 commit comments