PC sampling: online partial PC sampling decoding (#1004)

* PC sampling: online partial PC sampling decoding

PC sampling service decodes a PC sample partially
by replacing the PC with an id of the loaded code object instance
containing PC and the offset of the PC within that code object instance.

* PC sampling: marker records removed

* PC sampling parser: minor doc update in mock

* PC sampling: introducing rocprofiler_pc_t

* NULL value of the code object id introduced.

* Clarifying documenation related to PC offset.

* PC offset documentation improvement

* PC sampling parser benchmark: Reducing the number of samples to recreate half of performance.
This commit is contained in:
Vladimir Indic
2024-09-05 18:35:46 +02:00
committed by GitHub
parent fa91169479
commit 93e82663d9
12 changed files with 160 additions and 129 deletions
@@ -26,6 +26,7 @@
# include "lib/common/container/operators.hpp"
# include "lib/common/logging.hpp"
# include "lib/common/static_object.hpp"
# include "lib/rocprofiler-sdk/code_object/code_object.hpp"
# include "lib/rocprofiler-sdk/pc_sampling/service.hpp"
@@ -44,6 +45,13 @@ namespace pc_sampling
{
namespace code_object
{
CodeobjTableTranslatorSynchronized*
get_code_object_translator()
{
static auto*& _v = common::static_object<CodeobjTableTranslatorSynchronized>::construct();
return _v;
}
namespace
{
auto&
@@ -74,14 +82,13 @@ get_destroy_function()
* @param [in] code_object - loaded/unloaded code object.
*/
void
flush_buffers_generate_marker_record(rocprofiler_callback_phase_t phase,
const rocprofiler::code_object::hsa::code_object& code_object)
flush_pc_sampling_buffers(const rocprofiler::code_object::hsa::code_object& code_object)
{
auto agent_id = code_object.rocp_data.rocp_agent;
if(!is_pc_sample_service_configured(agent_id)) return;
// The PC sampling service is configured on the agent.
// Find the agent's buffer and place marker record.
// The PC sampling service is configured on the agent,
// so flush its PC sampling buffer
// TODO: Creating a function that gives the buffer_id based on the agent_id?
const auto* pcs_service = get_configured_pc_sampling_service().load();
const auto* agent_session = pcs_service->agent_sessions.at(agent_id).get();
@@ -89,46 +96,6 @@ flush_buffers_generate_marker_record(rocprofiler_callback_phase_t
// flush internal PC sampling buffers
flush_internal_agent_buffers(agent_buffer_id);
auto* buff = rocprofiler::buffer::get_buffer(agent_buffer_id);
// create code object load/unload marker record and emplace it into the SDK's PC SAMPLING
// buffer.
if(phase == ROCPROFILER_CALLBACK_PHASE_LOAD)
{
auto marker =
common::init_public_api_struct(rocprofiler_pc_sampling_code_object_load_marker_t{});
marker.code_object_id = code_object.rocp_data.code_object_id;
// emplace marker to the SDK's PC sampling buffer
buff->emplace(ROCPROFILER_BUFFER_CATEGORY_PC_SAMPLING,
ROCPROFILER_PC_SAMPLING_RECORD_CODE_OBJECT_LOAD_MARKER,
marker);
}
else
{
auto marker =
common::init_public_api_struct(rocprofiler_pc_sampling_code_object_unload_marker_t{});
marker.code_object_id = code_object.rocp_data.code_object_id;
// emplace marker to the SDK's PC sampling buffer
buff->emplace(ROCPROFILER_BUFFER_CATEGORY_PC_SAMPLING,
ROCPROFILER_PC_SAMPLING_RECORD_CODE_OBJECT_UNLOAD_MARKER,
marker);
}
// Assuming that the `rocprofiler_pc_sampling_code_object_load_marker_t` and
// `rocprofiler_pc_sampling_code_object_unload_marker_t` share the same content,
// we could replace the previous if else with the following
/*
auto marker =
common::init_public_api_struct(rocprofiler_pc_sampling_code_object_load_marker_t{});
marker.code_object_id = code_object.rocp_data.code_object_id;
// emplace marker to the SDK's PC sampling buffer
buff->emplace(ROCPROFILER_BUFFER_CATEGORY_PC_SAMPLING,
(phase == ROCPROFILER_CALLBACK_PHASE_LOAD) ?
ROCPROFILER_PC_SAMPLING_RECORD_CODE_OBJECT_LOAD_MARKER
: ROCPROFILER_PC_SAMPLING_RECORD_CODE_OBJECT_UNLOAD_MARKER,
marker);
*/
}
hsa_status_t
@@ -141,7 +108,14 @@ executable_freeze(hsa_executable_t executable, const char* options)
rocprofiler::code_object::iterate_loaded_code_objects(
[&](const rocprofiler::code_object::hsa::code_object& code_object) {
if(code_object.hsa_executable == executable)
flush_buffers_generate_marker_record(ROCPROFILER_CALLBACK_PHASE_LOAD, code_object);
{
const auto& code_object_rocp = code_object.rocp_data;
get_code_object_translator()->insert(
address_range_t{code_object_rocp.load_base,
code_object_rocp.load_size,
code_object_rocp.code_object_id});
flush_pc_sampling_buffers(code_object);
}
});
return HSA_STATUS_SUCCESS;
@@ -153,8 +127,14 @@ executable_destroy(hsa_executable_t executable)
rocprofiler::code_object::iterate_loaded_code_objects(
[&](const rocprofiler::code_object::hsa::code_object& code_object) {
if(code_object.hsa_executable == executable)
flush_buffers_generate_marker_record(ROCPROFILER_CALLBACK_PHASE_UNLOAD,
code_object);
{
flush_pc_sampling_buffers(code_object);
const auto& code_object_rocp = code_object.rocp_data;
get_code_object_translator()->remove(
address_range_t{code_object_rocp.load_base,
code_object_rocp.load_size,
code_object_rocp.code_object_id});
}
});
// Call underlying function
@@ -183,7 +163,7 @@ finalize()
{
rocprofiler::code_object::iterate_loaded_code_objects(
[&](const rocprofiler::code_object::hsa::code_object& code_object) {
flush_buffers_generate_marker_record(ROCPROFILER_CALLBACK_PHASE_UNLOAD, code_object);
flush_pc_sampling_buffers(code_object);
});
}
@@ -22,12 +22,19 @@
#pragma once
#include "lib/common/static_object.hpp"
#include "lib/rocprofiler-sdk/pc_sampling/defines.hpp"
#if ROCPROFILER_SDK_HSA_PC_SAMPLING > 0
# include <rocprofiler-sdk/callback_tracing.h>
# include <rocprofiler-sdk/cxx/codeobj/segment.hpp>
# include <hsa/hsa_api_trace.h>
# include <mutex>
# include <shared_mutex>
namespace rocprofiler
{
namespace pc_sampling
@@ -39,6 +46,49 @@ initialize(HsaApiTable* table);
void
finalize();
using address_range_t = rocprofiler::sdk::codeobj::segment::address_range_t;
using CodeobjTableTranslator = rocprofiler::sdk::codeobj::segment::CodeobjTableTranslator;
class CodeobjTableTranslatorSynchronized : public CodeobjTableTranslator
{
using Super = CodeobjTableTranslator;
using code_object_id_t = uint64_t;
public:
// Must acquire write lock
void insert(address_range_t addr_range)
{
auto lock = std::unique_lock{mut};
this->Super::insert(addr_range);
}
// Must acquire write lock
bool remove(address_range_t addr_range)
{
auto lock = std::unique_lock{mut};
return this->Super::remove(addr_range);
}
// Must acquire read lock
address_range_t find_codeobj_in_range(uint64_t addr) const
{
// TODO: It would be good to have a way to cache search results
// (caching could be done easily in the parser)
auto lock = std::shared_lock{mut};
auto it = this->find(address_range_t{addr, 0, 0});
// `addr` might originate from an unknown code object.
if(it == this->end()) return address_range_t{0, 0, ROCPROFILER_CODE_OBJECT_ID_NONE};
return *it;
}
private:
mutable std::shared_mutex mut = {};
};
CodeobjTableTranslatorSynchronized*
get_code_object_translator();
} // namespace code_object
} // namespace pc_sampling
} // namespace rocprofiler
@@ -23,6 +23,7 @@
#pragma once
#include "lib/common/logging.hpp"
#include "lib/rocprofiler-sdk/pc_sampling/code_object.hpp"
#include "lib/rocprofiler-sdk/pc_sampling/parser/translation.hpp"
#include <rocprofiler-sdk/fwd.h>
@@ -186,22 +187,37 @@ add_upcoming_samples(const device_handle device,
Parser::CorrelationMap* corr_map,
rocprofiler_pc_sampling_record_t* samples)
{
pcsample_status_t status = PCSAMPLE_STATUS_SUCCESS;
pcsample_status_t status = PCSAMPLE_STATUS_SUCCESS;
auto cache_addr_range = rocprofiler::pc_sampling::code_object::address_range_t{0, 0, 0};
const auto* code_object_translator =
rocprofiler::pc_sampling::code_object::get_code_object_translator();
for(uint64_t p = 0; p < available_samples; p++)
{
const auto* snap = reinterpret_cast<const perf_sample_snapshot_v1*>(buffer + p);
samples[p] = copySample<bHostTrap, GFXIP>((const void*) (buffer + p));
samples[p].size = sizeof(rocprofiler_pc_sampling_record_t);
auto& pc_sample = samples[p];
pc_sample.size = sizeof(rocprofiler_pc_sampling_record_t);
// Convert PC -> (loaded code object id containing PC, offset within code object)
if(!cache_addr_range.inrange(snap->pc))
{
cache_addr_range = code_object_translator->find_codeobj_in_range(snap->pc);
}
pc_sample.pc.loaded_code_object_id = cache_addr_range.id;
pc_sample.pc.loaded_code_object_offset = snap->pc - cache_addr_range.addr;
try
{
Parser::trap_correlation_id_t trap{.raw = snap->correlation_id};
samples[p].correlation_id = corr_map->get(device, trap);
pc_sample.correlation_id = corr_map->get(device, trap);
} catch(std::exception& e)
{
samples[p].correlation_id = {.internal = ROCPROFILER_CORRELATION_ID_INTERNAL_NONE,
.external = rocprofiler_user_data_t{
.value = ROCPROFILER_CORRELATION_ID_INTERNAL_NONE}};
status = PCSAMPLE_STATUS_PARSER_ERROR;
pc_sample.correlation_id = {.internal = ROCPROFILER_CORRELATION_ID_INTERNAL_NONE,
.external = rocprofiler_user_data_t{
.value = ROCPROFILER_CORRELATION_ID_INTERNAL_NONE}};
status = PCSAMPLE_STATUS_PARSER_ERROR;
}
}
return status;
@@ -35,7 +35,7 @@ static bool
Benchmark(bool bWarmup)
{
constexpr size_t SAMPLE_PER_DISPATCH = 8192;
constexpr size_t DISP_PER_QUEUE = 12;
constexpr size_t DISP_PER_QUEUE = 8;
constexpr size_t NUM_QUEUES = MockDoorBell::num_unique_bells;
std::shared_ptr<MockRuntimeBuffer> buffer = std::make_shared<MockRuntimeBuffer>();
@@ -53,8 +53,10 @@ alloc_callback(rocprofiler_pc_sampling_record_t** buffer, uint64_t size, void* u
static bool
check_samples(rocprofiler_pc_sampling_record_t* samples, uint64_t size)
{
// TODO: replace with (code_obj_id, pc)
for(size_t i = 0; i < size; i++)
if(samples[i].correlation_id.internal != samples[i].pc) return false;
if(samples[i].correlation_id.internal != samples[i].pc.loaded_code_object_offset)
return false;
return true;
}
@@ -299,7 +299,10 @@ class WaveIssueAndErrorTest : public WaveSnapTest
{
rocprofiler_pc_sampling_record_t sample;
::memset(&sample, 0, sizeof(sample));
sample.pc = dispatch->unique_id;
// TODO: Since code objects are not mocked, use pc.loaded_code_object_offset
// as the absolute physical address of the mocked PC.
sample.pc.loaded_code_object_offset = dispatch->unique_id;
sample.correlation_id.internal = dispatch->getMockId().raw;
sample.flags.valid = valid && !error;
@@ -39,7 +39,6 @@ copySampleHeader(const SType& sample)
ret.flags = pcsample_header_v1_t{.raw = 0}.flags;
ret.flags.type = AMD_SNAPSHOT_V1;
ret.pc = sample.pc;
ret.exec_mask = sample.exec_mask;
ret.workgroup_id.x = sample.workgroup_id_x;
ret.workgroup_id.y = sample.workgroup_id_y;