Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/rocprofiler-sdk/source/lib/output/csv.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ struct csv_encoder
};

using api_csv_encoder = csv_encoder<7>;
using hip_api_csv_encoder = csv_encoder<10>;
using agent_info_csv_encoder = csv_encoder<53>;
using counter_collection_csv_encoder = csv_encoder<19>;
using memory_allocation_csv_encoder = csv_encoder<8>;
Expand Down
20 changes: 16 additions & 4 deletions projects/rocprofiler-sdk/source/lib/output/generateCSV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,31 +339,43 @@ generate_csv(const output_config& cfg,

if(cfg.stats && stats) write_stats(get_stats_output_file(cfg, domain_type::HIP), stats.entries);

// HIP CSV is widened to 10 columns: in addition to the standard 7 it carries
// Stream_Id (the call's own stream) and the inter-stream sync metadata
// Wait_On_Stream_Id / Wait_On_Correlation_Id, which are populated only on
// hipStreamWaitEvent / hipEventSynchronize records whose awaited event was
// observed being recorded earlier in the same trace; otherwise the wait_on_*
// columns are 0 (default-stream-id / no-resolved-dependency sentinel).
auto ofs = tool::csv_output_file{cfg,
domain_type::HIP,
tool::csv::api_csv_encoder{},
tool::csv::hip_api_csv_encoder{},
{"Domain",
"Function",
"Process_Id",
"Thread_Id",
"Correlation_Id",
"Start_Timestamp",
"End_Timestamp"}};
"End_Timestamp",
"Stream_Id",
"Wait_On_Stream_Id",
"Wait_On_Correlation_Id"}};
for(auto ditr : data)
{
for(auto record : data.get(ditr))
{
auto row_ss = std::stringstream{};
auto api_name = tool_metadata.get_operation_name(record.kind, record.operation);
rocprofiler::tool::csv::api_csv_encoder::write_row(
rocprofiler::tool::csv::hip_api_csv_encoder::write_row(
row_ss,
tool_metadata.get_kind_name(record.kind),
api_name,
tool_metadata.process_id,
record.thread_id,
record.correlation_id.internal,
record.start_timestamp,
record.end_timestamp);
record.end_timestamp,
record.stream_id.handle,
record.wait_on_stream.handle,
record.wait_on_correlation_id);

ofs << row_ss.str();
}
Expand Down
86 changes: 63 additions & 23 deletions projects/rocprofiler-sdk/source/lib/output/generatePerfetto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,29 +366,69 @@ write_perfetto(
auto name = buffer_names.at(itr.kind, itr.operation);
auto& track = thread_tracks.at(itr.thread_id);

TRACE_EVENT_BEGIN(sdk::perfetto_category<sdk::category::hip_api>::name,
::perfetto::StaticString(name.data()),
track,
itr.start_timestamp,
::perfetto::Flow::ProcessScoped(itr.correlation_id.internal),
"begin_ns",
itr.start_timestamp,
"end_ns",
itr.end_timestamp,
"delta_ns",
(itr.end_timestamp - itr.start_timestamp),
"tid",
itr.thread_id,
"kind",
itr.kind,
"operation",
itr.operation,
"corr_id",
itr.correlation_id.internal,
"ancestor_id",
itr.correlation_id.ancestor,
"stream_ID",
itr.stream_id.handle);
// When this record is a sync API (hipStreamWaitEvent / hipEventSynchronize)
// whose awaited event has a resolved producer, emit a SECOND Flow whose
// id is the producer's correlation_id. Perfetto draws an arrow between
// all events sharing the same flow id, so this connects the waiter slice
// back to the original hipEventRecord call. Two separate macro invocations
// because the variadic flow-arg list has to be fixed at expansion time.
if(itr.wait_on_correlation_id != 0)
{
TRACE_EVENT_BEGIN(sdk::perfetto_category<sdk::category::hip_api>::name,
::perfetto::StaticString(name.data()),
track,
itr.start_timestamp,
::perfetto::Flow::ProcessScoped(itr.correlation_id.internal),
::perfetto::Flow::ProcessScoped(itr.wait_on_correlation_id),
"begin_ns",
itr.start_timestamp,
"end_ns",
itr.end_timestamp,
"delta_ns",
(itr.end_timestamp - itr.start_timestamp),
"tid",
itr.thread_id,
"kind",
itr.kind,
"operation",
itr.operation,
"corr_id",
itr.correlation_id.internal,
"ancestor_id",
itr.correlation_id.ancestor,
"stream_ID",
itr.stream_id.handle,
"wait_on_stream_ID",
itr.wait_on_stream.handle,
"wait_on_corr_id",
itr.wait_on_correlation_id);
}
else
{
TRACE_EVENT_BEGIN(sdk::perfetto_category<sdk::category::hip_api>::name,
::perfetto::StaticString(name.data()),
track,
itr.start_timestamp,
::perfetto::Flow::ProcessScoped(itr.correlation_id.internal),
"begin_ns",
itr.start_timestamp,
"end_ns",
itr.end_timestamp,
"delta_ns",
(itr.end_timestamp - itr.start_timestamp),
"tid",
itr.thread_id,
"kind",
itr.kind,
"operation",
itr.operation,
"corr_id",
itr.correlation_id.internal,
"ancestor_id",
itr.correlation_id.ancestor,
"stream_ID",
itr.stream_id.handle);
}

TRACE_EVENT_END(
sdk::perfetto_category<sdk::category::hip_api>::name, track, itr.end_timestamp);
Expand Down
27 changes: 27 additions & 0 deletions projects/rocprofiler-sdk/source/lib/output/stream_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ struct tool_buffer_tracing_hip_api_ext_record_t : rocprofiler_buffer_tracing_hip
, stream_id{_stream_id}
{}

// Extended constructor used by the buffer-flush path when the record is for a
// synchronization API (hipStreamWaitEvent / hipEventSynchronize) and the producer
// of the awaited event has been resolved by the HIP_RUNTIME_API callback service.
tool_buffer_tracing_hip_api_ext_record_t(const base_type& _base,
const rocprofiler_stream_id_t _stream_id,
const rocprofiler_stream_id_t _wait_on_stream,
const uint64_t _wait_on_correlation_id)
: base_type{_base}
, stream_id{_stream_id}
, wait_on_stream{_wait_on_stream}
, wait_on_correlation_id{_wait_on_correlation_id}
{}

tool_buffer_tracing_hip_api_ext_record_t() = delete;
~tool_buffer_tracing_hip_api_ext_record_t() = default;
tool_buffer_tracing_hip_api_ext_record_t(const tool_buffer_tracing_hip_api_ext_record_t&) =
Expand All @@ -132,6 +145,18 @@ struct tool_buffer_tracing_hip_api_ext_record_t : rocprofiler_buffer_tracing_hip
tool_buffer_tracing_hip_api_ext_record_t&&) noexcept = default;

rocprofiler_stream_id_t stream_id = {};

// Stream on which the awaited event was last hipEventRecord-ed.
// Default {.handle = 0} (= the default stream) is also used as the sentinel
// for "no resolved dependency" -- distinguished from a real default-stream
// dependency by wait_on_correlation_id != 0 below.
rocprofiler_stream_id_t wait_on_stream = {};

// correlation_id of the hipEventRecord call that produced the awaited event.
// 0 means "no resolved dependency" (this record is not a wait-style API, or
// the producer was not observed -- e.g. event was recorded before tracing
// started, or by a different process).
uint64_t wait_on_correlation_id = 0;
};

} // namespace tool
Expand Down Expand Up @@ -174,6 +199,8 @@ save(ArchiveT& ar, const ::rocprofiler::tool::tool_buffer_tracing_hip_api_ext_re
{
cereal::save(ar, static_cast<const rocprofiler_buffer_tracing_hip_api_ext_record_t&>(data));
SAVE_DATA_FIELD(stream_id);
SAVE_DATA_FIELD(wait_on_stream);
SAVE_DATA_FIELD(wait_on_correlation_id);
}

#undef SAVE_DATA_FIELD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

rocprofiler_activate_clang_tidy()

set(TOOL_HEADERS config.hpp execution_profile.hpp helper.hpp stream_stack.hpp)
set(TOOL_HEADERS config.hpp execution_profile.hpp helper.hpp stream_stack.hpp
event_producer_map.hpp)

set(TOOL_SOURCES config.cpp main.c tool.cpp stream_stack.cpp)
set(TOOL_SOURCES config.cpp main.c tool.cpp stream_stack.cpp event_producer_map.cpp)

add_library(rocprofiler-sdk-tool SHARED)
target_sources(rocprofiler-sdk-tool PRIVATE ${TOOL_SOURCES} ${TOOL_HEADERS})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// MIT License
//
// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "event_producer_map.hpp"

#include "lib/common/static_object.hpp"
#include "lib/common/synchronized.hpp"

#include <unordered_map>

namespace rocprofiler
{
namespace tool
{
namespace event_producer
{
namespace
{
// Distinct context types so the two static_object singletons don't collide.
struct producer_map_ctx
{};
struct resolved_wait_map_ctx
{};

using producer_map_t = std::unordered_map<void*, producer_info>;
using resolved_wait_map_t = std::unordered_map<uint64_t, resolved_wait_info>;

using sync_producer_map_t = common::Synchronized<producer_map_t>;
using sync_resolved_wait_map_t = common::Synchronized<resolved_wait_map_t>;

sync_producer_map_t&
get_producer_map()
{
static sync_producer_map_t*& _m =
common::static_object<sync_producer_map_t, producer_map_ctx>::construct();
return *_m;
}

sync_resolved_wait_map_t&
get_resolved_wait_map()
{
static sync_resolved_wait_map_t*& _m =
common::static_object<sync_resolved_wait_map_t, resolved_wait_map_ctx>::construct();
return *_m;
}
} // namespace

void
record_event_producer(void* event,
rocprofiler_stream_id_t stream_id,
uint64_t correlation_id)
{
if(event == nullptr) return;
get_producer_map().wlock([&](producer_map_t& m) {
m[event] = producer_info{stream_id, correlation_id};
});
}

void
forget_event(void* event)
{
if(event == nullptr) return;
get_producer_map().wlock([&](producer_map_t& m) { m.erase(event); });
}

std::optional<producer_info>
lookup_event_producer(void* event)
{
if(event == nullptr) return std::nullopt;
return get_producer_map().rlock([&](const producer_map_t& m) -> std::optional<producer_info> {
auto it = m.find(event);
if(it == m.end()) return std::nullopt;
return it->second;
});
}

void
stash_resolved_wait(uint64_t waiter_correlation_id,
rocprofiler_stream_id_t wait_on_stream,
uint64_t wait_on_correlation_id)
{
if(waiter_correlation_id == 0) return;
get_resolved_wait_map().wlock([&](resolved_wait_map_t& m) {
m[waiter_correlation_id] =
resolved_wait_info{wait_on_stream, wait_on_correlation_id};
});
}

std::optional<resolved_wait_info>
consume_resolved_wait(uint64_t waiter_correlation_id)
{
if(waiter_correlation_id == 0) return std::nullopt;
return get_resolved_wait_map().wlock(
[&](resolved_wait_map_t& m) -> std::optional<resolved_wait_info> {
auto it = m.find(waiter_correlation_id);
if(it == m.end()) return std::nullopt;
auto out = it->second;
m.erase(it);
return out;
});
}

counters
get_counters()
{
counters c{};
c.producer_map_size =
get_producer_map().rlock([](const producer_map_t& m) { return m.size(); });
c.resolved_wait_size =
get_resolved_wait_map().rlock([](const resolved_wait_map_t& m) { return m.size(); });
return c;
}

void
clear_all()
{
get_producer_map().wlock([](producer_map_t& m) { m.clear(); });
get_resolved_wait_map().wlock([](resolved_wait_map_t& m) { m.clear(); });
}
} // namespace event_producer
} // namespace tool
} // namespace rocprofiler
Loading