Skip to content

fix(metrics): classify per-GPU series as gauges not counters#114

Open
shingonoide wants to merge 5 commits into
mainfrom
gpu-util-gauge
Open

fix(metrics): classify per-GPU series as gauges not counters#114
shingonoide wants to merge 5 commits into
mainfrom
gpu-util-gauge

Conversation

@shingonoide

@shingonoide shingonoide commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Per-GPU system metrics (gpuN, gpuN_mem) are emitted as monotonic OpenTelemetry counters instead of point-in-time gauges. Downstream, the reported "GPU Utilization %" accumulates the running sum of utilization samples, so it grows without bound and exceeds 100% (a near-straight ramp from 0% to 155% over ~5 minutes was observed on a single-GPU instance).

Root cause

OpenTelemetryClient decides gauge-vs-counter by membership in a static _GAUGE_METRICS set. The per-GPU keys are emitted with dynamic names (gpu0, gpu1, gpu0_mem, ...), so they are never in that set and fall through to create_counter, recorded via instrument.add(value) — accumulating the sum of samples over time. cpu/mem/fps are created as gauges and are unaffected.

Fix

Add name-based classification for the dynamic per-GPU series (gpuN / gpuN_mem) alongside the existing static set, so they are created as gauges. Genuine counters (frame_count, megapx_count, *_ts, ...) and the health gauges (gpu_usage_percent, gpu_accessible) are unchanged.

The fix also corrects the data type of the gpuN_mem series. No downstream read-path change is required — the read path faithfully displays whatever the emitter sends.

Tests

Added TestGpuGaugeClassification in tests/test_timing_metrics.py covering gpu0 / gpu10 / gpu0_mem / gpu12_mem (gauge) and non-matches like foo_gpu0, frame_count. Full module: 39 passed.

…599)

Per-GPU utilization/memory series (gpuN, gpuN_mem) use dynamic names, so
they missed the static _GAUGE_METRICS set and fell through to counters,
accumulating a running sum (the GPU Utilization tile ramped past 100%).
Add pattern-based gauge classification for gpu[0-9]+(_mem)? at the
gauge-vs-counter decision, alongside the static set.

Signed-off-by: Rui Andrada <randrada@plainsight.ai>
@shingonoide shingonoide self-assigned this Jul 10, 2026
@shingonoide shingonoide requested a review from lucasmundim July 10, 2026 21:39
Add RELEASE.md entry and bump VERSION for the per-GPU counter->gauge fix.

Signed-off-by: Rui Andrada <randrada@plainsight.ai>
Replace the _GPU_GAUGE_PATTERN regex and its import re with an equivalent
endswith/startswith/isdigit check in _is_gauge_metric. Behavior is identical
across all classified names; removes the extra import and compiled attribute.

Signed-off-by: Rui Andrada <randrada@plainsight.ai>
Drop the internal Jira link from the v1.1.3 changelog entry and reword the
implementation note to match the string-based per-GPU classifier (no regex).

Signed-off-by: Rui Andrada <randrada@plainsight.ai>
@shingonoide shingonoide changed the title fix(metrics): classify per-GPU series as gauges not counters (FILTER-599) fix(metrics): classify per-GPU series as gauges not counters Jul 10, 2026
Strip Jira ticket hyperlinks and bare keys from all changelog entries;
keep GitHub PR self-references and public schema URLs.

Signed-off-by: Rui Andrada <randrada@plainsight.ai>

@leandrobmarinho leandrobmarinho left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Summary: Classifies the dynamic per-GPU series (gpuN, gpuN_mem) as gauges instead of counters, fixing the GPU Utilization % that accumulated a running sum and climbed past 100%.

Looks good. No blocking issues found. The root cause is right — dynamic per-GPU keys never appear in the static _GAUGE_METRICS set, so they fell through to create_counter + add() and accumulated — and the _is_gauge_metric classifier is precise:

  • core.startswith("gpu") and core[3:].isdigit() (after stripping an optional _mem) matches gpu0 / gpu10 / gpu0_mem / gpu12_mem (incl. multi-GPU two-digit indices) but correctly rejects foo_gpu0 (uses startswith, not substring), bare gpu ("".isdigit() is False), gpux, and gpu0_foo. Health gauges gpu_usage_percent / gpu_accessible don't match the digit pattern, so their classification is untouched, and genuine counters (frame_count, *_ts) are unaffected.

Tests are thorough — they pin the classifier boundaries, assert gpu0/gpu0_mem create observable gauges (no counters), that repeated updates store the latest value rather than add()-ing a sum, and that frame_count still records via add(). This is also the correct emitter-side complement to the read-side VRAM/memory classification in plainsight-api#944.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants