Skip to content

feat(power): host CPU power telemetry, utilization, and energy / perf-per-watt report - #410

Merged
FrankD412 merged 59 commits into
NVIDIA:mainfrom
FrankD412:kylliang/power_collection_feature
Sep 10, 2026
Merged

FrankD412 merged 59 commits into
NVIDIA:mainfrom
FrankD412:kylliang/power_collection_feature

Conversation

@FrankD412

Copy link
Copy Markdown
Collaborator

Summary

Host CPU power telemetry, utilization, and an energy / perf-per-watt report for benchmark measurement windows. Builds on the merged DCGM power leg (#288, #290) and follows the same shape: per-node exporters, a head-node collector, durable CSV + manifest artifacts, and best-effort semantics that never change the benchmark's exit code unless a recipe opts into required.

Draft for early review of the design and config surface. Cluster validation of the new CPU utilization fields is still pending (see "Verification").

What's in here

CPU power collection, two independent legs under telemetry:

  • telemetry.cpu_power_exporter -- a Rust cpu-power-exporter (src/cpu-power-exporter/, ACPI hwmon or DCGM field 1130, bundled by make setup) runs on each worker's bare host and serves /metrics; CpuPowerCollector polls it from the head node on the same clock as the DCGM leg and writes power/cpu/samples.csv. A stdlib Python exporter is the fallback when the binary is absent. Port-collision validation against the DCGM exporter, the tachometer's resolved exporters, and Dynamo system ports.
  • telemetry.cpu_power -- the in-job Python host collector (srtctl.core.cpu_power): one process per worker node reads ACPI power_meter hwmon channels (Grace socket-total vs. component-rail domain mapping) or DCGM CPU entities directly and writes per-node CSVs that the head node aggregates at teardown. Has fail-closed required semantics. With the DCGM source it also records per-socket utilization (DCGM CPU fields 1100-1104) as trailing columns.

Either leg alone satisfies telemetry.enabled; both may run together (no shared ports or directories).

GPU utilization -- gpu_util_pct (DCGM_FI_DEV_GPU_UTIL) and sm_active (DCGM_FI_PROF_SM_ACTIVE) as optional trailing columns on power/samples.csv (schema v2; v1 files still load), advertised in the manifest.

Energy / perf report (python -m srtctl.analysis.power_energy_report <log_dir>) -- per concurrency window, for sa-bench and aiperf/AgentX sweeps:

  • trapezoidal joules per CPU socket, GPU, node, and role; J/output-token and J/total-token
  • perf/W: output and total tokens/s per GPU watt and per CPU+GPU watt (combined is null with a warning unless both legs ran, so it never silently equals GPU-only)
  • utilization mean/max per socket, GPU, node, role
  • power distribution per breakdown row: time-weighted avg_power_w, sample mean_w, min/p5/p50/p95/p99/max
  • a timing comparison, for reference only: the computed window, the benchmark's self-reported duration (sa-bench duration; aiperf benchmark_duration, falling back to the profiling-phase NOTICE lines when exactly one phase ran), and the first/last power sample actually spanned

An incremental emitter writes the same payload per concurrency during the job (power_energy_c<N>.json + a JSONL index) so partial results survive a walltime kill.

Also: telemetry accepts agentic/agentx/custom benchmark types; sa-bench publishes its measurement windows; collect_interval_ms replaces the retired default_frequency on the CPU leg; DCGM 4.7 runtime notes; docs/cpu-power-telemetry.md documents all of the above.

Verification

  • make lint clean on src/ (ruff); ty at 9 diagnostics tree-wide, down from 18 on main.
  • Full suite: 1874 passed, 2 skipped, 7 failed. All 7 fail identically on upstream/main (or on this branch's pre-existing baseline before any of this work): test_apply_mock, three test_sa_bench_http_reuse_flag cases, test_probe_cpu_captures_affinity, and two IPv6 tachometer config tests. None touch the power code.
  • Not yet verified on hardware: (1) the scale of DCGM's CPU utilization fields (treated as a 0-1 fraction); (2) sm_active needs DCGM_FI_PROF_SM_ACTIVE in dcgm-exporter's counter set, which the default set omits, so that column is expected to be blank until a counters option is added (deliberately out of scope here).

Review guide

Start with src/srtctl/core/schema.py (CpuPowerConfig, CpuPowerExporterConfig, _validate_telemetry) and src/srtctl/cli/mixins/telemetry_stage.py for the config surface and lifecycle, then src/srtctl/core/power/contract.py for the artifact contracts, then src/srtctl/analysis/power_energy_report.py. The Rust exporter under src/cpu-power-exporter/ is self-contained.

🤖 Generated with Claude Code

kyleliang-nv and others added 30 commits September 7, 2026 18:06
Adds a timestamp_local column (ISO 8601 with UTC offset) to the CPU
power telemetry CSV, derived from timestamp_unix. Lets an offline
consumer read a node's real UTC offset directly from the samples file
instead of guessing the cluster's timezone when correlating against
benchmark logs that only carry local time-of-day text.

Bumps schema_version 1 -> 2; cpu_power_session.py's merge logic is
updated for the shifted column indices.

Signed-off-by: Frank Di Natale <3429989+FrankD412@users.noreply.github.com>
Adds srtctl.analysis.power_energy_report, a standalone module that
joins a run's CPU (power/cpu/samples.csv) and GPU (power/samples.csv)
power telemetry against each concurrency point's profiling window and
token counts, integrating power into energy with numpy.trapezoid.

Timestamps are read directly from existing wall-clock sources rather
than reconstructed: aiperf's profile_export.jsonl already carries
time.time_ns() per record, and sa-bench's result JSON already carries
benchmark_start_time_unix/benchmark_end_time_unix (mtime-based
derivation was tried and found unreliable once run directories get
copied/archived). GPU energy is broken down per-device, per-node, and
per-role using the topology already recorded in power/manifest.json.

Wires this into run_postprocess (postprocess_stage.py) as a best-effort
step alongside the perf dashboard build, writing
<log_dir>/power_energy_report.json on every job where CPU/GPU
telemetry and a supported benchmark type are present; quietly skipped
otherwise.

Signed-off-by: Frank Di Natale <3429989+FrankD412@users.noreply.github.com>
telemetry validation previously required benchmark.type: sa-bench,
rejecting agentic, agentx, and custom benchmark runs outright.
Ships the statically-linked Rust binary (aarch64/x86_64 musl) that reads
/sys/class/hwmon hwmon*/power*_average and serves cpu_power_acpi_watts
on :9405/metrics for AIPerf --server-metrics scraping.

Covers:
- src/cpu-power-exporter/ Rust crate (tokio, tracing, clap, anyhow)
- Workspace member added to Cargo.toml
- docker/Dockerfile.cpu-power-exporter (musl static, two-arch)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
…r-exporter

- Makefile: cpu-power-exporter / cpu-power-exporter-download targets,
  CPU_POWER_EXPORTER_RELEASE var, setup now depends on both downloads
- release.yaml: unified change detection (single step, two outputs),
  build-cpu-power-exporter job (linux/amd64 + linux/arm64),
  graceful carry-forward for both binaries from the previous release,
  nullglob-safe gh release create

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
…metrics

- telemetry_stage: add _resolve_bundled_binary (generalized from
  _resolve_tachometer_binary), add _start_cpu_power_prometheus_exporters
  which launches bin/cpu-power-exporter via srun on each worker node,
  call it from start_cpu_power_telemetry when prometheus_port > 0,
  fix dcgm_exporter guard (return None instead of raise when None)
- benchmark_stage: inject cpu-power-exporter node URLs into
  AIPERF_SERVER_METRICS_URLS when cpu_power.prometheus_port > 0
- submit.py: validate_setup checks bin/cpu-power-exporter alongside tachometer
- schema.py: add CpuPowerConfig.prometheus_port (default 9405),
  gate _validate_dcgm_power on dcgm_exporter is not None

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
Uses trace-replay benchmark type so AIPerf actually consumes the
AIPERF_SERVER_METRICS_URLS endpoints. Qwen3.5-27B BF16 to avoid the
8-minute FP4 JIT autotuning pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
…power

Ports cpu_power_exporter.py from kylliang/power_study_20260901 (commit 2a13cb1).
Default port updated to 9405 (consistent with the rest of the cpu-power stack).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
…orter is absent

If the bundled Rust binary is not present or not executable (e.g. x86_64 nodes
or a checkout without a pre-built bin/), _start_cpu_power_prometheus_exporters
now falls back to `python3 -m srtctl.core.cpu_power_exporter` so Prometheus
scraping still works everywhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
…etup, and tests from ajc/rust-cpu-exporter

- Add url_host() to ip_utils for IPv6 URL bracket safety
- Use url_host() in benchmark_stage.py CPU power URL construction
- Add arch_from_binary() to dynamo_wheels.py
- Rewrite CpuPowerConfig: drop 'dcgm' source, add storage_subdir, acpi_mandatory
- Add _dynamo_system_ports(), _validate_collector_budget(), _validate_cpu_power(),
  _reject_inert_cpu_power_demand() to schema validation
- Rewrite _validate_telemetry() to support CPU-only, DCGM-only, or combined modes
- Fix _validate_observability() to not conflict CPU-only telemetry with Tachometer DCGM
- Improve telemetry_stage.py: drop permitted_device_keys, use cpu_power.storage_subdir
- Update Makefile: version marker, arch check, warn-on-failure for latest release
- Add cpu-power-exporter-setup target; switch setup to use it
- Update validate_setup() to conditionally require cpu-power-exporter
- Add _cpu_power_exporter_problem() with executability and arch mismatch detection
- Port IPv6 tachometer tests and cpu-only telemetry stage test from rust-cpu-exporter
- Port validate_setup tests: disabled/enabled/arch-mismatch/non-executable cases

Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
Mirrors tachometer.yaml: runs cargo fmt/test on source changes, and
a two-arch Docker cross-compile to validate the release Dockerfile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
git cherry-pick of e11d928 concatenated the new test's def line with
the pre-existing one during auto-merge, breaking the file's syntax.
Cargo.toml already referenced tracing/tracing-subscriber via
workspace = true, but the workspace root never declared them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implement CpuReading, ParsedCpuScrape, and parse_cpu_scrape to parse
Prometheus-format /metrics text from the cpu-power-exporter Rust binary.
Supports both DCGM (one aggregated value per socket) and ACPI (per-channel
detail: cpu/sysio/grace) modes, with ACPI preferred when both present.

In ACPI mode, total_power_w sums only grace-kind channels, reflecting the
hardware's actual power boundary measurement for Grace SoC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…head-node scraper

This branch had two CPU power telemetry implementations: an older
host-side collector (src/srtctl/core/cpu_power.py,
cpu_power_session.py, TelemetryConfig.cpu_power) that runs on every
worker node and reads ACPI/DCGM sysfs directly, and a newer
head-node scraper (src/srtctl/core/power/cpu_session.py,
CpuPowerCollector) that polls each worker's cpu-power-exporter
/metrics endpoint from the head node, matching the DCGM power leg's
architecture.

Keep the newer collector, delete the older system, and repoint the
two things worth keeping from it:
  - AIPerf's AIPERF_SERVER_METRICS_URLS injection (benchmark_stage.py)
  - the submit-time preflight check for the bundled cpu-power-exporter
    binary (submit.py)

both now read the new, much smaller TelemetryConfig.cpu_power_exporter
(presence-gated, not a separate enabled flag) instead of the removed
CpuPowerConfig. telemetry_stage.py gains start_cpu_power_telemetry /
finalize_cpu_power_telemetry methods that launch the exporter with the
same bundled-binary-with-Python-fallback pattern the old code used,
but on the bare host (no container) and fully best-effort: launch
failures are absorbed and CPU power never mutates the job's exit code.

Also fixes a MagicMock-config test in test_power_collector.py that
started reaching real endpoint-allocation code once
start_cpu_power_telemetry stopped early-returning on a mocked
telemetry.cpu_power.enabled.
Mirrors the existing dcgm_exporter row; cpu_power_exporter has no
container_image so the row is just the port.
…mit.py preflight gate

- schema.py: _validate_telemetry now only raises when both dcgm_exporter and
  cpu_power_exporter are None, restoring standalone CPU-power-only telemetry.
  _validate_cpu_power_exporter() runs unconditionally when telemetry is enabled.
- submit.py: preflight cpu_power_enabled gate now also requires
  config.telemetry.enabled, not just cpu_power_exporter being set.
- telemetry_stage.py: start_tachometer's dcgm exporter sharing check now
  requires telemetry.dcgm_exporter to actually be configured, not just
  telemetry.enabled -- otherwise a CPU-power-only telemetry config silently
  suppressed the tachometer's own dcgm_exporter with no exporter running at all.
- tests: add coverage for CPU-power-only telemetry (schema + validate_setup),
  and restore test_cpu_only_telemetry_leaves_tachometers_dcgm_exporter_running
  adapted to the new CpuPowerExporterConfig field.
… blank-total crash

- schema.py: _validate_cpu_power_exporter now checks source is one of
  auto/acpi/dcgm, and rejects a port colliding with telemetry.dcgm_exporter,
  observability.tachometer.{dcgm,node}_exporter, or a Dynamo system port
  (via the previously-unused _dynamo_system_ports helper). Ported from the
  deleted _validate_cpu_power validator.
- power_energy_report.py: load_cpu_samples now skips rows with a blank
  total_power_w instead of crashing with ValueError(float("")) -- blank is
  the documented normal case for an ACPI scrape with no grace channel.
…-launch bugs

- schema.py: add CpuPowerExporterConfig.source (auto|acpi|dcgm), mirroring
  the Rust cpu-power-exporter binary's own --source flag; validated in
  _validate_cpu_power_exporter.
- telemetry_stage.py: pass --source through to the bundled Rust binary;
  warn (not silently drop) when a non-auto source is requested but the
  ACPI-only Python fallback exporter is used instead. Also move the
  het-group resolution block inside the try/except so an unresolvable node
  is absorbed like any other launch failure, per the method's own
  best-effort docstring contract, instead of aborting the sweep.
- configs/cpu-power-test.yaml: fix stale telemetry.cpu_power block (no
  longer loads -- "Unknown field") to the current telemetry.cpu_power_exporter
  shape, dropping the removed `required` flag and preserving `source: acpi`.
- submit.py: move the cpu_power_exporter dry-run row inside the
  `telemetry.enabled` guard so it doesn't display a port that will never
  actually launch.
- Tests: new TestCpuPowerExporterConfig source/collision cases, new
  TestCpuPowerExporterLaunch source-passthrough/fallback-warning/het-absorb
  cases, an explicit configs/cpu-power-test.yaml load test (configs/ isn't
  covered by the recipes/**/*.yaml glob and holds non-recipe files, so a
  narrow explicit test is safer than widening that glob), and a dry-run
  test confirming the row is hidden when telemetry is disabled.
…r system

- config-reference.md: replace the deleted telemetry.cpu_power block
  (BTK ordering, per-rail sensor names, sum-based total) with
  telemetry.cpu_power_exporter (port, source), the grace-rail-is-the-total
  convention, and best-effort/no-required semantics.
- cpu-power-telemetry.md: full rewrite describing the current
  CpuPowerCollector head-node HTTP-scrape design, cpu/samples.csv format
  (including the blank-total-without-grace caveat), and the bundled
  Rust-binary-with-Python-fallback launch model. The old doc described a
  fully deleted system (srtctl.core.cpu_power, CpuPowerConfig,
  wait_for_readiness/.ready.json, per-node CSV merging, embedded-mode DCGM
  bindings) that no longer exists.
- dcgm-4.7-runtime-support.md: fix the stale telemetry.cpu_power reference
  to telemetry.cpu_power_exporter.
Reformats the cpu_power_exporter dry-run row (added in the previous commit)
to fit the 120-char line limit per ruff format.
FrankD412 and others added 8 commits September 7, 2026 18:20
… in manifest

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…with utilization present

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r leg

Upstream NVIDIA#401 retired telemetry.default_frequency in favour of
collect_interval_ms. The DCGM leg picked that up in the rebase; the CPU
power collector still read the removed field and raised AttributeError at
launch. Derive its sample interval the same way the DCGM leg does, move
the dry-run and telemetry fixtures to the new knob, expect the built-in
node exporter that NVIDIA#358 now launches by default, and drop the retired
name from the CPU power docs.
…achometer exporters

Upstream NVIDIA#358 makes the tachometer launch built-in DCGM (9401) and node
(9101) exporters when no explicit block is configured. The CPU power
exporter's collision check compared against the raw fields, which are None
in that case, so a cpu_power_exporter on 9401 passed validation and
collided at runtime. Compare against resolved_dcgm_exporter /
resolved_node_exporter and cover both defaults.
…ndependent leg

Commit 0975241 replaced the in-job host collector (srtctl.core.cpu_power,
cpu_power_session, TelemetryConfig.cpu_power) with the head-node scraper
over cpu-power-exporter. The scraper stays, but the host collector is
needed as an alternative: it has fail-closed `required` semantics, needs
no exporter binary or reserved port, and reads ACPI/DCGM directly on each
node.

Bring back cpu_power.py, cpu_power_session.py, and tests/test_cpu_power.py
from the tree just before the cleanup (the newest version, with the Grace
socket-total domain mapping and timestamp_local). Reinstate
`telemetry.cpu_power` in Kyle Liang's original shape -- enabled, source
(auto|acpi|dcgm), sample_interval_seconds, startup_timeout_seconds,
required, storage_subdir -- without the `prometheus_port` field that was
later bolted on: the exporter and its AIPerf URL injection now belong to
`telemetry.cpu_power_exporter`, so the two blocks share no ports and no
output directories and may be enabled independently or together.

The stage gains start_cpu_power_host_telemetry / finalize_cpu_power_host_
telemetry alongside the scraper's methods, power_telemetry_blocks_benchmark
honours cpu_power.required again, do_sweep calls both legs, and dry-run
shows the block. power_energy_report is deliberately untouched: aligning
this leg's CSV header and location with the scraper (and adding
utilization) is follow-on work, noted in docs/cpu-power-telemetry.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… utilization per window

The host-side collector (telemetry.cpu_power) now watches DCGM CPU entity
fields 1100-1104 (total/user/nice/sys/irq) in the same field group as power
field 1130 and reads them in the same latest-values call. Five trailing
columns join every per-socket sample row (cpu_util_total, cpu_util_user,
cpu_util_nice, cpu_util_sys, cpu_util_irq); the samples schema moves from
v2 to v3. ACPI has no utilization, so those cells stay blank there, and the
per-node metadata lists the field ids and the DCGM unit (fraction of socket
CPU time). The GPU samples.csv is untouched: gpu_util_pct and sm_active
already live there.

The energy report gains utilization next to the joules. It reads the CPU
and GPU utilization columns by name (absent columns and blank cells are
skipped, so v1/v2 files still load), and for each concurrency window
reports the mean and max of the samples inside the window per CPU socket
and node, and per GPU, node, and role. Utilization is a gauge, so it is
summarized rather than integrated, and a window with no utilization
samples is a warning rather than an error. Discovery now also accepts the
host collector's cpu_power/samples.csv (previously misclassified as a GPU
file); when a run has both CPU legs, --cpu-samples picks one.

