// MIT License // // Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma once #include "common/defines.h" #include "core/common.hpp" #include "core/concepts.hpp" #include "core/config.hpp" #include "core/defines.hpp" #include "core/demangler.hpp" #include "core/perfetto.hpp" #include "core/state.hpp" #include "core/timemory.hpp" #include "core/utility.hpp" #include "library/causal/sampling.hpp" #include "library/runtime.hpp" #include "library/sampling.hpp" #include "library/thread_data.hpp" #include "library/tracing/annotation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "logger/debug.hpp" #include #include #include #include #include #include #include #include #include namespace rocprofsys { namespace tracing { using interval_data_instances = thread_data>; using hash_value_t = tim::hash_value_t; using perfetto_annotate_component_types = tim::mpl::available_t>; // // declarations // extern ROCPROFSYS_HIDDEN_API bool debug_push; extern ROCPROFSYS_HIDDEN_API bool debug_pop; extern ROCPROFSYS_HIDDEN_API bool debug_user; extern ROCPROFSYS_HIDDEN_API bool debug_mark; std::unordered_map& get_perfetto_track_uuids(); void copy_timemory_hash_ids(); std::vector>& get_finalization_functions(); void record_thread_start_time(); void thread_init(); template auto& get_category_stack(); template auto get_perfetto_string(T& name) { if constexpr(std::is_const_v) { return ::perfetto::StaticString{ name }; } else { return ::perfetto::DynamicString{ name }; } } template inline void push_perfetto(CategoryT, const char*, Args&&...); template inline void pop_perfetto(CategoryT, const char*, Args&&...); template inline void push_perfetto_ts(CategoryT, const char*, uint64_t _ts, Args&&...); template inline void pop_perfetto_ts(CategoryT, const char*, uint64_t, Args&&...); template inline void push_perfetto_track(CategoryT, const char*, ::perfetto::Track, uint64_t, Args&&...); template inline void pop_perfetto_track(CategoryT, const char*, ::perfetto::Track, uint64_t, Args&&...); template inline void mark_perfetto(CategoryT, const char*, Args&&...); template inline void mark_perfetto_ts(CategoryT, const char*, uint64_t, Args&&...); template inline void mark_perfetto_track(CategoryT, const char*, ::perfetto::Track, uint64_t, Args&&...); // // definitions // template auto get_perfetto_category_uuid(Args&&... _args) { return tim::hash::get_hash_id(tim::hash::get_hash_id(fmt::format( "rocprofsys_{}", trait::name::value)), std::forward(_args)...); } template auto get_perfetto_track(CategoryT, FuncT&& _desc_generator, Args&&... _args) { auto _uuid = get_perfetto_category_uuid(std::forward(_args)...); auto& _track_uuids = get_perfetto_track_uuids(); if(_track_uuids.find(_uuid) == _track_uuids.end()) { const auto _track = TrackT(_uuid, ::perfetto::ProcessTrack::Current()); auto _desc = _track.Serialize(); auto _name = std::forward(_desc_generator)(std::forward(_args)...); _desc.set_name(_name); ::perfetto::TrackEvent::SetTrackDescriptor(_track, _desc); LOG_TRACE("[{}] Created {}({}) with description: \"{}\"", trait::name::value, rocprofsys::utility::demangle(), _uuid, _name); _track_uuids.emplace(_uuid, _name); } // guard this with ppdefs in addition to runtime check to avoid // overhead of generating string during releases #if defined(ROCPROFSYS_CI) && ROCPROFSYS_CI > 0 auto _name = std::forward(_desc_generator)(std::forward(_args)...); if(get_is_continuous_integration() && _track_uuids.at(_uuid) != _name) { throw std::runtime_error( fmt::format("Error! Multiple invocations of UUID {} produced different " "descriptions: \"{}\" and \"{}\"", _uuid, _track_uuids.at(_uuid), _name)); } #endif return TrackT(_uuid, ::perfetto::ProcessTrack::Current()); } template ROCPROFSYS_INLINE auto now() { return ::tim::get_clock_real_now(); } inline auto& get_instrumentation_bundles(int64_t _tid = threading::get_id()) { return instrumentation_bundles::instance(construct_on_thread{ _tid }); } inline auto& push_count() { static std::atomic _v{ 0 }; return _v; } inline auto& pop_count() { static std::atomic _v{ 0 }; return _v; } struct category_stack { int32_t profile = 0; // use signed so compiler doesn't have to int32_t tracing = 0; // account for underflow/overflow }; template auto& get_category_stack() { static thread_local auto _v = category_stack{}; return _v; } template auto& get_tracing_stack() { return get_category_stack().tracing; } template auto& get_profile_stack() { return get_category_stack().profile; } template auto category_push_disabled() { return !trait::runtime_enabled::get(); } template auto category_mark_disabled() { return !trait::runtime_enabled::get(); } template auto category_pop_disabled() { return !trait::runtime_enabled::get() && (get_profile_stack() + get_tracing_stack()) <= 0; } template auto tracing_pop_disabled() { return !trait::runtime_enabled::get() && get_tracing_stack() <= 0; } template auto profile_pop_disabled() { return !trait::runtime_enabled::get() && get_profile_stack() <= 0; } template inline void push_timemory(CategoryT, std::string_view name, Args&&... args) { // skip if category is disabled if(category_push_disabled()) return; auto& _data = tracing::get_instrumentation_bundles(); if(ROCPROFSYS_LIKELY(_data != nullptr)) { // this generates a hash for the raw string array auto _hash = tim::add_hash_id(name); _data->construct(_hash)->start(std::forward(args)...); // increment the profile stack ++get_profile_stack(); } } template inline std::pair get_timemory(CategoryT, std::string_view name) { using return_type = std::pair; // skip if category is disabled and not pushed on this thread if(profile_pop_disabled()) return return_type{ nullptr, -1 }; auto _hash = tim::hash::get_hash_id(name); auto& _data = tracing::get_instrumentation_bundles(); if(ROCPROFSYS_UNLIKELY(_data == nullptr || _data->empty())) { LOG_DEBUG("[rocprofsys_pop_trace] skipped {} :: empty bundle stack", name); return return_type{ nullptr, -1 }; } auto*& _v_back = _data->back(); if(ROCPROFSYS_LIKELY(_v_back->get_hash() == _hash)) { return std::make_pair(_v_back, _data->size() - 1); } else if(_data->size() > 1) { for(size_t i = _data->size() - 1; i > 0; --i) { auto*& _v = _data->at(i - 1); if(_v->get_hash() == _hash) { return std::make_pair(_v, i - 1); } } } return return_type{ nullptr, -1 }; } template inline auto stop_timemory(CategoryT, std::string_view name, Args&&... args) { using return_type = std::pair; // skip if category is disabled and not pushed on this thread if(profile_pop_disabled()) return return_type{ nullptr, -1 }; auto&& _data = get_timemory(CategoryT{}, name); if(_data.first) { _data.first->stop(std::forward(args)...); } return _data; } inline void destroy_timemory(std::pair _data) { if(_data.first) { auto& _bundles = tracing::get_instrumentation_bundles(); if(ROCPROFSYS_LIKELY(_bundles != nullptr)) _bundles->destroy(_data.first, _data.second); } } template inline void pop_timemory(CategoryT, std::string_view name, Args&&... args) { // skip if category is disabled and not pushed on this thread if(profile_pop_disabled()) return; auto _data = stop_timemory(CategoryT{}, name, std::forward(args)...); if(_data.first) destroy_timemory(std::move(_data)); } template inline void push_perfetto(CategoryT, const char* name, Args&&... args) { // skip if category is disabled if(category_push_disabled()) return; if constexpr(sizeof...(Args) == 1 && std::is_invocable::value) { ++get_tracing_stack(); uint64_t _ts = now(); if(config::get_perfetto_annotations()) { TRACE_EVENT_BEGIN(trait::name::value, get_perfetto_string(name), _ts, "begin_ns", _ts, std::forward(args)...); } else { TRACE_EVENT_BEGIN(trait::name::value, get_perfetto_string(name), _ts, std::forward(args)...); } } else { using tuple_type = std::tuple...>; using arg0_type = concepts::tuple_element_t<0, tuple_type>; using arg1_type = concepts::tuple_element_t<1, tuple_type>; if constexpr(std::is_same::value && std::is_same::value) { push_perfetto_track(CategoryT{}, name, std::forward(args)...); } else if constexpr(std::is_same::value) { push_perfetto_ts(CategoryT{}, name, std::forward(args)...); } else { ++get_tracing_stack(); uint64_t _ts = now(); TRACE_EVENT_BEGIN( trait::name::value, get_perfetto_string(name), _ts, std::forward(args)..., [&](::perfetto::EventContext ctx) { if(config::get_perfetto_annotations()) { tracing::add_perfetto_annotation(ctx, "begin_ns", _ts); } }); } } } /// \brief This function is used to take an existing lambda accepting a /// perfetto::EventContext and append the timemory annotations. Examples /// are seen in the pop_perfetto* functions template inline decltype(auto) perfetto_annotate_timemory_data(CategoryT, const char* name, Arg&& arg) { if constexpr(std::is_invocable::value) { return [&arg, name](::perfetto::EventContext _ctx) { if(config::get_perfetto_annotations()) { auto _timemory_data = get_timemory(CategoryT{}, name); if(_timemory_data.first) { _timemory_data.first->stop(); _timemory_data.first ->template invoke_with( perfetto_annotate_component_types{}, _ctx); } } std::forward(arg)(std::move(_ctx)); }; } else { return std::move(arg); } } template inline void pop_perfetto(CategoryT, const char* name, Args&&... args) { // skip if category is disabled and not pushed on this thread if(tracing_pop_disabled()) return; if constexpr(sizeof...(Args) == 1 && std::is_invocable::value) { // decrement tracing stack --get_tracing_stack(); uint64_t _ts = now(); if(config::get_perfetto_annotations()) { TRACE_EVENT_END(trait::name::value, _ts, "end_ns", _ts, perfetto_annotate_timemory_data(CategoryT{}, name, std::forward(args))...); } else { TRACE_EVENT_END(trait::name::value, _ts, std::forward(args)...); } } else { using tuple_type = std::tuple...>; using arg0_type = concepts::tuple_element_t<0, tuple_type>; using arg1_type = concepts::tuple_element_t<1, tuple_type>; if constexpr(std::is_same::value && std::is_same::value) { pop_perfetto_track(CategoryT{}, name, std::forward(args)...); } else if constexpr(std::is_same::value) { pop_perfetto_ts(CategoryT{}, name, std::forward(args)...); } else { // decrement tracing stack --get_tracing_stack(); uint64_t _ts = now(); TRACE_EVENT_END( trait::name::value, _ts, std::forward(args)..., perfetto_annotate_timemory_data( CategoryT{}, name, [&](::perfetto::EventContext ctx) { if(config::get_perfetto_annotations()) { tracing::add_perfetto_annotation(ctx, "end_ns", _ts); } })); } } (void) name; } template inline void push_perfetto_ts(CategoryT, const char* name, uint64_t _ts, Args&&... args) { // skip if category is disabled if(category_push_disabled()) return; ++get_tracing_stack(); TRACE_EVENT_BEGIN(trait::name::value, get_perfetto_string(name), _ts, std::forward(args)...); } template inline void pop_perfetto_ts(CategoryT, const char* name, uint64_t _ts, Args&&... args) { // skip if category is disabled and not pushed on this thread if(tracing_pop_disabled()) return; // decrement tracing stack --get_tracing_stack(); TRACE_EVENT_END( trait::name::value, _ts, perfetto_annotate_timemory_data(CategoryT{}, name, std::forward(args))...); } template inline void push_perfetto_track(CategoryT, const char* name, ::perfetto::Track _track, uint64_t _ts, Args&&... args) { // skip if category is disabled if(category_push_disabled()) return; ++get_tracing_stack(); TRACE_EVENT_BEGIN(trait::name::value, get_perfetto_string(name), _track, _ts, std::forward(args)...); } template inline void pop_perfetto_track(CategoryT, const char* name, ::perfetto::Track _track, uint64_t _ts, Args&&... args) { // skip if category is disabled and not pushed on this thread if(tracing_pop_disabled()) return; // decrement tracing stack --get_tracing_stack(); TRACE_EVENT_END( trait::name::value, _track, _ts, perfetto_annotate_timemory_data(CategoryT{}, name, std::forward(args))...); } template inline void mark_perfetto(CategoryT, const char* name, Args&&... args) { // skip if category is disabled if(category_mark_disabled()) return; if constexpr(sizeof...(Args) == 1 && std::is_invocable::value) { uint64_t _ts = now(); if(config::get_perfetto_annotations()) { TRACE_EVENT_INSTANT(trait::name::value, get_perfetto_string(name), _ts, "ns", _ts, std::forward(args)...); } else { TRACE_EVENT_INSTANT(trait::name::value, get_perfetto_string(name), _ts, std::forward(args)...); } } else { using tuple_type = std::tuple...>; using arg0_type = concepts::tuple_element_t<0, tuple_type>; using arg1_type = concepts::tuple_element_t<1, tuple_type>; if constexpr(std::is_same::value && std::is_same::value) { mark_perfetto_track(CategoryT{}, name, std::forward(args)...); } else if constexpr(std::is_same::value) { mark_perfetto_ts(CategoryT{}, name, std::forward(args)...); } else { uint64_t _ts = now(); TRACE_EVENT_INSTANT(trait::name::value, get_perfetto_string(name), _ts, std::forward(args)..., [&](::perfetto::EventContext ctx) { if(config::get_perfetto_annotations()) { tracing::add_perfetto_annotation(ctx, "ns", _ts); } }); } } } template inline void mark_perfetto_ts(CategoryT, const char* name, uint64_t _ts, Args&&... args) { // skip if category is disabled if(category_mark_disabled()) return; TRACE_EVENT_INSTANT(trait::name::value, get_perfetto_string(name), _ts, std::forward(args)...); } template inline void mark_perfetto_track(CategoryT, const char* name, ::perfetto::Track _track, uint64_t _ts, Args&&... args) { // skip if category is disabled if(category_mark_disabled()) return; TRACE_EVENT_INSTANT(trait::name::value, get_perfetto_string(name), _track, _ts, std::forward(args)...); } template int64_t get_clock_skew(FuncT&& _timestamp_func, int64_t _n = 1) { namespace cpu = tim::cpu; // synchronize timestamps // We'll take a CPU timestamp before and after taking a GPU timestmp, then // take the average of those two, hoping that it's roughly at the same time // as the GPU timestamp. auto _cpu_now = []() { cpu::fence(); return now(); }; auto _gpu_now = [&_timestamp_func]() { cpu::fence(); return std::forward(_timestamp_func)(); }; auto _compute = [&_cpu_now, &_gpu_now]() { volatile uint64_t _cpu_ts = 0; volatile uint64_t _gpu_ts = 0; _cpu_ts += _cpu_now(); _gpu_ts += _gpu_now(); _cpu_ts += _cpu_now(); return static_cast(_cpu_ts / 2) - static_cast(_gpu_ts); }; int64_t _diff = 0; for(int64_t i = 0; i < _n; ++i) { _diff += _compute(); } return (_diff / _n); } } // namespace tracing } // namespace rocprofsys