jcp-energy: RAPL reads each package's energy-status MSR where powercap lists none #625

Merged
dcharlot merged 2 commits from jcp-energy/rapl-msr into main 2026-11-05 04:52:42 -05:00

2026-11-04

Transaction Science
jcp-energy: RAPL MSR domains are per die on Intel, per package on AMD; die zones are package zones
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 30s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m2s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, -p sandbox-tee --features amd-kds,intel-pcs, sandbox-rs, linux) (pull_request) Successful in 1m2s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m30s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m18s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m8s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 3m30s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m7s
energy meters on real hardware / sink decode table · per-task energy and the GPU rail on macOS (pull_request) Successful in 2m41s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 1m39s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 9m25s
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 35s
energy meters on real hardware / sink decode table · RAPL on jetson-hub (NVML surveyed) (pull_request) Successful in 2m22s
The MSR fallback grouped CPUs by physical_package_id alone, but Linux scopes
Intel's RAPL counters per die: arch/x86/events/rapl.c gives only AMD and Hygon
a per-package package energy counter, and a multi-die Intel part (Cascade
Lake-AP) has die-scope RAPL MSRs. Reading each package's first CPU there
counts one die, an under-count, the unsafe direction at an energy ceiling.
A counter domain is now, on Intel family 6, the pair (physical_package_id,
die_id), die 0 where the kernel lists no die_id; on AMD and Hygon it stays the
package. Each domain is read on its lowest-numbered CPU and all are summed,
under the same rules: every domain or none, a zero unit is no counter, the
wrap at 2^32 counts, powercap preferred. rapl_access asks the same domains. A
die domain's rail is package-<n>-die-<m> msr where the package has more than
one die, package-<n> msr where it has one.

The powercap side had the matching gap. On such a part intel_rapl_common.c
names each top-level zone "package-%d-die-%d" (the MSRs not package-scope and
more than one die per package), one intel-rapl:<n> zone per die, and the
package-name rule accepted only package-<n>: the host listed no package. The
rule now also accepts package-<digits>-die-<digits>, and nothing looser, so
every die zone is summed. The hardware test's oracle follows both rules.

Tests: an Intel package with two dies is two domains, each read on its own
first CPU and summed, with a one-die package beside it keeping its name; a die
whose device file is missing leaves no reader; access over the dies; an AMD
package whose CPUs list different die_ids is one domain; an Intel host with no
die_id is read per package; the name rule (package-0-die-1 yes; package-,
package-0-die-, package-0-core-0, package-0-die-1-die-2 no); a fake two-die
powercap tree summing both zones. Passing on Linux x86_64 (default, nvml,
jcp-gateway) and on macOS (the CI steps with their greps; clippy clean).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 21:20:55 -05:00
Transaction Science
jcp-energy: RAPL reads each package's energy-status MSR where powercap lists none
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 7s
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 41s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m1s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m33s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m33s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m37s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, -p sandbox-tee --features amd-kds,intel-pcs, sandbox-rs, linux) (pull_request) Successful in 2m12s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m47s
energy meters on real hardware / sink decode table · per-task energy and the GPU rail on macOS (pull_request) Successful in 2m49s
energy meters on real hardware / sink decode table · RAPL on jetson-hub (NVML surveyed) (pull_request) Successful in 2m34s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 3m26s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 5m33s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m20s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m30s
Some kernels ship without the powercap RAPL driver (Ubuntu without
linux-modules-extra, as on some rented bare-metal servers), so
/sys/class/powercap lists no package while the counters are there, and the
RAPL rung was Unavailable on hosts that can measure. Where powercap lists no
package zone, the reader now takes the same counters directly: each package's
MSR_PKG_ENERGY_STATUS (0x611, Intel family 6; 0xC001029B, AMD Zen and Hygon)
through /dev/cpu/<first cpu of the package>/msr, the 32-bit count converted at
the package's own unit (bits 12:8 of the power-unit MSR), so it wraps at 2^32
counts in microjoules. A power-unit MSR that reads zero is no counter (KVM
guests read zeros). Powercap is still preferred, a listed package zone this
process cannot read is no reason to read the MSRs instead, and the liveness
rule is unchanged: every package readable and live, or no figure.

rapl_access now asks the MSRs where powercap exposes no package zone, so a
host whose MSR device files need CAP_SYS_RAWIO reports NeedsPrivilege rather
than Absent; no msr module, an unknown CPU or a zero unit is NoInterface. The
hardware test's oracle reads the MSRs itself on such a host, so the status,
the gate and the package-only bracket are checked there too.

On two bare-metal servers with no powercap zone, as root: a Xeon E-2278G
live, 8.85 J over a 300 ms one-thread burn, inside the oracle's bracket; two
Xeon Silver 4215R sockets, package 1 read on CPU 8, live, 23.1 J over the same
burn. As an unprivileged user (no CAP_SYS_RAWIO) RAPL is Unavailable, its
access NeedsPrivilege, and the meter is the default-power estimate.

Tests: fake device files (a register's bytes at its offset) cover a two-socket
host read on each package's first CPU, the 32-bit wrap, AMD's registers, a zero
unit, an unknown CPU, a missing device, a counter that does not read, powercap
preferred, the production probe over MSR packages, and the access answer with
permission errors injected. Passing on Linux x86_64 (default and nvml) and on
macOS (the CI step, with its ioreport grep; jcp-gateway; clippy clean for
jcp-energy and jcp-gateway).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:59:46 -05:00