_sorted_series becomes generic in its key type, which clears the
long-standing invariance errors ty reported in this module.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on per window

Each concurrency window in the energy report now carries perf per watt:
output and total tokens/s over the computed window, GPU average watts and
CPU+GPU combined average watts (joules / duration), and the four tokens/s
per watt ratios. These are the reciprocal of the existing J/token figures
and are derived from the same window, so the two can never disagree. The
combined variants are None, with a warning, unless both a CPU and a GPU
leg produced samples, so they never silently equal the GPU-only number.

Alongside, three timelines for comparison (never validation):
  computed  -- the window the trapezoid integrates over, as before
  reported  -- the benchmark's own account: sa-bench's `duration` and
               wall-clock start/end; aiperf's aggregate benchmark_duration
               and start_time/end_time; failing that, the profiling-phase
               NOTICE lines in benchmark.out when exactly one phase ran
               (time-of-day only, so duration without absolute start/end)
  coverage  -- the first/last power samples actually spanned, and every
               breakdown row records its own sample_start/end and count

Text output gains `timing:` and `perf/W:` lines; JSON gains `timing` and
`perf_per_watt` objects and per-breakdown sample fields. The incremental
during-job report inherits all of it through report_to_dict.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…down row

Each breakdown row (socket, GPU, node, role) now carries a distribution of
the power samples the trapezoid actually spanned: mean_w, min_w, p5_w,
p50_w, p95_w, p99_w, max_w. The existing avg_power_w stays as the
time-weighted average (joules / duration), and mean_w is kept next to it
so any gap between the two under uneven sampling is visible rather than
hidden. Node and role rows are computed over their summed series, so a
node p99 is the 99th percentile of the node's total power, not a sum of
per-device percentiles.

