[rocprofiler-compute] Use native tool for counter collection (#1212)

* Use native tool for counter collection

* Add native counter collection tool which uses rocprofiler-sdk C++
  library public API to get counter collection data
    * This is enabled by default, unless --no-native-tool option is
      provided or ROCPROF=rocprofv3 env. var. is provided
    * This tool is only supported for ROCm version >=7.x.x
    * This tool is not supported for attach/detach scenario
* Build native tool shared object during build time
* If using rocprof-compute without building then runtime compilation of
  t push native tool shared object is performed
* rocprofiler-sdk tools is still used for services other than counter
  collection and data collected by native tool is merged into the
  rocpd/csv output of rocprofiler-sdk tool

* Make `rocpd` choice the default choice for `--format-rocprof-output`
  option
    * If `rocpd` public API from rocprofiler-sdk library is not present,
      then fallback to `csv` choice
    * In this case only `pmc_perf.csv` is written in workload folder
      instead of multiple `csv` files for each profiling run
* Remove `json` choice from `--format-rocprof-output` option since it
  functions identical to `csv` option

* Rename option `--rocprofiler-sdk-library-path` to
  `--rocprofiler-sdk-tool-path` since we LD_PRELOAD the
  rocprofiler-sdk tool shared object and not the rocprofiler-sdk library
shared object

* Fix the meaning of `--dispatch` option in `profile` mode to mention
  dispatch iteration filtering instead of dispatch id filtering
    * --dispatch option in analyze mode does dispatch id filtering

* Move standalone binary creation logic from cmake file to docker file

* fix native counter collection tool during attach/detach

* improve logging

* fix attach detach with native tool

* fix attach detach with native tool

* do not support attach/detach in native tool

* Update changelog

* add standalone binary creation functionality in cmake

* address review comments

* address review comments

* fix formatting

* address review comments

* Adding paths for cmake to search. Also updated min. cmake requirement to 3.21 as this was when hip was supported.

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* Update hip compiler ID check, sometimes comes up as Clang, sometimes ROCMClang- depends on setup.
Updated formatting.

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* RHEL8.10 unable to compile due to defaulting to old c++ version, need to force c++17

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* Updating changelog per docs team recommendations

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* Apply suggestions from code review to changelog

Co-authored-by: Pratik Basyal <pratik.basyal@amd.com>

* Do not required HIP complier to build native counter collection tool

* fix cmake

* gersemi formatting on latest cmake change

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* ex ci updated dependencies to include rocprofiler-sdk, but cmake was still not capturing the path- there was a commit that added to the cmake_prefix_path entry that specified rocprof-sdk's cmake location ut was too specific for the search paths in find_package's config mode.
removing the cmake_prefix_path var and adding hints to find_package call instead, and specifying config mode so it knows how to construct the search paths

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* gersemi run for formatting

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* Still need prefix path, should not have been removed in last commit but does need to be shortened to just the rocm path to allow for find_package config mode to do the job

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* include cstdint for uint32_t

* Run formatting on helper.cpp

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* Remove rocm 7.2 release stuff from version and changelog and handle it in separate pr

* fix version

* fix changelog

* fix changelog

* run ruff formatter

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>

* fix rocprofiler-sdk attach so path

---------

Signed-off-by: Carrie Fallows <Carrie.Fallows@amd.com>
Co-authored-by: Carrie Fallows <Carrie.Fallows@amd.com>
Co-authored-by: Pratik Basyal <pratik.basyal@amd.com>
This commit is contained in:
vedithal-amd
2025-11-18 23:34:38 -05:00
committed by GitHub
parent 8418895b92
commit ae8f72fa79
25 changed files with 1599 additions and 573 deletions
@@ -80,6 +80,33 @@ jobs:
exit 1
fi
cxx:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: projects/rocprofiler-compute
- name: Install dependencies
working-directory: projects/rocprofiler-compute
run: |
sudo apt-get update
sudo apt-get install -y python3-pip
python3 -m pip install clang-format
- name: clang-format
working-directory: projects/rocprofiler-compute
run: |
set +e
clang-format -i $(find src -type f | egrep '\.(h|hpp|hh|c|cc|cpp)(|\.in)$')
if [ $(git diff | wc -l) -gt 0 ]; then
echo -e "\nError! cxx code not formatted. Run clang-format...\n"
echo -e "\nFiles:\n"
git diff --name-only
echo -e "\nFull diff:\n"
git diff
exit 1
fi
python-bytecode:
runs-on: ubuntu-22.04
+24
View File
@@ -4,6 +4,30 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs.
## Unreleased
### Added
* Native tool to perform counter collection using ROCprofiler-SDK public API. It is only supported for ROCm version 7.0.0 (and later).
* Native tool is now the default for counter collection.
* Native tool for counter collection will not be used under the following conditions:
* Specific profiler is provided through the ``ROCPROF`` environment variable.
* ``--no-native-tool`` option is provided, forcing usage of the default profiler.
* When performing a dynamic attach to a process for profiling.
### Changed
* Default output format for the underlying ROCprofiler-SDK tool has been changed from ``csv`` to ``rocpd``.
* If the ROCprofiler-SDK ``rocpd`` public library is not available, will fall back to ``csv`` format
* Option ``--rocprofiler-sdk-library-path`` has been changed to ``--rocprofiler-tool-library-path`` to better reflect the fact that we provide flexibility in choosing the path to ROCprofiler-SDK tool and not the library.
### Resolved issues
* Fixed the meaning of --dispatch option in profile mode in argparser to convey the fact that it control which iterations of the kernel to profile and not which dispatch ids to profile.
* The meaning of --dispatch option in analyze is still the same which is which dispatch ids to analyze
* Fix the functioning of --dispatch option to act as 1-based index and ensure that correct kernel iterations are being profiled
## ROCm Compute Profiler 3.4.0 for ROCm 7.2.0
### Added
+26 -7
View File
@@ -1,4 +1,8 @@
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
# Set cmake_prefix_path for searching, ROCM_PATH if avail otherwise default to general rocm install path
set(CMAKE_PREFIX_PATH $ENV{ROCM_PATH} "/opt/rocm/")
message(STATUS "ROCM_PATH: $ENV{ROCM_PATH}")
if(
CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR
@@ -36,7 +40,7 @@ string(
project(
rocprofiler-compute
VERSION ${ROCPROFCOMPUTE_VERSION}
LANGUAGES C
LANGUAGES CXX
DESCRIPTION
"A kernel-level profiling tool for machine learning/HPC workloads running on AMD MI GPUs"
HOMEPAGE_URL
@@ -471,6 +475,11 @@ if(${ENABLE_COVERAGE})
)
endif()
# -------------------
# Setup tool library
# -------------------
add_subdirectory(src/lib)
# ---------
# Install
# ---------
@@ -563,6 +572,12 @@ install(
COMPONENT main
)
#install librocprofiler-compute-tool.so
install(
TARGETS rocprofiler-compute-tool
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rocprofiler-compute COMPONENT main
)
# top-level symlink for bin/rocprof-compute
install(
CODE
@@ -590,21 +605,25 @@ add_custom_target(
add_custom_target(
standalonebinary
# Change working directory to src
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
# Install nuitka
COMMAND ${Python3_EXECUTABLE} -m pip install nuitka
# Install patchelf
COMMAND ${Python3_EXECUTABLE} -m pip install patchelf
# Check nuitka
COMMAND ${Python3_EXECUTABLE} -m pip list | grep -i nuitka > /dev/null 2>&1
# Check patchelf
COMMAND ${Python3_EXECUTABLE} -m pip list | grep -i patchelf > /dev/null 2>&1
# Create VERSION.sha file
COMMAND git -C ${PROJECT_SOURCE_DIR} rev-parse HEAD > VERSION.sha
COMMAND git rev-parse HEAD > VERSION.sha
# Build standalone binary
# NOTE: --no-deployment-flag=self-execution is used to avoid self-execution
# and fork
# bombs as explained in
# and fork bombs as explained in
# https://nuitka.net/user-documentation/common-issue-solutions.html#fork-bombs-self-execution
COMMAND
${Python3_EXECUTABLE} -m nuitka --mode=onefile --no-deployment-flag=self-execution
--include-data-files=${PROJECT_SOURCE_DIR}/VERSION*=./ --enable-plugin=no-qt
--include-data-files=src/lib/rocprofiler_compute_tool.cpp=lib/rocprofiler_compute_tool.cpp
--include-package=dash_svg --include-package-data=dash_svg
--include-package=dash_bootstrap_components
--include-package-data=dash_bootstrap_components --include-package=plotly
@@ -615,7 +634,7 @@ add_custom_target(
--include-package-data=rocprof_compute_profile
--include-package=rocprof_compute_tui --include-package-data=rocprof_compute_tui
--include-package=rocprof_compute_soc --include-package-data=rocprof_compute_soc
--include-package=utils --include-package-data=utils rocprof-compute
--include-package=utils --include-package-data=utils src/rocprof-compute
# Remove library rpath from executable
COMMAND patchelf --remove-rpath rocprof-compute.bin
# Move to build directory
@@ -1,8 +1,8 @@
FROM redhat/ubi8:8.10
WORKDIR /app
WORKDIR /app/projects/rocprofiler-compute
RUN yum install -y curl gcc cmake git
RUN yum install -y curl git cmake gcc-c++
# Allows running git commands in /app
RUN git config --global --add safe.directory /app
@@ -14,10 +14,25 @@ RUN yum install -y python39 python39-devel && \
python3 get-pip.py
CMD ["/bin/bash", "-c", "\
cd /app/projects/rocprofiler-compute \
&& python3 -m pip install -r requirements.txt \
python3 -m pip install -r requirements.txt \
&& python3 -m pip install nuitka patchelf \
&& rm -rf build \
&& cmake -B build -S . \
&& make -C build standalonebinary \
&& git rev-parse HEAD > VERSION.sha \
&& python3 -m nuitka --mode=onefile --no-deployment-flag=self-execution \
--enable-plugin=no-qt \
--include-data-files=VERSION*=./ \
--include-data-files=src/lib/rocprofiler_compute_tool.cpp=lib/rocprofiler_compute_tool.cpp \
--include-package=dash_svg --include-package-data=dash_svg \
--include-package=dash_bootstrap_components \
--include-package-data=dash_bootstrap_components \
--include-package=plotly --include-package-data=plotly \
--include-package=kaleido --include-package-data=kaleido \
--include-package=rocprof_compute_analyze \
--include-package-data=rocprof_compute_analyze \
--include-package=rocprof_compute_profile \
--include-package-data=rocprof_compute_profile \
--include-package=rocprof_compute_tui --include-package-data=rocprof_compute_tui \
--include-package=rocprof_compute_soc --include-package-data=rocprof_compute_soc \
--include-package=utils --include-package-data=utils \
src/rocprof-compute \
&& patchelf --remove-rpath rocprof-compute.bin \
"]
@@ -25,7 +25,7 @@
Some common filters include:
- `-k`/`--kernel` enables filtering kernels by name. `-d`/`--dispatch` enables filtering based on dispatch ID
- `-k`/`--kernel` enables filtering kernels by name. `-d`/`--dispatch` enables filtering based on dispatch iteration
- `-b`/`--ipblocks` enables collects metrics for only the specified (one or more) IP Blocks.
To view available metrics by IP Block you can use the `--list-metrics` argument to view a list of all available metrics organized by IP Block.
@@ -90,7 +90,7 @@ Profile Options:
SPI
CPC
CPF
-d [ ...], --dispatch [ ...] Dispatch ID filtering.
-d [ ...], --dispatch [ ...] Dispatch iteration filtering.
--no-roof Profile without collecting roofline data.
-- [ ...] Provide command for profiling after double dash.
@@ -30,7 +30,7 @@
Some common filters include:
- `-k`/`--kernel` enables filtering kernels by name.
- `-d`/`--dispatch` enables filtering based on dispatch ID.
- `-d`/`--dispatch` enables filtering based on dispatch iteration.
- `-b`/`--block` enables collects metrics for only the specified (one or more) hardware component blocks.
To view available metrics by hardware Block you can use the `--list-metrics` argument:
@@ -191,7 +191,7 @@ Filtering Options:
- The `-k` / `--kernel` \<kernel-substr> flag allows for kernel filtering. Usage is equivalent with the current rocProf utility ([see details below](#kernel-filtering)).
- The `-d` / `--dispatch` \<dispatch-id> flag allows for dispatch ID filtering. Usage is equivalent with the current rocProf utility ([see details below](#dispatch-filtering)).
- The `-d` / `--dispatch` \<dispatch-id> flag allows for dispatch iteration filtering. Usage is equivalent with the current rocProf utility ([see details below](#dispatch-filtering)).
- The `-b` / `--block` \<block-name> flag allows system profiling on one or more selected hardware components to speed up the profiling process ([see details below](#hardware-component-filtering)).
@@ -271,7 +271,7 @@ Filtering options
utility. See :ref:`profiling-kernel-filtering`.
``-d``, ``--dispatch <dispatch-id>``
Allows for dispatch ID filtering. Usage is equivalent with the current
Allows for dispatch iteration filtering. Usage is equivalent with the current
``rocprof`` utility. See :ref:`profiling-dispatch-filtering`.
``--set <metric-set>``
@@ -54,7 +54,7 @@ Common filters to customize data collection include:
Enables filtering kernels by name.
``-d``, ``--dispatch``
Enables filtering based on dispatch ID.
Enables filtering based on dispatch iteration.
``-b``, ``--block``
Enables collection metrics for only the specified analysis report blocks.
+31 -11
View File
@@ -178,7 +178,10 @@ Examples:
metavar="",
default=None,
required=False,
help="\t\t\tProcess id to be attached for profiling.",
help=(
"\t\t\tProcess id to be attached for profiling.\n"
"\t\t\tImplies --no-native-tool"
),
)
profile_group.add_argument(
"--attach-duration-msec",
@@ -188,9 +191,9 @@ Examples:
default=None,
required=False,
help=(
"\t\t\tWhen --attach-pid is used, it specifies the attach duration "
"in milliseconds. If not set, detachment occurs when "
'"Enter" key is pressed.'
"\t\t\tWhen --attach-pid is used, it specifies the attach duration\n"
"\t\t\tin milliseconds. If not set, detachment occurs when\n"
'\t\t\t"Enter" key is pressed.'
),
)
profile_group.add_argument(
@@ -255,7 +258,10 @@ Examples:
nargs="+",
dest="dispatch",
required=False,
help="\t\t\tDispatch ID filtering.",
help=(
"\t\t\tWhich dispatch iterations of the kernel to filter \n"
"\t\t\t(e.g. 1 3:5 captures 1st, 3rd, 4th and 5th iterations)."
),
)
profile_group.add_argument(
@@ -342,8 +348,8 @@ Examples:
metavar="",
dest="format_rocprof_output",
choices=["csv", "rocpd"],
default="csv",
help="\t\t\tSet the format of output file of rocprof.",
default="rocpd",
help=("\t\t\tSet the format of output file of rocprof."),
)
profile_group.add_argument(
"--pc-sampling-method",
@@ -370,14 +376,28 @@ Examples:
),
)
profile_group.add_argument(
"--rocprofiler-sdk-library-path",
"--rocprofiler-sdk-tool-path",
type=str,
dest="rocprofiler_sdk_library_path",
dest="rocprofiler_sdk_tool_path",
required=False,
default=str(
Path(os.getenv("ROCM_PATH", "/opt/rocm")) / "lib/librocprofiler-sdk.so"
Path(os.getenv("ROCM_PATH", "/opt/rocm"))
/ "lib/rocprofiler-sdk/librocprofiler-sdk-tool.so"
),
help="\t\t\tSet the path to rocprofiler-sdk tool.",
)
profile_group.add_argument(
"--no-native-tool",
required=False,
default=False,
action="store_true",
help=(
"\t\t\tDo not use the native counter collection tool.\n"
"\t\t\tNative tool is not used if ROCPROF env. var. is set "
"and not equal to rocprofiler-sdk.\n"
"\t\t\tNative tool is not used for ROCm version < 7.x.x.\n"
"\t\t\tNative tool is not used attach/detach scenario"
),
help="\t\t\tSet the path to rocprofiler SDK library.",
)
profile_group.add_argument(
"--retain-rocpd-output",
@@ -0,0 +1,17 @@
# Minimum required c++ standard is 17 for compilation
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set cmake_prefix_path for searching, ROCM_PATH if avail otherwise default to general rocm install path
set(CMAKE_PREFIX_PATH $ENV{ROCM_PATH} "/opt/rocm/")
find_package(
rocprofiler-sdk
HINTS $ENV{ROCM_PATH}/lib/cmake /opt/rocm/lib/cmake
CONFIG
REQUIRED
)
add_library(rocprofiler-compute-tool SHARED)
target_sources(rocprofiler-compute-tool PRIVATE rocprofiler_compute_tool.cpp helper.cpp)
target_link_libraries(rocprofiler-compute-tool PRIVATE rocprofiler-sdk::rocprofiler-sdk)
@@ -0,0 +1,145 @@
// 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.
#include "helper.hpp"
#include <cstdint>
#include <cxxabi.h>
#include <iostream>
#include <string>
namespace helper_utils {
// The function extracts the kernel name from
// input string. By using the iterators it finds the
// window in the string which contains only the kernel name.
// For example 'Foo<int, float>::foo(a[], int (int))' -> 'foo'
std::string truncate_name(std::string_view name) {
auto rit = name.rbegin();
auto rend = name.rend();
uint32_t counter = 0;
char open_token = 0;
char close_token = 0;
while (rit != rend) {
if (counter == 0) {
switch (*rit) {
case ')':
counter = 1;
open_token = ')';
close_token = '(';
break;
case '>':
counter = 1;
open_token = '>';
close_token = '<';
break;
case ']':
counter = 1;
open_token = ']';
close_token = '[';
break;
case ' ':
++rit;
continue;
}
if (counter == 0)
break;
} else {
if (*rit == open_token)
counter++;
if (*rit == close_token)
counter--;
}
++rit;
}
auto rbeg = rit;
while ((rit != rend) && (*rit != ' ') && (*rit != ':'))
rit++;
return std::string{name.substr(rend - rit, rit - rbeg)};
}
std::string cxa_demangle(std::string_view _mangled_name, int *_status) {
// return the mangled since there is no buffer
if (_mangled_name.empty()) {
*_status = -2;
return std::string{};
}
auto _demangled_name = std::string{_mangled_name};
// PARAMETERS to __cxa_demangle
// mangled_name:
// A NULL-terminated character string containing the name to be
// demangled.
// buffer:
// A region of memory, allocated with malloc, of *length bytes, into
// which the demangled name is stored. If output_buffer is not long
// enough, it is expanded using realloc. output_buffer may instead be
// NULL; in that case, the demangled name is placed in a region of memory
// allocated with malloc.
// _buflen:
// If length is non-NULL, the length of the buffer containing the
// demangled name is placed in *length.
// status:
// *status is set to one of the following values
size_t _demang_len = 0;
char *_demang = abi::__cxa_demangle(_demangled_name.c_str(), nullptr,
&_demang_len, _status);
switch (*_status) {
// 0 : The demangling operation succeeded.
// -1 : A memory allocation failure occurred.
// -2 : mangled_name is not a valid name under the C++ ABI mangling rules.
// -3 : One of the arguments is invalid.
case 0: {
if (_demang)
_demangled_name = std::string{_demang};
break;
}
case -1: {
std::clog << "[rocprofiler-compute] memory allocation failure occurred "
"demangling "
<< _demangled_name << std::endl;
break;
}
case -2: {
break;
}
case -3: {
std::clog << "[rocprofiler-compute] Invalid argument in: (\""
<< _demangled_name << "\", nullptr, nullptr, "
<< static_cast<void *>(_status) << ")" << std::endl;
break;
}
default:
break;
};
// if it "demangled" but the length is zero, set the status to -2
if (_demang_len == 0 && *_status == 0)
*_status = -2;
// free allocated buffer
::free(_demang);
return _demangled_name;
}
} // namespace helper_utils
@@ -0,0 +1,31 @@
// 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.
#pragma once
#include <string>
namespace helper_utils {
std::string truncate_name(std::string_view name);
std::string cxa_demangle(std::string_view _mangled_name, int *_status);
} // namespace helper_utils
@@ -0,0 +1,613 @@
// 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.
/*
This is a native tool for rocprofiler-compute to collect counters data for GPU
kernel dispatches using the rocprofiler-sdk public API. This C++ tool is
compiled into a shared object with hipcc/amdclang++ and dynamically links to the
rocprofiler-sdk library. The shared object is injected using the LD_PRELOAD
environment variable so that rocprofiler-sdk services can be configured before
the GPU workload starts executing.
An experimental feature for attach/detach scenarios is also provided.
Code Flow:
1. Entry point - rocprofiler_configure():
- Parses ROCPROF environment variables to configure profiling.
- Sets up tool metadata and logging.
- Returns pointers to tool_init() and tool_fini() functions.
2. Tool Initialization - tool_init():
- Creates a profiling context.
- Subscribes to dispatch tracing and counting services by providing function
callbacks.
- Starts the profiling context.
3. Kernel registration callback - tool_tracing_callback():
- Invoked when a kernel is registered.
- Stores the kernel name to kernel id mapping.
- Determines which kernel names/ids to target for profiling based on ROCPROF
environment variables.
4. Kernel dispatch callback - dispatch_callback():
- Invoked before a kernel dispatch is enqueued.
- Decides whether to profile this dispatch.
- If profiling is required, creates or fetches from cache a counter profile
for the agent and returns a pointer to it.
- The counter profile dictates which counters to collect for this dispatch.
5. Kernel dispatch record callback - record_callback():
- Invoked after a kernel dispatch is completed.
- Receives the collected counter records.
- Stores the counter records in tool data for later processing.
6. Tool Finalization - tool_fini():
- Called when the application is terminating.
- Stops the profiling context.
- Processes and writes the collected counter records to the output file.
- Cleans up resources.
*/
#include "helper.hpp"
#include <rocprofiler-sdk/registration.h>
#include <rocprofiler-sdk/rocprofiler.h>
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <random>
#include <regex>
#include <set>
#include <shared_mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#define ROCPROFILER_CALL(result, msg) \
{ \
rocprofiler_status_t CHECKSTATUS = result; \
if (CHECKSTATUS != ROCPROFILER_STATUS_SUCCESS) { \
std::string status_msg = rocprofiler_get_status_string(CHECKSTATUS); \
std::cerr << "[" #result "][" << __FILE__ << ":" << __LINE__ << "] " \
<< msg << " failed with error code " << CHECKSTATUS << ": " \
<< status_msg << std::endl; \
std::stringstream errmsg{}; \
errmsg << "[" #result "][" << __FILE__ << ":" << __LINE__ << "] " \
<< msg " failure (" << status_msg << ")"; \
throw std::runtime_error(errmsg.str()); \
} \
}
namespace {
// Struct to store a single counter info record
struct counter_info_record_t {
uint64_t dispatch_id;
uint64_t kernel_id;
uint64_t counter_id;
std::string counter_name;
double counter_value;
};
// Tool data struct, now includes a vector of counter_info_record_t
struct tool_data_t {
std::mutex mut{};
std::unique_ptr<std::ostream> output_stream{nullptr};
std::unordered_map<uint64_t, std::string> counter_id_name_map{};
std::string requested_counters{};
std::string kernel_filter_include_regex{};
std::vector<std::pair<uint64_t, uint64_t>> kernel_filter_ranges{};
std::vector<counter_info_record_t> counter_records;
std::set<uint64_t> target_kernel_ids{};
};
using kernel_symbol_data_t =
rocprofiler_callback_tracing_code_object_kernel_symbol_register_data_t;
rocprofiler_context_id_t &get_client_ctx() {
static rocprofiler_context_id_t ctx{0};
return ctx;
}
void record_callback(rocprofiler_dispatch_counting_service_data_t dispatch_data,
rocprofiler_counter_record_t *record_data,
size_t record_count,
rocprofiler_user_data_t /* user_data */,
void *callback_data_args) {
auto *tool_data_ptr =
static_cast<std::unique_ptr<tool_data_t> *>(callback_data_args);
tool_data_t *tool;
{
std::lock_guard<std::mutex> lock(tool_data_ptr->get()->mut);
tool = tool_data_ptr->get();
}
// For each counter, write: dispatch_id, counter_id, counter_name,
// counter_value
for (size_t i = 0; i < record_count; ++i) {
rocprofiler_counter_id_t counter_id{};
ROCPROFILER_CALL(
rocprofiler_query_record_counter_id(record_data[i].id, &counter_id),
"query record counter id");
// Store the counter info record in tool_data
counter_info_record_t record{dispatch_data.dispatch_info.dispatch_id,
dispatch_data.dispatch_info.kernel_id,
counter_id.handle,
tool->counter_id_name_map[counter_id.handle],
record_data[i].counter_value};
{
std::lock_guard<std::mutex> lock(tool->mut);
tool->counter_records.push_back(std::move(record));
}
}
}
/**
* Callback from rocprofiler when a code object is loaded.
* We use this to get record kernel names as they are registered.
*/
void tool_tracing_callback(rocprofiler_callback_tracing_record_t record,
rocprofiler_user_data_t * /*user_data*/,
void *callback_data) {
if (record.phase == ROCPROFILER_CALLBACK_PHASE_LOAD &&
record.kind == ROCPROFILER_CALLBACK_TRACING_CODE_OBJECT &&
record.operation ==
ROCPROFILER_CODE_OBJECT_DEVICE_KERNEL_SYMBOL_REGISTER) {
auto *data = static_cast<kernel_symbol_data_t *>(record.payload);
int demangle_status = 0;
auto kernel_name =
helper_utils::cxa_demangle(data->kernel_name, &demangle_status);
kernel_name = helper_utils::truncate_name(kernel_name);
// check if regex can be found in kernel name matches regex from tool data,
// if matches store kernel id
auto *tool_data_ptr =
static_cast<std::unique_ptr<tool_data_t> *>(callback_data);
auto *tool = tool_data_ptr->get();
// Lock before modifying target_kernel_ids
std::lock_guard<std::mutex> lock(tool->mut);
if (!tool->kernel_filter_include_regex.empty()) {
try {
std::regex re(tool->kernel_filter_include_regex);
if (!kernel_name.empty() && std::regex_search(kernel_name, re)) {
tool->target_kernel_ids.insert(data->kernel_id);
}
} catch (const std::regex_error &e) {
std::cerr
<< "[rocprofiler-compute] [" << __FUNCTION__
<< "] ERROR: Invalid regex in ROCPROF_KERNEL_FILTER_INCLUDE_REGEX: "
<< tool->kernel_filter_include_regex << " : " << e.what()
<< std::endl;
}
}
// If no regex specified, collect for all kernels
else {
tool->target_kernel_ids.insert(data->kernel_id);
}
}
}
/**
* Checks if the given kernel dispatch should be targeted for profiling.
* Returns true if the kernel_id is in the set of target_kernel_ids (if
* non-empty), and if the kernel_iteration (1-based index) matches the
* kernel_filter_range (if specified).
*
* @param tool Pointer to the tool_data_t structure containing profiling
* configuration.
* @param kernel_id The kernel ID of the dispatch.
* @param kernel_iteration The 1-based index of this kernel_id's dispatch (first
* dispatch is 1).
* @return true if the dispatch should be profiled, false otherwise.
*/
bool is_targetted_dispatch(const tool_data_t *tool, uint64_t kernel_id,
uint64_t kernel_iteration) {
// If target_kernel_ids is non-empty, only allow those kernel_ids
if (!tool->target_kernel_ids.empty() &&
!tool->target_kernel_ids.count(kernel_id))
return false;
// If kernel_filter_ranges is set, check if kernel_iteration is in any of the
// specified ranges
if (!tool->kernel_filter_ranges.empty())
return std::any_of(tool->kernel_filter_ranges.begin(),
tool->kernel_filter_ranges.end(),
[kernel_iteration](const auto &range) {
return kernel_iteration >= range.first &&
kernel_iteration <= range.second;
});
// If no filter ranges are specified, or all checks passed, profile this
// dispatch
return true;
}
/**
* @brief Creates a counter collection profile for performance monitoring on a
* specific GPU agent.
*
* This function parses the requested counters from the tool configuration,
* validates them against the counters supported by the target GPU agent, and
* creates a rocprofiler counter configuration for collecting the available
* requested counters during dispatch profiling.
*
* @param tool Pointer to tool data containing the requested counters string and
* counter mappings
* @param dispatch_data Dispatch counting service data containing agent
* information for the target GPU
*
* @return rocprofiler_counter_config_id_t A valid counter configuration profile
* ID that can be used for counter collection, or an invalid profile (handle =
* 0) if creation fails
*
* @details
* The function performs the following operations:
* 1. Parses the requested counters from tool->requested_counters string
* (format: "prefix:counter1 counter2 ...")
* 2. Queries all counters supported by the specified GPU agent
* 3. Filters the supported counters to match only those requested
* 4. Logs warnings for any requested counters that are not supported by the
* agent
* 5. Creates and returns a rocprofiler counter configuration for the valid
* counters
* 6. Updates the tool's counter ID to name mapping for later reference
*
* @note If no counters are requested or none of the requested counters are
* supported, an empty profile may be created. Unsupported counters are logged
* as warnings but do not cause the function to fail.
*/
rocprofiler_counter_config_id_t create_counter_collection_profile(
tool_data_t *tool,
rocprofiler_dispatch_counting_service_data_t dispatch_data) {
// get counters to collect
std::set<std::string> counters_to_collect;
const std::string &counters_str = tool->requested_counters;
if (!counters_str.empty()) {
auto pos = counters_str.find(':');
if (pos != std::string::npos) {
std::istringstream ss(counters_str.substr(pos + 1));
for (std::string token; ss >> token;)
counters_to_collect.insert(token);
}
}
// Get available counters for this agent
std::vector<rocprofiler_counter_id_t> gpu_counters;
ROCPROFILER_CALL(
rocprofiler_iterate_agent_supported_counters(
dispatch_data.dispatch_info.agent_id,
[](rocprofiler_agent_id_t, rocprofiler_counter_id_t *counters,
size_t num_counters, void *user_data) {
std::vector<rocprofiler_counter_id_t> *vec =
static_cast<std::vector<rocprofiler_counter_id_t> *>(user_data);
for (size_t i = 0; i < num_counters; i++) {
vec->push_back(counters[i]);
}
return ROCPROFILER_STATUS_SUCCESS;
},
static_cast<void *>(&gpu_counters)),
"fetch supported counters");
// Identify counters requested to collect which are available
std::vector<rocprofiler_counter_id_t> collect_counters;
std::vector<std::string> collect_counters_names;
for (auto &counter : gpu_counters) {
rocprofiler_counter_info_v0_t info;
ROCPROFILER_CALL(rocprofiler_query_counter_info(
counter, ROCPROFILER_COUNTER_INFO_VERSION_0,
static_cast<void *>(&info)),
"query counter info");
if (counters_to_collect.count(std::string(info.name)) > 0) {
collect_counters.push_back(counter);
collect_counters_names.push_back(std::string(info.name));
tool->counter_id_name_map[counter.handle] = std::string(info.name);
}
}
// Log unsupported counters in a concise, comma-separated line
std::vector<std::string> unsupported_counters;
for (const auto &requested : counters_to_collect) {
if (std::find(collect_counters_names.begin(), collect_counters_names.end(),
requested) == collect_counters_names.end()) {
unsupported_counters.push_back(requested);
}
}
if (!unsupported_counters.empty()) {
std::clog << "\033[33m[rocprofiler-compute] [" << __FUNCTION__
<< "] WARNING: Requested counters not available: ";
for (size_t i = 0; i < unsupported_counters.size(); ++i) {
std::clog << unsupported_counters[i];
if (i + 1 < unsupported_counters.size())
std::clog << ", ";
}
std::clog << "\033[0m" << std::endl;
}
// Create and return collection profile for the counters
rocprofiler_counter_config_id_t profile = {.handle = 0};
ROCPROFILER_CALL(
rocprofiler_create_counter_config(dispatch_data.dispatch_info.agent_id,
collect_counters.data(),
collect_counters.size(), &profile),
"construct profile cfg");
return profile;
}
/**
* Callback from rocprofiler when an kernel dispatch is enqueued into the HSA
* queue. rocprofiler_counter_config_id_t* is a return to specify what counters
* to collect for this dispatch (dispatch_packet).
* We store profile in a cache to prevent constructing many identical
* profiles. We first check the cache to see if we have already constructed a
* profile for the agent. If we have, return it. Otherwise, construct a new
* profile.
*/
void dispatch_callback(
rocprofiler_dispatch_counting_service_data_t dispatch_data,
rocprofiler_counter_config_id_t *config,
rocprofiler_user_data_t * /*user_data*/, void *callback_data_args) {
auto kernel_id = dispatch_data.dispatch_info.kernel_id;
// create static map of kernel_id to number of dispatches (zero indexed) and
// update it
static std::unordered_map<uint64_t, uint64_t> kernel_id_iteration_map{};
static std::shared_mutex kernel_id_iteration_mutex;
uint64_t kernel_iteration = 0;
{
// Acquire unique lock for update and ensure map is updated correctly
std::unique_lock<std::shared_mutex> lock(kernel_id_iteration_mutex);
auto &iter = kernel_id_iteration_map[kernel_id];
iter += 1;
kernel_iteration = iter;
}
// static cast tool
auto *tool_data_ptr =
static_cast<std::unique_ptr<tool_data_t> *>(callback_data_args);
tool_data_t *tool;
{
std::lock_guard<std::mutex> lock(tool_data_ptr->get()->mut);
tool = tool_data_ptr->get();
}
// kernel filtering
if (!is_targetted_dispatch(tool, kernel_id, kernel_iteration)) {
return;
}
static std::shared_mutex m_mutex = {};
static std::unordered_map<uint64_t, rocprofiler_counter_config_id_t>
profile_cache = {};
// check cache for existing profile for this agent
auto search_cache = [&]() {
if (auto pos =
profile_cache.find(dispatch_data.dispatch_info.agent_id.handle);
pos != profile_cache.end()) {
*config = pos->second;
return true;
}
return false;
};
{
auto rlock = std::shared_lock{m_mutex};
if (search_cache())
return;
}
// get write lock to update cache
auto wlock = std::unique_lock{m_mutex};
if (search_cache())
return;
// cache the profile for this agent
rocprofiler_counter_config_id_t profile =
create_counter_collection_profile(tool, dispatch_data);
profile_cache.emplace(dispatch_data.dispatch_info.agent_id.handle, profile);
// Return the profile to collect those counters for this dispatch
*config = profile;
}
int tool_init(rocprofiler_client_finalize_t, void *user_data) {
std::clog << "[rocprofiler-compute] In tool init\n";
ROCPROFILER_CALL(rocprofiler_create_context(&get_client_ctx()),
"context creation");
ROCPROFILER_CALL(rocprofiler_configure_callback_dispatch_counting_service(
get_client_ctx(), dispatch_callback, user_data,
record_callback, user_data),
"setup counting service");
ROCPROFILER_CALL(rocprofiler_configure_callback_tracing_service(
get_client_ctx(),
ROCPROFILER_CALLBACK_TRACING_CODE_OBJECT, nullptr, 0,
tool_tracing_callback, user_data),
"setup code object tracing service");
ROCPROFILER_CALL(rocprofiler_start_context(get_client_ctx()),
"start context");
return 0;
}
void generate_output(tool_data_t *tool_data) {
// Dispatches before the kernel to be filtered was registered may have been
// profiled. Remove any records whose kernel id does not match the
// target_kernel_ids
if (!tool_data->target_kernel_ids.empty()) {
tool_data->counter_records.erase(
std::remove_if(tool_data->counter_records.begin(),
tool_data->counter_records.end(),
[tool_data](const counter_info_record_t &record) {
return tool_data->target_kernel_ids.find(
record.kernel_id) ==
tool_data->target_kernel_ids.end();
}),
tool_data->counter_records.end());
}
// Write collected counter records and clean up
if (auto &os = tool_data->output_stream) {
for (const auto &r : tool_data->counter_records)
*os << r.dispatch_id << ',' << r.counter_id << ',' << r.counter_name
<< ',' << r.counter_value << '\n';
os->flush();
}
}
void tool_fini(void *user_data) {
assert(user_data);
std::clog << "[rocprofiler-compute] In tool fini\n";
rocprofiler_stop_context(get_client_ctx());
auto *tool_data_ptr = static_cast<std::unique_ptr<tool_data_t> *>(user_data);
generate_output(tool_data_ptr->get());
delete tool_data_ptr;
}
} // namespace
std::unique_ptr<tool_data_t> create_tool_data(rocprofiler_client_id_t *id) {
auto tool_data = std::make_unique<tool_data_t>();
// Generate a unique output filename using a random hex string (no libuuid
// dependency)
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32_t> dis(0, 0xFFFFFFFF);
std::stringstream filename_ss;
filename_ss << std::hex << dis(gen);
std::string base_filename =
"counter_collection_" + filename_ss.str().substr(0, 8) + ".csv";
// Require ROCPROF_OUTPUT_PATH to be set, otherwise error out
std::string filename;
const char *output_path = getenv("ROCPROF_OUTPUT_PATH");
if (!output_path || !*output_path) {
throw std::runtime_error(
"ROCPROF_OUTPUT_PATH environment variable must be set");
}
filename = output_path;
if (filename.back() != '/')
filename += '/';
// Use the generated base filename along with ROCPROF_OUTPUT_PATH
filename += base_filename;
// Set output stream to file
// Set output stream to file
auto ofs = std::make_unique<std::ofstream>(filename);
if (!ofs->is_open()) {
throw std::runtime_error("Failed to open output file: " + filename);
}
tool_data->output_stream = std::move(ofs);
// Write header at the beginning of the file
*tool_data->output_stream
<< "dispatch_id,counter_id,counter_name,counter_value\n";
tool_data->output_stream->flush();
// Write to clog the path of the logging file
std::clog << id->name << " [" << __FUNCTION__
<< "] Logging counter collection to: " << filename << std::endl;
// Store ROCPROF env. vars. in tool_data
// ROCPROF_COUNTERS env. var. is a string like "pmc: counter1 counter2 ..."
if (const char *v = getenv("ROCPROF_COUNTERS"))
tool_data->requested_counters = v;
// ROCPROF_KERNEL_FILTER_INCLUDE_REGEX env. var. is a regex string like
// kernel_name_1|kernel_name_2|... Used to collect counters only for kernels
// with names matching the regex
if (const char *v = getenv("ROCPROF_KERNEL_FILTER_INCLUDE_REGEX"))
tool_data->kernel_filter_include_regex = v;
// ROCPROF_KERNEL_FILTER_RANGE env. var. is a string like "[4,7-9,...]"
if (const char *v = getenv("ROCPROF_KERNEL_FILTER_RANGE")) {
// Remove square brackets at the ends if present
std::string v_str = v;
if (!v_str.empty() && v_str.front() == '[')
v_str.erase(0, 1);
if (!v_str.empty() && v_str.back() == ']')
v_str.pop_back();
v = v_str.c_str();
// Parse the range string into vector of pairs
std::istringstream ss(v);
for (std::string token; std::getline(ss, token, ',');) {
size_t dash_pos = token.find('-');
try {
if (dash_pos == std::string::npos) {
// single number
uint64_t num = std::stoull(token);
tool_data->kernel_filter_ranges.emplace_back(num, num);
} else {
// range of numbers
uint64_t start = std::stoull(token.substr(0, dash_pos));
uint64_t end = std::stoull(token.substr(dash_pos + 1));
tool_data->kernel_filter_ranges.emplace_back(start, end);
}
} catch (const std::invalid_argument &) {
std::cerr << "[rocprofiler-compute] [" << __FUNCTION__
<< "] ERROR: Invalid entry in ROCPROF_KERNEL_FILTER_RANGE: "
<< token << std::endl;
}
}
}
return tool_data;
}
rocprofiler_tool_configure_result_t *
rocprofiler_configure(uint32_t version, const char *runtime_version,
uint32_t priority, rocprofiler_client_id_t *id) {
// set the client name
id->name = "[rocprofiler-compute]";
// compute major/minor/patch version info
uint32_t major = version / 10000;
uint32_t minor = (version % 10000) / 100;
uint32_t patch = version % 100;
// generate info string
auto info = std::stringstream{};
info << id->name << " [" << __FUNCTION__ << "] (priority=" << priority
<< ") is using rocprofiler-sdk v" << major << "." << minor << "."
<< patch << " (" << runtime_version << ")";
std::clog << info.str() << std::endl;
// init tool data
auto tool_data = create_tool_data(id);
// create configure data
static auto cfg = rocprofiler_tool_configure_result_t{
sizeof(rocprofiler_tool_configure_result_t), &tool_init, &tool_fini,
static_cast<void *>(
new std::unique_ptr<tool_data_t>(std::move(tool_data)))};
// return pointer to configure data
return &cfg;
}
@@ -151,6 +151,22 @@ class RocProfCompute:
) and block:
console_error("Cannot use --list-available-metrics with --blocks")
# fallback to csv output format, if rocpd public api not available
if (
self.__mode == "profile"
and self.__args.format_rocprof_output == "rocpd"
and not (
Path(self.__args.rocprofiler_sdk_tool_path).parents[1]
/ "librocprofiler-sdk-rocpd.so"
).exists()
):
console_warning(
"rocpd output format is not supported with the "
"current rocprofiler-sdk version. "
"Falling back to csv output format."
)
self.__args.format_rocprof_output = "csv"
@demarcate
def load_soc_specs(self, sysinfo: Optional[dict] = None) -> None:
"""Load OmniSoC instance for RocProfCompute run"""
@@ -180,16 +196,6 @@ class RocProfCompute:
)
self.__args = parser.parse_args()
if (
hasattr(self.__args, "format_rocprof_output")
and self.__args.format_rocprof_output != "rocpd"
):
console_warning(
f"The option --format-rocprof-output currently set to "
f"{self.__args.format_rocprof_output} will default to rocpd "
"in a future release."
)
if self.__args.mode is None:
if self.__args.specs:
print(generate_machine_specs(self.__args))
@@ -27,6 +27,8 @@ import argparse
import csv
import shlex
import shutil
import sys
import tempfile
import time
from abc import abstractmethod
from pathlib import Path
@@ -67,9 +69,7 @@ class RocProfCompute_Base:
def get_args(self) -> argparse.Namespace:
return self.__args
def get_profiler_options(
self, fname: str, soc: OmniSoC_Base
) -> Union[list[str], dict[str, Any]]:
def get_profiler_options(self) -> Union[list[str], dict[str, Any]]:
"""Fetch any version specific arguments required by profiler"""
# assume no SoC specific options and return empty list by default
return []
@@ -415,6 +415,58 @@ class RocProfCompute_Base:
total_runs = len(input_files)
total_profiling_time = 0.0
native_tool_path = None
# Native counter collection tool is only compatible with
# rocprofiler-sdk public API for ROCm version >= 7.x.x
# Do not use native tool in attach
# mode until we figure out how multiple tools can attach
# TODO: Figure out how multiple tools can attach
if (
self.__profiler == "rocprofiler-sdk"
and not args.no_native_tool
and int(self._soc._mspec.rocm_version.split(".")[0]) >= 7
and not args.attach_pid
):
# Use native counter collection tool
native_tool_path = str(
Path(sys.argv[0]).resolve().parents[2]
/ "lib"
/ "rocprofiler-compute"
/ "librocprofiler-compute-tool.so"
)
if not Path(native_tool_path).is_file():
# Build native counter collection tool if not exists
native_tool_path = str(
Path(
tempfile.mkdtemp(prefix="rocprofiler-compute-tool-", dir="/tmp")
)
/ "librocprofiler-compute-tool.so"
)
link_libraries = ("rocprofiler-sdk",)
build_command = (
# Create shared object
"hipcc -shared -fPIC "
# Link with dependant libraries
+ " ".join(f"-l{lib}" for lib in link_libraries)
+ " "
# Compliler flags
"-std=c++17 -W -Wall -Wextra -Wshadow -O2 "
# rocprofiler sdk library path
f"-L {str(Path(args.rocprofiler_sdk_tool_path).parent.parent)} "
# native tool source files (tool.cpp and helper.cpp)
f"{str(Path(__file__).parent.parent)}/"
"lib/rocprofiler_compute_tool.cpp "
f"{str(Path(__file__).parent.parent)}/"
"lib/helper.cpp "
# temporary shared object for native tool
f"-o {native_tool_path}"
)
console_debug(f"Building native tool using command: {build_command}")
success, output = capture_subprocess_output(shlex.split(build_command))
console_debug(f"Build output: {output}")
if not success:
console_error("Failed to build native counter collection tool.")
for i, fname in enumerate(input_files):
run_number = i + 1
@@ -465,7 +517,10 @@ class RocProfCompute_Base:
console_debug(output)
console_log("profiling", f"Current input file: {fname}")
options = self.get_profiler_options(str(fname), self._soc)
if self.__profiler == "rocprofiler-sdk":
options = self.get_profiler_options(native_tool_path=native_tool_path)
else:
options = self.get_profiler_options()
start_time = time.time()
if self.__profiler == "rocprofv3" or self.__profiler == "rocprofiler-sdk":
# Only 1-run case is permitted for attach/detach
@@ -502,6 +557,10 @@ class RocProfCompute_Base:
else:
console_error("Profiler not supported")
# Delete temporary native tool if created
if native_tool_path and native_tool_path.startswith("/tmp"):
shutil.rmtree(Path(native_tool_path).parent, ignore_errors=True)
# PC sampling data is only collected when block "21" is specified
if not "21" in args.filter_blocks:
console_warning(
@@ -514,14 +573,13 @@ class RocProfCompute_Base:
console_log(f"[Run {total_runs + 1}/{total_runs + 1}][PC sampling profile run]")
start_time = time.time()
# No native tool for pc sampling
options = self.get_profiler_options()
pc_sampling_prof(
profiler_options=options,
method=args.pc_sampling_method,
interval=args.pc_sampling_interval,
workload_dir=args.path,
appcmd=shlex.split(
args.remaining
), # FIXME: the right solution is applying it when argparsing once!
rocprofiler_sdk_library_path=args.rocprofiler_sdk_library_path,
)
end_time = time.time()
@@ -46,7 +46,7 @@ class rocprof_v3_profiler(RocProfCompute_Base):
or not self.get_args().roof_only
)
def get_profiler_options(self, fname: str, soc: OmniSoC_Base) -> list[str]:
def get_profiler_options(self) -> list[str]:
args = self.get_args()
app_cmd = shlex.split(args.remaining)
@@ -90,12 +90,12 @@ class rocprof_v3_profiler(RocProfCompute_Base):
if args.dispatch:
for dispatch_id in args.dispatch:
if ":" in dispatch_id:
# 4:7 -> 5-7
# 4:7 -> 4-7
start, end = dispatch_id.split(":")
dispatch.append(f"{int(start) + 1}-{end}")
dispatch.append(f"{start}-{end}")
else:
# 4 -> 5
dispatch.append(f"{int(dispatch_id) + 1}")
# 4 -> 4
dispatch.append(f"{dispatch_id}")
if dispatch:
profiling_options.extend([
"--kernel-iteration-range",
@@ -26,7 +26,7 @@
import argparse
import shlex
from pathlib import Path
from typing import Union
from typing import Optional, Union
from rocprof_compute_profile.profiler_base import RocProfCompute_Base
from rocprof_compute_soc.soc_base import OmniSoC_Base
@@ -48,35 +48,44 @@ class rocprofiler_sdk_profiler(RocProfCompute_Base):
)
def get_profiler_options(
self, fname: str, soc: OmniSoC_Base
self, native_tool_path: Optional[str] = None
) -> dict[str, Union[str, list[str]]]:
args = self.get_args()
app_cmd = shlex.split(args.remaining)
rocm_libdir = Path(args.rocprofiler_sdk_library_path).parent
rocprofiler_sdk_tool_path = str(
rocm_libdir / "rocprofiler-sdk" / "librocprofiler-sdk-tool.so"
)
rocm_dir = Path(args.rocprofiler_sdk_library_path).parent.parent
rocprofiler_attach_tool_path = str(
rocm_dir / "lib" / "librocprofiler-sdk-rocattach.so"
)
ld_preload = [
rocprofiler_sdk_tool_path,
args.rocprofiler_sdk_library_path,
rocprofiler_attach_tool_path,
]
options = {
"ROCPROFILER_LIBRARY_CTOR": "1",
ld_preload = [args.rocprofiler_sdk_tool_path]
if native_tool_path:
# Use native tool to collect counters
ld_preload.append(native_tool_path)
options = {"ROCPROF_COUNTER_COLLECTION": "0"}
console_log(
f"Using native counter collection tool: {str(native_tool_path)}"
)
else:
options = {"ROCPROF_COUNTER_COLLECTION": "1"}
options.update({
"LD_PRELOAD": ":".join(ld_preload),
"ROCP_TOOL_LIBRARIES": rocprofiler_sdk_tool_path,
"LD_LIBRARY_PATH": str(rocm_libdir),
"ROCPROF_KERNEL_TRACE": "1",
"ROCPROF_OUTPUT_FORMAT": args.format_rocprof_output,
"ROCPROF_OUTPUT_PATH": f"{args.path}/out/pmc_1",
}
})
# Create folder pointed by ROCPROF_OUTPUT_PATH
Path(options["ROCPROF_OUTPUT_PATH"]).mkdir(parents=True, exist_ok=True)
if args.attach_pid:
# In attach mode, tools are provided using ROCP_TOOL_LIBRARIES
# instead of LD_PRELOAD.
options.update({
"ROCP_TOOL_LIBRARIES": ":".join(ld_preload),
})
options.pop("LD_PRELOAD", None)
rocprofiler_attach_tool_path = str(
Path(args.rocprofiler_sdk_tool_path).parent.parent
/ "librocprofiler-sdk-rocattach.so"
)
options.update({
"ROCPROF_ATTACH_TOOL_LIBRARY": rocprofiler_attach_tool_path,
"ROCPROF_ATTACH_PID": args.attach_pid,
@@ -108,13 +117,12 @@ class rocprofiler_sdk_profiler(RocProfCompute_Base):
if args.dispatch:
for dispatch_id in args.dispatch:
if ":" in dispatch_id:
# 4:7 -> 5-7
# 4:7 -> 4-7
start, end = dispatch_id.split(":")
dispatch.append(f"{int(start) + 1}-{end}")
dispatch.append(f"{start}-{end}")
else:
# 4 -> 5
dispatch.append(f"{int(dispatch_id) + 1}")
# 4 -> 4
dispatch.append(f"{dispatch_id}")
if dispatch:
options["ROCPROF_KERNEL_FILTER_RANGE"] = f"[{','.join(dispatch)}]"
if not args.attach_pid:
@@ -51,9 +51,7 @@ from utils.utils import (
METRIC_ID_RE,
add_counter_extra_config_input_yaml,
convert_metric_id_to_panel_info,
detect_rocprof,
get_panel_alias,
get_submodules,
is_tcc_channel_counter,
mibench,
parse_sets_yaml,
@@ -409,55 +407,35 @@ class OmniSoC_Base:
def get_rocprof_supported_counters(self) -> set[str]:
args = self.get_args()
rocprof_cmd = detect_rocprof(args)
if rocprof_cmd != "rocprofiler-sdk":
console_warning(
"rocprofv3 interface is deprecated and will be removed "
"in a future release."
)
rocprof_counters: set[str] = set()
if not (
str(rocprof_cmd).endswith("rocprofv3")
or str(rocprof_cmd) == "rocprofiler-sdk"
):
console_error(
f"Incompatible profiler: {rocprof_cmd}. "
"Supported profilers include: "
f"{get_submodules('rocprof_compute_profile')}"
# Point to counter definition
old_rocprofiler_metrics_path = os.environ.get("ROCPROFILER_METRICS_PATH")
os.environ["ROCPROFILER_METRICS_PATH"] = str(
config.rocprof_compute_home / "rocprof_compute_soc" / "profile_configs"
)
sys.path.append(
str(
Path(args.rocprofiler_sdk_tool_path).parents[1]
/ "python3/site-packages"
)
)
from rocprofv3 import avail
# Point to counter definition
old_rocprofiler_metrics_path = os.environ.get("ROCPROFILER_METRICS_PATH")
os.environ["ROCPROFILER_METRICS_PATH"] = str(
config.rocprof_compute_home / "rocprof_compute_soc" / "profile_configs"
)
sys.path.append(
str(
Path(self.get_args().rocprofiler_sdk_library_path).parent
/ "python3/site-packages"
)
)
from rocprofv3 import avail
avail.loadLibrary.libname = str(
Path(args.rocprofiler_sdk_library_path).parent
/ "rocprofiler-sdk"
/ "librocprofv3-list-avail.so"
)
counters = avail.get_counters()
rocprof_counters = {
counter.name
for counter in counters[list(counters.keys())[0]]
if hasattr(counter, "block") or hasattr(counter, "expression")
}
# Reset env. var.
if old_rocprofiler_metrics_path is None:
del os.environ["ROCPROFILER_METRICS_PATH"]
else:
os.environ["ROCPROFILER_METRICS_PATH"] = old_rocprofiler_metrics_path
avail.loadLibrary.libname = str(
Path(args.rocprofiler_sdk_tool_path).parent / "librocprofv3-list-avail.so"
)
counters = avail.get_counters()
rocprof_counters = {
counter.name
for counter in counters[list(counters.keys())[0]]
if hasattr(counter, "block") or hasattr(counter, "expression")
}
# Reset env. var.
if old_rocprofiler_metrics_path is None:
del os.environ["ROCPROFILER_METRICS_PATH"]
else:
os.environ["ROCPROFILER_METRICS_PATH"] = old_rocprofiler_metrics_path
return rocprof_counters
@@ -53,6 +53,12 @@ SELECT
value as Counter_Value
FROM counters_collection
"""
ROCPD_PMC_EVENT_TABLE_NAME_PREFIX = "rocpd_pmc_event_"
TABLE_NAME_PREFIX_QUERY = (
"SELECT name FROM sqlite_master WHERE type='table' "
"AND name LIKE '{table_name_prefix}%'"
)
INSERT_QUERY = "INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
def convert_db_to_csv(
@@ -120,3 +126,55 @@ def process_rocpd_csv(df: pd.DataFrame) -> pd.DataFrame:
# Reset dispatch IDs
df["Dispatch_ID"] = range(len(df))
return df
def update_rocpd_pmc_events(counter_info: pd.DataFrame, rocpd_db_path: str) -> None:
"""Update pmc_event table in the given rocpd database path"""
try:
with closing(sqlite3.connect(rocpd_db_path)) as conn:
# Get pmc_event table name
with closing(
conn.execute(
TABLE_NAME_PREFIX_QUERY.format(
table_name_prefix=ROCPD_PMC_EVENT_TABLE_NAME_PREFIX
)
)
) as cursor:
table_name = cursor.fetchone()
if table_name is None:
console_error("No pmc_event table found in the rocpd database")
table_name = table_name[0]
# get pmc_event table data
guid = table_name[len(ROCPD_PMC_EVENT_TABLE_NAME_PREFIX) :].replace(
"_", "-"
)
columns = ("guid", "event_id", "pmc_id", "value")
values = list(
zip(
# guid
[guid] * len(counter_info),
# event_id
counter_info["dispatch_id"],
# pmc_id
counter_info["counter_id"],
# value
counter_info["counter_value"],
)
)
# insert into pmc_event table
with conn:
placeholders = ", ".join(["?"] * len(columns))
conn.executemany(
INSERT_QUERY.format(
table_name=table_name,
columns=", ".join(columns),
placeholders=placeholders,
),
values,
)
except OSError as e:
console_error(f"Database error while updating pmc_event table: {e}")
except Exception as e:
console_error(f"Unexpected error updating pmc_event table: {e}")
+236 -160
View File
@@ -41,6 +41,7 @@ import sys
import tempfile
import threading
import time
import traceback
import uuid
from collections.abc import Generator
from contextlib import contextmanager
@@ -210,14 +211,14 @@ def detect_rocprof(args: argparse.Namespace) -> str:
# Default is rocprofiler-sdk
if os.environ.get("ROCPROF", "rocprofiler-sdk") == "rocprofiler-sdk":
if not Path(args.rocprofiler_sdk_library_path).exists():
if not Path(args.rocprofiler_sdk_tool_path).exists():
console_error(
"Could not find rocprofiler-sdk library at "
f"{args.rocprofiler_sdk_library_path}"
"Could not find rocprofiler-sdk tool at "
f"{args.rocprofiler_sdk_tool_path}"
)
rocprof_cmd = "rocprofiler-sdk"
console_debug(f"rocprof_cmd is {rocprof_cmd}")
console_debug(f"rocprofiler_sdk_path is {args.rocprofiler_sdk_library_path}")
console_debug(f"rocprofiler_sdk_tool_path is {args.rocprofiler_sdk_tool_path}")
else:
# If ROCPROF is not set to rocprofiler-sdk
rocprof_cmd = os.environ["ROCPROF"]
@@ -705,16 +706,12 @@ def run_prof(
# standard rocprof options
if rocprof_cmd == "rocprofiler-sdk":
options = cast(dict[str, Union[str, list[str]]], profiler_options)
options["ROCPROF_COUNTER_COLLECTION"] = "1"
options = cast(dict[str, Union[str, list[str]]], profiler_options).copy()
options["ROCPROF_COUNTERS"] = f"pmc: {' '.join(parse_text(fname))}"
options["ROCPROF_AGENT_INDEX"] = "absolute"
else:
default_options = ["-i", fname]
options = default_options + cast(list[str], profiler_options)
if rocprof_cmd == "rocprofiler-sdk":
options["ROCPROF_AGENT_INDEX"] = "absolute"
else:
options = ["-A", "absolute"] + options
new_env = os.environ.copy()
@@ -758,7 +755,6 @@ def run_prof(
):
new_env["ROCPROFILER_INDIVIDUAL_XCC_MODE"] = "1"
is_timestamps = Path(fname).name == "timestamps.txt"
time_1 = time.time()
if rocprof_cmd == "rocprofiler-sdk":
@@ -849,6 +845,16 @@ def run_prof(
results_files: list[str] = []
if format_rocprof_output == "rocpd":
# If using native tool for counter collection
if (
rocprof_cmd == "rocprofiler-sdk"
and options["ROCPROF_COUNTER_COLLECTION"] == "0"
):
# Update rocpd database with counter csv created by native tool
rocpd_data.update_rocpd_pmc_events(
pd.read_csv(glob.glob(workload_dir + "/out/pmc_1/*.csv")[0]),
glob.glob(workload_dir + "/out/pmc_1/*/*.db")[0],
)
# Write results_fbase.csv
rocpd_data.convert_db_to_csv(
glob.glob(workload_dir + "/out/pmc_1/*/*.db")[0],
@@ -865,88 +871,95 @@ def run_prof(
# Remove temp directory
shutil.rmtree(workload_dir + "/" + "out")
return
# rocprofv3 requires additional processing for each process
results_files = process_rocprofv3_output(
format_rocprof_output, workload_dir, is_timestamps
)
if rocprof_cmd == "rocprofiler-sdk":
# TODO: as rocprofv3 --kokkos-trace feature improves,
# rocprof-compute should make updates accordingly
if "ROCPROF_HIP_RUNTIME_API_TRACE" in options:
process_hip_trace_output(workload_dir, fbase)
else:
if "--kokkos-trace" in options:
elif format_rocprof_output == "csv":
if rocprof_cmd == "rocprofiler-sdk":
# rocprofv3 requires additional processing for each process
results_files = process_rocprofv3_output(
workload_dir,
# counter data collected using native tool
using_native_tool=options["ROCPROF_COUNTER_COLLECTION"] == "0",
)
# TODO: as rocprofv3 --kokkos-trace feature improves,
# rocprof-compute should make updates accordingly
process_kokkos_trace_output(workload_dir, fbase)
elif "--hip-trace" in options:
process_hip_trace_output(workload_dir, fbase)
if "ROCPROF_HIP_RUNTIME_API_TRACE" in options:
process_hip_trace_output(workload_dir, fbase)
else:
# rocprofv3 requires additional processing for each process
# rocprofv3 cannot use native tool
results_files = process_rocprofv3_output(
workload_dir, using_native_tool=False
)
if "--kokkos-trace" in options:
# TODO: as rocprofv3 --kokkos-trace feature improves,
# rocprof-compute should make updates accordingly
process_kokkos_trace_output(workload_dir, fbase)
elif "--hip-trace" in options:
process_hip_trace_output(workload_dir, fbase)
# Combine results into single CSV file
if results_files:
combined_results = pd.concat(
[pd.read_csv(f) for f in results_files], ignore_index=True
# Combine results into single CSV file
if results_files:
combined_results = pd.concat(
[pd.read_csv(f) for f in results_files], ignore_index=True
)
else:
console_warning(
f"Cannot write results for {fbase}.csv due to no counter "
"csv files generated."
)
return
# Overwrite column to ensure unique IDs.
combined_results["Dispatch_ID"] = range(0, len(combined_results))
combined_results.to_csv(
workload_dir + "/out/pmc_1/results_" + fbase + ".csv", index=False
)
if Path(f"{workload_dir}/out").exists():
# copy and remove out directory if needed
shutil.copyfile(
f"{workload_dir}/out/pmc_1/results_{fbase}.csv",
f"{workload_dir}/{fbase}.csv",
)
# Remove temp directory
shutil.rmtree(f"{workload_dir}/out")
# Standardize rocprof headers via overwrite
# {<key to remove>: <key to replace>}
output_headers = {
# ROCm-6.1.0 specific csv headers
"KernelName": "Kernel_Name",
"Index": "Dispatch_ID",
"grd": "Grid_Size",
"gpu-id": "GPU_ID",
"wgr": "Workgroup_Size",
"lds": "LDS_Per_Workgroup",
"scr": "Scratch_Per_Workitem",
"sgpr": "SGPR",
"arch_vgpr": "Arch_VGPR",
"accum_vgpr": "Accum_VGPR",
"BeginNs": "Start_Timestamp",
"EndNs": "End_Timestamp",
# ROCm-6.0.0 specific csv headers
"GRD": "Grid_Size",
"WGR": "Workgroup_Size",
"LDS": "LDS_Per_Workgroup",
"SCR": "Scratch_Per_Workitem",
"ACCUM_VGPR": "Accum_VGPR",
}
csv_path = Path(workload_dir) / f"{fbase}.csv"
df = pd.read_csv(csv_path)
df.rename(columns=output_headers, inplace=True)
df.to_csv(csv_path, index=False)
else:
console_warning(
f"Cannot write results for {fbase}.csv due to no counter "
"csv files generated."
)
return
# Overwrite column to ensure unique IDs.
combined_results["Dispatch_ID"] = range(0, len(combined_results))
combined_results.to_csv(
workload_dir + "/out/pmc_1/results_" + fbase + ".csv", index=False
)
if Path(f"{workload_dir}/out").exists():
# copy and remove out directory if needed
shutil.copyfile(
f"{workload_dir}/out/pmc_1/results_{fbase}.csv",
f"{workload_dir}/{fbase}.csv",
)
# Remove temp directory
shutil.rmtree(f"{workload_dir}/out")
# Standardize rocprof headers via overwrite
# {<key to remove>: <key to replace>}
output_headers = {
# ROCm-6.1.0 specific csv headers
"KernelName": "Kernel_Name",
"Index": "Dispatch_ID",
"grd": "Grid_Size",
"gpu-id": "GPU_ID",
"wgr": "Workgroup_Size",
"lds": "LDS_Per_Workgroup",
"scr": "Scratch_Per_Workitem",
"sgpr": "SGPR",
"arch_vgpr": "Arch_VGPR",
"accum_vgpr": "Accum_VGPR",
"BeginNs": "Start_Timestamp",
"EndNs": "End_Timestamp",
# ROCm-6.0.0 specific csv headers
"GRD": "Grid_Size",
"WGR": "Workgroup_Size",
"LDS": "LDS_Per_Workgroup",
"SCR": "Scratch_Per_Workitem",
"ACCUM_VGPR": "Accum_VGPR",
}
csv_path = Path(workload_dir) / f"{fbase}.csv"
df = pd.read_csv(csv_path)
df.rename(columns=output_headers, inplace=True)
df.to_csv(csv_path, index=False)
console_error(f"Unknown format_rocprof_output: {format_rocprof_output}")
def pc_sampling_prof(
profiler_options: Union[list[str], dict[str, Union[str, list[str]]]],
method: str,
interval: int,
workload_dir: str,
appcmd: list[str],
rocprofiler_sdk_library_path: str,
) -> None:
"""
Run rocprof with pc sampling. Current support v3 only.
@@ -957,19 +970,12 @@ def pc_sampling_prof(
unit = "time" if method == "host_trap" else "cycles"
if rocprof_cmd == "rocprofiler-sdk":
rocm_libdir = str(Path(rocprofiler_sdk_library_path).parent)
rocprofiler_sdk_tool_path = str(
Path(rocm_libdir) / "rocprofiler-sdk/librocprofiler-sdk-tool.so"
)
ld_preload = [
rocprofiler_sdk_tool_path,
rocprofiler_sdk_library_path,
]
options = {
"ROCPROFILER_LIBRARY_CTOR": "1",
"LD_PRELOAD": ":".join(ld_preload),
"ROCP_TOOL_LIBRARIES": rocprofiler_sdk_tool_path,
"LD_LIBRARY_PATH": rocm_libdir,
options = cast(dict[str, Union[str, list[str]]], profiler_options).copy()
options.update({
# no counter collection for pc sampling
"ROCPROF_COUNTER_COLLECTION": "0",
# no kernel tracing for pc sampling
"ROCPROF_KERNEL_TRACE": "0",
"ROCPROF_OUTPUT_FORMAT": "csv,json",
"ROCPROF_OUTPUT_PATH": workload_dir,
"ROCPROF_OUTPUT_FILE_NAME": "ps_file",
@@ -977,15 +983,15 @@ def pc_sampling_prof(
"ROCPROF_PC_SAMPLING_UNIT": unit,
"ROCPROF_PC_SAMPLING_INTERVAL": str(interval),
"ROCPROF_PC_SAMPLING_METHOD": method,
"ROCPROF_KERNEL_TRACE": "1",
}
})
app_cmd = options.pop("APP_CMD") if "APP_CMD" in options else None
new_env = os.environ.copy()
for key, value in options.items():
new_env[key] = value
console_debug(f"pc sampling rocprof sdk env vars: {new_env}")
console_debug(f"pc sampling rocprof sdk user provided command: {appcmd}")
console_debug(f"pc sampling rocprof sdk user provided command: {app_cmd}")
success, output = capture_subprocess_output(
appcmd, new_env=new_env, profileMode=True
app_cmd, new_env=new_env, profileMode=True
)
else:
options = [
@@ -1005,9 +1011,11 @@ def pc_sampling_prof(
"-o",
"ps_file", # TODO: sync up with the name from source in 2100_.yaml
"--",
cast(str, profiler_options[-1]), # app command
]
options.extend(appcmd)
console_debug(f"rocprof command: {shlex.join([rocprof_cmd] + options)}")
# profile the app
success, output = capture_subprocess_output(
[rocprof_cmd] + options, new_env=os.environ.copy(), profileMode=True
)
@@ -1016,72 +1024,140 @@ def pc_sampling_prof(
console_error("PC sampling failed.")
def process_rocprofv3_output(
rocprof_output: str, workload_dir: str, is_timestamps: bool
) -> list[str]:
def convert_native_counter_collection_csv(workload_dir: str) -> None:
"""
rocprofv3 specific output processing.
takes care of json or csv formats, for csv format,
additional processing is performed.
Use native counter collection csv and rocprofiler-sdk kernel
trace to write counter collection csv in rocprofiler-sdk format
for further processing to pmc_perf.csv file
"""
counter_data = pd.read_csv(
glob.glob(f"{workload_dir}/out/pmc_1/*.csv")[0], index_col=False
)
# Group by on counter_data based on dispatch_id and
# counter_id and sum the counter_value
counter_data = counter_data.groupby(
["dispatch_id", "counter_name"], as_index=False
).agg({"counter_value": "sum"})
kernel_data_filename = glob.glob(f"{workload_dir}/out/pmc_1/*/*_kernel_trace.csv")[
0
]
kernel_data = pd.read_csv(kernel_data_filename)
rocprofv3_counter_data = pd.DataFrame({
"Correlation_Id": counter_data["dispatch_id"],
"Dispatch_Id": counter_data["dispatch_id"],
"Agent_Id": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Agent_Id"
].values,
"Queue_Id": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Queue_Id"
].values,
"Process_Id": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Thread_Id"
].values,
"Thread_Id": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Thread_Id"
].values,
"Grid_Size": (
kernel_data.iloc[counter_data["dispatch_id"] - 1][
["Grid_Size_X", "Grid_Size_Y", "Grid_Size_Z"]
]
.prod(axis=1)
.values
),
"Kernel_Id": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Kernel_Id"
].values,
"Kernel_Name": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Kernel_Name"
].values,
"Workgroup_Size": (
kernel_data.iloc[counter_data["dispatch_id"] - 1][
["Workgroup_Size_X", "Workgroup_Size_Y", "Workgroup_Size_Z"]
]
.prod(axis=1)
.values
),
"LDS_Block_Size": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"LDS_Block_Size"
].values,
"Scratch_Size": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Scratch_Size"
].values,
"VGPR_Count": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"VGPR_Count"
].values,
"Accum_VGPR_Count": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Accum_VGPR_Count"
].values,
"SGPR_Count": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"SGPR_Count"
].values,
"Counter_Name": counter_data["counter_name"],
"Counter_Value": counter_data["counter_value"],
"Start_Timestamp": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"Start_Timestamp"
].values,
"End_Timestamp": kernel_data.iloc[counter_data["dispatch_id"] - 1][
"End_Timestamp"
].values,
})
rocprofv3_counter_data.to_csv(
kernel_data_filename.replace("kernel_trace", "counter_collection"),
index=False,
)
def process_rocprofv3_output(workload_dir: str, using_native_tool: bool) -> list[str]:
"""
rocprofv3 specific output processing for csv format.
"""
results_files_csv: list[str] = []
if rocprof_output == "json":
results_files_json = glob.glob(f"{workload_dir}/out/pmc_1/*/*.json")
for json_file in results_files_json:
csv_file = str(Path(json_file).with_suffix(".csv"))
v3_json_to_csv(json_file, csv_file)
results_files_csv = glob.glob(f"{workload_dir}/out/pmc_1/*/*.csv")
elif rocprof_output == "csv":
counter_info_csvs = glob.glob(
f"{workload_dir}/out/pmc_1/*/*_counter_collection.csv"
)
existing_counter_files_csv = [f for f in counter_info_csvs if Path(f).is_file()]
if existing_counter_files_csv:
for counter_file in existing_counter_files_csv:
counter_path = Path(counter_file)
current_dir = counter_path.parent
agent_info_filepath = current_dir / counter_path.name.replace(
"_counter_collection", "_agent_info"
)
if not agent_info_filepath.is_file():
raise ValueError(
f'{counter_file} has no corresponding "agent info" file'
)
converted_csv_file = current_dir / counter_path.name.replace(
"_counter_collection", "_converted"
)
try:
v3_counter_csv_to_v2_csv(
counter_file, str(agent_info_filepath), str(converted_csv_file)
)
except Exception as e:
console_warning(
f"Error converting {counter_file} from v3 to v2 csv: {e}"
)
return []
results_files_csv = glob.glob(f"{workload_dir}/out/pmc_1/*/*_converted.csv")
elif is_timestamps:
# when the input is timestamps, we know counter csv file
# is not generated and will instead parse kernel trace file
results_files_csv = glob.glob(
f"{workload_dir}/out/pmc_1/*/*_kernel_trace.csv"
if using_native_tool:
try:
convert_native_counter_collection_csv(workload_dir)
except Exception:
console_error(
"Error converting native counter collection csv.\n"
f"Stacktrace:\n{traceback.format_exc()}"
)
else:
# when the input is not for timestamps, and counter csv file
# is not generated, we assume failed rocprof run and will completely
# bypass the file generation and merging for current pmc
results_files_csv = []
counter_info_csvs = glob.glob(
f"{workload_dir}/out/pmc_1/*/*_counter_collection.csv"
)
existing_counter_files_csv = [f for f in counter_info_csvs if Path(f).is_file()]
if existing_counter_files_csv:
for counter_file in existing_counter_files_csv:
counter_path = Path(counter_file)
current_dir = counter_path.parent
agent_info_filepath = current_dir / counter_path.name.replace(
"_counter_collection", "_agent_info"
)
if not agent_info_filepath.is_file():
raise ValueError(
f'{counter_file} has no corresponding "agent info" file'
)
converted_csv_file = current_dir / counter_path.name.replace(
"_counter_collection", "_converted"
)
try:
v3_counter_csv_to_v2_csv(
counter_file, str(agent_info_filepath), str(converted_csv_file)
)
except Exception as e:
console_warning(
f"Error converting {counter_file} from v3 to v2 csv: {e}"
)
return []
results_files_csv = glob.glob(f"{workload_dir}/out/pmc_1/*/*_converted.csv")
else:
console_error("The output file of rocprofv3 can only support json or csv!!!")
return []
return results_files_csv
+14 -7
View File
@@ -51,12 +51,13 @@ def pytest_addoption(parser):
)
parser.addoption(
"--rocprofiler-sdk-library-path",
"--rocprofiler-sdk-tool-path",
type=str,
default=str(
Path(os.getenv("ROCM_PATH", "/opt/rocm")) / "lib/librocprofiler-sdk.so"
Path(os.getenv("ROCM_PATH", "/opt/rocm"))
/ "lib/rocprofiler-sdk/librocprofiler-sdk-tool.so"
),
help="Path to the rocprofiler-sdk library",
help="Path to the rocprofiler-sdk tool",
)
@@ -71,11 +72,11 @@ def binary_handler_profile_rocprof_compute(request):
app_name="app_1",
attach_detach_para=None,
):
if request.config.getoption("--rocprofiler-sdk-library-path"):
if request.config.getoption("--rocprofiler-sdk-tool-path"):
options.extend(
[
"--rocprofiler-sdk-library-path",
request.config.getoption("--rocprofiler-sdk-library-path"),
"--rocprofiler-sdk-tool-path",
request.config.getoption("--rocprofiler-sdk-tool-path"),
],
)
if request.config.getoption("--call-binary"):
@@ -114,7 +115,13 @@ def binary_handler_profile_rocprof_compute(request):
assert process.returncode == 0
return process.returncode
else:
baseline_opts = ["rocprof-compute", "profile", "-n", app_name, "-VVV"]
baseline_opts = [
"install/bin/rocprof-compute",
"profile",
"-n",
app_name,
"-VVV",
]
if not roof:
baseline_opts.append("--no-roof")
@@ -74,99 +74,23 @@ num_devices = 1
attach_detach_interval_msec_no_delay = 10000
attach_detach_interval_msec_with_delay = 60000
DEFAULT_ABS_DIFF = 15
DEFAULT_REL_DIFF = 50
MAX_REOCCURING_COUNT = 28
ALL_CSVS_MI100 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_LEVEL_WAVES.csv",
CSVS = sorted([
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"pmc_perf_6.csv",
"sysinfo.csv",
])
ALL_CSVS_MI200 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_INST_LEVEL_SMEM.csv",
"SQ_INST_LEVEL_VMEM.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"sysinfo.csv",
])
ALL_CSVS_MI300 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_INST_LEVEL_SMEM.csv",
"SQ_INST_LEVEL_VMEM.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"sysinfo.csv",
])
ALL_CSVS_MI350 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_INST_LEVEL_SMEM.csv",
"SQ_INST_LEVEL_VMEM.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"pmc_perf_6.csv",
"pmc_perf_7.csv",
"pmc_perf_8.csv",
"pmc_perf_9.csv",
"pmc_perf_10.csv",
"pmc_perf_11.csv",
"pmc_perf_12.csv",
"sysinfo.csv",
])
ROOF_ONLY_FILES = sorted([
"empirRoof_gpu-0_FP32.pdf",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"roofline.csv",
"sysinfo.csv",
])
PC_SAMPLING_HOST_TRAP_FILES = sorted([
"pmc_perf_0.csv",
"pmc_perf.csv",
"ps_file_agent_info.csv",
"ps_file_kernel_trace.csv",
@@ -176,7 +100,6 @@ PC_SAMPLING_HOST_TRAP_FILES = sorted([
])
PC_SAMPLING_STOCHASTIC_FILES = sorted([
"pmc_perf_0.csv",
"pmc_perf.csv",
"ps_file_agent_info.csv",
"ps_file_kernel_trace.csv",
@@ -550,13 +473,38 @@ def test_path(binary_handler_profile_rocprof_compute):
file_dict = test_utils.check_csv_files(workload_dir, num_devices, num_kernels)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"This test is not supported for {soc}")
assert 0
validate(inspect.stack()[0][3], workload_dir, file_dict)
test_utils.clean_output_dir(config["cleanup"], workload_dir)
@pytest.mark.path
def test_path_no_native(binary_handler_profile_rocprof_compute):
workload_dir = test_utils.get_output_dir()
options = ["--no-native-tool"]
binary_handler_profile_rocprof_compute(config, workload_dir, options)
file_dict = test_utils.check_csv_files(workload_dir, num_devices, num_kernels)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"This test is not supported for {soc}")
assert 0
@@ -586,6 +534,107 @@ def test_path_rocpd(
test_utils.clean_output_dir(config["cleanup"], workload_dir)
@pytest.mark.path
def test_path_csv(
binary_handler_profile_rocprof_compute, binary_handler_analyze_rocprof_compute
):
workload_dir = test_utils.get_output_dir()
options = ["--format-rocprof-output", "csv"]
binary_handler_profile_rocprof_compute(config, workload_dir, options)
file_dict = test_utils.check_csv_files(workload_dir, num_devices, num_kernels)
all_csvs_mi100 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"pmc_perf_6.csv",
"sysinfo.csv",
])
all_csvs_mi200 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_INST_LEVEL_SMEM.csv",
"SQ_INST_LEVEL_VMEM.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"sysinfo.csv",
])
all_csvs_mi300 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_INST_LEVEL_SMEM.csv",
"SQ_INST_LEVEL_VMEM.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"sysinfo.csv",
])
all_csvs_mi350 = sorted([
"SQC_DCACHE_INFLIGHT_LEVEL.csv",
"SQC_ICACHE_INFLIGHT_LEVEL.csv",
"SQ_IFETCH_LEVEL.csv",
"SQ_INST_LEVEL_LDS.csv",
"SQ_INST_LEVEL_SMEM.csv",
"SQ_INST_LEVEL_VMEM.csv",
"SQ_LEVEL_WAVES.csv",
"pmc_perf.csv",
"pmc_perf_0.csv",
"pmc_perf_1.csv",
"pmc_perf_2.csv",
"pmc_perf_3.csv",
"pmc_perf_4.csv",
"pmc_perf_5.csv",
"pmc_perf_6.csv",
"pmc_perf_7.csv",
"pmc_perf_8.csv",
"pmc_perf_9.csv",
"pmc_perf_10.csv",
"pmc_perf_11.csv",
"pmc_perf_12.csv",
"sysinfo.csv",
])
if soc == "MI100":
assert sorted(list(file_dict.keys())) == all_csvs_mi100
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == all_csvs_mi200
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == all_csvs_mi300
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == all_csvs_mi350
else:
print(f"This test is not supported for {soc}")
assert 0
validate(inspect.stack()[0][3], workload_dir, file_dict)
test_utils.clean_output_dir(config["cleanup"], workload_dir)
@pytest.mark.roofline
def test_roof_basic_validation(binary_handler_profile_rocprof_compute):
"""
@@ -1422,13 +1471,13 @@ def test_device_filter(binary_handler_profile_rocprof_compute):
file_dict = test_utils.check_csv_files(workload_dir, 1, num_kernels)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
@@ -1452,13 +1501,13 @@ def test_kernel(binary_handler_profile_rocprof_compute):
file_dict = test_utils.check_csv_files(workload_dir, num_devices, num_kernels)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
@@ -1474,19 +1523,19 @@ def test_kernel(binary_handler_profile_rocprof_compute):
@pytest.mark.dispatch
def test_dispatch_0(binary_handler_profile_rocprof_compute):
options = ["--dispatch", "0"]
options = ["--dispatch", "1"]
workload_dir = test_utils.get_output_dir()
binary_handler_profile_rocprof_compute(config, workload_dir, options)
file_dict = test_utils.check_csv_files(workload_dir, num_devices, 1)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
@@ -1497,7 +1546,7 @@ def test_dispatch_0(binary_handler_profile_rocprof_compute):
file_dict,
[
"--dispatch",
"0",
"1",
],
)
@@ -1506,19 +1555,19 @@ def test_dispatch_0(binary_handler_profile_rocprof_compute):
@pytest.mark.dispatch
def test_dispatch_0_1(binary_handler_profile_rocprof_compute):
options = ["--dispatch", "0:2"]
options = ["--dispatch", "1:2"]
workload_dir = test_utils.get_output_dir()
binary_handler_profile_rocprof_compute(config, workload_dir, options)
file_dict = test_utils.check_csv_files(workload_dir, num_devices, 2)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
@@ -1527,7 +1576,7 @@ def test_dispatch_0_1(binary_handler_profile_rocprof_compute):
inspect.stack()[0][3],
workload_dir,
file_dict,
["--dispatch", "0", "1"],
["--dispatch", "1", "2"],
)
test_utils.clean_output_dir(config["cleanup"], workload_dir)
@@ -1535,19 +1584,19 @@ def test_dispatch_0_1(binary_handler_profile_rocprof_compute):
@pytest.mark.dispatch
def test_dispatch_2(binary_handler_profile_rocprof_compute):
options = ["--dispatch", "0"]
options = ["--dispatch", "1"]
workload_dir = test_utils.get_output_dir()
binary_handler_profile_rocprof_compute(config, workload_dir, options)
file_dict = test_utils.check_csv_files(workload_dir, num_devices, 1)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
@@ -1558,7 +1607,7 @@ def test_dispatch_2(binary_handler_profile_rocprof_compute):
file_dict,
[
"--dispatch",
"0",
"1",
],
)
@@ -1573,13 +1622,13 @@ def test_join_type_grid(binary_handler_profile_rocprof_compute):
file_dict = test_utils.check_csv_files(workload_dir, num_devices, num_kernels)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
@@ -1602,13 +1651,13 @@ def test_join_type_kernel(binary_handler_profile_rocprof_compute):
file_dict = test_utils.check_csv_files(workload_dir, num_devices, num_kernels)
if soc == "MI100":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI100
assert sorted(list(file_dict.keys())) == CSVS
elif soc == "MI200":
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI200
assert sorted(list(file_dict.keys())) == CSVS
elif "MI300" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI300
assert sorted(list(file_dict.keys())) == CSVS
elif "MI350" in soc:
assert sorted(list(file_dict.keys())) == ALL_CSVS_MI350
assert sorted(list(file_dict.keys())) == CSVS
else:
print(f"Testing isn't supported yet for {soc}")
assert 0
+39 -164
View File
@@ -381,7 +381,7 @@ def test_detect_rocprof_env_rocprof_not_found(monkeypatch):
"""
class DummyArgs:
rocprofiler_sdk_library_path = "/fake/path"
rocprofiler_sdk_tool_path = "/fake/path"
# Set ROCPROF to 'rocprof'
monkeypatch.setenv("ROCPROF", "rocprofv3")
@@ -416,7 +416,7 @@ def test_detect_rocprof_env_rocprof_found(monkeypatch):
"""
class DummyArgs:
rocprofiler_sdk_library_path = "/fake/path"
rocprofiler_sdk_tool_path = "/fake/path"
monkeypatch.setenv("ROCPROF", "rocprof")
# shutil.which returns a fake path for 'rocprof'
@@ -448,7 +448,7 @@ def test_detect_rocprof_env_not_set(monkeypatch):
"""
class DummyArgs:
rocprofiler_sdk_library_path = "/fake/path"
rocprofiler_sdk_tool_path = "/fake/path"
monkeypatch.delenv("ROCPROF", raising=False)
monkeypatch.setattr("pathlib.Path.exists", lambda _: True)
@@ -475,7 +475,7 @@ def test_detect_rocprof_sdk(monkeypatch):
"""
class DummyArgs:
rocprofiler_sdk_library_path = "/some/sdk/path"
rocprofiler_sdk_tool_path = "/some/sdk/path"
monkeypatch.setenv("ROCPROF", "rocprofiler-sdk")
monkeypatch.setattr("pathlib.Path.exists", lambda self: True)
@@ -2500,6 +2500,7 @@ def test_run_prof_success_rocprofiler_sdk(tmp_path, monkeypatch):
profiler_options = {
"APP_CMD": ["./test_app"],
"ROCPROF_OUTPUT_PATH": workload_dir,
"ROCPROF_COUNTER_COLLECTION": "1",
"ROCP_TOOL_LIBRARIES": "/opt/rocm/lib/rocprofiler-sdk/"
"librocprofiler-sdk-tool.so",
}
@@ -3061,13 +3062,14 @@ def test_run_prof_v3_sdk_and_cli_calls_trace_processing(tmp_path, monkeypatch):
mspec = MockMSpec()
loglevel = logging.INFO
format_rocprof_output = True
format_rocprof_output = "csv"
monkeypatch.setattr("utils.utils.rocprof_cmd", "rocprofiler-sdk")
profiler_options_sdk_hip = {
"APP_CMD": "my_app",
"ROCPROF_HIP_RUNTIME_API_TRACE": "1",
"ROCPROF_COUNTER_COLLECTION": "1",
"ROCP_TOOL_LIBRARIES": "/opt/rocm/lib/rocprofiler-sdk/"
"librocprofiler-sdk-tool.so",
}
@@ -3123,44 +3125,6 @@ def test_run_prof_v3_sdk_and_cli_calls_trace_processing(tmp_path, monkeypatch):
# =============================================================================
def test_process_rocprofv3_output_json_format(tmp_path, monkeypatch):
"""
Test process_rocprofv3_output with json format converts JSON files to CSV.
Args:
tmp_path (Path): Temporary directory for test files.
monkeypatch (pytest.MonkeyPatch): Pytest fixture for patching.
Returns:
None: Asserts CSV files are created from JSON files.
"""
workload_dir = str(tmp_path)
output_dir = tmp_path / "out" / "pmc_1" / "subdir"
output_dir.mkdir(parents=True)
json_file1 = output_dir / "test1.json"
json_file2 = output_dir / "test2.json"
json_file1.write_text('{"test": "data1"}')
json_file2.write_text('{"test": "data2"}')
monkeypatch.setattr("glob.glob", lambda pattern: [str(json_file1), str(json_file2)])
def mock_v3_json_to_csv(json_path, csv_path):
Path(csv_path).write_text("csv,data\ntest,value")
monkeypatch.setattr("utils.utils.v3_json_to_csv", mock_v3_json_to_csv)
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("json", workload_dir, False)
assert len(result) == 2
csv_file1 = output_dir / "test1.csv"
csv_file2 = output_dir / "test2.csv"
assert csv_file1.exists()
assert csv_file2.exists()
def test_process_rocprofv3_output_csv_format_with_counter_files(tmp_path, monkeypatch):
"""
Test process_rocprofv3_output with csv format processes counter collection files.
@@ -3201,7 +3165,7 @@ def test_process_rocprofv3_output_csv_format_with_counter_files(tmp_path, monkey
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("csv", workload_dir, False)
result = utils_mod.process_rocprofv3_output(workload_dir, False)
assert len(result) == 1
assert str(converted_file) in result
@@ -3247,7 +3211,7 @@ def test_process_rocprofv3_output_csv_format_conversion_error(tmp_path, monkeypa
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("csv", workload_dir, False)
result = utils_mod.process_rocprofv3_output(workload_dir, False)
assert result == []
assert len(warnings) == 1
@@ -3282,42 +3246,7 @@ def test_process_rocprofv3_output_csv_format_missing_agent_file(tmp_path, monkey
import utils.utils as utils_mod
with pytest.raises(ValueError, match='has no corresponding "agent info" file'):
utils_mod.process_rocprofv3_output("csv", workload_dir, False)
def test_process_rocprofv3_output_csv_format_timestamps_fallback(tmp_path, monkeypatch):
"""
Test process_rocprofv3_output falls back to kernel trace files for timestamps.
Args:
tmp_path (Path): Temporary directory for test files.
monkeypatch (pytest.MonkeyPatch): Pytest fixture for patching.
Returns:
None: Asserts kernel trace files are used when is_timestamps is True.
"""
workload_dir = str(tmp_path)
output_dir = tmp_path / "out" / "pmc_1" / "subdir"
output_dir.mkdir(parents=True)
trace_file = output_dir / "test_kernel_trace.csv"
trace_file.write_text("kernel,trace\ntest,data")
def mock_glob(pattern):
if "_counter_collection.csv" in pattern:
return []
elif "_kernel_trace.csv" in pattern:
return [str(trace_file)]
return []
monkeypatch.setattr("glob.glob", mock_glob)
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("csv", workload_dir, True)
assert len(result) == 1
assert str(trace_file) in result
utils_mod.process_rocprofv3_output(workload_dir, False)
def test_process_rocprofv3_output_csv_format_no_files_non_timestamps(
@@ -3340,53 +3269,7 @@ def test_process_rocprofv3_output_csv_format_no_files_non_timestamps(
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("csv", workload_dir, False)
assert result == []
def test_process_rocprofv3_output_invalid_format(monkeypatch):
"""
Test process_rocprofv3_output raises error for invalid output format.
Args:
monkeypatch (pytest.MonkeyPatch): Pytest fixture for patching.
Returns:
None: Asserts console_error is called for invalid format.
"""
def mock_console_error(msg):
raise RuntimeError(f"console_error: {msg}")
monkeypatch.setattr("utils.utils.console_error", mock_console_error)
import utils.utils as utils_mod
with pytest.raises(
RuntimeError, match="The output file of rocprofv3 can only support json or csv"
):
utils_mod.process_rocprofv3_output("invalid", "/tmp", False)
def test_process_rocprofv3_output_json_format_no_files(tmp_path, monkeypatch):
"""
Test process_rocprofv3_output with json format when no JSON files exist.
Args:
tmp_path (Path): Temporary directory for test files.
monkeypatch (pytest.MonkeyPatch): Pytest fixture for patching.
Returns:
None: Asserts empty list returned when no JSON files found.
"""
workload_dir = str(tmp_path)
monkeypatch.setattr("glob.glob", lambda pattern: [])
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("json", workload_dir, False)
result = utils_mod.process_rocprofv3_output(workload_dir, False)
assert result == []
@@ -3439,7 +3322,7 @@ def test_process_rocprofv3_output_csv_format_multiple_counter_files(
import utils.utils as utils_mod
result = utils_mod.process_rocprofv3_output("csv", workload_dir, False)
result = utils_mod.process_rocprofv3_output(workload_dir, False)
assert len(result) == 2
assert str(converted_file1) in result
@@ -8180,8 +8063,8 @@ def test_add_counter_overwrite_existing():
# additional test detect_rocprof console error
# =============================================================================
class MockArgs:
def __init__(self, rocprofiler_sdk_library_path):
self.rocprofiler_sdk_library_path = rocprofiler_sdk_library_path
def __init__(self, rocprofiler_sdk_tool_path):
self.rocprofiler_sdk_tool_path = rocprofiler_sdk_tool_path
@mock.patch.dict(os.environ, {"ROCPROF": "rocprofiler-sdk"}, clear=True)
@@ -8192,7 +8075,7 @@ def test_detect_rocprof_calls_console_error_if_sdk_path_invalid(
):
"""
Tests that detect_rocprof calls console_error when ROCPROF is 'rocprofiler-sdk'
and the rocprofiler_sdk_library_path does not exist.
and the rocprofiler_sdk_tool_path does not exist.
Focuses on the console_error call.
"""
mock_path_instance = mock.Mock()
@@ -8200,13 +8083,13 @@ def test_detect_rocprof_calls_console_error_if_sdk_path_invalid(
mock_path_constructor.return_value = mock_path_instance
fake_library_path = "/some/invalid/path/to/librocprofiler_sdk.so"
args = MockArgs(rocprofiler_sdk_library_path=fake_library_path)
args = MockArgs(rocprofiler_sdk_tool_path=fake_library_path)
with mock.patch("utils.utils.console_debug") as mock_console_debug: # noqa
utils.detect_rocprof(args)
expected_error_message = (
"Could not find rocprofiler-sdk library at " + fake_library_path
"Could not find rocprofiler-sdk tool at " + fake_library_path
)
mock_console_error_func.assert_called_once_with(expected_error_message)
@@ -8442,7 +8325,7 @@ def test_pc_sampling_prof_sdk_path_nonexistent_librocprofiler_sdk_tool(
mock_console_debug, mock_console_error, mock_capture_subprocess, tmp_path
):
"""
Edge Case: rocprofiler_sdk_library_path is valid, but librocprofiler-sdk-tool.so
Edge Case: rocprofiler_sdk_tool_path is valid, but librocprofiler-sdk-tool.so
is NOT found next to it (or in rocprofiler-sdk subdir).
This test primarily checks if the paths are constructed. The actual check for
file existence before `capture_subprocess_output` is not in the provided snippet,
@@ -8452,31 +8335,29 @@ def test_pc_sampling_prof_sdk_path_nonexistent_librocprofiler_sdk_tool(
method = "host_trap"
interval = 1000
workload_dir = str(tmp_path)
appcmd = "my_app --arg"
options = {"APP_CMD": "my_app --arg"}
sdk_lib_dir = tmp_path / "rocm_sdk" / "lib"
sdk_lib_dir.mkdir(parents=True, exist_ok=True)
rocprofiler_sdk_library_path = str(sdk_lib_dir / "librocprofiler_sdk.so")
Path(rocprofiler_sdk_library_path).touch()
rocprofiler_sdk_tool_path = str(sdk_lib_dir / "librocprofiler_sdk.so")
Path(rocprofiler_sdk_tool_path).touch()
expected_tool_path = str(
sdk_lib_dir / "rocprofiler-sdk" / "librocprofiler-sdk-tool.so"
)
options["LD_PRELOAD"] = expected_tool_path
mock_capture_subprocess.return_value = (True, "Success output")
utils.pc_sampling_prof(
method, interval, workload_dir, appcmd, rocprofiler_sdk_library_path
)
utils.pc_sampling_prof(options, method, interval, workload_dir)
assert mock_capture_subprocess.called
call_args = mock_capture_subprocess.call_args
called_env = call_args.kwargs.get("new_env", {})
assert "LD_PRELOAD" in called_env
ld_preload_paths = called_env["LD_PRELOAD"].split(":")
assert expected_tool_path in ld_preload_paths
assert rocprofiler_sdk_library_path in ld_preload_paths
assert called_env["LD_PRELOAD"] == expected_tool_path
mock_console_error.assert_not_called()
@@ -8495,14 +8376,12 @@ def test_pc_sampling_prof_subprocess_fails(
method = "stochastic"
interval = 5000
workload_dir = str(tmp_path)
appcmd = "another_app"
rocprofiler_sdk_library_path = "/some/path/librocprofiler_sdk.so"
options = ["another_app"]
rocprofiler_sdk_tool_path = "/some/path/librocprofiler_sdk.so" # noqa: F841
mock_capture_subprocess.return_value = (False, "Error output from subprocess")
utils.pc_sampling_prof(
method, interval, workload_dir, appcmd, rocprofiler_sdk_library_path
)
utils.pc_sampling_prof(options, method, interval, workload_dir)
mock_capture_subprocess.assert_called_once()
mock_console_error.assert_called_once_with("PC sampling failed.")
@@ -8510,10 +8389,11 @@ def test_pc_sampling_prof_subprocess_fails(
mock_capture_subprocess.reset_mock()
mock_console_error.reset_mock()
with mock.patch("utils.utils.rocprof_cmd", "rocprofiler-sdk"):
options = {"APP_CMD": "another_app"}
sdk_lib_dir = tmp_path / "rocm_sdk_fail" / "lib"
sdk_lib_dir.mkdir(parents=True, exist_ok=True)
rocprofiler_sdk_library_path_sdk = str(sdk_lib_dir / "librocprofiler_sdk.so")
Path(rocprofiler_sdk_library_path_sdk).touch()
rocprofiler_sdk_tool_path_sdk = str(sdk_lib_dir / "librocprofiler_sdk.so")
Path(rocprofiler_sdk_tool_path_sdk).touch()
tool_dir = sdk_lib_dir / "rocprofiler-sdk"
tool_dir.mkdir(parents=True, exist_ok=True)
@@ -8524,9 +8404,7 @@ def test_pc_sampling_prof_subprocess_fails(
"Error output from SDK subprocess",
)
utils.pc_sampling_prof(
method, interval, workload_dir, appcmd, rocprofiler_sdk_library_path_sdk
)
utils.pc_sampling_prof(options, method, interval, workload_dir)
mock_capture_subprocess.assert_called_once()
mock_console_error.assert_called_once_with("PC sampling failed.")
@@ -8547,14 +8425,12 @@ def test_pc_sampling_prof_empty_appcmd(
method = "host_trap"
interval = 100
workload_dir = str(tmp_path)
appcmd = ""
rocprofiler_sdk_library_path = "/some/path/librocprofiler_sdk.so"
options = ["--"]
rocprofiler_sdk_tool_path = "/some/path/librocprofiler_sdk.so" # noqa: F841
mock_capture_subprocess.return_value = (True, "Output with empty appcmd")
utils.pc_sampling_prof(
method, interval, workload_dir, appcmd, rocprofiler_sdk_library_path
)
utils.pc_sampling_prof(options, method, interval, workload_dir)
assert mock_capture_subprocess.called
options_list = mock_capture_subprocess.call_args[0][0]
@@ -8566,17 +8442,16 @@ def test_pc_sampling_prof_empty_appcmd(
with mock.patch("utils.utils.rocprof_cmd", "rocprofiler-sdk"):
sdk_lib_dir = tmp_path / "rocm_sdk_empty" / "lib"
sdk_lib_dir.mkdir(parents=True, exist_ok=True)
rocprofiler_sdk_library_path_sdk = str(sdk_lib_dir / "librocprofiler_sdk.so")
Path(rocprofiler_sdk_library_path_sdk).touch()
rocprofiler_sdk_tool_path_sdk = str(sdk_lib_dir / "librocprofiler_sdk.so")
Path(rocprofiler_sdk_tool_path_sdk).touch()
tool_dir = sdk_lib_dir / "rocprofiler-sdk"
tool_dir.mkdir(parents=True, exist_ok=True)
(tool_dir / "librocprofiler-sdk-tool.so").touch()
mock_capture_subprocess.return_value = (True, "Output with empty appcmd SDK")
options = {"APP_CMD": ""}
utils.pc_sampling_prof(
method, interval, workload_dir, appcmd, rocprofiler_sdk_library_path_sdk
)
utils.pc_sampling_prof(options, method, interval, workload_dir)
assert mock_capture_subprocess.called
assert mock_capture_subprocess.call_args[0][0] == ""