Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

500 lines
20 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include "Agent.hpp"
#include <rocprofiler-sdk/buffer.h>
#include <rocprofiler-sdk/callback_tracing.h>
#include <rocprofiler-sdk/cxx/codeobj/code_printing.hpp>
#include <rocprofiler-sdk/experimental/thread_trace.h>
#include <rocprofiler-sdk/fwd.h>
#include <rocprofiler-sdk/registration.h>
#include <rocprofiler-sdk/rocprofiler.h>
#include <rocRoller/Utilities/Error.hpp>
#include <rocRoller/Utilities/HipUtils.hpp>
#include <rocRoller/Utilities/Logging.hpp>
#include <rocRoller/Utilities/Settings.hpp>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <thread>
// Based on https://github.com/ROCm/rocm-systems/tree/develop/projects/rocprofiler-sdk/samples/thread_trace
#define ROCPROFILER_CALL(result, msg) \
if(auto ec = (result); ec != ROCPROFILER_STATUS_SUCCESS) \
{ \
rocRoller::Throw<rocRoller::FatalError>( \
"rocprofiler-sdk error: ", rocprofiler_get_status_string(ec), " :: ", msg); \
}
// Set to false when another profiler (e.g. rocprofv3) is registered first
bool enable_agent = true;
namespace rocRoller
{
struct TraceDecodeCallbackUserData
{
bool ok;
profiler::InstructionLatencyMap instruction_map;
};
rocprofiler_thread_trace_decoder_id_t decoder{};
rocprofiler_context_id_t client_ctx{};
rocprofiler::sdk::codeobj::disassembly::CodeobjAddressTranslate address_table;
// To enable waiting for dispatch data
std::vector<uint8_t> profile_data;
std::condition_variable profile_data_cv;
std::mutex profile_data_mutex;
bool enable_profiler = false;
std::mutex enable_profiler_mutex;
void codeobj_callback(rocprofiler_callback_tracing_record_t record,
rocprofiler_user_data_t*,
void*)
{
if(record.kind != ROCPROFILER_CALLBACK_TRACING_CODE_OBJECT
|| record.operation != ROCPROFILER_CODE_OBJECT_LOAD
|| record.phase != ROCPROFILER_CALLBACK_PHASE_LOAD)
return;
auto* data
= static_cast<rocprofiler_callback_tracing_code_object_load_data_t*>(record.payload);
Log::debug("codeobj_callback: code_object_id {}, storage_type {}",
data->code_object_id,
static_cast<std::underlying_type_t<rocprofiler_code_object_storage_type_t>>(
data->storage_type));
if(data->storage_type == ROCPROFILER_CODE_OBJECT_STORAGE_TYPE_FILE)
{
address_table.addDecoder(
data->uri, data->code_object_id, data->load_delta, data->load_size);
}
else
{
auto* memorybase = reinterpret_cast<const void*>(data->memory_base);
if(memorybase)
{
rocprofiler_thread_trace_decoder_codeobj_load(decoder,
data->code_object_id,
data->load_delta,
data->load_size,
memorybase,
data->memory_size);
address_table.addDecoder(memorybase,
data->memory_size,
data->code_object_id,
data->load_delta,
data->load_size);
}
}
}
void trace_decode_callback(rocprofiler_thread_trace_decoder_record_type_t record_type_id,
void* events,
uint64_t num_events,
void* userdata_raw)
{
/*
Don't use AssertFatal macro, as that is caught as a rocprofiler error and masks the real issue.
Handle other record types https://rocm.docs.amd.com/projects/rocprofiler-sdk/en/latest/api-reference/thread_trace.html#trace-decoder-info-events
*/
Log::debug(
"trace_decode_callback: record_type_id {}, num_events {}",
static_cast<std::underlying_type_t<rocprofiler_thread_trace_decoder_record_type_t>>(
record_type_id),
num_events);
TraceDecodeCallbackUserData* userdata
= static_cast<TraceDecodeCallbackUserData*>(userdata_raw);
if(!userdata->ok)
return;
switch(record_type_id)
{
case ROCPROFILER_THREAD_TRACE_DECODER_RECORD_WAVE:
{
assert(num_events == 1 && "expected one event for WAVE record type");
auto* wave = static_cast<rocprofiler_thread_trace_decoder_wave_t*>(events);
Log::debug(" wave : cu {}, simd {}, wave_id {}, contexts {}, instructions_size {}",
wave->cu,
wave->simd,
wave->wave_id,
wave->contexts,
wave->instructions_size);
assert(wave->instructions_size >= 1 && "expected at least one instruction in wave");
uint32_t prev_time = wave->instructions_array[0].time;
uint32_t prev_latency = 0;
for(size_t i = 0; i < wave->instructions_size; i++)
{
auto& inst = wave->instructions_array[i];
Log::debug(" inst {}: code_object_id {}, address 0x{:x}, duration {}",
i,
inst.pc.code_object_id,
inst.pc.address,
inst.duration);
if(inst.pc.code_object_id == 0)
{
Log::warn("trace_decode_callback: code_object_id is 0");
assert(userdata->instruction_map.empty()
&& "expected all instructions to have code_object_id of 0 if any do");
userdata->ok = false;
return;
}
uint32_t latencyWithPrecedingNone = inst.time - prev_time;
Log::trace("trace_decode_callback: duration {}, stall {}, time {}, "
"prev_time {}, prev_latency {}, latencyWithPrecedingNone {}",
inst.duration,
static_cast<uint32_t>(inst.stall),
inst.time,
prev_time,
prev_latency,
latencyWithPrecedingNone);
prev_time = inst.time;
prev_latency = inst.duration;
auto& data = userdata->instruction_map[inst.pc];
data.totalLatency += inst.duration;
data.totalLatencyWithPrecedingNone += latencyWithPrecedingNone;
data.hitcount += 1;
}
return;
}
case ROCPROFILER_THREAD_TRACE_DECODER_RECORD_OCCUPANCY:
case ROCPROFILER_THREAD_TRACE_DECODER_RECORD_GFXIP:
case ROCPROFILER_THREAD_TRACE_DECODER_RECORD_REALTIME:
case ROCPROFILER_THREAD_TRACE_DECODER_RECORD_RT_FREQUENCY:
// these data not used currently
return;
case ROCPROFILER_THREAD_TRACE_DECODER_RECORD_INFO:
{
assert(num_events == 1 && "expected one event for INFO record type");
auto* info = static_cast<rocprofiler_thread_trace_decoder_info_t*>(events);
Log::warn("parse: record info {}",
static_cast<std::underlying_type_t<rocprofiler_thread_trace_decoder_info_t>>(
*info));
// Only seen these enumerations; investigate if others are encountered
assert(((*info == ROCPROFILER_THREAD_TRACE_DECODER_INFO_STITCH_INCOMPLETE)
|| (*info == ROCPROFILER_THREAD_TRACE_DECODER_INFO_DATA_LOST))
&& "received unexpected INFO record type");
userdata->ok = false;
userdata->instruction_map.clear();
return;
}
default:
Log::error(
"parse: unhandled record type {}",
static_cast<std::underlying_type_t<rocprofiler_thread_trace_decoder_record_type_t>>(
record_type_id));
assert(!"unhandled record type");
return;
}
}
void shader_data_callback(rocprofiler_agent_id_t agent,
int64_t shader_engine_id,
void* data,
size_t data_size,
rocprofiler_user_data_t userdata)
{
// Based on https://github.com/ROCm/rocm-systems/blob/e9dac39102606ac6f7ab2778f74745e27841fb6c/projects/rocprofiler-sdk/source/lib/rocprofiler-sdk-tool/tool.cpp#L1387-L1408
// Avoid decoding directly in this function body per https://rocm.docs.amd.com/projects/rocprofiler-sdk/en/latest/api-reference/thread_trace.html#processing-thread-trace-data
rocprofiler_dispatch_id_t dispatch_id
= static_cast<rocprofiler_dispatch_id_t>(userdata.value);
Log::debug("shader_data_callback: dispatch_id {}, data_size {}", dispatch_id, data_size);
// Note this exists to also prevent a deadlock, as profile_data.size() is checked under profile_data_cv.wait
assert(data != nullptr && data_size > 0 && "invalid shader data callback");
{
std::lock_guard lock(profile_data_mutex);
profile_data = std::vector<uint8_t>(static_cast<uint8_t*>(data),
static_cast<uint8_t*>(data) + data_size);
}
profile_data_cv.notify_all();
}
rocprofiler_thread_trace_control_flags_t
dispatch_callback(rocprofiler_agent_id_t agent_id,
rocprofiler_queue_id_t queue_id,
rocprofiler_async_correlation_id_t correlation_id,
rocprofiler_kernel_id_t kernel_id,
rocprofiler_dispatch_id_t dispatch_id,
void* userdata_config,
rocprofiler_user_data_t* userdata_shader)
{
// Protect against multiple dispatches, current behavior is to only profile the first dispatch after enabling
std::lock_guard lock(enable_profiler_mutex);
Log::debug(
"dispatch_callback: dispatch_id {}, enable_profiler {}", dispatch_id, enable_profiler);
if(enable_profiler)
{
userdata_shader->value = dispatch_id;
enable_profiler = false;
return ROCPROFILER_THREAD_TRACE_CONTROL_START_AND_STOP;
}
return ROCPROFILER_THREAD_TRACE_CONTROL_NONE;
}
rocprofiler_status_t query_agents(rocprofiler_agent_version_t,
const void** agents,
size_t num_agents,
void* user_data)
{
for(size_t idx = 0; idx < num_agents; idx++)
{
const auto* agent = static_cast<const rocprofiler_agent_v0_t*>(agents[idx]);
if(agent->type != ROCPROFILER_AGENT_TYPE_GPU)
continue;
auto parameters = std::vector<rocprofiler_thread_trace_parameter_t>{};
parameters.push_back({ROCPROFILER_THREAD_TRACE_PARAMETER_TARGET_CU, 1});
parameters.push_back({ROCPROFILER_THREAD_TRACE_PARAMETER_SHADER_ENGINE_MASK, 0x1});
parameters.push_back({ROCPROFILER_THREAD_TRACE_PARAMETER_SERIALIZE_ALL, 1});
parameters.push_back({ROCPROFILER_THREAD_TRACE_PARAMETER_BUFFER_SIZE, 0x100000000});
ROCPROFILER_CALL(
rocprofiler_configure_dispatch_thread_trace_service(client_ctx,
agent->id,
parameters.data(),
parameters.size(),
dispatch_callback,
shader_data_callback,
user_data),
"configure dispatch thread trace");
}
return ROCPROFILER_STATUS_SUCCESS;
}
int tool_init(rocprofiler_client_finalize_t, void*)
{
ROCPROFILER_CALL(
rocprofiler_thread_trace_decoder_create(
&decoder, (Settings::getInstance()->get(Settings::ROCMPath) + "/lib").c_str()),
"create decoder");
ROCPROFILER_CALL(rocprofiler_create_context(&client_ctx), "create context");
ROCPROFILER_CALL(
rocprofiler_configure_callback_tracing_service(client_ctx,
ROCPROFILER_CALLBACK_TRACING_CODE_OBJECT,
nullptr,
0,
codeobj_callback,
nullptr),
"configure code object tracing");
ROCPROFILER_CALL(rocprofiler_query_available_agents(ROCPROFILER_AGENT_INFO_VERSION_0,
&query_agents,
sizeof(rocprofiler_agent_t),
nullptr),
"query agents");
ROCPROFILER_CALL(rocprofiler_start_context(client_ctx), "start context");
return 0;
}
void tool_fini(void*)
{
rocprofiler_thread_trace_decoder_destroy(decoder);
}
namespace profiler
{
std::optional<std::vector<InstructionProfile>> waitForData()
{
if(!enable_agent)
return std::nullopt;
Log::debug("waitForData");
std::unique_lock<std::mutex> lock(profile_data_mutex);
profile_data_cv.wait(lock, [] { return !profile_data.empty(); });
Log::debug("waitForData: acquired profile data, decoding {} bytes",
profile_data.size());
TraceDecodeCallbackUserData callback_user_data{.ok = true, .instruction_map = {}};
const auto status = rocprofiler_trace_decode(decoder,
trace_decode_callback,
profile_data.data(),
profile_data.size(),
&callback_user_data);
Log::debug("waitForData: decoding complete, clearing {} bytes of profile data",
profile_data.size());
profile_data.clear();
lock.unlock();
if(status != ROCPROFILER_STATUS_SUCCESS)
{
Log::warn("waitForData: decoding error, rocprofiler_trace_decode returned {}",
rocprofiler_get_status_string(status));
return std::nullopt;
}
if(not callback_user_data.ok)
{
Log::warn("waitForData: decoding error in callback, check previous logs");
return std::nullopt;
}
if(callback_user_data.instruction_map.empty())
{
Log::warn("waitForData: no instructions decoded");
return std::nullopt;
}
for(auto& [pc, data] : callback_user_data.instruction_map)
{
auto inst = address_table.get(pc.code_object_id, pc.address);
if(inst != nullptr)
data.instruction = inst->inst;
}
std::vector<InstructionProfile> result;
result.reserve(callback_user_data.instruction_map.size());
for(const auto& [pc, data] : callback_user_data.instruction_map)
{
result.push_back(data);
}
Log::debug("waitForData: retrieved {} instructions", result.size());
return result;
}
std::optional<std::vector<InstructionProfile>>
getDispatchData(std::function<void()> dispatch)
{
Log::debug("getDispatchData");
HIP_CHECK(hipDeviceSynchronize()); // Ensure all prior dispatches finished
{
std::lock_guard lock(enable_profiler_mutex);
enable_profiler = true;
}
dispatch();
const auto data = waitForData();
return data;
}
std::vector<InstructionProfile> loopUntilDispatchData(std::function<void()> dispatch)
{
Log::debug("loopUntilDispatchData: starting loop to get dispatch data");
std::optional<std::vector<InstructionProfile>> data;
while(true)
{
data = getDispatchData(dispatch);
if(data.has_value())
{
return *data;
}
if(!enable_agent)
{
Log::debug(
"loopUntilDispatchData: profiler disabled, returning empty data set");
return {};
}
Log::debug("loopUntilDispatchData: got no data, invoking another dispatch");
}
}
void reset()
{
std::scoped_lock lock{profile_data_mutex, enable_profiler_mutex};
profile_data.clear();
enable_profiler = false;
}
uint64_t InstructionProfile::meanLatency() const
{
if(hitcount == 0)
return 0;
return totalLatency / hitcount;
}
uint64_t InstructionProfile::meanLatencyWithPrecedingNone() const
{
if(hitcount == 0)
return 0;
return totalLatencyWithPrecedingNone / hitcount;
}
std::string InstructionProfile::toString() const
{
return fmt::format("'{}', totalLatency: {}, totalLatencyWithPrecedingNone: {}, "
"hitcount: {}, meanLatency: {}, meanLatencyWithPrecedingNone: {}",
instruction,
totalLatency,
totalLatencyWithPrecedingNone,
hitcount,
meanLatency(),
meanLatencyWithPrecedingNone());
}
std::string toString(std::vector<InstructionProfile> const& profiles)
{
std::ostringstream result;
for(const auto& profile : profiles)
{
result << profile.toString() + "\n";
}
result << "\n";
return result.str();
}
} // namespace profiler
} // namespace rocRoller
extern "C" rocprofiler_tool_configure_result_t*
rocprofiler_configure(uint32_t, const char*, uint32_t priority, rocprofiler_client_id_t* id)
{
if(priority > 0)
{
enable_agent = false;
return nullptr;
}
id->name = "rocRoller rocprofiler: I'm a string literal, just do a text search to find me :)";
static auto cfg
= rocprofiler_tool_configure_result_t{sizeof(rocprofiler_tool_configure_result_t),
&rocRoller::tool_init,
&rocRoller::tool_fini,
nullptr};
return &cfg;
}