Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions .github/workflows/amd-exporter-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: AMD exporter image

on:
push:
branches: [chore/powerx-amd-exporter-image]
paths: [.github/workflows/amd-exporter-image.yml, tools/amd-exporter/**]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: amd-exporter-image-${{ github.ref }}
cancel-in-progress: false

jobs:
image:
name: Build and publish the AMD power exporter on CPU
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read # Reuse the pinned prepared runtime artifact.
packages: write # Publish the verified image to the repository-linked GHCR package.
timeout-minutes: 30
env:
SOURCE_SHA: 9d0eb8c88af99f1dbe2914d803382092671456d9
PATCH_SHA256: b0bb7d3477be7ff5b3b455d17f83c454d3aae96ac8bf2e289306d3fd3df0e7a5
GPU_AGENT_SHA: bcae57f9b395363f3340ad0b7393659b7df13f37
ARCHIVE_NAME: device-metrics-exporter-v1.5.4-10.2.0a20261001.tar.gz
ARCHIVE_SHA256: 0ba397bc31abc1c538e24f8d7264fb421cc5fb8dd59b2e1c493a8efec404c2ba
BASE_IMAGE: powerx-amd-recovered:runtime-36985488002
BASE_CONFIG_ID: sha256:aa151c70573fd5d3f94538c73adac6b235f052bb7dc9a8681ad2bfd0a8417929
RECOVERY_SQUASH_SHA256: 0f2cbb4e2c41c1506db74c6653ea09ec5e7d42236d9532e2bc38fb9e0be5879e
IMAGE_REF: ghcr.io/semianalysisai/amd-device-metrics-exporter:powerx-9d0eb8c-cache-${{ github.sha }}
ENROOT_PACKAGE: enroot_4.2.0-1_amd64.deb
ENROOT_PACKAGE_SHA256: 50bfcf8336bfe1a5d19481081d7800ae80300a27795595757f7d20957e079a62
steps:
- name: Checkout build recipe
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Checkout exact upstream source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ROCm/device-metrics-exporter
ref: 9d0eb8c88af99f1dbe2914d803382092671456d9
path: exporter-source
persist-credentials: false
- name: Install matching Go toolchain
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: 1.25.13
cache: false
- name: Download the retained prepared runtime
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
repository: SemiAnalysisAI/InferenceX
run-id: 36985488002
artifact-ids: 11217112734
github-token: ${{ github.token }}
path: ${{ runner.temp }}/amd-exporter-image
- name: Apply cache patch and test the exporter
run: |
set -eo pipefail
source inferencex-e2e/benchmarks/benchmark_lib.sh --validation-only
check_env_vars SOURCE_SHA PATCH_SHA256 GPU_AGENT_SHA GITHUB_WORKSPACE
printf '%s %s\n' "$PATCH_SHA256" tools/amd-exporter/cache.patch | sha256sum --check -
test "$(git -C exporter-source rev-parse HEAD)" = "$SOURCE_SHA"
git -C exporter-source apply "$GITHUB_WORKSPACE/tools/amd-exporter/cache.patch"
python3 -m unittest discover -s tools/amd-exporter -p 'test_*.py' -v
cd exporter-source
go test -race ./pkg/amdgpu/gpuagent -run 'Test(ParseGPUGetCacheTTL|CacheRead(HonorsGPUGetCacheTTL|ErrorClearsSlot|ConcurrentTTL)|NewAgentGPUGetCacheTTL)' -count=1
go test ./pkg/exporter -run TestExporterOptions -count=1
go test ./pkg/exporter/utils
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -C cmd/exporter -trimpath \
-ldflags "-X main.Version=1.5.4-powerx-cache -X main.GitCommit=$SOURCE_SHA+patch.$PATCH_SHA256 -X main.ROCmCommit=unknown" \
-o "$GITHUB_WORKSPACE/tools/amd-exporter/amd-metrics-exporter"
- name: Build and check the image without GPU allocation
env:
IMAGE_DIR: ${{ runner.temp }}/amd-exporter-image
OUTPUT_DIR: ${{ runner.temp }}/amd-exporter-output
run: |
set -eo pipefail
source inferencex-e2e/benchmarks/benchmark_lib.sh --validation-only
check_env_vars IMAGE_DIR OUTPUT_DIR RECOVERY_SQUASH_SHA256 BASE_IMAGE IMAGE_REF \
ENROOT_PACKAGE ENROOT_PACKAGE_SHA256 SOURCE_SHA PATCH_SHA256 GITHUB_REPOSITORY GITHUB_SHA
printf '%s %s\n' "$RECOVERY_SQUASH_SHA256" "$IMAGE_DIR/amd-exporter.sqsh" | sha256sum --check -
curl --fail --location --output "$IMAGE_DIR/$ENROOT_PACKAGE" \
"https://github.com/NVIDIA/enroot/releases/download/v4.2.0/$ENROOT_PACKAGE"
printf '%s %s\n' "$ENROOT_PACKAGE_SHA256" "$IMAGE_DIR/$ENROOT_PACKAGE" | sha256sum --check -
sudo apt-get update
sudo apt-get install --yes squashfs-tools "$IMAGE_DIR/$ENROOT_PACKAGE"
sudo unsquashfs -d "$IMAGE_DIR/rootfs" "$IMAGE_DIR/amd-exporter.sqsh"
mkdir -p "$OUTPUT_DIR"
sudo python3 tools/amd-exporter/recover-base.py "$IMAGE_DIR/rootfs" \
"$IMAGE_DIR/provenance.json" tools/amd-exporter/base-runtime.json \
"$BASE_IMAGE" "$OUTPUT_DIR/base-recovery.json"
docker build --pull=false --build-arg "BASE_IMAGE=$BASE_IMAGE" \
--label "org.opencontainers.image.source=https://github.com/$GITHUB_REPOSITORY" \
--label "org.opencontainers.image.revision=$GITHUB_SHA" \
--label "io.semianalysis.exporter.upstream=$SOURCE_SHA" \
--label "io.semianalysis.exporter.patch-sha256=$PATCH_SHA256" \
--tag "$IMAGE_REF" tools/amd-exporter
mkdir -p "$OUTPUT_DIR"
docker run --rm --entrypoint /home/amd/bin/server "$IMAGE_REF" --version | tee "$OUTPUT_DIR/version.txt"
if docker run --rm -e AMD_GPU_GET_CACHE_TTL=invalid --entrypoint /home/amd/bin/server "$IMAGE_REF" > "$OUTPUT_DIR/invalid-ttl.log" 2>&1; then
echo 'Invalid cache TTL unexpectedly started the exporter' >&2
exit 1
fi
grep -F 'invalid GPUGet cache configuration:' "$OUTPUT_DIR/invalid-ttl.log"
python3 tools/amd-exporter/verify-image.py "$BASE_IMAGE" "$IMAGE_REF"
- name: Publish immutable image
env:
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OUTPUT_DIR: ${{ runner.temp }}/amd-exporter-output
run: |
set -eo pipefail
source inferencex-e2e/benchmarks/benchmark_lib.sh --validation-only
check_env_vars GHCR_TOKEN GITHUB_ACTOR IMAGE_REF OUTPUT_DIR
printf '%s' "$GHCR_TOKEN" | docker login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
docker push "$IMAGE_REF"
docker image inspect "$IMAGE_REF" > "$OUTPUT_DIR/image-inspect.json"
- name: Prepare runner squash and provenance
env:
IMAGE_DIR: ${{ runner.temp }}/amd-exporter-image
OUTPUT_DIR: ${{ runner.temp }}/amd-exporter-output
run: |
set -eo pipefail
source inferencex-e2e/benchmarks/benchmark_lib.sh --validation-only
check_env_vars IMAGE_DIR OUTPUT_DIR IMAGE_REF ENROOT_PACKAGE ENROOT_PACKAGE_SHA256 SOURCE_SHA \
PATCH_SHA256 GPU_AGENT_SHA ARCHIVE_NAME ARCHIVE_SHA256 BASE_CONFIG_ID GITHUB_REPOSITORY \
GITHUB_RUN_ID GITHUB_RUN_ATTEMPT GITHUB_SHA
enroot import --output "$OUTPUT_DIR/amd-exporter.sqsh" "dockerd://$IMAGE_REF"
unsquashfs -s "$OUTPUT_DIR/amd-exporter.sqsh"
cp tools/amd-exporter/cache.patch "$OUTPUT_DIR/cache.patch"
cp tools/amd-exporter/amd-metrics-exporter "$OUTPUT_DIR/amd-metrics-exporter"
python3 tools/amd-exporter/provenance.py "$OUTPUT_DIR"
cd "$OUTPUT_DIR"
sha256sum amd-exporter.sqsh amd-metrics-exporter cache.patch > SHA256SUMS
- name: Upload image evidence and runner squash
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: amd-dme-cache-squash
path: ${{ runner.temp }}/amd-exporter-output/
if-no-files-found: error
compression-level: 0
4 changes: 4 additions & 0 deletions tools/amd-exporter/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
COPY --chmod=0755 amd-metrics-exporter /home/amd/bin/server
ENV AMD_GPU_GET_CACHE_TTL=0s
13 changes: 13 additions & 0 deletions tools/amd-exporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# AMD power exporter image

**English** | [中文](README_zh.md)

The CPU-only `AMD exporter image` workflow builds a fresh-sample exporter and publishes a unique tag to `ghcr.io/semianalysisai/amd-device-metrics-exporter`. It does not start a GPU benchmark or change production data.

The official ROCm nightly artifact from run `36867107435` has been deleted upstream. Reuse the retained Enroot conversion from InferenceX run `36985488002`, artifact `11217112734`, verified against squash SHA256 `0f2cbb4e2c41c1506db74c6653ea09ec5e7d42236d9532e2bc38fb9e0be5879e` and its original provenance. It contains source `9d0eb8c88af99f1dbe2914d803382092671456d9`. The runtime includes the upstream 255 W sentinel correction. `cache.patch` makes the exporter's GPUGet response-cache TTL a startup setting, `AMD_GPU_GET_CACHE_TTL`: `cmd/exporter/main.go` parses it once and fails fast on a bad value, the exporter and gpuagent options carry it the same way `exit-on-agent-down` travels, and `cacheRead` applies it. Unset keeps the upstream 15 s cache, so the patch changes nothing unless the variable is set; `0s` makes every scrape issue a new GPUGet. The locking, query timeout and error handling around the cache are untouched. Its tests cover the parser, the cache decision at 15 s, 1 s and 0 s, the error path, sixteen concurrent readers at `0s`, and the exporter and agent defaults. `0s` is the only value that guarantees a fresh read per scrape: the cache timestamp is taken after GPUGet returns, so any positive TTL serves cached data on some scrapes. Each read holds the cache lock for the duration of its RPC, which the one-hour MI355X run tolerated at a 1.01 s worst gap. It replaces the earlier copy of [functionstackx/device-metrics-exporter#1](https://github.com/functionstackx/device-metrics-exporter/pull/1), which read the variable twice and changed the default to 1 s.

`base-runtime.json` records the original Docker configuration and hashes/symlink targets for 61 critical paths, extracted from the retained official OCI archive. Recovery verifies GPUAgent, entrypoint, AMD-SMI, ROCm sysdeps and companion binaries before importing the root filesystem with the original environment, entrypoint, labels and working directory. Enroot flattened layers, normalized ownership/permissions and generated its `/etc` startup files; the reconstructed base has a new identity and is not the original OCI image. Only `/home/amd/bin/server` is replaced in the subsequent overlay. `AMD_GPU_GET_CACHE_TTL=0s` forces each exporter request to issue a new GPUGet RPC. This removes exporter response reuse; actual hardware freshness, overhead and one-second benchmark collection still require live validation.

The workflow verifies the patch checksum, runs focused cache race tests, builds Linux/amd64, checks the image's version and invalid-setting rejection, and verifies the unchanged reconstructed base layers and runtime configuration. It then publishes a commit-specific tag and saves an Enroot squash with `SHA256SUMS` and `provenance.json` in `amd-dme-cache-squash`. Consumers must record the published registry digest and the squash checksum. `base-recovery.json` records the recovery run/artifact, source receipt, runtime manifest hash, verified-file count and reconstructed image identity. The source commit, patch hash, GPUAgent commit and workflow revision are separate provenance fields; the embedded ROCm commit is explicitly unknown.

Dispatch this workflow manually on the reviewed branch, or push the dedicated `chore/powerx-amd-exporter-image` branch. The job has a 30-minute CPU limit and needs `packages: write`. It does not move a `latest` tag. A successful image build is preparation, not AMD measurement acceptance.
13 changes: 13 additions & 0 deletions tools/amd-exporter/README_zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# AMD 功耗 exporter 镜像

[English](README.md) | **中文**

仅使用 CPU 的 `AMD exporter image` 工作流构建可获取新读数的 exporter,并向 `ghcr.io/semianalysisai/amd-device-metrics-exporter` 发布唯一标签。它不启动 GPU 基准测试,也不修改生产数据。

官方 ROCm nightly 工作流 `36867107435` 的原始产物已被上游删除。现在复用 InferenceX 工作流 `36985488002` 保留的 Enroot 转换产物 `11217112734`,校验 squash SHA256 `0f2cbb4e2c41c1506db74c6653ea09ec5e7d42236d9532e2bc38fb9e0be5879e` 及原始来源回执。其源码为 `9d0eb8c88af99f1dbe2914d803382092671456d9`。该运行时包含上游 255 W 哨兵值修复。`cache.patch` 把 exporter 的 GPUGet 响应缓存 TTL 变成启动配置 `AMD_GPU_GET_CACHE_TTL`:`cmd/exporter/main.go` 解析一次并在取值非法时立即退出,exporter 与 gpuagent 的 options 像 `exit-on-agent-down` 一样把它传下去,最终由 `cacheRead` 应用。未设置时保持上游 15 s 缓存,因此不设该变量时补丁不改变任何行为;`0s` 让每次 scrape 都发起新的 GPUGet。缓存周围的加锁、查询超时和错误处理保持不变。测试覆盖解析器、15 s / 1 s / 0 s 三种缓存判定、错误路径、`0s` 下 16 个并发读取,以及 exporter 与 agent 的默认值。只有 `0s` 能保证每次 scrape 都是新读数:缓存时间戳在 GPUGet 返回后才记录,任何正的 TTL 都会让部分 scrape 拿到缓存数据。每次读取在 RPC 期间持有缓存锁,一小时的 MI355X 运行在最差 1.01 s 间隔下未受影响。它取代此前复制自 [functionstackx/device-metrics-exporter#1](https://github.com/functionstackx/device-metrics-exporter/pull/1) 的版本;旧版本读取该变量两次,并把默认值改成了 1 s。

`base-runtime.json` 从保留的官方 OCI 归档提取原始 Docker 配置,以及 61 个关键路径的文件哈希和符号链接目标。恢复过程先验证 GPUAgent、入口脚本、AMD-SMI、ROCm sysdeps 和配套二进制,再使用原始环境变量、入口、标签及工作目录导入根文件系统。Enroot 已合并镜像层、规范化所有权和权限,并生成 `/etc` 启动文件,因此重建基础镜像使用新标识,不能称为原始 OCI 镜像。之后仅在新增镜像层替换 `/home/amd/bin/server`。`AMD_GPU_GET_CACHE_TTL=0s` 强制每次 exporter 请求发起新的 GPUGet RPC。这消除了 exporter 响应复用;硬件实际刷新率、采集开销和基准测试中的一秒采集仍需实机验证。

工作流校验补丁摘要,运行缓存竞态测试,构建 Linux/amd64 二进制,检查版本与非法配置拒绝行为,并验证重建基础镜像层及运行时配置未变。随后发布提交专属标签,生成 Enroot squash,并将 `SHA256SUMS`、`provenance.json` 保存为 `amd-dme-cache-squash` 产物。使用方应记录发布的注册表摘要及 squash 校验值。`base-recovery.json` 记录恢复工作流和产物、原始来源回执、运行时清单哈希、已验证文件数及重建镜像标识。源码提交、补丁哈希、GPUAgent 提交和工作流版本分别记录;二进制中的 ROCm 提交明确标记为未知。

在已审阅分支手动触发,或推送专用分支 `chore/powerx-amd-exporter-image`。CPU 作业上限为 30 分钟,需要 `packages: write` 权限,不移动 `latest` 标签。镜像构建通过只代表准备完成,不代表 AMD 功耗测量已验收。
Loading
Loading