2
0

[rocprofiler-systems] - Implement subset of CTests into PyTests (#2666)

Convert a subset of the ctest to pytest to be used in TheRock CI.
Create a new cmake flag `ROCPROFSYS_INSTALL_TESTING` to control test suite installation.
- pytest package will be installed to share/rocprofiler-systems/tests
- all compiled examples are put in share/rocprofiler-systems/examples
- all test relevant scripts are put in share/rocprofiler-systems/tests
- see README.md in share/rocprofiler-systems/tests
Este cometimento está contido em:
Kian Cossettini
2026-01-26 23:10:01 -05:00
cometido por GitHub
ascendente 922762e9b9
cometimento 0eac446cb0
26 ficheiros modificados com 7339 adições e 80 eliminações
+4
Ver ficheiro
@@ -37,6 +37,10 @@
# Python cache files
*.pyc
# Python virtual environments
venv*
.venv*
# Documentation artifacts
/_build
_toc.yml
+53 -8
Ver ficheiro
@@ -1,3 +1,6 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
if(
@@ -153,27 +156,60 @@ if(CI_BUILD)
)
else()
rocprofiler_systems_add_option(ROCPROFSYS_BUILD_CI "Enable internal asserts, etc."
OFF ADVANCED NO_FEATURE
OFF ADVANCED NO_FEATURE
)
rocprofiler_systems_add_option(ROCPROFSYS_BUILD_EXAMPLES
"Enable building the examples" OFF ADVANCED
"Enable building the examples" OFF ADVANCED
)
rocprofiler_systems_add_option(ROCPROFSYS_INSTALL_EXAMPLES
"Install the examples" OFF
)
rocprofiler_systems_add_option(ROCPROFSYS_BUILD_TESTING
"Enable building the testing suite" OFF ADVANCED
"Enable building the testing suite" OFF ADVANCED
)
rocprofiler_systems_add_option(ROCPROFSYS_INSTALL_TESTING
"Install the test suite" OFF
)
rocprofiler_systems_add_option(
ROCPROFSYS_BUILD_DEBUG "Enable building with extensive debug symbols" OFF
ADVANCED
ROCPROFSYS_BUILD_DEBUG "Enable building with extensive debug symbols" OFF
ADVANCED
)
rocprofiler_systems_add_option(
ROCPROFSYS_BUILD_HIDDEN_VISIBILITY
"Build with hidden visibility (disable for Debug builds)" ON ADVANCED
ROCPROFSYS_BUILD_HIDDEN_VISIBILITY
"Build with hidden visibility (disable for Debug builds)" ON ADVANCED
)
rocprofiler_systems_add_option(ROCPROFSYS_STRIP_LIBRARIES "Strip the libraries"
${_STRIP_LIBRARIES_DEFAULT} ADVANCED
${_STRIP_LIBRARIES_DEFAULT} ADVANCED
)
endif()
rocprofiler_systems_add_option(ROCPROFSYS_BUILD_FOR_THEROCK "Build rocprofiler-systems for use with TheRock" OFF
ADVANCED NO_FEATURE
)
if(ROCPROFSYS_BUILD_FOR_THEROCK)
set(ROCPROFSYS_INSTALL_TESTING
ON
CACHE BOOL
"Install testing scripts and pytest package"
FORCE
)
set(ROCPROFSYS_USE_PYTESTS ON CACHE BOOL "Enable pytest suite" FORCE)
# Lulesh does not build with TheRock
if(NOT DEFINED ROCPROFSYS_DISABLE_EXAMPLES)
set(ROCPROFSYS_DISABLE_EXAMPLES
"lulesh"
CACHE STRING
"Disable building examples"
FORCE
)
else()
if(NOT "lulesh" IN_LIST ROCPROFSYS_DISABLE_EXAMPLES)
list(APPEND ROCPROFSYS_DISABLE_EXAMPLES "lulesh")
endif()
endif()
endif()
include(Compilers) # compiler identification
include(BuildSettings) # compiler flags
@@ -267,6 +303,15 @@ elseif("$ENV{ROCPROFSYS_CI}")
endif()
endif()
if(ROCPROFSYS_INSTALL_TESTING)
set(ROCPROFSYS_INSTALL_EXAMPLES ON CACHE BOOL "Enable installing examples" FORCE)
set(ROCPROFSYS_BUILD_TESTING ON CACHE BOOL "Enable building the testing suite" FORCE)
endif()
if(ROCPROFSYS_INSTALL_EXAMPLES)
set(ROCPROFSYS_BUILD_EXAMPLES ON CACHE BOOL "Enable building the examples" FORCE)
endif()
if(ROCPROFSYS_BUILD_TESTING)
set(ROCPROFSYS_BUILD_EXAMPLES ON CACHE BOOL "Enable building the examples" FORCE)
endif()
+1 -1
Ver ficheiro
@@ -642,7 +642,7 @@ function(CHECK_ROCMINFO _REGEX _RESULT_VARIABLE)
set(_failure TRUE)
endif()
if(DEFINED ARG_GET_OUTPUT)
if(ARG_GET_OUTPUT)
if(NOT _failure)
set(${_RESULT_VARIABLE} "${rocminfo_OUTPUT}" PARENT_SCOPE)
else()
+19
Ver ficheiro
@@ -0,0 +1,19 @@
# Requirements for rocprofiler-systems pytest test suite
#
# Install with:
# pip install -r requirements.txt
# Core testing framework
pytest>=7.4.0
pytest-subtests>=0.10.0
pytest-timeout>=2.0.0
pytest-xdist>=3.0.0
# Optional: Coverage reporting
pytest-cov>=4.0.0
# Perfetto trace processing (optional, for trace validation)
perfetto>=0.7.0
# Type checking support (optional)
typing-extensions>=4.0.0
@@ -102,7 +102,21 @@ if [ "$(basename ${1})" = "cmake" ] && [ "${2}" = "-E" ] && [ "${3}" = "__run_co
fi
fi
if [[ "${CXX_COMPILER}" != "${1}" ]]; then
# Handle ccache wrapper (the actual compiler is the next argument after ccache)
CCACHE_PREFIX=""
ACTUAL_COMPILER="${1}"
if [ "$(basename ${1})" = "ccache" ]; then
if [ -z "${2:-}" ]; then
echo -e "\nError: ${BASH_SOURCE[0]} detected 'ccache' as a compiler wrapper," >&2
echo "but no underlying compiler was specified as the next argument." >&2
echo "Usage: ccache <actual-compiler> [args...]" >&2
exit 1
fi
CCACHE_PREFIX="${1}"
ACTUAL_COMPILER="${2}"
fi
if [[ "${CXX_COMPILER}" != "${ACTUAL_COMPILER}" ]]; then
debug-message $@
# the command does not depend on rocprofiler-systems so just execute the command w/o re-directing to ${ROCPROFSYS_COMPILER}
eval $@
@@ -113,6 +127,11 @@ else
exit 1
fi
# discard ccache if present
if [ -n "${CCACHE_PREFIX}" ]; then
shift
fi
# discard the compiler from the command
shift
@@ -123,7 +142,12 @@ else
export LIBRARY_PATH=${LLVM_LIB_DIR}:${LIBRARY_PATH}
fi
debug-message ${ROCPROFSYS_COMPILER} $@
# execute ${ROCPROFSYS_COMPILER} (again, usually nvcc_wrapper)
${ROCPROFSYS_COMPILER} $@
# Use ccache with the rocprofiler-systems compiler if ccache was originally requested
if [ -n "${CCACHE_PREFIX}" ]; then
debug-message ${CCACHE_PREFIX} ${ROCPROFSYS_COMPILER} $@
${CCACHE_PREFIX} ${ROCPROFSYS_COMPILER} $@
else
debug-message ${ROCPROFSYS_COMPILER} $@
${ROCPROFSYS_COMPILER} $@
fi
fi
+116 -67
Ver ficheiro
@@ -1,81 +1,130 @@
# MIT License
#
# Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
#
# rocprofiler-systems tests
#
include_guard(GLOBAL)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-testing.cmake)
if(ROCPROFSYS_USE_PYTESTS)
include(${CMAKE_CURRENT_LIST_DIR}/pytest/CMakeLists.txt)
else()
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-testing.cmake)
# test groups
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-unit-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-config-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-instrument-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-pthread-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-rocm-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-user-api-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-mpi-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-ucx-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-kokkos-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-openmp-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-code-coverage-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-fork-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-time-window-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-attach-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-rccl-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-overflow-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-annotate-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-causal-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-python-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-decode-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-gpu-connect-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-nic-perf.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-roctx-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-rocm-hip-stream.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-binary-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-thread-limit-tests.cmake)
# test groups
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-unit-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-config-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-instrument-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-pthread-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-rocm-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-user-api-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-mpi-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-ucx-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-kokkos-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-openmp-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-code-coverage-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-fork-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-time-window-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-attach-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-rccl-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-overflow-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-annotate-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-causal-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-python-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-decode-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-gpu-connect-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-nic-perf.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-roctx-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-rocm-hip-stream.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-binary-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-thread-limit-tests.cmake)
# -------------------------------------------------------------------------------------- #
# -------------------------------------------------------------------------------------- #
#
# Global cleanup test for temporary files
# This runs once after ALL tests complete to clean up trace cache temporary files
# Uses FIXTURES_CLEANUP to ensure it runs after all tests requiring the fixture
#
# -------------------------------------------------------------------------------------- #
#delete temp files created by rocprofiler-sys tests in /tmp owned by the current user. Always return success.
add_test(
NAME rocprofsys-cleanup-tmp-files
COMMAND
sh -c
"find /tmp -maxdepth 1 -user $(whoami) \\( -name 'buffered_storage*.bin' -o -name 'metadata*.json' \\) -delete 2>/dev/null || true"
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
)
set_tests_properties(
rocprofsys-cleanup-tmp-files
PROPERTIES
FIXTURES_CLEANUP rocprofsys-global-tmp-files
LABELS "cleanup;global"
TIMEOUT 30
)
endif()
# ------------------------------------------------------------------------------#
#
# Global cleanup test for temporary files
# This runs once after ALL tests complete to clean up trace cache temporary files
# Uses FIXTURES_CLEANUP to ensure it runs after all tests requiring the fixture
# Move test files to build directory
#
# -------------------------------------------------------------------------------------- #
# ------------------------------------------------------------------------------#
#delete temp files created by rocprofiler-sys tests in /tmp owned by the current user. Always return success.
add_test(
NAME rocprofsys-cleanup-tmp-files
set(ROCPROFSYS_PYTHON_VALIDATION_FILES
${CMAKE_CURRENT_LIST_DIR}/validate-causal-json.py
${CMAKE_CURRENT_LIST_DIR}/validate-perfetto-proto.py
${CMAKE_CURRENT_LIST_DIR}/validate-rocpd.py
${CMAKE_CURRENT_LIST_DIR}/validate-timemory-json.py
)
set(ROCPROFSYS_TEST_SCRIPTS
${CMAKE_CURRENT_LIST_DIR}/run-rocprof-sys-pid.sh
${CMAKE_CURRENT_LIST_DIR}/get_default_nic.sh
${CMAKE_CURRENT_LIST_DIR}/generate_papi_nic_events.sh
)
add_custom_target(
copy-test-files
ALL
COMMAND
sh -c
"find /tmp -maxdepth 1 -user $(whoami) \\( -name 'buffered_storage*.bin' -o -name 'metadata*.json' \\) -delete 2>/dev/null || true"
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
${CMAKE_COMMAND} -E make_directory
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${ROCPROFSYS_PYTHON_VALIDATION_FILES}
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests
# copy_directory for directories (copy_if_different only works for files)
COMMAND
${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_LIST_DIR}/rocpd-validation-rules
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/rocpd-validation-rules
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${ROCPROFSYS_TEST_SCRIPTS}
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests
)
set_tests_properties(
rocprofsys-cleanup-tmp-files
PROPERTIES
FIXTURES_CLEANUP rocprofsys-global-tmp-files
LABELS "cleanup;global"
TIMEOUT 30
)
# ------------------------------------------------------------------------------#
#
# Pytests install
#
# ------------------------------------------------------------------------------#
if(ROCPROFSYS_INSTALL_TESTING)
# Python Validation scripts
install(
PROGRAMS ${ROCPROFSYS_PYTHON_VALIDATION_FILES}
DESTINATION share/rocprofiler-systems/tests
COMPONENT rocprofiler-systems-tests
)
install(
DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/rocpd-validation-rules
DESTINATION share/rocprofiler-systems/tests
COMPONENT rocprofiler-systems-tests
)
# Scripts
install(
PROGRAMS ${ROCPROFSYS_TEST_SCRIPTS}
DESTINATION share/rocprofiler-systems/tests
COMPONENT rocprofiler-systems-tests
)
endif()
+85
Ver ficheiro
@@ -0,0 +1,85 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
#
# rocprofiler-systems pytest install
#
include_guard(GLOBAL)
# ------------------------------------------------------------------------------#
# Pytest specific
# ------------------------------------------------------------------------------#
set(ROCPROFSYS_PYTEST_PACKAGE_FILES
${CMAKE_CURRENT_LIST_DIR}/rocprofsys/__init__.py
${CMAKE_CURRENT_LIST_DIR}/rocprofsys/config.py
${CMAKE_CURRENT_LIST_DIR}/rocprofsys/gpu.py
${CMAKE_CURRENT_LIST_DIR}/rocprofsys/runners.py
${CMAKE_CURRENT_LIST_DIR}/rocprofsys/validators.py
)
file(GLOB ROCPROFSYS_PYTEST_TEST_FILES "${CMAKE_CURRENT_LIST_DIR}/test_*.py")
set(ROCPROFSYS_PYTEST_FILES
${CMAKE_CURRENT_LIST_DIR}/conftest.py
${ROCPROFSYS_PYTEST_TEST_FILES}
)
add_custom_target(
copy-pytest-files
ALL
DEPENDS ${ROCPROFSYS_PYTEST_PACKAGE_FILES} ${ROCPROFSYS_PYTEST_FILES}
COMMAND
${CMAKE_COMMAND} -E make_directory
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/pytest/rocprofsys
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${ROCPROFSYS_PYTEST_PACKAGE_FILES}
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/pytest/rocprofsys/
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${ROCPROFSYS_PYTEST_FILES}
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/pytest/
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/requirements.txt
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/README.md
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/
COMMENT "Copying pytest files to build directory"
)
if(ROCPROFSYS_INSTALL_TESTING)
# Under install mode, run build_standalone.sh under default mode
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/rocprofsys-tests.pyz
COMMAND
${CMAKE_CURRENT_LIST_DIR}/build_standalone.sh --output-dir
${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests
DEPENDS
${CMAKE_CURRENT_LIST_DIR}/build_standalone.sh
${ROCPROFSYS_PYTEST_PACKAGE_FILES}
${ROCPROFSYS_PYTEST_FILES}
COMMENT "Building standalone pytest binary"
VERBATIM
)
add_custom_target(
build-standalone-pytest
ALL
DEPENDS ${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/rocprofsys-tests.pyz
)
install(
FILES ${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/rocprofsys-tests.pyz
DESTINATION share/rocprofiler-systems/tests
COMPONENT rocprofiler-systems-tests
)
install(
FILES ${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/requirements.txt
DESTINATION share/rocprofiler-systems/tests
COMPONENT rocprofiler-systems-tests
)
install(
FILES ${CMAKE_BINARY_DIR}/share/rocprofiler-systems/tests/README.md
DESTINATION share/rocprofiler-systems/tests
COMPONENT rocprofiler-systems-tests
)
endif()
+149
Ver ficheiro
@@ -0,0 +1,149 @@
# rocprofiler-systems Pytest Suite
## General Use
### Setup
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### Running Tests
Tests can run in two modes: **build** or **install**.
#### Build Mode (Default)
Runs tests using binaries from your build directory.
```bash
cd <path to rocprofiler-systems>
pytest <build-dir>/share/rocprofiler-systems/tests/pytest/
```
Default output directory: `<build-dir>/rocprof-sys-pytest-output/`
If auto detection of the build directory fails, specify `ROCPROFSYS_BUILD_DIR=<path to build-dir>`
#### Install Mode
Runs tests using binaries from your install location.
```bash
ROCPROFSYS_INSTALL_DIR=<install prefix> pytest <build-dir>/share/rocprofiler-systems/tests/pytest/
# Using /opt/rocprofiler-systems
ROCPROFSYS_INSTALL_DIR=/opt/rocprofiler-systems pytest <build-dir>/share/rocprofiler-systems/tests/pytest/
```
Default output directory: `/tmp/$USER/rocprof-sys-pytest-output/`
> **Note:** Install mode requires `ROCPROFSYS_INSTALL_TESTING=ON` during build.
#### Using the Standalone Package
A standalone `.pyz` package is included at `<install-dir>/share/rocprofiler-systems/tests/rocprofsys-tests.pyz`. This can be run directly with Python:
```bash
python3 <install-dir>/share/rocprofiler-systems/tests/rocprofsys-tests.pyz
```
All standard pytest flags work with the standalone package.
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `ROCPROFSYS_BUILD_DIR` | Path to build directory | Auto-detected |
| `ROCPROFSYS_INSTALL_DIR` | Path to install prefix (enables install mode) | Not set |
| `ROCPROFSYS_SOURCE_DIR` | Path to source directory | Auto-detected |
| `ROCPROFSYS_KEEP_TEST_OUTPUT` | Keep test output on success (`ON`/`OFF`) | `ON` |
| `ROCPROFSYS_USE_ROCPD` | Enable/disable ROCpd validation (`ON`/`OFF`) | `ON` if available |
| `ROCPROFSYS_VALIDATE_PERFETTO` | Enable/disable Perfetto tracing (`ON`/`OFF`) | `ON` if available|
| `ROCPROFSYS_TRACE_PROCESSOR_SHELL` | Path to trace_processor_shell binary | Auto-detected |
| `ROCM_PATH` | Path to ROCm installation | `/opt/rocm` |
### Common Commands
**Running by marker** (`-m`): Use for running groups of tests with specific labels.
```bash
# See all available markers
pytest --markers
# Run tests with a specific marker
pytest <test-path> -m gpu
pytest <test-path> -m "slow and gpu"
pytest <test-path> -m "not slow"
```
**Running by keyword** (`-k`): Use for running specific test classes or methods.
```bash
# Run tests matching a keyword
pytest <test-path> -k transpose
pytest <test-path> -k "TestTranspose and sampling"
pytest <test-path> -k "not binary_rewrite"
```
**Quick Start Examples:**
| Mode | Command |
|------|---------|
| Run all tests | `pytest <test-path>` |
| Recommended | `pytest <test-path> -n auto -v --show-output-on-subtest-fail --show-config` |
| Standalone package | `python3 <pyz-path>` |
Where `<test-path>` is `<build-dir>/share/rocprofiler-systems/tests/pytest/`
and `<pyz-path>` is `<install-dir>/share/rocprofiler-systems/tests/rocprofsys-tests.pyz`.
### Parallel Execution (pytest-xdist)
Tests can be run in parallel using `pytest-xdist`:
```bash
pytest <build-dir>/share/rocprofiler-systems/tests/pytest/ -n auto # Use all available cores
pytest <build-dir>/share/rocprofiler-systems/tests/pytest/ -n 4 # Use 4 workers
```
> **Warning:** Running tests in parallel can cause timeouts due to resource contention, especially for `runtime_instrument` tests. If you experience unexpected timeouts, try reducing the number of workers or running sequentially.
### Custom Flags
| Flag | Description |
|------|-------------|
| `--show-config` | Show test configuration in the pytest header |
| `--show-output` | Show runner output when tests **pass** |
| `--show-output-on-subtest-fail` | Show runner output only when **subtests** fail |
| `--output-dir=<path>` | Set the test output directory (default: `<build_dir>/pytest-output`) |
| `--output-log=<path>` | Write pytest output to the specified file (default: `<output_dir>/pytest-output.txt`) |
| `--monochrome` | Disable colored output and set `ROCPROFSYS_MONOCHROME=ON` for runners |
| `--allow-disabled` | Run tests with `@pytest.mark.disable` in CI mode (developer flag) |
**Tip:** Use `--tb=short` to hide source code in tracebacks, or `--tb=no` for no output.
#### Output Display Logic
The `_result_output` fixture controls when runner output is printed:
| Scenario | Default | `--show-output-on-subtest-fail` | `--show-output` |
|----------|---------|--------------------------------|-----------------|
| Test passes | ❌ | ❌ | ✅ |
| Subtest fails | ❌ | ✅ | ✅ |
| Main test fails | ✅ | ✅ | ✅ |
**Note:** With `--show-output`, runner output appears *before* the failure report. With `--show-output-on-subtest-fail`, it appears *after* (in the FAILURES section). This is due to how pytest processes report sections.
#### Perfetto GLIBC Issue
If Perfetto validation fails due to GLIBC version mismatch (this may occur on RHEL-8.x or SUSE-15.5), set `ROCPROFSYS_TRACE_PROCESSOR_PATH` to a compatible binary.
```bash
curl -L https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/trace_processor_shell -o /tmp/$USER/trace_processor_shell
chmod +x /tmp/$USER/trace_processor_shell
export ROCPROFSYS_TRACE_PROCESSOR_PATH=/tmp/$USER/trace_processor_shell
```
Then run pytest with the environment variable set.
+643
Ver ficheiro
@@ -0,0 +1,643 @@
#!/bin/bash
#
# Build standalone pytest executables for rocprofiler-systems tests
#
# This script creates packaging options:
# 1. PyInstaller: Single binary (~50-100MB), no Python needed on target
# 2. PyInstaller+Docker: Uses manylinux for broad glibc compatibility
# 3. Shiv: Python zipapp (~5MB), requires Python on target
#
# Usage:
# ./build_standalone.sh [--pyinstaller] [--pyinstaller-docker] [--shiv] [--all] [--output-dir DIR]
#
# After building, copy the output to your target machine and run:
# PyInstaller: ./rocprofsys-tests [pytest args...]
# Shiv: python3 rocprofsys-tests.pyz [pytest args...]
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT_DIR="${SCRIPT_DIR}/dist"
BUILD_PYINSTALLER=0
BUILD_PYINSTALLER_DOCKER=0
BUILD_SHIV=0
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--pyinstaller)
BUILD_PYINSTALLER=1
shift
;;
--pyinstaller-docker)
BUILD_PYINSTALLER_DOCKER=1
shift
;;
--shiv)
BUILD_SHIV=1
shift
;;
--all)
BUILD_PYINSTALLER=1
BUILD_SHIV=1
shift
;;
--all-docker)
BUILD_PYINSTALLER_DOCKER=1
BUILD_SHIV=1
shift
;;
--output-dir)
OUTPUT_DIR="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [--pyinstaller] [--pyinstaller-docker] [--shiv] [--all] [--output-dir DIR]"
echo ""
echo "Options:"
echo " --pyinstaller Build PyInstaller binary (uses system Python/glibc)"
echo " --pyinstaller-docker Build PyInstaller binary in Docker (glibc 2.17+ compatible)"
echo " --shiv Build Shiv zipapp (requires Python on target)"
echo " --all Build pyinstaller + shiv"
echo " --all-docker Build pyinstaller-docker + shiv"
echo " --output-dir Output directory (default: ./dist)"
echo ""
echo "NOTE: If you get glibc errors on target, use --pyinstaller-docker or --shiv"
echo ""
echo "Examples:"
echo " $0 --all # Build pyinstaller + shiv"
echo " $0 --pyinstaller-docker # Build compatible binary via Docker"
echo " $0 --shiv --output-dir /tmp # Build Shiv to /tmp"
exit 0
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Default to shiv if nothing specified (safest option)
if [[ $BUILD_PYINSTALLER -eq 0 && $BUILD_PYINSTALLER_DOCKER -eq 0 && $BUILD_SHIV -eq 0 ]]; then
BUILD_SHIV=1
echo "No option specified, defaulting to --shiv (most compatible)"
fi
mkdir -p "$OUTPUT_DIR"
echo "=============================================="
echo "Building rocprofiler-systems pytest packages"
echo "=============================================="
echo "Source directory: $SCRIPT_DIR"
echo "Output directory: $OUTPUT_DIR"
echo ""
# Create the test runner wrapper script
create_runner_script() {
cat > "${SCRIPT_DIR}/run_rocprofsys_tests.py" << 'RUNNER_EOF'
#!/usr/bin/env python3
"""
Standalone test runner for rocprofiler-systems pytest tests.
This script is designed to be packaged with PyInstaller or Shiv to create
a standalone executable for running tests on machines with rocprofiler-systems
installed.
Usage:
./rocprofsys-tests [pytest options...]
Examples:
./rocprofsys-tests # Run all tests
./rocprofsys-tests -v # Verbose output
./rocprofsys-tests -k transpose # Run only transpose tests
./rocprofsys-tests --collect-only # List available tests
./rocprofsys-tests test_transpose.py # Run specific test file
Environment Variables:
ROCPROFSYS_INSTALL_DIR - Path to rocprofiler-systems installation
ROCPROFSYS_BUILD_DIR - Path to build directory (for development)
ROCPROFSYS_SOURCE_DIR - Path to source directory (for development)
ROCPROFSYS_KEEP_TEST_OUTPUT - Keep test output on success (ON/OFF, default: ON)
ROCPROFSYS_USE_ROCPD - Enable/disable ROCpd validation (ON/OFF, default: ON if available)
ROCPROFSYS_VALIDATE_PERFETTO - Enable/disable Perfetto validation (ON/OFF, default: ON if available)
ROCPROFSYS_TRACE_PROC_SHELL - Path to trace_processor_shell binary (auto-detected)
ROCM_PATH - Path to ROCm installation (default: /opt/rocm)
ROCM_LLVM_OBJDUMP - Path to ROCm's llvm-objdump (default: auto-detected)
"""
import os
import sys
def get_test_dir():
"""Find the tests directory - handles both packaged and development modes."""
# When packaged with PyInstaller, files are extracted to _MEIPASS
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
test_dir = os.path.join(base_path, 'tests', 'pytest')
if os.path.isdir(test_dir):
return test_dir
# Fallback: tests might be at root level
test_dir = os.path.join(base_path, 'pytest')
if os.path.isdir(test_dir):
return test_dir
return base_path
else:
# Running as regular Python script
return os.path.dirname(os.path.abspath(__file__))
def main():
import pytest
test_dir = get_test_dir()
# Add test directory to path so imports work
if test_dir not in sys.path:
sys.path.insert(0, test_dir)
# Build pytest arguments
args = list(sys.argv[1:])
# If no test path specified, use the test directory
has_test_path = any(
arg.endswith('.py') or
os.path.isdir(arg) or
'::' in arg
for arg in args if not arg.startswith('-')
)
if not has_test_path:
args.append(test_dir)
# Print info
print(f"rocprofiler-systems pytest runner")
print(f"Test directory: {test_dir}")
print(f"Arguments: {' '.join(args)}")
print("-" * 50)
# Run pytest
return pytest.main(args)
if __name__ == "__main__":
sys.exit(main())
RUNNER_EOF
echo "Created: run_rocprofsys_tests.py"
}
# Build with PyInstaller
build_pyinstaller() {
echo ""
echo "=== Building PyInstaller standalone binary ==="
echo ""
# Check if PyInstaller and required packages are installed
if ! python3 -c "import PyInstaller" 2>/dev/null; then
echo "Installing PyInstaller..."
pip install pyinstaller
fi
# Install pytest plugins needed for bundling
echo "Installing pytest and required plugins..."
pip install pytest pytest-subtests pytest-timeout pytest-xdist
# Create spec file for more control
cat > "${SCRIPT_DIR}/rocprofsys_tests.spec" << SPEC_EOF
# -*- mode: python ; coding: utf-8 -*-
import os
block_cipher = None
# Collect all test files and the rocprofsys package
test_dir = '${SCRIPT_DIR}'
datas = []
# Add all Python files from the test directory
for root, dirs, files in os.walk(test_dir):
# Skip __pycache__ and build directories
dirs[:] = [d for d in dirs if d not in ('__pycache__', 'dist', 'build')]
for f in files:
if f.endswith(('.py', '.txt', '.md', '.json')):
src = os.path.join(root, f)
# Compute relative destination
rel_path = os.path.relpath(root, test_dir)
if rel_path == '.':
dst = 'tests/pytest'
else:
dst = os.path.join('tests/pytest', rel_path)
datas.append((src, dst))
a = Analysis(
['${SCRIPT_DIR}/run_rocprofsys_tests.py'],
pathex=['${SCRIPT_DIR}'],
binaries=[],
datas=datas,
hiddenimports=[
'pytest',
'_pytest',
'_pytest.assertion',
'_pytest.config',
'_pytest.fixtures',
'_pytest.python',
'pytest_subtests',
'pytest_subtests.plugin',
'pytest_timeout',
'xdist',
'rocprofsys',
'rocprofsys.config',
'rocprofsys.runners',
'rocprofsys.validators',
'rocprofsys.gpu',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='rocprofsys-tests',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
SPEC_EOF
# Build with PyInstaller
cd "$SCRIPT_DIR"
python3 -m PyInstaller \
--distpath "$OUTPUT_DIR" \
--workpath "${SCRIPT_DIR}/build/pyinstaller" \
--clean \
--noconfirm \
rocprofsys_tests.spec
# Cleanup
rm -f "${SCRIPT_DIR}/rocprofsys_tests.spec"
rm -rf "${SCRIPT_DIR}/build/pyinstaller"
echo ""
echo "PyInstaller build complete!"
echo "Binary: ${OUTPUT_DIR}/rocprofsys-tests"
echo "Size: $(du -h "${OUTPUT_DIR}/rocprofsys-tests" | cut -f1)"
}
# Build with PyInstaller in Docker (manylinux for glibc compatibility)
build_pyinstaller_docker() {
echo ""
echo "=== Building PyInstaller binary in Docker (manylinux) ==="
echo ""
# Check if Docker is available
if ! command -v docker &> /dev/null; then
echo "ERROR: Docker is not installed or not in PATH"
echo "Install Docker or use --shiv instead"
exit 1
fi
# Create a temporary build context
BUILD_CONTEXT=$(mktemp -d)
trap "rm -rf \"$BUILD_CONTEXT\"" EXIT
# Copy test files to build context
cp -r "${SCRIPT_DIR}" "${BUILD_CONTEXT}/pytest"
cp "${SCRIPT_DIR}/run_rocprofsys_tests.py" "${BUILD_CONTEXT}/" 2>/dev/null || \
create_runner_script && cp "${SCRIPT_DIR}/run_rocprofsys_tests.py" "${BUILD_CONTEXT}/"
# Create Dockerfile
cat > "${BUILD_CONTEXT}/Dockerfile" << 'DOCKERFILE_EOF'
# Use manylinux for broad glibc compatibility (glibc 2.17+)
FROM quay.io/pypa/manylinux2014_x86_64
# Install Python and pip
RUN /opt/python/cp310-cp310/bin/python -m pip install --upgrade pip
RUN /opt/python/cp310-cp310/bin/python -m pip install pyinstaller pytest pytest-subtests pytest-timeout pytest-xdist
# Set Python path
ENV PATH="/opt/python/cp310-cp310/bin:$PATH"
WORKDIR /build
# Copy test files
COPY pytest /build/pytest
COPY run_rocprofsys_tests.py /build/
# Create spec file
RUN cat > /build/rocprofsys_tests.spec << 'SPEC_EOF'
# -*- mode: python ; coding: utf-8 -*-
import os
block_cipher = None
test_dir = '/build/pytest'
datas = []
for root, dirs, files in os.walk(test_dir):
dirs[:] = [d for d in dirs if d not in ('__pycache__', 'dist', 'build')]
for f in files:
if f.endswith(('.py', '.txt', '.md', '.json')):
src = os.path.join(root, f)
rel_path = os.path.relpath(root, test_dir)
if rel_path == '.':
dst = 'tests/pytest'
else:
dst = os.path.join('tests/pytest', rel_path)
datas.append((src, dst))
a = Analysis(
['/build/run_rocprofsys_tests.py'],
pathex=['/build/pytest'],
binaries=[],
datas=datas,
hiddenimports=[
'pytest', '_pytest', '_pytest.assertion', '_pytest.config',
'_pytest.fixtures', '_pytest.python',
'pytest_subtests', 'pytest_subtests.plugin', 'pytest_timeout', 'xdist',
'rocprofsys', 'rocprofsys.config', 'rocprofsys.runners',
'rocprofsys.validators', 'rocprofsys.gpu',
],
hookspath=[],
runtime_hooks=[],
excludes=[],
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [],
name='rocprofsys-tests',
debug=False,
strip=True,
upx=False,
console=True,
)
SPEC_EOF
# Build
RUN pyinstaller --clean --noconfirm rocprofsys_tests.spec
# Output is in /build/dist/rocprofsys-tests
DOCKERFILE_EOF
echo "Building Docker image..."
docker build -t rocprofsys-tests-builder "${BUILD_CONTEXT}"
echo "Extracting binary from container..."
CONTAINER_ID=$(docker create rocprofsys-tests-builder)
docker cp "${CONTAINER_ID}:/build/dist/rocprofsys-tests" "${OUTPUT_DIR}/rocprofsys-tests"
docker rm "${CONTAINER_ID}"
# Cleanup
docker rmi rocprofsys-tests-builder 2>/dev/null || true
echo ""
echo "PyInstaller (Docker/manylinux) build complete!"
echo "Binary: ${OUTPUT_DIR}/rocprofsys-tests"
echo "Size: $(du -h "${OUTPUT_DIR}/rocprofsys-tests" | cut -f1)"
echo ""
echo "This binary is compatible with glibc 2.17+ (RHEL 7, Ubuntu 14.04+, etc.)"
}
# Build simple zipapp (requires pytest on target, but no glibc issues)
build_shiv() {
echo ""
echo "=== Building Python zipapp ==="
echo ""
# Create a temporary directory
BUILD_DIR=$(mktemp -d)
echo "Build directory: $BUILD_DIR"
# Create the package structure
mkdir -p "${BUILD_DIR}/rocprofsys"
mkdir -p "${BUILD_DIR}/tests"
# Copy the rocprofsys test framework package
cp -r "${SCRIPT_DIR}/rocprofsys/"* "${BUILD_DIR}/rocprofsys/"
# Copy test files
cp "${SCRIPT_DIR}"/test_*.py "${BUILD_DIR}/tests/" 2>/dev/null || true
cp "${SCRIPT_DIR}/conftest.py" "${BUILD_DIR}/tests/"
# Ensure __init__.py exists
touch "${BUILD_DIR}/rocprofsys/__init__.py"
touch "${BUILD_DIR}/tests/__init__.py"
# Create __main__.py (entry point for zipapp)
cat > "${BUILD_DIR}/__main__.py" << 'MAIN_EOF'
#!/usr/bin/env python3
"""
rocprofiler-systems pytest runner - zipapp version
Usage:
python3 rocprofsys-tests.pyz [pytest options...]
Requirements:
- pytest must be installed: pip install pytest
- rocprofiler-systems must be installed on the system
Examples:
python3 rocprofsys-tests.pyz --collect-only
python3 rocprofsys-tests.pyz -v
python3 rocprofsys-tests.pyz -k transpose -v
"""
import os
import sys
import zipfile
import tempfile
import shutil
import atexit
# Global for cleanup
_extract_dir = None
def cleanup():
"""Remove extracted files on exit."""
global _extract_dir
if _extract_dir and os.path.isdir(_extract_dir):
shutil.rmtree(_extract_dir, ignore_errors=True)
def extract_tests():
"""Extract tests from zipapp to temp directory."""
global _extract_dir
# Find the zipapp path
# When running as zipapp, __file__ points inside the zip
# The zip path is everything before the first component after .pyz
zipapp_path = None
for path in sys.path:
if path.endswith('.pyz') and os.path.isfile(path):
zipapp_path = path
break
if not zipapp_path:
# Try to find it from __file__
current = os.path.abspath(__file__)
while current and not current.endswith('.pyz'):
parent = os.path.dirname(current)
if parent == current:
break
current = parent
if current.endswith('.pyz'):
zipapp_path = current
if not zipapp_path or not os.path.isfile(zipapp_path):
# Not running from zipapp, use local directory
return os.path.dirname(os.path.abspath(__file__))
# Create temp directory and extract
_extract_dir = tempfile.mkdtemp(prefix='rocprofsys-tests-')
atexit.register(cleanup)
with zipfile.ZipFile(zipapp_path, 'r') as zf:
zf.extractall(_extract_dir)
return _extract_dir
def main():
# Check pytest is available
try:
import pytest
except ImportError:
print("ERROR: pytest is not installed")
print("Please install it: pip install pytest")
sys.exit(1)
# Extract tests to temp directory
app_path = extract_tests()
# Add app path to sys.path for imports
if app_path not in sys.path:
sys.path.insert(0, app_path)
# Find tests directory
tests_dir = os.path.join(app_path, 'tests')
if not os.path.isdir(tests_dir):
print(f"ERROR: Tests directory not found: {tests_dir}")
print(f"Contents of {app_path}:")
for item in os.listdir(app_path):
print(f" {item}")
sys.exit(1)
# Build pytest arguments
args = list(sys.argv[1:])
# Check if user specified a test path
has_test_path = any(
(arg.endswith('.py') or os.path.isdir(arg) or '::' in arg)
for arg in args if not arg.startswith('-')
)
if not has_test_path:
args.append(tests_dir)
# Print info
print("=" * 60)
print("rocprofiler-systems pytest runner")
print("=" * 60)
print(f"Tests dir: {tests_dir}")
print(f"Command: pytest {' '.join(args)}")
print("=" * 60)
print()
# Run pytest
return pytest.main(args)
if __name__ == "__main__":
sys.exit(main())
MAIN_EOF
# Create the zipapp (don't use --main since we have __main__.py)
cd "$BUILD_DIR"
python3 -m zipapp \
--python "/usr/bin/env python3" \
--output "${OUTPUT_DIR}/rocprofsys-tests.pyz" \
--compress \
.
# Make it executable
chmod +x "${OUTPUT_DIR}/rocprofsys-tests.pyz"
# Cleanup
rm -rf "$BUILD_DIR"
echo ""
echo "Zipapp build complete!"
echo "Output: ${OUTPUT_DIR}/rocprofsys-tests.pyz"
echo "Size: $(du -h "${OUTPUT_DIR}/rocprofsys-tests.pyz" | cut -f1)"
echo ""
echo "Requirements on target machine:"
echo " - Python 3.8+"
echo " - Install dependencies: pip install pytest pytest-subtests pytest-timeout pytest-xdist"
}
# Main build process
create_runner_script
if [[ $BUILD_PYINSTALLER -eq 1 ]]; then
build_pyinstaller
fi
if [[ $BUILD_PYINSTALLER_DOCKER -eq 1 ]]; then
build_pyinstaller_docker
fi
if [[ $BUILD_SHIV -eq 1 ]]; then
build_shiv
fi
# Cleanup runner script
rm -f "${SCRIPT_DIR}/run_rocprofsys_tests.py"
echo ""
echo "=============================================="
echo "Build complete!"
echo "=============================================="
echo ""
echo "Output files in: $OUTPUT_DIR"
ls -lh "$OUTPUT_DIR" 2>/dev/null || echo "(no files yet)"
echo ""
echo "=== How to use on target machine ==="
echo ""
echo "1. Copy the binary/zipapp to your target machine"
echo ""
echo "2. Ensure rocprofiler-systems is installed and in PATH, or set:"
echo " export ROCPROFSYS_INSTALL_DIR=/opt/rocm"
echo ""
echo "3. Run tests:"
if [[ $BUILD_PYINSTALLER -eq 1 || $BUILD_PYINSTALLER_DOCKER -eq 1 ]]; then
echo " PyInstaller: ./rocprofsys-tests -v"
fi
if [[ $BUILD_SHIV -eq 1 ]]; then
echo " Shiv: python3 rocprofsys-tests.pyz -v"
echo " (Requires: pip install pytest pytest-subtests pytest-timeout pytest-xdist)"
fi
echo ""
echo "4. Common pytest options:"
echo " -v Verbose output"
echo " -k 'transpose' Run only tests matching 'transpose'"
echo " --collect-only List available tests"
echo " -x Stop on first failure"
echo ""
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande Carregar diff
@@ -0,0 +1,74 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
rocprofsys testing utilities package.
Provides reusable components for testing rocprofiler-systems:
- Test runners (sampling, binary rewrite, runtime instrument)
- Output validators (perfetto, rocpd, timemory, regex patterns)
- Configuration management
- GPU and system detection utilities
"""
from .config import (
RocprofsysConfig,
discover_install_config,
discover_build_config,
)
from .runners import (
TestResult,
BaselineRunner,
SamplingRunner,
BinaryRewriteRunner,
RuntimeInstrumentRunner,
SysRunRunner,
)
from .validators import (
ValidationResult,
validate_perfetto_trace,
validate_rocpd_database,
validate_timemory_json,
validate_causal_json,
validate_file_exists,
validate_regex,
)
from .gpu import (
GPUInfo,
get_rocminfo,
detect_gpu,
lookup_gpu_category,
get_target_gpu_arch,
get_offload_extractor,
)
__all__ = [
# Config
"RocprofsysConfig",
"discover_build_config",
"discover_install_config",
# Runners
"TestResult",
"BaselineRunner",
"SamplingRunner",
"BinaryRewriteRunner",
"RuntimeInstrumentRunner",
"SysRunRunner",
# Validators
"ValidationResult",
"validate_perfetto_trace",
"validate_rocpd_database",
"validate_timemory_json",
"validate_causal_json",
"validate_file_exists",
"validate_regex",
# GPU
"GPUInfo",
"get_rocminfo",
"detect_gpu",
"lookup_gpu_category",
"get_target_gpu_arch",
"get_offload_extractor",
]
@@ -0,0 +1,505 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
from __future__ import annotations
from dataclasses import dataclass
import getpass
import os
from pathlib import Path
import shutil
import tempfile
from typing import Optional
import re
@dataclass
class RocprofsysConfig:
"""Configuration for rocprofiler-systems test execution
Contains necessary paths to configure tests for build or for install modes.
Attributes:
- rocprofsys_build_dir: Path to either the build or install directory
- rocprofsys_instrument: Path to rocprof-sys-instrument executable
- rocprofsys_run: Path to rocprof-sys-run executable
- rocprofsys_sample: Path to rocprof-sys-sample executable
- rocprofsys_causal: Path to rocprof-sys-causal executable
- rocprofsys_avail: Path to rocprof-sys-avail executable
- rocm_path: Path to ROCm installation directory
- rocprofsys_lib_dir: Path to rocprofsys library directory
- rocprofsys_bin_dir: Path to rocprofsys binary directory
- rocprofsys_examples_dir:
In build mode, this is the root of the build directory.
In install mode, this is the examples/ directory.
- rocprofsys_tests_dir: Path to rocprofsys tests directory
- test_output_dir: Path to test output directory
- rocpd_validation_rules: Path to rocprofiler-systems rocpd validation rules directory
- mpiexec: Path to MPI launcher executable
- is_installed: Whether this is an installed configuration
"""
rocprofsys_build_dir: Path
rocprofsys_instrument: Path
rocprofsys_run: Path
rocprofsys_sample: Path
rocprofsys_causal: Path
rocprofsys_avail: Path
rocm_path: Path
rocprofsys_lib_dir: Path
rocprofsys_bin_dir: Path
rocprofsys_examples_dir: Path
rocprofsys_tests_dir: Path
rocpd_validation_rules: Path
test_output_dir: Path
mpiexec: Path
is_installed: bool = False
rocm_version: Optional[tuple[int, int, int]] = None
def get_llvm_lib_paths(self) -> list[Path]:
"""Get list of found ROCm LLVM lib paths.
Returns:
List of existing LLVM lib paths found, empty list if none found.
"""
found_paths = []
if self.rocm_path:
# Match discover_llvm_libdir_for_ompt() logic
candidates = [
self.rocm_path / "llvm" / "lib",
self.rocm_path / "lib" / "llvm" / "lib",
]
for candidate in candidates:
if candidate.exists():
found_paths.append(candidate)
return found_paths
def get_library_path(self) -> str:
"""Get LD_LIBRARY_PATH including rocprofiler-systems libraries.
Returns:
LD_LIBRARY_PATH string with rocprofiler-systems libraries
"""
paths = [str(self.rocprofsys_lib_dir.resolve())]
existing = os.environ.get("LD_LIBRARY_PATH", "")
if existing:
paths.append(existing)
# Add ROCm LLVM lib as fallback
for llvm_path in self.get_llvm_lib_paths():
paths.append(str(llvm_path))
return ":".join(paths)
def get_target_executable(self, name: str) -> Path:
"""Get path to a test target executable.
When is_installed is True, searches in the following order:
1. rocprofsys_build_dir/name (build directory layout)
2. rocprofsys_examples_dir/name/name (build directory layout)
3. PATH lookup
When is_installed is False, searches in the following order:
1. rocprofsys_examples_dir/name
2. rocprofsys_bin_dir/name
3. PATH lookup
Args:
name: Name of the target executable
Returns:
Path to the executable
Raises:
FileNotFoundError: If the executable is not found
"""
if self.is_installed:
# examples directory layout
exe = self.rocprofsys_examples_dir / name
if exe.exists() and exe.is_file():
return exe
# binary directory
exe = self.rocprofsys_bin_dir / name
if exe.exists() and exe.is_file():
return exe
# PATH lookup via shutil.which
exe = shutil.which(name)
if exe:
return Path(exe)
raise FileNotFoundError(
f"Target executable '{name}' not found. Searched in:\n"
f" - {self.rocprofsys_examples_dir}/{name}\n"
f" - {self.rocprofsys_bin_dir}/{name}\n"
f" - PATH"
)
else:
# Build directory mode
exe = self.rocprofsys_examples_dir / name
if exe.exists() and exe.is_file():
return exe
exe = self.rocprofsys_examples_dir / "examples" / name / name
if exe.exists() and exe.is_file():
return exe
# rccl tests lie in their own directory
exe = self.rocprofsys_examples_dir / "examples" / "rccl" / name
if exe.exists() and exe.is_file():
return exe
# binary directory
exe = self.rocprofsys_bin_dir / name
if exe.exists() and exe.is_file():
return exe
# PATH lookup via shutil.which
exe = shutil.which(name)
if exe:
return Path(exe)
raise FileNotFoundError(
f"Target executable '{name}' not found. Searched in:\n"
f" - {self.rocprofsys_examples_dir}/{name}\n"
f" - {self.rocprofsys_examples_dir}/examples/{name}/{name}\n"
f" - {self.rocprofsys_bin_dir}/{name}\n"
f" - PATH"
)
def get_fundamental_environment(self) -> dict[str, str]:
"""Get fundamental environment variables inherited from parent process."""
return {
"PATH": os.environ.get("PATH", ""),
"HOME": os.environ.get("HOME", ""),
"USER": os.environ.get("USER", ""),
"SHELL": os.environ.get("SHELL", ""),
"TERM": os.environ.get("TERM", ""),
"LANG": os.environ.get("LANG", ""),
}
def get_base_environment(self) -> dict[str, str]:
"""Get base environment variables for test execution."""
return {
"ROCPROFSYS_CI": "ON",
"ROCPROFSYS_CONFIG_FILE": "",
"ROCPROFSYS_TRACE": "ON",
"ROCPROFSYS_PROFILE": "ON",
"ROCPROFSYS_USE_SAMPLING": "ON",
"ROCPROFSYS_USE_PROCESS_SAMPLING": "ON",
"ROCPROFSYS_TIME_OUTPUT": "OFF",
"ROCPROFSYS_FILE_OUTPUT": "ON",
"ROCPROFSYS_USE_PID": "OFF",
"ROCPROFSYS_VERBOSE": "1",
"ROCPROFSYS_SAMPLING_FREQ": "300",
"ROCPROFSYS_SAMPLING_DELAY": "0.05",
"OMP_PROC_BIND": "spread",
"OMP_PLACES": "threads",
"OMP_NUM_THREADS": "2",
"LD_LIBRARY_PATH": self.get_library_path(),
}
def get_base_binary_environment(self) -> dict[str, str]:
"""Get base environment variables for rocprof-sys binary test execution."""
return {
"ROCPROFSYS_TRACE": "ON",
"ROCPROFSYS_PROFILE": "ON",
"ROCPROFSYS_USE_SAMPLING": "ON",
"ROCPROFSYS_TIME_OUTPUT": "OFF",
"LD_LIBRARY_PATH": self.get_library_path(),
"ROCPROFSYS_CI": "ON",
"ROCPROFSYS_CI_TIMEOUT": "300",
"ROCPROFSYS_CONFIG_FILE": "",
}
def _find_rocm_path() -> Optional[Path]:
"""Find ROCm installation path."""
for candidate in [
os.environ.get("ROCM_PATH"),
"/opt/rocm",
"/usr/local/rocm",
]:
if candidate and Path(candidate).exists():
return Path(candidate).resolve()
return None
def _get_rocm_version() -> Optional[tuple[int, int, int]]:
"""Get the installed ROCm version as a tuple (major, minor, patch).
Returns:
Tuple of (major, minor, patch) or None if ROCm not found or version undetectable.
"""
rocm_path = _find_rocm_path()
if not rocm_path:
return None
# Check .info/version file
version_file = rocm_path / ".info" / "version"
if not version_file.exists():
# Try alternative location
version_file = rocm_path / "share" / "rocm" / "version"
if version_file.exists():
try:
version_str = version_file.read_text().strip()
match = re.match(r"(\d+)\.(\d+)\.(\d+)", version_str)
if match:
return (int(match.group(1)), int(match.group(2)), int(match.group(3)))
except (OSError, ValueError):
pass
return None
def _find_mpiexec() -> Optional[Path]:
"""Find MPI launcher executable."""
for candidate in ["mpiexec", "mpirun"]:
path = shutil.which(candidate)
if path:
return Path(path)
return None
def _find_executable(name: str, search_paths: list[Path]) -> Optional[Path]:
"""Find an executable in search paths or via PATH."""
for search_dir in search_paths:
exe = search_dir / name
if exe.exists() and exe.is_file():
return exe.resolve()
# Fallback to PATH
path_exe = shutil.which(name)
if path_exe:
return Path(path_exe)
return None
def discover_install_config(
install_dir: Optional[Path] = None,
output_dir: Optional[Path] = None,
) -> RocprofsysConfig:
"""Discover rocprofiler-systems installation configuration.
Creates configuration for testing against installed binaries.
Args:
install_dir: Installation prefix (e.g., /opt/rocm or /usr/local)
Returns:
RocprofsysConfig configured for installed binaries
Raises:
FileNotFoundError: If installation cannot be found
"""
if install_dir is None:
env_install = os.environ.get("ROCPROFSYS_INSTALL_DIR")
if env_install:
install_dir = Path(env_install).resolve()
else:
for candidate in [
_find_rocm_path(),
Path("/usr/local"),
Path("/usr"),
Path(
"/opt/rocprofiler-systems"
), # Standard install location from README.md
]:
if (
candidate
and (candidate / "share" / "rocprofiler-systems" / "tests").is_dir()
and (
candidate / "share" / "rocprofiler-systems" / "examples"
).is_dir()
):
install_dir = candidate
break
if install_dir is None:
raise FileNotFoundError(
"Could not find a suitable rocprofiler-systems installation. Set ROCPROFSYS_INSTALL_DIR "
"environment variable."
"A suitable installation is one that has the following directory: share/rocprofiler-systems/examples "
"and share/rocprofiler-systems/tests"
)
install_dir = install_dir.resolve()
# Determine directory layout
bin_dir = install_dir / "bin"
lib_dir = install_dir / "lib"
# For lib64 systems
if not lib_dir.exists() and (install_dir / "lib64").exists():
lib_dir = install_dir / "lib64"
examples_dir = install_dir / "share" / "rocprofiler-systems" / "examples"
tests_dir = install_dir / "share" / "rocprofiler-systems" / "tests"
rocpd_validation_rules = tests_dir / "rocpd-validation-rules"
# Create a temporary directory for test outputs
try:
username = getpass.getuser()
except Exception:
username = str(os.getuid())
if output_dir is None:
output_dir = Path(tempfile.gettempdir()) / username / "rocprof-sys-pytest-output"
else:
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
rocm_path = _find_rocm_path()
mpiexec = _find_mpiexec()
search_paths = [bin_dir]
rocprof_instrument = _find_executable("rocprof-sys-instrument", search_paths)
rocprof_sample = _find_executable("rocprof-sys-sample", search_paths)
rocprof_run = _find_executable("rocprof-sys-run", search_paths)
rocprof_causal = _find_executable("rocprof-sys-causal", search_paths)
rocprof_avail = _find_executable("rocprof-sys-avail", search_paths)
# If any of the executables are not found, raise an error
required_executables = {
"rocprof-sys-instrument": rocprof_instrument,
"rocprof-sys-sample": rocprof_sample,
"rocprof-sys-run": rocprof_run,
"rocprof-sys-causal": rocprof_causal,
"rocprof-sys-avail": rocprof_avail,
}
missing = [name for name, path in required_executables.items() if path is None]
if missing:
raise FileNotFoundError(
f"Required executables not found: {', '.join(missing)}. "
f"Searched in: {search_paths}"
)
return RocprofsysConfig(
rocprofsys_build_dir=install_dir,
rocprofsys_instrument=rocprof_instrument,
rocprofsys_run=rocprof_run,
rocprofsys_sample=rocprof_sample,
rocprofsys_causal=rocprof_causal,
rocprofsys_avail=rocprof_avail,
rocm_path=rocm_path,
rocprofsys_lib_dir=lib_dir,
rocprofsys_bin_dir=bin_dir,
rocprofsys_examples_dir=examples_dir,
rocprofsys_tests_dir=tests_dir,
rocpd_validation_rules=rocpd_validation_rules,
test_output_dir=output_dir,
mpiexec=mpiexec,
rocm_version=_get_rocm_version(),
is_installed=True,
)
def discover_build_config(
build_dir: Optional[Path] = None,
output_dir: Optional[Path] = None,
) -> RocprofsysConfig:
"""Discover rocprofiler-systems build configuration.
Attempts to find the build directory and source directory automatically
if not provided, checking common locations and environment variables.
If no build directory is found but an installation is available,
falls back to discover_install_config().
Args:
build_dir: Explicit build directory path
Returns:
RocprofsysConfig with discovered paths
Raises:
FileNotFoundError: If neither build directory nor installation found
"""
# Explicit install directory check
if os.environ.get("ROCPROFSYS_INSTALL_DIR"):
return discover_install_config(output_dir=output_dir)
# When running from pyz package (extracted to /tmp), fall back to install config
# The pyz extracts to paths like /tmp/rocprofsys-tests-*/tests/rocprofsys/config.py
current_file = Path(__file__).resolve()
if str(current_file).startswith(tempfile.gettempdir()):
return discover_install_config()
# All files should be in the build directory
if build_dir is None:
env_build = os.environ.get("ROCPROFSYS_BUILD_DIR")
if env_build:
build_dir = Path(env_build).resolve()
else:
build_dir = Path(__file__).resolve().parent.parent.parent.parent.parent.parent
if build_dir is None or not build_dir.exists():
raise FileNotFoundError(
"Could not find build directory or installation. Set one of:\n"
" - ROCPROFSYS_BUILD_DIR: Path to build directory\n"
" - ROCPROFSYS_INSTALL_DIR: Path to installation prefix"
)
rocm_path = _find_rocm_path()
mpiexec = _find_mpiexec()
bin_dir = build_dir / "bin"
lib_dir = build_dir / "lib"
search_paths = [bin_dir]
rocprof_instrument = _find_executable("rocprof-sys-instrument", search_paths)
rocprof_sample = _find_executable("rocprof-sys-sample", search_paths)
rocprof_run = _find_executable("rocprof-sys-run", search_paths)
rocprof_causal = _find_executable("rocprof-sys-causal", search_paths)
rocprof_avail = _find_executable("rocprof-sys-avail", search_paths)
# If any of the executables are not found, raise an error
required_executables = {
"rocprof-sys-instrument": rocprof_instrument,
"rocprof-sys-sample": rocprof_sample,
"rocprof-sys-run": rocprof_run,
"rocprof-sys-causal": rocprof_causal,
"rocprof-sys-avail": rocprof_avail,
}
missing = [name for name, path in required_executables.items() if path is None]
if missing:
raise FileNotFoundError(
f"Required executables not found: {', '.join(missing)}. "
f"Searched in: {search_paths}"
)
share_path = build_dir / "share" / "rocprofiler-systems"
if output_dir is None:
output_dir = build_dir / "rocprof-sys-pytest-output"
else:
output_dir = Path(output_dir)
return RocprofsysConfig(
rocprofsys_build_dir=build_dir,
rocprofsys_instrument=rocprof_instrument,
rocprofsys_run=rocprof_run,
rocprofsys_sample=rocprof_sample,
rocprofsys_causal=rocprof_causal,
rocprofsys_avail=rocprof_avail,
rocm_path=rocm_path,
rocprofsys_lib_dir=lib_dir,
rocprofsys_bin_dir=bin_dir,
rocprofsys_examples_dir=build_dir, # Example binaries are (almost always) in root of build directory
rocprofsys_tests_dir=share_path / "tests",
rocpd_validation_rules=share_path / "tests" / "rocpd-validation-rules",
test_output_dir=output_dir,
mpiexec=mpiexec,
rocm_version=_get_rocm_version(),
is_installed=False,
)
+364
Ver ficheiro
@@ -0,0 +1,364 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
from __future__ import annotations
import re
import shutil
import subprocess
import os
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Optional
@dataclass
class GPUInfo:
"""Information about detected GPU(s)
Attributes:
available: Whether any GPU is available
architectures: List of GPU architectures
device_count: Number of GPUs detected
categories: Categories the GPU belongs to (instinct, radeon, apu)
"""
available: bool
architectures: list[str]
device_count: int
categories: set[str]
@property
def rocm_events_for_test(self) -> str:
"""Get appropriate ROCm events for testing based on architecture."""
mi300_or_later = False
for arch in self.architectures:
if re.match(r"gfx9[4-9][0-9A-Fa-f]", arch):
mi300_or_later = True
break
if mi300_or_later:
return "GRBM_COUNT,SQ_WAVES,SQ_INSTS_VALU,TA_TA_BUSY:device=0"
return "SQ_WAVES"
@property
def counter_names(self) -> list[str]:
"""Get counter names for validation based on architecture"""
mi300_or_later = False
for arch in self.architectures:
if re.match(r"gfx9[4-9][0-9A-Fa-f]", arch):
mi300_or_later = True
break
if mi300_or_later:
return ["GRBM_COUNT", "SQ_WAVES", "SQ_INSTS_VALU", "TA_TA_BUSY"]
return ["SQ_WAVES"]
@property
def expected_counter_files(self) -> list[str]:
"""Get expected counter output files based on architecture."""
return [f"rocprof-device-0-{name}.txt" for name in self.counter_names]
def get_rocminfo(rocm_path: Optional[Path] = None) -> Optional[Path]:
"""Get the path to the rocminfo executable.
Args:
rocm_path: Path to the ROCm installation directory
Returns:
Path to the rocminfo executable or None if not found
"""
if rocm_path:
candidate = rocm_path / "bin" / "rocminfo"
if candidate.exists():
return Path(candidate).resolve()
rocminfo = shutil.which("rocminfo")
if rocminfo:
return Path(rocminfo).resolve()
return None
@lru_cache(maxsize=1)
def detect_gpu(rocm_path: Optional[Path] = None) -> GPUInfo:
"""Detect available AMD GPUs and their capabilities.
Uses rocminfo to get the list of GPU architectures.
Regex avoids matching "gfxX-X-generic" which may appear.
"""
categories: set[str] = set()
architectures: list[str] = []
device_count = 0
# Detect available GPUs
rocminfo = None
if rocm_path:
rocminfo = rocm_path / "bin" / "rocminfo"
if not rocminfo:
rocminfo = shutil.which("rocminfo")
if rocminfo:
try:
result = subprocess.run(
[str(rocminfo)],
capture_output=True,
text=True,
timeout=30,
)
if result.returncode == 0:
# Only match gfx on "Name:"
name_gfx_pattern = re.compile(
r"^\s*Name:\s+(gfx[0-9A-Fa-f][0-9A-Fa-f]+)", re.MULTILINE
)
all_matches = name_gfx_pattern.findall(result.stdout)
# gfx000 is the cpu, remove it
filtered = [arch for arch in all_matches if arch != "gfx000"]
device_count = len(filtered)
# Remove duplicates
architectures = list(set(filtered))
except (subprocess.TimeoutExpired, OSError):
pass
for arch in architectures:
categories.update(lookup_gpu_category(arch, rocm_path))
return GPUInfo(
available=device_count > 0,
architectures=sorted(architectures),
device_count=device_count,
categories=categories,
)
def lookup_gpu_category(arch: str, rocm_path: Optional[Path] = None) -> list[str]:
"""Lookup the GPU category for an architecture.
Args:
arch: Architecture string (e.g., 'gfx940')
Returns:
List of GPU categories the architecture belongs to (instinct, radeon, apu)
"""
instinct_list = [
"gfx900",
"gfx906", # MI50/MI60
"gfx908",
"gfx90a",
"gfx942",
"gfx950",
]
# Also includes PRO GPUs
# Ignore Radeon VII (gfx906)
radeon_list = [
"gfx1010",
"gfx1011",
"gfx1012",
"gfx1030",
"gfx1031",
"gfx1032",
"gfx1100",
"gfx1101",
"gfx1102",
"gfx1200",
"gfx1201",
"gfx1202",
]
apu_list = [
"gfx1035",
"gfx1036",
"gfx1103",
"gfx1151",
"gfx1152",
"gfx1153",
]
categories: list[str] = []
if arch in instinct_list:
categories.append("instinct")
# Some instinct GPUs may also be an APU (ex: MI300A)
rocminfo = get_rocminfo(rocm_path)
if rocminfo:
try:
result = subprocess.run(
[str(rocminfo)],
capture_output=True,
text=True,
timeout=30,
)
if result.returncode == 0 and "APU" in result.stdout:
categories.append("apu")
except (subprocess.TimeoutExpired, OSError):
pass
if arch in radeon_list:
categories.append("radeon")
if arch in apu_list:
categories.append("apu")
if not categories:
# Unknown architecture, default to instinct
categories.append("instinct")
return categories
@lru_cache(maxsize=1)
def get_offload_extractor(rocm_path: Path) -> tuple[Optional[Path], Optional[bool]]:
"""Get offload extractor path
An offload extractor is one of:
llvm-objdump (only if version >= 20) - Preferred
roc-obj-ls (deprecated) - Fallback
Args:
rocm_path: Path to the ROCm installation directory
Returns:
Path to the offload extractor
Bool representing whether found llvm-objdump's version < 20 (None if llvm-objdump not found)
"""
is_llvm_too_old = None
offload_extractor = None
# Check env var - accepts either path to binary or directory containing it
llvm_objdump_env = os.environ.get("ROCM_LLVM_OBJDUMP")
if llvm_objdump_env:
llvm_objdump_path = Path(llvm_objdump_env)
if llvm_objdump_path.is_file() and llvm_objdump_path.exists():
offload_extractor = llvm_objdump_path
elif llvm_objdump_path.is_dir():
candidate = llvm_objdump_path / "llvm-objdump"
if candidate.exists():
offload_extractor = candidate
# Fallback to ROCm path
if not offload_extractor and rocm_path:
llvm_objdump_candidates = [
rocm_path / "llvm" / "bin" / "llvm-objdump",
rocm_path / "bin" / "llvm-objdump",
]
for candidate in llvm_objdump_candidates:
if candidate.exists():
offload_extractor = candidate
break
if offload_extractor:
# We have found llvm-objdump, check version
try:
version_result = subprocess.run(
[str(offload_extractor), "--version"],
capture_output=True,
text=True,
timeout=10,
)
version_match = re.search(r"version\s+(\d+)", version_result.stdout)
if version_match:
major_version = int(version_match.group(1))
if major_version >= 20:
is_llvm_too_old = False
return (
Path(offload_extractor).resolve(),
is_llvm_too_old,
)
else:
is_llvm_too_old = True
except Exception:
pass
# Fallback to roc-obj-ls
offload_extractor = None
if rocm_path:
candidate = rocm_path / "bin" / "roc-obj-ls"
if candidate.exists():
offload_extractor = Path(candidate).resolve()
return offload_extractor, is_llvm_too_old
if not offload_extractor:
offload_extractor = shutil.which("roc-obj-ls")
if offload_extractor:
return offload_extractor, is_llvm_too_old
return None, is_llvm_too_old
def get_target_gpu_arch(rocm_path: Path, target_path: Path) -> list[str]:
"""Get the list of gpu architectures (gfx) the target was compiled for.
Args:
rocm_path: Path to the ROCm installation directory
target_path: Path to the binary to check
Returns:
List of GPU architectures the target was compiled for
Raises:
FileNotFoundError: If offload extractor is not found
"""
import tempfile
target_archs: set[str] = set()
result = get_offload_extractor(rocm_path)
if not result:
raise FileNotFoundError(
f"Could not find offload extractor in {rocm_path} "
"or environment variable ROCM_LLVM_OBJDUMP"
)
tool_path, _ = result
if "llvm-objdump" in tool_path.name:
with tempfile.TemporaryDirectory() as tmpdir:
tmp_symlink = Path(tmpdir) / target_path.name
try:
tmp_symlink.symlink_to(target_path)
except OSError:
return list(target_archs)
extracted_files: list[Path] = []
try:
result = subprocess.run(
[str(tool_path), "--offloading", str(tmp_symlink)],
capture_output=True,
text=True,
timeout=30,
)
if result.returncode == 0:
for line in result.stdout.strip().split("\n"):
# Match any gfxXXXX pattern in the line
match = re.search(r"(gfx[0-9a-fA-F]+)", line)
if match:
target_archs.add(match.group(1))
# Capture extracted bundle paths for cleanup
bundle_match = re.search(
r"Extracting offload bundle:\s*(.+)$", line
)
if bundle_match:
extracted_files.append(Path(bundle_match.group(1)))
except (subprocess.TimeoutExpired, OSError):
pass
# Immediately clean up extracted files to free disk space
for extracted_file in extracted_files:
try:
if extracted_file.exists():
extracted_file.unlink()
except OSError:
pass
elif "roc-obj-ls" in tool_path.name:
try:
result = subprocess.run(
[str(tool_path), str(target_path)],
capture_output=True,
text=True,
timeout=30,
)
if result.returncode == 0:
for line in result.stdout.strip().split("\n"):
# Match any gfxXXXX pattern in the line
match = re.search(r"(gfx[0-9a-fA-F]+)", line)
if match:
target_archs.add(match.group(1))
except (subprocess.TimeoutExpired, OSError):
pass
return list(target_archs)
@@ -0,0 +1,585 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Test runners for different rocprofiler-systems instrumentation modes.
Provides classes for running tests with:
- Baseline execution (no instrumentation)
- Sampling instrumentation
- Binary rewrite instrumentation
- Runtime instrumentation
- rocprof-sys-run wrapper
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
import os
from pathlib import Path
import shutil
import subprocess
from typing import Optional
from .config import RocprofsysConfig
def _safe_remove_file(filepath: Path) -> None:
"""Safely remove a file, ignoring errors."""
try:
if filepath.is_file():
filepath.unlink()
except OSError:
pass
def _safe_remove_directory(dirpath: Path) -> None:
"""Safely remove a directory recursively, ignoring errors."""
try:
if dirpath.is_dir():
shutil.rmtree(dirpath)
except OSError:
pass
def _decode_bytes(data: bytes | None, encoding: str = "utf-8") -> str:
"""Decode bytes to string, returning empty string if None."""
if data is None:
return ""
return data.decode(encoding, errors="replace")
@dataclass
class TestResult:
"""Result of a test execution
Attributes:
returncode: Process exit code
test_output: Standard output and error content
extra_output: Extra output set by the test itself
(as of now, only used for timeout errors)
output_dir: Directory containing output files
command: The command that was executed
env: Environment variables used
duration: Execution time in seconds (if measured)
_instrumented_files: List of instrumented binary files created
"""
returncode: int
test_output: str
output_dir: Path
command: list[str]
environment: dict[str, str]
extra_output: Optional[str] = None
duration: Optional[float] = None
_instrumented_files: list[Path] = field(default_factory=list)
@property
def success(self) -> bool:
"""Check if test execution succeeded.
Returns True only if:
- Return code is 0
"""
return self.returncode == 0
@property
def perfetto_file(self) -> Optional[Path]:
candidates = [
self.output_dir / "perfetto-trace.proto",
self.output_dir / "perfetto-trace-0.proto",
]
for candidate in candidates:
if candidate.exists():
return candidate
protos = list(self.output_dir.glob("perfetto-trace*.proto"))
return protos[0] if protos else None
@property
def rocpd_file(self) -> Optional[Path]:
candidate = self.output_dir / "rocpd.db"
if candidate.exists():
return candidate
# Try globbing
dbs = list(self.output_dir.glob("*.db"))
return dbs[0] if dbs else None
@property
def timemory_files(self) -> list[Path]:
"""List of timemory output files."""
return list(self.output_dir.glob("*.json")) + list(self.output_dir.glob("*.txt"))
def get_output_file(self, pattern: str) -> Optional[Path]:
"""Get an output file matching the given pattern.
Args:
pattern: Glob pattern to match
Returns:
First matching file or None
"""
matches = list(self.output_dir.glob(pattern))
return matches[0] if matches else None
def cleanup(self, keep_on_failure: bool = True) -> None:
"""Clean up test output files.
Args:
keep_on_failure: If True, keep files when test failed for debugging
"""
if os.environ.get("ROCPROFSYS_KEEP_TEST_OUTPUT", "1") == "1":
return
if keep_on_failure and not self.success:
return
# Clean up instrumented binaries
for inst_file in self._instrumented_files:
_safe_remove_file(inst_file)
# Clean up output directory
if self.output_dir.exists():
_safe_remove_directory(self.output_dir)
def cleanup_instrumented_binaries(self) -> None:
"""Clean up only the instrumented binary files."""
if os.environ.get("ROCPROFSYS_KEEP_TEST_OUTPUT", "1") == "1":
return
for inst_file in self._instrumented_files:
_safe_remove_file(inst_file)
# Also clean any .inst files in output directory
if self.output_dir.exists():
for inst_file in self.output_dir.glob("*.inst"):
_safe_remove_file(inst_file)
class BaseRunner(ABC):
"""Abstract base class for test runners."""
def __init__(
self,
config: RocprofsysConfig,
target: str,
output_dir: Path,
run_args: Optional[list[str]] = None,
env: Optional[dict[str, str]] = None,
timeout: int = 300,
mpi_ranks: int = 0,
working_directory: Optional[Path] = None,
):
self.config = config
self.target = target
self.target_exe = config.get_target_executable(target)
self.output_dir = Path(output_dir)
self.run_args = run_args or []
self.timeout = timeout
self.mpi_ranks = mpi_ranks
self.working_directory = working_directory or config.rocprofsys_build_dir
self.env = config.get_fundamental_environment()
self.env.update(config.get_base_environment())
self.env["ROCPROFSYS_OUTPUT_PATH"] = str(self.output_dir)
if env:
self.env.update(env)
@abstractmethod
def build_command(self) -> list[str]:
"""Build the command to execute.
Returns:
List of command components
"""
pass
def _wrap_with_mpi(self, command: list[str]) -> list[str]:
"""Wrap command with MPI launcher if needed.
Args:
command: Base command
Returns:
Command wrapped with mpiexec if MPI is enabled
"""
if self.mpi_ranks > 0 and self.config.mpiexec:
mpi_cmd = [
str(self.config.mpiexec),
"-n",
str(self.mpi_ranks),
]
try:
result = subprocess.run(
[str(self.config.mpiexec), "--oversubscribe", "-n", "1", "true"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
timeout=5,
)
if result.returncode == 0:
mpi_cmd.insert(1, "--oversubscribe")
except (subprocess.TimeoutExpired, OSError):
pass
return mpi_cmd + command
return command
def run(self) -> TestResult:
"""Execute the test.
Returns:
TestResult with execution results
"""
import time
self.output_dir.mkdir(parents=True, exist_ok=True)
command = self.build_command()
command = self._wrap_with_mpi(command)
start_time = time.time()
try:
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=self.timeout,
env=self.env,
cwd=self.working_directory,
)
duration = time.time() - start_time
test_result = TestResult(
returncode=result.returncode,
test_output=result.stdout,
output_dir=self.output_dir,
command=command,
environment=self.env,
duration=duration,
)
except subprocess.TimeoutExpired as e:
duration = time.time() - start_time
stdout = _decode_bytes(e.stdout)
stderr = _decode_bytes(e.stderr)
test_result = TestResult(
returncode=-1,
test_output=stdout,
extra_output=f"Timeout after {self.timeout}s\n{stderr}",
output_dir=self.output_dir,
command=command,
environment=self.env,
duration=duration,
)
return test_result
class BaselineRunner(BaseRunner):
"""Run target without any instrumentation.
Can also be used to run arbitrary commands by providing the `command` parameter.
- command + run_args are executed as a single command
If a rocprof-sys binary is provided, uses "base_binary_environment" instead of "base_environment".
Args:
config: rocprofiler-systems configuration
target: Name of target executable (used if command is None)
output_dir: Directory for output files
command: Optional full command to run instead of target executable
**kwargs: Additional arguments passed to BaseRunner
"""
# rocprof-sys binaries that should use get_base_binary_environment()
ROCPROFSYS_BINARIES = {
"rocprof-sys-instrument",
"rocprof-sys-sample",
"rocprof-sys-run",
"rocprof-sys-avail",
}
def __init__(
self,
config: RocprofsysConfig,
target: str,
output_dir: Path,
command: Optional[list[str]] = None,
**kwargs,
):
super().__init__(config, target, output_dir, **kwargs)
self.command = command
# If target is a rocprof-sys binary, use binary environment instead
if target in self.ROCPROFSYS_BINARIES:
self.env = config.get_fundamental_environment()
self.env.update(config.get_base_binary_environment())
self.env["ROCPROFSYS_OUTPUT_PATH"] = str(self.output_dir)
# Re-apply any custom env passed via kwargs
if "env" in kwargs and kwargs["env"]:
self.env.update(kwargs["env"])
def build_command(self) -> list[str]:
if self.command:
return self.command + self.run_args
return [str(self.target_exe)] + self.run_args
class SamplingRunner(BaseRunner):
"""Run target with sampling instrumentation."""
def __init__(
self,
config: RocprofsysConfig,
target: str,
output_dir: Path,
sample_args: Optional[list[str]] = None,
**kwargs,
):
"""Initialize sampling runner.
Args:
config: rocprofiler-systems configuration
target: Name of target executable
output_dir: Directory for output files
sample_args: Arguments for rocprof-sys-sample
**kwargs: Additional arguments passed to BaseRunner
"""
super().__init__(config, target, output_dir, **kwargs)
self.sample_args = sample_args or []
def build_command(self) -> list[str]:
return (
[str(self.config.rocprofsys_sample)]
+ self.sample_args
+ ["--", str(self.target_exe)]
+ self.run_args
)
class BinaryRewriteRunner(BaseRunner):
"""Run binary rewrite instrumentation (two-phase: rewrite then run)."""
def __init__(
self,
config: RocprofsysConfig,
target: str,
output_dir: Path,
rewrite_args: Optional[list[str]] = None,
cleanup_on_success: bool = False,
**kwargs,
):
"""Initialize binary rewrite runner.
Args:
config: rocprofiler-systems configuration
target: Name of target executable
output_dir: Directory for output files
rewrite_args: Arguments for rocprof-sys-instrument
cleanup_on_success: Whether to clean up instrumented binary immediately
after successful run. Default is False - let the test_output_dir
fixture handle cleanup after validation completes.
**kwargs: Additional arguments passed to BaseRunner
"""
super().__init__(config, target, output_dir, **kwargs)
self.rewrite_args = rewrite_args or []
self.instrumented_exe = output_dir / f"{target}.inst"
self.cleanup_on_success = cleanup_on_success
self._instrumented_files: list[Path] = []
def rewrite(self) -> TestResult:
"""Perform binary rewrite phase.
Returns:
TestResult from rewrite operation
"""
import time
self.output_dir.mkdir(parents=True, exist_ok=True)
command = (
[str(self.config.rocprofsys_instrument)]
+ ["-o", str(self.instrumented_exe)]
+ self.rewrite_args
+ ["--print-instrumented", "functions"]
+ ["--", str(self.target_exe)]
)
start_time = time.time()
try:
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=self.timeout,
env=self.env,
cwd=self.config.rocprofsys_build_dir,
)
duration = time.time() - start_time
test_result = TestResult(
returncode=result.returncode,
test_output=result.stdout,
output_dir=self.output_dir,
command=command,
environment=self.env,
duration=duration,
_instrumented_files=self._instrumented_files.copy(),
)
except subprocess.TimeoutExpired as e:
duration = time.time() - start_time
stdout = _decode_bytes(e.stdout)
stderr = _decode_bytes(e.stderr)
test_result = TestResult(
returncode=-1,
test_output=stdout,
extra_output=f"Timeout after {self.timeout}s\n{stderr}",
output_dir=self.output_dir,
command=command,
environment=self.env,
duration=duration,
_instrumented_files=self._instrumented_files.copy(),
)
# Track instrumented files for cleanup
if self.instrumented_exe.exists():
self._instrumented_files.append(self.instrumented_exe)
return test_result
def build_command(self) -> list[str]:
"""Build command to run the instrumented binary."""
return [
str(self.config.rocprofsys_run),
"--",
str(self.instrumented_exe),
] + self.run_args
def run(self) -> TestResult:
"""Execute full rewrite + run sequence.
Returns:
TestResult from full rewrite + run sequence
Note:
By default, cleanup is handled by the test_output_dir fixture
AFTER the test completes (including validation). Set cleanup_on_success=True
only if you want immediate cleanup of .inst files (validation files are
preserved regardless).
"""
# First, perform rewrite
rewrite_result = self.rewrite()
if not rewrite_result.success:
return rewrite_result
# Then run the instrumented binary
run_result = super().run()
# Add instrumented files to result for cleanup (used by fixtures)
run_result._instrumented_files = self._instrumented_files.copy()
# Optional immediate cleanup of .inst files only (NOT validation files)
# Default is False - let test_output_dir fixture handle all cleanup
# after validation completes
if self.cleanup_on_success and run_result.success:
run_result.cleanup_instrumented_binaries()
# Combine rewrite and run output
run_result.test_output = (
f"=== REWRITE PHASE ===\n{rewrite_result.test_output}\n"
f"=== RUN PHASE ===\n{run_result.test_output}"
)
run_result.duration = rewrite_result.duration + run_result.duration
extra_parts = []
if rewrite_result.extra_output:
extra_parts.append(f"=== REWRITE PHASE ===\n{rewrite_result.extra_output}")
if run_result.extra_output:
extra_parts.append(f"=== RUN PHASE ===\n{run_result.extra_output}")
if extra_parts:
run_result.extra_output = "\n".join(extra_parts)
return run_result
def cleanup(self) -> None:
"""Clean up instrumented binary files."""
if os.environ.get("ROCPROFSYS_KEEP_TEST_OUTPUT", "1") == "1":
return
for inst_file in self._instrumented_files:
_safe_remove_file(inst_file)
# Also clean any .inst files in output directory
if self.output_dir.exists():
for inst_file in self.output_dir.glob("*.inst"):
_safe_remove_file(inst_file)
class RuntimeInstrumentRunner(BaseRunner):
"""Run target with runtime instrumentation."""
def __init__(
self,
config: RocprofsysConfig,
target: str,
output_dir: Path,
instrument_args: Optional[list[str]] = None,
**kwargs,
):
"""Initialize runtime instrument runner.
Args:
config: rocprofiler-systems configuration
target: Name of target executable
output_dir: Directory for output files
instrument_args: Arguments for rocprof-sys-instrument
**kwargs: Additional arguments passed to BaseRunner
"""
super().__init__(config, target, output_dir, **kwargs)
self.instrument_args = instrument_args or []
def build_command(self) -> list[str]:
return (
[str(self.config.rocprofsys_instrument)]
+ self.instrument_args
+ ["--print-instrumented", "functions"]
+ ["--", str(self.target_exe)]
+ self.run_args
)
class SysRunRunner(BaseRunner):
"""Run target with rocprof-sys-run wrapper."""
def __init__(
self,
config: RocprofsysConfig,
target: str,
output_dir: Path,
sysrun_args: Optional[list[str]] = None,
**kwargs,
):
"""Initialize sys-run runner.
Args:
config: rocprofiler-systems configuration
target: Name of target executable
output_dir: Directory for output files
sysrun_args: Arguments for rocprof-sys-run (before --)
**kwargs: Additional arguments passed to BaseRunner
"""
super().__init__(config, target, output_dir, **kwargs)
self.sysrun_args = sysrun_args or []
def build_command(self) -> list[str]:
return (
[str(self.config.rocprofsys_run)]
+ self.sysrun_args
+ ["--", str(self.target_exe)]
+ self.run_args
)
@@ -0,0 +1,417 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Output validators for rocprofiler-systems test results.
This module wraps the existing validation scripts from the tests/ directory:
- validate-perfetto-proto.py
- validate-rocpd.py
- validate-timemory-json.py
- validate-causal-json.py
We also provide the following validators:
- validate_file_exists
"""
from __future__ import annotations
import os
import re
import shlex
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional
@dataclass
class ValidationResult:
"""Result of a validation operation.
Attributes:
is_valid: Whether the validation passed
message: Description of result or error
details: Additional details (e.g., query results)
stdout: Standard output from validation script
stderr: Standard error from validation script
command: The command that was executed
"""
is_valid: bool
message: str
details: Optional[dict[str, Any]] = None
stdout: str = ""
stderr: str = ""
command: str = ""
ROCPROFSYS_ABORT_FAIL_REGEX = [
r"### ERROR ###",
r"unknown-hash=",
r"address of faulting memory reference",
r"exiting with non-zero exit code",
r"terminate called after throwing an instance",
r"calling abort\.\. in ",
r"Exit code: [1-9]",
]
from rocprofsys.runners import TestResult
def validate_regex(
test_result: TestResult,
pass_regex: Optional[list[str]] = None,
fail_regex: Optional[list[str]] = None,
use_abort_fail_regex: bool = True,
) -> ValidationResult:
"""Validate the regex patterns in the test result.
Does not check for result return code.
Args:
test_result: TestResult object (after test execution)
pass_regex: Optional list of regex patterns that must be found for success
fail_regex: Optional list of regex patterns that must NOT be found
use_abort_fail_regex: Whether to validate against ROCPROFSYS_ABORT_FAIL_REGEX (default: True)
Returns:
ValidationResult with is_valid=True if all patterns pass, False otherwise
"""
# Do not check for result return code
# Build fail regex list
fail_patterns: list[str] = []
if fail_regex:
fail_patterns.extend(fail_regex)
if use_abort_fail_regex:
fail_patterns.extend(ROCPROFSYS_ABORT_FAIL_REGEX)
# Build combined regex with named groups
all_patterns: list[str] = []
fail_indices: set[str] = set()
pass_indices: set[str] = set()
if fail_patterns:
for i, pattern in enumerate(fail_patterns):
all_patterns.append(f"(?P<f{i}>{pattern})")
fail_indices.add(f"f{i}")
if pass_regex:
for i, pattern in enumerate(pass_regex):
all_patterns.append(f"(?P<p{i}>{pattern})")
pass_indices.add(f"p{i}")
if not all_patterns:
return ValidationResult(is_valid=True, message="No patterns to validate")
# Single scan with combined regex
combined_regex = re.compile("|".join(all_patterns))
found_pass: set[str] = set()
for match in combined_regex.finditer(test_result.test_output):
matched_group = match.lastgroup
if matched_group in fail_indices:
original_idx = int(matched_group[1:])
return ValidationResult(
is_valid=False,
message=f"Fail pattern matched: {fail_patterns[original_idx]}",
)
if matched_group in pass_indices:
found_pass.add(matched_group)
# Check if all pass patterns were found
if pass_regex:
missing = pass_indices - found_pass
if missing:
missing_idx = int(next(iter(missing))[1:])
return ValidationResult(
is_valid=False,
message=f"Pass pattern not found: {pass_regex[missing_idx]}",
)
return ValidationResult(is_valid=True, message="All patterns validated successfully")
def validate_file_exists(path: Path, description: str = "File") -> ValidationResult:
"""Validate that a file exists and is non-empty.
Args:
path: Path to check
description: Description for error messages
Returns:
ValidationResult
"""
if not path.exists():
return ValidationResult(False, f"{description} not found: {path}")
if path.stat().st_size == 0:
return ValidationResult(False, f"{description} is empty: {path}")
return ValidationResult(True, f"{description} exists: {path}")
def _run_validation_script(
script_name: str,
args: list[str],
tests_dir: Path,
timeout: int = 60,
) -> ValidationResult:
"""Run an existing validation script from the tests directory.
Args:
script_name: Name of the script (e.g., 'validate-perfetto-proto.py')
args: Arguments to pass to the script
tests_dir: Path to directory containing validation scripts
timeout: Timeout in seconds
Returns:
ValidationResult with script output
"""
script_path = tests_dir / script_name
if not script_path.exists():
return ValidationResult(False, f"Validation script not found: {script_path}")
cmd = [sys.executable, str(script_path)] + args
cmd_str = " ".join(shlex.quote(arg) for arg in cmd)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
)
if result.returncode == 0:
message = result.stdout.strip()
else:
message = (
result.stderr.strip()
or result.stdout.strip()
or f"Exit code: {result.returncode}"
)
return ValidationResult(
is_valid=(result.returncode == 0),
message=message,
stdout=result.stdout,
stderr=result.stderr,
command=cmd_str,
)
except subprocess.TimeoutExpired:
return ValidationResult(
False, f"Validation timed out after {timeout}s", command=cmd_str
)
except Exception as e:
return ValidationResult(False, f"Validation error: {e}", command=cmd_str)
# ============================================================================
# Perfetto Validation - wraps validate-perfetto-proto.py
# ============================================================================
def validate_perfetto_trace(
trace_path: Path,
tests_dir: Path,
categories: Optional[list[str]] = None,
labels: Optional[list[str]] = None,
counts: Optional[list[int]] = None,
depths: Optional[list[int]] = None,
label_substrings: Optional[list[str]] = None,
counter_names: Optional[list[str]] = None,
key_names: Optional[list[str]] = None,
key_counts: Optional[list[int]] = None,
trace_processor_path: Optional[Path] = None,
print_output: bool = False,
timeout: int = 120,
) -> ValidationResult:
"""Validate a Perfetto trace file using validate-perfetto-proto.py.
Args:
trace_path: Path to perfetto-trace.proto file
tests_dir: Path to directory containing validation scripts
categories: List of categories to filter by (-m flag)
labels: Expected labels (-l flag)
counts: Expected counts (-c flag)
depths: Expected depths (-d flag)
label_substrings: Expected label substrings (-s flag)
counter_names: Counter names to validate (--counter-names flag)
key_names: Debug key names to check (--key-names flag)
key_counts: Expected counts for debug keys (--key-counts flag)
trace_processor_path: Path to trace_processor_shell (-t flag)
print_output: Whether to print trace data (-p flag)
timeout: Validation timeout in seconds
Returns:
ValidationResult with validation status
"""
if not trace_path.exists():
return ValidationResult(False, f"Trace file not found: {trace_path}")
# Allow override of trace_processor_path to allow perfetto validation using older GLIBC versions
env_path = os.environ.get("ROCPROFSYS_TRACE_PROC_SHELL")
if env_path:
trace_processor_path = Path(env_path)
args = ["-i", str(trace_path)]
if categories:
args.extend(["-m"] + categories)
if labels:
args.extend(["-l"] + labels)
elif label_substrings:
args.extend(["-s"] + label_substrings)
if counts:
args.extend(["-c"] + [str(c) for c in counts])
if depths:
args.extend(["-d"] + [str(d) for d in depths])
if counter_names:
args.extend(["--counter-names"] + counter_names)
if key_names:
args.extend(["--key-names"] + key_names)
if key_counts:
args.extend(["--key-counts"] + [str(k) for k in key_counts])
if trace_processor_path:
args.extend(["-t", str(trace_processor_path)])
if print_output:
args.append("-p")
return _run_validation_script("validate-perfetto-proto.py", args, tests_dir, timeout)
# ============================================================================
# ROCpd Database Validation - wraps validate-rocpd.py
# ============================================================================
def validate_rocpd_database(
db_path: Path,
tests_dir: Path,
rules_files: Optional[list[Path]] = None,
timeout: int = 60,
) -> ValidationResult:
"""Validate a ROCpd database file using validate-rocpd.py.
Args:
db_path: Path to rocpd.db file
tests_dir: Path to directory containing validation scripts
rules_files: List of JSON rules files to use for validation
timeout: Validation timeout in seconds
Returns:
ValidationResult with validation status
"""
if not db_path.exists():
return ValidationResult(False, f"Database not found: {db_path}")
args = ["-db", str(db_path)]
if rules_files:
existing_rules = [str(r) for r in rules_files if r.exists()]
if existing_rules:
args.extend(["-r"] + existing_rules)
return _run_validation_script("validate-rocpd.py", args, tests_dir, timeout)
# ============================================================================
# Timemory JSON Validation - wraps validate-timemory-json.py
# ============================================================================
def validate_timemory_json(
json_path: Path,
tests_dir: Path,
metric: str,
labels: Optional[list[str]] = None,
counts: Optional[list[int]] = None,
depths: Optional[list[int]] = None,
print_output: bool = False,
timeout: int = 60,
) -> ValidationResult:
"""Validate a timemory JSON output file using validate-timemory-json.py.
Args:
json_path: Path to JSON file
metric: Metric name to validate (-m flag)
tests_dir: Path to directory containing validation scripts
labels: Expected labels (-l flag)
counts: Expected counts (-c flag)
depths: Expected depths (-d flag)
print_output: Whether to print data (-p flag)
timeout: Validation timeout in seconds
Returns:
ValidationResult with validation status
"""
if not json_path.exists():
return ValidationResult(False, f"JSON file not found: {json_path}")
args = ["-i", str(json_path), "-m", metric]
if labels:
args.extend(["-l"] + labels)
if counts:
args.extend(["-c"] + [str(c) for c in counts])
if depths:
args.extend(["-d"] + [str(d) for d in depths])
if print_output:
args.append("-p")
return _run_validation_script("validate-timemory-json.py", args, tests_dir, timeout)
# ============================================================================
# Causal JSON Validation - wraps validate-causal-json.py
# ============================================================================
def validate_causal_json(
json_path: Path,
tests_dir: Path,
ci_mode: bool = False,
additional_args: Optional[list[str]] = None,
timeout: int = 60,
) -> ValidationResult:
"""Validate a causal profiling JSON output file using validate-causal-json.py.
Args:
json_path: Path to causal JSON file
tests_dir: Path to directory containing validation scripts
ci_mode: Whether running in CI mode (--ci flag)
additional_args: Additional arguments to pass to the script
timeout: Validation timeout in seconds
Returns:
ValidationResult with validation status
"""
if not json_path.exists():
return ValidationResult(False, f"JSON file not found: {json_path}")
args = [str(json_path)]
if ci_mode:
args.append("--ci")
if additional_args:
args.extend(additional_args)
return _run_validation_script("validate-causal-json.py", args, tests_dir, timeout)
+757
Ver ficheiro
@@ -0,0 +1,757 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests rocprof-sys binaries
"""
from __future__ import annotations
import pytest
from pathlib import Path
import os
pytestmark = [pytest.mark.rocprof_binary]
# ============================================================================
# Helper functions
# ============================================================================
def get_ls_command() -> tuple[str, list[str]]:
"""Get ls binary name and args (handles RedHat coreutils wrapper).
Returns:
Tuple of (binary_name, args_list)
"""
if os.path.exists("/usr/bin/coreutils"):
return "coreutils", ["--coreutils-prog=ls"]
return "ls", []
# ============================================================================
# rocprof-sys-instrument tests
# ============================================================================
class TestInstrumentBinary:
"""Tests for rocprof-sys-instrument binary."""
target = "rocprof-sys-instrument"
def test_help(
self,
run_test,
assert_regex,
):
pass_regex = [
r"\[rocprof-sys-instrument\] Usage:[\s\S]*"
r"\[DEBUG OPTIONS\][\s\S]*"
r"\[MODE OPTIONS\][\s\S]*"
r"\[LIBRARY OPTIONS\][\s\S]*"
r"\[SYMBOL SELECTION OPTIONS\][\s\S]*"
r"\[RUNTIME OPTIONS\][\s\S]*"
r"\[GRANULARITY OPTIONS\][\s\S]*"
r"\[DYNINST OPTIONS\]"
]
result = run_test(
"baseline",
target=self.target,
run_args=["--help"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_simulate_ls(
self,
run_test,
assert_regex,
assert_file_exists,
):
ls_name, ls_args = get_ls_command()
test_args = [
"--simulate",
"--print-format",
"json",
"txt",
"xml",
"-v",
"2",
"--all-functions",
"--",
ls_name,
*ls_args,
]
expected_files = [
"available.json",
"available.txt",
"available.xml",
"excluded.json",
"excluded.txt",
"excluded.xml",
"instrumented.json",
"instrumented.txt",
"instrumented.xml",
"overlapping.json",
"overlapping.txt",
"overlapping.xml",
]
result = run_test(
"baseline",
target=self.target,
run_args=test_args,
timeout=240,
fail_on_not_found=True,
)
assert_regex(result)
expected_files_paths = [
result.output_dir / "instrumentation" / f for f in expected_files
]
assert_file_exists(expected_files_paths)
def test_simulate_lib(
self,
rocprof_config,
run_test,
assert_regex,
):
user_lib = rocprof_config.rocprofsys_lib_dir / "librocprof-sys-user.so"
if not user_lib.exists():
pytest.fail("librocprof-sys-user.so not found")
pass_regex = [
r"\[rocprof-sys\]\[exe\] Runtime instrumentation is not possible![\s\S]*"
r"\[rocprof-sys\]\[exe\] Switching to binary rewrite mode and assuming '--simulate --all-functions'"
]
result = run_test(
"baseline",
target=self.target,
run_args=["--print-available", "functions", "-v", "2", "--", str(user_lib)],
timeout=120,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_simulate_lib_basename(
self,
rocprof_config,
test_output_dir,
run_test,
assert_regex,
):
"""Test instrument with library basename.
This MUST be run from a tmp directory, NOT from the actual lib directory.
Running from the lib directory causes Dyninst to modify the library in-place,
contaminating it with instrumentation markers. This breaks all subsequent
binary rewrite tests with "unable to reinstrument previously instrumented
binary" errors.
"""
lib_basename = "librocprof-sys-user.so"
user_lib = rocprof_config.rocprofsys_lib_dir / lib_basename
if not user_lib.exists():
pytest.skip(f"{lib_basename} not built")
tmp_dir = test_output_dir / "tmp"
tmp_dir.mkdir(parents=True, exist_ok=True)
output_lib = test_output_dir / lib_basename
result = run_test(
"baseline",
target=self.target,
run_args=[
"--print-available",
"functions",
"-v",
"2",
"-o",
str(output_lib),
"--",
lib_basename,
],
timeout=120,
working_directory=tmp_dir,
fail_on_not_found=True,
)
assert_regex(result)
def test_write_log(
self,
run_test,
assert_regex,
assert_file_exists,
):
"""Test instrument writing to log file."""
ls_name, ls_args = get_ls_command()
pass_regex = [r"Opening .*/instrumentation/user\.log"]
result = run_test(
"baseline",
target=self.target,
run_args=[
"--print-instrumented",
"functions",
"-v",
"1",
"--log-file",
"user.log",
"--",
ls_name,
*ls_args,
],
timeout=120,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
assert_file_exists(result.output_dir / "instrumentation" / "user.log")
# ============================================================================
# rocprof-sys-avail tests
# ============================================================================
class TestAvailBinary:
"""Tests for rocprof-sys-avail binary."""
target = "rocprof-sys-avail"
def test_help(
self,
run_test,
assert_regex,
):
pass_regex = [
r"\[rocprof-sys-avail\] Usage:[\s\S]*"
r"\[DEBUG OPTIONS\][\s\S]*"
r"\[INFO OPTIONS\][\s\S]*"
r"\[FILTER OPTIONS\][\s\S]*"
r"\[COLUMN OPTIONS\][\s\S]*"
r"\[DISPLAY OPTIONS\][\s\S]*"
r"\[OUTPUT OPTIONS\][\s\S]*"
]
result = run_test(
"baseline",
target=self.target,
run_args=["--help"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_all(
self,
run_test,
assert_regex,
):
result = run_test(
"baseline",
target=self.target,
run_args=["--all"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result)
def test_all_expand_keys(
self,
run_test,
assert_regex,
):
fail_regex = [r"%[a-zA-Z_]%"]
result = run_test(
"baseline",
target=self.target,
run_args=["--all", "--expand-keys"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, fail_regex=fail_regex)
def test_all_only_available_alphabetical(
self,
run_test,
test_output_dir,
assert_regex,
assert_file_exists,
):
log_file = (
test_output_dir / "rocprof-sys-avail-all-only-available-alphabetical.log"
)
result = run_test(
"baseline",
target=self.target,
run_args=[
"--all",
"--available",
"--alphabetical",
"--debug",
"--output",
str(log_file),
],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result)
assert_file_exists(log_file)
def test_all_csv(
self,
run_test,
assert_regex,
):
pass_regex = [
r"COMPONENT#AVAILABLE#VALUE_TYPE#STRING_IDS#FILENAME#DESCRIPTION#CATEGORY#[\s\S]*"
r"ENVIRONMENT VARIABLE#VALUE#DATA TYPE#DESCRIPTION#CATEGORIES#[\s\S]*"
r"HARDWARE COUNTER#DEVICE#AVAILABLE#DESCRIPTION#"
]
result = run_test(
"baseline",
target=self.target,
run_args=["--all", "--csv", "--csv-separator", "#"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_filter_wall_clock_available(
self,
run_test,
assert_regex,
):
pass_regex = [
r"\|[-]+\|[\s\S]*"
r"\|[ ]+COMPONENT[ ]+\|[\s\S]*"
r"\|[-]+\|[\s\S]*"
r"\| (wall_clock)[ ]+\|[\s\S]*"
r"\|[-]+\|"
]
result = run_test(
"baseline",
target=self.target,
run_args=["-r", "wall_clock", "-C", "--available"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_category_filter_rocprofiler_systems(
self,
run_test,
assert_regex,
):
pass_regex = [r"ROCPROFSYS_(SETTINGS_DESC|OUTPUT_FILE|OUTPUT_PREFIX)"]
fail_regex = [
r"ROCPROFSYS_(ADD_SECONDARY|SCIENTIFIC|PRECISION|MEMORY_PRECISION|TIMING_PRECISION)",
]
result = run_test(
"baseline",
target=self.target,
run_args=["--categories", "settings::rocprofsys", "--brief"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex, fail_regex=fail_regex)
def test_category_filter_timemory(
self,
run_test,
assert_regex,
):
pass_regex = [
r"ROCPROFSYS_(ADD_SECONDARY|SCIENTIFIC|PRECISION|MEMORY_PRECISION|TIMING_PRECISION)"
]
fail_regex = [r"ROCPROFSYS_(SETTINGS_DESC|OUTPUT_FILE)"]
result = run_test(
"baseline",
target=self.target,
run_args=["--categories", "settings::timemory", "--brief", "--advanced"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex, fail_regex=fail_regex)
def test_regex_negation(
self,
run_test,
assert_regex,
):
pass_regex = [
r"ENVIRONMENT VARIABLE,[\s\S]*"
r"ROCPROFSYS_CI_SKIP_PUSH_POP_CHECK,[\s\S]*"
r"ROCPROFSYS_THREAD_POOL_SIZE,[\s\S]*"
r"ROCPROFSYS_USE_PID,"
]
fail_regex = [r"ROCPROFSYS_TRACE"]
result = run_test(
"baseline",
target=self.target,
run_args=[
"-R",
"rocprofsys",
"~timemory",
"-r",
"_P",
"~PERFETTO",
"~PROCESS_SAMPLING",
"~KOKKOSP",
"~PAGE",
"--csv",
"--brief",
"--advanced",
],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex, fail_regex=fail_regex)
def test_write_config(
self,
run_test,
test_output_dir,
assert_regex,
assert_file_exists,
):
config_base = test_output_dir / "rocprof-sys-test"
avail_cfg_path = test_output_dir / "rocprof-sys-"
avail_cfg_path = str(avail_cfg_path).replace("+", r"\+")
pass_regex = [
rf"Outputting JSON configuration file '{avail_cfg_path}test\.json'"
r"[\s\S]*"
rf"Outputting XML configuration file '{avail_cfg_path}test\.xml'"
r"[\s\S]*"
rf"Outputting text configuration file '{avail_cfg_path}test\.cfg'"
]
result = run_test(
"baseline",
target=self.target,
run_args=[
"-G",
str(config_base) + ".cfg",
"-F",
"txt",
"json",
"xml",
"--force",
"--all",
"-c",
"rocprofsys",
],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
config_files = [
test_output_dir / f"rocprof-sys-test.{ext}" for ext in ["cfg", "json", "xml"]
]
assert_file_exists(config_files, subtest_name="Config file existence validation")
def test_write_config_tweak(
self,
run_test,
test_output_dir,
assert_regex,
assert_file_exists,
):
config_base = test_output_dir / "rocprof-sys-tweak"
env_overrides = {
"ROCPROFSYS_TRACE": "OFF",
"ROCPROFSYS_PROFILE": "ON",
"ROCPROFSYS_USE_SAMPLING": "OFF",
"ROCPROFSYS_TIME_OUTPUT": "OFF",
}
avail_cfg_path = test_output_dir / "rocprof-sys-"
avail_cfg_path = str(avail_cfg_path).replace("+", r"\+")
pass_regex = [
rf"Outputting JSON configuration file '{avail_cfg_path}tweak\.json'"
r"[\s\S]*"
rf"Outputting XML configuration file '{avail_cfg_path}tweak\.xml'"
r"[\s\S]*"
rf"Outputting text configuration file '{avail_cfg_path}tweak\.cfg'"
]
result = run_test(
"baseline",
target=self.target,
run_args=[
"-G",
str(config_base) + ".cfg",
"-F",
"txt",
"json",
"xml",
"--force",
],
timeout=45,
fail_on_not_found=True,
env=env_overrides,
)
assert_regex(result, pass_regex=pass_regex)
config_files = [
test_output_dir / f"rocprof-sys-tweak.{ext}" for ext in ["cfg", "json", "xml"]
]
assert_file_exists(config_files, subtest_name="Config file existence validation")
def test_list_keys(
self,
run_test,
assert_regex,
):
pass_regex = [r"Output Keys:[\s\S]*%argv%[\s\S]*%argv_hash%"]
result = run_test(
"baseline",
target=self.target,
run_args=["--list-keys", "--expand-keys"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_list_keys_markdown(
self,
run_test,
assert_regex,
):
pass_regex = [r"`%argv%`[\s\S]*`%argv_hash%`"]
result = run_test(
"baseline",
target=self.target,
run_args=["--list-keys", "--expand-keys", "--markdown"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_list_categories(
self,
run_test,
assert_regex,
):
pass_regex = [r" component::[\s\S]* hw_counters::[\s\S]* settings::"]
result = run_test(
"baseline",
target=self.target,
run_args=["--list-categories"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
def test_core_categories(
self,
run_test,
assert_regex,
):
pass_regex = [
r"ROCPROFSYS_CONFIG_FILE[\s\S]*ROCPROFSYS_ENABLED[\s\S]*"
r"ROCPROFSYS_SUPPRESS_CONFIG[\s\S]*ROCPROFSYS_SUPPRESS_PARSING[\s\S]*ROCPROFSYS_VERBOSE"
]
result = run_test(
"baseline",
target=self.target,
run_args=["-c", "core"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result, pass_regex=pass_regex)
# ============================================================================
# rocprof-sys-run tests
# ============================================================================
class TestRunBinary:
"""Tests for rocprof-sys-run binary."""
target = "rocprof-sys-run"
def test_help(
self,
run_test,
assert_regex,
):
"""Test rocprof-sys-run --help output."""
result = run_test(
"baseline",
target=self.target,
run_args=["--help"],
timeout=45,
fail_on_not_found=True,
)
assert_regex(result)
def test_args(
self,
test_output_dir,
run_test,
assert_regex,
):
"""Test rocprof-sys-run with comprehensive arguments."""
import shutil
# Check if sleep command exists
sleep_cmd = shutil.which("sleep")
if not sleep_cmd:
pytest.skip("sleep command not found")
# Create empty config file
config_dir = test_output_dir / "config"
config_dir.mkdir(parents=True, exist_ok=True)
empty_cfg = config_dir / "empty.cfg"
empty_cfg.write_text("#\n# empty config file\n#\n")
tmpdir = test_output_dir / "tmpdir"
tmpdir = tmpdir.resolve()
tmpdir.mkdir(parents=True, exist_ok=True)
args = [
"--monochrome",
"--debug=false",
"-v",
"1",
"-c",
str(empty_cfg),
"-o",
str(test_output_dir),
"run-args-output/",
"-TPHD",
"-S",
"cputime",
"realtime",
"--trace-wait=1.0e-12",
"--trace-duration=5.0",
"--wait=1.0",
"--duration=3.0",
"--trace-file=perfetto-run-args-trace.proto",
"--trace-buffer-size=100",
"--trace-fill-policy=ring_buffer",
"--profile-format",
"console",
"json",
"text",
"--process-freq",
"1000",
"--process-wait",
"0.0",
"--process-duration",
"10",
"--cpus",
"0-4",
"--gpus",
"0",
"-f",
"1000",
"--sampling-wait",
"1.0",
"--sampling-duration",
"10",
"-t",
"0-3",
"--sample-cputime",
"1000",
"1.0",
"0-3",
"--sample-realtime",
"10",
"0.5",
"0-3",
"-I",
"all",
"-E",
"mutex-locks",
"rw-locks",
"spin-locks",
"-C",
"perf::INSTRUCTIONS",
"--inlines",
"--hsa-interrupt",
"0",
"--use-causal=false",
"--use-kokkosp",
"--num-threads-hint=4",
"--sampling-allocator-size=32",
"--ci",
"--dl-verbose=3",
"--perfetto-annotations=off",
"--kokkosp-kernel-logger",
"--kokkosp-name-length-max=1024",
'--kokkosp-prefix="[kokkos]"',
"--tmpdir",
str(tmpdir),
"--perfetto-backend",
"inprocess",
"--use-pid",
"false",
"--time-output",
"off",
"--thread-pool-size",
"0",
"--timemory-components",
"wall_clock",
"cpu_clock",
"peak_rss",
"page_rss",
"--fork",
"--",
sleep_cmd,
"5",
]
result = run_test(
"baseline",
target=self.target,
run_args=args,
timeout=45,
fail_on_not_found=True,
)
assert_regex(result)
+108
Ver ficheiro
@@ -0,0 +1,108 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
General configuration file tests.
"""
from __future__ import annotations
import pytest
from pathlib import Path
import shutil
pytestmark = [pytest.mark.rocprof_config]
# ============================================================================
# Helper functions
# ============================================================================
def write_invalid_config_file(output_dir: Path) -> Path:
"""Write an invalid configuration file."""
config_path = output_dir / "invalid.cfg"
config_path.write_text("""\
ROCPROFSYS_CONFIG_FILE =
FOOBAR = ON
""")
return config_path
# =============================================================================
# Config fixtures
# =============================================================================
@pytest.fixture
def config_target(rocprof_config) -> str:
"""Get the target executable for config tests."""
target_name = "parallel-overhead"
try:
rocprof_config.get_target_executable(target_name)
except FileNotFoundError:
# Fall back to system ls command
target_name = shutil.which("ls") or "ls"
return target_name
# =============================================================================
# Configuration file tests
# =============================================================================
class TestConfig:
"""Tests for configuration file tests."""
def test_invalid_config(
self,
test_output_dir: Path,
config_target: str,
run_test,
assert_regex,
):
"""Test that invalid config file causes failure."""
# Write invalid configuration file to test output directory
config_file = write_invalid_config_file(test_output_dir)
env = {"ROCPROFSYS_CONFIG_FILE": str(config_file)}
result = run_test(
"runtime_instrument",
target=config_target,
env=env,
timeout=400, # In xdist, it can take much longer
fail_on_pass=True, # Expected to fail
)
assert_regex(
result,
pass_regex=[r"Unknown setting 'FOOBAR' \(value = 'ON'\)"],
use_abort_fail_regex=False,
)
def test_missing_config(
self,
test_output_dir: Path,
config_target: str,
run_test,
assert_regex,
):
"""Test that missing config file causes failure."""
# Use a path to a config file that doesn't exist
missing_config = test_output_dir / "missing.cfg"
env = {"ROCPROFSYS_CONFIG_FILE": str(missing_config)}
result = run_test(
"runtime_instrument",
target=config_target,
env=env,
timeout=120,
fail_on_pass=True, # Expected to fail
)
assert_regex(
result,
pass_regex=[r"Error reading configuration file"],
use_abort_fail_regex=False,
)
@@ -0,0 +1,79 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for GPU connectivity
"""
from __future__ import annotations
import pytest
from pathlib import Path
# =============================================================================
# GPU connectivity fixtures
# =============================================================================
@pytest.fixture
def gpu_connect_env() -> dict[str, str]:
"""Environment variables for GPU connectivity tests."""
return {
"ROCPROFSYS_TRACE": "ON",
"ROCPROFSYS_TRACE_LEGACY": "ON",
"ROCPROFSYS_ROCM_DOMAINS": "hip_runtime_api",
"ROCPROFSYS_AMD_SMI_METRICS": "busy,temp,power,xgmi,pcie",
"ROCPROFSYS_SAMPLING_CPUS": "none",
"ROCPROFSYS_USE_SAMPLING": "OFF",
"ROCPROFSYS_PROCESS_SAMPLING_FREQ": "50",
"ROCPROFSYS_CPU_FREQ_ENABLED": "OFF",
}
@pytest.fixture
def gpu_connect_rules(validation_rules_dir: Path) -> list[Path]:
"""Get validation rules for GPU connectivity tests."""
rules_dir = validation_rules_dir / "gpu-connect"
return [
rules_dir / "validation-rules.json",
rules_dir / "amd-smi-rules.json",
]
# =============================================================================
# GPU connectivity tests
# =============================================================================
@pytest.mark.gpu
@pytest.mark.xgmi
@pytest.mark.run_if_gpu_category("not apu or instinct")
class TestGPUConnect:
"""Tests for GPU connectivity tests."""
@pytest.mark.rocpd("gpu_connect_env")
def test_sys_run(
self,
run_test,
gpu_connect_env: dict[str, str],
gpu_connect_rules: list[Path],
assert_regex,
assert_perfetto,
assert_rocpd,
):
result = run_test(
"sys_run",
target="transferBench",
env=gpu_connect_env,
timeout=120,
)
# Determine whether to skip or not
if "Error: No valid transfers created" in result.test_output:
pytest.skip("No valid transfers created")
else:
assert_regex(result)
assert_perfetto(
result,
counter_names=["XGMI Read Data", "XGMI Write Data"],
)
assert_rocpd(result, rules_files=gpu_connect_rules)
@@ -0,0 +1,96 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for HIP stream API
"""
from __future__ import annotations
import pytest
# =============================================================================
# HIP stream tests
# =============================================================================
@pytest.mark.gpu
@pytest.mark.rocm_min_version("7.0")
@pytest.mark.group_by_queue
class TestTransposeGroupByQueue:
"""Tests for transpose with group by queue"""
def test_sampling(
self,
run_test,
base_env: dict[str, str],
assert_regex,
):
env = base_env.copy()
env["ROCPROFSYS_ROCM_GROUP_BY_QUEUE"] = "YES"
result = run_test(
"sampling",
target="transpose",
env=env,
timeout=120,
)
assert_regex(result)
def test_sys_run(
self,
run_test,
base_env: dict[str, str],
assert_regex,
):
env = base_env.copy()
env["ROCPROFSYS_ROCM_GROUP_BY_QUEUE"] = "YES"
result = run_test(
"sys_run",
target="transpose",
env=env,
timeout=120,
)
assert_regex(result)
@pytest.mark.gpu
@pytest.mark.rocm_min_version("7.0")
@pytest.mark.group_by_stream
class TestTransposeGroupByStream:
def test_sampling(
self,
run_test,
base_env: dict[str, str],
assert_regex,
):
env = base_env.copy()
env["ROCPROFSYS_ROCM_GROUP_BY_QUEUE"] = "NO"
result = run_test(
"sampling",
target="transpose",
env=env,
timeout=120,
)
assert_regex(result)
def test_sys_run(
self,
run_test,
base_env: dict[str, str],
assert_regex,
):
env = base_env.copy()
env["ROCPROFSYS_ROCM_GROUP_BY_QUEUE"] = "NO"
result = run_test(
"sys_run",
target="transpose",
env=env,
timeout=120,
)
assert_regex(result)
@@ -0,0 +1,118 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for the jpegdecode example.
"""
from __future__ import annotations
import pytest
from pathlib import Path
pytestmark = [pytest.mark.gpu, pytest.mark.decode, pytest.mark.jpegdecode]
from rocprofsys import (
GPUInfo,
RocprofsysConfig,
)
# =============================================================================
# JPEG decode fixtures
# =============================================================================
@pytest.fixture
def jpeg_decode_env() -> dict[str, str]:
"""Environment variables for JPEG decode tests."""
return {
"ROCPROFSYS_ROCM_DOMAINS": "hip_runtime_api,kernel_dispatch,memory_copy,rocjpeg_api",
"ROCPROFSYS_AMD_SMI_METRICS": "busy,temp,power,jpeg_activity,mem_usage",
"ROCPROFSYS_SAMPLING_CPUS": "none",
}
@pytest.fixture
def jpeg_decode_rules(validation_rules_dir: Path) -> list[Path]:
"""Get validation rules for JPEG decode tests."""
rules_dir = validation_rules_dir / "jpeg-decode"
return [
validation_rules_dir / "default-rules.json",
rules_dir / "validation-rules.json",
rules_dir / "sdk-metrics-rules.json",
]
# =============================================================================
# JPEG decode tests
# =============================================================================
class TestJPEGDecode:
"""Tests for the jpegdecode example."""
@pytest.mark.rocpd("jpeg_decode_env")
def test_sampling(
self,
run_test,
rocprof_config: RocprofsysConfig,
jpeg_decode_env: dict[str, str],
gpu_info: GPUInfo,
jpeg_decode_rules: list[Path],
assert_regex,
assert_perfetto,
assert_rocpd,
):
env = jpeg_decode_env.copy()
if "instinct" in gpu_info.categories:
rules_dir = rocprof_config.rocpd_validation_rules / "jpeg-decode"
jpeg_decode_rules.append(rules_dir / "amd-smi-rules.json")
result = run_test(
"sampling",
target="jpegdecode",
env=env,
timeout=120,
run_args=[
"-i",
str(rocprof_config.rocprofsys_examples_dir / "images"),
"-b",
"32",
],
no_check_target_arch=True,
)
assert_regex(result)
assert_perfetto(
result,
categories=["rocm_rocjpeg_api"],
labels=["rocJpegCreate"],
counts=[1],
depths=[1],
counter_names=(
["JPEG Activity"] if "instinct" in gpu_info.categories else None
),
)
assert_rocpd(result, rules_files=jpeg_decode_rules)
def test_sys_run(
self,
run_test,
rocprof_config: RocprofsysConfig,
jpeg_decode_env: dict[str, str],
assert_regex,
):
result = run_test(
"sys_run",
target="jpegdecode",
env=jpeg_decode_env,
timeout=120,
run_args=[
"-i",
str(rocprof_config.rocprofsys_examples_dir / "images"),
"-b",
"32",
],
no_check_target_arch=True,
)
assert_regex(result)
+529
Ver ficheiro
@@ -0,0 +1,529 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for OpenMP integration with rocprofiler-systems.
This module tests OpenMP examples with various configurations:
- OpenMP CG (Conjugate Gradient) with OMPT
- OpenMP LU decomposition
- OpenMP target offload (GPU)
- OpenMP VV Host
- OpenMP VV Offload (GPU)
- Sampling duration tests
Note: OMPT backend is unavailable and tests are skipped if no GPU is available.
"""
from __future__ import annotations
import pytest
from pathlib import Path
# OpenMP will not be traced if no GPU is available, this includes CPU-only
pytestmark = [pytest.mark.gpu, pytest.mark.openmp]
# ============================================================================
# OpenMP Fixtures
# ============================================================================
@pytest.fixture
def ompt_env() -> dict[str, str]:
"""Environment variables for OMPT tests."""
return {
"ROCPROFSYS_TRACE": "ON",
"ROCPROFSYS_PROFILE": "ON",
"ROCPROFSYS_TIME_OUTPUT": "OFF",
"ROCPROFSYS_USE_OMPT": "ON",
"ROCPROFSYS_TIMEMORY_COMPONENTS": "wall_clock,trip_count,peak_rss",
"OMP_PROC_BIND": "spread",
"OMP_PLACES": "threads",
"OMP_NUM_THREADS": "2",
}
@pytest.fixture
def ompt_sampling_env(ompt_env: dict[str, str]) -> dict[str, str]:
"""Environment variables for sampling duration tests."""
env = ompt_env.copy()
env.update(
{
"ROCPROFSYS_VERBOSE": "2",
"ROCPROFSYS_USE_OMPT": "OFF",
"ROCPROFSYS_USE_SAMPLING": "ON",
"ROCPROFSYS_USE_PROCESS_SAMPLING": "OFF",
"ROCPROFSYS_SAMPLING_FREQ": "100",
"ROCPROFSYS_SAMPLING_DELAY": "0.1",
"ROCPROFSYS_SAMPLING_DURATION": "0.25",
"ROCPROFSYS_SAMPLING_CPUTIME": "ON",
"ROCPROFSYS_SAMPLING_REALTIME": "ON",
"ROCPROFSYS_SAMPLING_CPUTIME_FREQ": "1000",
"ROCPROFSYS_SAMPLING_REALTIME_FREQ": "500",
"ROCPROFSYS_MONOCHROME": "ON",
}
)
return env
@pytest.fixture
def openmp_target_env(ompt_env: dict[str, str]) -> dict[str, str]:
"""Environment variables for OpenMP target (GPU) tests."""
env = ompt_env.copy()
env["ROCPROFSYS_ROCM_DOMAINS"] = "hip_api,hsa_api,kernel_dispatch"
return env
@pytest.fixture
def ompt_no_tmp_env(ompt_env: dict[str, str]) -> dict[str, str]:
"""Environment variables for no-tmp-files tests."""
env = ompt_env.copy()
env.update(
{
"ROCPROFSYS_VERBOSE": "2",
"ROCPROFSYS_USE_OMPT": "OFF",
"ROCPROFSYS_USE_SAMPLING": "ON",
"ROCPROFSYS_USE_PROCESS_SAMPLING": "OFF",
"ROCPROFSYS_SAMPLING_CPUTIME": "ON",
"ROCPROFSYS_SAMPLING_REALTIME": "OFF",
"ROCPROFSYS_SAMPLING_CPUTIME_FREQ": "700",
"ROCPROFSYS_USE_TEMPORARY_FILES": "OFF",
"ROCPROFSYS_MONOCHROME": "ON",
}
)
return env
@pytest.fixture
def openmp_target_rules(validation_rules_dir: Path) -> list[Path]:
"""Get validation rules for OpenMP target tests."""
rules_dir = validation_rules_dir / "openmp-target"
return [
rules_dir / "kernel-rules.json",
rules_dir / "sdk-metrics-rules.json",
]
# ============================================================================
# Test Class: OpenMP CG Tests
# ============================================================================
class TestOpenMPCG:
"""Tests for OpenMP Conjugate Gradient example."""
REWRITE_ARGS = ["-e", "-v", "2", "--instrument-loops"]
def test_sampling(
self,
ompt_env: dict[str, str],
run_test,
assert_regex,
):
env = ompt_env.copy()
env["ROCPROFSYS_USE_SAMPLING"] = "OFF"
env["ROCPROFSYS_COUT_OUTPUT"] = "ON"
result = run_test(
"sampling",
target="openmp-cg",
env=env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result)
def test_binary_rewrite(
self,
run_test,
ompt_env: dict[str, str],
assert_regex,
):
env = ompt_env.copy()
env["ROCPROFSYS_USE_SAMPLING"] = "OFF"
env["ROCPROFSYS_COUT_OUTPUT"] = "ON"
result = run_test(
"binary_rewrite",
target="openmp-cg",
rewrite_args=self.REWRITE_ARGS,
env=env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result)
# ============================================================================
# Test Class: OpenMP LU Tests
# ============================================================================
class TestOpenMPLU:
"""Tests for OpenMP LU decomposition example."""
REWRITE_ARGS = ["-e", "-v", "2", "--instrument-loops"]
def test_binary_rewrite(
self,
run_test,
ompt_env: dict[str, str],
assert_regex,
):
env = ompt_env.copy()
env["ROCPROFSYS_USE_SAMPLING"] = "ON"
env["ROCPROFSYS_SAMPLING_FREQ"] = "50"
env["ROCPROFSYS_COUT_OUTPUT"] = "ON"
result = run_test(
"binary_rewrite",
target="openmp-lu",
rewrite_args=self.REWRITE_ARGS,
env=env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result)
# ============================================================================
# Test Class: OpenMP Target (GPU) Tests
# ============================================================================
@pytest.mark.openmp_target
class TestOpenMPTarget:
"""Tests for OpenMP target offload (GPU) example."""
@pytest.mark.rocpd("openmp_target_env")
def test_sampling(
self,
run_test,
openmp_target_env: dict[str, str],
openmp_target_rules: list[Path],
assert_regex,
assert_perfetto,
assert_rocpd,
):
result = run_test(
"sampling",
target="openmp-target",
env=openmp_target_env,
timeout=300,
no_check_target_arch=True,
)
assert_regex(result)
assert_rocpd(result, rules_files=openmp_target_rules)
assert_perfetto(
result,
subtest_name="Perfetto Kernel Dispatch Validation",
categories=["rocm_kernel_dispatch"],
label_substrings=[
"Z4vmulIiEvPT_S1_S1_i_l51.kd",
"Z4vmulIfEvPT_S1_S1_i_l51.kd",
"Z4vmulIdEvPT_S1_S1_i_l51.kd",
],
depths=[0, 0, 0],
counts=[4, 4, 4],
)
# ============================================================================
# Test Class: OpenMP-VV Host Tests
# ============================================================================
@pytest.mark.parametrize(
"target_name",
[
"openmp-vv-host-test-parallel-for-simd-atomic",
"openmp-vv-host-test-team-default-shared",
],
ids=["parallel-for-simd-atomic", "team-default-shared"],
)
@pytest.mark.ompvv
class TestOpenMPVVHost:
"""Tests for OpenMP VV host programs."""
def test_baseline(
self,
run_test,
ompt_env: dict[str, str],
target_name: str,
assert_regex,
):
result = run_test(
"baseline",
target=target_name,
env=ompt_env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result)
def test_sampling(
self,
run_test,
ompt_env: dict[str, str],
target_name: str,
assert_regex,
assert_perfetto,
):
result = run_test(
"sampling",
target=target_name,
env=ompt_env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result)
assert_perfetto(result)
def test_binary_rewrite(
self,
run_test,
ompt_env: dict[str, str],
target_name: str,
assert_regex,
):
env = ompt_env.copy()
env["ROCPROFSYS_COUT_OUTPUT"] = "ON"
result = run_test(
"binary_rewrite",
target=target_name,
rewrite_args=["-e", "-v", "2", "--instrument-loops"],
env=env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result, pass_regex=[r"omp_parallel"])
def test_runtime_instrument(
self,
run_test,
ompt_env: dict[str, str],
target_name: str,
assert_regex,
):
env = ompt_env.copy()
env["ROCPROFSYS_COUT_OUTPUT"] = "ON"
env["ROCPROFSYS_CI_SKIP_PUSH_POP_CHECK"] = "ON"
result = run_test(
"runtime_instrument",
target=target_name,
instrument_args=["-e", "-v", "1", "--label", "return", "args"],
env=env,
no_check_target_arch=True,
)
assert_regex(result, pass_regex=[r"omp_parallel"])
def test_sys_run(
self,
run_test,
ompt_env: dict[str, str],
target_name: str,
assert_regex,
assert_perfetto,
):
result = run_test(
"sys_run",
target=target_name,
env=ompt_env,
timeout=180,
no_check_target_arch=True,
)
assert_regex(result)
assert_perfetto(result)
# ============================================================================
# Test Class: OpenMP-VV Offload (GPU) Tests
# ============================================================================
@pytest.mark.parametrize(
"target_name",
[
"openmp-vv-offload-test-target-simd-if",
"openmp-vv-offload-test-target-teams-distribute-parallel-for-collapse",
],
ids=["target-simd-if", "target-teams-distribute-parallel-for-collapse"],
)
@pytest.mark.openmp_target
@pytest.mark.ompvv
class TestOpenMPVVOffload:
"""Tests for OpenMP VV offload programs."""
def test_baseline(
self,
run_test,
openmp_target_env: dict[str, str],
target_name: str,
assert_regex,
):
result = run_test(
"baseline",
target=target_name,
env=openmp_target_env,
timeout=300,
)
assert_regex(result)
def test_sampling(
self,
run_test,
openmp_target_env: dict[str, str],
target_name: str,
assert_regex,
):
result = run_test(
"sampling",
target=target_name,
env=openmp_target_env,
timeout=300,
)
assert_regex(result)
def test_binary_rewrite(
self,
run_test,
openmp_target_env: dict[str, str],
target_name: str,
assert_regex,
):
env = openmp_target_env.copy()
env["ROCPROFSYS_COUT_OUTPUT"] = "ON"
result = run_test(
"binary_rewrite",
target=target_name,
rewrite_args=["-e", "-v", "2"],
env=env,
timeout=300,
)
assert_regex(result, pass_regex=[r"omp_offloading"])
def test_sys_run(
self,
run_test,
openmp_target_env: dict[str, str],
target_name: str,
assert_regex,
assert_perfetto,
):
result = run_test(
"sys_run",
target=target_name,
run_args=["-e", "-v", "1", "--label", "return", "args"],
env=openmp_target_env,
timeout=300,
)
assert_regex(result)
assert_perfetto(result)
# ============================================================================
# Test Class: Sampling Duration Tests
# ============================================================================
@pytest.mark.sampling_duration
class TestSamplingDuration:
"""Tests for sampling duration functionality."""
# Regex patterns from CMake _ompt_sampling_samp_regex and _ompt_sampling_file_regex
SAMPLING_PASS_REGEX = [
r"Sampler for thread 0 will be triggered 1000\.0x per second of CPU-time",
r"Sampler for thread 0 will be triggered 500\.0x per second of wall-time",
r"Sampling will be disabled after 0\.250000 seconds",
r"Sampling duration of 0\.250000 seconds has elapsed\. Shutting down sampling",
r"sampling_percent\.(json|txt)",
r"sampling_cpu_clock\.(json|txt)",
r"sampling_wall_clock\.(json|txt)",
]
def test_cg_sampling_duration(
self,
ompt_sampling_env: dict[str, str],
run_test,
assert_regex,
):
result = run_test(
"sampling",
target="openmp-cg",
env=ompt_sampling_env,
timeout=300,
no_check_target_arch=True,
)
assert_regex(result, pass_regex=self.SAMPLING_PASS_REGEX)
def test_lu_sampling_duration(
self,
run_test,
ompt_sampling_env: dict[str, str],
assert_regex,
):
"""Test OpenMP LU with sampling duration limits."""
result = run_test(
"sampling",
target="openmp-lu",
env=ompt_sampling_env,
timeout=300,
no_check_target_arch=True,
)
assert_regex(result, pass_regex=self.SAMPLING_PASS_REGEX)
# ============================================================================
# Test Class: No Temporary Files Tests
# ============================================================================
@pytest.mark.no_tmp_files
class TestNoTmpFiles:
"""Tests for operation without temporary files."""
NOTMP_SAMPLING_FILE_REGEX = [
r"sampling_percent\.(json|txt)",
r"sampling_cpu_clock\.(json|txt)",
r"sampling_wall_clock\.(json|txt)",
]
def test_cg_no_tmp_files(
self,
run_test,
ompt_no_tmp_env: dict[str, str],
assert_regex,
assert_perfetto,
assert_file_exists,
):
"""Test OpenMP CG without temporary files."""
result = run_test(
"sampling",
target="openmp-cg",
env=ompt_no_tmp_env,
timeout=300,
no_check_target_arch=True,
)
assert_regex(result, pass_regex=self.NOTMP_SAMPLING_FILE_REGEX)
assert_perfetto(result)
sampling_files = list(result.output_dir.glob("sampling_*.json")) + list(
result.output_dir.glob("sampling_*.txt")
)
assert_file_exists(sampling_files)
+187
Ver ficheiro
@@ -0,0 +1,187 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for RCCL
MPI is unsupported for RCCL tests.
"""
from __future__ import annotations
import pytest
pytestmark = [pytest.mark.rccl, pytest.mark.disable("all")]
# =============================================================================
# RCCL fixtures
# =============================================================================
@pytest.fixture
def rccl_env() -> dict[str, str]:
"""Environment variables for RCCL tests."""
return {
"ROCPROFSYS_TRACE_LEGACY": "OFF",
"ROCPROFSYS_TRACE_CACHED": "ON",
"ROCPROFSYS_PROFILE": "ON",
"ROCPROFSYS_USE_SAMPLING": "OFF",
"ROCPROFSYS_USE_PROCESS_SAMPLING": "ON",
"ROCPROFSYS_TIME_OUTPUT": "OFF",
"ROCPROFSYS_USE_PID": "OFF",
"ROCPROFSYS_USE_RCCLP": "ON",
"ROCPROFSYS_ROCM_DOMAINS": "hip_runtime_api,kernel_dispatch,memory_copy",
"OMP_PROC_BIND": "spread",
"OMP_PLACES": "threads",
"OMP_NUM_THREADS": "2",
}
# =============================================================================
# RCCL tests
# =============================================================================
# RCCL test binaries
RCCL_TARGETS = [
"all_reduce_perf",
"all_gather_perf",
"broadcast_perf",
"reduce_scatter_perf",
"reduce_perf",
"alltoall_perf",
"scatter_perf",
"gather_perf",
"sendrecv_perf",
"alltoallv_perf",
]
@pytest.mark.parametrize(
"rccl_target",
RCCL_TARGETS,
ids=[t.replace("_", "-") for t in RCCL_TARGETS],
)
@pytest.mark.gpu
class TestRCCL:
REWRITE_ARGS = [
"-e",
"-v",
"2",
"-i",
"8",
"--label",
"file",
"line",
"return",
"args",
]
RUNTIME_ARGS = [
"-e",
"-v",
"1",
"-i",
"8",
"--label",
"file",
"line",
"return",
"args",
"-ME",
"sysdeps",
"--log-file",
"rccl-test.log",
]
RUN_ARGS = [
"-t",
"1",
"-g",
"1",
"-i",
"10",
"-w",
"2",
"-m",
"2",
"-p",
"-c",
"1",
"-z",
"-s",
"1",
]
def test_sampling(
self,
rccl_target: str,
run_test,
rccl_env: dict[str, str],
assert_regex,
assert_perfetto,
):
result = run_test(
"sampling",
target=rccl_target,
env=rccl_env,
run_args=self.RUN_ARGS,
timeout=300,
)
assert_regex(result)
assert_perfetto(
result,
categories=["rocm_rccl_api"],
counter_names=["RCCL Comm"],
)
def test_binary_rewrite(
self,
rccl_target: str,
run_test,
rccl_env: dict[str, str],
assert_regex,
):
result = run_test(
"binary_rewrite",
target=rccl_target,
env=rccl_env,
run_args=self.RUN_ARGS,
rewrite_args=self.REWRITE_ARGS,
timeout=300,
)
assert_regex(result)
@pytest.mark.slow
def test_runtime_instrument(
self,
rccl_target: str,
run_test,
rccl_env: dict[str, str],
assert_regex,
):
result = run_test(
"runtime_instrument",
target=rccl_target,
env=rccl_env,
run_args=self.RUN_ARGS,
instrument_args=self.RUNTIME_ARGS,
timeout=300,
)
assert_regex(result)
def test_sys_run(
self,
rccl_target: str,
run_test,
rccl_env: dict[str, str],
assert_regex,
):
result = run_test(
"sys_run",
target=rccl_target,
env=rccl_env,
run_args=self.RUN_ARGS,
timeout=300,
)
assert_regex(result)
+164
Ver ficheiro
@@ -0,0 +1,164 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for the ROCTX marker API integration with rocprofiler-systems.
Equivalent to rocprof-sys-roctx-tests.cmake
"""
from __future__ import annotations
import pytest
from pathlib import Path
pytestmark = [pytest.mark.gpu, pytest.mark.roctx]
# =============================================================================
# rocTX fixtures
# =============================================================================
@pytest.fixture
def roctx_env() -> dict[str, str]:
"""Environment variables for rocTX tests."""
return {
"ROCPROFSYS_TRACE_LEGACY": "ON",
"ROCPROFSYS_ROCM_DOMAINS": "hip_runtime_api,marker_api,kernel_dispatch",
}
@pytest.fixture
def roctx_rules(validation_rules_dir: Path) -> list[Path]:
"""Get validation rules for rocTX tests."""
rules_dir = validation_rules_dir / "roctx"
return [
rules_dir / "validation-rules.json",
rules_dir / "amd-smi-rules.json",
rules_dir / "sdk-metrics-rules.json",
]
# ============================================================================
# Test Class: rocTX Tests
# ============================================================================
class TestRoctx:
"""Tests for rocTX marker API."""
def roctx_legacy_labels(self) -> list[str]:
return [
"roctxMark_GPU_workload",
"roctxRangePush_run_profiling",
"roctxRangeStart_GPU_Compute",
"roctxRangeStart_GPU_Compute",
"roctxRangePush_HIP_Kernel",
"roctxRangePush_HIP_Kernel",
"roctxGetThreadId",
"roctxMark_RoctxProfilerPause_End",
"roctxMark_Thread_Start",
"roctxMark_End",
"roctxMark_Finished_GPU",
]
def roctx_legacy_count(self) -> list[int]:
return [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
def roctx_legacy_depth(self) -> list[int]:
return [1, 1, 2, 0, 3, 1, 2, 2, 0, 0, 1]
def roctx_cached_labels(self) -> list[str]:
return [
"roctxMark_GPU_workload",
"roctxRangePush_HIP_Kernel",
"roctxRangeStart_GPU_Compute",
"roctxGetThreadId",
"roctxMark_RoctxProfilerPause_End",
"roctxMark_Thread_Start",
"roctxMark_End",
"roctxRangePush_run_profiling",
"roctxMark_Finished_GPU",
]
def roctx_cached_count(self) -> list[int]:
return [1, 2, 2, 1, 1, 1, 1, 1, 1]
def roctx_cached_depth(self) -> list[int]:
return [1, 1, 1, 1, 1, 2, 1, 1, 1]
REWRITE_ARGS = ["-e", "-v", "2", "--instrument-loops"]
def test_baseline(
self,
roctx_env: dict[str, str],
run_test,
assert_regex,
):
result = run_test("baseline", target="roctx", env=roctx_env, timeout=120)
assert_regex(result)
@pytest.mark.disable("assert_rocpd")
@pytest.mark.rocpd("roctx_env")
def test_sampling(
self,
run_test,
roctx_env: dict[str, str],
roctx_rules: list[Path],
assert_regex,
assert_perfetto,
assert_rocpd,
):
env = roctx_env.copy()
categories = ["rocm_marker_api"]
if env["ROCPROFSYS_TRACE_LEGACY"] == "ON":
labels = self.roctx_legacy_labels()
counts = self.roctx_legacy_count()
depths = self.roctx_legacy_depth()
else:
labels = self.roctx_cached_labels()
counts = self.roctx_cached_count()
depths = self.roctx_cached_depth()
result = run_test("sampling", target="roctx", env=env, timeout=120)
assert_regex(result)
assert_perfetto(
result,
subtest_name="Perfetto counter validation",
categories=categories,
labels=labels,
counts=counts,
depths=depths,
)
assert_rocpd(
result,
rules_files=roctx_rules,
)
def test_binary_rewrite(
self,
run_test,
roctx_env: dict[str, str],
assert_regex,
):
result = run_test(
"binary_rewrite",
target="roctx",
rewrite_args=self.REWRITE_ARGS,
env=roctx_env,
timeout=120,
)
assert_regex(result)
def test_sys_run(
self,
run_test,
roctx_env: dict[str, str],
assert_regex,
):
result = run_test(
"sys_run",
target="roctx",
env=roctx_env,
timeout=120,
)
assert_regex(result)
@@ -0,0 +1,205 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for the trace time window example.
Equivalent to rocprof-sys-time-window-tests.cmake
"""
from __future__ import annotations
import pytest
pytestmark = [pytest.mark.time_window]
# ============================================================================
# Time Window Fixtures
# ============================================================================
@pytest.fixture
def time_window_env() -> dict[str, str]:
"""Environment variables for time window tests."""
return {
"ROCPROFSYS_USE_SAMPLING": "OFF",
"ROCPROFSYS_USE_PROCESS_SAMPLING": "OFF",
"ROCPROFSYS_VERBOSE": "2",
}
# ============================================================================
# Test Class: Trace Time Window Tests
# ============================================================================
class TestTraceTimeWindow:
REWRITE_ARGS = ["-e", "-v", "2", "--caller-include", "inner", "-i", "4096"]
RUNTIME_ARGS = ["-e", "-v", "1", "--caller-include", "inner", "-i", "4096"]
def test_binary_rewrite(
self,
run_test,
time_window_env: dict[str, str],
assert_perfetto,
assert_timemory,
assert_regex,
):
env = time_window_env.copy()
env.update({"ROCPROFSYS_TRACE_DURATION": "1.25"})
result = run_test(
"binary_rewrite",
target="trace-time-window",
rewrite_args=self.REWRITE_ARGS,
env=env,
timeout=120,
)
assert_regex(result)
assert_timemory(
result,
file_name="wall_clock.json",
metric="wall_clock",
labels=["trace-time-window.inst", "outer_a", "outer_b", "outer_c"],
counts=[1, 1, 1, 1],
depths=[0, 1, 1, 1],
fail_regex=["outer_d"], # time window should exclude this
)
assert_perfetto(
result,
labels=["trace-time-window.inst", "outer_a", "outer_b", "outer_c"],
counts=[1, 1, 1, 1],
depths=[0, 1, 1, 1],
fail_regex=["outer_d"], # time window should exclude this
)
def test_runtime_instrument(
self,
run_test,
time_window_env: dict[str, str],
assert_regex,
assert_perfetto,
assert_timemory,
):
env = time_window_env.copy()
env.update({"ROCPROFSYS_TRACE_DURATION": "1.25"})
result = run_test(
"runtime_instrument",
target="trace-time-window",
instrument_args=self.RUNTIME_ARGS,
env=env,
timeout=400, # In xdist, it can take much longer
)
assert_regex(result)
assert_timemory(
result,
file_name="wall_clock.json",
metric="wall_clock",
labels=["trace-time-window", "outer_a", "outer_b", "outer_c"],
counts=[1, 1, 1, 1],
depths=[0, 1, 1, 1],
fail_regex=["outer_d"], # time window should exclude this
)
assert_perfetto(
result,
categories=["host"],
labels=["trace-time-window", "outer_a", "outer_b", "outer_c"],
counts=[1, 1, 1, 1],
depths=[0, 1, 1, 1],
fail_regex=["outer_d"], # time window should exclude this
)
# ============================================================================
# Test Class: Trace Time Window Delay Tests
# ============================================================================
class TestTraceTimeWindowDelay:
"""Tests for trace time window with delay."""
REWRITE_ARGS = ["-e", "-v", "2", "--caller-include", "inner", "-i", "4096"]
RUNTIME_ARGS = ["-e", "-v", "1", "--caller-include", "inner", "-i", "4096"]
def test_binary_rewrite(
self,
run_test,
time_window_env: dict[str, str],
assert_perfetto,
assert_timemory,
assert_regex,
):
env = time_window_env.copy()
env.update(
{
"ROCPROFSYS_TRACE_DELAY": "0.75",
"ROCPROFSYS_TRACE_DURATION": "0.75",
}
)
result = run_test(
"binary_rewrite",
target="trace-time-window",
rewrite_args=self.REWRITE_ARGS,
env=env,
timeout=120,
)
assert_regex(result)
assert_timemory(
result,
file_name="wall_clock.json",
metric="wall_clock",
labels=["outer_c", "outer_d"],
counts=[1, 1],
depths=[0, 0],
)
assert_perfetto(
result,
categories=["host"],
labels=["outer_c", "outer_d"],
counts=[1, 1],
depths=[0, 0],
)
def test_runtime_instrument(
self,
run_test,
time_window_env: dict[str, str],
assert_perfetto,
assert_timemory,
assert_regex,
):
"""Test trace time window delay with runtime instrumentation."""
env = time_window_env.copy()
env.update(
{
"ROCPROFSYS_TRACE_DELAY": "0.75",
"ROCPROFSYS_TRACE_DURATION": "0.75",
}
)
result = run_test(
"runtime_instrument",
target="trace-time-window",
instrument_args=self.RUNTIME_ARGS,
env=env,
)
assert_regex(result)
assert_timemory(
result,
file_name="wall_clock.json",
metric="wall_clock",
labels=["outer_c", "outer_d"],
counts=[1, 1],
depths=[0, 0],
)
assert_perfetto(
result,
categories=["host"],
labels=["outer_c", "outer_d"],
counts=[1, 1],
depths=[0, 0],
)
+407
Ver ficheiro
@@ -0,0 +1,407 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for the transpose example.
Equivalent to rocprof-sys-rocm-tests.cmake
Note: MPI is not yet supported
This module tests the transpose HIP example with various instrumentation modes:
- Baseline execution (no instrumentation)
- Sampling instrumentation
- Binary rewrite instrumentation
- Runtime instrumentation
- sys-run wrapper execution
It also validates outputs including:
- Perfetto traces
- ROCpd databases
- ROCProfiler counter data
"""
from __future__ import annotations
import pytest
from pathlib import Path
pytestmark = [pytest.mark.transpose, pytest.mark.gpu]
from rocprofsys import (
GPUInfo,
)
# =============================================================================
# Transpose fixtures
# =============================================================================
@pytest.fixture
def transpose_env() -> dict[str, str]:
"""Environment variables for transpose tests."""
return {
"ROCPROFSYS_ROCM_DOMAINS": "hip_runtime_api,kernel_dispatch,memory_copy,memory_allocation,hsa_api"
}
@pytest.fixture
def rocprofiler_env(transpose_env: dict[str, str], gpu_info: GPUInfo) -> dict[str, str]:
"""Environment with ROCm events configured."""
env = transpose_env.copy()
env["ROCPROFSYS_ROCM_EVENTS"] = gpu_info.rocm_events_for_test
return env
@pytest.fixture
def transpose_rules(validation_rules_dir: Path) -> list[Path]:
"""Get validation rules files for transpose tests."""
rules_dir = validation_rules_dir / "transpose"
return [
validation_rules_dir / "default-rules.json",
rules_dir / "validation-rules.json",
rules_dir / "amd-smi-rules.json",
rules_dir / "cpu-metrics-rules.json",
rules_dir / "timer-sampling-rules.json",
rules_dir / "sdk-metrics-rules.json",
]
# ============================================================================
# Test Class: Basic Transpose Tests
# ============================================================================
class TestTranspose:
"""Basic transpose tests with all instrumentation modes."""
REWRITE_ARGS = [
"-e",
"-v",
"2",
"--print-instructions",
"-E",
"uniform_int_distribution",
]
RUNTIME_ARGS = [
"-e",
"-v",
"1",
"--label",
"file",
"line",
"return",
"args",
"-E",
"uniform_int_distribution",
]
def test_baseline(
self,
run_test,
transpose_env: dict[str, str],
assert_regex,
):
result = run_test("baseline", target="transpose", env=transpose_env, timeout=120)
assert_regex(result)
@pytest.mark.rocpd("transpose_env")
def test_sampling(
self,
run_test,
transpose_env: dict[str, str],
transpose_rules: list[Path],
assert_rocpd,
assert_perfetto,
assert_regex,
):
result = run_test("sampling", target="transpose", env=transpose_env, timeout=120)
if not result.output_dir.exists():
pytest.fail(f"Output directory not created")
assert_regex(result)
assert_perfetto(
result,
subtest_name="Perfetto HIP API Call Validation",
categories=["hip_runtime_api"],
)
assert_rocpd(result, rules_files=transpose_rules)
def test_binary_rewrite(
self,
run_test,
transpose_env: dict[str, str],
assert_perfetto,
assert_regex,
):
result = run_test(
"binary_rewrite",
target="transpose",
rewrite_args=self.REWRITE_ARGS,
env=transpose_env,
timeout=120,
)
assert_regex(result)
assert_perfetto(result)
def test_runtime_instrument(
self,
run_test,
transpose_env: dict[str, str],
assert_perfetto,
assert_regex,
):
result = run_test(
"runtime_instrument",
target="transpose",
instrument_args=self.RUNTIME_ARGS,
env=transpose_env,
timeout=480,
)
assert_regex(result)
assert_perfetto(result)
def test_sys_run(
self,
run_test,
transpose_env: dict[str, str],
assert_regex,
):
result = run_test(
"sys_run",
target="transpose",
env=transpose_env,
timeout=300,
)
assert_regex(result)
# ============================================================================
# Test Class: Two Kernels Configuration
# ============================================================================
class TestTransposeTwoKernels:
"""Test transpose with two kernels configuration (1 iteration, 2x2 size)."""
RUN_ARGS = ["1", "2", "2"]
def test_sampling(
self,
run_test,
transpose_env: dict[str, str],
assert_regex,
):
result = run_test(
"sampling",
target="transpose",
run_args=self.RUN_ARGS,
env=transpose_env,
timeout=120,
)
assert_regex(result)
def test_sys_run(
self,
run_test,
transpose_env: dict[str, str],
assert_regex,
):
result = run_test(
"sys_run",
target="transpose",
run_args=self.RUN_ARGS,
env=transpose_env,
timeout=300,
)
assert_regex(result)
# ============================================================================
# Test Class: Loop Instrumentation
# ============================================================================
@pytest.mark.loops
class TestTransposeLoops:
"""Test transpose with loop instrumentation."""
REWRITE_ARGS = [
"-e",
"-v",
"2",
"--label",
"return",
"args",
"-l",
"-i",
"8",
"-E",
"uniform_int_distribution",
]
RUN_ARGS = ["2", "100", "50"]
def test_sampling(
self,
run_test,
transpose_env: dict[str, str],
assert_regex,
):
result = run_test(
"sampling",
target="transpose",
run_args=self.RUN_ARGS,
env=transpose_env,
timeout=120,
)
assert_regex(result)
def test_binary_rewrite(
self,
run_test,
transpose_env: dict[str, str],
assert_regex,
):
result = run_test(
"binary_rewrite",
target="transpose",
rewrite_args=self.REWRITE_ARGS,
run_args=self.RUN_ARGS,
env=transpose_env,
timeout=120,
)
assert_regex(result, fail_regex=["0 instrumented loops in procedure transpose"])
# ============================================================================
# Test Class: ROCProfiler Counter Collection
# ============================================================================
@pytest.mark.rocprofiler
class TestTransposeROCProfiler:
"""Test transpose with ROCProfiler counter collection."""
REWRITE_ARGS = [
"-e",
"-v",
"2",
"-E",
"uniform_int_distribution",
]
def test_sampling(
self,
run_test,
rocprofiler_env: dict[str, str],
gpu_info: GPUInfo,
assert_perfetto,
assert_regex,
assert_file_exists,
):
result = run_test(
"sampling",
target="transpose",
env=rocprofiler_env,
timeout=120,
)
assert_regex(result)
counter_files = [result.output_dir / f for f in gpu_info.expected_counter_files]
assert_file_exists(
counter_files, subtest_name="ROCProfiler counter files existence validation"
)
assert_perfetto(
result,
subtest_name="Perfetto counter validation",
counter_names=gpu_info.counter_names,
)
def test_binary_rewrite(
self,
run_test,
rocprofiler_env: dict[str, str],
gpu_info: GPUInfo,
assert_file_exists,
assert_regex,
):
result = run_test(
"binary_rewrite",
target="transpose",
rewrite_args=self.REWRITE_ARGS,
env=rocprofiler_env,
timeout=120,
)
assert_regex(result)
counter_files = [result.output_dir / f for f in gpu_info.expected_counter_files]
assert_file_exists(
counter_files, subtest_name="ROCProfiler counter files existence validation"
)
# ============================================================================
# Parametrized Tests
# ============================================================================
class TestTransposeParametrized:
"""Parametrized tests for various transpose configurations."""
@pytest.mark.parametrize(
"iterations,tile_dim,block_rows",
[
(1, 16, 16),
(2, 32, 32),
(5, 64, 64),
],
ids=["small", "medium", "large"],
)
def test_transpose_configurations(
self,
run_test,
transpose_env: dict[str, str],
iterations: int,
tile_dim: int,
block_rows: int,
assert_regex,
):
"""Test transpose with different iteration and tile configurations."""
result = run_test(
"sampling",
target="transpose",
run_args=[str(iterations), str(tile_dim), str(block_rows)],
env=transpose_env,
timeout=120,
fail_message=f"Config ({iterations}, {tile_dim}, {block_rows}) failed",
)
assert_regex(result)
@pytest.mark.parametrize(
"runner_type,runner_kwargs",
[
("sampling", {}),
("sys_run", {}),
],
ids=["sampling", "sys-run"],
)
def test_instrumentation_modes(
self,
run_test,
transpose_env: dict[str, str],
runner_type: str,
runner_kwargs: dict,
assert_regex,
):
"""Test different instrumentation modes produce valid output."""
result = run_test(
runner_type,
target="transpose",
env=transpose_env,
timeout=120,
**runner_kwargs,
)
if not result.output_dir.exists():
pytest.fail(f"Output directory not created")
assert_regex(result)
@@ -0,0 +1,116 @@
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Tests for the videodecode example.
"""
from __future__ import annotations
import pytest
pytestmark = [pytest.mark.gpu, pytest.mark.decode, pytest.mark.videodecode]
from rocprofsys import (
GPUInfo,
RocprofsysConfig,
)
from pathlib import Path
# =============================================================================
# Video decode fixtures
# =============================================================================
@pytest.fixture
def video_decode_env() -> dict[str, str]:
"""Environment variables for video decode tests."""
return {
"ROCPROFSYS_ROCM_DOMAINS": "hip_runtime_api,kernel_dispatch,memory_copy,rocdecode_api",
"ROCPROFSYS_AMD_SMI_METRICS": "busy,temp,power,vcn_activity,mem_usage",
"ROCPROFSYS_SAMPLING_CPUS": "none",
}
@pytest.fixture
def video_decode_rules(validation_rules_dir: Path) -> list[Path]:
"""Get validation rules for video decode tests."""
rules_dir = validation_rules_dir / "video-decode"
return [
rules_dir / "validation-rules.json",
rules_dir / "sdk-metrics-rules.json",
]
# =============================================================================
# Video decode tests
# =============================================================================
class TestVideoDecode:
"""Tests for the videodecode example."""
@pytest.mark.rocpd("video_decode_env")
def test_sampling(
self,
run_test,
rocprof_config: RocprofsysConfig,
video_decode_env: dict[str, str],
gpu_info: GPUInfo,
video_decode_rules: list[Path],
assert_rocpd,
assert_perfetto,
assert_regex,
):
env = video_decode_env.copy()
if "instinct" in gpu_info.categories:
rules_dir = rocprof_config.rocpd_validation_rules / "video-decode"
video_decode_rules.append(rules_dir / "amd-smi-rules.json")
result = run_test(
"sampling",
target="videodecode",
env=env,
timeout=120,
run_args=[
"-i",
str(rocprof_config.rocprofsys_examples_dir / "videos"),
"-t",
"1",
],
no_check_target_arch=True,
)
assert_regex(result)
assert_perfetto(
result,
categories=["rocm_rocdecode_api"],
labels=["rocDecCreateVideoParser"],
counts=[2],
depths=[1],
counter_names=["VCN Activity"] if "instinct" in gpu_info.categories else None,
)
assert_rocpd(result, rules_files=video_decode_rules)
def test_sys_run(
self,
run_test,
rocprof_config: RocprofsysConfig,
video_decode_env: dict[str, str],
assert_regex,
):
result = run_test(
"sys_run",
target="videodecode",
env=video_decode_env,
timeout=120,
run_args=[
"-i",
str(rocprof_config.rocprofsys_examples_dir / "videos"),
"-t",
"1",
],
no_check_target_arch=True,
)
assert_regex(result)