The text table appends p50/p95/p99/max to each breakdown line; JSON gets
all seven fields, and the incremental report inherits them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
FrankD412 and others added 3 commits September 9, 2026 00:23
…ollector

The ACPI OEM-string naming for CPU power rails varies by platform (e.g.
"Grace Power Socket N" vs. a generic "Total Power socket N", some suffixed
with "in uW"). Kyle's host-side Python collector already accounted for all
these variants; the Rust exporter only recognized 3 literal substrings and
was missing the "Total Power" spelling, the "in uW" suffix, and the
CPU-rail/SoC-rail/DRAM domains entirely, silently dropping total_power_w on
platforms that don't report a "Grace" rail.

Renames the exporter's type= taxonomy to match Python's semantic kinds
(total/cpu_rail/soc/dram) and updates the downstream Prometheus scrape
parser to sum on kind=="total" instead of kind=="grace".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@FrankD412
FrankD412 marked this pull request as ready for review September 10, 2026 17:46
FrankD412 and others added 5 commits September 10, 2026 11:26
Two commits landed without running rustfmt, so both Rust workflows fail
at `cargo fmt --all --check` on six over-width lines in dcgm.rs and
main.rs. No behaviour change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…onger has

The wedged-collector test and its serve() helper reference Collector,
CollectRequest, mpsc and thread, none of which exist in this crate: the
port replaced the request/reply collector thread with a background poller
that fills an RwLock cache, so a scrape never touches sysfs and the 503
timeout path the test asserted on is gone. The test binary therefore
failed to compile, which CI never reached because rustfmt failed first.

