Skip to content
Merged
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
57 changes: 48 additions & 9 deletions .github/workflows/rdc-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ jobs:
if: ${{ (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }}
needs: rdc_build
runs-on: lstt-ci-runner
timeout-minutes: 30
timeout-minutes: 45
container: rocm/dev-ubuntu-22.04:7.2

steps:
Expand All @@ -230,8 +230,8 @@ jobs:
apt update -y
# rdctst pulls in libamd_smi's netlink (NIC) code, so the runtime
# libnl stack must be present or the binary fails to load.
apt install -y python3 python3-pip libnl-3-200 libnl-genl-3-200 libmnl0
pip3 install pytest prometheus_client
apt install -y python3 python3-pip libnl-3-200 libnl-genl-3-200 libmnl0 binutils
pip3 install pytest pytest-timeout prometheus_client

- name: Download RDC packages
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -284,11 +284,38 @@ jobs:
echo "No missing libs found!"
fi

- name: Check the installed fake-GPU suite
shell: bash
run: |
# TheRock runs this copy; it must import on its own and hold only the
# fake library, the suite and its value table.
suite="$ROCM_DIR/share/rdc/rdctst_tests/fake_gpu"
python3 -m pytest --collect-only -q -p no:cacheprovider "$suite"
extra=$(find "$suite" -type f ! -name '*.py' ! -name '*.so' ! -name '*.inc')
test -z "$extra" || { echo "unexpected files: $extra"; exit 1; }
env:
PYTHONDONTWRITEBYTECODE: "1"

- name: Run the fake-GPU suite (installed copy)
shell: bash
run: |
# rdcd and rdci against the fake amd-smi. A separate run, because rdcd
# allows one instance per host and the next step keeps one running.
python3 -m pytest "$ROCM_DIR/share/rdc/rdctst_tests/fake_gpu" \
-v --tb=short -p no:cacheprovider --timeout=180 \
--junitxml=build/test-results-fake-gpu.xml
env:
PYTHONDONTWRITEBYTECODE: "1"
RDC_TEST_PREFIX: ${{ env.ROCM_DIR }}
RDC_CI_REQUIRE_FAKE: "1"

- name: Run RDC test suite (no GPU)
if: ${{ !cancelled() }}
shell: bash
run: |
python3 -m pytest tests/ci \
-v --tb=short \
# Without a GPU, the daemon tests run against the fake amd-smi too.
python3 -m pytest tests/ci --ignore-glob='tests/ci/test_fake_gpu*.py' \
-v --tb=short --timeout=180 \
--junitxml=build/test-results-nogpu.xml
env:
RDC_SRC_DIR: ${{ github.workspace }}/projects/rdc/python_binding
Expand Down Expand Up @@ -329,8 +356,8 @@ jobs:
apt update -y
# rdctst pulls in libamd_smi's netlink (NIC) code, so the runtime
# libnl stack must be present or the binary fails to load.
apt install -y python3 python3-pip libnl-3-200 libnl-genl-3-200 libmnl0
pip3 install pytest prometheus_client
apt install -y python3 python3-pip libnl-3-200 libnl-genl-3-200 libmnl0 binutils
pip3 install pytest pytest-timeout prometheus_client

- name: Download RDC packages
uses: actions/download-artifact@v4
Expand All @@ -357,11 +384,23 @@ jobs:
- name: Create test results directory
run: mkdir -p build

- name: Run the fake-GPU suite (installed copy)
shell: bash
run: |
python3 -m pytest "$ROCM_DIR/share/rdc/rdctst_tests/fake_gpu" \
-v --tb=short -p no:cacheprovider --timeout=180 \
--junitxml=build/test-results-fake-gpu.xml
env:
PYTHONDONTWRITEBYTECODE: "1"
RDC_TEST_PREFIX: ${{ env.ROCM_DIR }}
RDC_CI_REQUIRE_FAKE: "1"

- name: Run RDC test suite (with GPU)
if: ${{ !cancelled() }}
shell: bash
run: |
python3 -m pytest tests/ci \
-v --tb=short \
python3 -m pytest tests/ci --ignore-glob='tests/ci/test_fake_gpu*.py' \
-v --tb=short --timeout=180 \
--junitxml=build/test-results-gpu.xml
env:
RDC_SRC_DIR: ${{ github.workspace }}/projects/rdc/python_binding
Expand Down
1 change: 1 addition & 0 deletions projects/rdc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ if(BUILD_TESTS)
# build root, not just from tests/unit.
enable_testing()
add_subdirectory("tests/unit")
add_subdirectory("tests/fake_amdsmi")
endif()

if(BUILD_EXAMPLES)
Expand Down
28 changes: 23 additions & 5 deletions projects/rdc/tests/ci/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,16 @@ def terminate_process(proc, timeout=10):
class RdcdServer:
"""The session rdcd. Only one rdcd can run per host (rdcd.lock)."""

def __init__(self, rdcd_path):
def __init__(self, rdcd_path, env=None):
self.rdcd_path = rdcd_path
self.env = env
self.proc = None

def start(self):
"""Start rdcd on port 50051; return False if it is not listening within 15 s."""
self.proc = subprocess.Popen(
[self.rdcd_path, "-u"], # unauthenticated mode
env=self.env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
Expand All @@ -145,17 +147,33 @@ def poll(self):
return self.proc.poll()


def fake_amdsmi(rocm_dir):
"""The fake amd-smi from tests/fake_amdsmi, if it is installed."""
for path in (
os.environ.get("RDC_FAKE_LIB"),
os.path.join(rocm_dir, "share", "rdc", "rdctst_tests", "fake_gpu", "librdc_fake_amdsmi.so"),
):
if path and os.path.isfile(path):
return path
return None


@pytest.fixture(scope="session")
def rdcd_server(rdcd_path, has_gpu):
def rdcd_server(rdcd_path, has_gpu, rocm_dir):
"""Start rdcd in unauthenticated mode for the test session.

Yields the RdcdServer. Cleans up on teardown.
Skips if no GPU is present (daemon won't be useful).
Without a GPU, rdcd runs against the fake amd-smi (four simulated GPUs)
when it is installed; otherwise the daemon tests are skipped.
"""
env = os.environ.copy()
if not has_gpu:
Comment thread
adam360x marked this conversation as resolved.
Resolved
pytest.skip("No AMD GPU detected -- skipping daemon tests")
fake = fake_amdsmi(rocm_dir)
if fake is None:
pytest.skip("No AMD GPU and no fake amd-smi -- skipping daemon tests")
env.update(LD_PRELOAD=fake, RDC_FAKE_GPUS="4", RDC_DISABLE_ROCP="yes")

server = RdcdServer(rdcd_path)
server = RdcdServer(rdcd_path, env)
if not server.start():
server.stop()
pytest.fail("rdcd did not start within 15 s")
Expand Down
71 changes: 71 additions & 0 deletions projects/rdc/tests/ci/fake_gpu_golden.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Expected RDC readings for the fake amd-smi in tests/fake_amdsmi.

The fake answers every amdsmi_* call with per-GPU readings from fake_values.inc. This
module reads the same table and converts each reading to the unit RDC documents for the
field (see `rdci dmon -l`), so tests compare RDC's output with an expectation that does
not depend on RDC's own conversion code.
"""

import os
import re

_ROW = re.compile(r"^FAKE_VALUE\((\w+),\s*(-?\d+),\s*(-?\d+),\s*(\d+)\)$")
_HERE = os.path.dirname(os.path.abspath(__file__))


def _table_path():
for path in (
os.path.join(_HERE, "fake_values.inc"), # installed next to this file
os.path.join(_HERE, "..", "fake_amdsmi", "fake_values.inc"), # source tree
):
if os.path.isfile(path):
return path
raise FileNotFoundError("fake_values.inc not found next to this file or in ../fake_amdsmi")


def _load():
table, rows = {}, 0
with open(_table_path()) as f:
for line in f:
rows += line.startswith("FAKE_VALUE(")
m = _ROW.match(line.strip())
if m:
table[m.group(1)] = tuple(int(m.group(i)) for i in (2, 3, 4))
if not table or len(table) != rows:
raise ValueError(f"parsed {len(table)} of {rows} FAKE_VALUE rows in fake_values.inc")
return table


TABLE = _load()

GIB = 1 << 30


def reading(name, gpu):
"""Raw reading `name` of GPU `gpu`, in amd-smi's unit."""
base, step, modulo = TABLE[name]
value = base + step * gpu
return value % modulo if modulo else value


# RDC field id -> (fake reading, factor to the unit RDC documents for the field).
FIELDS = {
100: ("GFX_CLK_MHZ", 1_000_000), # RDC_FI_GPU_CLOCK, Hz
101: ("MEM_CLK_MHZ", 1_000_000), # RDC_FI_MEM_CLOCK, Hz
200: ("TEMP_MEM_C", 1000), # RDC_FI_MEMORY_TEMP, millidegrees Celsius
201: ("TEMP_EDGE_C", 1000), # RDC_FI_GPU_TEMP, millidegrees Celsius
300: ("SOCKET_POWER_W", 1_000_000), # RDC_FI_POWER_USAGE, microwatts
500: ("GFX_ACTIVITY_PCT", 1), # RDC_FI_GPU_UTIL, percent
501: ("VRAM_USED_GIB", GIB), # RDC_FI_GPU_MEMORY_USAGE, bytes
502: ("VRAM_TOTAL_GIB", GIB), # RDC_FI_GPU_MEMORY_TOTAL, bytes
}


def expected(field_id, gpu):
"""Value RDC must report for `field_id` on fake GPU `gpu`."""
name, factor = FIELDS[field_id]
return reading(name, gpu) * factor


def device_name(gpu):
return f"Fake AMD Instinct GPU {gpu}"
Loading
Loading