Adding rocprofilerv2
Change-Id: Ic0cc280ba207d2b8f6ccae1cd4ac3184152fc1ad
This commit is contained in:
@@ -0,0 +1 @@
|
||||
README.html
|
||||
@@ -0,0 +1,161 @@
|
||||
################################################################################
|
||||
## Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
##
|
||||
## 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.
|
||||
################################################################################
|
||||
|
||||
# Plugin shared object.
|
||||
add_library(ctf_plugin SHARED
|
||||
ctf.cpp
|
||||
plugin.cpp
|
||||
barectf.c "${CMAKE_CURRENT_BINARY_DIR}/barectf.h"
|
||||
${PROJECT_SOURCE_DIR}/src/utils/helper.cpp
|
||||
hsa_begin.cpp.i hsa_end.cpp.i
|
||||
hip_begin.cpp.i hip_end.cpp.i)
|
||||
set_target_properties(ctf_plugin PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../exportmap"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
|
||||
set(METADATA_STREAM_FILE_DIR "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/plugin/ctf")
|
||||
target_compile_definitions(ctf_plugin PRIVATE
|
||||
HIP_PROF_HIP_API_STRING=1
|
||||
__HIP_PLATFORM_HCC__=1
|
||||
CTF_PLUGIN_METADATA_FILE_PATH="${CMAKE_INSTALL_PREFIX}/${METADATA_STREAM_FILE_DIR}/metadata")
|
||||
target_include_directories(ctf_plugin PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/inc"
|
||||
"${PROJECT_SOURCE_DIR}"
|
||||
"${CMAKE_BINARY_DIR}/src/api"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}")
|
||||
target_link_options(ctf_plugin PRIVATE
|
||||
"-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/../exportmap"
|
||||
-Wl,--no-undefined)
|
||||
target_link_libraries(ctf_plugin PRIVATE
|
||||
${ROCPROFILER_TARGET}
|
||||
hsa-runtime64::hsa-runtime64
|
||||
systemd
|
||||
stdc++fs
|
||||
dl)
|
||||
install(TARGETS ctf_plugin LIBRARY
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}"
|
||||
COMPONENT runtime)
|
||||
|
||||
# `gen_api_files.py` and `gen_env_yaml.py` require Python 3,
|
||||
# CppHeaderParser, PyYAML, and barectf.
|
||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||
|
||||
message("Python: ${Python3_EXECUTABLE})")
|
||||
|
||||
execute_process(COMMAND Python3::Interpreter -c "print('hello')")
|
||||
|
||||
function(check_py3_pkg pkg_name)
|
||||
execute_process(COMMAND "${Python3_EXECUTABLE}" -c "import ${pkg_name}"
|
||||
RESULT_VARIABLE PY3_IMPORT_RES
|
||||
OUTPUT_QUIET)
|
||||
|
||||
if(NOT (${PY3_IMPORT_RES} EQUAL 0))
|
||||
message(FATAL_ERROR "Cannot find Python 3 package `${pkg_name}`")
|
||||
endif()
|
||||
|
||||
message(STATUS "Found Python 3 package `${pkg_name}`")
|
||||
endfunction()
|
||||
|
||||
check_py3_pkg(CppHeaderParser)
|
||||
check_py3_pkg(yaml)
|
||||
find_program(BARECTF_RES barectf REQUIRED)
|
||||
|
||||
# Generate barectf YAML and C++ files for HSA API.
|
||||
get_property(HSA_RUNTIME_INCLUDE_DIRS
|
||||
TARGET hsa-runtime64::hsa-runtime64
|
||||
PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
|
||||
find_file(HSA_H hsa.h
|
||||
PATHS ${HSA_RUNTIME_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES hsa
|
||||
NO_DEFAULT_PATH
|
||||
REQUIRED)
|
||||
get_filename_component(HSA_RUNTIME_INC_PATH "${HSA_H}" DIRECTORY)
|
||||
add_custom_command(
|
||||
OUTPUT hsa_erts.yaml hsa_begin.cpp.i hsa_end.cpp.i
|
||||
COMMAND ${CMAKE_C_COMPILER} -E "${HSA_RUNTIME_INC_PATH}/hsa.h" -o hsa.h.i
|
||||
COMMAND ${CMAKE_C_COMPILER} -E "${HSA_RUNTIME_INC_PATH}/hsa_ext_amd.h"
|
||||
-o hsa_ext_amd.h.i
|
||||
COMMAND ${CMAKE_COMMAND} -E cat hsa.h.i
|
||||
hsa_ext_amd.h.i
|
||||
"${CMAKE_BINARY_DIR}/src/api/hsa_prof_str.h"
|
||||
> hsa_input.h
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/gen_api_files.py"
|
||||
hsa hsa_input.h
|
||||
BYPRODUCTS hsa.h.i hsa_ext_amd.h.i hsa_input.h
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/gen_api_files.py"
|
||||
"${HSA_RUNTIME_INC_PATH}/hsa.h"
|
||||
"${HSA_RUNTIME_INC_PATH}/hsa_ext_amd.h"
|
||||
"${CMAKE_BINARY_DIR}/src/api/hsa_prof_str.h"
|
||||
COMMENT "Generating HSA API files for the `ctf` plugin...")
|
||||
|
||||
# Generate barectf YAML and C++ files for HIP API.
|
||||
get_property(HIP_INCLUDE_DIRS TARGET hip::amdhip64
|
||||
PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
|
||||
find_file(HIP_RUNTIME_API_H hip_runtime_api.h
|
||||
PATHS ${HIP_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES hip
|
||||
NO_DEFAULT_PATH
|
||||
REQUIRED)
|
||||
find_file(HIP_PROF_STR_H hip_prof_str.h
|
||||
PATHS ${HIP_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES hip hip/amd_detail
|
||||
NO_DEFAULT_PATH
|
||||
REQUIRED)
|
||||
list(TRANSFORM HIP_INCLUDE_DIRS PREPEND -I)
|
||||
add_custom_command(
|
||||
OUTPUT hip_erts.yaml hip_begin.cpp.i hip_end.cpp.i
|
||||
COMMAND ${CMAKE_C_COMPILER} ${HIP_INCLUDE_DIRS}
|
||||
-E "${HIP_RUNTIME_API_H}"
|
||||
-D__HIP_PLATFORM_HCC__=1
|
||||
-D__HIP_ROCclr__=1
|
||||
-o hip_runtime_api.h.i
|
||||
COMMAND cat hip_runtime_api.h.i "${HIP_PROF_STR_H}" > hip_input.h
|
||||
BYPRODUCTS hip_runtime_api.h.i hip_input.h
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/gen_api_files.py"
|
||||
hip hip_input.h
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/gen_api_files.py"
|
||||
"${HIP_RUNTIME_API_H}"
|
||||
"${HIP_PROF_STR_H}"
|
||||
COMMENT "Generating HIP API files for the `ctf` plugin...")
|
||||
|
||||
# Generate `env.yaml` (trace environment for barectf).
|
||||
add_custom_command(
|
||||
OUTPUT env.yaml
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/gen_env_yaml.py"
|
||||
${PROJECT_VERSION}
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/gen_env_yaml.py"
|
||||
COMMENT "Generating `env.yaml`...")
|
||||
|
||||
# Generate raw CTF tracer with barectf.
|
||||
add_custom_command(
|
||||
OUTPUT barectf.c barectf.h barectf-bitfield.h metadata
|
||||
COMMAND "${BARECTF_RES}" gen "-I${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"-I${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/config.yaml"
|
||||
DEPENDS hsa_erts.yaml
|
||||
hip_erts.yaml
|
||||
env.yaml
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/config.yaml"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/dst_base.yaml"
|
||||
COMMENT "Generating raw CTF tracer with barectf...")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/metadata"
|
||||
DESTINATION "${METADATA_STREAM_FILE_DIR}")
|
||||
@@ -0,0 +1,260 @@
|
||||
= CTF plugin for ROCMTools
|
||||
13 December 2022
|
||||
Philippe Proulx
|
||||
|
||||
This plugin writes the received ROCMTools tracer and profiler records to
|
||||
a https://diamon.org/ctf/[CTF] trace.
|
||||
|
||||
== Build requirements
|
||||
|
||||
* Python ≥ 3.10
|
||||
* barectf ≥ 3.1.1 (`pip3 install barectf`)
|
||||
* PyYAML (`apt-get install python3-yaml`)
|
||||
* CppHeaderParser (`pip3 install CppHeaderParser`)
|
||||
|
||||
== Usage
|
||||
|
||||
Once installed, you may load this plugin with `rocprofv2` using
|
||||
the `--plugin ctf` command-line arguments.
|
||||
|
||||
This plugin honours the `OUTPUT_PATH` environment variable which
|
||||
`rocprofv2` sets with the `-d` option. If you pass `-d my-dir` to
|
||||
`rocprofv2`, then the plugin will write the CTF trace to the
|
||||
`my-dir/trace` directory.
|
||||
|
||||
IMPORTANT: This plugin performs important cleanup tasks at finalization
|
||||
time, so the resulting CTF trace could be corrupted if the plugin is
|
||||
never finalized.
|
||||
|
||||
Once the plugin is finalized, open the resulting trace directory with
|
||||
either https://babeltrace.org/[Babeltrace{nbsp}2] or
|
||||
https://www.eclipse.org/tracecompass/[Trace Compass] to view or analyze
|
||||
it.
|
||||
|
||||
=== Event record types
|
||||
|
||||
This plugin writes to different CTF data streams having different types.
|
||||
On the file system, the prefix of a data stream file name indicates the
|
||||
data stream type, that is:
|
||||
|
||||
`roctx_`::
|
||||
rocTX messages.
|
||||
+
|
||||
Each CTF event record is named `roctx` and corresponds to a rocTX
|
||||
tracer record.
|
||||
+
|
||||
The fields are:
|
||||
+
|
||||
--
|
||||
[horizontal]
|
||||
`thread_id`::
|
||||
Thread ID.
|
||||
|
||||
`id`::
|
||||
rocTX ID.
|
||||
|
||||
`msg`::
|
||||
rocTX message.
|
||||
--
|
||||
|
||||
`hsa_api_`::
|
||||
HSA API beginning and end function calls.
|
||||
+
|
||||
All CTF event records have the following common fields:
|
||||
+
|
||||
--
|
||||
[horizontal]
|
||||
`thread_id`::
|
||||
Thread ID.
|
||||
|
||||
`queue_id`::
|
||||
Queue ID.
|
||||
|
||||
`agent_id`::
|
||||
Agent ID.
|
||||
|
||||
`correlation_id`::
|
||||
Correlation ID.
|
||||
--
|
||||
+
|
||||
For each ROCMTools HSA API tracer record for the HSA function named
|
||||
`__name__`, this plugin writes two event records:
|
||||
+
|
||||
`__name___begin`:::
|
||||
Beginning of the function call.
|
||||
+
|
||||
The event record contains fields which correspond to most of the
|
||||
parameters of the HSA function.
|
||||
|
||||
`__name___end`:::
|
||||
End of the function call.
|
||||
|
||||
`hip_api_`::
|
||||
HIP API beginning and end function calls.
|
||||
+
|
||||
All CTF event records have the following common fields:
|
||||
+
|
||||
--
|
||||
[horizontal]
|
||||
`thread_id`::
|
||||
Thread ID.
|
||||
|
||||
`queue_id`::
|
||||
Queue ID.
|
||||
|
||||
`agent_id`::
|
||||
Agent ID.
|
||||
|
||||
`correlation_id`::
|
||||
Correlation ID.
|
||||
|
||||
`kernel_name`::
|
||||
Kernel name (empty string if not available).
|
||||
--
|
||||
+
|
||||
For each ROCMTools HIP API tracer record for the HIP function named
|
||||
`__name__`, this plugin writes two event records:
|
||||
+
|
||||
`__name__Begin`:::
|
||||
Beginning of the function call.
|
||||
+
|
||||
The event record contains fields which correspond to most of the
|
||||
parameters of the HIP function.
|
||||
|
||||
`__name__End`:::
|
||||
End of the function call.
|
||||
|
||||
`api_ops_`::
|
||||
HSA/HIP API beginning and end operations.
|
||||
+
|
||||
All CTF event records have the following common fields:
|
||||
+
|
||||
--
|
||||
[horizontal]
|
||||
`thread_id`::
|
||||
Thread ID.
|
||||
|
||||
`queue_id`::
|
||||
Queue ID.
|
||||
|
||||
`agent_id`::
|
||||
Agent ID.
|
||||
|
||||
`correlation_id`::
|
||||
Correlation ID.
|
||||
--
|
||||
+
|
||||
The possible CTF event records are:
|
||||
+
|
||||
`hsa_op_begin`:::
|
||||
HSA API operation beginning.
|
||||
|
||||
`hsa_op_end`:::
|
||||
HSA API operation end.
|
||||
|
||||
`hip_op_begin`:::
|
||||
HIP API operation beginning.
|
||||
+
|
||||
Such an event record also has the field `kernel_name` which is the
|
||||
kernel name (empty string if not available).
|
||||
|
||||
`hip_op_end`:::
|
||||
HIP API operation end.
|
||||
|
||||
`profiler_`::
|
||||
Profiler records.
|
||||
+
|
||||
All CTF event records have the following common fields:
|
||||
+
|
||||
--
|
||||
[horizontal]
|
||||
`dispatch`::
|
||||
Dispatch ID.
|
||||
|
||||
`gpu_id`::
|
||||
GPU ID.
|
||||
|
||||
`queue_id`::
|
||||
Queue ID.
|
||||
|
||||
`queue_index`::
|
||||
Queue index.
|
||||
|
||||
`process_id`::
|
||||
Process ID.
|
||||
|
||||
`thread_id`::
|
||||
Thread ID.
|
||||
|
||||
`kernel_id`::
|
||||
Kernel ID.
|
||||
|
||||
`kernel_name`::
|
||||
Kernel name (empty string if not available).
|
||||
|
||||
`counter_names`::
|
||||
Array of counter names, each one having a corresponding integral
|
||||
value in the `counter_values` field.
|
||||
|
||||
`counter_values`::
|
||||
Array of integers, each one being the value of a counter of which
|
||||
the name is a corresponding string in the `counter_names` field.
|
||||
--
|
||||
+
|
||||
The possible CTF event records are:
|
||||
+
|
||||
`profiler_record`:::
|
||||
Profiler record.
|
||||
|
||||
`profiler_record_with_kernel_properties`:::
|
||||
Profiler record with kernel properties.
|
||||
+
|
||||
Such an event record also has the following fields:
|
||||
+
|
||||
--
|
||||
`grid_size`::
|
||||
Grid size.
|
||||
|
||||
`workgroup_size`::
|
||||
Workgroup size.
|
||||
|
||||
`lds_size`::
|
||||
Local memory size.
|
||||
|
||||
`scratch_size`::
|
||||
Scratch size.
|
||||
|
||||
`arch_vgpr_count`::
|
||||
Architecture vector general purpose register count.
|
||||
|
||||
`accum_vgpr_count`::
|
||||
Accum. vector general purpose register count
|
||||
|
||||
`sgpr_count`::
|
||||
Scalar general purpose register count.
|
||||
|
||||
`wave_size`::
|
||||
Wavefront size.
|
||||
|
||||
`signal_handle`::
|
||||
Signal handle.
|
||||
--
|
||||
|
||||
`hsa_handles_`::
|
||||
HSA handle type mappings.
|
||||
+
|
||||
Each CTF event record is named `hsa_handle_type` and maps an HSA handle
|
||||
to a processor unit type (CPU or GPU).
|
||||
+
|
||||
The clock value of those event records is irrelevant (always{nbsp}0).
|
||||
+
|
||||
The fields are:
|
||||
+
|
||||
--
|
||||
[horizontal]
|
||||
`handle`::
|
||||
HSA handle.
|
||||
|
||||
`type`::
|
||||
Processor unit type (`CPU` or `GPU` enumeration label).
|
||||
--
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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. */
|
||||
|
||||
#ifndef PLUGIN_CTF_BARECTF_EVENT_RECORD_H
|
||||
#define PLUGIN_CTF_BARECTF_EVENT_RECORD_H
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
struct barectf_default_ctx;
|
||||
|
||||
namespace rocm_ctf {
|
||||
|
||||
// Abstract base class of any barectf event record.
|
||||
//
|
||||
// A concrete event record class must implement Write() which must call
|
||||
// a corresponding barectf tracing function.
|
||||
//
|
||||
// `CtxT` is the specific type of the barectf context which Write()
|
||||
// receives.
|
||||
template <typename CtxT> class BarectfEventRecord {
|
||||
protected:
|
||||
// Builds a barectf event record having the clock value `clock_val`.
|
||||
explicit BarectfEventRecord(const std::uint64_t clock_val) noexcept : clock_val_{clock_val} {}
|
||||
|
||||
public:
|
||||
// Shared pointer to const barectf event record.
|
||||
using SP = std::shared_ptr<const BarectfEventRecord>;
|
||||
|
||||
virtual ~BarectfEventRecord() = default;
|
||||
|
||||
// Disabled copy operations to make this class simpler.
|
||||
BarectfEventRecord(const BarectfEventRecord&) = delete;
|
||||
BarectfEventRecord& operator=(const BarectfEventRecord&) = delete;
|
||||
|
||||
// Clock value of this event record.
|
||||
std::uint64_t GetClockVal() const noexcept { return clock_val_; }
|
||||
|
||||
// Calls a corresponding barectf tracing function using the barectf
|
||||
// context `barectf_ctx`.
|
||||
virtual void Write(CtxT& barectf_ctx) const = 0;
|
||||
|
||||
private:
|
||||
// Clock value.
|
||||
std::uint64_t clock_val_;
|
||||
};
|
||||
|
||||
} // namespace rocm_ctf
|
||||
|
||||
#endif // PLUGIN_CTF_BARECTF_EVENT_RECORD_H
|
||||
@@ -0,0 +1,192 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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. */
|
||||
|
||||
#ifndef PLUGIN_CTF_BARECTF_PLATFORM_H
|
||||
#define PLUGIN_CTF_BARECTF_PLATFORM_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
#include "barectf.h"
|
||||
|
||||
namespace rocm_ctf {
|
||||
|
||||
template <typename> class BarectfWriter;
|
||||
|
||||
// A barectf platform for any barectf writer.
|
||||
//
|
||||
// The user doesn't deal directly with such an object: it's closely
|
||||
// coupled with a barectf writer.
|
||||
//
|
||||
// Each platform takes care of a single CTF data stream file.
|
||||
//
|
||||
// After building such a platform, get the raw barectf context with
|
||||
// GetCtx() to call tracing functions. The platform must still exist
|
||||
// when calling a tracing function.
|
||||
//
|
||||
// Such a platform opens the data stream file on construction and closes
|
||||
// it on destruction.
|
||||
//
|
||||
// `DescrT` is the specific barectf platform descriptor. It must be a
|
||||
// structure having:
|
||||
//
|
||||
// `Ctx`:
|
||||
// Specific barectf context type.
|
||||
//
|
||||
// `static void OpenPacket(Ctx&)`:
|
||||
// Packet opening function.
|
||||
//
|
||||
// `static void ClosePacket(Ctx&)`:
|
||||
// Packet closing function.
|
||||
template <typename DescrT> class BarectfPlatform final {
|
||||
friend class BarectfWriter<DescrT>;
|
||||
|
||||
private:
|
||||
// Builds a barectf platform.
|
||||
//
|
||||
// The platform writes CTF packets of size `packet_size` bytes to the
|
||||
// CTF data stream file `data_stream_file_path`.
|
||||
//
|
||||
// For each event record to write, the platform reads `clock_val` to
|
||||
// know the current timestamp.
|
||||
explicit BarectfPlatform(const std::size_t packet_size,
|
||||
const std::experimental::filesystem::path& data_stream_file_path,
|
||||
const std::uint64_t& clock_val)
|
||||
: clock_val_{&clock_val}, buffer_(packet_size) {
|
||||
// Initialize barectf callbacks.
|
||||
barectf_platform_callbacks callbacks;
|
||||
|
||||
callbacks.default_clock_get_value = GetClockCb;
|
||||
callbacks.is_backend_full = IsBackendFullCb;
|
||||
callbacks.open_packet = OpenPacketCb;
|
||||
callbacks.close_packet = ClosePacketCb;
|
||||
|
||||
// Configure exceptions so that stream operations throw instead of
|
||||
// just setting flags on error.
|
||||
output_.exceptions(std::ofstream::failbit | std::ofstream::badbit);
|
||||
|
||||
// Open CTF data stream output file in binary mode.
|
||||
output_.open(data_stream_file_path, std::ios_base::out | std::ios_base::binary);
|
||||
|
||||
// Initialize the raw barectf context.
|
||||
barectf_init(&ctx_, buffer_.data(), buffer_.size(), callbacks, this);
|
||||
|
||||
// Open the initial packet.
|
||||
OpenPacketCb();
|
||||
}
|
||||
|
||||
public:
|
||||
// Disabled copy operations to make this class simpler.
|
||||
BarectfPlatform(const BarectfPlatform&) = delete;
|
||||
BarectfPlatform& operator=(const BarectfPlatform&) = delete;
|
||||
|
||||
// Closes/writes any last CTF packet and closes the data stream file.
|
||||
~BarectfPlatform() {
|
||||
if (barectf_packet_is_open(&ctx_) && !barectf_packet_is_empty(&ctx_)) {
|
||||
// Close and write last CTF packet (not empty).
|
||||
ClosePacketCb();
|
||||
}
|
||||
|
||||
// Close data stream output file.
|
||||
output_.close();
|
||||
}
|
||||
|
||||
// Returns the raw barectf context of this platform.
|
||||
const typename DescrT::Ctx& GetCtx() const noexcept { return ctx_; }
|
||||
typename DescrT::Ctx& GetCtx() noexcept { return ctx_; }
|
||||
|
||||
private:
|
||||
static BarectfPlatform& AsPlatform(void* const data) noexcept {
|
||||
return *static_cast<BarectfPlatform*>(data);
|
||||
}
|
||||
|
||||
// Four callbacks for barectf.
|
||||
//
|
||||
// Those four functions receive an instance of this class as `data`.
|
||||
|
||||
static std::uint64_t GetClockCb(void* const data) noexcept {
|
||||
// Forward to instance method.
|
||||
return AsPlatform(data).GetClockCb();
|
||||
}
|
||||
|
||||
static int IsBackendFullCb(void* const data) noexcept {
|
||||
// Forward to instance method.
|
||||
return AsPlatform(data).IsBackendFullCb();
|
||||
}
|
||||
|
||||
static void OpenPacketCb(void* const data) {
|
||||
// Forward to instance method.
|
||||
AsPlatform(data).OpenPacketCb();
|
||||
}
|
||||
|
||||
static void ClosePacketCb(void* const data) {
|
||||
// Forward to instance method.
|
||||
AsPlatform(data).ClosePacketCb();
|
||||
}
|
||||
|
||||
// Instance version of the "get clock value" callback.
|
||||
std::uint64_t GetClockCb() noexcept { return *clock_val_; }
|
||||
|
||||
// Instance version of the "is the back end full?" callback.
|
||||
int IsBackendFullCb() noexcept {
|
||||
// Never full.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Instance version of the "open packet" callback.
|
||||
void OpenPacketCb() {
|
||||
// Forward to user (descriptor) function.
|
||||
DescrT::OpenPacket(ctx_);
|
||||
}
|
||||
|
||||
// Instance version of the "close packet" callback.
|
||||
void ClosePacketCb() {
|
||||
// Forward to user (descriptor) function to finalize the packet.
|
||||
DescrT::ClosePacket(ctx_);
|
||||
|
||||
// Write to the data stream file.
|
||||
WriteCurrentPacket();
|
||||
}
|
||||
|
||||
// Writes the current CTF packet (`buffer_`) to the data stream file.
|
||||
void WriteCurrentPacket() {
|
||||
output_.write(reinterpret_cast<const char*>(buffer_.data()), buffer_.size());
|
||||
}
|
||||
|
||||
// Clock value pointer.
|
||||
const std::uint64_t* clock_val_;
|
||||
|
||||
// CTF data stream output file stream.
|
||||
std::ofstream output_;
|
||||
|
||||
// Raw barectf context.
|
||||
typename DescrT::Ctx ctx_;
|
||||
|
||||
// CTF packet buffer.
|
||||
std::vector<std::uint8_t> buffer_;
|
||||
};
|
||||
|
||||
} // namespace rocm_ctf
|
||||
|
||||
#endif // PLUGIN_CTF_BARECTF_PLATFORM_H
|
||||
@@ -0,0 +1,124 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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. */
|
||||
|
||||
#ifndef PLUGIN_CTF_BARECTF_TRACER_H
|
||||
#define PLUGIN_CTF_BARECTF_TRACER_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
#include "barectf_event_record.h"
|
||||
#include "barectf_writer.h"
|
||||
|
||||
namespace rocm_ctf {
|
||||
|
||||
// A barectf tracer offers the AddEventRecord() method to add an event
|
||||
// record which it will ultimately write to some CTF data stream file
|
||||
// within some specified CTF trace directory.
|
||||
//
|
||||
// One important feature of such a tracer is that you don't need to add
|
||||
// event records in order of time. A barectf tracer manages one or more
|
||||
// barectf writers, each one managing a single barectf platform/context
|
||||
// (CTF data stream file).
|
||||
//
|
||||
// All the CTF data stream files which a barectf tracer indirectly
|
||||
// manages share a common specified prefix. You must not use the same
|
||||
// prefix for two barectf tracers writing to the same CTF trace
|
||||
// directory.
|
||||
//
|
||||
// `PlatformDescrT` is the specific barectf platform descriptor (see the
|
||||
// documentation of the `BarectfPlatform` class template).
|
||||
template <typename PlatformDescrT> class BarectfTracer final {
|
||||
public:
|
||||
// Specific barectf event record type.
|
||||
using EventRecord = typename BarectfWriter<PlatformDescrT>::EventRecord;
|
||||
|
||||
// Builds a barectf tracer to write CTF packets of size `packet_size`
|
||||
// bytes to CTF data stream files having the prefix
|
||||
// `data_stream_file_name_prefix` within the CTF trace directory
|
||||
// `trace_dir`.
|
||||
//
|
||||
// The internal barectf writers manage event record queues having a
|
||||
// maximum size of `max_writer_queue_size`. Increasing
|
||||
// `max_writer_queue_size` increases the memory footprint of the
|
||||
// tracer, but may reduce the number of required CTF data stream files
|
||||
// to ensure time-ordered event records.
|
||||
explicit BarectfTracer(const std::size_t packet_size,
|
||||
std::experimental::filesystem::path trace_dir,
|
||||
const char* const data_stream_file_name_prefix,
|
||||
const std::size_t max_writer_queue_size = 200)
|
||||
: packet_size_{packet_size},
|
||||
trace_dir_{std::move(trace_dir)},
|
||||
data_stream_file_name_prefix_{data_stream_file_name_prefix},
|
||||
max_writer_queue_size_{max_writer_queue_size} {}
|
||||
|
||||
// Disabled copy operations to make this class simpler.
|
||||
BarectfTracer(const BarectfTracer&) = delete;
|
||||
BarectfTracer& operator=(const BarectfTracer&) = delete;
|
||||
|
||||
// Adds the event record `event_record` to this tracer.
|
||||
//
|
||||
// The clock value of `event_record` may be less than the clock value
|
||||
// of previously added event records.
|
||||
void AddEventRecord(typename EventRecord::SP event_record) {
|
||||
// Try to find a barectf writer to accept `event_record`.
|
||||
for (auto& writer : writers_) {
|
||||
if (writer->MayAddEventRecord(*event_record)) {
|
||||
// Found: add the event record to this writer and return.
|
||||
writer->AddEventRecord(std::move(event_record));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No barectf writer found: create a new one.
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << data_stream_file_name_prefix_ << writers_.size();
|
||||
writers_.emplace_back(new BarectfWriter<PlatformDescrT>{packet_size_, trace_dir_ / ss.str(),
|
||||
max_writer_queue_size_});
|
||||
|
||||
// Add the event record to this new barectf writer.
|
||||
assert(writers_.back()->MayAddEventRecord(*event_record));
|
||||
writers_.back()->AddEventRecord(std::move(event_record));
|
||||
}
|
||||
|
||||
private:
|
||||
// CTF packet size.
|
||||
std::size_t packet_size_;
|
||||
|
||||
// CTF trace directory.
|
||||
std::experimental::filesystem::path trace_dir_;
|
||||
|
||||
// CTF data stream file name prefix.
|
||||
std::string data_stream_file_name_prefix_;
|
||||
|
||||
// Maximum event record queue size of a barectf writer.
|
||||
std::size_t max_writer_queue_size_;
|
||||
|
||||
// barectf writers.
|
||||
std::vector<std::unique_ptr<BarectfWriter<PlatformDescrT>>> writers_;
|
||||
};
|
||||
|
||||
} // namespace rocm_ctf
|
||||
|
||||
#endif // PLUGIN_CTF_BARECTF_TRACER_H
|
||||
@@ -0,0 +1,178 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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. */
|
||||
|
||||
#ifndef PLUGIN_CTF_BARECTF_WRITER_H
|
||||
#define PLUGIN_CTF_BARECTF_WRITER_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
#include "barectf_platform.h"
|
||||
#include "barectf_event_record.h"
|
||||
|
||||
namespace rocm_ctf {
|
||||
|
||||
template <typename> class BarectfTracer;
|
||||
|
||||
// A barectf writer manages a queue of event records, writing them
|
||||
// through barectf when needed.
|
||||
//
|
||||
// Such an object makes it possible to add some event record with a
|
||||
// clock value V and then some other event record of which the clock
|
||||
// value is less than V. The barectf writer ensures that actual barectf
|
||||
// tracing functions are called chronologically, a requirement of CTF.
|
||||
//
|
||||
// A barectf writer keeps event records in memory until its queue is
|
||||
// full (you provide the maximum queue size at construction time), in
|
||||
// which case it writes the oldest event record to some current CTF
|
||||
// packet through a barectf tracing function.
|
||||
//
|
||||
// Call MayAddEventRecord() to check whether or not you may add an event
|
||||
// record to the barectf writer, and then AddEventRecord() if you may.
|
||||
//
|
||||
// A barectf writer writes all its remaining event records on
|
||||
// destruction.
|
||||
//
|
||||
// `PlatformDescrT` is the specific barectf platform descriptor (see the
|
||||
// documentation of the `BarectfPlatform` class template).
|
||||
template <typename PlatformDescrT> class BarectfWriter final {
|
||||
friend class BarectfTracer<PlatformDescrT>;
|
||||
|
||||
public:
|
||||
// Specific barectf event record type.
|
||||
using EventRecord = BarectfEventRecord<typename PlatformDescrT::Ctx>;
|
||||
|
||||
private:
|
||||
// Builds a barectf writer to write CTF packets of size `packet_size`
|
||||
// bytes to the CTF data stream file `data_stream_file_path`.
|
||||
//
|
||||
// The built barectf writer manages an event record queue having a
|
||||
// maximum size of `max_queue_size`.
|
||||
explicit BarectfWriter(const std::size_t packet_size,
|
||||
const std::experimental::filesystem::path& data_stream_file_path,
|
||||
const std::size_t max_queue_size)
|
||||
: platform_{packet_size, data_stream_file_path, clock_val_},
|
||||
max_queue_size_{max_queue_size} {}
|
||||
|
||||
public:
|
||||
// Writes all its remaining event records.
|
||||
~BarectfWriter() {
|
||||
// Write all the remaining event records from the oldest to the
|
||||
// newest.
|
||||
while (!queue_.empty()) {
|
||||
WriteOldestEventRecord();
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled copy operations to make this class simpler.
|
||||
BarectfWriter(const BarectfWriter&) = delete;
|
||||
BarectfWriter& operator=(const BarectfWriter&) = delete;
|
||||
|
||||
// Whether or not you may add the event record `event_record` to this
|
||||
// writer with AddEventRecord().
|
||||
bool MayAddEventRecord(const EventRecord& event_record) const noexcept {
|
||||
if (queue_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// One may only add an event record if its clock value is greater
|
||||
// than or equal to the clock value of the most recently written
|
||||
// event record.
|
||||
return event_record.GetClockVal() >= clock_val_;
|
||||
}
|
||||
|
||||
// Adds the event record `event_record` to this writer.
|
||||
//
|
||||
// `MayAddEventRecord(*event_record)` must return `true`.
|
||||
void AddEventRecord(typename EventRecord::SP event_record) {
|
||||
assert(MayAddEventRecord(*event_record) && "May add event record");
|
||||
|
||||
// Add event record to queue.
|
||||
queue_.emplace(std::move(event_record));
|
||||
|
||||
if (queue_.size() > max_queue_size_) {
|
||||
// Queue is too large: write the oldest event record now to
|
||||
// satisfy the requirement.
|
||||
WriteOldestEventRecord();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Comparison type for `queue_`.
|
||||
struct EventRecordQueueCompare final {
|
||||
bool operator()(const typename EventRecord::SP& left,
|
||||
const typename EventRecord::SP& right) const noexcept {
|
||||
// "Greater than" so that the top element of the queue is the
|
||||
// oldest event record.
|
||||
return left->GetClockVal() > right->GetClockVal();
|
||||
}
|
||||
};
|
||||
|
||||
// Oldest event record within `queue_`.
|
||||
//
|
||||
// `queue_` must not be empty.
|
||||
const EventRecord& GetOldestEventRecord() const noexcept {
|
||||
assert(!queue_.empty() && "Queue isn't empty");
|
||||
return *queue_.top();
|
||||
}
|
||||
|
||||
// Writes the oldest event record through a barectf tracing function
|
||||
// and removes it from the event record queue.
|
||||
void WriteOldestEventRecord() {
|
||||
auto& oldest_event_record = GetOldestEventRecord();
|
||||
|
||||
// When calling a barectf tracing function, it calls the clock value
|
||||
// accessor callback of the platform, which itself reads from
|
||||
// `clock_val_`.
|
||||
clock_val_ = oldest_event_record.GetClockVal();
|
||||
|
||||
// Forward to a barectf tracing function.
|
||||
oldest_event_record.Write(platform_.GetCtx());
|
||||
|
||||
// Remove from queue.
|
||||
queue_.pop();
|
||||
}
|
||||
|
||||
// barectf platform (manages file I/O).
|
||||
BarectfPlatform<PlatformDescrT> platform_;
|
||||
|
||||
// Current clock value for `platform_`.
|
||||
//
|
||||
// This is also the clock value of the most recently written event
|
||||
// record, therefore that MayAddEventRecord() can rely on this.
|
||||
std::uint64_t clock_val_ = 0;
|
||||
|
||||
// Maximum size of `queue_` below.
|
||||
std::size_t max_queue_size_;
|
||||
|
||||
// Event record queue.
|
||||
std::priority_queue<typename EventRecord::SP, std::vector<typename EventRecord::SP>,
|
||||
EventRecordQueueCompare>
|
||||
queue_;
|
||||
};
|
||||
|
||||
} // namespace rocm_ctf
|
||||
|
||||
#endif // PLUGIN_CTF_BARECTF_WRITER_H
|
||||
@@ -0,0 +1,165 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
################################################################################
|
||||
|
||||
%YAML 1.2
|
||||
--- !<tag:barectf.org,2020/3/config>
|
||||
trace:
|
||||
$include:
|
||||
# Environment (generated file).
|
||||
- env.yaml
|
||||
type:
|
||||
$include:
|
||||
- stdint.yaml
|
||||
- stdmisc.yaml
|
||||
native-byte-order: little-endian
|
||||
clock-types:
|
||||
default:
|
||||
origin-is-unix-epoch: true
|
||||
$c-type: uint64_t
|
||||
data-stream-types:
|
||||
hsa_api:
|
||||
event-record-common-context-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _thread_id: uint32
|
||||
- _queue_id: uint32
|
||||
- _agent_id: uint32
|
||||
- _correlation_id: uint64
|
||||
$include:
|
||||
# Base.
|
||||
- dst_base.yaml
|
||||
|
||||
# HSA API event record types (generated file).
|
||||
- hsa_erts.yaml
|
||||
hip_api:
|
||||
event-record-common-context-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _thread_id: uint32
|
||||
- _queue_id: uint32
|
||||
- _agent_id: uint32
|
||||
- _correlation_id: uint64
|
||||
- _kernel_name: str
|
||||
$include:
|
||||
# Base.
|
||||
- dst_base.yaml
|
||||
|
||||
# HIP API event record types (generated file).
|
||||
- hip_erts.yaml
|
||||
roctx:
|
||||
$include:
|
||||
# Base
|
||||
- dst_base.yaml
|
||||
event-record-common-context-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _thread_id: uint32
|
||||
event-record-types:
|
||||
roctx:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _id: sint64
|
||||
- _msg: str
|
||||
hsa_handles:
|
||||
$include:
|
||||
# Base.
|
||||
- dst_base.yaml
|
||||
event-record-types:
|
||||
hsa_handle_type:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _handle: uint64
|
||||
- _type:
|
||||
field-type:
|
||||
class: uenum
|
||||
size: 8
|
||||
mappings:
|
||||
CPU: [0]
|
||||
GPU: [1]
|
||||
api_ops:
|
||||
$include:
|
||||
# Base.
|
||||
- dst_base.yaml
|
||||
event-record-common-context-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _thread_id: uint32
|
||||
- _queue_id: uint32
|
||||
- _agent_id: uint32
|
||||
- _correlation_id: uint64
|
||||
event-record-types:
|
||||
hsa_op_begin:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
hsa_op_end:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
hip_op_begin:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _kernel_name: str
|
||||
hip_op_end:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
profiler:
|
||||
$include:
|
||||
# Base.
|
||||
- dst_base.yaml
|
||||
event-record-common-context-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _dispatch: uint64
|
||||
- _gpu_id: uint64
|
||||
- _queue_id: uint64
|
||||
- _queue_index: uint64
|
||||
- _process_id: uint32
|
||||
- _thread_id: uint32
|
||||
- _kernel_id: uint64
|
||||
- _kernel_name: str
|
||||
- _counter_names:
|
||||
field-type:
|
||||
class: dynamic-array
|
||||
element-field-type: str
|
||||
- _counter_values:
|
||||
field-type:
|
||||
class: dynamic-array
|
||||
element-field-type: uint64
|
||||
event-record-types:
|
||||
profiler_record:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
profiler_record_with_kernel_properties:
|
||||
payload-field-type:
|
||||
class: struct
|
||||
members:
|
||||
- _grid_size: uint64
|
||||
- _workgroup_size: uint64
|
||||
- _lds_size: uint64
|
||||
- _scratch_size: uint64
|
||||
- _arch_vgpr_count: uint64
|
||||
- _accum_vgpr_count: uint64
|
||||
- _sgpr_count: uint64
|
||||
- _wave_size: uint64
|
||||
- _signal_handle: uint64
|
||||
@@ -0,0 +1,107 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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 <cassert>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
#include "rocprofiler.h"
|
||||
#include "rocprofiler_plugin.h"
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
// Global plugin instance
|
||||
rocm_ctf::Plugin* the_plugin = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
ROCPROFILER_EXPORT int rocprofiler_plugin_initialize(const uint32_t rocprofiler_major_version,
|
||||
const uint32_t rocprofiler_minor_version) {
|
||||
if (rocprofiler_major_version != ROCPROFILER_VERSION_MAJOR ||
|
||||
rocprofiler_minor_version < ROCPROFILER_VERSION_MINOR) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (the_plugin) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto output_dir = getenv("OUTPUT_PATH");
|
||||
|
||||
if (!output_dir) {
|
||||
std::cerr << "rocprofiler_plugin_initialize(): "
|
||||
<< "`OUTPUT_PATH` environment variable isn't set" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create the plugin instance.
|
||||
try {
|
||||
the_plugin = new rocm_ctf::Plugin{256 * 1024, fs::path{output_dir} / "trace",
|
||||
CTF_PLUGIN_METADATA_FILE_PATH};
|
||||
} catch (const std::exception& exc) {
|
||||
std::cerr << "rocprofiler_plugin_initialize(): " << exc.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ROCPROFILER_EXPORT void rocprofiler_plugin_finalize() {
|
||||
delete the_plugin;
|
||||
the_plugin = nullptr;
|
||||
}
|
||||
|
||||
ROCPROFILER_EXPORT int rocprofiler_plugin_write_buffer_records(
|
||||
const rocprofiler_record_header_t* const begin, const rocprofiler_record_header_t* const end,
|
||||
const rocprofiler_session_id_t session_id, const rocprofiler_buffer_id_t buffer_id) {
|
||||
assert(the_plugin);
|
||||
|
||||
try {
|
||||
the_plugin->HandleBufferRecords(begin, end, session_id, buffer_id);
|
||||
} catch (const std::exception& exc) {
|
||||
std::cerr << "rocprofiler_plugin_write_buffer_records(): " << exc.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ROCPROFILER_EXPORT int rocprofiler_plugin_write_record(const rocprofiler_record_tracer_t record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
assert(the_plugin);
|
||||
|
||||
if (record.header.id.handle == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
the_plugin->HandleTracerRecord(record, session_id);
|
||||
} catch (const std::exception& exc) {
|
||||
std::cerr << "rocprofiler_plugin_write_record(): " << exc.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
################################################################################
|
||||
|
||||
$default-clock-type-name: default
|
||||
$features:
|
||||
packet:
|
||||
beginning-timestamp-field-type: false
|
||||
discarded-event-records-counter-snapshot-field-type: false
|
||||
end-timestamp-field-type: false
|
||||
@@ -0,0 +1,645 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
################################################################################
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import re
|
||||
import yaml
|
||||
import CppHeaderParser
|
||||
|
||||
|
||||
# Numeric field type (abstract).
|
||||
class _NumericFt:
|
||||
# Returns the C++ expression to cast the expression `expr` to the C
|
||||
# type of this field type.
|
||||
def cast(self, expr):
|
||||
return f'static_cast<{self.c_type}>({expr})'
|
||||
|
||||
|
||||
# Integer field type (abstract).
|
||||
class _IntFt(_NumericFt):
|
||||
def __init__(self, size, pref_disp_base='dec'):
|
||||
self._size = size
|
||||
self._pref_disp_base = pref_disp_base
|
||||
|
||||
# Size (bits).
|
||||
@property
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
# Preferred display base (`dec` or `hex`).
|
||||
@property
|
||||
def pref_disp_base(self):
|
||||
return self._pref_disp_base
|
||||
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
return {
|
||||
'size': self._size,
|
||||
'preferred-display-base': self._pref_disp_base,
|
||||
}
|
||||
|
||||
|
||||
# Signed integer field type.
|
||||
class _SIntFt(_IntFt):
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
ret = super().barectf_yaml
|
||||
ret['class'] = 'sint'
|
||||
return ret
|
||||
|
||||
# Equivalent C type
|
||||
@property
|
||||
def c_type(self):
|
||||
return f'std::int{self._size}_t'
|
||||
|
||||
|
||||
# Unsigned integer field type.
|
||||
class _UIntFt(_IntFt):
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
ret = super().barectf_yaml
|
||||
ret['class'] = 'uint'
|
||||
return ret
|
||||
|
||||
# Equivalent C type.
|
||||
@property
|
||||
def c_type(self):
|
||||
return f'std::uint{self._size}_t'
|
||||
|
||||
|
||||
# Pointer field type.
|
||||
class _PointerFt(_UIntFt):
|
||||
def __init__(self):
|
||||
super().__init__(64, 'hex')
|
||||
|
||||
# Returns the C++ expression to cast the expression `expr` to the C
|
||||
# type of this field type.
|
||||
def cast(self, expr):
|
||||
return f'static_cast<{self.c_type}>(reinterpret_cast<std::uintptr_t>({expr}))'
|
||||
|
||||
|
||||
# Enumeration field type (abstract).
|
||||
class _EnumFt(_IntFt):
|
||||
def __init__(self, size, mappings):
|
||||
super().__init__(size)
|
||||
self._mappings = mappings.copy()
|
||||
|
||||
# Mappings (names to integers).
|
||||
@property
|
||||
def mappings(self):
|
||||
return self._mappings
|
||||
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
ret = super().barectf_yaml
|
||||
mappings = {}
|
||||
|
||||
for name, val in self._mappings.items():
|
||||
mappings[name] = [val]
|
||||
|
||||
ret['mappings'] = mappings
|
||||
return ret
|
||||
|
||||
|
||||
# Unsigned enumeration field type.
|
||||
class _UEnumFt(_EnumFt, _UIntFt):
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
ret = super().barectf_yaml
|
||||
ret['class'] = 'uenum'
|
||||
return ret
|
||||
|
||||
|
||||
# Signed enumeration field type.
|
||||
class _SEnumFt(_EnumFt, _UIntFt):
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
ret = super().barectf_yaml
|
||||
ret['class'] = 'senum'
|
||||
return ret
|
||||
|
||||
|
||||
# Optional string field type.
|
||||
class _OptStrFt:
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
return {
|
||||
'class': 'str',
|
||||
}
|
||||
|
||||
|
||||
# String field type.
|
||||
class _StrFt(_OptStrFt):
|
||||
pass
|
||||
|
||||
|
||||
# Floating-point number field type.
|
||||
class _FloatFt(_NumericFt):
|
||||
def __init__(self, size):
|
||||
self._size = size
|
||||
|
||||
# Size (bits): 32 or 64.
|
||||
@property
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
# Equivalent barectf field type in YAML.
|
||||
@property
|
||||
def barectf_yaml(self):
|
||||
return {
|
||||
'class': 'real',
|
||||
'size': self._size,
|
||||
}
|
||||
|
||||
# Equivalent C type.
|
||||
@property
|
||||
def c_type(self):
|
||||
if self._size == 32:
|
||||
return 'float'
|
||||
else:
|
||||
assert self._size == 64
|
||||
return 'double'
|
||||
|
||||
|
||||
# Event record type.
|
||||
class _Ert:
|
||||
def __init__(self, api_func_name, members):
|
||||
self._api_func_name = api_func_name
|
||||
self._members = members
|
||||
|
||||
# API function name
|
||||
@property
|
||||
def api_func_name(self):
|
||||
return self._api_func_name
|
||||
|
||||
# Parameters of function (list of `_ErtMember`).
|
||||
@property
|
||||
def members(self):
|
||||
return self._members
|
||||
|
||||
|
||||
# Beginning event record type.
|
||||
class _BeginErt(_Ert):
|
||||
# Name of event record type depending on the API prefix.
|
||||
def name(self, api_prefix):
|
||||
suffix = '_begin' if api_prefix == 'hsa' else 'Begin'
|
||||
return f'{self._api_func_name}{suffix}'
|
||||
|
||||
|
||||
# End event record type.
|
||||
class _EndErt(_Ert):
|
||||
# Name of event record type depending on the API prefix.
|
||||
def name(self, api_prefix):
|
||||
suffix = '_end' if api_prefix == 'hsa' else 'End'
|
||||
return f'{self._api_func_name}{suffix}'
|
||||
|
||||
|
||||
# Event record type member.
|
||||
class _ErtMember:
|
||||
def __init__(self, access, member_names, ft):
|
||||
self._access = access
|
||||
self._member_names = member_names.copy()
|
||||
self._ft = ft
|
||||
|
||||
# C++ access expression.
|
||||
@property
|
||||
def access(self):
|
||||
return self._access
|
||||
|
||||
# List of member names.
|
||||
@property
|
||||
def member_names(self):
|
||||
return self._member_names
|
||||
|
||||
# Equivalent field type.
|
||||
@property
|
||||
def ft(self):
|
||||
return self._ft
|
||||
|
||||
|
||||
# Makes sure some condition is satisfied, or prints the error message
|
||||
# `error_msg` and quits with exit status 1 otherwise.
|
||||
#
|
||||
# This is an unconditional assertion.
|
||||
def _make_sure(cond, error_msg):
|
||||
if not cond:
|
||||
print(f'Error: {error_msg}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _enumerator_effective_val(enum_val):
|
||||
# Try the value, but this value may be a string (an
|
||||
# enumerator/definition).
|
||||
val = enum_val.get('value')
|
||||
|
||||
if type(val) is int:
|
||||
return val
|
||||
|
||||
# Try the raw value.
|
||||
val = enum_val.get('raw_value')
|
||||
|
||||
if val is not None:
|
||||
if type(val) is int:
|
||||
# Raw value is already an integer.
|
||||
return val
|
||||
else:
|
||||
# Try to parse the raw value string as an integer.
|
||||
try:
|
||||
return int(val, 0)
|
||||
except:
|
||||
pass
|
||||
|
||||
_make_sure(False,
|
||||
f'Cannot get the integral value of enumerator `{enum_val["name"]}`')
|
||||
|
||||
|
||||
# Returns the equivalent field type of the C type `c_type`.
|
||||
def _number_ft_from_c_type(cpp_header, c_type):
|
||||
# Check for known enumeration.
|
||||
m = re.match(r'(?:enum\s+)?(\w+)', c_type)
|
||||
|
||||
if m:
|
||||
size = 32
|
||||
|
||||
for enum_info in cpp_header.enums:
|
||||
if m.group(1) == enum_info.get('name'):
|
||||
# Fill enumeration field type mappings.
|
||||
mappings = {
|
||||
str(v['name']): _enumerator_effective_val(v)
|
||||
for v in enum_info['values']
|
||||
}
|
||||
|
||||
if len(mappings) == 0:
|
||||
return _SIntFt(64)
|
||||
|
||||
if max(mappings.values()) >= 2**31 or min(mappings.values()) < -2**31:
|
||||
size = 64
|
||||
|
||||
_make_sure(len(mappings) > 0, f'Enumeration `{enum_info["name"]}` is empty')
|
||||
|
||||
# Create corresponding enumeration field type.
|
||||
return _SEnumFt(size, mappings)
|
||||
|
||||
# Find corresponding basic field type.
|
||||
is_unsigned = 'unsigned' in c_type
|
||||
|
||||
if 'long' in c_type:
|
||||
if is_unsigned:
|
||||
return _UIntFt(64)
|
||||
else:
|
||||
return _SIntFt(64)
|
||||
elif 'short' in c_type:
|
||||
if is_unsigned:
|
||||
return _UIntFt(16)
|
||||
else:
|
||||
return _SIntFt(16)
|
||||
elif 'char' in c_type:
|
||||
if is_unsigned:
|
||||
return _UIntFt(8)
|
||||
else:
|
||||
return _SIntFt(8)
|
||||
elif 'float' in c_type:
|
||||
return _FloatFt(32)
|
||||
elif 'double' in c_type:
|
||||
return _FloatFt(64)
|
||||
else:
|
||||
# Assume `int` (often an unresolved C enumeration).
|
||||
if is_unsigned:
|
||||
return _UIntFt(32)
|
||||
else:
|
||||
return _SIntFt(32)
|
||||
|
||||
|
||||
# Returns whether or not a property has a pointer type.
|
||||
def _prop_is_pointer(prop, c_type):
|
||||
if prop['pointer'] or prop['function_pointer']:
|
||||
return True
|
||||
|
||||
if prop['array'] and 'array_size' in prop:
|
||||
return True
|
||||
|
||||
if prop['unresolved']:
|
||||
# HSA API function pointers.
|
||||
if prop['name'] in ('callback', 'handler'):
|
||||
return True
|
||||
|
||||
# HIP API function pointers.
|
||||
if c_type.endswith('Fn_t'):
|
||||
return True
|
||||
|
||||
# Check the C type itself.
|
||||
if '*' in c_type or '*' in prop.get('raw_type', ''):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# Returns a list of event record type member objects for the structure
|
||||
# `struct` considering the initial C++ access expression `access` and
|
||||
# member names `member_names`.
|
||||
def _get_ert_members_for_struct(cpp_header, struct, access, member_names):
|
||||
members = []
|
||||
member_names = member_names.copy()
|
||||
member_names.append(None)
|
||||
props = struct['properties']['public']
|
||||
|
||||
for index, prop in enumerate(props):
|
||||
# Property name.
|
||||
name = prop['name']
|
||||
|
||||
# Member names, access, and C type.
|
||||
member_names[-1] = str(name)
|
||||
this_access = f'{access}.{name}'
|
||||
c_type = prop['type']
|
||||
aliases = prop['aliases']
|
||||
|
||||
# Skip no type.
|
||||
if c_type == '':
|
||||
continue
|
||||
|
||||
# Skip unnamed or union.
|
||||
if name == '' or 'union' in name or re.match(r'\bunion\b', c_type):
|
||||
continue
|
||||
|
||||
# Check for known C type alias.
|
||||
while True:
|
||||
c_type_alias = cpp_header.typedefs.get(c_type)
|
||||
|
||||
if c_type_alias is None:
|
||||
break
|
||||
|
||||
c_type = c_type_alias
|
||||
|
||||
# Check for C string.
|
||||
if re.match(r'^((const\s+char)|(char\s+const)|char)\s*\*$',
|
||||
c_type.strip()):
|
||||
members.append(_ErtMember(this_access, member_names, _OptStrFt()))
|
||||
continue
|
||||
|
||||
# Check for pointer.
|
||||
if _prop_is_pointer(prop, c_type):
|
||||
# Pointer: use numeric value.
|
||||
members.append(_ErtMember(this_access, member_names, _PointerFt()))
|
||||
continue
|
||||
|
||||
# Check for substructure.
|
||||
sub_struct = cpp_header.classes.get(c_type)
|
||||
|
||||
if sub_struct is None and len(aliases) == 1:
|
||||
sub_struct = cpp_header.classes.get(aliases[0])
|
||||
|
||||
if sub_struct is not None:
|
||||
members += _get_ert_members_for_struct(cpp_header, sub_struct,
|
||||
this_access, member_names)
|
||||
continue
|
||||
|
||||
# Use a basic field type.
|
||||
members.append(_ErtMember(this_access, member_names,
|
||||
_number_ft_from_c_type(cpp_header, c_type)))
|
||||
|
||||
return members
|
||||
|
||||
|
||||
# Returns the beginning and end event record type objects for the
|
||||
# callback data structure `struct`.
|
||||
def _erts_from_cb_data_struct(api_prefix, cpp_header, retval_info, struct):
|
||||
# The location of the `args` union within the nested structures of
|
||||
# `struct`.
|
||||
args_nested_cls_index = 0
|
||||
|
||||
# Create return value members (to be used later).
|
||||
if retval_info is not None:
|
||||
args_nested_cls_index = 1
|
||||
retval_members = {}
|
||||
nested_classes = struct['nested_classes']
|
||||
_make_sure(len(nested_classes) >= 1,
|
||||
f"Return value union doesn't exist in `{struct['name']}`")
|
||||
retval_union = nested_classes[0]
|
||||
|
||||
for prop in retval_union['properties']['public']:
|
||||
name = str(prop['name'])
|
||||
member = _ErtMember(f'GetApiData().{name}', ['retval'],
|
||||
_number_ft_from_c_type(cpp_header, prop['type']))
|
||||
retval_members[prop['name']] = member
|
||||
|
||||
# Make sure we have everything we need.
|
||||
for api_func_name, retval_name in retval_info.items():
|
||||
if retval_name is not None:
|
||||
_make_sure(retval_name in retval_members,
|
||||
f"Return value union member `{retval_name}` doesn't exist (function {api_func_name}())")
|
||||
|
||||
# Create beginning/end event record type objects.
|
||||
begin_erts = []
|
||||
end_erts = []
|
||||
nested_classes = struct['nested_classes'][args_nested_cls_index]['nested_classes']
|
||||
props = struct['nested_classes'][args_nested_cls_index]['properties']['public']
|
||||
_make_sure(len(nested_classes) == len(props),
|
||||
f'Mismatch between nested structure and member count in `{struct["name"]}`')
|
||||
|
||||
for index, prop in enumerate(props):
|
||||
# API function name is the name of the member.
|
||||
api_func_name = str(prop['name'])
|
||||
|
||||
# Get the parameters.
|
||||
members = _get_ert_members_for_struct(cpp_header,
|
||||
nested_classes[index],
|
||||
f'GetApiData().args.{api_func_name}',
|
||||
[])
|
||||
|
||||
# Append new beginning event record type object.
|
||||
begin_erts.append(_BeginErt(api_func_name, members))
|
||||
|
||||
# Append new end event record type object if possible.
|
||||
ret_members = []
|
||||
|
||||
if retval_info is not None:
|
||||
retval_type = retval_info.get(api_func_name)
|
||||
|
||||
if retval_type is not None:
|
||||
ret_members.append(retval_members[retval_type])
|
||||
|
||||
end_erts.append(_EndErt(api_func_name, ret_members))
|
||||
|
||||
return begin_erts, end_erts
|
||||
|
||||
|
||||
# Creates and returns the return value information dictionary.
|
||||
#
|
||||
# This dictionary maps API function names to the member to get within
|
||||
# the callback data structure.
|
||||
#
|
||||
# This only applies to the HSA API: for other APIs, this function
|
||||
# returns `None`.
|
||||
def _get_retval_info(path):
|
||||
if 'hsa' not in os.path.basename(path):
|
||||
return
|
||||
|
||||
retval_info = {}
|
||||
cur_api_func_name = None
|
||||
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
if 'out << ")' in line and cur_api_func_name is not None:
|
||||
m = re.search(r'api_data.(\w+_retval)', line)
|
||||
retval_info[cur_api_func_name] = m.group(1) if m else None
|
||||
else:
|
||||
m = re.search(r'out << "(hsa_\w+)\(";', line)
|
||||
|
||||
if m:
|
||||
cur_api_func_name = m.group(1)
|
||||
|
||||
return retval_info
|
||||
|
||||
|
||||
# Returns a partial barectf data stream type in YAML with the event
|
||||
# record types `erts`.
|
||||
def _yaml_dst_from_erts(api_prefix, erts):
|
||||
# Base.
|
||||
yaml_erts = {}
|
||||
yaml_dst = {
|
||||
'event-record-types': yaml_erts,
|
||||
}
|
||||
|
||||
# Create one event record type per API function.
|
||||
for ert in erts:
|
||||
# Base.
|
||||
yaml_members = []
|
||||
yaml_ert = {
|
||||
'payload-field-type': {
|
||||
'class': 'struct',
|
||||
'members': yaml_members,
|
||||
},
|
||||
}
|
||||
|
||||
# Create one structure field type member per member.
|
||||
for member in ert.members:
|
||||
# barectf doesn't support nested CTF structures, so join
|
||||
# individual member names with `__` to flatten.
|
||||
yaml_members.append({
|
||||
'_' + '__'.join(member.member_names): {
|
||||
'field-type': member.ft.barectf_yaml,
|
||||
},
|
||||
})
|
||||
|
||||
# Add event record type.
|
||||
yaml_erts[ert.name(api_prefix)] = yaml_ert
|
||||
|
||||
# Convert to YAML.
|
||||
return yaml.dump(yaml_dst)
|
||||
|
||||
|
||||
# Returns the C++ switch statement which calls the correct barectf
|
||||
# tracing function depending on the API function operation ID.
|
||||
def _cpp_switch_statement_from_erts(api_prefix, erts):
|
||||
lines = []
|
||||
lines.append('switch (GetOp()) {')
|
||||
|
||||
for ert in erts:
|
||||
lines.append(f' case {api_prefix.upper()}_API_ID_{ert.api_func_name}:')
|
||||
lines.append(f' barectf_{api_prefix}_api_trace_{ert.name(api_prefix)}(')
|
||||
lines.append(f' &barectf_ctx,')
|
||||
lines.append(f' GetThreadId(),')
|
||||
lines.append(f' GetQueueId(),')
|
||||
lines.append(f' GetAgentId(),')
|
||||
lines.append(f' GetCorrelationId(),')
|
||||
|
||||
if api_prefix == 'hip':
|
||||
lines.append(f' GetKernelName().c_str(),')
|
||||
|
||||
if len(ert.members) == 0:
|
||||
# Remove last comma.
|
||||
lines[-1] = lines[-1].replace(',', '')
|
||||
|
||||
for index, member in enumerate(ert.members):
|
||||
if type(member.ft) is _OptStrFt:
|
||||
# Only dereference C string if not null, otherwise use
|
||||
# an empty string.
|
||||
lines.append(f' {member.access} ? {member.access} : ""')
|
||||
elif type(member.ft) is _StrFt:
|
||||
lines.append(f' {member.access}')
|
||||
else:
|
||||
lines.append(f' {member.ft.cast(member.access)}')
|
||||
|
||||
if index + 1 < len(ert.members):
|
||||
lines[-1] += ','
|
||||
|
||||
lines.append(' );')
|
||||
lines.append(' break;')
|
||||
|
||||
lines.append('}')
|
||||
return lines
|
||||
|
||||
|
||||
# Processes the complete API header file `path`.
|
||||
def _process_file(api_prefix, path):
|
||||
# Create `CppHeader` object.
|
||||
try:
|
||||
cpp_header = CppHeaderParser.CppHeader(path)
|
||||
except CppHeaderParser.CppParseError as exc:
|
||||
print(exc, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Get return value information dictionary.
|
||||
retval_info = _get_retval_info(path)
|
||||
|
||||
# Find callback data structure.
|
||||
for struct_name, struct in cpp_header.classes.items():
|
||||
if re.match(r'^' + api_prefix + r'_api_data\w+$', struct_name):
|
||||
# Process callback data structure.
|
||||
begin_erts, end_erts = _erts_from_cb_data_struct(api_prefix,
|
||||
cpp_header,
|
||||
retval_info,
|
||||
struct)
|
||||
|
||||
# Write barectf YAML file.
|
||||
with open(f'{api_prefix}_erts.yaml', 'w') as f:
|
||||
f.write(_yaml_dst_from_erts(api_prefix, begin_erts + end_erts))
|
||||
|
||||
# Write C++ code (beginning event record).
|
||||
with open(f'{api_prefix}_begin.cpp.i', 'w') as f:
|
||||
f.write('\n'.join(_cpp_switch_statement_from_erts(api_prefix,
|
||||
begin_erts)))
|
||||
|
||||
# Write C++ code (end event record).
|
||||
with open(f'{api_prefix}_end.cpp.i', 'w') as f:
|
||||
f.write('\n'.join(_cpp_switch_statement_from_erts(api_prefix,
|
||||
end_erts)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Disable `CppHeaderParser` printing to standard output.
|
||||
CppHeaderParser.CppHeaderParser.print_warnings = 0
|
||||
CppHeaderParser.CppHeaderParser.print_errors = 0
|
||||
CppHeaderParser.CppHeaderParser.debug = 0
|
||||
CppHeaderParser.CppHeaderParser.debug_trace = 0
|
||||
|
||||
# Process the complete API header file.
|
||||
_process_file(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,33 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
################################################################################
|
||||
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with open('env.yaml', 'w') as f:
|
||||
f.write(yaml.dump({
|
||||
'environment': {
|
||||
'rocprofiler_version': sys.argv[1],
|
||||
}
|
||||
}))
|
||||
@@ -0,0 +1,869 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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 <cassert>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <fstream>
|
||||
#include <experimental/filesystem>
|
||||
#include <time.h>
|
||||
|
||||
#include <hsa/hsa.h>
|
||||
#include <hsa/hsa_ext_amd.h>
|
||||
#include "hsa_prof_str.h"
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/amd_detail/hip_prof_str.h>
|
||||
|
||||
#include "rocprofiler.h"
|
||||
#include "rocprofiler_plugin.h"
|
||||
#include "../utils.h"
|
||||
|
||||
#include "barectf.h"
|
||||
#include "barectf_event_record.h"
|
||||
#include "barectf_tracer.h"
|
||||
#include "plugin.h"
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
namespace rocm_ctf {
|
||||
namespace {
|
||||
|
||||
// Abstract tracer event record using the barectf context type `CtxT`.
|
||||
template <typename CtxT> class TracerEventRecord : public BarectfEventRecord<CtxT> {
|
||||
protected:
|
||||
explicit TracerEventRecord(const rocprofiler_record_tracer_t& record, const std::uint64_t clock_val)
|
||||
: BarectfEventRecord<CtxT>{clock_val},
|
||||
op_{record.operation_id.id},
|
||||
thread_id_{record.thread_id.value},
|
||||
queue_id_{record.queue_id.handle},
|
||||
agent_id_{record.agent_id.handle},
|
||||
correlation_id_{record.correlation_id.value} {}
|
||||
|
||||
std::uint32_t GetOp() const noexcept { return op_; }
|
||||
std::uint32_t GetThreadId() const noexcept { return thread_id_; }
|
||||
std::uint64_t GetQueueId() const noexcept { return queue_id_; }
|
||||
std::uint64_t GetAgentId() const noexcept { return agent_id_; }
|
||||
std::uint64_t GetCorrelationId() const noexcept { return correlation_id_; }
|
||||
|
||||
private:
|
||||
std::uint32_t op_;
|
||||
std::uint32_t thread_id_;
|
||||
std::uint64_t queue_id_;
|
||||
std::uint64_t agent_id_;
|
||||
std::uint64_t correlation_id_;
|
||||
};
|
||||
|
||||
// Returns the beginning clock value of the tracer or profiler record
|
||||
// `record`.
|
||||
template <typename RecordT> std::uint64_t GetRecordBeginClockVal(const RecordT& record) {
|
||||
return record.timestamps.begin.value;
|
||||
}
|
||||
|
||||
// Returns the end clock value of the tracer or profiler record
|
||||
// `record`.
|
||||
template <typename RecordT> std::uint64_t GetRecordEndClockVal(const RecordT& record) {
|
||||
return record.timestamps.end.value;
|
||||
}
|
||||
|
||||
// Queries allocated string data using the size query function
|
||||
// `query_size_func` and the data query function `query_data_func`,
|
||||
// returning the corresponding string and freeing temporary allocated
|
||||
// memory.
|
||||
//
|
||||
// Returns an empty string if anything goes wrong.
|
||||
template <typename QuerySizeFuncT, typename QueryDataFuncT>
|
||||
std::string QueryAllocStr(QuerySizeFuncT&& query_size_func, QueryDataFuncT&& query_data_func) {
|
||||
// Query size first.
|
||||
std::size_t size = 0;
|
||||
[[maybe_unused]] auto ret = query_size_func(&size);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query size");
|
||||
|
||||
if (size == 0) {
|
||||
// No size: return empty string.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Query data (allocated by query_data_func()).
|
||||
char* alloc_str = nullptr;
|
||||
|
||||
ret = query_data_func(&alloc_str);
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query data");
|
||||
|
||||
if (!alloc_str) {
|
||||
// No data: return empty string.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Allocate return value.
|
||||
std::string str_ret{alloc_str};
|
||||
|
||||
// Free allocated data.
|
||||
std::free(alloc_str);
|
||||
|
||||
// Return string object.
|
||||
return str_ret;
|
||||
}
|
||||
|
||||
// rocTX event record.
|
||||
class RocTxEventRecord final : public TracerEventRecord<barectf_roctx_ctx> {
|
||||
public:
|
||||
explicit RocTxEventRecord(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: TracerEventRecord<barectf_roctx_ctx>{record, GetRecordBeginClockVal(record)},
|
||||
id_{QueryId(record, session_id)},
|
||||
msg_{QueryMsg(record, session_id)} {}
|
||||
|
||||
void Write(barectf_roctx_ctx& barectf_ctx) const override {
|
||||
barectf_roctx_trace_roctx(&barectf_ctx, GetThreadId(), id_, msg_.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
// Queries and returns the rocTX message of the record `record` and
|
||||
// session ID `session_id`.
|
||||
//
|
||||
// Returns an empty string if not available.
|
||||
static std::string QueryMsg(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
// Query size first.
|
||||
std::size_t msg_size = 0;
|
||||
[[maybe_unused]] auto ret = rocprofiler_query_roctx_tracer_api_data_info_size(
|
||||
session_id, ROCPROFILER_ROCTX_MESSAGE, record.api_data_handle, record.operation_id,
|
||||
&msg_size);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query rocTX message size");
|
||||
|
||||
if (msg_size == 0) {
|
||||
// No size: return empty string.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Query data (borrowed from the record: no need to free).
|
||||
char* msg = nullptr;
|
||||
|
||||
ret = rocprofiler_query_roctx_tracer_api_data_info(
|
||||
session_id, ROCPROFILER_ROCTX_MESSAGE, record.api_data_handle, record.operation_id, &msg);
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query rocTX message");
|
||||
|
||||
if (!msg) {
|
||||
// No data: return empty string.
|
||||
return {};
|
||||
}
|
||||
|
||||
return rocmtools::cxx_demangle(msg);
|
||||
}
|
||||
|
||||
// Queries and returns the rocTX ID of the record `record` and the
|
||||
// session ID `session_id`.
|
||||
//
|
||||
// Returns 0 if anything goes wrong.
|
||||
static std::uint64_t QueryId(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
try {
|
||||
return std::stoull(QueryAllocStr(
|
||||
[&record, session_id](const auto size) {
|
||||
return rocprofiler_query_roctx_tracer_api_data_info_size(
|
||||
session_id, ROCPROFILER_ROCTX_ID, record.api_data_handle, record.operation_id, size);
|
||||
},
|
||||
[&record, session_id](const auto str) {
|
||||
return rocprofiler_query_roctx_tracer_api_data_info(
|
||||
session_id, ROCPROFILER_ROCTX_ID, record.api_data_handle, record.operation_id, str);
|
||||
}));
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t id_;
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
// Abstract HSA API event record.
|
||||
class HsaApiEventRecord : public TracerEventRecord<barectf_hsa_api_ctx> {
|
||||
protected:
|
||||
explicit HsaApiEventRecord(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id, const std::uint64_t clock_val)
|
||||
: TracerEventRecord<barectf_hsa_api_ctx>{record, clock_val},
|
||||
api_data_{QueryApiData(record, session_id)} {}
|
||||
|
||||
const hsa_api_data_t& GetApiData() const noexcept { return api_data_; }
|
||||
|
||||
private:
|
||||
// Queries and returns the API data of the record `record` and session
|
||||
// ID `session_id`.
|
||||
static const hsa_api_data_t& QueryApiData(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
// Query size first (only for assertions).
|
||||
[[maybe_unused]] std::size_t size = 0;
|
||||
[[maybe_unused]] auto ret = rocprofiler_query_hsa_tracer_api_data_info_size(
|
||||
session_id, ROCPROFILER_HSA_API_DATA, record.api_data_handle, record.operation_id, &size);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query HSA API data size");
|
||||
assert(size > 0);
|
||||
|
||||
// Query data (borrowed from the record).
|
||||
char* data = nullptr;
|
||||
ret = rocprofiler_query_hsa_tracer_api_data_info(
|
||||
session_id, ROCPROFILER_HSA_API_DATA, record.api_data_handle, record.operation_id, &data);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query HSA API data");
|
||||
assert(data);
|
||||
|
||||
// Reinterpret as an HSA API data pointer.
|
||||
return *reinterpret_cast<const hsa_api_data_t*>(data);
|
||||
}
|
||||
|
||||
hsa_api_data_t api_data_;
|
||||
};
|
||||
|
||||
// HSA API event record (beginning).
|
||||
class HsaApiEventRecordBegin final : public HsaApiEventRecord {
|
||||
public:
|
||||
explicit HsaApiEventRecordBegin(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: HsaApiEventRecord{record, session_id, GetRecordBeginClockVal(record)} {}
|
||||
|
||||
void Write(barectf_hsa_api_ctx& barectf_ctx) const override {
|
||||
// Include generated switch statement.
|
||||
#include "hsa_begin.cpp.i"
|
||||
}
|
||||
};
|
||||
|
||||
// HSA API event record (end).
|
||||
class HsaApiEventRecordEnd final : public HsaApiEventRecord {
|
||||
public:
|
||||
explicit HsaApiEventRecordEnd(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: HsaApiEventRecord{record, session_id, GetRecordEndClockVal(record)} {}
|
||||
|
||||
void Write(barectf_hsa_api_ctx& barectf_ctx) const override {
|
||||
// Include generated switch statement.
|
||||
#include "hsa_end.cpp.i"
|
||||
}
|
||||
};
|
||||
|
||||
// Abstract HIP API event record.
|
||||
class HipApiEventRecord : public TracerEventRecord<barectf_hip_api_ctx> {
|
||||
protected:
|
||||
explicit HipApiEventRecord(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id, const std::uint64_t clock_val)
|
||||
: TracerEventRecord<barectf_hip_api_ctx>{record, clock_val},
|
||||
api_data_{QueryApiData(record, session_id)},
|
||||
kernel_name_{QueryKernelName(record, session_id)} {}
|
||||
|
||||
const hip_api_data_t& GetApiData() const noexcept { return api_data_; }
|
||||
const std::string& GetKernelName() const noexcept { return kernel_name_; }
|
||||
|
||||
private:
|
||||
// Queries and returns the API data of the record `record` and session
|
||||
// ID `session_id`.
|
||||
static const hip_api_data_t& QueryApiData(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
// Query size first (only for assertions).
|
||||
[[maybe_unused]] std::size_t size = 0;
|
||||
[[maybe_unused]] auto ret = rocprofiler_query_hip_tracer_api_data_info_size(
|
||||
session_id, ROCPROFILER_HIP_API_DATA, record.api_data_handle, record.operation_id, &size);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query HIP API data size");
|
||||
assert(size > 0);
|
||||
|
||||
// Query data (borrowed from the record).
|
||||
char* data = nullptr;
|
||||
|
||||
ret = rocprofiler_query_hip_tracer_api_data_info(
|
||||
session_id, ROCPROFILER_HIP_API_DATA, record.api_data_handle, record.operation_id, &data);
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query HIP API data");
|
||||
assert(data);
|
||||
|
||||
// Reinterpret as an HIP API data pointer.
|
||||
return *reinterpret_cast<const hip_api_data_t*>(data);
|
||||
}
|
||||
|
||||
// Queries and returns the kernel name of the record `record` and
|
||||
// session ID `session_id`.
|
||||
//
|
||||
// Returns an empty string if not available.
|
||||
static std::string QueryKernelName(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
const auto kernel_name = QueryAllocStr(
|
||||
[&record, session_id](const auto size) {
|
||||
return rocprofiler_query_hip_tracer_api_data_info_size(
|
||||
session_id, ROCPROFILER_HIP_KERNEL_NAME, record.api_data_handle, record.operation_id,
|
||||
size);
|
||||
},
|
||||
[&record, session_id](const auto str) {
|
||||
return rocprofiler_query_hip_tracer_api_data_info(session_id, ROCPROFILER_HIP_KERNEL_NAME,
|
||||
record.api_data_handle,
|
||||
record.operation_id, str);
|
||||
});
|
||||
|
||||
if (kernel_name.size() > 1) {
|
||||
// Return demangled version.
|
||||
return rocmtools::cxx_demangle(kernel_name);
|
||||
}
|
||||
|
||||
return kernel_name;
|
||||
}
|
||||
|
||||
hip_api_data_t api_data_;
|
||||
std::string kernel_name_;
|
||||
};
|
||||
|
||||
// HIP API event record (beginning).
|
||||
class HipApiEventRecordBegin final : public HipApiEventRecord {
|
||||
public:
|
||||
explicit HipApiEventRecordBegin(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: HipApiEventRecord{record, session_id, GetRecordBeginClockVal(record)} {}
|
||||
|
||||
void Write(barectf_hip_api_ctx& barectf_ctx) const override {
|
||||
// Include generated switch statement.
|
||||
#include "hip_begin.cpp.i"
|
||||
}
|
||||
};
|
||||
|
||||
// HIP API event record (end).
|
||||
class HipApiEventRecordEnd final : public HipApiEventRecord {
|
||||
public:
|
||||
explicit HipApiEventRecordEnd(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: HipApiEventRecord{record, session_id, GetRecordEndClockVal(record)} {}
|
||||
|
||||
void Write(barectf_hip_api_ctx& barectf_ctx) const override {
|
||||
// Include generated switch statement.
|
||||
#include "hip_end.cpp.i"
|
||||
}
|
||||
};
|
||||
|
||||
// HSA API handle type event record.
|
||||
class HsaHandleTypeEventRecord final : public BarectfEventRecord<barectf_hsa_handles_ctx> {
|
||||
public:
|
||||
enum class Type {
|
||||
CPU = 0,
|
||||
GPU = 1,
|
||||
};
|
||||
|
||||
explicit HsaHandleTypeEventRecord(const std::uint64_t handle, const Type type)
|
||||
: BarectfEventRecord<barectf_hsa_handles_ctx>{0}, handle_{handle}, type_{type} {}
|
||||
|
||||
void Write(barectf_hsa_handles_ctx& barectf_ctx) const override {
|
||||
barectf_hsa_handles_trace_hsa_handle_type(&barectf_ctx, handle_,
|
||||
static_cast<std::uint8_t>(type_));
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint64_t handle_;
|
||||
Type type_;
|
||||
};
|
||||
|
||||
// Abstract API operation event record.
|
||||
class ApiOpEventRecord : public TracerEventRecord<barectf_api_ops_ctx> {
|
||||
protected:
|
||||
explicit ApiOpEventRecord(const rocprofiler_record_tracer_t& record, const std::uint64_t clock_val)
|
||||
: TracerEventRecord<barectf_api_ops_ctx>{record, clock_val} {}
|
||||
};
|
||||
|
||||
// HSA API operation event record (beginning).
|
||||
class HsaOpEventRecordBegin final : public ApiOpEventRecord {
|
||||
public:
|
||||
explicit HsaOpEventRecordBegin(const rocprofiler_record_tracer_t& record)
|
||||
: ApiOpEventRecord{record, GetRecordBeginClockVal(record)} {}
|
||||
|
||||
void Write(barectf_api_ops_ctx& barectf_ctx) const override {
|
||||
barectf_api_ops_trace_hsa_op_begin(&barectf_ctx, GetThreadId(), GetQueueId(), GetAgentId(),
|
||||
GetCorrelationId());
|
||||
}
|
||||
};
|
||||
|
||||
// HSA API operation event record (end).
|
||||
class HsaOpEventRecordEnd final : public ApiOpEventRecord {
|
||||
public:
|
||||
explicit HsaOpEventRecordEnd(const rocprofiler_record_tracer_t& record)
|
||||
: ApiOpEventRecord{record, GetRecordEndClockVal(record)} {}
|
||||
|
||||
void Write(barectf_api_ops_ctx& barectf_ctx) const override {
|
||||
barectf_api_ops_trace_hsa_op_end(&barectf_ctx, GetThreadId(), GetQueueId(), GetAgentId(),
|
||||
GetCorrelationId());
|
||||
}
|
||||
};
|
||||
|
||||
// HIP API operation event record (beginning).
|
||||
class HipOpEventRecordBegin final : public ApiOpEventRecord {
|
||||
public:
|
||||
explicit HipOpEventRecordBegin(const rocprofiler_record_tracer_t& record)
|
||||
: ApiOpEventRecord{record, GetRecordBeginClockVal(record)},
|
||||
kernel_name_{QueryKernelName(record)} {}
|
||||
|
||||
void Write(barectf_api_ops_ctx& barectf_ctx) const override {
|
||||
barectf_api_ops_trace_hip_op_begin(&barectf_ctx, GetThreadId(), GetQueueId(), GetAgentId(),
|
||||
GetCorrelationId(), kernel_name_.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
// Queries and returns the kernel name of the record `record`.
|
||||
//
|
||||
// Returns an empty string if not available.
|
||||
static std::string QueryKernelName(const rocprofiler_record_tracer_t& record) {
|
||||
if (record.operation_id.id == 0) {
|
||||
if (const auto api_handle = record.api_data_handle.handle) {
|
||||
const auto str = reinterpret_cast<const char*>(api_handle);
|
||||
|
||||
if (std::strlen(str) > 1) {
|
||||
// Return demangled version.
|
||||
return rocmtools::cxx_demangle(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string kernel_name_;
|
||||
};
|
||||
|
||||
// HIP API operation event record (end).
|
||||
class HipOpEventRecordEnd final : public ApiOpEventRecord {
|
||||
public:
|
||||
explicit HipOpEventRecordEnd(const rocprofiler_record_tracer_t& record)
|
||||
: ApiOpEventRecord{record, GetRecordEndClockVal(record)} {}
|
||||
|
||||
void Write(barectf_api_ops_ctx& barectf_ctx) const override {
|
||||
barectf_api_ops_trace_hip_op_end(&barectf_ctx, GetThreadId(), GetQueueId(), GetAgentId(),
|
||||
GetCorrelationId());
|
||||
}
|
||||
};
|
||||
|
||||
// Profiler record base.
|
||||
class ProfilerEventRecord : public BarectfEventRecord<barectf_profiler_ctx> {
|
||||
public:
|
||||
explicit ProfilerEventRecord(const rocprofiler_record_profiler_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: BarectfEventRecord<barectf_profiler_ctx>{GetRecordBeginClockVal(record)},
|
||||
dispatch_{record.header.id.handle},
|
||||
gpu_id_{record.gpu_id.handle},
|
||||
queue_id_{record.queue_id.handle},
|
||||
queue_index_{record.queue_idx.value},
|
||||
process_id_{GetPid()},
|
||||
thread_id_{record.thread_id.value},
|
||||
kernel_id_{record.kernel_id.handle},
|
||||
kernel_name_{QueryKernelName(record)},
|
||||
counter_infos_{QueryCounterInfos(record, session_id)} {}
|
||||
|
||||
void Write(barectf_profiler_ctx& barectf_ctx) const override {
|
||||
barectf_profiler_trace_profiler_record(
|
||||
&barectf_ctx, dispatch_, gpu_id_, queue_id_, queue_index_, process_id_, thread_id_,
|
||||
kernel_id_, kernel_name_.c_str(), counter_infos_.names.size(), counter_infos_.names.data(),
|
||||
counter_infos_.values.size(), counter_infos_.values.data());
|
||||
}
|
||||
|
||||
protected:
|
||||
// Counter infos.
|
||||
//
|
||||
// `names[i]` names the counter value `values[i]`.
|
||||
struct CounterInfos final {
|
||||
// `names_storage` owns the strings while the elements of `names`
|
||||
// point to the internal C strings of `names_storage`.
|
||||
//
|
||||
// This is needed because barectf expects an array of contiguous
|
||||
// C string pointers.
|
||||
std::vector<std::string> names_storage;
|
||||
std::vector<const char*> names;
|
||||
|
||||
// Counter values.
|
||||
std::vector<std::uint64_t> values;
|
||||
};
|
||||
|
||||
std::uint64_t GetDispatch() const noexcept { return dispatch_; }
|
||||
std::uint64_t GetGpuId() const noexcept { return gpu_id_; }
|
||||
std::uint64_t GetQueueId() const noexcept { return queue_id_; }
|
||||
std::uint64_t GetQueueIndex() const noexcept { return queue_index_; }
|
||||
std::uint32_t GetProcessId() const noexcept { return process_id_; }
|
||||
std::uint32_t GetThreadId() const noexcept { return thread_id_; }
|
||||
std::uint64_t GetKernelId() const noexcept { return kernel_id_; }
|
||||
const std::string& GetKernelName() const noexcept { return kernel_name_; }
|
||||
const CounterInfos& GetCounterInfos() const noexcept { return counter_infos_; }
|
||||
|
||||
private:
|
||||
// Queries and returns the kernel name of the record `record`.
|
||||
//
|
||||
// Returns an empty string if not available.
|
||||
static std::string QueryKernelName(const rocprofiler_record_profiler_t& record) {
|
||||
const auto kernel_name = QueryAllocStr(
|
||||
[&record](const auto size) {
|
||||
return rocprofiler_query_kernel_info_size(ROCPROFILER_KERNEL_NAME, record.kernel_id, size);
|
||||
},
|
||||
[&record](const auto str) {
|
||||
return rocprofiler_query_kernel_info(ROCPROFILER_KERNEL_NAME, record.kernel_id,
|
||||
const_cast<const char**>(str));
|
||||
});
|
||||
|
||||
if (kernel_name.size() <= 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Return truncated and demangled version.
|
||||
return rocmtools::truncate_name(rocmtools::cxx_demangle(kernel_name));
|
||||
}
|
||||
|
||||
// Queries and returns the counter infos of the record `record` and
|
||||
// session ID `session_id`.
|
||||
static CounterInfos QueryCounterInfos(const rocprofiler_record_profiler_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
if (!record.counters) {
|
||||
// No counters.
|
||||
return {};
|
||||
}
|
||||
|
||||
CounterInfos infos;
|
||||
|
||||
for (std::size_t i = 0; i < record.counters_count.value; ++i) {
|
||||
auto& counter = record.counters[i];
|
||||
|
||||
if (counter.counter_handler.handle == 0) {
|
||||
// Not available: continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query counter name size first
|
||||
std::size_t counter_name_size = 0;
|
||||
[[maybe_unused]] auto ret = rocprofiler_query_counter_info_size(
|
||||
session_id, ROCPROFILER_COUNTER_NAME, counter.counter_handler, &counter_name_size);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query counter name size");
|
||||
|
||||
if (counter_name_size == 0) {
|
||||
// No size: continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query counter name (borrowed from `record`: no need to free).
|
||||
const char* counter_name = nullptr;
|
||||
|
||||
ret = rocprofiler_query_counter_info(session_id, ROCPROFILER_COUNTER_NAME,
|
||||
counter.counter_handler, &counter_name);
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Query counter name");
|
||||
|
||||
if (!counter_name) {
|
||||
// Not available: continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Push back infos.
|
||||
infos.names_storage.emplace_back(counter_name);
|
||||
infos.names.push_back(infos.names_storage.back().c_str());
|
||||
infos.values.push_back(counter.value.value);
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
std::uint64_t dispatch_;
|
||||
std::uint64_t gpu_id_;
|
||||
std::uint64_t queue_id_;
|
||||
std::uint64_t queue_index_;
|
||||
std::uint32_t process_id_;
|
||||
std::uint32_t thread_id_;
|
||||
std::uint64_t kernel_id_;
|
||||
std::string kernel_name_;
|
||||
CounterInfos counter_infos_;
|
||||
};
|
||||
|
||||
// Profiler record base.
|
||||
class ProfilerWithKernelPropsEventRecord final : public ProfilerEventRecord {
|
||||
private:
|
||||
// According to `plugin/file/file.cpp`:
|
||||
//
|
||||
// > Taken from rocprofiler: The size hasn't changed in recent past
|
||||
static constexpr std::uint32_t lds_block_size_ = 128 * 4;
|
||||
|
||||
public:
|
||||
explicit ProfilerWithKernelPropsEventRecord(const rocprofiler_record_profiler_t& record,
|
||||
const rocprofiler_session_id_t session_id)
|
||||
: ProfilerEventRecord{record, session_id},
|
||||
grid_size_{record.kernel_properties.grid_size},
|
||||
workgroup_size_{record.kernel_properties.workgroup_size},
|
||||
lds_size_{
|
||||
((record.kernel_properties.lds_size + (lds_block_size_ - 1)) & ~(lds_block_size_ - 1))},
|
||||
scratch_size_{record.kernel_properties.scratch_size},
|
||||
arch_vgpr_count_{record.kernel_properties.arch_vgpr_count},
|
||||
accum_vgpr_count_{record.kernel_properties.accum_vgpr_count},
|
||||
sgpr_count_{record.kernel_properties.sgpr_count},
|
||||
wave_size_{record.kernel_properties.wave_size},
|
||||
signal_handle_{record.kernel_properties.signal_handle} {}
|
||||
|
||||
void Write(barectf_profiler_ctx& barectf_ctx) const override {
|
||||
barectf_profiler_trace_profiler_record_with_kernel_properties(
|
||||
&barectf_ctx, GetDispatch(), GetGpuId(), GetQueueId(), GetQueueIndex(), GetProcessId(),
|
||||
GetThreadId(), GetKernelId(), GetKernelName().c_str(), GetCounterInfos().names.size(),
|
||||
GetCounterInfos().names.data(), GetCounterInfos().values.size(),
|
||||
GetCounterInfos().values.data(), grid_size_, workgroup_size_, lds_size_, scratch_size_,
|
||||
arch_vgpr_count_, accum_vgpr_count_, sgpr_count_, wave_size_, signal_handle_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint64_t grid_size_;
|
||||
std::uint64_t workgroup_size_;
|
||||
std::uint64_t lds_size_;
|
||||
std::uint64_t scratch_size_;
|
||||
std::uint64_t arch_vgpr_count_;
|
||||
std::uint64_t accum_vgpr_count_;
|
||||
std::uint64_t sgpr_count_;
|
||||
std::uint64_t wave_size_;
|
||||
std::uint64_t signal_handle_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Plugin::Plugin(const std::size_t packet_size, const fs::path& trace_dir,
|
||||
const fs::path& metadata_stream_path)
|
||||
: roctx_tracer_{packet_size, trace_dir, "roctx_"},
|
||||
hsa_api_tracer_{packet_size, trace_dir, "hsa_api_"},
|
||||
hip_api_tracer_{packet_size, trace_dir, "hip_api_"},
|
||||
api_ops_tracer_{packet_size, trace_dir, "api_ops_"},
|
||||
hsa_handles_tracer_{packet_size, trace_dir, "hsa_handles_"},
|
||||
profiler_tracer_{packet_size, trace_dir, "profiler_"} {
|
||||
// Make sure the trace directory doesn't exist.
|
||||
if (fs::exists(trace_dir)) {
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "CTF trace directory `" << trace_dir.string() << "` already exists";
|
||||
throw std::runtime_error{ss.str()};
|
||||
}
|
||||
|
||||
// Make sure the metadata stream file exists.
|
||||
if (!fs::exists(metadata_stream_path)) {
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "CTF metadata stream file `" << metadata_stream_path.string() << "` doesn't exist";
|
||||
throw std::runtime_error{ss.str()};
|
||||
}
|
||||
|
||||
// Create trace directory.
|
||||
if (!fs::create_directory(trace_dir)) {
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "Cannot create the CTF trace directory `" << trace_dir.string() << "`";
|
||||
throw std::runtime_error{ss.str()};
|
||||
}
|
||||
|
||||
// Copy adjusted metadata stream file to trace directory.
|
||||
try {
|
||||
CopyAdjustedMetadataStreamFile(metadata_stream_path, trace_dir);
|
||||
} catch (const std::exception& exc) {
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "Cannot adjust and copy metadata stream file `" << metadata_stream_path.string()
|
||||
<< "` to the CTF trace directory `" << trace_dir.string() << "`: " << exc.what();
|
||||
throw std::runtime_error{ss.str()};
|
||||
}
|
||||
|
||||
// Write HSA handle type event records.
|
||||
WriteHsaHandleTypes();
|
||||
}
|
||||
|
||||
void Plugin::HandleTracerRecord(const rocprofiler_record_tracer_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
std::lock_guard<std::mutex> lock{lock_};
|
||||
|
||||
// Depending on the domain, create and add an event record to the
|
||||
// corresponding tracer.
|
||||
switch (record.domain) {
|
||||
case ACTIVITY_DOMAIN_ROCTX:
|
||||
roctx_tracer_.AddEventRecord(std::make_shared<const RocTxEventRecord>(record, session_id));
|
||||
break;
|
||||
case ACTIVITY_DOMAIN_HSA_API: {
|
||||
hsa_api_tracer_.AddEventRecord(
|
||||
std::make_shared<const HsaApiEventRecordBegin>(record, session_id));
|
||||
hsa_api_tracer_.AddEventRecord(
|
||||
std::make_shared<const HsaApiEventRecordEnd>(record, session_id));
|
||||
break;
|
||||
}
|
||||
case ACTIVITY_DOMAIN_HIP_API: {
|
||||
hip_api_tracer_.AddEventRecord(
|
||||
std::make_shared<const HipApiEventRecordBegin>(record, session_id));
|
||||
hip_api_tracer_.AddEventRecord(
|
||||
std::make_shared<const HipApiEventRecordEnd>(record, session_id));
|
||||
break;
|
||||
}
|
||||
case ACTIVITY_DOMAIN_HSA_OPS:
|
||||
api_ops_tracer_.AddEventRecord(std::make_shared<const HsaOpEventRecordBegin>(record));
|
||||
api_ops_tracer_.AddEventRecord(std::make_shared<const HsaOpEventRecordEnd>(record));
|
||||
break;
|
||||
case ACTIVITY_DOMAIN_HIP_OPS:
|
||||
api_ops_tracer_.AddEventRecord(std::make_shared<const HipOpEventRecordBegin>(record));
|
||||
api_ops_tracer_.AddEventRecord(std::make_shared<const HipOpEventRecordEnd>(record));
|
||||
break;
|
||||
default:
|
||||
// Warn
|
||||
std::cerr << "rocm_ctf::Plugin::HandleTracerRecord(): "
|
||||
<< "ignoring record for unknown domain #" << record.domain << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Plugin::HandleProfilerRecord(const rocprofiler_record_profiler_t& record,
|
||||
const rocprofiler_session_id_t session_id) {
|
||||
std::lock_guard<std::mutex> lock{lock_};
|
||||
profiler_tracer_.AddEventRecord(
|
||||
std::make_shared<const ProfilerWithKernelPropsEventRecord>(record, session_id));
|
||||
}
|
||||
|
||||
void Plugin::HandleBufferRecords(const rocprofiler_record_header_t* begin,
|
||||
const rocprofiler_record_header_t* const end,
|
||||
const rocprofiler_session_id_t session_id,
|
||||
const rocprofiler_buffer_id_t buffer_id) {
|
||||
while (begin && begin < end) {
|
||||
if (begin->kind == ROCPROFILER_TRACER_RECORD) {
|
||||
HandleTracerRecord(*reinterpret_cast<const rocprofiler_record_tracer_t*>(begin), session_id);
|
||||
} else {
|
||||
assert(begin->kind == ROCPROFILER_PROFILER_RECORD);
|
||||
HandleProfilerRecord(*reinterpret_cast<const rocprofiler_record_profiler_t*>(begin),
|
||||
session_id);
|
||||
}
|
||||
|
||||
rocprofiler_next_record(begin, &begin, session_id, buffer_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Plugin::WriteHsaHandleTypes() {
|
||||
[[maybe_unused]] const auto status = hsa_iterate_agents(
|
||||
[](const auto agent, const auto user_data) {
|
||||
auto& tracer = *static_cast<HsaHandlesTracer*>(user_data);
|
||||
hsa_device_type_t type;
|
||||
|
||||
if (hsa_agent_get_info(agent, HSA_AGENT_INFO_DEVICE, &type) != HSA_STATUS_SUCCESS) {
|
||||
return HSA_STATUS_ERROR;
|
||||
}
|
||||
|
||||
using Type = HsaHandleTypeEventRecord::Type;
|
||||
|
||||
auto event_record = std::make_shared<HsaHandleTypeEventRecord>(
|
||||
agent.handle, type == HSA_DEVICE_TYPE_CPU ? Type::CPU : Type::GPU);
|
||||
|
||||
tracer.AddEventRecord(std::move(event_record));
|
||||
return HSA_STATUS_SUCCESS;
|
||||
},
|
||||
&hsa_handles_tracer_);
|
||||
|
||||
assert(status == HSA_STATUS_SUCCESS && "Iterate HSA agents");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t ns_per_s = 1'000'000'000ULL;
|
||||
|
||||
// Samples the ROCMTools clock and returns the value.
|
||||
std::uint64_t GetClkVal() {
|
||||
rocprofiler_timestamp_t ts;
|
||||
[[maybe_unused]] const auto ret = rocprofiler_get_timestamp(&ts);
|
||||
|
||||
assert(ret == ROCPROFILER_STATUS_SUCCESS && "Get timestamp");
|
||||
return ts.value;
|
||||
}
|
||||
|
||||
// Updates `offset` and `delta`, if needed, to a more accurate clock
|
||||
// class offset and a smaller ROCMTools clock value delta.
|
||||
//
|
||||
// This function samples the ROCMTools clock twice, also sampling the
|
||||
// real-time clock in between, and uses the average ROCMTools clock
|
||||
// value to approximate the actual clock class offset.
|
||||
//
|
||||
// This strategy is based on the measure_single_clock_offset() function
|
||||
// of the LTTng-tools project <https://lttng.org/>.
|
||||
void UpdateClkClsOffsetAndDelta(std::uint64_t& offset, std::uint64_t& delta) {
|
||||
// Sample ROCMTools clock (first time).
|
||||
const auto rocm_clk_val1 = GetClkVal();
|
||||
|
||||
// Sample real-time clock.
|
||||
timespec realtime_spec = {0, 0};
|
||||
[[maybe_unused]] const auto ret = clock_gettime(CLOCK_REALTIME, &realtime_spec);
|
||||
|
||||
assert(ret == 0);
|
||||
|
||||
// Sample ROCMTools clock (second time).
|
||||
const auto rocm_clk_val2 = GetClkVal();
|
||||
|
||||
// Compute the current ROCMTools clock value delta.
|
||||
const auto this_delta = rocm_clk_val2 - rocm_clk_val1;
|
||||
|
||||
if (this_delta > delta) {
|
||||
// Discard larger delta.
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the average ROCMTools clock value.
|
||||
const auto rocm_clk_val_avg = (rocm_clk_val1 + rocm_clk_val2) >> 1;
|
||||
|
||||
// Compute the real-time clock value in nanoseconds.
|
||||
const auto realtime_ns =
|
||||
(static_cast<std::uint64_t>(realtime_spec.tv_sec) * ns_per_s) + realtime_spec.tv_nsec;
|
||||
|
||||
// Update clock class offset and delta.
|
||||
assert(rocm_clk_val_avg < realtime_ns);
|
||||
offset = realtime_ns - rocm_clk_val_avg;
|
||||
delta = this_delta;
|
||||
}
|
||||
|
||||
// Computes and returns the most possible accurate clock class offset.
|
||||
std::uint64_t GetMetadataClkClsOffset() {
|
||||
std::uint64_t offset = 0;
|
||||
std::uint64_t delta = std::numeric_limits<std::uint64_t>::max();
|
||||
|
||||
// Best effort to find the most accurate offset.
|
||||
for (auto i = 0U; i < 50U; ++i) {
|
||||
UpdateClkClsOffsetAndDelta(offset, delta);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Plugin::CopyAdjustedMetadataStreamFile(const fs::path& metadata_stream_path,
|
||||
const fs::path& trace_dir) {
|
||||
// Load installed metadata stream file contents.
|
||||
std::string metadata;
|
||||
std::getline(std::ifstream{metadata_stream_path}, metadata, '\0');
|
||||
|
||||
// Replace the original `offset` property.
|
||||
{
|
||||
static constexpr auto offset_term = "offset = 0;";
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "offset = " << GetMetadataClkClsOffset() << ';';
|
||||
metadata.replace(metadata.find(offset_term), std::strlen(offset_term), ss.str());
|
||||
}
|
||||
|
||||
// Write adjusted metadata stream to trace directory.
|
||||
{
|
||||
std::ofstream output{trace_dir / "metadata"};
|
||||
|
||||
output.write(metadata.data(), metadata.size());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocm_ctf
|
||||
@@ -0,0 +1,146 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
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. */
|
||||
|
||||
#ifndef PLUGIN_CTF_PLUGIN_H
|
||||
#define PLUGIN_CTF_PLUGIN_H
|
||||
|
||||
#include <mutex>
|
||||
#include <cstdlib>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
#include "rocprofiler.h"
|
||||
#include "rocprofiler_plugin.h"
|
||||
|
||||
#include "barectf.h"
|
||||
#include "barectf_tracer.h"
|
||||
|
||||
namespace rocm_ctf {
|
||||
|
||||
// CTF plugin.
|
||||
//
|
||||
// Build a plugin instance, and then call HandleTracerRecord(),
|
||||
// HandleProfilerRecord(), and HandleBufferRecords() to add event
|
||||
// records.
|
||||
//
|
||||
// A plugin instance performs important tasks at destruction time.
|
||||
class Plugin final {
|
||||
public:
|
||||
// Builds a plugin instance to write a CTF trace in the `trace_dir`
|
||||
// directory with packets of size `packet_size` bytes.
|
||||
//
|
||||
// `trace_dir` must not exist.
|
||||
//
|
||||
// This constructor immediately adjusts and copies the metadata stream
|
||||
// file `metadata_stream_path` to the trace directory (`trace_dir`).
|
||||
explicit Plugin(std::size_t packet_size, const std::experimental::filesystem::path& trace_dir,
|
||||
const std::experimental::filesystem::path& metadata_stream_path);
|
||||
|
||||
// Handles a tracer record.
|
||||
void HandleTracerRecord(const rocprofiler_record_tracer_t& record,
|
||||
rocprofiler_session_id_t session_id);
|
||||
|
||||
// Handles a profiler record.
|
||||
void HandleProfilerRecord(const rocprofiler_record_profiler_t& record,
|
||||
rocprofiler_session_id_t session_id);
|
||||
|
||||
// Handles tracer or profiler records from `begin` to `end`
|
||||
// (excluded).
|
||||
void HandleBufferRecords(const rocprofiler_record_header_t* begin,
|
||||
const rocprofiler_record_header_t* end, rocprofiler_session_id_t session_id,
|
||||
rocprofiler_buffer_id_t buffer_id);
|
||||
|
||||
private:
|
||||
// rocTX barectf platform descriptor.
|
||||
struct RocTxPlatformDescr final {
|
||||
using Ctx = barectf_roctx_ctx;
|
||||
|
||||
static void OpenPacket(Ctx& ctx) { barectf_roctx_open_packet(&ctx); }
|
||||
static void ClosePacket(Ctx& ctx) { barectf_roctx_close_packet(&ctx); }
|
||||
};
|
||||
|
||||
// HSA API barectf platform descriptor.
|
||||
struct HsaApiPlatformDescr final {
|
||||
using Ctx = barectf_hsa_api_ctx;
|
||||
|
||||
static void OpenPacket(Ctx& ctx) { barectf_hsa_api_open_packet(&ctx); }
|
||||
static void ClosePacket(Ctx& ctx) { barectf_hsa_api_close_packet(&ctx); }
|
||||
};
|
||||
|
||||
// HIP API barectf platform descriptor.
|
||||
struct HipApiPlatformDescr final {
|
||||
using Ctx = barectf_hip_api_ctx;
|
||||
|
||||
static void OpenPacket(Ctx& ctx) { barectf_hip_api_open_packet(&ctx); }
|
||||
static void ClosePacket(Ctx& ctx) { barectf_hip_api_close_packet(&ctx); }
|
||||
};
|
||||
|
||||
// HSA handles barectf platform descriptor.
|
||||
struct HsaHandlesPlatformDescr final {
|
||||
using Ctx = barectf_hsa_handles_ctx;
|
||||
|
||||
static void OpenPacket(Ctx& ctx) { barectf_hsa_handles_open_packet(&ctx); }
|
||||
static void ClosePacket(Ctx& ctx) { barectf_hsa_handles_close_packet(&ctx); }
|
||||
};
|
||||
|
||||
// API operations barectf platform descriptor.
|
||||
struct ApiOpsPlatformDescr final {
|
||||
using Ctx = barectf_api_ops_ctx;
|
||||
|
||||
static void OpenPacket(Ctx& ctx) { barectf_api_ops_open_packet(&ctx); }
|
||||
static void ClosePacket(Ctx& ctx) { barectf_api_ops_close_packet(&ctx); }
|
||||
};
|
||||
|
||||
// Profiler barectf platform descriptor.
|
||||
struct ProfilerPlatformDescr final {
|
||||
using Ctx = barectf_profiler_ctx;
|
||||
|
||||
static void OpenPacket(Ctx& ctx) { barectf_profiler_open_packet(&ctx); }
|
||||
static void ClosePacket(Ctx& ctx) { barectf_profiler_close_packet(&ctx); }
|
||||
};
|
||||
|
||||
// barectf tracer for HSA handle mappings.
|
||||
using HsaHandlesTracer = BarectfTracer<HsaHandlesPlatformDescr>;
|
||||
|
||||
// Writes the HSA handle type mappings to a dedicated data stream
|
||||
// file.
|
||||
void WriteHsaHandleTypes();
|
||||
|
||||
// Loads the existing metadata stream file `metadata_stream_path`,
|
||||
// adjusts the `offset` property of its single clock class, and writes
|
||||
// the result to the `metadata` file within the `trace_dir` directory.
|
||||
void CopyAdjustedMetadataStreamFile(
|
||||
const std::experimental::filesystem::path& metadata_stream_path,
|
||||
const std::experimental::filesystem::path& trace_dir);
|
||||
|
||||
// Dedicated tracers.
|
||||
BarectfTracer<RocTxPlatformDescr> roctx_tracer_;
|
||||
BarectfTracer<HsaApiPlatformDescr> hsa_api_tracer_;
|
||||
BarectfTracer<HipApiPlatformDescr> hip_api_tracer_;
|
||||
BarectfTracer<ApiOpsPlatformDescr> api_ops_tracer_;
|
||||
HsaHandlesTracer hsa_handles_tracer_;
|
||||
BarectfTracer<ProfilerPlatformDescr> profiler_tracer_;
|
||||
|
||||
// Locks any operation performed on the data of this.
|
||||
std::mutex lock_;
|
||||
};
|
||||
|
||||
} // namespace rocm_ctf
|
||||
|
||||
#endif // PLUGIN_CTF_PLUGIN_H
|
||||
Reference in New Issue
Block a user