Also removes the now-unused COLLECT_TIMEOUT constant and oneshot import.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…link

The arm64 release build fails with `ld: cannot find -ldl`. libloading
emits `-ldl` on every linux target, and a static link needs libdl.a, but
`--no-install-recommends gcc-aarch64-linux-gnu` only pulls the glibc
runtime cross package (libc6-arm64-cross); the dev package that ships
/usr/aarch64-linux-gnu/lib/libdl.a is a Recommends. The amd64 build was
unaffected because the base image already carries the host libc6-dev.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Commit 7640839 ported this test from ajc/rust-cpu-exporter, but upstream
dropped the aiperf/tachometer scrape exclusion in NVIDIA#396, so
generate_tachometer_config no longer accepts exclude_urls and the test
failed with a TypeError.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…URLs

get_hostname_ip can return an IPv6 literal, and the backend and frontend
scrape targets interpolated it bare, so the colons inside the address were
read as the port separator. Route both through url_host(), which the
benchmark stage already uses for the cpu-power-exporter targets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.17266% with 308 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@d0529aa). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/srtctl/core/cpu_power_exporter.py 0.00% 114 Missing ⚠️
src/srtctl/core/cpu_power.py 81.90% 57 Missing ⚠️
src/srtctl/core/cpu_power_session.py 73.14% 29 Missing ⚠️
src/srtctl/analysis/power_energy_report.py 95.13% 27 Missing ⚠️
src/srtctl/cli/mixins/telemetry_stage.py 81.96% 22 Missing ⚠️
src/srtctl/core/power/cpu_session.py 89.43% 15 Missing ⚠️
src/srtctl/analysis/incremental_power.py 91.94% 12 Missing ⚠️
src/srtctl/cli/mixins/postprocess_stage.py 78.37% 8 Missing ⚠️
src/srtctl/core/power/cpu_samples.py 90.80% 8 Missing ⚠️
src/srtctl/core/power/cpu_parser.py 91.78% 6 Missing ⚠️
... and 3 more
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #410   +/-   ##
=======================================
  Coverage        ?   75.03%           
