From 931898675913c12c6d00045a0beb579f66232a94 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 12:06:39 -0400 Subject: [PATCH] ci: run the energy meters' RAPL and NVML tests on real hardware Every existing job runs in a container. Docker masks /sys/devices/virtual/powercap there and gives no GPU, so the energy crates' hardware tests only ever took their "unavailable, and the oracle agrees" branch. This job runs on `linux-hw`, a host executor on jetson-hub. It runs as an unprivileged user (no sudo, no docker) that can read powercap and open /dev/nvidia*, on an Intel i9-13900H with package-0, a core sub-zone, psys and an mmio package, and an RTX 4050 Laptop GPU. It runs jcp-energy, eoc-meter, sandbox-meter, sandbox-exec-microvm, proof-cascade and jouleclaw-energy, with their NVML features on. Each step fails unless the live path ran: - RAPL: the oracle's package-only bracket was printed; - NVML: the GPU was seen. The source is fetched with plain git (the host has no Node), and the step checks it got the commit the run is for. Target dirs persist in the runner's home between runs. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/energy-hardware.yml | 123 ++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 .github/workflows/energy-hardware.yml diff --git a/.github/workflows/energy-hardware.yml b/.github/workflows/energy-hardware.yml new file mode 100644 index 000000000..00118e5f3 --- /dev/null +++ b/.github/workflows/energy-hardware.yml @@ -0,0 +1,123 @@ +name: energy meters on real hardware + +# The Linux RAPL and NVML meters, run against real counters. +# +# Every other job runs in a container, where Docker masks +# /sys/devices/virtual/powercap and there is no GPU, so the energy crates' +# hardware tests only ever take their "unavailable, and the oracle agrees" +# branch there. This job runs on `linux-hw`: a host executor on jetson-hub +# (Intel i9-13900H: package-0, a core sub-zone, psys, an mmio package; NVIDIA +# RTX 4050 Laptop GPU), as an unprivileged user that can read +# /sys/class/powercap and open /dev/nvidia*. Here the live branch runs, and each +# step fails unless it did: +# +# - RAPL: the test printed the oracle's package-only bracket ("package-only +# inner ... outer ..."), which only the live path prints, after asserting the +# meter read inside it, below package+psys and below package+core. +# - NVML: the test saw the GPU ("nvml hardware: 1 GPU"). On this GPU the +# counter runs past the board while it is polled, so the test asserts the +# meter calls it Implausible and never grades it as hardware. +# +# The host has no Node, so there are no JavaScript actions: the source is +# fetched with plain git, and each workspace keeps its target directory in the +# runner's home between runs. + +on: + pull_request: + paths: + - 'jcp/jcp-rs/crates/jcp-energy/**' + - 'eoc/eoc-rs/crates/eoc-meter/**' + - 'sandbox/sandbox-rs/crates/sandbox-meter/**' + - 'sandbox/sandbox-rs/crates/sandbox-exec-microvm/**' + - 'proof/proof-rs/crates/proof-cascade/**' + - 'jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/**' + - '.github/workflows/energy-hardware.yml' + push: + branches: + - main + paths: + - 'jcp/jcp-rs/crates/jcp-energy/**' + - 'eoc/eoc-rs/crates/eoc-meter/**' + - 'sandbox/sandbox-rs/crates/sandbox-meter/**' + - 'sandbox/sandbox-rs/crates/sandbox-exec-microvm/**' + - 'proof/proof-rs/crates/proof-cascade/**' + - 'jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/**' + - '.github/workflows/energy-hardware.yml' + +jobs: + hardware: + name: RAPL and NVML on jetson-hub + runs-on: linux-hw + timeout-minutes: 150 + env: + CARGO_PROFILE_DEV_DEBUG: '0' + CARGO_BUILD_JOBS: '8' + steps: + - name: Fetch the source + shell: bash + run: | + set -euo pipefail + ref="${{ github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}" + want="${{ github.event.pull_request.head.sha || github.sha }}" + rm -rf src && mkdir src && cd src + git init -q + git fetch -q --depth 1 http://100.64.224.0:3400/Transaction-Science/open-standards.git "$ref" + git checkout -q FETCH_HEAD + got="$(git rev-parse HEAD)" + echo "fetched $ref at $got" + if [ "$got" != "$want" ]; then + echo "::error::$ref is at $got, not $want: it moved after this run started, and a newer run tests it" + exit 1 + fi + + - name: jcp-energy (RAPL, NVML) + shell: bash + working-directory: src/jcp/jcp-rs + run: | + set -euo pipefail + log=$(mktemp) + CARGO_TARGET_DIR="$HOME/hw-target/jcp-rs" nice -n 10 cargo test -p jcp-energy --features nvml --test rapl_hardware --test nvml_hardware -- --nocapture --test-threads=1 2>&1 | tee "$log" + grep -q 'package-only inner' "$log" || { echo "::error::the RAPL live path never ran"; exit 1; } + grep -q 'nvml hardware: 1 GPU' "$log" || { echo "::error::NVML saw no GPU"; exit 1; } + + - name: eoc-meter (RAPL, NVML) + shell: bash + working-directory: src/eoc/eoc-rs + run: | + set -euo pipefail + log=$(mktemp) + CARGO_TARGET_DIR="$HOME/hw-target/eoc-rs" nice -n 10 cargo test -p eoc-meter --features cuda --test rapl_hardware --test nvml_hardware -- --nocapture --test-threads=1 2>&1 | tee "$log" + grep -q 'package-only inner' "$log" || { echo "::error::the RAPL live path never ran"; exit 1; } + grep -q 'nvml hardware: 1 GPU' "$log" || { echo "::error::NVML saw no GPU"; exit 1; } + + - name: sandbox-meter (RAPL, NVML) and sandbox-exec-microvm (RAPL) + shell: bash + working-directory: src/sandbox/sandbox-rs + run: | + set -euo pipefail + log=$(mktemp) + CARGO_TARGET_DIR="$HOME/hw-target/sandbox-rs" nice -n 10 cargo test -p sandbox-meter --test rapl_hardware --test nvml_hardware -- --nocapture --test-threads=1 2>&1 | tee "$log" + grep -q 'package-only inner' "$log" || { echo "::error::sandbox-meter: the RAPL live path never ran"; exit 1; } + grep -q 'nvml hardware: 1 GPU' "$log" || { echo "::error::sandbox-meter: NVML saw no GPU"; exit 1; } + log=$(mktemp) + CARGO_TARGET_DIR="$HOME/hw-target/sandbox-rs" nice -n 10 cargo test -p sandbox-exec-microvm --test rapl_hardware -- --nocapture --test-threads=1 2>&1 | tee "$log" + grep -q 'package-only inner' "$log" || { echo "::error::sandbox-exec-microvm: the RAPL live path never ran"; exit 1; } + + - name: proof-cascade (RAPL) + shell: bash + working-directory: src/proof/proof-rs + run: | + set -euo pipefail + log=$(mktemp) + CARGO_TARGET_DIR="$HOME/hw-target/proof-rs" nice -n 10 cargo test -p proof-cascade --test rapl_hardware -- --nocapture --test-threads=1 2>&1 | tee "$log" + grep -q 'rapl hardware:.*oracle Live' "$log" || { echo "::error::the RAPL counter was not live to the oracle"; exit 1; } + + - name: jouleclaw-energy (RAPL, NVML) + shell: bash + working-directory: src/jouleclaw/jouleclaw-rs + run: | + set -euo pipefail + log=$(mktemp) + CARGO_TARGET_DIR="$HOME/hw-target/jouleclaw-rs" nice -n 10 cargo test -p jouleclaw-energy --features rapl,nvml --test rapl_hardware --test nvml_hardware -- --nocapture --test-threads=1 2>&1 | tee "$log" + grep -q 'package-only inner' "$log" || { echo "::error::the RAPL live path never ran"; exit 1; } + grep -q 'nvml hardware: 1 GPU' "$log" || { echo "::error::NVML saw no GPU"; exit 1; } -- 2.54.0 (Apple Git-157)