fix(metrics): classify per-GPU series as gauges not counters#114
fix(metrics): classify per-GPU series as gauges not counters#114shingonoide wants to merge 5 commits into
Conversation
…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>
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>
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
left a comment
There was a problem hiding this comment.
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) matchesgpu0/gpu10/gpu0_mem/gpu12_mem(incl. multi-GPU two-digit indices) but correctly rejectsfoo_gpu0(usesstartswith, not substring), baregpu("".isdigit()is False),gpux, andgpu0_foo. Health gaugesgpu_usage_percent/gpu_accessibledon'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.
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
OpenTelemetryClientdecides gauge-vs-counter by membership in a static_GAUGE_METRICSset. The per-GPU keys are emitted with dynamic names (gpu0,gpu1,gpu0_mem, ...), so they are never in that set and fall through tocreate_counter, recorded viainstrument.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_memseries. No downstream read-path change is required — the read path faithfully displays whatever the emitter sends.Tests
Added
TestGpuGaugeClassificationintests/test_timing_metrics.pycoveringgpu0/gpu10/gpu0_mem/gpu12_mem(gauge) and non-matches likefoo_gpu0,frame_count. Full module: 39 passed.