=======================================
  Files           ?      109           
  Lines           ?    15988           
  Branches        ?        0           
=======================================
  Hits            ?    11997           
  Misses          ?     3991           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@FrankD412
FrankD412 merged commit 911c2cc into NVIDIA:main Sep 10, 2026
10 checks passed
ishandhanani added a commit that referenced this pull request Sep 13, 2026
Nine conflicting files, resolved by hand:

- The process exporter (#413) and the wider node-exporter collector set (#415)
  arrive as tachometer-stage exporter launches; on this branch the exporters
  are services. process-exporter is now a service kind (`type:
  process-exporter`, implied while tachometer runs, `placement.node: all`),
  host-native from the `configs/process-exporter` binary `make setup`
  installs (no container, no mounts), skipped with a warning when the binary
  is missing, container launch when a recipe declares a `container`. The
  group file is written by the kind's `prepare` hook; `ServiceKind` gains
  `host_native`, `prepare`, and `skip_reason`. node-exporter's built command
  carries the stat/vmstat/pressure/meminfo_numa/processes collectors and the
  widened vmstat field filter. The templates, the group YAML, and the host
  binary resolver move to `srtctl.services.exporters`; their tests follow.
- Load-window tachometer (#359): start/stop inside run_benchmark is kept;
  `stop_tachometer` now terminates through `ManagedProcess.terminate`, which
  signals the Slurm step (SIGTERM to the srun client would abort the step and
  SIGKILL the scraper). The scraper's `terminate_timeout` is the recipe's
  `shutdown_grace_secs`; the 90 s module constant is gone.
- Ingest timestamp fallback (#414) is taken from main wholesale (it also
  offers `--start-ns`); this branch's own fallback is dropped, its dedup
  module and docstring edits re-applied.
- CPU power telemetry (#410, #422): taken as is (power is frozen); the
  energy report runs before the S3 upload, which returns the URL only.
- Makefile: the `examples`/`golden-check` targets plus the cpu-power targets;
  the `recipes/`-based runner targets stay deleted. SUMMARY: both new pages,
  `analyzing.md` stays deleted. submit.py: the direct-host renderer import
  stays deleted; the arch helpers the cpu-power preflight uses are kept.
  telemetry.py: IPv6-safe host and the SGLang gateway metrics port together.

2355 tests on Python 3.10 and 3.13, lint, schema docs, 21 examples validated,
golden 574 identical / 0 mismatched.
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.

4 participants