[Fix] OpenMetrics compliance for session_duration metric#14476
[Fix] OpenMetrics compliance for session_duration metric#14476mayagbarnes merged 1 commit intodevelopfrom
session_duration metric#14476Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
There was a problem hiding this comment.
Pull request overview
Fixes OpenMetrics unit compliance for the session_duration counter by separating the metric family name (used for # TYPE/# UNIT metadata and protobuf MetricFamily.name) from the emitted counter sample name (which must keep the _total suffix).
Changes:
- Rename
SESSION_DURATION_FAMILYfromsession_duration_seconds_total→session_duration_secondsto make the declared unit (seconds) a valid suffix of the family name. - Extend
CounterStatwith an optionalsample_nameoverride so emitted samples can differ from family metadata while preserving existing behavior when unset. - Update runtime producer and tests to emit
session_duration_seconds_totalsamples under thesession_duration_secondsfamily for both text and protobuf outputs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| lib/streamlit/runtime/stats.py | Renames SESSION_DURATION_FAMILY and adds CounterStat.sample_name to decouple family metadata name from emitted sample name. |
| lib/streamlit/runtime/websocket_session_manager.py | Emits the session duration counter with canonical family name + _total sample name override. |
| lib/tests/streamlit/runtime/stats_test.py | Adds coverage for CounterStat.sample_name and verifies default remains None. |
| lib/tests/streamlit/runtime/websocket_session_manager_test.py | Updates assertions to the new family name and validates the _total sample name is still emitted. |
| lib/tests/streamlit/web/server/stats_handler_test.py | Adds/updates endpoint tests to verify canonical family metadata vs _total sample name in both text and protobuf formats. |
| lib/tests/streamlit/web/server/starlette/starlette_app_test.py | Mirrors the same assertions for the Starlette server implementation (text + protobuf). |
There was a problem hiding this comment.
Summary
This PR fixes an OpenMetrics compliance error where Prometheus scrapers reject the session_duration_seconds_total metric with: unit "seconds" not a suffix of metrics "session_duration_seconds_total". Per the OpenMetrics spec, the declared UNIT must be a suffix of the MetricFamily name.
The fix renames SESSION_DURATION_FAMILY from "session_duration_seconds_total" to "session_duration_seconds" and introduces an optional sample_name field on CounterStat so the emitted sample line can still use the required _total suffix while the family metadata uses the canonical (unit-suffixed) name.
Reviewer consensus: All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) approved the PR unanimously. There were no disagreements on the approach, correctness, or scope of the change.
Code Quality
All reviewers agree the implementation is clean, minimal, and well-structured:
- Adding
sample_nameas an optional field on aNamedTuplewith aNonedefault preserves full backward compatibility. All existing call sites use keyword arguments, so the new field at the end is safe. to_metric_str()cleanly falls back tofamily_namewhensample_nameisNone, preserving existing behavior for all other counters.marshall_metric_proto()correctly does not usesample_name— in protobuf format, the family name already identifies the metric family with no separate "sample name" concept.- The scope is well-contained: only the broken unitful counter is renamed, and the PR description explains why
session_events_totalis intentionally left unchanged.
Test Coverage
All reviewers rated test coverage as excellent/strong. The PR adds and updates tests across all layers:
stats_test.py: New unit tests forCounterStat.to_metric_str()withsample_name(with and without labels), plus assertion thatsample_name is Noneby default.websocket_session_manager_test.py: All references to the old family name updated; new assertion verifyingsample_nameis correctly set.stats_handler_test.py: New tests for unitful counter protobuf output and text-format output verifying canonical# TYPE,# UNIT, and sample lines.starlette_app_test.py: New protobuf test and text-format assertions added for the metrics endpoint.
No e2e tests were added, but all reviewers agree the existing unit/integration coverage is sufficient for this backend-only metrics formatting change.
Backwards Compatibility
All reviewers agree the change is backward compatible for normal usage. The observable changes are:
| Aspect | Before | After |
|---|---|---|
# TYPE line |
session_duration_seconds_total |
session_duration_seconds |
# UNIT line |
session_duration_seconds_total seconds |
session_duration_seconds seconds |
| Sample line | session_duration_seconds_total <value> |
session_duration_seconds_total <value> (unchanged) |
| Protobuf family name | session_duration_seconds_total |
session_duration_seconds |
The sample line is unchanged, so Prometheus scraping rules matching on session_duration_seconds_total continue to work. One reviewer (gpt-5.3-codex-high) noted a minor compatibility consideration: consumers that filter by metric family name (e.g., via families=session_duration_seconds_total) would need to update to families=session_duration_seconds. This is a valid but low-impact observation since the old output was non-compliant and caused Prometheus scraping errors. The SESSION_DURATION_FAMILY constant is in an internal module and not part of the public st.* API.
Security & Risk
All reviewers agree there are no security concerns. Changes are limited to internal metrics reporting plumbing — no new endpoints, no authentication changes, no external dependencies, and no user-facing input handling.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
- Changes are limited to internal metrics data model (
stats.py) and its consumer (websocket_session_manager.py) - No routing, auth, websocket handshake, embedding, asset serving, CORS, or security header changes
- The
/_stcore/metricsendpoint behavior change is purely in metric name/format compliance
- Changes are limited to internal metrics data model (
- Suggested external_test focus areas: N/A
- Confidence: High (unanimous across all three reviewers)
- Assumptions and gaps: None — the changes are clearly scoped to internal metrics infrastructure with no impact on externally hosted or embedded behavior.
Accessibility
No frontend changes; accessibility is not applicable. All reviewers concur.
Recommendations
- Consider calling out the metric-family rename (
session_duration_seconds_total→session_duration_seconds) in release notes for operators usingfamilies=filters or protobuf consumers keyed by family name. (Raised by gpt-5.3-codex-high; agreed as good practice by consolidation review.)
Verdict
APPROVED: All three reviewers unanimously approved. The fix is well-scoped, correctly addresses the OpenMetrics unit-suffix compliance issue, has thorough test coverage across all serialization layers, and carries no backward compatibility concerns for normal usage.
This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from: claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high. All expected models completed their reviews successfully.
I'm wondering if we might as well just update this at the same time to keep the naming compliant with the spec? Non-blocking though. |
I was also struggling with whether to just get this fully compliant 😅 so opting to compromise and do it as a separate PR |
- Canonicalize counter metric families in `/_stcore/metrics` so metadata uses OpenMetrics-compliant family names while emitted counter samples still use the `_total` suffix. - Tighten text serialization to omit empty `# UNIT` lines and emit `# HELP <family_name> <help>`. - Update runtime, server, and e2e metrics tests for the `session_events` family rename and the canonical family/sample split. Follow-up to #14476 ### API-visible change - `families=session_events_total` no longer works. - Consumers should now request `families=session_events`. - The emitted sample line remains `session_events_total{...}`.
Describe your changes
Fixes the Prometheus scraping error:
The OpenMetrics spec requires the UNIT value to be a suffix of the MetricFamily name. The session duration counter was using
session_duration_seconds_totalas both the family name and the sample name, so the declared unitsecondswasn't a valid suffix (it's buried before_total).What changed
SESSION_DURATION_FAMILYconstant from"session_duration_seconds_total"to"session_duration_seconds".sample_namefield toCounterStatso counter samples can use a name distinct from the family metadata name. When unset, behavior is unchanged.sample_name="session_duration_seconds_total"so the emitted sample line keeps the required_totalsuffix while the family metadata uses the canonical name.family_name) from sample rendering (to_metric_str()).Scope notes
session_duration) is renamed.session_events_totalis technically non-canonical per OpenMetrics but does not cause a Prometheus failure, so it is left unchanged to limit the API-visible break.# UNITlines for unitless metrics is tracked separately.GitHub Issue Link (if applicable)
Closes #14432
Testing Plan