feat(observability): collect host scheduler-pressure metrics (PSI, procs, reclaim, per-NUMA) - #415
Draft
nv-yna wants to merge 1 commit into
Draft
Conversation
The tachometer node_exporter launch enabled only cpu/infiniband/meminfo, and
the host sampler read /proc/stat + /proc/meminfo but no PSI. The result: none
of the scheduler-pressure signal the retired steady_probe.sh sampler carried
(PSI stall time, procs_running/blocked, context-switch and page-fault/reclaim
counters, per-NUMA free memory) was collected anywhere -- exactly the class of
signal that distinguishes a busy box from one whose work is blocked waiting.
Two complementary changes, both cheap procfs/sysfs reads with no measured
decode-latency cost (unlike dense NVML sampling):
- node_exporter (all worker nodes): add the stat, vmstat, pressure, and
meminfo_numa collectors. The vendored NodeExporterFilter passes the new
families through its default arm, so no scraper change is needed. Extracted
into tachometer_node_exporter_command_template() mirroring the DCGM template;
an explicit recipe node_exporter.command still wins.
- host_sampler (orchestrator/head node, which gets no exporter): add a
/proc/pressure/{cpu,memory,io} PSI read to each sample. This is that node's
only PSI source and covers the frontend node the exporters never reach.
Tests: node-exporter template asserts the four new collectors and
explicit-command override; host_sampler PSI parse + CONFIG_PSI-absent cases.
Signed-off-by: Yuewei Na <nv-yna@users.noreply.github.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #415 +/- ##
=======================================
Coverage ? 73.88%
=======================================
Files ? 101
Lines ? 14128
Branches ? 0
=======================================
Hits ? 10438
Misses ? 3690
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
nv-yna
added a commit
to nv-yna/srt-slurm
that referenced
this pull request
Sep 10, 2026
…xporterFilter
Two node_exporter families enabled by the combined observability branch lost
their only distinguishing label in the vendored scraper filter, so every series
of the family collapsed into one metric name in the parquet:
- node_memory_numa_*{node="N"} (--collector.meminfo_numa, NVIDIA#415): the generic
`memory_` arm emitted the bare metric name, folding all NUMA nodes together.
Now `memory_numa_<x>{numa_node=N}`; host-wide meminfo stays label-free.
- node_processes_state{state="R"|"S"|"D"|...} and
node_processes_threads_state{thread_state=...} (--collector.processes, NVIDIA#413):
the default arm keeps only a fixed label whitelist that had neither key.
`state` and `thread_state` are added to the whitelist.
Label-free stat/vmstat/pressure families were already passed through unchanged;
a test pins that too.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yuewei Na <nv-yna@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Tachometer is on by default for every run, but its host-side scrape surface omits the
scheduler-pressure signal set that the retired
steady_probe.shsampler carried. Thenode_exporter launch enabled only
cpu/infiniband/meminfo, andhost_sampler.pyread
/proc/stat+/proc/meminfobut no PSI. Net result: none of these werecollected anywhere —
/proc/pressure/{cpu,memory,io})procs_running/procs_blocked/ context-switch counters (/proc/stat)/proc/vmstat:pgmajfault,pgsteal_*)/sys/devices/system/node/*/meminfo)This is exactly the class of signal that distinguishes "the box is busy" from "real
work is blocked waiting for a resource" — the question a host sampler exists to answer.
Change
Two complementary, minimal changes. Both are cheap procfs/sysfs reads with no measured
decode-latency cost (unlike dense NVML sampling, which already has a cadence guard).
node_exporter (all worker nodes) — add the
stat,vmstat,pressure, andmeminfo_numacollectors, and widen--collector.vmstat.fieldsto includepgsteal(node_exporter's default vmstat field regex ships
pgmajfaultbut not reclaim). Thecommand is extracted into
tachometer_node_exporter_command_template()mirroring theexisting DCGM template; an explicit recipe
node_exporter.commandstill wins viaresolve_exporter_command. The vendoredNodeExporterFilterpasses the new familiesthrough its default arm, so no scraper/Rust change is needed.
host_sampler (orchestrator/head node) — add a
/proc/pressure/{cpu,memory,io}PSIread to each sample. The exporters are launched on backend nodes only, so the sampler
is the orchestrator node's only PSI source; this covers the frontend node the exporters
never reach.
Maps the probe's host columns 1:1:
procs_running/blocked→node_procs_running/blocked,ctxt→node_context_switches_total,pgmajfault/pgsteal→node_vmstat_*,numa*_free→node_memory_numa_MemFree{node=N}, PSI →node_pressure_*/ samplerpsi.Verification — real run on hecate (aarch64)
Ran the pinned image
quay.io/prometheus/node-exporter:v1.8.2with the exact generatedflags via pyxis on a live hecate node (kernel
6.17.0-1029-nvidia-64k), scraped from thehost side:
node_procs_runningnode_context_switches_totalnode_vmstat_pgmajfaultnode_vmstat_pgsteal_kswapdnode_memory_numa_MemFree{node=0}node_pressure_cpu_waiting_*Kernel-gated PSI, discovered by this real run: hecate's kernel is built without
CONFIG_PSI—/proc/pressuredoes not exist — so thepressurecollector and thesampler's PSI read yield nothing there. Both paths degrade gracefully (node_exporter omits
the family;
_pressure()returns{}and thepsikey is empty). On PSI-enabled kernels(verified on a workstation with the same image + flags) all
node_pressure_*families andthe sampler
psiblock populate. This is documented in-code so nobody expects PSI columnsfrom a hecate run.
Full evidence:
tmp_context/srt-slurm/20260909-tachometer-host-metrics/VERIFICATION.md.Tests
make-level unit tests (9 new/adjacent green; fulltest_telemetry.py= 69 green):pgstealfield override, and theexplicit-command override still winning;
host_sampler._pressure()parse of PSI totals and theCONFIG_PSI-absent →{}case.Out of scope (noted for reviewers)
top1_comm/top_pid/top_threads/vol+nonvol_ctxt) is already covered byhost_sampler._proc_sample()whenobservability.enabled— on the orchestrator node.orchestration change.