Rename Omnitrace to ROCm Systems Profiler (#4)

The Omnitrace program is being renamed. 

Full name: "ROCm Systems Profiler"
Package name: "rocprofiler-systems"
Binary / Library names: "rocprof-sys-*"

---------
Co-authored-by: Xuan Chen <xuchen@amd.com>
Signed-off-by: David Galiffi <David.Galiffi@amd.com>

[ROCm/rocprofiler-systems commit: d07bf508a9]
This commit is contained in:
David Galiffi
2024-10-15 11:20:40 -04:00
committed by GitHub
parent c444ba6131
commit 489eda995d
420 changed files with 10418 additions and 9914 deletions
@@ -0,0 +1,73 @@
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems object library
#
# ------------------------------------------------------------------------------#
add_library(rocprofiler-systems-object-library OBJECT)
add_library(rocprofiler-systems::rocprofiler-systems-object-library ALIAS
rocprofiler-systems-object-library)
target_sources(
rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/library.cpp ${CMAKE_CURRENT_LIST_DIR}/regions.cpp
${CMAKE_CURRENT_LIST_DIR}/progress.cpp ${CMAKE_CURRENT_LIST_DIR}/api.cpp
${CMAKE_CURRENT_LIST_DIR}/timeout.cpp ${CMAKE_CURRENT_LIST_DIR}/api.hpp)
add_subdirectory(library)
target_link_libraries(rocprofiler-systems-object-library
PRIVATE rocprofiler-systems::rocprofiler-systems-interface-library)
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems static library
#
# ------------------------------------------------------------------------------#
add_library(rocprofiler-systems-static-library STATIC
$<TARGET_OBJECTS:rocprofiler-systems-object-library>)
add_library(rocprofiler-systems::librocprofiler-systems-static ALIAS
rocprofiler-systems-static-library)
target_link_libraries(
rocprofiler-systems-static-library
PRIVATE rocprofiler-systems::rocprofiler-systems-interface-library
rocprofiler-systems::rocprofiler-systems-core
rocprofiler-systems::rocprofiler-systems-binary)
set_target_properties(rocprofiler-systems-static-library PROPERTIES OUTPUT_NAME
${BINARY_NAME_PREFIX})
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems shared library
#
# ------------------------------------------------------------------------------#
add_library(rocprofiler-systems-shared-library SHARED
$<TARGET_OBJECTS:rocprofiler-systems-object-library>)
add_library(rocprofiler-systems::librocprofiler-systems-shared ALIAS
rocprofiler-systems-shared-library)
add_library(rocprofiler-systems::rocprofiler-systems-library ALIAS
rocprofiler-systems-shared-library)
target_link_libraries(
rocprofiler-systems-shared-library
PRIVATE rocprofiler-systems::rocprofiler-systems-interface-library
rocprofiler-systems::rocprofiler-systems-core
rocprofiler-systems::rocprofiler-systems-binary)
set_target_properties(
rocprofiler-systems-shared-library
PROPERTIES OUTPUT_NAME ${BINARY_NAME_PREFIX}
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
INSTALL_RPATH "${ROCPROFSYS_LIB_INSTALL_RPATH}")
rocprofiler_systems_strip_target(rocprofiler-systems-shared-library)
install(
TARGETS rocprofiler-systems-shared-library
DESTINATION ${CMAKE_INSTALL_LIBDIR}
OPTIONAL)
@@ -0,0 +1,169 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "api.hpp"
#include "core/debug.hpp"
#include <exception>
#include <stdexcept>
extern "C" void
rocprofsys_push_trace(const char* _name)
{
rocprofsys_push_trace_hidden(_name);
}
extern "C" void
rocprofsys_pop_trace(const char* _name)
{
rocprofsys_pop_trace_hidden(_name);
}
extern "C" int
rocprofsys_push_region(const char* _name)
{
try
{
rocprofsys_push_region_hidden(_name);
} catch(std::exception& _e)
{
ROCPROFSYS_WARNING_F(1, "Exception caught: %s\n", _e.what());
return -1;
}
return 0;
}
extern "C" int
rocprofsys_pop_region(const char* _name)
{
try
{
rocprofsys_pop_region_hidden(_name);
} catch(std::exception& _e)
{
ROCPROFSYS_WARNING_F(1, "Exception caught: %s\n", _e.what());
return -1;
}
return 0;
}
extern "C" int
rocprofsys_push_category_region(rocprofsys_category_t _category, const char* _name,
rocprofsys_annotation_t* _annotations,
size_t _annotation_count)
{
try
{
rocprofsys_push_category_region_hidden(_category, _name, _annotations,
_annotation_count);
} catch(std::exception& _e)
{
ROCPROFSYS_WARNING_F(1, "Exception caught: %s\n", _e.what());
return -1;
}
return 0;
}
extern "C" int
rocprofsys_pop_category_region(rocprofsys_category_t _category, const char* _name,
rocprofsys_annotation_t* _annotations,
size_t _annotation_count)
{
try
{
rocprofsys_pop_category_region_hidden(_category, _name, _annotations,
_annotation_count);
} catch(std::exception& _e)
{
ROCPROFSYS_WARNING_F(1, "Exception caught: %s\n", _e.what());
return -1;
}
return 0;
}
extern "C" void
rocprofsys_progress(const char* _name)
{
rocprofsys_progress_hidden(_name);
}
extern "C" void
rocprofsys_annotated_progress(const char* _name, rocprofsys_annotation_t* _annotations,
size_t _annotation_count)
{
rocprofsys_annotated_progress_hidden(_name, _annotations, _annotation_count);
}
extern "C" void
rocprofsys_init_library(void)
{
rocprofsys_init_library_hidden();
}
extern "C" void
rocprofsys_init_tooling(void)
{
rocprofsys_init_tooling_hidden();
}
extern "C" void
rocprofsys_init(const char* _mode, bool _rewrite, const char* _arg0)
{
rocprofsys_init_hidden(_mode, _rewrite, _arg0);
}
extern "C" void
rocprofsys_finalize(void)
{
rocprofsys_finalize_hidden();
}
extern "C" void
rocprofsys_reset_preload(void)
{
rocprofsys_reset_preload_hidden();
}
extern "C" void
rocprofsys_set_env(const char* env_name, const char* env_val)
{
rocprofsys_set_env_hidden(env_name, env_val);
}
extern "C" void
rocprofsys_set_mpi(bool use, bool attached)
{
rocprofsys_set_mpi_hidden(use, attached);
}
extern "C" void
rocprofsys_register_source(const char* file, const char* func, size_t line,
size_t address, const char* source)
{
rocprofsys_register_source_hidden(file, func, line, address, source);
}
extern "C" void
rocprofsys_register_coverage(const char* file, const char* func, size_t address)
{
rocprofsys_register_coverage_hidden(file, func, address);
}
@@ -0,0 +1,121 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/defines.hpp"
#include "rocprofiler-systems/categories.h" // in rocprof-sys-user
#include <timemory/compat/macros.h>
#include <cstddef>
// forward decl of the API
extern "C"
{
/// handles configuration logic
void rocprofsys_init_library(void) ROCPROFSYS_PUBLIC_API;
/// handles configuration logic
void rocprofsys_init_tooling(void) ROCPROFSYS_PUBLIC_API;
/// starts gotcha wrappers
void rocprofsys_init(const char*, bool, const char*) ROCPROFSYS_PUBLIC_API;
/// shuts down all tooling and generates output
void rocprofsys_finalize(void) ROCPROFSYS_PUBLIC_API;
/// remove librocprof-sys from LD_PRELOAD
void rocprofsys_reset_preload(void) ROCPROFSYS_PUBLIC_API;
/// sets an environment variable
void rocprofsys_set_env(const char*, const char*) ROCPROFSYS_PUBLIC_API;
/// sets whether MPI should be used
void rocprofsys_set_mpi(bool, bool) ROCPROFSYS_PUBLIC_API;
/// starts an instrumentation region
void rocprofsys_push_trace(const char*) ROCPROFSYS_PUBLIC_API;
/// stops an instrumentation region
void rocprofsys_pop_trace(const char*) ROCPROFSYS_PUBLIC_API;
/// starts an instrumentation region (user-defined)
int rocprofsys_push_region(const char*) ROCPROFSYS_PUBLIC_API;
/// stops an instrumentation region (user-defined)
int rocprofsys_pop_region(const char*) ROCPROFSYS_PUBLIC_API;
/// starts an instrumentation region in a user-defined category and (optionally)
/// adds annotations to the perfetto trace.
int rocprofsys_push_category_region(rocprofsys_category_t, const char*,
rocprofsys_annotation_t*,
size_t) ROCPROFSYS_PUBLIC_API;
/// stops an instrumentation region in a user-defined category and (optionally)
/// adds annotations to the perfetto trace.
int rocprofsys_pop_category_region(rocprofsys_category_t, const char*,
rocprofsys_annotation_t*,
size_t) ROCPROFSYS_PUBLIC_API;
/// stores source code information
void rocprofsys_register_source(const char* file, const char* func, size_t line,
size_t address,
const char* source) ROCPROFSYS_PUBLIC_API;
/// increments coverage values
void rocprofsys_register_coverage(const char* file, const char* func,
size_t address) ROCPROFSYS_PUBLIC_API;
/// mark causal progress
void rocprofsys_progress(const char*) ROCPROFSYS_PUBLIC_API;
/// mark causal progress with annotations
void rocprofsys_annotated_progress(const char*, rocprofsys_annotation_t*,
size_t) ROCPROFSYS_PUBLIC_API;
// these are the real implementations for internal calling convention
void rocprofsys_init_library_hidden(void) ROCPROFSYS_HIDDEN_API;
bool rocprofsys_init_tooling_hidden(void) ROCPROFSYS_HIDDEN_API;
void rocprofsys_init_hidden(const char*, bool, const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_finalize_hidden(void) ROCPROFSYS_HIDDEN_API;
void rocprofsys_reset_preload_hidden(void) ROCPROFSYS_HIDDEN_API;
void rocprofsys_set_env_hidden(const char*, const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_set_mpi_hidden(bool, bool) ROCPROFSYS_HIDDEN_API;
void rocprofsys_push_trace_hidden(const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_pop_trace_hidden(const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_push_region_hidden(const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_pop_region_hidden(const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_push_category_region_hidden(rocprofsys_category_t, const char*,
rocprofsys_annotation_t*,
size_t) ROCPROFSYS_HIDDEN_API;
void rocprofsys_pop_category_region_hidden(rocprofsys_category_t, const char*,
rocprofsys_annotation_t*,
size_t) ROCPROFSYS_HIDDEN_API;
void rocprofsys_register_source_hidden(const char*, const char*, size_t, size_t,
const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_register_coverage_hidden(const char*, const char*,
size_t) ROCPROFSYS_HIDDEN_API;
void rocprofsys_progress_hidden(const char*) ROCPROFSYS_HIDDEN_API;
void rocprofsys_annotated_progress_hidden(const char*, rocprofsys_annotation_t*,
size_t) ROCPROFSYS_HIDDEN_API;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
#
set(library_sources
${CMAKE_CURRENT_LIST_DIR}/coverage.cpp
${CMAKE_CURRENT_LIST_DIR}/cpu_freq.cpp
${CMAKE_CURRENT_LIST_DIR}/kokkosp.cpp
${CMAKE_CURRENT_LIST_DIR}/ompt.cpp
${CMAKE_CURRENT_LIST_DIR}/perf.cpp
${CMAKE_CURRENT_LIST_DIR}/process_sampler.cpp
${CMAKE_CURRENT_LIST_DIR}/ptl.cpp
${CMAKE_CURRENT_LIST_DIR}/runtime.cpp
${CMAKE_CURRENT_LIST_DIR}/sampling.cpp
${CMAKE_CURRENT_LIST_DIR}/thread_deleter.cpp
${CMAKE_CURRENT_LIST_DIR}/thread_info.cpp
${CMAKE_CURRENT_LIST_DIR}/tracing.cpp)
set(library_headers
${CMAKE_CURRENT_LIST_DIR}/coverage.hpp
${CMAKE_CURRENT_LIST_DIR}/cpu_freq.hpp
${CMAKE_CURRENT_LIST_DIR}/ompt.hpp
${CMAKE_CURRENT_LIST_DIR}/process_sampler.hpp
${CMAKE_CURRENT_LIST_DIR}/perf.hpp
${CMAKE_CURRENT_LIST_DIR}/ptl.hpp
${CMAKE_CURRENT_LIST_DIR}/rcclp.hpp
${CMAKE_CURRENT_LIST_DIR}/rocm.hpp
${CMAKE_CURRENT_LIST_DIR}/rocm_smi.hpp
${CMAKE_CURRENT_LIST_DIR}/rocprofiler.hpp
${CMAKE_CURRENT_LIST_DIR}/roctracer.hpp
${CMAKE_CURRENT_LIST_DIR}/runtime.hpp
${CMAKE_CURRENT_LIST_DIR}/sampling.hpp
${CMAKE_CURRENT_LIST_DIR}/thread_data.hpp
${CMAKE_CURRENT_LIST_DIR}/thread_deleter.hpp
${CMAKE_CURRENT_LIST_DIR}/thread_info.hpp
${CMAKE_CURRENT_LIST_DIR}/tracing.hpp)
target_sources(rocprofiler-systems-object-library PRIVATE ${library_sources}
${library_headers})
if(ROCPROFSYS_USE_ROCTRACER OR ROCPROFSYS_USE_ROCPROFILER)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rocm.cpp)
endif()
if(ROCPROFSYS_USE_ROCTRACER)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/roctracer.cpp)
endif()
if(ROCPROFSYS_USE_RCCL)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rcclp.cpp)
endif()
if(ROCPROFSYS_USE_ROCPROFILER)
target_sources(
rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rocprofiler.cpp
${CMAKE_CURRENT_LIST_DIR}/rocprofiler.hpp)
endif()
if(ROCPROFSYS_USE_ROCM_SMI)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rocm_smi.cpp)
endif()
add_subdirectory(causal)
add_subdirectory(components)
add_subdirectory(coverage)
add_subdirectory(rocm)
add_subdirectory(tracing)
set(ndebug_sources
${CMAKE_CURRENT_LIST_DIR}/components/mpi_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/components/backtrace_metrics.cpp
${CMAKE_CURRENT_LIST_DIR}/rcclp.cpp
${CMAKE_CURRENT_LIST_DIR}/kokkosp.cpp
${CMAKE_CURRENT_LIST_DIR}/rocm_smi.cpp
${CMAKE_CURRENT_LIST_DIR}/ompt.cpp)
set_source_files_properties(
${ndebug_sources} DIRECTORY ${PROJECT_SOURCE_DIR}/source/lib/rocprof-sys
PROPERTIES COMPILE_DEFINITIONS NDEBUG COMPILE_OPTIONS "-g0;-O3")
@@ -0,0 +1,15 @@
#
set(causal_sources
${CMAKE_CURRENT_LIST_DIR}/data.cpp ${CMAKE_CURRENT_LIST_DIR}/delay.cpp
${CMAKE_CURRENT_LIST_DIR}/experiment.cpp ${CMAKE_CURRENT_LIST_DIR}/sample_data.cpp
${CMAKE_CURRENT_LIST_DIR}/sampling.cpp ${CMAKE_CURRENT_LIST_DIR}/selected_entry.cpp)
set(causal_headers
${CMAKE_CURRENT_LIST_DIR}/data.hpp ${CMAKE_CURRENT_LIST_DIR}/delay.hpp
${CMAKE_CURRENT_LIST_DIR}/experiment.hpp ${CMAKE_CURRENT_LIST_DIR}/sample_data.hpp
${CMAKE_CURRENT_LIST_DIR}/sampling.hpp ${CMAKE_CURRENT_LIST_DIR}/selected_entry.hpp)
target_sources(rocprofiler-systems-object-library PRIVATE ${causal_sources}
${causal_headers})
add_subdirectory(components)
@@ -0,0 +1,17 @@
#
set(component_sources
${CMAKE_CURRENT_LIST_DIR}/backtrace.cpp
${CMAKE_CURRENT_LIST_DIR}/blocking_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/causal_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/progress_point.cpp
${CMAKE_CURRENT_LIST_DIR}/unblocking_gotcha.cpp)
set(component_headers
${CMAKE_CURRENT_LIST_DIR}/backtrace.hpp
${CMAKE_CURRENT_LIST_DIR}/blocking_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/causal_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/progress_point.hpp
${CMAKE_CURRENT_LIST_DIR}/unblocking_gotcha.hpp)
target_sources(rocprofiler-systems-object-library PRIVATE ${component_sources}
${component_headers})
@@ -0,0 +1,247 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/components/backtrace.hpp"
#include "core/concepts.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/state.hpp"
#include "core/utility.hpp"
#include "library/causal/data.hpp"
#include "library/causal/delay.hpp"
#include "library/causal/experiment.hpp"
#include "library/perf.hpp"
#include "library/runtime.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include "library/tracing.hpp"
#include <timemory/components/timing/backends.hpp>
#include <timemory/components/timing/wall_clock.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/process/threading.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/backtrace.hpp>
#include <atomic>
#include <ctime>
#include <execinfo.h>
#include <type_traits>
namespace rocprofsys
{
namespace causal
{
namespace component
{
namespace
{
using ::tim::backtrace::get_unw_signal_frame_stack_raw;
int realtime_signal = 0;
int cputime_signal = 0;
int overflow_signal = 0;
void
generic_global_init()
{
// do not delete these lines. The thread data needs to be allocated
// before it is called in sampler or else a deadlock will occur when
// the sample interrupts a malloc call
if(realtime_signal + cputime_signal + overflow_signal == 0)
{
realtime_signal = get_sampling_realtime_signal();
cputime_signal = get_sampling_cputime_signal();
overflow_signal = get_sampling_overflow_signal();
}
}
} // namespace
void
overflow::global_init()
{
// do not delete these lines.
generic_global_init();
}
void
backtrace::global_init()
{
// do not delete these lines.
generic_global_init();
}
void
overflow::sample(int _sig)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
static thread_local const auto& _tinfo = thread_info::get();
auto _tid = _tinfo->index_data->sequent_value;
auto& _perf_event = perf::get_instance(_tid);
if(!_perf_event) return;
m_index = causal::experiment::get_index();
_perf_event->stop();
for(auto itr : *_perf_event)
{
if(itr.is_sample())
{
auto _sample_ip = itr.get_ip();
auto _data = callchain_t{};
_data.emplace_back(_sample_ip);
for(auto ditr : itr.get_callchain())
{
if(ditr != _sample_ip) _data.emplace_back(ditr);
if(_data.size() == _data.capacity()) break;
}
if(causal::experiment::is_active() && causal::experiment::is_selected(_data))
{
++m_selected;
causal::experiment::add_selected();
causal::delay::get_local() += causal::experiment::get_delay();
}
else if(!causal::experiment::is_active())
{
causal::set_current_selection(_data);
}
m_stack.emplace_back(_data);
}
}
_perf_event->start();
if(_sig == cputime_signal) causal::delay::process();
}
void
backtrace::sample(int _sig)
{
constexpr size_t depth = ::rocprofsys::causal::unwind_depth;
constexpr int64_t ignore_depth = ::rocprofsys::causal::unwind_offset;
constexpr size_t select_init = std::numeric_limits<size_t>::max();
constexpr size_t select_ival = 5; // interval at which realtime signal contributes
// update the last sample for backtrace signal(s) even when in use
static thread_local size_t _protect_flag = 0;
// the select_count is initialized to max so that realtime signal does
// not initially set the current selection
static thread_local size_t _select_count = select_init;
static thread_local size_t _select_zeros = 0;
if((_protect_flag & 1) == 1 ||
ROCPROFSYS_UNLIKELY(!trait::runtime_enabled<causal::component::backtrace>::get()))
{
return;
}
++_protect_flag;
// on RedHat, the unw_step within get_unw_signal_frame_stack_raw involves a mutex lock
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
m_index = causal::experiment::get_index();
m_stack = get_unw_signal_frame_stack_raw<depth, ignore_depth>();
auto _set_current_selection = [](auto _stack) {
// save the former selection count
auto _former_count = _select_count;
// get the current selection count
_select_count = causal::set_current_selection(_stack);
// if the selection count was reduced, reset select zeros.
// this typically means that a new experiment was started
if(_former_count > _select_count) _select_zeros = 0;
// if no PCs were selected, increment the select zeros.
// if the cputime signal has not selected a PC in select_ival iterations,
// then the realtime signal will start contributing to the current
// selection. We generally want only the cputime signal to contribute
// because those PCs are in-use (since the thread CPU clock in increasing)
if(_select_count == 0) ++_select_zeros;
};
// the batch handler timer delivers a signal according to the thread CPU
// clock, ensuring that setting the current selection is preferred when the thread
// is active and processing the delays happens only when the thread is active
if(_sig == cputime_signal)
{
if(causal::experiment::is_active())
causal::delay::process();
else
_set_current_selection(m_stack);
}
else if(_sig == realtime_signal)
{
if(causal::experiment::is_active() && causal::experiment::is_selected(m_stack))
{
m_selected = true;
causal::experiment::add_selected();
causal::delay::get_local() += causal::experiment::get_delay();
}
else if(!causal::experiment::is_active())
{
// if no PCs have been selected after at least "select_ival" call-stacks via
// the cputime signal, then contribute the call-stack via the realtime signal.
// This can be particularly relevant in end-to-end runs targeting a particular
// line/function since it is possible that the line/function is situated such
// the cputime signal is never delivered when executing the particular
// line/function... despite the line/function executing in between the
// the cputime signals. This is rare but has been observed
//
if(_select_count == 0 && _select_zeros >= select_ival)
_set_current_selection(m_stack);
}
}
else
{
ROCPROFSYS_THROW("unhandled signal %i\n", _sig);
}
++_protect_flag;
}
template <typename Tp>
Tp
backtrace::get_period(uint64_t _units)
{
using cast_type = std::conditional_t<std::is_floating_point<Tp>::value, Tp, double>;
double _period = 1.0 / 1000.0;
int64_t _period_nsec = static_cast<int64_t>(_period * units::sec) % units::sec;
return static_cast<Tp>(_period_nsec) / static_cast<cast_type>(_units);
}
} // namespace component
} // namespace causal
} // namespace rocprofsys
#define INSTANTIATE_BT_CAUSAL_PERIOD(TYPE) \
template TYPE rocprofsys::causal::component::backtrace::get_period<TYPE>(uint64_t);
INSTANTIATE_BT_CAUSAL_PERIOD(float)
INSTANTIATE_BT_CAUSAL_PERIOD(double)
INSTANTIATE_BT_CAUSAL_PERIOD(int64_t)
INSTANTIATE_BT_CAUSAL_PERIOD(uint64_t)
@@ -0,0 +1,104 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/causal/data.hpp"
#include "library/causal/fwd.hpp"
#include "library/perf.hpp"
#include <timemory/components/base.hpp>
#include <timemory/macros/language.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/tpls/cereal/cereal/cereal.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/unwind.hpp>
#include <chrono>
#include <cstdint>
namespace rocprofsys
{
namespace causal
{
namespace component
{
struct overflow : comp::empty_base
{
static constexpr auto alt_stack_size = perf::perf_event::max_batch_size;
using value_type = void;
using callchain_t = container::static_vector<uintptr_t, unwind_depth>;
using alt_stack_t = container::static_vector<callchain_t, alt_stack_size>;
static std::string label() { return "causal::overflow"; }
static void global_init();
void sample(int = -1);
auto get_selected() const { return m_selected; }
auto get_index() const { return m_index; }
const auto& get_stack() const { return m_stack; }
private:
int32_t m_selected = 0;
uint32_t m_index = 0;
alt_stack_t m_stack = {};
};
struct backtrace : comp::empty_base
{
using value_type = void;
using callchain_t = container::static_vector<uint64_t, unwind_depth>;
static std::string label() { return "causal::backtrace"; }
static void global_init();
backtrace() = default;
~backtrace() = default;
backtrace(const backtrace&) = default;
backtrace(backtrace&&) noexcept = default;
backtrace& operator=(const backtrace&) = default;
backtrace& operator=(backtrace&&) noexcept = default;
void sample(int = -1);
auto get_selected() const { return m_selected; }
auto get_index() const { return m_index; }
auto get_stack() const { return m_stack; }
template <typename Tp = uint64_t>
static Tp get_period(uint64_t _units = units::nsec);
private:
bool m_selected = false;
uint32_t m_index = 0;
causal::unwind_addr_t m_stack = {};
};
} // namespace component
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,278 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/components/blocking_gotcha.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/state.hpp"
#include "library/causal/components/causal_gotcha.hpp"
#include "library/causal/delay.hpp"
#include "library/causal/experiment.hpp"
#include "library/causal/sampling.hpp"
#include "library/runtime.hpp"
#include <timemory/components/macros.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/utility/types.hpp>
#include <atomic>
#include <csignal>
#include <cstdint>
#include <pthread.h>
#include <stdexcept>
#include <type_traits>
#pragma weak pthread_join
#pragma weak pthread_mutex_lock
#pragma weak pthread_spin_lock
#pragma weak pthread_cond_wait
#pragma weak pthread_rwlock_rdlock
#pragma weak pthread_rwlock_wrlock
#pragma weak pthread_tryjoin_np
#pragma weak pthread_timedjoin_np
#pragma weak pthread_cond_timedwait
#pragma weak pthread_rwlock_timedrdlock
#pragma weak pthread_rwlock_timedwrlock
#pragma weak pthread_mutex_trylock
#pragma weak pthread_spin_trylock
#pragma weak pthread_rwlock_trywrlock
#pragma weak sigwait
#pragma weak sigwaitinfo
#pragma weak sigtimedwait
#pragma weak sigsuspend
namespace rocprofsys
{
namespace causal
{
namespace component
{
std::string
blocking_gotcha::label()
{
return "causal_blocking_gotcha";
}
std::string
blocking_gotcha::description()
{
return "Handles executing all necessary pauses before the thread performs some "
"blocking function";
}
void
blocking_gotcha::preinit()
{
configure();
}
void
blocking_gotcha::configure()
{
blocking_gotcha_t::get_initializer() = []() {
if(!config::get_use_causal()) return;
// postblock(true)
// - pthread_join
// - pthread_mutex_lock
// - pthread_cond_wait
// - pthread_barrier_wait
// - pthread_rwlock_rdlock
// - pthread_rwlock_wrlock
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 0, pthread_join);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 1, pthread_mutex_lock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 2, pthread_spin_lock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 3, pthread_cond_wait);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 4, pthread_rwlock_rdlock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 5, pthread_rwlock_wrlock);
// postblock(result == 0)
// - pthread_tryjoin_np
// - pthread_timedjoin_np
// - pthread_cond_timedwait
// - pthread_rwlock_timedrdlock
// - pthread_rwlock_timedwrlock
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 6, pthread_tryjoin_np);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 7, pthread_timedjoin_np);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 8, pthread_cond_timedwait);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 9, pthread_rwlock_timedrdlock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 10, pthread_rwlock_timedwrlock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 11, pthread_mutex_trylock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 12, pthread_spin_trylock);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 13, pthread_rwlock_trywrlock);
// postblock(...)
// - sigwait
// - sigwaitinfo
// - sigtimedwait
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 14, sigwait);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 15, sigwaitinfo);
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 16, sigtimedwait);
// other
TIMEMORY_C_GOTCHA(blocking_gotcha_t, 17, sigsuspend);
};
}
void
blocking_gotcha::shutdown()
{
blocking_gotcha_t::disable();
}
template <size_t Idx, typename Ret, typename... Args>
std::enable_if_t<(Idx <= blocking_gotcha::indexes::maybe_post_block_max_idx), Ret>
blocking_gotcha::operator()(gotcha_index<Idx>, Ret (*_func)(Args...),
Args... _args) const noexcept
{
int64_t _delay_value = causal::delay::get_global().load(std::memory_order_relaxed);
causal::sampling::block_backtrace_samples();
auto _ret = (*_func)(_args...);
causal::sampling::unblock_backtrace_samples();
if(get_thread_state() < ::rocprofsys::ThreadState::Internal)
{
if constexpr(Idx >= always_post_block_min_idx && Idx <= always_post_block_max_idx)
{
causal::delay::postblock(_delay_value);
}
else if constexpr(Idx >= maybe_post_block_min_idx &&
Idx <= maybe_post_block_max_idx)
{
if(_ret == 0) causal::delay::postblock(_delay_value);
}
else
{
static_assert(Idx > maybe_post_block_max_idx, "Error! bad overload");
}
}
return _ret;
}
int
blocking_gotcha::operator()(gotcha_index<sigwait_idx>, int (*)(const sigset_t*, int*),
const sigset_t* _set_v, int* _sig) const noexcept
{
auto _active = get_thread_state() < ::rocprofsys::ThreadState::Internal;
sigset_t _set = *_set_v;
causal_gotcha::remove_signals(&_set);
siginfo_t _info;
int64_t _delay_value = (_active) ? causal::delay::get_global().load() : 0;
auto* _data = blocking_gotcha_t::at(16);
auto f_sigwaitinfo = reinterpret_cast<decltype(&sigwaitinfo)>(_data->wrappee);
causal::sampling::block_backtrace_samples();
auto _ret = (*f_sigwaitinfo)(&_set, &_info);
causal::sampling::unblock_backtrace_samples();
// Woken up by another thread if the call did not fail and this is waking process
if(_active && _ret != -1 && _info.si_pid == process::get_id())
causal::delay::postblock(_delay_value);
if(_ret == -1)
return errno; // If there was an error, return the error code
else
*_sig = _ret; // sig is declared as non-null so skip check
return 0;
}
int
blocking_gotcha::operator()(gotcha_index<sigwaitinfo_idx>,
int (*_func)(const sigset_t*, siginfo_t*),
const sigset_t* _set_v, siginfo_t* _info_v) const noexcept
{
auto _active = get_thread_state() < ::rocprofsys::ThreadState::Internal;
sigset_t _set = *_set_v;
causal_gotcha::remove_signals(&_set);
siginfo_t _info;
int64_t _delay_value = (_active) ? causal::delay::get_global().load() : 0;
causal::sampling::block_backtrace_samples();
auto _ret = (*_func)(&_set, &_info);
causal::sampling::unblock_backtrace_samples();
// Woken up by another thread if the call did not fail and this is waking process
if(_active && _ret > 0 && _info.si_pid == process::get_id())
causal::delay::postblock(_delay_value);
if(_ret > 0 && _info_v) *_info_v = _info;
return _ret;
}
int
blocking_gotcha::operator()(gotcha_index<sigtimedwait_idx>,
int (*_func)(const sigset_t*, siginfo_t*,
const struct timespec*),
const sigset_t* _set_v, siginfo_t* _info_v,
const struct timespec* _wait_v) const noexcept
{
auto _active = get_thread_state() < ::rocprofsys::ThreadState::Internal;
sigset_t _set = *_set_v;
causal_gotcha::remove_signals(&_set);
siginfo_t _info;
int64_t _delay_value = (_active) ? causal::delay::get_global().load() : 0;
causal::sampling::block_backtrace_samples();
auto _ret = (*_func)(&_set, &_info, _wait_v);
causal::sampling::unblock_backtrace_samples();
// Woken up by another thread if the call did not fail and this is waking process
if(_active && _ret > 0 && _info.si_pid == process::get_id())
causal::delay::postblock(_delay_value);
if(_ret > 0 && _info_v) *_info_v = _info;
return _ret;
}
int
blocking_gotcha::operator()(gotcha_index<sigsuspend_idx>, int (*)(const sigset_t*),
const sigset_t* _set_v) const noexcept
{
auto _old_set = sigset_t{};
int _sig = 0;
::sigprocmask(SIG_SETMASK, _set_v, &_old_set);
// sigwait is wrapped so no need to block/unblock signals
auto _ret = ::sigwait(_set_v, &_sig);
::sigprocmask(SIG_SETMASK, &_old_set, nullptr);
return _ret;
}
} // namespace component
} // namespace causal
} // namespace rocprofsys
TIMEMORY_INVOKE_PREINIT(rocprofsys::causal::component::blocking_gotcha)
@@ -0,0 +1,103 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/components/gotcha/backends.hpp>
#include <timemory/mpl/macros.hpp>
#include <timemory/utility/types.hpp>
#include <array>
#include <cstddef>
#include <string>
namespace rocprofsys
{
namespace causal
{
namespace component
{
struct blocking_gotcha : comp::base<blocking_gotcha, void>
{
static constexpr size_t gotcha_capacity = 19;
template <size_t Idx>
using gotcha_index = std::integral_constant<size_t, Idx>;
enum indexes
{
always_post_block_min_idx = 0,
always_post_block_max_idx = 5,
maybe_post_block_min_idx = 6,
maybe_post_block_max_idx = 13,
sigwait_idx = 14,
sigwaitinfo_idx = 15,
sigtimedwait_idx = 16,
sigsuspend_idx = 17,
indexes_max = gotcha_capacity - 1,
};
ROCPROFSYS_DEFAULT_OBJECT(blocking_gotcha)
// string id for component
static std::string label();
static std::string description();
static void preinit();
// generate the gotcha wrappers
static void configure();
static void shutdown();
template <size_t Idx, typename Ret, typename... Args>
std::enable_if_t<(Idx <= maybe_post_block_max_idx), Ret> operator()(
gotcha_index<Idx>, Ret (*)(Args...), Args...) const noexcept;
int operator()(gotcha_index<sigwait_idx>, int (*)(const sigset_t*, int*),
const sigset_t*, int*) const noexcept;
int operator()(gotcha_index<sigwaitinfo_idx>, int (*)(const sigset_t*, siginfo_t*),
const sigset_t*, siginfo_t*) const noexcept;
int operator()(gotcha_index<sigtimedwait_idx>,
int (*)(const sigset_t*, siginfo_t*, const struct timespec*),
const sigset_t*, siginfo_t*, const struct timespec*) const noexcept;
int operator()(gotcha_index<sigsuspend_idx>, int (*)(const sigset_t*),
const sigset_t*) const noexcept;
};
using blocking_gotcha_t =
comp::gotcha<blocking_gotcha::gotcha_capacity, tim::type_list<>, blocking_gotcha>;
} // namespace component
} // namespace causal
} // namespace rocprofsys
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(prevent_reentry, causal::component::blocking_gotcha_t,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(static_data, causal::component::blocking_gotcha_t,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(fast_gotcha, causal::component::blocking_gotcha_t,
true_type)
@@ -0,0 +1,116 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/components/causal_gotcha.hpp"
#include "core/config.hpp"
#include "library/causal/components/blocking_gotcha.hpp"
#include "library/causal/components/unblocking_gotcha.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/signals/signal_mask.hpp>
#include <timemory/utility/macros.hpp>
#include <timemory/utility/types.hpp>
#include <array>
#include <vector>
namespace rocprofsys
{
namespace causal
{
namespace component
{
namespace
{
using bundle_t = tim::lightweight_tuple<blocking_gotcha_t, unblocking_gotcha_t>;
auto&
get_bundle()
{
static auto _v = std::unique_ptr<bundle_t>{};
if(!_v) _v = std::make_unique<bundle_t>("causal_gotcha");
return _v;
}
const auto&
sampling_signals()
{
static auto _v = get_sampling_signals();
return _v;
}
bool is_configured = false;
} // namespace
//--------------------------------------------------------------------------------------//
void
causal_gotcha::configure()
{
if(!is_configured)
{
blocking_gotcha::configure();
unblocking_gotcha::configure();
is_configured = true;
}
}
void
causal_gotcha::shutdown()
{
if(is_configured)
{
blocking_gotcha::shutdown();
unblocking_gotcha::shutdown();
is_configured = false;
}
}
void
causal_gotcha::start()
{
configure();
get_bundle()->start();
}
void
causal_gotcha::stop()
{
get_bundle()->stop();
shutdown();
}
void
causal_gotcha::remove_signals(sigset_t* _set)
{
for(auto _sig : sampling_signals())
{
if(sigismember(_set, _sig) != 0) sigdelset(_set, _sig);
}
if(sigismember(_set, SIGSEGV) != 0) sigdelset(_set, SIGSEGV);
if(sigismember(_set, SIGABRT) != 0) sigdelset(_set, SIGABRT);
}
} // namespace component
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,56 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <cstdint>
#include <future>
namespace rocprofsys
{
namespace causal
{
namespace component
{
struct causal_gotcha : tim::component::base<causal_gotcha, void>
{
ROCPROFSYS_DEFAULT_OBJECT(causal_gotcha)
// string id for component
static std::string label() { return "causal_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
static void start();
static void stop();
static void remove_signals(sigset_t*);
};
} // namespace component
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,240 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/components/progress_point.hpp"
#include "core/common.hpp"
#include "core/concepts.hpp"
#include "core/debug.hpp"
#include "core/timemory.hpp"
#include "library/causal/experiment.hpp"
#include "library/thread_data.hpp"
#include <timemory/hash/types.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/units.hpp>
namespace rocprofsys
{
namespace causal
{
namespace component
{
namespace
{
using progress_allocator_t = tim::data::ring_buffer_allocator<progress_point>;
using progress_map_t = std::unordered_map<tim::hash_value_t, progress_point*>;
auto&
get_progress_map()
{
using thread_data_t = thread_data<identity<progress_map_t>>;
static auto& _v = thread_data_t::instance(construct_on_init{});
return _v;
}
progress_map_t&
get_progress_map(int64_t _tid)
{
return get_progress_map()->at(_tid);
}
auto&
get_progress_allocator(int64_t _tid)
{
return thread_data<progress_allocator_t>::instance(construct_on_thread{ _tid });
}
} // namespace
std::unordered_map<tim::hash_value_t, progress_point>
progress_point::get_progress_points()
{
auto _data = std::unordered_map<tim::hash_value_t, progress_point>{};
if(!get_progress_map()) return _data;
for(const auto& titr : *get_progress_map())
{
for(const auto& itr : titr)
{
if(itr.second)
{
auto& ditr = _data[itr.first];
ditr += *itr.second;
ditr.set_hash(itr.second->get_hash());
itr.second->set_value(0);
}
}
}
return _data;
}
std::string
progress_point::label()
{
return "progress_point";
}
std::string
progress_point::description()
{
return "Tracks progress point latency and throughput for causal profiling";
}
void
progress_point::start()
{
++m_arrival;
}
void
progress_point::stop()
{
++m_departure;
}
void
progress_point::mark()
{
++m_delta;
}
void
progress_point::set_value(int64_t _v)
{
m_delta = _v;
m_arrival = _v;
m_departure = _v;
}
progress_point&
progress_point::operator+=(const progress_point& _v)
{
if(this != &_v)
{
m_delta += _v.m_delta;
m_arrival += _v.m_arrival;
m_departure += _v.m_departure;
}
return *this;
}
progress_point&
progress_point::operator-=(const progress_point& _v)
{
if(this != &_v)
{
m_delta -= _v.m_delta;
m_arrival -= _v.m_arrival;
m_departure -= _v.m_departure;
}
return *this;
}
bool
progress_point::is_throughput_point() const
{
return (m_delta != 0);
}
bool
progress_point::is_latency_point() const
{
return (m_arrival != 0 || m_departure != 0);
}
int64_t
progress_point::get_delta() const
{
return m_delta;
}
int64_t
progress_point::get_arrival() const
{
if(!is_latency_point()) return m_arrival;
// when it is a latency point, we want the difference to be greater than zero
return (m_arrival >= m_departure) ? (m_arrival + 1) : m_arrival;
}
int64_t
progress_point::get_departure() const
{
// if(!is_latency_point()) return m_departure;
// return (m_departure <= m_arrival) ? m_departure : (m_departure + 1);
return m_departure;
}
int64_t
progress_point::get_latency_delta() const
{
return (get_arrival() - get_departure());
}
int64_t
progress_point::get_laps() const
{
return std::max(get_delta(), get_latency_delta());
}
void
progress_point::print(std::ostream& os) const
{
os << tim::get_hash_identifier(m_hash) << " :: ";
tim::operation::base_printer<progress_point>(os, *this);
}
} // namespace component
} // namespace causal
} // namespace rocprofsys
namespace tim
{
namespace operation
{
namespace causal = rocprofsys::causal;
void
push_node<causal::component::progress_point>::operator()(type& _obj, scope::config,
hash_value_t _hash,
int64_t _tid) const
{
auto itr = causal::component::get_progress_map(_tid).emplace(_hash, nullptr);
if(itr.second && !itr.first->second)
{
auto& _alloc = causal::component::get_progress_allocator(_tid);
auto* _val = _alloc->allocate(1);
_alloc->construct(_val);
_val->set_hash(_hash);
itr.first->second = _val;
}
_obj.set_hash(_hash);
_obj.set_iterator(itr.first->second);
}
void
pop_node<causal::component::progress_point>::operator()(type& _obj, int64_t) const
{
auto* itr = _obj.get_iterator();
if(itr && !(_obj.get_is_invalid() || _obj.get_is_running()))
{
*itr += _obj;
}
}
} // namespace operation
} // namespace tim
@@ -0,0 +1,157 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include <timemory/components/base.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/utility/types.hpp>
#include <cstdint>
#include <unordered_map>
namespace rocprofsys
{
namespace causal
{
namespace component
{
struct progress_point : comp::base<progress_point, void>
{
using base_type = comp::base<progress_point, void>;
using value_type = int64_t;
using hash_type = tim::hash_value_t;
using iterator_type = progress_point*;
static std::string label();
static std::string description();
ROCPROFSYS_DEFAULT_OBJECT(progress_point)
void start();
void stop();
void mark();
void set_value(int64_t);
progress_point& operator+=(const progress_point&);
progress_point& operator-=(const progress_point&);
bool is_throughput_point() const;
bool is_latency_point() const;
void print(std::ostream& os) const;
void set_hash(hash_type _v) { m_hash = _v; }
void set_iterator(iterator_type _v) { m_iterator = _v; }
auto get_iterator() const { return m_iterator; }
auto get_hash() const { return m_hash; }
int64_t get_delta() const;
int64_t get_arrival() const;
int64_t get_departure() const;
int64_t get_latency_delta() const;
int64_t get_laps() const;
template <typename ArchiveT>
void load(ArchiveT& ar, const unsigned)
{
namespace cereal = ::tim::cereal;
auto _name = std::string{};
ar(cereal::make_nvp("name", _name));
ar(cereal::make_nvp("delta", m_delta));
ar(cereal::make_nvp("arrival", m_arrival));
ar(cereal::make_nvp("departure", m_departure));
m_hash = tim::hash::add_hash_id(_name);
}
template <typename ArchiveT>
void save(ArchiveT& ar, const unsigned) const
{
namespace cereal = ::tim::cereal;
ar(cereal::make_nvp("hash", m_hash));
ar(cereal::make_nvp("name", std::string{ tim::get_hash_identifier(m_hash) }));
ar(cereal::make_nvp("delta", m_delta));
ar(cereal::make_nvp("arrival", m_arrival));
ar(cereal::make_nvp("departure", m_departure));
}
static std::unordered_map<tim::hash_value_t, progress_point> get_progress_points();
private:
hash_type m_hash = 0;
int64_t m_delta = 0;
int64_t m_arrival = 0;
int64_t m_departure = 0;
progress_point* m_iterator = nullptr;
};
} // namespace component
} // namespace causal
} // namespace rocprofsys
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(uses_storage, causal::component::progress_point,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(flat_storage, causal::component::progress_point,
true_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(uses_timing_units, causal::component::progress_point,
true_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(is_timing_category, causal::component::progress_point,
true_type)
namespace tim
{
namespace operation
{
template <>
struct push_node<rocprofsys::causal::component::progress_point>
{
using type = rocprofsys::causal::component::progress_point;
ROCPROFSYS_DEFAULT_OBJECT(push_node)
push_node(type& _obj, scope::config _scope, hash_value_t _hash,
int64_t _tid = threading::get_id())
{
(*this)(_obj, _scope, _hash, _tid);
}
void operator()(type& _obj, scope::config, hash_value_t _hash,
int64_t _tid = threading::get_id()) const;
};
template <>
struct pop_node<rocprofsys::causal::component::progress_point>
{
using type = rocprofsys::causal::component::progress_point;
ROCPROFSYS_DEFAULT_OBJECT(pop_node)
pop_node(type& _obj, int64_t _tid = threading::get_id()) { (*this)(_obj, _tid); }
void operator()(type& _obj, int64_t _tid = threading::get_id()) const;
};
} // namespace operation
} // namespace tim
@@ -0,0 +1,145 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/components/unblocking_gotcha.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/state.hpp"
#include "library/causal/components/causal_gotcha.hpp"
#include "library/causal/delay.hpp"
#include "library/causal/experiment.hpp"
#include "library/causal/sampling.hpp"
#include "library/runtime.hpp"
#include <timemory/components/macros.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/utility/types.hpp>
#include <csignal>
#include <cstdint>
#include <pthread.h>
#include <stdexcept>
#pragma weak pthread_mutex_unlock
#pragma weak pthread_spin_unlock
#pragma weak pthread_cond_signal
#pragma weak pthread_cond_broadcast
#pragma weak pthread_kill
#pragma weak pthread_sigqueue
#pragma weak pthread_barrier_wait
#pragma weak kill
namespace rocprofsys
{
namespace causal
{
namespace component
{
std::string
unblocking_gotcha::label()
{
return "causal_unblocking_gotcha";
}
std::string
unblocking_gotcha::description()
{
return "Handles executing all necessary pauses before the thread performs some "
"blocking function";
}
void
unblocking_gotcha::preinit()
{
configure();
}
void
unblocking_gotcha::configure()
{
unblocking_gotcha_t::get_initializer() = []() {
if(!config::get_use_causal()) return;
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 0, pthread_mutex_unlock);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 1, pthread_spin_unlock);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 2, pthread_rwlock_unlock);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 3, pthread_cond_signal);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 4, pthread_cond_broadcast);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 5, pthread_kill);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 6, pthread_sigqueue);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 7, pthread_barrier_wait);
TIMEMORY_C_GOTCHA(unblocking_gotcha_t, 8, kill);
};
}
void
unblocking_gotcha::shutdown()
{
unblocking_gotcha_t::disable();
}
template <size_t Idx, typename Ret, typename... Args>
std::enable_if_t<(Idx < unblocking_gotcha::indexes::kill_idx), Ret>
unblocking_gotcha::operator()(gotcha_index<Idx>, Ret (*_func)(Args...),
Args... _args) const noexcept
{
auto _active = get_thread_state() < ::rocprofsys::ThreadState::Internal;
if(_active)
{
causal::delay::process();
if constexpr(Idx == pthread_barrier_wait_idx)
{
int64_t _delay_value = (_active) ? causal::delay::get_global().load() : 0;
causal::sampling::block_backtrace_samples();
auto _ret = (*_func)(_args...);
causal::sampling::unblock_backtrace_samples();
causal::delay::postblock(_delay_value);
return _ret;
}
}
return (*_func)(_args...);
}
int
unblocking_gotcha::operator()(gotcha_index<kill_idx>, int (*_func)(pid_t, int),
pid_t _pid, int _sig) const noexcept
{
auto _active = get_thread_state() < ::rocprofsys::ThreadState::Internal;
if(_active && _pid == process::get_id()) causal::delay::process();
causal::sampling::block_backtrace_samples();
auto _ret = (*_func)(_pid, _sig);
causal::sampling::unblock_backtrace_samples();
return _ret;
}
} // namespace component
} // namespace causal
} // namespace rocprofsys
TIMEMORY_INVOKE_PREINIT(rocprofsys::causal::component::unblocking_gotcha)
@@ -0,0 +1,87 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/components/gotcha/backends.hpp>
#include <timemory/mpl/macros.hpp>
#include <array>
#include <cstddef>
#include <string>
namespace rocprofsys
{
namespace causal
{
namespace component
{
struct unblocking_gotcha : comp::base<unblocking_gotcha, void>
{
static constexpr size_t gotcha_capacity = 9;
enum indexes
{
pthread_barrier_wait_idx = 7,
kill_idx = 8,
indexes_max = gotcha_capacity,
};
template <size_t Idx>
using gotcha_index = std::integral_constant<size_t, Idx>;
ROCPROFSYS_DEFAULT_OBJECT(unblocking_gotcha)
// string id for component
static std::string label();
static std::string description();
static void preinit();
// generate the gotcha wrappers
static void configure();
static void shutdown();
template <size_t Idx, typename Ret, typename... Args>
std::enable_if_t<(Idx < kill_idx), Ret> operator()(gotcha_index<Idx>,
Ret (*)(Args...),
Args...) const noexcept;
int operator()(gotcha_index<kill_idx>, int (*)(pid_t, int), pid_t,
int) const noexcept;
};
using unblocking_gotcha_t =
comp::gotcha<unblocking_gotcha::gotcha_capacity, tim::type_list<>, unblocking_gotcha>;
} // namespace component
} // namespace causal
} // namespace rocprofsys
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(prevent_reentry, causal::component::unblocking_gotcha_t,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(static_data, causal::component::unblocking_gotcha_t,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(fast_gotcha, causal::component::unblocking_gotcha_t,
true_type)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,80 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "binary/analysis.hpp"
#include "core/binary/fwd.hpp"
#include "core/containers/c_array.hpp"
#include "core/containers/static_vector.hpp"
#include "core/defines.hpp"
#include "core/utility.hpp"
#include "library/causal/fwd.hpp"
#include "library/thread_data.hpp"
#include <timemory/hash/types.hpp>
#include <timemory/tpls/cereal/cereal/cereal.hpp>
#include <timemory/utility/procfs/maps.hpp>
#include <timemory/utility/unwind.hpp>
#include <deque>
#include <dlfcn.h>
#include <map>
namespace rocprofsys
{
namespace causal
{
void
save_line_info(const settings::compose_filename_config&, int _verbose);
std::deque<binary::symbol>
get_line_info(uintptr_t _addr, bool include_discarded = true);
bool is_eligible_address(uintptr_t);
size_t set_current_selection(unwind_addr_t);
size_t set_current_selection(container::c_array<uint64_t>);
void
reset_sample_selection();
selected_entry
sample_selection(size_t _nitr = 1000, size_t _wait_ns = 100000);
void push_progress_point(std::string_view);
void pop_progress_point(std::string_view);
void
mark_progress_point(std::string_view, bool force = false);
uint16_t
sample_virtual_speedup();
void
start_experimenting();
void
finish_experimenting();
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,191 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/delay.hpp"
#include "core/state.hpp"
#include "core/utility.hpp"
#include "library/causal/components/causal_gotcha.hpp"
#include "library/causal/experiment.hpp"
#include "library/causal/sampling.hpp"
#include "library/runtime.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include "library/tracing.hpp"
#include <timemory/components/macros.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/process/threading.hpp>
#include <atomic>
#include <chrono>
#include <random>
namespace rocprofsys
{
namespace causal
{
namespace
{
auto&
get_delay_data()
{
using thread_data_t = thread_data<identity<int64_t>, delay>;
static auto& _v = thread_data_t::construct(
construct_on_init{}, []() { return delay::get_global().load(); });
return _v;
}
int64_t
compute_sleep_for_overhead()
{
using random_engine_t = std::mt19937_64;
auto _engine = random_engine_t{ std::random_device{}() };
auto _dist = std::uniform_int_distribution<int64_t>{ 0, 5 };
size_t _ntot = 250;
size_t _nwarm = 50;
auto _stats = tim::statistics<double>{};
for(size_t i = 0; i < _ntot; ++i)
{
auto _val = _dist(_engine);
int64_t _beg = tracing::now();
std::this_thread::sleep_for(std::chrono::nanoseconds{ _val });
int64_t _end = tracing::now();
if(i < _nwarm) continue;
auto _diff = (_end - _beg);
ROCPROFSYS_CONDITIONAL_THROW(
_diff < _val, "Error! sleep_for(%zu) [nanoseconds] >= %zu", _val, _diff);
_stats += (_diff - _val);
}
ROCPROFSYS_BASIC_VERBOSE(2,
"[causal] overhead of std::this_thread::sleep_for(...) "
"invocation = %6.3f usec +/- %e\n",
_stats.get_mean() / units::usec,
_stats.get_stddev() / units::usec);
tim::manager::instance()->add_metadata([_stats](auto& ar) {
ar(tim::cereal::make_nvp("causal thread sleep overhead [nsec]", _stats));
});
(void) get_delay_data();
return _stats.get_mean();
}
int64_t sleep_for_overhead = 0;
} // namespace
void
delay::setup()
{
static std::once_flag _once{};
std::call_once(_once, []() { sleep_for_overhead = compute_sleep_for_overhead(); });
}
void
delay::process()
{
if(causal::experiment::is_active())
{
if(get_global() < get_local())
{
get_global() += (get_local() - get_global());
}
else if(get_global() > get_local())
{
::rocprofsys::causal::sampling::pause();
auto _beg = tracing::now();
std::this_thread::sleep_for(
std::chrono::nanoseconds{ get_global() - get_local() });
get_local() += (tracing::now() - _beg);
::rocprofsys::causal::sampling::resume();
}
}
else
{
get_local() = get_global();
}
}
void
delay::credit()
{
auto _diff = get_global() - get_local();
if(_diff > 0)
{
get_local() += _diff;
}
}
void
delay::preblock()
{
auto _diff = get_global() - get_local();
if(_diff > 0)
{
get_local() += _diff;
}
}
void
delay::postblock(int64_t _preblock_global_delay_value)
{
get_local() += (get_global() - _preblock_global_delay_value);
}
int64_t
delay::sync()
{
auto _v = get_global().load(std::memory_order_seq_cst);
if(get_delay_data()) get_delay_data()->fill(_v);
return _v;
}
std::atomic<int64_t>&
delay::get_global()
{
static auto _v = std::atomic<int64_t>{ 0 };
return _v;
}
int64_t&
delay::get_local(int64_t _tid)
{
auto& _data = get_delay_data();
static thread_local auto _thr_init = []() {
using thread_data_t = thread_data<identity<int64_t>, delay>;
thread_data_t::construct(construct_on_thread{ threading::get_id() },
get_global().load());
return true;
}();
return _data->at(_tid);
(void) _thr_init;
}
uint64_t
delay::compute_total_delay(uint64_t _baseline)
{
return get_global().load() - _baseline;
}
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,61 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "library/thread_data.hpp"
#include <timemory/components/base.hpp>
#include <timemory/macros/language.hpp>
#include <timemory/mpl/concepts.hpp>
#include <atomic>
#include <cstdint>
namespace rocprofsys
{
namespace causal
{
struct delay : comp::empty_base
{
using value_type = void;
ROCPROFSYS_DEFAULT_OBJECT(delay)
static void setup();
static void process();
static void credit();
static void preblock();
static void postblock(int64_t);
static int64_t sync();
static std::atomic<int64_t>& get_global();
static int64_t& get_local(int64_t _tid = threading::get_id());
static int64_t get(int64_t _tid = threading::get_id());
static uint64_t compute_total_delay(uint64_t);
};
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,715 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/experiment.hpp"
#include "binary/analysis.hpp"
#include "binary/dwarf_entry.hpp"
#include "binary/symbol.hpp"
#include "common/defines.h"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/state.hpp"
#include "library/causal/components/backtrace.hpp"
#include "library/causal/components/progress_point.hpp"
#include "library/causal/data.hpp"
#include "library/causal/delay.hpp"
#include "library/causal/sample_data.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include "library/tracing.hpp"
#include <timemory/components/timing/backends.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/mpl/policy.hpp>
#include <timemory/tpls/cereal/archives.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/tpls/cereal/cereal/archives/json.hpp>
#include <timemory/tpls/cereal/types.hpp>
#include <timemory/units.hpp>
#include <timemory/unwind/dlinfo.hpp>
#include <chrono>
#include <ratio>
#include <regex>
#include <string>
#include <thread>
#include <vector>
namespace rocprofsys
{
namespace causal
{
namespace
{
using backtrace_causal = rocprofsys::causal::component::backtrace;
namespace cereal = ::tim::cereal;
auto current_experiment_value = experiment{};
auto current_selected_count = std::atomic<uint64_t>{ 0 };
auto current_experiment = std::atomic<experiment*>{ nullptr };
auto experiment_history = std::vector<experiment>{};
int64_t global_scaling = 1;
int64_t global_scaling_increments = 0;
bool use_exp_speedup_scaling =
get_env<bool>("ROCPROFSYS_CAUSAL_SCALE_EXPERIMENT_TIME_BY_SPEEDUP", false);
} // namespace
experiment::sample::sample(const base_type& _b, uint64_t _c)
: base_type{ _b }
, count{ _c }
{
if(lineinfo)
{
for(const auto& itr : lineinfo.lines)
{
if(itr.inlined)
inlines.emplace_back(
binary::inlined_symbol{ itr.line, itr.location, itr.name });
}
}
}
bool
experiment::sample::operator==(const sample& _v) const
{
return base_type::operator==(_v);
}
bool
experiment::sample::operator<(const sample& _v) const
{
return base_type::operator<(_v);
}
const auto&
experiment::sample::operator+=(const sample& _v) const
{
if(*this == _v && this != &_v) count += _v.count;
return *this;
}
template <typename ArchiveT>
void
experiment::sample::serialize(ArchiveT& ar, const unsigned)
{
namespace cereal = ::tim::cereal;
using cereal::make_nvp;
ar(cereal::make_nvp("count", count));
if constexpr(concepts::is_output_archive<ArchiveT>::value)
{
ar(cereal::make_nvp("location", get_identifier()));
}
ar.setNextName("info");
ar.startNode();
ar(make_nvp("address", address), make_nvp("line", lineno), make_nvp("file", location),
make_nvp("func", name));
if constexpr(concepts::is_output_archive<ArchiveT>::value)
{
ar(cereal::make_nvp("dfunc", demangle(name)),
cereal::make_nvp("dwarf_info", std::vector<binary::dwarf_entry>{}));
}
ar(cereal::make_nvp("inlines", inlines));
ar.finishNode();
ar(cereal::make_nvp("dlinfo", info));
}
std::string
experiment::sample::get_identifier() const
{
return (lineno > 0 && !location.empty()) ? join(":", location, lineno)
: demangle(name);
}
template <typename ArchiveT>
void
experiment::record::serialize(ArchiveT& ar, const unsigned)
{
namespace cereal = ::tim::cereal;
ar(cereal::make_nvp("startup_time", startup),
cereal::make_nvp("experiments", experiments),
cereal::make_nvp("runtime", runtime));
auto _samples = std::vector<sample>{};
if constexpr(concepts::is_input_archive<ArchiveT>::value)
{
ar(cereal::make_nvp("samples", _samples));
for(auto& itr : _samples)
samples.emplace_back(std::move(itr));
}
else
{
ar(cereal::make_nvp("samples", samples));
}
}
template <typename ArchiveT>
void
experiment::serialize(ArchiveT& ar, const unsigned)
{
namespace cereal = ::tim::cereal;
ar(cereal::make_nvp("index", index),
cereal::make_nvp("virtual_speedup", virtual_speedup),
cereal::make_nvp("sampling_period", sampling_period),
cereal::make_nvp("start_time", start_time), cereal::make_nvp("end_time", end_time),
cereal::make_nvp("experiment_time", experiment_time),
cereal::make_nvp("batch_size", batch_size), cereal::make_nvp("duration", duration),
cereal::make_nvp("scaling_factor", scaling_factor),
cereal::make_nvp("selected", selected),
cereal::make_nvp("sample_delay", sample_delay),
cereal::make_nvp("delay_scaling", delay_scaling),
cereal::make_nvp("total_delay", total_delay),
cereal::make_nvp("global_delay", global_delay),
cereal::make_nvp("selection", selection));
if constexpr(concepts::is_input_archive<ArchiveT>::value)
{
auto _ppts = std::vector<component::progress_point>{};
init_progress.clear();
fini_progress.clear();
ar(cereal::make_nvp("progress_points", _ppts));
for(auto itr : _ppts)
fini_progress.emplace(itr.get_hash(), itr);
}
else
{
auto _ppts = std::vector<component::progress_point>{};
{
auto ppts = fini_progress;
for(auto& pitr : ppts)
pitr.second.set_hash(pitr.first);
for(auto pitr : init_progress)
ppts[pitr.first] -= pitr.second;
_ppts.reserve(ppts.size());
for(auto& pitr : ppts)
_ppts.emplace_back(pitr.second);
}
ar(cereal::make_nvp("progress_points", _ppts));
}
}
std::string
experiment::label()
{
return "causal_experiment";
}
std::string
experiment::description()
{
return "Records an experiment for causal profiling";
}
const std::atomic<experiment*>&
experiment::get_current_experiment()
{
return current_experiment;
}
bool
experiment::start()
{
if(running && tracing::now() < start_time + experiment_time) return false;
selection = sample_selection();
if(!selection) return false;
// sampling period in nanoseconds
sampling_period = backtrace_causal::get_period(units::nsec);
// experiment time is scaled up for longer speedups
index = experiment_history.size() + 1;
virtual_speedup = sample_virtual_speedup();
delay_scaling = virtual_speedup / 100.0;
if(use_exp_speedup_scaling) scaling_factor *= (1.0 + delay_scaling);
experiment_time = global_scaling * scaling_factor * sampling_period * batch_size;
sample_delay = sampling_period * delay_scaling;
total_delay = delay::sync();
init_progress = component::progress_point::get_progress_points();
start_time = tracing::now();
ROCPROFSYS_VERBOSE(0, "Starting causal experiment #%-3u: %s\n", index,
as_string().c_str());
if(get_state() < State::Finalized)
{
current_experiment_value = *this;
current_selected_count.store(0);
current_experiment.store(this);
return true;
}
return false;
}
bool
experiment::wait() const
{
auto _now = tracing::now();
auto _wait = experiment_time - (_now - start_time);
auto _end = _now + _wait;
auto _incr = std::min<uint64_t>(_wait / 100, 1000000);
while(tracing::now() < _end && get_state() < State::Finalized)
{
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::nanoseconds{ _incr });
}
return (tracing::now() >= _end);
}
bool
experiment::stop()
{
auto _now = tracing::now();
if(_now < start_time + experiment_time) return false;
current_experiment.store(nullptr);
selected = current_selected_count.load();
running = false;
end_time = _now;
experiment_time = (end_time - start_time);
global_delay = delay::compute_total_delay(0);
total_delay = (global_delay - total_delay);
duration = (experiment_time > total_delay) ? (experiment_time - total_delay) : 0;
fini_progress = component::progress_point::get_progress_points();
// sync data
delay::sync();
auto _prog_stats = tim::statistics<double>{};
auto _prog_vals = std::vector<int64_t>{};
_prog_vals.reserve(fini_progress.size());
for(auto fitr : fini_progress)
{
auto _pt = fitr.second - init_progress[fitr.first];
int64_t _num =
std::max<int64_t>({ _pt.get_laps(), _pt.get_arrival(), _pt.get_departure() });
if(_num > 0) _prog_vals.emplace_back(_num);
}
std::sort(_prog_vals.begin(), _prog_vals.end());
for(auto itr : _prog_vals)
_prog_stats += itr;
auto _nvals = _prog_vals.size();
auto _medi = (_nvals > 2) ? _prog_vals.at(_nvals / 2) : _prog_vals.front();
auto _mean = (_nvals > 0) ? _prog_stats.get_mean() : 0;
auto _high = (_nvals > 0) ? _prog_stats.get_max() : 0;
auto _lowv = (_nvals > 0) ? _prog_stats.get_min() : 0;
if(_lowv <= 3 && (_mean < 5 || _medi < 5))
{
ROCPROFSYS_VERBOSE(2,
"[progress points] increasing experiment time :: low: %6.3f, "
"high: %6.3f, mean: %6.3f, median: %zi\n",
_lowv, _high, _mean, _medi);
global_scaling *= 2;
++global_scaling_increments; // keep track of how many successive increments have
// been performed
}
else if(_mean > 10 && _lowv >= 8 && global_scaling > 1)
{
ROCPROFSYS_VERBOSE(2,
"[progress points] decreasing experiment time :: low: %6.3f, "
"high: %6.3f, mean: %6.3f, median: %zi\n",
_lowv, _high, _mean, _medi);
global_scaling /= 2;
global_scaling_increments = 0;
}
if(ROCPROFSYS_UNLIKELY(global_scaling_increments >= 5))
{
ROCPROFSYS_WARNING(
0,
"Warning! causal experimentation hasn't seen at least 5 progress points "
"in the last %li experiments. Progress points are necessary for measuring "
"the effect of the virtual speed-up. Please visit "
"https://rocm.docs.amd.com/projects/rocprofiler-systems/en/latest/ for "
"documentation on progress "
"points and how to add them\n",
global_scaling_increments);
}
if(_high > 0) experiment_history.emplace_back(*this);
std::this_thread::sleep_for(
std::chrono::nanoseconds{ 5 * sampling_period * batch_size });
return true;
}
std::string
experiment::as_string() const
{
std::stringstream _ss{};
auto _dur = static_cast<double>(experiment_time) / static_cast<double>(units::sec);
_ss << std::boolalpha << "speed-up: " << std::setw(3) << virtual_speedup
<< "%, period: " << std::setw(4) << std::fixed << std::setprecision(2)
<< (sampling_period / static_cast<double>(units::msec)) << " msec";
if(!config::get_causal_end_to_end())
_ss << ", duration: " << std::setw(5) << std::fixed << std::setprecision(3)
<< _dur << " sec";
_ss << " :: experiment: " << as_hex(selection.address) << " ";
if(selection.symbol_address > 0 && selection.address != selection.symbol_address)
_ss << "(symbol@" << as_hex(selection.symbol_address) << ") ";
if(!selection.symbol.file.empty() && selection.symbol.line > 0)
_ss << "[" << filepath::basename(selection.symbol.file) << ":"
<< selection.symbol.line << "]";
auto _patch = [](std::string _v) {
auto _pos = std::string::npos;
using strpair_t = std::pair<std::string_view, std::string>;
for(const auto& itr :
{ strpair_t{
"::basic_string<char, std::char_traits<char>, std::allocator<char> > ",
"::string" },
strpair_t{ "::__cxx11::", "::" } })
{
while((_pos = _v.find(itr.first)) != std::string::npos)
_v = _v.replace(_pos, itr.first.length(), itr.second);
}
return _v;
};
auto _func = _patch(demangle(selection.symbol.func));
_ss << "['" << _func << "']";
return _ss.str();
}
// in nanoseconds
uint64_t
experiment::get_delay()
{
if(!current_experiment.load()) return 0;
return current_experiment_value.sample_delay;
}
double
experiment::get_delay_scaling()
{
if(!current_experiment.load()) return 0;
return current_experiment_value.delay_scaling;
}
uint32_t
experiment::get_index()
{
if(!is_active()) return 0;
return current_experiment_value.index;
}
bool
experiment::is_active()
{
return (current_experiment.load(std::memory_order_relaxed) != nullptr);
}
bool
experiment::is_selected(uint64_t _addr)
{
return (is_active() && current_experiment_value.selection.contains(_addr));
}
bool
experiment::is_selected(unwind_addr_t _stack)
{
if(is_active())
{
for(auto itr : _stack)
if(itr > 0 && current_experiment_value.selection.contains(itr)) return true;
}
return false;
}
bool
experiment::is_selected(container::c_array<uint64_t> _stack)
{
if(is_active())
{
for(auto itr : _stack)
if(itr > 0 && current_experiment_value.selection.contains(itr)) return true;
}
return false;
}
void
experiment::add_selected()
{
if(current_experiment.load() == nullptr) return;
++current_selected_count;
}
std::vector<experiment>
experiment::get_experiments()
{
return experiment_history;
}
void
experiment::save_experiments()
{
auto _cfg = settings::compose_filename_config{};
_cfg.subdirectory = "causal";
_cfg.use_suffix = config::get_use_pid();
save_experiments(config::get_causal_output_filename(), _cfg);
}
void // NOLINTNEXTLINE
experiment::save_experiments(std::string _fname_base, const filename_config_t& _cfg)
{
const auto& _info0 = thread_info::get(0, InternalTID);
auto current_record = record{};
current_record.startup = _info0->lifetime.first;
// update experiments
{
for(auto& itr : experiment_history)
{
if(itr.duration == 0 || itr.experiment_time == 0) continue;
current_record.experiments.emplace_back(std::move(itr));
}
experiment_history.clear();
}
// update runtime value
{
uint64_t _beg_runtime = std::numeric_limits<uint64_t>::max();
uint64_t _end_runtime = std::numeric_limits<uint64_t>::min();
for(auto& itr : current_record.experiments)
{
if(itr.duration == 0) continue;
if(itr.experiment_time == 0) continue;
_beg_runtime = std::min<uint64_t>(_beg_runtime, itr.start_time);
_end_runtime = std::max<uint64_t>(_end_runtime, itr.end_time);
}
current_record.runtime = (_end_runtime - _beg_runtime);
}
// update sample data
{
auto _add_sample = [&current_record](sample&& _v) {
current_record.samples.emplace_back(std::move(_v));
};
auto _total_samples = std::map<uintptr_t, size_t>{};
for(const auto& itr : get_samples())
{
for(const auto& sitr : itr.second)
{
_total_samples[sitr.address] += sitr.count;
}
}
ROCPROFSYS_VERBOSE_F(1, "Processing line info for %zu sampled addresses...\n",
_total_samples.size());
for(const auto& itr : _total_samples)
{
auto _entry = binary::lookup_ipaddr_entry<true>(itr.first);
if(_entry) _add_sample(sample{ *_entry, itr.second });
}
auto _binfo_cfg = settings::compose_filename_config{};
_binfo_cfg.subdirectory = "causal/binary-info";
_binfo_cfg.use_suffix = config::get_use_pid();
save_line_info(_binfo_cfg, config::get_verbose());
}
bool _causal_output_reset =
config::get_setting_value<bool>("ROCPROFSYS_CAUSAL_FILE_RESET").value_or(false);
{
auto _saved_experiments = (_causal_output_reset)
? std::vector<experiment::record>{}
: load_experiments(_fname_base, _cfg, false);
_saved_experiments.emplace_back(current_record);
std::stringstream oss{};
{
auto ar =
tim::policy::output_archive<cereal::PrettyJSONOutputArchive>::get(oss);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName("causal");
ar->startNode();
(*ar)(cereal::make_nvp("records", _saved_experiments));
ar->finishNode();
ar->finishNode();
}
auto _fname = tim::settings::compose_output_filename(_fname_base, "json", _cfg);
auto ofs = std::ofstream{};
if(tim::filepath::open(ofs, _fname))
{
if(get_verbose() >= 0)
operation::file_output_message<experiment>{}(
_fname, std::string{ "causal_experiments" });
ofs << oss.str() << "\n";
}
else
{
ROCPROFSYS_THROW("Error opening causal experiments output file: %s",
_fname.c_str());
}
}
auto _fname = tim::settings::compose_output_filename(_fname_base, "coz", _cfg);
// read in existing data
auto _existing = std::stringstream{};
if(!_causal_output_reset)
{
std::ifstream ifs{ _fname };
if(ifs)
{
while(ifs && ifs.good())
{
std::string _line;
std::getline(ifs, _line);
_existing << _line << "\n";
}
}
}
std::ofstream ofs{};
ofs.setf(std::ios::fixed);
if(tim::filepath::open(ofs, _fname))
{
if(get_verbose() >= 0)
operation::file_output_message<experiment>{}(
_fname, std::string{ "causal_experiments" });
ofs << _existing.str();
ofs << "startup\ttime=" << current_record.startup << "\n";
for(auto& itr : current_record.experiments)
{
auto& _selection = itr.selection;
auto& _line_info = _selection.symbol;
std::string _name = (_selection.symbol_address > 0)
? _line_info.func
: join(":", _line_info.file, _line_info.line);
ROCPROFSYS_CONDITIONAL_THROW(
_name.empty(),
"Error! causal experiment selection has no name: address=%s, file=%s, "
"line=%u, func=%s",
as_hex(_line_info.address).c_str(), _line_info.file.c_str(),
_line_info.line, _line_info.func.c_str());
ofs << "experiment\tselected=" << demangle(_name)
<< "\tspeedup=" << std::setprecision(2)
<< static_cast<double>(itr.virtual_speedup / 100.0)
<< "\tduration=" << itr.duration << "\tselected-samples=" << itr.selected
<< "\n";
auto ppts = itr.fini_progress;
for(auto pitr : itr.init_progress)
ppts[pitr.first] -= pitr.second;
for(auto pitr : ppts)
{
// if(pitr.second.get_laps() == 0) continue;
if(get_causal_end_to_end() && pitr.second.get_laps() > 1) continue;
if(pitr.second.is_throughput_point() && pitr.second.get_delta() != 0)
{
ofs << "throughput-point\tname="
<< tim::demangle(tim::get_hash_identifier(pitr.first))
<< "\tdelta=" << pitr.second.get_delta() << "\n";
if(get_causal_end_to_end()) break;
}
if(pitr.second.is_latency_point())
{
if(get_causal_end_to_end()) continue;
auto _delta = std::max<int64_t>(pitr.second.get_latency_delta(), 1);
ofs << "latency-point\tname="
<< tim::demangle(tim::get_hash_identifier(pitr.first))
<< "\tarrivals=" << pitr.second.get_arrival()
<< "\tdepartures=" << pitr.second.get_departure()
<< "\tdifference=" << _delta << "\n";
}
}
}
ofs << "runtime\ttime=" << current_record.runtime << "\n";
for(const auto& itr : current_record.samples)
{
ofs << "samples\tlocation=" << itr.get_identifier()
<< "\tcount=" << itr.count;
if(config::get_debug()) ofs << "\taddress=" << as_hex(itr.address);
ofs << "\n";
}
}
else
{
ROCPROFSYS_THROW("Error opening causal experiments output file: %s",
_fname.c_str());
}
}
std::vector<experiment::record>
experiment::load_experiments(bool _throw_on_error)
{
auto _cfg = settings::compose_filename_config{};
_cfg.subdirectory = "causal";
_cfg.use_suffix = config::get_use_pid();
return load_experiments(config::get_causal_output_filename(), _cfg, _throw_on_error);
}
std::vector<experiment::record>
experiment::load_experiments(std::string _fname, const filename_config_t& _cfg,
bool _throw_on_error)
{
_fname = tim::settings::compose_input_filename(_fname, "json", _cfg);
auto ifs = std::ifstream{};
auto _data = std::vector<experiment::record>{};
if(tim::filepath::open(ifs, _fname))
{
auto ar = tim::policy::input_archive<cereal::JSONInputArchive>::get(ifs);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName("causal");
ar->startNode();
(*ar)(cereal::make_nvp("records", _data));
ar->finishNode();
ar->finishNode();
}
else
{
if(_throw_on_error)
{
ROCPROFSYS_THROW("Error opening causal experiments input file: %s",
_fname.c_str());
}
}
return _data;
}
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,148 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "binary/dwarf_entry.hpp"
#include "binary/symbol.hpp"
#include "core/containers/c_array.hpp"
#include "core/defines.hpp"
#include "core/utility.hpp"
#include "library/causal/components/backtrace.hpp"
#include "library/causal/components/progress_point.hpp"
#include "library/causal/data.hpp"
#include "library/causal/selected_entry.hpp"
#include <timemory/hash/types.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/tpls/cereal/cereal/cereal.hpp>
#include <timemory/unwind/types.hpp>
#include <timemory/utility/unwind.hpp>
#include <atomic>
#include <cstdint>
#include <unordered_map>
namespace rocprofsys
{
namespace causal
{
using hash_value_t = ::tim::hash_value_t;
struct experiment
{
using progress_points_t =
std::unordered_map<tim::hash_value_t, component::progress_point>;
using experiments_t = std::vector<experiment>;
using filename_config_t = settings::compose_filename_config;
using period_stats_t = tim::statistics<int64_t>;
struct sample : unwind::processed_entry
{
using base_type = unwind::processed_entry;
sample() = default;
sample(const base_type&, uint64_t);
mutable uint64_t count = 0;
std::vector<binary::inlined_symbol> inlines = {};
bool operator==(const sample&) const;
bool operator<(const sample&) const;
const auto& operator+=(const sample&) const;
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned);
std::string get_identifier() const;
};
struct record
{
int64_t startup = 0;
uint64_t runtime = 0;
std::vector<experiment> experiments = {};
std::vector<sample> samples = {};
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned);
};
static std::string label();
static std::string description();
static const std::atomic<experiment*>& get_current_experiment();
ROCPROFSYS_DEFAULT_OBJECT(experiment)
bool start();
bool wait() const; // returns false if interrupted
bool stop();
std::string as_string() const;
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned version);
// in nanoseconds
static uint64_t get_delay();
static double get_delay_scaling();
static uint32_t get_index();
static bool is_active();
static bool is_selected(uint64_t);
static bool is_selected(unwind_addr_t);
static bool is_selected(container::c_array<uint64_t>);
static void add_selected();
static experiments_t get_experiments();
template <size_t N>
static bool is_selected(std::array<uint64_t, N> _v)
{
return is_selected(container::c_array<uint64_t>{ _v.data(), _v.size() });
}
static void save_experiments();
static void save_experiments(std::string, const filename_config_t&);
static std::vector<record> load_experiments(bool _throw_on_err = true);
static std::vector<record> load_experiments(std::string, const filename_config_t&,
bool = true);
bool running = false;
uint16_t virtual_speedup = 0; /// 0-100 in multiples of 5
uint32_t index = 0; /// experiment number
uint64_t sampling_period = 0; /// period b/t samples [nsec]
uint64_t start_time = 0; /// start of experiment [nsec]
uint64_t end_time = 0; /// end of experiment [nsec]
uint64_t experiment_time = 0; /// how long the experiment ran [nsec]
uint64_t duration = 0; /// runtime - delays [nsec]
uint64_t batch_size = 10; /// batch factor for experiment/cooloff
uint64_t scaling_factor = 100; /// scaling factor for experiment time
uint64_t sample_delay = 0; /// how long to delay [nsec]
uint64_t total_delay = 0; /// total delays [nsec]
uint64_t selected = 0; /// num times selected line sampled
uint64_t global_delay = 0;
double delay_scaling = 0.0; /// virtual_speedup / 100.
selected_entry selection = {}; /// which line was selected
progress_points_t init_progress = {}; /// progress points at start
progress_points_t fini_progress = {}; /// progress points at end
};
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,53 @@
// MIT License
//
// Copyright (c) 2022-2024 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/binary/fwd.hpp"
#include "core/containers/static_vector.hpp"
#include "core/defines.hpp"
#include <timemory/hash/types.hpp>
#include <timemory/unwind/stack.hpp>
#include <timemory/utility/procfs/maps.hpp>
#include <cstddef>
#include <cstdint>
#include <map>
#include <utility>
namespace rocprofsys
{
namespace unwind = ::tim::unwind;
namespace causal
{
static constexpr size_t unwind_depth = ROCPROFSYS_MAX_UNWIND_DEPTH;
static constexpr size_t unwind_offset = 0;
using unwind_stack_t = unwind::stack<unwind_depth>;
using unwind_addr_t = container::static_vector<uintptr_t, unwind_depth>;
using hash_value_t = tim::hash_value_t;
struct selected_entry;
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,84 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/sample_data.hpp"
#include <chrono>
#include <cstdint>
#include <map>
#include <set>
namespace rocprofsys
{
namespace causal
{
namespace
{
auto samples = std::map<uint32_t, std::map<uintptr_t, uint64_t>>{};
}
std::vector<sample_data>
get_samples(uint32_t _index)
{
auto _data = std::vector<sample_data>{};
_data.reserve(samples.at(_index).size());
for(const auto& itr : samples.at(_index))
{
_data.emplace_back(sample_data{ itr.first, itr.second });
}
return _data;
}
std::map<uint32_t, std::vector<sample_data>>
get_samples()
{
auto _data = std::map<uint32_t, std::vector<sample_data>>{};
for(const auto& itr : samples)
{
_data[itr.first] = get_samples(itr.first);
}
return _data;
}
void
add_sample(uint32_t _index, uintptr_t _addr, uint64_t _count)
{
samples[_index][_addr] += _count;
}
void
add_samples(uint32_t _index, const std::vector<uintptr_t>& _v)
{
for(const auto& itr : _v)
add_sample(_index, itr);
}
void
add_samples(uint32_t _index, const std::map<uintptr_t, uint64_t>& _v)
{
for(const auto& itr : _v)
add_sample(_index, itr.first, itr.second);
}
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,67 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/defines.hpp"
#include "core/timemory.hpp"
#include <chrono>
#include <cstdint>
namespace rocprofsys
{
namespace causal
{
struct sample_data
{
uintptr_t address = 0x0;
mutable uint64_t count = 0;
bool operator==(sample_data _v) const { return (address == _v.address); }
bool operator!=(sample_data _v) const { return !(*this == _v); }
bool operator<(sample_data _v) const { return (address < _v.address); }
bool operator>(sample_data _v) const { return (address > _v.address); }
bool operator<=(sample_data _v) const { return (address <= _v.address); }
bool operator>=(sample_data _v) const { return (address >= _v.address); }
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned)
{
ar(cereal::make_nvp("address", address), cereal::make_nvp("count", count));
}
};
std::map<uint32_t, std::vector<sample_data>>
get_samples();
void
add_samples(uint32_t, const std::vector<uintptr_t>&);
std::vector<sample_data> get_samples(uint32_t);
void add_sample(uint32_t, uintptr_t, uint64_t = 1);
void
add_samples(uint32_t, const std::map<uintptr_t, uint64_t>&);
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,647 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/sampling.hpp"
#include "binary/analysis.hpp"
#include "core/common.hpp"
#include "core/concepts.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/locking.hpp"
#include "core/state.hpp"
#include "core/utility.hpp"
#include "library/causal/components/backtrace.hpp"
#include "library/causal/data.hpp"
#include "library/causal/sample_data.hpp"
#include "library/perf.hpp"
#include "library/ptl.hpp"
#include "library/runtime.hpp"
#include "library/sampling.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include <timemory/macros.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/sampling/allocator.hpp>
#include <timemory/sampling/overflow.hpp>
#include <timemory/sampling/sampler.hpp>
#include <timemory/sampling/timer.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/backtrace.hpp>
#include <timemory/variadic.hpp>
#include <csignal>
#include <cstring>
#include <ctime>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <type_traits>
namespace rocprofsys
{
namespace causal
{
namespace sampling
{
using ::tim::sampling::dynamic;
using ::tim::sampling::overflow;
using ::tim::sampling::timer;
using causal_bundle_t =
tim::lightweight_tuple<causal::component::overflow, causal::component::backtrace>;
using causal_sampler_t = tim::sampling::sampler<causal_bundle_t, dynamic>;
using backtrace_enabled = trait::runtime_enabled<component::backtrace>;
using overflow_enabled = trait::runtime_enabled<component::overflow>;
} // namespace sampling
} // namespace causal
} // namespace rocprofsys
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(prevent_reentry, causal::sampling::causal_sampler_t,
std::true_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(provide_backtrace, causal::sampling::causal_sampler_t,
std::false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(buffer_size, causal::sampling::causal_sampler_t,
TIMEMORY_ESC(std::integral_constant<size_t, 4096>))
namespace rocprofsys
{
namespace causal
{
namespace sampling
{
namespace
{
using causal_sampler_allocator_t = typename causal_sampler_t::allocator_t;
using causal_sampler_bundle_t = typename causal_sampler_t::bundle_type;
using causal_sampler_buffer_t = tim::data_storage::ring_buffer<causal_sampler_bundle_t>;
struct causal_sampling
{};
std::set<int>
configure(bool _setup, int64_t _tid = threading::get_id());
std::shared_ptr<causal_sampler_allocator_t>&
get_causal_sampler_allocator(bool _construct)
{
static auto _v = std::shared_ptr<causal_sampler_allocator_t>{};
if(!_v && _construct) _v = std::make_shared<causal_sampler_allocator_t>();
return _v;
}
auto&
get_causal_sampler_signals()
{
using thread_data_t = thread_data<identity<std::set<int>>, causal_sampling>;
static auto& _v = thread_data_t::instance(construct_on_init{});
return _v;
}
auto&
get_causal_sampler_running()
{
using thread_data_t = thread_data<identity<bool>, causal_sampling>;
static auto& _v = thread_data_t::instance(construct_on_init{});
return _v;
}
auto&
get_causal_samplers()
{
using thread_data_t =
thread_data<identity<std::unique_ptr<causal_sampler_t>>, causal_sampling>;
static auto& _v = thread_data_t::instance(construct_on_init{});
return _v;
}
std::set<int>&
get_causal_sampler_signals(int64_t _tid)
{
auto& _data = get_causal_sampler_signals();
if(static_cast<size_t>(_tid) >= _data->size())
_data->resize(_tid + 1, std::set<int>{});
return _data->at(_tid);
}
bool&
get_causal_sampler_running(int64_t _tid)
{
auto& _data = get_causal_sampler_running();
if(static_cast<size_t>(_tid) >= _data->size()) _data->resize(_tid + 1, false);
return _data->at(_tid);
}
auto&
get_causal_sampler(int64_t _tid)
{
auto& _data = get_causal_samplers();
if(static_cast<size_t>(_tid) >= _data->size()) _data->resize(_tid + 1);
return _data->at(_tid);
}
void
causal_offload_buffer(int64_t, causal_sampler_buffer_t&& _buf)
{
auto _data = std::move(_buf);
auto _processed = std::map<uint32_t, std::map<uintptr_t, uint64_t>>{};
while(!_data.is_empty())
{
auto _bundle = causal_sampler_bundle_t{};
_data.read(&_bundle);
const auto* _bt_causal = _bundle.get<causal::component::backtrace>();
if(_bt_causal)
{
auto _stack = _bt_causal->get_stack();
for(auto itr : _stack)
{
if(itr > 0) _processed[_bt_causal->get_index()][itr] += 1;
}
}
const auto* _of_causal = _bundle.get<causal::component::overflow>();
if(_of_causal)
{
const auto& _stack = _of_causal->get_stack();
for(const auto& ditr : _stack)
{
for(auto aitr : ditr)
{
if(aitr > 0) _processed[_of_causal->get_index()][aitr] += 1;
}
}
}
}
_data.destroy();
if(!_processed.empty())
{
static auto _mutex = locking::atomic_mutex{};
auto _lk = locking::atomic_lock{ _mutex };
for(const auto& itr : _processed)
{
add_samples(itr.first, itr.second);
}
}
}
std::set<int>
configure(bool _setup, int64_t _tid)
{
const auto& _info = thread_info::get(_tid, SequentTID);
auto& _causal = get_causal_sampler(_tid);
auto& _causal_perf = perf::get_instance(_tid);
auto& _running = get_causal_sampler_running(_tid);
auto& _signal_types = get_causal_sampler_signals(_tid);
ROCPROFSYS_CONDITIONAL_THROW(get_use_sampling(),
"Internal error! configuring causal profiling not "
"permitted when sampling is enabled");
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
if(_setup && _signal_types.empty()) _signal_types = get_sampling_signals(_tid);
// initialize
if(_setup)
{
using global_init_mode = operation::mode_constant<operation::init_mode::global>;
using thread_init_mode = operation::mode_constant<operation::init_mode::thread>;
// initialize backtrace
operation::init<component::backtrace>{}(global_init_mode{});
operation::init<component::backtrace>{}(thread_init_mode{});
// initialize overflow
operation::init<component::overflow>{}(global_init_mode{});
operation::init<component::overflow>{}(thread_init_mode{});
}
if(_setup && !_causal && !_running && !_signal_types.empty())
{
auto _verbose = std::min<int>(get_verbose() - 2, 2);
if(get_debug_sampling()) _verbose = 2;
// if this thread has an offset ID, that means it was created internally
// and is probably here bc it called a function which was instrumented.
// thus we should not start a sampler for it
if(_tid > 0 && _info && _info->is_offset) return std::set<int>{};
// if the thread state is disabled or completed, return
if(_info && _info->index_data->sequent_value == _tid &&
get_thread_state() == ThreadState::Disabled)
return std::set<int>{};
(void) get_debug_sampling(); // make sure query in sampler does not allocate
assert(_tid == threading::get_id());
auto _causal_alloc = get_causal_sampler_allocator(true);
_causal = std::make_unique<causal_sampler_t>(_causal_alloc, "rocprofsys", _tid,
_verbose);
auto _activate_perf_backend = [&_causal, &_causal_perf, &_info, &_tid]() {
_causal_perf = std::make_unique<perf::perf_event>();
auto _open_error =
_causal_perf->open(1000.0, 10, _info->index_data->system_value);
if(_open_error)
{
_causal_perf.reset();
}
else
{
overflow_enabled::set(true);
overflow_enabled::set(scope::thread_scope{}, true);
backtrace_enabled::set(false);
backtrace_enabled::set(scope::thread_scope{}, false);
_causal->configure(overflow{ get_sampling_overflow_signal(),
[](int, pid_t, long, int64_t) {
// perf::get_instance(_idx)->set_ready_signal(_sig);
return true;
},
[](int, pid_t, long, int64_t _idx) {
return perf::get_instance(_idx)->start();
},
[](int, pid_t, long, int64_t _idx) {
return perf::get_instance(_idx)->stop();
},
_tid, threading::get_sys_tid() });
if(_tid == 0) ROCPROFSYS_VERBOSE(1, "causal profiling backend: perf\n");
}
return _open_error;
};
auto _activate_timer_backend = [&_causal, &_tid]() {
backtrace_enabled::set(true);
backtrace_enabled::set(scope::thread_scope{}, true);
overflow_enabled::set(false);
overflow_enabled::set(scope::thread_scope{}, false);
_causal->configure(timer{ get_sampling_realtime_signal(), CLOCK_REALTIME,
SIGEV_THREAD_ID, 1000.0, 1.0e-6, _tid,
threading::get_sys_tid() });
if(_tid == 0) ROCPROFSYS_VERBOSE(1, "causal profiling backend: timer\n");
return true;
};
TIMEMORY_REQUIRE(_causal) << "nullptr to causal profiling instance";
_causal->set_flags(SA_RESTART);
_causal->set_verbose(_verbose);
_causal->set_offload(&causal_offload_buffer);
if(get_causal_backend() == CausalBackend::Perf)
{
auto _perf_error = _activate_perf_backend();
ROCPROFSYS_REQUIRE(!_perf_error)
<< "perf backend for causal profiling failed to activate: "
<< *_perf_error << "\n";
}
else if(get_causal_backend() == CausalBackend::Timer)
{
ROCPROFSYS_REQUIRE(_activate_timer_backend())
<< "timer backend for causal profiling failed to activate\n";
}
else if(get_causal_backend() == CausalBackend::Auto)
{
auto _perf_error = _activate_perf_backend();
if(!_perf_error)
{
config::set_setting_value("ROCPROFSYS_CAUSAL_BACKEND",
std::string{ "perf" });
}
else
{
ROCPROFSYS_WARNING_F(
0, "perf backend for causal profiling failed to activate: %s\n",
_perf_error->c_str());
ROCPROFSYS_REQUIRE(_activate_timer_backend())
<< "timer backend for causal profiling failed to activate\n";
config::set_setting_value("ROCPROFSYS_CAUSAL_BACKEND",
std::string{ "timer" });
}
}
_causal->configure(timer{ get_sampling_cputime_signal(), CLOCK_THREAD_CPUTIME_ID,
SIGEV_THREAD_ID, 1000.0, 1.0e-6, _tid,
threading::get_sys_tid() });
_running = true;
_causal->start();
}
else if(!_setup && _causal && _running)
{
ROCPROFSYS_DEBUG("Destroying causal sampler for thread %lu...\n", _tid);
_running = false;
if(_tid == threading::get_id() && !_signal_types.empty())
block_signals(_signal_types);
if(_tid == 0)
{
block_samples();
// this propagates to all threads
_causal->ignore(_signal_types);
for(int64_t i = 1; i < ROCPROFSYS_MAX_THREADS; ++i)
{
if(get_causal_sampler(i))
{
get_causal_sampler(i)->stop();
get_causal_sampler(i)->reset();
}
if(perf::get_instance(i))
{
perf::get_instance(i).reset();
}
}
}
_causal->stop();
_causal->reset();
if(_causal_perf)
{
_causal_perf.reset();
}
ROCPROFSYS_DEBUG("Causal sampler destroyed for thread %lu\n", _tid);
}
return _signal_types;
}
void
post_process_causal(int64_t _tid, const std::vector<causal_bundle_t>& _data);
} // namespace
std::set<int>
get_signal_types(int64_t _tid)
{
return (get_causal_sampler_signals()) ? get_causal_sampler_signals(_tid)
: std::set<int>{};
}
std::set<int>
setup()
{
if(!get_use_causal()) return std::set<int>{};
return configure(true);
}
std::set<int>
shutdown()
{
auto _v = configure(false);
return _v;
}
void
block_samples()
{
trait::runtime_enabled<causal_sampler_t>::set(false);
trait::runtime_enabled<causal::component::backtrace>::set(false);
}
void
unblock_samples()
{
trait::runtime_enabled<causal::component::backtrace>::set(true);
trait::runtime_enabled<causal_sampler_t>::set(true);
}
void
block_backtrace_samples()
{
pause(scope::thread_scope{});
}
void
unblock_backtrace_samples()
{
resume(scope::thread_scope{});
}
namespace
{
std::optional<bool> _process_paused = {};
thread_local std::optional<bool> _thread_paused = {};
namespace signals = ::tim::signals;
const auto&
sampling_signals()
{
static thread_local auto _v = get_signal_types(threading::get_id());
return _v;
}
} // namespace
template <typename ScopeT>
void pause(ScopeT)
{
static_assert(
tim::is_one_of<ScopeT,
type_list<scope::thread_scope, scope::process_scope>>::value,
"Unsupported scope");
if constexpr(std::is_same<ScopeT, scope::thread_scope>::value)
{
if(!_thread_paused) _thread_paused = false;
bool _paused_v = *_thread_paused;
if(!_paused_v)
{
auto& _causal_perf = perf::get_instance(threading::get_id());
if(_causal_perf) _causal_perf->stop();
signals::block_signals(sampling_signals(), signals::sigmask_scope::thread);
_thread_paused = true;
}
}
else
{
if(!_process_paused) _process_paused = false;
bool _paused_v = *_process_paused;
if(!_paused_v)
{
for(auto i = 0; i < ROCPROFSYS_MAX_THREADS; ++i)
{
auto& _causal_perf = perf::get_instance(i);
if(_causal_perf) _causal_perf->stop();
}
signals::block_signals(sampling_signals(), signals::sigmask_scope::process);
_process_paused = true;
}
}
}
template <typename ScopeT>
void resume(ScopeT)
{
static_assert(
tim::is_one_of<ScopeT,
type_list<scope::thread_scope, scope::process_scope>>::value,
"Unsupported scope");
if constexpr(std::is_same<ScopeT, scope::thread_scope>::value)
{
if(!_thread_paused) _thread_paused = true;
bool _paused_v = *_thread_paused;
if(_paused_v)
{
auto& _causal_perf = perf::get_instance(threading::get_id());
if(_causal_perf) _causal_perf->start();
signals::unblock_signals(sampling_signals(), signals::sigmask_scope::thread);
_thread_paused = false;
}
}
else
{
if(!_process_paused) _process_paused = true;
bool _paused_v = *_process_paused;
if(_paused_v)
{
for(auto i = 0; i < ROCPROFSYS_MAX_THREADS; ++i)
{
auto& _causal_perf = perf::get_instance(i);
if(_causal_perf) _causal_perf->start();
}
signals::unblock_signals(sampling_signals(), signals::sigmask_scope::process);
_process_paused = false;
}
}
}
template void pause<scope::thread_scope>(scope::thread_scope);
template void pause<scope::process_scope>(scope::process_scope);
template void resume<scope::thread_scope>(scope::thread_scope);
template void resume<scope::process_scope>(scope::process_scope);
void
block_signals(std::set<int> _signals)
{
if(_signals.empty()) _signals = get_signal_types(threading::get_id());
if(_signals.empty()) return;
::rocprofsys::sampling::block_signals(_signals);
}
void
unblock_signals(std::set<int> _signals)
{
if(_signals.empty()) _signals = get_signal_types(threading::get_id());
if(_signals.empty()) return;
::rocprofsys::sampling::unblock_signals(_signals);
}
void
post_process()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
ROCPROFSYS_VERBOSE(2 || get_debug_sampling(),
"Stopping causal sampling components...\n");
block_samples();
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
{
auto& _causal = get_causal_sampler(i);
if(_causal) _causal->stop();
auto& _causal_perf = perf::get_instance(i);
if(_causal_perf) _causal_perf->stop();
}
configure(false, 0);
auto _allocator = get_causal_sampler_allocator(false);
if(_allocator) _allocator->flush();
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
{
auto& _causal = get_causal_sampler(i);
auto _causal_data =
(_causal) ? _causal->get_data() : std::vector<sampling::causal_bundle_t>{};
if(!_causal_data.empty()) post_process_causal(i, _causal_data);
}
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
{
get_causal_sampler(i).reset();
auto& _causal_perf = perf::get_instance(i);
if(_causal_perf)
{
_causal_perf.reset();
}
}
if(_allocator) _allocator.reset();
}
namespace
{
void
post_process_causal(int64_t, const std::vector<causal_bundle_t>& _data)
{
for(const auto& itr : _data)
{
const auto* _bt_causal = itr.get<causal::component::backtrace>();
if(_bt_causal)
{
auto _stack = _bt_causal->get_stack();
for(auto&& ditr : _stack)
{
if(ditr > 0) add_sample(_bt_causal->get_index(), ditr);
}
}
const auto* _of_causal = itr.get<causal::component::overflow>();
if(_of_causal)
{
const auto& _stack = _of_causal->get_stack();
for(const auto& ditr : _stack)
{
for(auto aitr : ditr)
{
if(aitr > 0) add_sample(_of_causal->get_index(), aitr);
}
}
}
}
}
} // namespace
} // namespace sampling
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,74 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/concepts.hpp"
#include "core/defines.hpp"
#include <cstdint>
#include <memory>
#include <set>
#include <type_traits>
namespace rocprofsys
{
namespace causal
{
namespace sampling
{
std::set<int>
get_signal_types(int64_t _tid);
void
block_samples();
void
unblock_samples();
void
block_backtrace_samples();
void
unblock_backtrace_samples();
template <typename Tp = tim::scope::thread_scope>
void pause(Tp = {});
template <typename Tp = tim::scope::thread_scope>
void resume(Tp = {});
void block_signals(std::set<int> = {});
void unblock_signals(std::set<int> = {});
std::set<int>
setup();
std::set<int>
shutdown();
void
post_process();
} // namespace sampling
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,52 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/causal/selected_entry.hpp"
#include "core/common.hpp"
#include "core/timemory.hpp"
namespace rocprofsys
{
namespace causal
{
template <typename ArchiveT>
void
selected_entry::serialize(ArchiveT& ar, const unsigned int)
{
using ::tim::cereal::make_nvp;
ar(make_nvp("address", address), make_nvp("symbol_address", symbol_address),
make_nvp("info", symbol));
}
template void
selected_entry::serialize<cereal::JSONInputArchive>(cereal::JSONInputArchive&,
const unsigned int);
template void
selected_entry::serialize<cereal::MinimalJSONOutputArchive>(
cereal::MinimalJSONOutputArchive&, const unsigned int);
template void
selected_entry::serialize<cereal::PrettyJSONOutputArchive>(
cereal::PrettyJSONOutputArchive&, const unsigned int);
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,70 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "binary/dwarf_entry.hpp"
#include "binary/symbol.hpp"
#include "core/binary/fwd.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "library/causal/fwd.hpp"
#include <timemory/hash/types.hpp>
#include <timemory/unwind/dlinfo.hpp>
#include <timemory/unwind/stack.hpp>
#include <timemory/utility/macros.hpp>
#include <timemory/utility/procfs/maps.hpp>
#include <cstddef>
#include <cstdint>
#include <dlfcn.h>
#include <map>
#include <utility>
namespace rocprofsys
{
namespace causal
{
struct selected_entry
{
ROCPROFSYS_DEFAULT_OBJECT(selected_entry)
uintptr_t address = 0x0;
uintptr_t symbol_address = 0x0;
binary::symbol symbol = {};
template <typename ArchiveT>
void serialize(ArchiveT&, const unsigned int);
bool contains(uintptr_t) const;
explicit operator bool() const { return (address > 0 && symbol.address); }
};
inline bool
selected_entry::contains(uintptr_t _v) const
{
return (_v == address || (symbol_address > 0 && _v == symbol_address) ||
symbol.ipaddr().contains(_v));
}
} // namespace causal
} // namespace rocprofsys
@@ -0,0 +1,53 @@
#
set(component_sources
${CMAKE_CURRENT_LIST_DIR}/backtrace.cpp
${CMAKE_CURRENT_LIST_DIR}/backtrace_metrics.cpp
${CMAKE_CURRENT_LIST_DIR}/backtrace_timestamp.cpp
${CMAKE_CURRENT_LIST_DIR}/callchain.cpp
${CMAKE_CURRENT_LIST_DIR}/comm_data.cpp
${CMAKE_CURRENT_LIST_DIR}/cpu_freq.cpp
${CMAKE_CURRENT_LIST_DIR}/exit_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/fork_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/mpi_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/numa_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/pthread_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/pthread_create_gotcha.cpp
${CMAKE_CURRENT_LIST_DIR}/pthread_mutex_gotcha.cpp)
set(component_headers
${CMAKE_CURRENT_LIST_DIR}/backtrace.hpp
${CMAKE_CURRENT_LIST_DIR}/backtrace_metrics.hpp
${CMAKE_CURRENT_LIST_DIR}/backtrace_timestamp.hpp
${CMAKE_CURRENT_LIST_DIR}/callchain.hpp
${CMAKE_CURRENT_LIST_DIR}/category_region.hpp
${CMAKE_CURRENT_LIST_DIR}/comm_data.hpp
${CMAKE_CURRENT_LIST_DIR}/cpu_freq.hpp
${CMAKE_CURRENT_LIST_DIR}/ensure_storage.hpp
${CMAKE_CURRENT_LIST_DIR}/exit_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/fork_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/mpi_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/numa_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/rcclp.hpp
${CMAKE_CURRENT_LIST_DIR}/rocprofiler.hpp
${CMAKE_CURRENT_LIST_DIR}/roctracer.hpp
${CMAKE_CURRENT_LIST_DIR}/pthread_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/pthread_create_gotcha.hpp
${CMAKE_CURRENT_LIST_DIR}/pthread_mutex_gotcha.hpp)
target_sources(rocprofiler-systems-object-library PRIVATE ${component_sources}
${component_headers})
if(ROCPROFSYS_USE_ROCPROFILER)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rocprofiler.cpp)
endif()
if(ROCPROFSYS_USE_ROCTRACER)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/roctracer.cpp)
endif()
if(ROCPROFSYS_USE_RCCL)
target_sources(rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rcclp.cpp)
endif()
@@ -0,0 +1,209 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/perfetto.hpp"
#include "core/state.hpp"
#include "library/components/ensure_storage.hpp"
#include "library/ptl.hpp"
#include "library/runtime.hpp"
#include "library/sampling.hpp"
#include <timemory/backends/papi.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/components/data_tracker/components.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/components/papi/extern.hpp>
#include <timemory/components/papi/papi_array.hpp>
#include <timemory/components/papi/papi_vector.hpp>
#include <timemory/components/rusage/components.hpp>
#include <timemory/components/rusage/types.hpp>
#include <timemory/components/timing/backends.hpp>
#include <timemory/components/trip_count/extern.hpp>
#include <timemory/macros.hpp>
#include <timemory/math.hpp>
#include <timemory/mpl.hpp>
#include <timemory/mpl/quirks.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/operations.hpp>
#include <timemory/storage.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/backtrace.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/types.hpp>
#include <timemory/variadic.hpp>
#include <array>
#include <cstring>
#include <ctime>
#include <initializer_list>
#include <mutex>
#include <regex>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <pthread.h>
#include <signal.h>
namespace rocprofsys
{
namespace component
{
std::vector<backtrace::entry_type>
backtrace::get() const
{
std::vector<entry_type> _v = {};
if(size() == 0) return _v;
{
static auto _cache = cache_type{ get_sampling_include_inlines() };
auto_lock_t _lk{ type_mutex<backtrace>() };
_v = m_data.get(&_cache, false);
}
// put the bottom of the call-stack on top
std::reverse(_v.begin(), _v.end());
//
auto _known_excludes =
std::set<std::string>{ "funlockfile", "killpg", "__restore_rt" };
// remove some known functions which are by-products of interrupts
while(!_v.empty() && _known_excludes.find(_v.back().name) != _known_excludes.end())
_v.pop_back();
return _v;
}
std::string
backtrace::label()
{
return "backtrace";
}
std::string
backtrace::description()
{
return "Records backtrace data";
}
std::vector<backtrace::entry_type>
backtrace::filter_and_patch(const std::vector<entry_type>& _data)
{
// check whether the call-stack entry should be used. -1 means break, 0 means continue
auto _use_label = [](std::string_view _lbl) -> short {
// debugging feature
bool _keep_internal = get_sampling_keep_internal();
const auto _npos = std::string::npos;
if(_keep_internal) return 1;
if(_lbl.find("rocprofsys_main") != _npos) return 0;
if(_lbl.find("rocprofsys::") != _npos) return 0;
if(_lbl.find("tim::openmp::") != _npos) return -1;
if(_lbl.find("tim::") != _npos) return 0;
if(_lbl.find("DYNINST_") != _npos) return 0;
if(_lbl.find("rocprofsys_") != _npos) return -1;
if(_lbl.find("rocprofiler_") != _npos) return -1;
if(_lbl.find("roctracer_") != _npos) return -1;
if(_lbl.find("perfetto::") != _npos) return -1;
if(_lbl.find("protozero::") == 0) return -1;
if(_lbl.find("gotcha_") != _npos) return -1;
return 1;
};
static bool _keep_suffix = tim::get_env<bool>(
"ROCPROFSYS_SAMPLING_KEEP_DYNINST_SUFFIX", get_debug_sampling());
// in the dyninst binary rewrite runtime, instrumented functions are appended with
// "_dyninst", i.e. "main" will show up as "main_dyninst" in the backtrace.
auto _patch_label = [](std::string_view _lbl) -> std::string {
// debugging feature
if(_keep_suffix) return std::string{ _lbl };
const std::string _dyninst{ "_dyninst" };
auto _pos = _lbl.find(_dyninst);
if(_pos == std::string::npos) return std::string{ _lbl };
return std::string{ _lbl }.replace(_pos, _dyninst.length(), "");
};
auto _ret = std::vector<entry_type>{};
_ret.reserve(_data.size());
for(const auto& itr : _data)
{
auto _name = tim::demangle(_patch_label(itr.name));
auto _use = _use_label(_name);
if(_use == -1) break;
if(_use == 0) continue;
auto _v = itr;
_v.name = _name;
_ret.emplace_back(_v);
}
return _ret;
}
void
backtrace::start()
{}
void
backtrace::stop()
{}
bool
backtrace::empty() const
{
return (size() == 0);
}
size_t
backtrace::size() const
{
return m_data.size();
}
void
backtrace::sample(int signo)
{
if(signo == get_sampling_overflow_signal()) return;
// on RedHat, the unw_step within get_unw_stack involves a mutex lock
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
using namespace tim::backtrace;
constexpr bool with_signal_frame = false;
constexpr size_t ignore_depth = 3;
// ignore depth based on:
// 1. this frame
// 2. tim::sampling::sampler<...>::sample(...) [always inline]
// 3. tim::sampling::sampler<...>::execute(...)
// 4a. funlockfile [common but not explicitly in call-stack]
// 4b. __resume_rt [common but not explicitly in call-stack]
// 4c. killpg [common but not explicitly in call-stack]
m_data = get_unw_stack<stack_depth, ignore_depth, with_signal_frame>();
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_INITIALIZE_STORAGE(rocprofsys::component::backtrace)
@@ -0,0 +1,86 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/thread_data.hpp"
#include <timemory/components/base/declaration.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/unwind/cache.hpp>
#include <timemory/unwind/processed_entry.hpp>
#include <timemory/unwind/stack.hpp>
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <set>
#include <vector>
namespace rocprofsys
{
namespace component
{
struct backtrace : comp::empty_base
{
static constexpr size_t stack_depth = ROCPROFSYS_MAX_UNWIND_DEPTH;
using data_t = tim::unwind::stack<stack_depth>;
using cache_type = typename data_t::cache_type;
using entry_type = tim::unwind::processed_entry;
using clock_type = std::chrono::steady_clock;
using value_type = void;
using system_clock = std::chrono::system_clock;
using system_time_point = typename system_clock::time_point;
static std::string label();
static std::string description();
backtrace() = default;
~backtrace() = default;
backtrace(const backtrace&) = default;
backtrace(backtrace&&) noexcept = default;
backtrace& operator=(const backtrace&) = default;
backtrace& operator=(backtrace&&) noexcept = default;
static std::vector<entry_type> filter_and_patch(const std::vector<entry_type>&);
static void start();
static void stop();
void sample(int = -1);
bool empty() const;
size_t size() const;
std::vector<entry_type> get() const;
data_t get_data() const { return m_data; }
private:
data_t m_data = {};
};
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,424 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/backtrace_metrics.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/perfetto.hpp"
#include "library/components/ensure_storage.hpp"
#include "library/ptl.hpp"
#include "library/runtime.hpp"
#include "library/thread_info.hpp"
#include "library/tracing.hpp"
#include <timemory/backends/papi.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/components/data_tracker/components.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/components/papi/extern.hpp>
#include <timemory/components/papi/papi_array.hpp>
#include <timemory/components/papi/papi_vector.hpp>
#include <timemory/components/rusage/components.hpp>
#include <timemory/components/rusage/types.hpp>
#include <timemory/components/timing/backends.hpp>
#include <timemory/components/trip_count/extern.hpp>
#include <timemory/macros.hpp>
#include <timemory/math.hpp>
#include <timemory/mpl.hpp>
#include <timemory/mpl/quirks.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/operations.hpp>
#include <timemory/storage.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/backtrace.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/types.hpp>
#include <timemory/variadic.hpp>
#include <array>
#include <cstring>
#include <ctime>
#include <initializer_list>
#include <mutex>
#include <regex>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <pthread.h>
#include <signal.h>
namespace tracing
{
using namespace ::rocprofsys::tracing;
}
namespace rocprofsys
{
namespace component
{
using hw_counters = typename backtrace_metrics::hw_counters;
using signal_type_instances = thread_data<std::set<int>, category::sampling>;
using backtrace_metrics_init_instances =
thread_data<backtrace_metrics, category::sampling>;
using sampler_running_instances = thread_data<bool, category::sampling>;
using papi_vector_instances = thread_data<hw_counters, category::sampling>;
using papi_label_instances = thread_data<std::vector<std::string>, category::sampling>;
namespace
{
struct perfetto_rusage
{};
unique_ptr_t<std::vector<std::string>>&
get_papi_labels(int64_t _tid)
{
return papi_label_instances::instance(construct_on_thread{ _tid });
}
unique_ptr_t<hw_counters>&
get_papi_vector(int64_t _tid)
{
return papi_vector_instances::instance(construct_on_thread{ _tid });
}
unique_ptr_t<backtrace_metrics>&
get_backtrace_metrics_init(int64_t _tid)
{
return backtrace_metrics_init_instances::instance(construct_on_thread{ _tid });
}
unique_ptr_t<bool>&
get_sampler_running(int64_t _tid)
{
return sampler_running_instances::instance(construct_on_thread{ _tid }, false);
}
} // namespace
std::string
backtrace_metrics::label()
{
return "backtrace_metrics";
}
std::string
backtrace_metrics::description()
{
return "Records sampling data";
}
std::vector<std::string>
backtrace_metrics::get_hw_counter_labels(int64_t _tid)
{
auto& _v = get_papi_labels(_tid);
return (_v) ? *_v : std::vector<std::string>{};
}
void
backtrace_metrics::start()
{}
void
backtrace_metrics::stop()
{}
namespace
{
template <typename... Tp>
auto get_enabled(tim::type_list<Tp...>)
{
constexpr size_t N = sizeof...(Tp);
auto _v = std::bitset<N>{};
size_t _n = 0;
(_v.set(_n++, trait::runtime_enabled<Tp>::get()), ...);
return _v;
}
} // namespace
void
backtrace_metrics::sample(int)
{
if(!get_enabled(type_list<category::process_sampling, backtrace_metrics>{}).all())
{
m_valid.reset();
return;
}
m_valid = get_enabled(categories_t{});
// return if everything is disabled
if(!m_valid.any()) return;
auto _cache = tim::rusage_cache{ RUSAGE_THREAD };
m_cpu = tim::get_clock_thread_now<int64_t, std::nano>();
m_mem_peak = _cache.get_peak_rss();
m_ctx_swch = _cache.get_num_priority_context_switch() +
_cache.get_num_voluntary_context_switch();
m_page_flt = _cache.get_num_major_page_faults() + _cache.get_num_minor_page_faults();
if constexpr(tim::trait::is_available<hw_counters>::value)
{
constexpr auto hw_counters_idx = tim::index_of<hw_counters, categories_t>::value;
constexpr auto hw_category_idx =
tim::index_of<category::thread_hardware_counter, categories_t>::value;
auto _tid = threading::get_id();
if(m_valid.test(hw_category_idx) && m_valid.test(hw_counters_idx))
{
assert(get_papi_vector(_tid).get() != nullptr);
m_hw_counter = get_papi_vector(_tid)->record();
}
}
}
void
backtrace_metrics::configure(bool _setup, int64_t _tid)
{
auto& _running = get_sampler_running(_tid);
bool _is_running = (!_running) ? false : *_running;
ensure_storage<comp::trip_count, sampling_wall_clock, sampling_cpu_clock, hw_counters,
sampling_percent>{}();
if(_setup && !_is_running)
{
(void) get_debug_sampling(); // make sure query in sampler does not allocate
assert(_tid == threading::get_id());
if constexpr(tim::trait::is_available<hw_counters>::value)
{
perfetto_counter_track<hw_counters>::init();
ROCPROFSYS_DEBUG("HW COUNTER: starting...\n");
if(get_papi_vector(_tid))
{
get_papi_vector(_tid)->start();
*get_papi_labels(_tid) = get_papi_vector(_tid)->get_config()->labels;
}
}
}
else if(!_setup && _is_running)
{
ROCPROFSYS_DEBUG("Destroying sampler for thread %lu...\n", _tid);
*_running = false;
if constexpr(tim::trait::is_available<hw_counters>::value)
{
if(_tid == threading::get_id())
{
if(get_papi_vector(_tid)) get_papi_vector(_tid)->stop();
ROCPROFSYS_DEBUG("HW COUNTER: stopped...\n");
}
}
ROCPROFSYS_DEBUG("Sampler destroyed for thread %lu\n", _tid);
}
}
void
backtrace_metrics::init_perfetto(int64_t _tid, valid_array_t _valid)
{
auto _hw_cnt_labels = *get_papi_labels(_tid);
auto _tid_name = JOIN("", '[', _tid, ']');
if(!perfetto_counter_track<perfetto_rusage>::exists(_tid))
{
if(get_valid(category::thread_cpu_time{}, _valid))
perfetto_counter_track<perfetto_rusage>::emplace(
_tid, JOIN(' ', "Thread CPU time", _tid_name, "(S)"), "sec");
if(get_valid(category::thread_peak_memory{}, _valid))
perfetto_counter_track<perfetto_rusage>::emplace(
_tid, JOIN(' ', "Thread Peak Memory Usage", _tid_name, "(S)"), "MB");
if(get_valid(category::thread_context_switch{}, _valid))
perfetto_counter_track<perfetto_rusage>::emplace(
_tid, JOIN(' ', "Thread Context Switches", _tid_name, "(S)"));
if(get_valid(category::thread_page_fault{}, _valid))
perfetto_counter_track<perfetto_rusage>::emplace(
_tid, JOIN(' ', "Thread Page Faults", _tid_name, "(S)"));
}
if(!perfetto_counter_track<hw_counters>::exists(_tid) &&
get_valid(type_list<hw_counters>{}, _valid) &&
get_valid(category::thread_hardware_counter{}, _valid))
{
for(auto& itr : _hw_cnt_labels)
{
std::string _desc = tim::papi::get_event_info(itr).short_descr;
if(_desc.empty()) _desc = itr;
ROCPROFSYS_CI_THROW(_desc.empty(), "Empty description for %s\n", itr.c_str());
perfetto_counter_track<hw_counters>::emplace(
_tid, JOIN(' ', "Thread", _desc, _tid_name, "(S)"));
}
}
}
void
backtrace_metrics::fini_perfetto(int64_t _tid, valid_array_t _valid)
{
auto _hw_cnt_labels = *get_papi_labels(_tid);
const auto& _thread_info = thread_info::get(_tid, SequentTID);
ROCPROFSYS_CI_THROW(!_thread_info, "Error! missing thread info for tid=%li\n", _tid);
if(!_thread_info) return;
uint64_t _ts = _thread_info->get_stop();
uint64_t _rusage_idx = 0;
if(get_valid(category::thread_cpu_time{}, _valid))
{
TRACE_COUNTER(trait::name<category::thread_cpu_time>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, 0);
}
if(get_valid(category::thread_peak_memory{}, _valid))
{
TRACE_COUNTER(trait::name<category::thread_peak_memory>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, 0);
}
if(get_valid(category::thread_context_switch{}, _valid))
{
TRACE_COUNTER(trait::name<category::thread_context_switch>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, 0);
}
if(get_valid(category::thread_page_fault{}, _valid))
{
TRACE_COUNTER(trait::name<category::thread_page_fault>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, 0);
}
if(get_valid(type_list<hw_counters>{}, _valid) &&
get_valid(category::thread_hardware_counter{}, _valid))
{
for(size_t i = 0; i < perfetto_counter_track<hw_counters>::size(_tid); ++i)
{
if(i < _hw_cnt_labels.size())
{
TRACE_COUNTER(trait::name<category::thread_hardware_counter>::value,
perfetto_counter_track<hw_counters>::at(_tid, i), _ts, 0.0);
}
}
}
}
backtrace_metrics&
backtrace_metrics::operator-=(const backtrace_metrics& _rhs)
{
auto& _lhs = *this;
if(_lhs(category::thread_cpu_time{}))
{
_lhs.m_cpu -= _rhs.m_cpu;
}
if(_lhs(category::thread_peak_memory{}))
{
_lhs.m_mem_peak -= _rhs.m_mem_peak;
}
if(_lhs(category::thread_context_switch{}))
{
_lhs.m_ctx_swch -= _rhs.m_ctx_swch;
}
if(_lhs(category::thread_page_fault{}))
{
_lhs.m_page_flt -= _rhs.m_page_flt;
}
if(_lhs(type_list<hw_counters>{}) && _lhs(category::thread_hardware_counter{}))
{
for(size_t i = 0; i < _lhs.m_hw_counter.size(); ++i)
_lhs.m_hw_counter.at(i) -= _rhs.m_hw_counter.at(i);
}
return _lhs;
}
void
backtrace_metrics::post_process_perfetto(int64_t _tid, uint64_t _ts) const
{
uint64_t _rusage_idx = 0;
if((*this)(category::thread_cpu_time{}))
{
TRACE_COUNTER(trait::name<category::thread_cpu_time>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, m_cpu / units::sec);
}
if((*this)(category::thread_peak_memory{}))
{
TRACE_COUNTER(trait::name<category::thread_peak_memory>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, m_mem_peak / units::megabyte);
}
if((*this)(category::thread_context_switch{}))
{
TRACE_COUNTER(trait::name<category::thread_context_switch>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, m_ctx_swch);
}
if((*this)(category::thread_page_fault{}))
{
TRACE_COUNTER(trait::name<category::thread_page_fault>::value,
perfetto_counter_track<perfetto_rusage>::at(_tid, _rusage_idx++),
_ts, m_page_flt);
}
if((*this)(type_list<hw_counters>{}) && (*this)(category::thread_hardware_counter{}))
{
for(size_t i = 0; i < perfetto_counter_track<hw_counters>::size(_tid); ++i)
{
if(i < m_hw_counter.size())
{
TRACE_COUNTER(trait::name<category::thread_hardware_counter>::value,
perfetto_counter_track<hw_counters>::at(_tid, i), _ts,
m_hw_counter.at(i));
}
}
}
}
} // namespace component
} // namespace rocprofsys
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_wall_clock>), true,
double)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_cpu_clock>), true,
double)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_fraction>), true,
double)
TIMEMORY_INITIALIZE_STORAGE(rocprofsys::component::backtrace_metrics)
@@ -0,0 +1,184 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/components/backtrace.hpp"
#include "library/thread_data.hpp"
#include <timemory/components/base.hpp>
#include <timemory/components/papi/papi_array.hpp>
#include <timemory/components/papi/types.hpp>
#include <timemory/macros/language.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/utility/type_list.hpp>
#include <timemory/variadic/types.hpp>
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <set>
#include <vector>
namespace rocprofsys
{
template <typename... Tp>
using type_list = ::tim::type_list<Tp...>;
namespace component
{
struct backtrace_metrics : comp::empty_base
{
static constexpr size_t num_hw_counters = TIMEMORY_PAPI_ARRAY_SIZE;
using clock_type = std::chrono::steady_clock;
using value_type = void;
using hw_counters = tim::component::papi_array<num_hw_counters>;
using hw_counter_data_t = typename hw_counters::value_type;
using system_clock = std::chrono::system_clock;
using system_time_point = typename system_clock::time_point;
using categories_t =
type_list<category::thread_cpu_time, category::thread_peak_memory,
category::thread_context_switch, category::thread_page_fault,
category::thread_hardware_counter, hw_counters>;
static constexpr size_t num_categories = std::tuple_size<categories_t>::value;
using valid_array_t = std::bitset<num_categories>;
static std::string label();
static std::string description();
backtrace_metrics() = default;
~backtrace_metrics() = default;
backtrace_metrics(const backtrace_metrics&) = default;
backtrace_metrics(backtrace_metrics&&) noexcept = default;
backtrace_metrics& operator=(const backtrace_metrics&) = default;
backtrace_metrics& operator=(backtrace_metrics&&) noexcept = default;
static void configure(bool, int64_t _tid = threading::get_id());
static void init_perfetto(int64_t _tid, valid_array_t);
static void fini_perfetto(int64_t _tid, valid_array_t);
static std::vector<std::string> get_hw_counter_labels(int64_t);
template <typename Tp>
static bool get_valid(Tp, valid_array_t);
template <typename Tp>
static bool get_valid(type_list<Tp>, valid_array_t);
static void start();
static void stop();
void sample(int = -1);
void post_process(int64_t _tid, const backtrace* _bt,
const backtrace_metrics* _last) const;
explicit operator bool() const { return m_valid.any(); }
template <typename Tp>
bool operator()(Tp) const;
template <typename Tp>
bool operator()(type_list<Tp>) const;
auto get_valid() const { return m_valid; }
auto get_cpu_timestamp() const { return m_cpu; }
auto get_peak_memory() const { return m_mem_peak; }
auto get_context_switches() const { return m_ctx_swch; }
auto get_page_faults() const { return m_page_flt; }
const auto& get_hw_counters() const { return m_hw_counter; }
void post_process_perfetto(int64_t _tid, uint64_t _ts) const;
backtrace_metrics& operator-=(const backtrace_metrics&);
friend backtrace_metrics operator-(backtrace_metrics _lhs,
const backtrace_metrics& _rhs)
{
return (_lhs -= _rhs);
}
private:
valid_array_t m_valid = {};
int64_t m_cpu = 0;
int64_t m_mem_peak = 0;
int64_t m_ctx_swch = 0;
int64_t m_page_flt = 0;
hw_counter_data_t m_hw_counter = {};
};
template <typename Tp>
bool
backtrace_metrics::get_valid(type_list<Tp>, valid_array_t _valid)
{
constexpr auto idx = tim::index_of<Tp, categories_t>::value;
return _valid.test(idx);
}
template <typename Tp>
bool backtrace_metrics::operator()(type_list<Tp>) const
{
static_assert(!concepts::is_type_listing<Tp>::value,
"Error! invalid call with tuple");
constexpr auto idx = tim::index_of<Tp, categories_t>::value;
return m_valid.test(idx);
}
template <typename Tp>
bool
backtrace_metrics::get_valid(Tp, valid_array_t _valid)
{
return get_valid(type_list<Tp>{}, _valid);
}
template <typename Tp>
bool backtrace_metrics::operator()(Tp) const
{
return (*this)(type_list<Tp>{});
}
} // namespace component
} // namespace rocprofsys
#if !defined(ROCPROFSYS_EXTERN_COMPONENTS) || \
(defined(ROCPROFSYS_EXTERN_COMPONENTS) && ROCPROFSYS_EXTERN_COMPONENTS > 0)
# include <timemory/operations.hpp>
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_wall_clock>), true,
double)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_cpu_clock>), true,
double)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_fraction>), true,
double)
#endif
@@ -0,0 +1,54 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/backtrace_timestamp.hpp"
#include "library/thread_info.hpp"
#include <timemory/components/timing/backends.hpp>
namespace rocprofsys
{
namespace component
{
bool
backtrace_timestamp::operator<(const backtrace_timestamp& rhs) const
{
return std::tie(m_tid, m_real) < std::tie(rhs.m_tid, rhs.m_real);
}
bool
backtrace_timestamp::is_valid() const
{
const auto& _info = thread_info::get(m_tid, SequentTID);
return (_info) ? _info->is_valid_time(m_real) : false;
}
void
backtrace_timestamp::sample(int)
{
m_tid = tim::threading::get_id();
m_real = tim::get_clock_real_now<uint64_t, std::nano>();
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_INITIALIZE_STORAGE(rocprofsys::component::backtrace_timestamp)
@@ -0,0 +1,72 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/components/base.hpp>
#include <timemory/macros/language.hpp>
#include <timemory/mpl/concepts.hpp>
#include <chrono>
#include <cstdint>
namespace rocprofsys
{
namespace component
{
struct backtrace_timestamp : comp::empty_base
{
using value_type = void;
static std::string label() { return "backtrace_timestamp"; }
static std::string description() { return "Timestamp for backtrace"; }
backtrace_timestamp() = default;
~backtrace_timestamp() = default;
backtrace_timestamp(const backtrace_timestamp&) = default;
backtrace_timestamp(backtrace_timestamp&&) noexcept = default;
backtrace_timestamp& operator=(const backtrace_timestamp&) = default;
backtrace_timestamp& operator=(backtrace_timestamp&&) noexcept = default;
bool operator<(const backtrace_timestamp& rhs) const;
static void start() {}
static void stop() {}
void sample(int = -1);
auto get_tid() const { return m_tid; }
auto get_timestamp() const { return m_real; }
bool is_valid() const;
private:
int64_t m_tid = 0;
uint64_t m_real = 0;
};
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,219 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "callchain.hpp"
#include "binary/analysis.hpp"
#include "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/perfetto.hpp"
#include "core/state.hpp"
#include "library/components/ensure_storage.hpp"
#include "library/perf.hpp"
#include "library/ptl.hpp"
#include "library/runtime.hpp"
#include "library/sampling.hpp"
#include "library/thread_info.hpp"
#include <timemory/backends/papi.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/components/data_tracker/components.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/components/papi/extern.hpp>
#include <timemory/components/papi/papi_array.hpp>
#include <timemory/components/papi/papi_vector.hpp>
#include <timemory/components/rusage/components.hpp>
#include <timemory/components/rusage/types.hpp>
#include <timemory/components/timing/backends.hpp>
#include <timemory/components/trip_count/extern.hpp>
#include <timemory/macros.hpp>
#include <timemory/math.hpp>
#include <timemory/mpl.hpp>
#include <timemory/mpl/quirks.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/operations.hpp>
#include <timemory/storage.hpp>
#include <timemory/units.hpp>
#include <timemory/unwind/entry.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/types.hpp>
#include <timemory/variadic.hpp>
#include <array>
#include <cstring>
#include <ctime>
#include <initializer_list>
#include <mutex>
#include <regex>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <pthread.h>
#include <signal.h>
namespace rocprofsys
{
namespace component
{
bool
callchain::record::operator<(const record& rhs) const
{
return timestamp < rhs.timestamp;
}
std::vector<callchain::ts_entry_vec_t>
callchain::get() const
{
std::vector<ts_entry_vec_t> _v = {};
if(size() == 0) return _v;
_v.reserve(size());
auto _data = m_data;
std::sort(_data.begin(), _data.end());
for(const auto& itr : _data)
{
auto _v2 = ts_entry_vec_t{ itr.timestamp, {} };
for(auto iitr : itr.data)
{
auto _entry = binary::lookup_ipaddr_entry<true>(iitr);
if(_entry) _v2.second.emplace_back(*_entry);
}
if(!_v2.second.empty())
{
// put the bottom of the call-stack on top
std::reverse(_v2.second.begin(), _v2.second.end());
_v.emplace_back(std::move(_v2));
}
}
auto _known_excludes =
std::set<std::string>{ "funlockfile", "killpg", "__restore_rt" };
// remove some known functions which are by-products of interrupts
for(auto& itr : _v)
{
while(!itr.second.empty() &&
_known_excludes.find(itr.second.back().name) != _known_excludes.end())
itr.second.pop_back();
}
std::sort(_v.begin(), _v.end(),
[](const auto& _lhs, const auto& _rhs) { return _lhs.first < _rhs.first; });
return _v;
}
std::string
callchain::label()
{
return "callchain";
}
std::string
callchain::description()
{
return "Records callchain data";
}
std::vector<callchain::ts_entry_vec_t>
callchain::filter_and_patch(const std::vector<ts_entry_vec_t>& _data)
{
auto _ret = std::vector<ts_entry_vec_t>{};
_ret.reserve(_data.size());
for(const auto& itr : _data)
{
auto _v = backtrace::filter_and_patch(itr.second);
if(!_v.empty()) _ret.emplace_back(ts_entry_vec_t{ itr.first, std::move(_v) });
}
return _ret;
}
void
callchain::start()
{}
void
callchain::stop()
{}
bool
callchain::empty() const
{
return (size() == 0);
}
size_t
callchain::size() const
{
return m_data.size();
}
void
callchain::sample(int signo)
{
if(signo != get_sampling_overflow_signal()) return;
// on RedHat, the unw_step within get_unw_stack involves a mutex lock
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
static thread_local const auto& _tinfo = thread_info::get();
auto _tid = _tinfo->index_data->sequent_value;
auto& _perf_event = perf::get_instance(_tid);
if(!_perf_event) return;
_perf_event->stop();
for(auto itr : *_perf_event)
{
if(itr.is_sample())
{
auto _ip = itr.get_ip();
auto _data = record{};
_data.timestamp = itr.get_time();
_data.data.emplace_back(_ip);
bool _skip_ip = true;
for(auto ditr : itr.get_callchain())
{
// skip the first instance of current IP but allow after that since this
// might be a recursive call
if(ditr == _ip && _skip_ip)
_skip_ip = false;
else
_data.data.emplace_back(ditr);
if(_data.data.size() == _data.data.capacity()) break;
}
if(!_data.data.empty()) m_data.emplace_back(_data);
}
}
_perf_event->start();
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_INITIALIZE_STORAGE(rocprofsys::component::callchain)
@@ -0,0 +1,95 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/containers/static_vector.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/thread_data.hpp"
#include <timemory/components/base/declaration.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/unwind/cache.hpp>
#include <timemory/unwind/processed_entry.hpp>
#include <timemory/unwind/stack.hpp>
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <set>
#include <vector>
namespace rocprofsys
{
namespace component
{
struct callchain : comp::empty_base
{
static constexpr size_t stack_depth = ROCPROFSYS_MAX_UNWIND_DEPTH;
struct record
{
uint64_t timestamp = 0;
container::static_vector<uintptr_t, stack_depth> data = {};
bool operator<(const record& rhs) const;
};
using cache_type = tim::unwind::cache;
using entry_type = tim::unwind::processed_entry;
using value_type = void;
using data_t = container::static_vector<record, 64>;
using entry_vec_t = std::vector<entry_type>;
using ts_entry_vec_t = std::pair<uint64_t, entry_vec_t>;
static std::string label();
static std::string description();
callchain() = default;
~callchain() = default;
callchain(const callchain&) = default;
callchain(callchain&&) noexcept = default;
callchain& operator=(const callchain&) = default;
callchain& operator=(callchain&&) noexcept = default;
static std::vector<ts_entry_vec_t> filter_and_patch(
const std::vector<ts_entry_vec_t>&);
static void start();
static void stop();
void sample(int = -1);
bool empty() const;
size_t size() const;
std::vector<ts_entry_vec_t> get() const;
data_t get_data() const { return m_data; }
private:
data_t m_data = {};
};
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,420 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/config.hpp"
#include "core/defines.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include "library/causal/data.hpp"
#include "library/runtime.hpp"
#include "library/tracing.hpp"
#include "library/tracing/annotation.hpp"
#include <timemory/components/gotcha/backends.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/utility/types.hpp>
#include <string_view>
namespace tim
{
namespace quirk
{
struct causal : concepts::quirk_type
{};
struct perfetto : concepts::quirk_type
{};
struct timemory : concepts::quirk_type
{};
} // namespace quirk
} // namespace tim
namespace rocprofsys
{
namespace component
{
using tim::is_one_of;
using tim::type_list;
// these categories increment push/pop counts, which are used for sanity checks since
// they should ALWAYS be popped if they were pushed
using tracing_count_categories_t =
type_list<category::host, category::mpi, category::pthread, category::rocm_hip,
category::rocm_hsa, category::rocm_rccl>;
// convert these categories to throughput points
using causal_throughput_categories_t =
type_list<category::host, category::kokkos, category::ompt, category::rocm_hip,
category::rocm_hsa, category::rocm_rccl, category::rocm_roctx>;
// define this outside of category region functions so that the
// static thread_local is global instead of per-template instantiation
inline ThreadState
get_thread_status()
{
static thread_local auto _thread_init_once = std::once_flag{};
std::call_once(_thread_init_once, tracing::thread_init);
return get_thread_state();
}
// timemory component which calls rocprof-sys functions
// (used in gotcha wrappers)
template <typename CategoryT>
struct category_region : comp::base<category_region<CategoryT>, void>
{
using gotcha_data_t = tim::component::gotcha_data;
static constexpr auto category_name = trait::name<CategoryT>::value;
static std::string label()
{
return JOIN('_', "rocprofsys", category_name, "region");
}
template <typename... OptsT, typename... Args>
static void start(std::string_view name, Args&&...);
template <typename... OptsT, typename... Args>
static void stop(std::string_view name, Args&&...);
template <typename... OptsT, typename... Args>
static void mark(std::string_view name, Args&&...);
template <typename... OptsT, typename... Args>
static void audit(const gotcha_data_t&, audit::incoming, Args&&...);
template <typename... OptsT, typename... Args>
static void audit(const gotcha_data_t&, audit::outgoing, Args&&...);
template <typename... OptsT, typename... Args>
static void audit(std::string_view, audit::incoming, Args&&...);
template <typename... OptsT, typename... Args>
static void audit(std::string_view, audit::outgoing, Args&&...);
template <typename... OptsT, typename... Args>
static void audit(quirk::config<OptsT...>, Args&&...);
};
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::start(std::string_view name, Args&&... args)
{
// skip if category is disabled
if(tracing::category_push_disabled<CategoryT>()) return;
// unconditionally return if thread is disabled or finalized
if(get_thread_state() == ThreadState::Disabled) return;
if(get_state() >= State::Finalized) return;
if(name.empty()) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
// the expectation here is that if the state is not active then the call
// to rocprofsys_init_tooling_hidden will activate all the appropriate
// tooling one time and as it exits set it to active and return true.
if(get_state() != State::Active && !rocprofsys_init_tooling_hidden()) return;
if(get_thread_status() == ThreadState::Disabled) return;
constexpr bool _ct_use_timemory =
(sizeof...(OptsT) == 0 || is_one_of<quirk::timemory, type_list<OptsT...>>::value);
constexpr bool _ct_use_perfetto =
(sizeof...(OptsT) == 0 || is_one_of<quirk::perfetto, type_list<OptsT...>>::value);
constexpr bool _ct_use_causal =
(sizeof...(OptsT) == 0 || is_one_of<quirk::causal, type_list<OptsT...>>::value);
ROCPROFSYS_CONDITIONAL_PRINT(
tracing::debug_push,
"[%s][PID=%i][state=%s][thread_state=%s] rocprofsys_push_region(%s)\n",
category_name, process::get_id(), std::to_string(get_state()).c_str(),
std::to_string(get_thread_state()).c_str(), name.data());
if constexpr(is_one_of<CategoryT, tracing_count_categories_t>::value)
{
++tracing::push_count();
}
auto _hash = tim::add_hash_id(name);
name = tim::get_hash_identifier_fast(_hash);
if constexpr(_ct_use_causal)
{
if constexpr(!is_one_of<CategoryT, causal_throughput_categories_t>::value)
{
if(get_use_causal()) causal::push_progress_point(name);
}
}
if constexpr(_ct_use_timemory)
{
if(get_use_timemory())
{
tracing::push_timemory(CategoryT{}, name, std::forward<Args>(args)...);
}
}
if constexpr(_ct_use_perfetto)
{
if(get_use_perfetto())
{
tracing::push_perfetto(CategoryT{}, name.data(), std::forward<Args>(args)...);
}
}
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::stop(std::string_view name, Args&&... args)
{
// skip if category is disabled
if(tracing::category_pop_disabled<CategoryT>()) return;
if(get_thread_state() == ThreadState::Disabled) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
constexpr bool _ct_use_timemory =
(sizeof...(OptsT) == 0 || is_one_of<quirk::timemory, type_list<OptsT...>>::value);
constexpr bool _ct_use_perfetto =
(sizeof...(OptsT) == 0 || is_one_of<quirk::perfetto, type_list<OptsT...>>::value);
constexpr bool _ct_use_causal =
(sizeof...(OptsT) == 0 || is_one_of<quirk::causal, type_list<OptsT...>>::value);
ROCPROFSYS_CONDITIONAL_PRINT(
tracing::debug_pop,
"[%s][PID=%i][state=%s][thread_state=%s] rocprofsys_pop_region(%s)\n",
category_name, process::get_id(), std::to_string(get_state()).c_str(),
std::to_string(get_thread_state()).c_str(), name.data());
// only execute when active
if(get_state() == State::Active)
{
if constexpr(is_one_of<CategoryT, tracing_count_categories_t>::value)
{
++tracing::pop_count();
}
if constexpr(_ct_use_perfetto)
{
if(get_use_perfetto())
{
tracing::pop_perfetto(CategoryT{}, name.data(),
std::forward<Args>(args)...);
}
}
if constexpr(_ct_use_timemory)
{
if(get_use_timemory())
{
tracing::pop_timemory(CategoryT{}, name, std::forward<Args>(args)...);
}
}
if constexpr(_ct_use_causal)
{
if constexpr(is_one_of<CategoryT, causal_throughput_categories_t>::value)
{
if(get_use_causal()) causal::mark_progress_point(name);
}
else
{
if(get_use_causal()) causal::pop_progress_point(name);
}
}
}
else
{
static auto _debug = get_debug_env();
ROCPROFSYS_CONDITIONAL_BASIC_PRINT(
_debug, "[%s] rocprofsys_pop_region(%s) ignored :: state = %s\n",
category_name, name.data(), std::to_string(get_state()).c_str());
}
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::mark(std::string_view name, Args&&...)
{
constexpr bool _ct_use_causal =
(sizeof...(OptsT) == 0 || is_one_of<quirk::causal, type_list<OptsT...>>::value);
if constexpr(!_ct_use_causal) return;
// skip if category is disabled
if(tracing::category_mark_disabled<CategoryT>()) return;
// the expectation here is that if the state is not active then the call
// to rocprofsys_init_tooling_hidden will activate all the appropriate
// tooling one time and as it exits set it to active and return true.
if(get_state() != State::Active && !rocprofsys_init_tooling_hidden()) return;
// unconditionally return if thread is disabled or finalized
if(get_thread_state() >= ThreadState::Completed) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
if(get_use_causal())
{
ROCPROFSYS_CONDITIONAL_PRINT(
tracing::debug_mark,
"[%s][PID=%i][state=%s][thread_state=%s] rocprofsys_progress(%s)\n",
category_name, process::get_id(), std::to_string(get_state()).c_str(),
std::to_string(get_thread_state()).c_str(), name.data());
causal::mark_progress_point(name);
}
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::audit(const gotcha_data_t& _data, audit::incoming,
Args&&... _args)
{
start<OptsT...>(_data.tool_id.c_str(), [&](::perfetto::EventContext ctx) {
if(config::get_perfetto_annotations())
{
int64_t _n = 0;
ROCPROFSYS_FOLD_EXPRESSION(tracing::add_perfetto_annotation(
ctx, tim::try_demangle<std::remove_reference_t<Args>>(), _args, _n++));
}
});
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::audit(const gotcha_data_t& _data, audit::outgoing,
Args&&... _args)
{
stop<OptsT...>(_data.tool_id.c_str(), [&](::perfetto::EventContext ctx) {
if(config::get_perfetto_annotations())
tracing::add_perfetto_annotation(ctx, "return", JOIN(", ", _args...));
});
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::audit(std::string_view _name, audit::incoming,
Args&&... _args)
{
start<OptsT...>(_name.data(), [&](::perfetto::EventContext ctx) {
if(config::get_perfetto_annotations())
{
int64_t _n = 0;
ROCPROFSYS_FOLD_EXPRESSION(tracing::add_perfetto_annotation(
ctx, tim::try_demangle<std::remove_reference_t<Args>>(), _args, _n++));
}
});
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::audit(std::string_view _name, audit::outgoing,
Args&&... _args)
{
stop<OptsT...>(_name.data(), [&](::perfetto::EventContext ctx) {
if(config::get_perfetto_annotations())
tracing::add_perfetto_annotation(ctx, "return", JOIN(", ", _args...));
});
}
template <typename CategoryT>
template <typename... OptsT, typename... Args>
void
category_region<CategoryT>::audit(quirk::config<OptsT...>, Args&&... _args)
{
audit<OptsT...>(std::forward<Args>(_args)...);
}
template <typename CategoryT>
struct local_category_region : comp::base<local_category_region<CategoryT>, void>
{
using impl_type = category_region<CategoryT>;
static constexpr auto category_name = impl_type::category_name;
static std::string label() { return impl_type::label(); }
template <typename... OptsT, typename... Args>
auto start(Args&&... args)
{
if(m_prefix.empty()) return;
return impl_type::template start<OptsT...>(m_prefix, std::forward<Args>(args)...);
}
template <typename... OptsT, typename... Args>
auto stop(Args&&... args)
{
if(m_prefix.empty()) return;
return impl_type::template stop<OptsT...>(m_prefix, std::forward<Args>(args)...);
}
template <typename... OptsT, typename... Args>
auto mark(Args&&... args)
{
if(m_prefix.empty()) return;
return impl_type::template mark<OptsT...>(m_prefix, std::forward<Args>(args)...);
}
template <typename... OptsT, typename... Args>
auto audit(Args&&... args)
-> decltype(impl_type::template audit<OptsT...>(std::declval<std::string_view>(),
std::forward<Args>(args)...))
{
if(m_prefix.empty()) return;
return impl_type::template audit<OptsT...>(m_prefix, std::forward<Args>(args)...);
}
template <typename... OptsT, typename... Args>
auto audit(quirk::config<OptsT...>, Args&&... args)
{
if(m_prefix.empty()) return;
return impl_type::template audit<OptsT...>(quirk::config<OptsT...>{}, m_prefix,
std::forward<Args>(args)...);
}
void set_prefix(std::string_view _v) { m_prefix = _v; }
private:
std::string_view m_prefix = {};
};
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,425 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/comm_data.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/perfetto.hpp"
#include "library/tracing.hpp"
#include <timemory/backends/mpi.hpp>
#include <timemory/manager.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/locking.hpp>
namespace rocprofsys
{
namespace component
{
namespace
{
template <typename Tp, typename... Args>
void
write_perfetto_counter_track(uint64_t _val)
{
using counter_track = rocprofsys::perfetto_counter_track<Tp>;
if(rocprofsys::get_use_perfetto() &&
rocprofsys::get_state() == rocprofsys::State::Active)
{
auto _emplace = [](const size_t _idx) {
if(!counter_track::exists(_idx))
{
std::string _label = (_idx > 0)
? JOIN(" ", Tp::label, JOIN("", '[', _idx, ']'))
: Tp::label;
counter_track::emplace(_idx, _label, "bytes");
}
};
const size_t _idx = 0;
static std::once_flag _once{};
std::call_once(_once, _emplace, _idx);
static std::mutex _mutex{};
static uint64_t value = 0;
uint64_t _now = 0;
{
std::unique_lock<std::mutex> _lk{ _mutex };
_now = rocprofsys::tracing::now<uint64_t>();
_val = (value += _val);
}
TRACE_COUNTER(Tp::value, counter_track::at(_idx, 0), _now, _val);
}
}
} // namespace
void
comm_data::preinit()
{
configure();
}
void
comm_data::global_finalize()
{
configure();
}
void
comm_data::configure()
{
static bool _once = false;
if(_once) return;
_once = true;
comm_data_tracker_t::label() = "comm_data";
comm_data_tracker_t::description() = "Tracks MPI/RCCL communication data sizes";
comm_data_tracker_t::display_unit() = "MB";
comm_data_tracker_t::unit() = units::megabyte;
auto _fmt_flags = comm_data_tracker_t::get_format_flags();
_fmt_flags &= (std::ios_base::fixed & std::ios_base::scientific);
_fmt_flags |= (std::ios_base::scientific);
comm_data_tracker_t::set_precision(3);
comm_data_tracker_t::set_format_flags(_fmt_flags);
}
#if defined(ROCPROFSYS_USE_MPI)
// MPI_Send
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm)
{
int _size = mpi_type_size(datatype);
if(_size == 0) return;
write_perfetto_counter_track<mpi_send>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _a{ _name };
add(_a, count * _size);
tracker_t _b{ JOIN('/', _name, JOIN('=', "dst", dst)) };
add(_b, count * _size);
add(JOIN('/', _name, JOIN('=', "dst", dst), JOIN('=', "tag", tag)), count * _size);
}
// MPI_Recv
void
comm_data::audit(const gotcha_data& _data, audit::incoming, void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm, MPI_Status*)
{
int _size = mpi_type_size(datatype);
if(_size == 0) return;
write_perfetto_counter_track<mpi_recv>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _a{ _name };
add(_a, count * _size);
tracker_t _b{ JOIN('/', _name, JOIN('=', "dst", dst)) };
add(_b, count * _size);
add(JOIN('/', _name, JOIN('=', "dst", dst), JOIN('=', "tag", tag)), count * _size);
}
// MPI_Isend
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm, MPI_Request*)
{
int _size = mpi_type_size(datatype);
if(_size == 0) return;
write_perfetto_counter_track<mpi_send>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _a{ _name };
add(_a, count * _size);
tracker_t _b{ JOIN('/', _name, JOIN('=', "dst", dst)) };
add(_b, count * _size);
add(JOIN('/', _name, JOIN('=', "dst", dst), JOIN('=', "tag", tag)), count * _size);
}
// MPI_Irecv
void
comm_data::audit(const gotcha_data& _data, audit::incoming, void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm, MPI_Request*)
{
int _size = mpi_type_size(datatype);
if(_size == 0) return;
write_perfetto_counter_track<mpi_recv>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _a{ _name };
add(_a, count * _size);
tracker_t _b{ JOIN('/', _name, JOIN('=', "dst", dst)) };
add(_b, count * _size);
add(JOIN('/', _name, JOIN('=', "dst", dst), JOIN('=', "tag", tag)), count * _size);
}
// MPI_Bcast
void
comm_data::audit(const gotcha_data& _data, audit::incoming, void*, int count,
MPI_Datatype datatype, int root, MPI_Comm)
{
int _size = mpi_type_size(datatype);
if(_size == 0) return;
write_perfetto_counter_track<mpi_send>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _t{ _name };
add(_t, count * _size);
add(JOIN('/', _name, JOIN('=', "root", root)), count * _size);
}
// MPI_Allreduce
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, void*, int count,
MPI_Datatype datatype, MPI_Op, MPI_Comm)
{
int _size = mpi_type_size(datatype);
if(_size == 0) return;
write_perfetto_counter_track<mpi_recv>(count * _size);
write_perfetto_counter_track<mpi_send>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
add(_data, count * _size);
}
// MPI_Sendrecv
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, int sendcount,
MPI_Datatype sendtype, int dst, int sendtag, void*, int recvcount,
MPI_Datatype recvtype, int src, int recvtag, MPI_Comm, MPI_Status*)
{
int _send_size = mpi_type_size(sendtype);
int _recv_size = mpi_type_size(recvtype);
if(_send_size == 0 || _recv_size == 0) return;
write_perfetto_counter_track<mpi_send>(sendcount * _send_size);
write_perfetto_counter_track<mpi_recv>(recvcount * _recv_size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _t{ _name };
add(_t, sendcount * _send_size + recvcount * _recv_size);
{
tracker_t _b{ JOIN('/', _name, "send") };
add(_b, sendcount * _send_size);
tracker_t _c{ JOIN('/', _name, JOIN('=', "send", dst)) };
add(_b, sendcount * _send_size);
add(JOIN('/', _name, "send", JOIN('=', "tag", sendtag)), sendcount * _send_size);
add(JOIN('/', _name, JOIN('=', "send", dst), JOIN('=', "tag", sendtag)),
sendcount * _send_size);
}
{
tracker_t _b{ JOIN('/', _name, "recv") };
add(_b, recvcount * _recv_size);
tracker_t _c{ JOIN('/', _name, JOIN('=', "recv", src)) };
add(_b, recvcount * _recv_size);
add(JOIN('/', _name, "recv", JOIN('=', "tag", recvtag)), recvcount * _recv_size);
add(JOIN('/', _name, JOIN('=', "recv", src), JOIN('=', "tag", recvtag)),
recvcount * _recv_size);
}
}
// MPI_Gather
// MPI_Scatter
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, int sendcount,
MPI_Datatype sendtype, void*, int recvcount, MPI_Datatype recvtype,
int root, MPI_Comm)
{
int _send_size = mpi_type_size(sendtype);
int _recv_size = mpi_type_size(recvtype);
if(_send_size == 0 || _recv_size == 0) return;
write_perfetto_counter_track<mpi_send>(sendcount * _send_size);
write_perfetto_counter_track<mpi_recv>(recvcount * _recv_size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _t{ _name };
add(_t, sendcount * _send_size + recvcount * _recv_size);
tracker_t _r(JOIN('/', _name, JOIN('=', "root", root)));
add(_r, sendcount * _send_size + recvcount * _recv_size);
add(JOIN('/', _name, JOIN('=', "root", root), "send"), sendcount * _send_size);
add(JOIN('/', _name, JOIN('=', "root", root), "recv"), recvcount * _recv_size);
}
// MPI_Alltoall
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, int sendcount,
MPI_Datatype sendtype, void*, int recvcount, MPI_Datatype recvtype,
MPI_Comm)
{
int _send_size = mpi_type_size(sendtype);
int _recv_size = mpi_type_size(recvtype);
if(_send_size == 0 || _recv_size == 0) return;
write_perfetto_counter_track<mpi_send>(sendcount * _send_size);
write_perfetto_counter_track<mpi_recv>(recvcount * _recv_size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _t{ _name };
add(_t, sendcount * _send_size + recvcount * _recv_size);
add(JOIN('/', _name, "send"), sendcount * _send_size);
add(JOIN('/', _name, "recv"), recvcount * _recv_size);
}
#endif
#if defined(ROCPROFSYS_USE_RCCL)
// ncclReduce
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, ncclRedOp_t, int root, ncclComm_t,
hipStream_t)
{
int _size = rccl_type_size(datatype);
if(_size <= 0) return;
write_perfetto_counter_track<rccl_recv>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _t{ _name };
add(_t, count * _size);
add(JOIN('/', _name, JOIN('=', "root", root)), count * _size);
}
// ncclSend
// ncclGather
// ncclBcast
// ncclRecv
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, size_t count,
ncclDataType_t datatype, int peer, ncclComm_t, hipStream_t)
{
int _size = rccl_type_size(datatype);
if(_size <= 0) return;
static auto _send_types = std::unordered_set<std::string>{ "ncclSend", "ncclBcast" };
static auto _recv_types = std::unordered_set<std::string>{ "ncclGather", "ncclRecv" };
if(_send_types.count(_data.tool_id) > 0)
{
write_perfetto_counter_track<rccl_send>(count * _size);
}
else if(_recv_types.count(_data.tool_id) > 0)
{
write_perfetto_counter_track<rccl_recv>(count * _size);
}
else
{
ROCPROFSYS_CI_THROW(true, "RCCL function not handled: %s", _data.tool_id.c_str());
}
write_perfetto_counter_track<rccl_recv>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
std::string _label = "root";
if(_name.find("Send") != std::string::npos) _label = "peer";
tracker_t _t{ _name };
add(_t, count * _size);
add(JOIN('/', _name, JOIN('=', _label, peer)), count * _size);
}
// ncclBroadcast
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, int root, ncclComm_t, hipStream_t)
{
int _size = rccl_type_size(datatype);
if(_size <= 0) return;
write_perfetto_counter_track<rccl_send>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
auto _name = std::string_view{ _data.tool_id };
tracker_t _t{ _name };
add(_t, count * _size);
add(JOIN('/', _data.tool_id, JOIN('=', "root", root)), count * _size);
}
// ncclAllReduce
// ncclReduceScatter
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, ncclRedOp_t, ncclComm_t,
hipStream_t)
{
int _size = rccl_type_size(datatype);
if(_size <= 0) return;
static auto _recv_types = std::unordered_set<std::string>{ "ncclAllReduce" };
static auto _send_types = std::unordered_set<std::string>{ "ncclReduceScatter" };
if(_send_types.count(_data.tool_id) > 0)
{
write_perfetto_counter_track<rccl_send>(count * _size);
}
else if(_recv_types.count(_data.tool_id) > 0)
{
write_perfetto_counter_track<rccl_recv>(count * _size);
}
else
{
ROCPROFSYS_CI_THROW(true, "RCCL function not handled: %s", _data.tool_id.c_str());
}
if(!rocprofsys::get_use_timemory()) return;
add(_data, count * _size);
}
// ncclAllGather
void
comm_data::audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, ncclComm_t, hipStream_t)
{
int _size = rccl_type_size(datatype);
if(_size <= 0) return;
write_perfetto_counter_track<rccl_recv>(count * _size);
if(!rocprofsys::get_use_timemory()) return;
add(_data, count * _size);
}
#endif
} // namespace component
} // namespace rocprofsys
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<float, tim::project::rocprofsys>), true, float)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(comm_data, false, void)
@@ -0,0 +1,244 @@
// MIT License
//
// Copyright (c) 2020, The Regents of the University of California,
// through Lawrence Berkeley National Laboratory (subject to receipt of any
// required approvals from the U.S. Dept. of Energy). 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 "common/join.hpp"
#include "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/rccl.hpp"
#include "core/timemory.hpp"
#include "library/components/category_region.hpp"
#include <timemory/api/macros.hpp>
#include <timemory/components/gotcha/backends.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/operations/types/set.hpp>
#include <timemory/utility/types.hpp>
#include <optional>
#if defined(ROCPROFSYS_USE_MPI)
# include <mpi.h>
#endif
#include <atomic>
#include <functional>
#include <memory>
#include <set>
#include <string>
#include <utility>
ROCPROFSYS_COMPONENT_ALIAS(comm_data_tracker_t,
::tim::component::data_tracker<float, project::rocprofsys>)
namespace rocprofsys
{
namespace component
{
using gotcha_data = ::tim::component::gotcha_data;
struct comm_data : base<comm_data, void>
{
using value_type = void;
using this_type = comm_data;
using base_type = base<this_type, value_type>;
using tracker_t = tim::auto_tuple<comm_data_tracker_t>;
using data_type = float;
struct mpi_recv
{
static constexpr auto value = "comm_data";
static constexpr auto label = "MPI Comm Recv";
};
struct mpi_send
{
static constexpr auto value = "comm_data";
static constexpr auto label = "MPI Comm Send";
};
struct rccl_recv
{
static constexpr auto value = "comm_data";
static constexpr auto label = "RCCL Comm Recv";
};
struct rccl_send
{
static constexpr auto value = "comm_data";
static constexpr auto label = "RCCL Comm Send";
};
ROCPROFSYS_DEFAULT_OBJECT(comm_data)
static void preinit();
static void configure();
static void global_finalize();
static void start() {}
static void stop() {}
#if defined(ROCPROFSYS_USE_MPI)
static int mpi_type_size(MPI_Datatype _datatype)
{
int _size = 0;
PMPI_Type_size(_datatype, &_size);
return _size;
}
// MPI_Send
static void audit(const gotcha_data& _data, audit::incoming, const void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm);
// MPI_Recv
static void audit(const gotcha_data& _data, audit::incoming, void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm, MPI_Status*);
// MPI_Isend
static void audit(const gotcha_data& _data, audit::incoming, const void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm, MPI_Request*);
// MPI_Irecv
static void audit(const gotcha_data& _data, audit::incoming, void*, int count,
MPI_Datatype datatype, int dst, int tag, MPI_Comm, MPI_Request*);
// MPI_Bcast
static void audit(const gotcha_data& _data, audit::incoming, void*, int count,
MPI_Datatype datatype, int root, MPI_Comm);
// MPI_Allreduce
static void audit(const gotcha_data& _data, audit::incoming, const void*, void*,
int count, MPI_Datatype datatype, MPI_Op, MPI_Comm);
// MPI_Sendrecv
static void audit(const gotcha_data& _data, audit::incoming, const void*,
int sendcount, MPI_Datatype sendtype, int, int sendtag, void*,
int recvcount, MPI_Datatype recvtype, int, int recvtag, MPI_Comm,
MPI_Status*);
// MPI_Gather
// MPI_Scatter
static void audit(const gotcha_data& _data, audit::incoming, const void*,
int sendcount, MPI_Datatype sendtype, void*, int recvcount,
MPI_Datatype recvtype, int root, MPI_Comm);
// MPI_Alltoall
static void audit(const gotcha_data& _data, audit::incoming, const void*,
int sendcount, MPI_Datatype sendtype, void*, int recvcount,
MPI_Datatype recvtype, MPI_Comm);
#endif
#if defined(ROCPROFSYS_USE_RCCL)
static auto rccl_type_size(ncclDataType_t datatype)
{
switch(datatype)
{
case ncclInt8:
case ncclUint8: return 1;
case ncclFloat16: return 2;
case ncclInt32:
case ncclUint32:
case ncclFloat32: return 4;
case ncclInt64:
case ncclUint64:
case ncclFloat64: return 8;
default: return 0;
};
}
// ncclReduce
static void audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, ncclRedOp_t, int root,
ncclComm_t, hipStream_t);
// ncclSend
// ncclGather
// ncclBcast
// ncclRecv
static void audit(const gotcha_data& _data, audit::incoming, const void*,
size_t count, ncclDataType_t datatype, int peer, ncclComm_t,
hipStream_t);
// ncclBroadcast
static void audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, int root, ncclComm_t,
hipStream_t);
// ncclAllReduce
// ncclReduceScatter
static void audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, ncclRedOp_t, ncclComm_t,
hipStream_t);
// ncclAllGather
// ncclAlltoAll
static void audit(const gotcha_data& _data, audit::incoming, const void*, const void*,
size_t count, ncclDataType_t datatype, ncclComm_t, hipStream_t);
#endif
private:
static auto& add(tracker_t& _t, data_type value)
{
if(rocprofsys::get_state() != rocprofsys::State::Active)
{
_t.invoke<operation::set_is_invalid>(true);
return _t;
}
_t.store(std::plus<data_type>{}, value);
return _t;
}
static auto add(const gotcha_data& _data, data_type value)
{
tracker_t _t{ std::string_view{ _data.tool_id.c_str() } };
return add(_t, value);
}
static auto add(std::string&& _name, data_type value)
{
tracker_t _t{ _name };
return add(_t, value);
}
static auto add(std::string_view _name, data_type value)
{
tracker_t _t{ _name };
return add(_t, value);
}
};
} // namespace component
} // namespace rocprofsys
#if !defined(ROCPROFSYS_EXTERN_COMPONENTS) || \
(defined(ROCPROFSYS_EXTERN_COMPONENTS) && ROCPROFSYS_EXTERN_COMPONENTS > 0)
# include <timemory/components/base.hpp>
# include <timemory/components/data_tracker/components.hpp>
# include <timemory/operations.hpp>
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<float, tim::project::rocprofsys>), true, float)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(comm_data, false, void)
#endif
@@ -0,0 +1,222 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/cpu_freq.hpp"
#include "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/perfetto.hpp"
#include "core/timemory.hpp"
#include <timemory/components/macros.hpp>
#include <timemory/components/rusage/backends.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/procfs/cpuinfo.hpp>
#include <timemory/utility/type_list.hpp>
namespace cpuinfo = tim::procfs::cpuinfo;
namespace rocprofsys
{
namespace component
{
cpu_freq::cpu_id_set_t&
cpu_freq::get_enabled_cpus()
{
static auto _v = cpu_id_set_t{};
return _v;
}
std::string
cpu_freq::label()
{
return "cpu_freq";
}
std::string
cpu_freq::description()
{
return "Records the current CPU frequencies";
}
int64_t
cpu_freq::unit()
{
return tim::units::MHz;
}
std::string
cpu_freq::display_unit()
{
return tim::units::freq_repr(unit());
}
void
cpu_freq::configure()
{
auto _ncpu = cpuinfo::freq::size();
auto _enabled_freqs = std::set<uint64_t>{};
auto _enabled_val = get_sampling_cpus();
for(auto& itr : _enabled_val)
itr = tolower(itr);
if(_enabled_val == "off")
_enabled_val = "none";
else if(_enabled_val == "on")
_enabled_val = "all";
if(_enabled_val != "none" && _enabled_val != "all")
{
auto _enabled = tim::delimit(_enabled_val, ",; \t");
if(_enabled.empty())
{
for(size_t i = 0; i < _ncpu; ++i)
_enabled_freqs.emplace(i);
}
for(auto&& _v : _enabled)
{
if(_v.find_first_not_of("0123456789-") != std::string::npos)
{
ROCPROFSYS_VERBOSE_F(
0,
"Invalid CPU specification. Only numerical values (e.g., 0) or "
"ranges (e.g., 0-7) are permitted. Ignoring %s...",
_v.c_str());
continue;
}
if(_v.find('-') != std::string::npos)
{
auto _vv = tim::delimit(_v, "-");
ROCPROFSYS_CONDITIONAL_THROW(
_vv.size() != 2,
"Invalid CPU range specification: %s. Required format N-M, e.g. 0-4",
_v.c_str());
for(size_t i = std::stoull(_vv.at(0)); i <= std::stoull(_vv.at(1)); ++i)
_enabled_freqs.emplace(i);
}
else
{
_enabled_freqs.emplace(std::stoull(_v));
}
}
}
else if(_enabled_val == "all")
{
for(size_t i = 0; i < _ncpu; ++i)
_enabled_freqs.emplace(i);
}
else if(_enabled_val == "none")
{
_enabled_freqs.clear();
}
for(auto itr : _enabled_freqs)
{
if(itr < cpuinfo::freq::size())
_enabled_freqs.emplace(itr);
else
{
ROCPROFSYS_VERBOSE(
0, "[cpu_freq::config] Warning! Removing invalid cpu %zu...\n", itr);
}
}
if(!cpuinfo::freq{})
{
ROCPROFSYS_VERBOSE(0, "[cpu_freq::config] Warning! CPU frequencies are disabled "
":: unable to open /proc/cpuinfo");
_enabled_freqs.clear();
}
ROCPROFSYS_CI_FAIL(!cpuinfo::freq{},
"[cpu_freq::config] CPU frequencies are disabled "
":: unable to open /proc/cpuinfo");
get_enabled_cpus() = _enabled_freqs;
}
std::string
cpu_freq::as_string() const
{
return tim::operation::base_printer<cpu_freq>{}(std::stringstream{}, *this).str();
}
cpu_freq::value_type
cpu_freq::record()
{
auto& enabled_cpu_freqs = get_enabled_cpus();
std::vector<uint64_t> _freqs{};
if(!enabled_cpu_freqs.empty())
{
_freqs.reserve(enabled_cpu_freqs.size());
auto&& _freq = cpuinfo::freq{};
for(const auto& itr : enabled_cpu_freqs)
{
_freqs.emplace_back(_freq(itr) * tim::units::MHz);
}
}
return _freqs;
}
void
cpu_freq::start()
{
value = record();
}
void
cpu_freq::stop()
{
using namespace tim::stl;
value = (record() - value);
}
cpu_freq&
cpu_freq::sample()
{
value = record();
return *this;
}
float
cpu_freq::at(size_t _idx, int64_t _unit) const
{
return (value.at(_idx) / static_cast<float>(_unit));
}
std::vector<float>
cpu_freq::get(int64_t _unit) const
{
std::vector<float> _v{};
_v.reserve(value.size());
for(const auto& itr : value)
_v.emplace_back(itr / static_cast<float>(_unit));
return _v;
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_INITIALIZE_STORAGE(rocprofsys::component::cpu_freq)
@@ -0,0 +1,111 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/mpl/concepts.hpp>
#include <timemory/units.hpp>
namespace rocprofsys
{
namespace component
{
struct cpu_freq
: comp::empty_base
, tim::component::base_format<cpu_freq>
, tim::component::base_data<std::vector<uint64_t>, 1>
{
using base_type = comp::empty_base;
using this_type = cpu_freq;
using value_type = std::vector<uint64_t>;
using storage_type = tim::storage<cpu_freq, value_type>;
using cpu_id_set_t = std::set<uint64_t>;
ROCPROFSYS_DEFAULT_OBJECT(cpu_freq)
// string id for component
static std::string label();
static std::string description();
static int64_t unit();
static std::string display_unit();
static void configure();
static cpu_id_set_t& get_enabled_cpus();
static value_type record();
// this will get called right before fork
void start();
void stop();
cpu_freq& sample();
std::string as_string() const;
float at(size_t _idx, int64_t _unit = unit()) const;
std::vector<float> get(int64_t _unit = unit()) const;
public:
static auto get_label() { return label(); }
static auto get_description() { return description(); }
static auto get_unit() { return unit(); }
static auto get_display_unit() { return display_unit(); }
static int64_t get_laps() { return 0; }
static storage_type* get_storage() { return nullptr; }
auto get_display() const { return as_string(); }
friend std::ostream& operator<<(std::ostream& _os, const cpu_freq& _v)
{
return (_os << _v.as_string());
}
template <typename ArchiveT>
void serialize(ArchiveT& _ar, const unsigned _version)
{
if constexpr(tim::concepts::is_output_archive<ArchiveT>::value)
operation::serialization<cpu_freq>{}(*this, _ar, _version);
else
_ar(tim::cereal::make_nvp("value", value));
(void) _version;
}
this_type& operator+=(const this_type& _rhs)
{
using namespace tim::stl;
value += _rhs.value;
return *this;
}
this_type& operator-=(const this_type& _rhs)
{
using namespace tim::stl;
value -= _rhs.value;
return *this;
}
private:
using tim::component::base_data<value_type, 1>::value;
};
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,69 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/defines.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/operations/types.hpp>
#include <timemory/utility/macros.hpp>
#include <timemory/utility/type_list.hpp>
namespace rocprofsys
{
namespace component
{
namespace
{
template <typename... Tp>
struct ensure_storage
{
ROCPROFSYS_DEFAULT_OBJECT(ensure_storage)
void operator()() const { ROCPROFSYS_FOLD_EXPRESSION((*this)(tim::type_list<Tp>{})); }
private:
template <typename Up, std::enable_if_t<tim::trait::is_available<Up>::value, int> = 0>
void operator()(tim::type_list<Up>) const
{
using namespace tim;
static thread_local auto _storage = operation::get_storage<Up>{}();
static thread_local auto _tid = threading::get_id();
static thread_local auto _dtor =
scope::destructor{ []() { operation::set_storage<Up>{}(nullptr, _tid); } };
tim::operation::set_storage<Up>{}(_storage, _tid);
if(_tid == 0 && !_storage) tim::trait::runtime_enabled<Up>::set(false);
}
template <typename Up,
std::enable_if_t<!tim::trait::is_available<Up>::value, long> = 0>
void operator()(tim::type_list<Up>) const
{
tim::trait::runtime_enabled<Up>::set(false);
}
};
} // namespace
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,125 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/exit_gotcha.hpp"
#include "core/common.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include "library/runtime.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/process/threading.hpp>
#include <timemory/utility/types.hpp>
#include <cstddef>
#include <cstdlib>
namespace rocprofsys
{
namespace component
{
void
exit_gotcha::configure()
{
exit_gotcha_t::get_initializer() = []() {
exit_gotcha_t::configure<0, void>("abort");
exit_gotcha_t::configure<1, void, int>("exit");
exit_gotcha_t::configure<2, void, int>("quick_exit");
exit_gotcha_t::configure<3, void, int>("_Exit");
};
}
namespace
{
auto _exit_info = exit_gotcha::exit_info{};
template <typename FuncT, typename... Args>
void
invoke_exit_gotcha(const exit_gotcha::gotcha_data& _data, FuncT _func, Args... _args)
{
threading::clear_callbacks();
if(get_state() < State::Finalized)
{
if(config::settings_are_configured())
{
ROCPROFSYS_VERBOSE(0, "finalizing %s before calling %s(%s)...\n",
get_exe_name().c_str(), _data.tool_id.c_str(),
JOIN(", ", _args...).c_str());
}
else
{
ROCPROFSYS_BASIC_VERBOSE(0, "finalizing %s before calling %s(%s)...\n",
get_exe_name().c_str(), _data.tool_id.c_str(),
JOIN(", ", _args...).c_str());
}
rocprofsys_finalize();
}
if(config::settings_are_configured())
{
ROCPROFSYS_VERBOSE(0, "calling %s(%s) in %s...\n", _data.tool_id.c_str(),
JOIN(", ", _args...).c_str(), get_exe_name().c_str());
}
else
{
ROCPROFSYS_BASIC_VERBOSE(0, "calling %s(%s) in %s...\n", _data.tool_id.c_str(),
JOIN(", ", _args...).c_str(), get_exe_name().c_str());
}
if(_exit_info.is_known && _exit_info.exit_code != 0)
{
ROCPROFSYS_BASIC_VERBOSE(0, "%s exiting with non-zero exit code: %i...\n",
get_exe_name().c_str(), _exit_info.exit_code);
}
(*_func)(_args...);
}
} // namespace
// exit
// quick_exit
void
exit_gotcha::operator()(const gotcha_data& _data, exit_func_t _func, int _ec) const
{
_exit_info = { true, _data.tool_id.find("quick") != std::string::npos, _ec };
invoke_exit_gotcha(_data, _func, _ec);
}
// abort
void
exit_gotcha::operator()(const gotcha_data& _data, abort_func_t _func) const
{
_exit_info = { true, false, SIGABRT };
invoke_exit_gotcha(_data, _func);
}
exit_gotcha::exit_info
exit_gotcha::get_exit_info()
{
return _exit_info;
}
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,76 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/components/base.hpp>
#include <timemory/components/gotcha/backends.hpp>
#include <cstdint>
#include <cstdlib>
namespace rocprofsys
{
namespace component
{
struct exit_gotcha : tim::component::base<exit_gotcha, void>
{
static constexpr size_t gotcha_capacity = 4;
using gotcha_data = tim::component::gotcha_data;
using exit_func_t = void (*)(int);
using abort_func_t = void (*)();
ROCPROFSYS_DEFAULT_OBJECT(exit_gotcha)
// string id for component
static std::string label() { return "exit_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
static inline void start() {}
static inline void stop() {}
// exit / _Exit / quick_exit
void operator()(const gotcha_data&, exit_func_t, int) const;
// abort
void operator()(const gotcha_data&, abort_func_t) const;
struct exit_info
{
bool is_known = false;
bool is_quick = false;
int exit_code = EXIT_SUCCESS;
};
static exit_info get_exit_info();
};
} // namespace component
using exit_gotcha_t = tim::component::gotcha<component::exit_gotcha::gotcha_capacity,
std::tuple<>, component::exit_gotcha>;
} // namespace rocprofsys
@@ -0,0 +1,178 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "api.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/perfetto.hpp"
#include "core/perfetto_fwd.hpp"
#include "core/state.hpp"
#include "library/components/fork_gotcha.hpp"
#include "library/runtime.hpp"
#include "library/sampling.hpp"
#include <timemory/backends/process.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/process/process.hpp>
#include <cstdlib>
#include <memory>
#include <pthread.h>
#include <unistd.h>
namespace rocprofsys
{
namespace component
{
namespace
{
// these are used to prevent handlers from executing multiple times
bool prefork_lock = false;
bool postfork_parent_lock = false;
bool postfork_child_lock = false;
// this does a quick exit (no cleanup) on child processes
// because perfetto has a tendency to access memory it
// shouldn't during cleanup
void
child_exit(int _ec, void*)
{
std::quick_exit(_ec);
}
void
prefork_setup()
{
if(prefork_lock) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
if(get_state() < State::Active && !config::settings_are_configured())
rocprofsys_init_library_hidden();
tim::set_env("ROCPROFSYS_PRELOAD", "0", 1);
tim::set_env("ROCPROFSYS_ROOT_PROCESS", process::get_id(), 0);
rocprofsys_reset_preload_hidden();
ROCPROFSYS_BASIC_VERBOSE(0, "fork() called on PID %i (rank: %i), TID %li\n",
process::get_id(), dmp::rank(), threading::get_id());
ROCPROFSYS_BASIC_DEBUG(
"Warning! Calling fork() within an OpenMPI application using libfabric "
"may result is segmentation fault\n");
TIMEMORY_CONDITIONAL_DEMANGLED_BACKTRACE(get_debug_env(), 16);
if(config::get_use_sampling()) sampling::block_samples();
rocprofsys::categories::disable_categories(config::get_enabled_categories());
// prevent re-entry until post-fork routines have been called
prefork_lock = true;
postfork_parent_lock = false;
postfork_child_lock = false;
}
void
postfork_parent()
{
if(postfork_parent_lock) return;
rocprofsys::categories::enable_categories(config::get_enabled_categories());
if(config::get_use_sampling()) sampling::unblock_samples();
// prevent re-entry until prefork has been called
postfork_parent_lock = true;
prefork_lock = false;
}
void
postfork_child()
{
if(postfork_child_lock) return;
ROCPROFSYS_REQUIRE(is_child_process())
<< "Error! child process " << process::get_id()
<< " believes it is the root process " << get_root_process_id() << "\n";
settings::enabled() = false;
settings::verbose() = -127;
settings::debug() = false;
rocprofsys::sampling::shutdown();
rocprofsys::categories::shutdown();
set_thread_state(::rocprofsys::ThreadState::Disabled);
rocprofsys::get_perfetto_session(process::get_parent_id()).release();
// register these exit handlers to avoid cleaning up resources
on_exit(&child_exit, nullptr);
std::atexit([]() { child_exit(EXIT_SUCCESS, nullptr); });
// prevent re-entry until prefork has been called
postfork_child_lock = true;
prefork_lock = false;
}
} // namespace
void
fork_gotcha::configure()
{
fork_gotcha_t::get_initializer() = []() {
TIMEMORY_C_GOTCHA(fork_gotcha_t, 0, fork);
};
// registering the pthread_atfork and gotcha means that we might execute twice
// handlers twice, hence the locks
pthread_atfork(&prefork_setup, &postfork_parent, &postfork_child);
}
pid_t
fork_gotcha::operator()(const gotcha_data_t&, pid_t (*_real_fork)()) const
{
prefork_setup();
auto _pid = (*_real_fork)();
if(_pid != 0)
{
ROCPROFSYS_BASIC_VERBOSE(0, "fork() called on PID %i created PID %i\n", getpid(),
_pid);
postfork_parent();
}
else
{
postfork_child();
}
if(!settings::use_output_suffix())
{
ROCPROFSYS_BASIC_VERBOSE(
0, "Application which make calls to fork() should enable using an process "
"identifier output suffix (i.e. set ROCPROFSYS_USE_PID=ON)\n");
}
return _pid;
}
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,59 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
namespace rocprofsys
{
namespace component
{
// this is used to wrap fork()
struct fork_gotcha : comp::base<fork_gotcha, void>
{
static constexpr size_t gotcha_capacity = 1;
using gotcha_data_t = comp::gotcha_data;
ROCPROFSYS_DEFAULT_OBJECT(fork_gotcha)
// string id for component
static std::string label() { return "fork_gotcha"; }
// generate the gotcha wrappers
static void configure();
// this will get called right before fork
pid_t operator()(const gotcha_data_t&, pid_t (*)()) const;
// silence SFINAE disabled for rocprofsys::fork_gotcha warnings
static inline void start() {}
static inline void stop() {}
};
} // namespace component
using fork_gotcha_t = comp::gotcha<component::fork_gotcha::gotcha_capacity, std::tuple<>,
component::fork_gotcha>;
} // namespace rocprofsys
@@ -0,0 +1,385 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/mpi_gotcha.hpp"
#include "api.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/mproc.hpp"
#include "library/components/category_region.hpp"
#include "library/components/comm_data.hpp"
#include <timemory/backends/mpi.hpp>
#include <timemory/backends/process.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/signals/signal_mask.hpp>
#include <timemory/utility/locking.hpp>
#include <cstdint>
#include <limits>
#include <thread>
#include <unistd.h>
namespace rocprofsys
{
namespace component
{
namespace
{
using mpip_bundle_t =
tim::component_tuple<category_region<category::mpi>, comp::comm_data>;
struct comm_rank_data
{
int rank = -1;
int size = -1;
uintptr_t comm = mpi_gotcha::null_comm();
auto updated() const
{
return comm != mpi_gotcha::null_comm() && rank >= 0 && size > 0;
};
friend bool operator==(const comm_rank_data& _lhs, const comm_rank_data& _rhs)
{
auto _lupd = _lhs.updated();
auto _rupd = _rhs.updated();
return std::tie(_lupd, _lhs.rank, _lhs.size, _lhs.comm) ==
std::tie(_rupd, _rhs.rank, _rhs.size, _rhs.comm);
}
friend bool operator!=(const comm_rank_data& _lhs, const comm_rank_data& _rhs)
{
return !(_lhs == _rhs);
}
friend bool operator>(const comm_rank_data& _lhs, const comm_rank_data& _rhs)
{
ROCPROFSYS_CI_THROW(!_lhs.updated() && !_rhs.updated(),
"Error! comparing rank data that is not updated");
if(_lhs.updated() && !_rhs.updated()) return true;
if(!_lhs.updated() && _rhs.updated()) return false;
if(_lhs.size != _rhs.size) return _lhs.size > _rhs.size;
if(_lhs.rank != _rhs.rank) return _lhs.rank > _rhs.rank;
// lesser comm is greater
return _lhs.comm < _rhs.comm;
}
friend bool operator<(const comm_rank_data& _lhs, const comm_rank_data& _rhs)
{
return (_lhs != _rhs && !(_lhs > _rhs));
}
};
uint64_t mpip_index = std::numeric_limits<uint64_t>::max();
auto last_comm_record = comm_rank_data{};
auto mproc_comm_record = comm_rank_data{};
auto mpi_comm_records = std::map<uintptr_t, comm_rank_data>{};
using tim::auto_lock_t;
using tim::type_mutex;
#if defined(TIMEMORY_USE_MPI)
int
rocprofsys_mpi_copy(MPI_Comm, int, void*, void*, void*, int*)
{
return MPI_SUCCESS;
}
int
rocprofsys_mpi_fini(MPI_Comm, int, void*, void*)
{
ROCPROFSYS_DEBUG("MPI Comm attribute finalize\n");
auto _blocked = get_sampling_signals();
if(!_blocked.empty())
tim::signals::block_signals(_blocked, tim::signals::sigmask_scope::process);
if(mpip_index != std::numeric_limits<uint64_t>::max())
comp::deactivate_mpip<mpip_bundle_t, project::rocprofsys>(mpip_index);
if(is_root_process()) rocprofsys_finalize_hidden();
return MPI_SUCCESS;
}
#endif
// this ensures rocprofsys_finalize is called before MPI_Finalize
void
rocprofsys_mpi_set_attr()
{
#if defined(TIMEMORY_USE_MPI)
auto _blocked = get_sampling_signals();
if(!_blocked.empty())
tim::signals::block_signals(_blocked, tim::signals::sigmask_scope::process);
int _comm_key = -1;
if(PMPI_Comm_create_keyval(&rocprofsys_mpi_copy, &rocprofsys_mpi_fini, &_comm_key,
nullptr) == MPI_SUCCESS)
PMPI_Comm_set_attr(MPI_COMM_SELF, _comm_key, nullptr);
if(!_blocked.empty())
tim::signals::unblock_signals(_blocked, tim::signals::sigmask_scope::process);
#endif
}
using strset_t = std::set<std::string>;
auto permit_bindings = strset_t{};
auto reject_bindings = strset_t{};
} // namespace
void
mpi_gotcha::configure()
{
// don't emit warnings for missing MPI functions unless debug or verbosity >= 3
if(get_verbose_env() < 3 && !get_debug_env())
{
for(size_t i = 0; i < mpi_gotcha_t::capacity(); ++i)
{
auto* itr = mpi_gotcha_t::at(i);
if(itr) itr->verbose = -1;
}
}
mpi_gotcha_t::get_initializer() = []() {
mpi_gotcha_t::template configure<0, int, int*, char***>("MPI_Init");
mpi_gotcha_t::template configure<1, int, int*, char***, int, int*>(
"MPI_Init_thread");
mpi_gotcha_t::template configure<2, int>("MPI_Finalize");
reject_bindings.emplace("MPI_Init");
reject_bindings.emplace("MPI_Init_thread");
reject_bindings.emplace("MPI_Finalize");
#if defined(ROCPROFSYS_USE_MPI_HEADERS) && ROCPROFSYS_USE_MPI_HEADERS > 0
mpi_gotcha_t::template configure<3, int, comm_t, int*>("MPI_Comm_rank");
mpi_gotcha_t::template configure<4, int, comm_t, int*>("MPI_Comm_size");
reject_bindings.emplace("MPI_Comm_rank");
reject_bindings.emplace("MPI_Comm_size");
#endif
};
}
void
mpi_gotcha::shutdown()
{
update();
}
bool
mpi_gotcha::update()
{
auto_lock_t _lk{ type_mutex<mpi_gotcha>(), std::defer_lock };
if(!_lk.owns_lock()) _lk.lock();
comm_rank_data _rank_data = mproc_comm_record;
for(const auto& itr : mpi_comm_records)
{
// skip null comms
if(itr.first == null_comm()) continue;
// if currently have null comm, replace
else if(_rank_data.comm == null_comm())
_rank_data = itr.second;
// if
else if(itr.second > _rank_data)
_rank_data = itr.second;
}
if(_rank_data.updated() && _rank_data != last_comm_record)
{
auto _rank = _rank_data.rank;
auto _size = _rank_data.size;
tim::mpi::set_rank(_rank);
tim::mpi::set_size(_size);
tim::settings::default_process_suffix() = _rank;
ROCPROFSYS_BASIC_VERBOSE(0, "[pid=%i] MPI rank: %i (%i), MPI size: %i (%i)\n",
process::get_id(), tim::mpi::rank(), _rank,
tim::mpi::size(), _size);
last_comm_record = _rank_data;
config::get_use_pid() = true;
return true;
}
return false;
}
void
mpi_gotcha::disable_comm_intercept()
{
#if defined(ROCPROFSYS_USE_MPI_HEADERS) && ROCPROFSYS_USE_MPI_HEADERS > 0
mpi_gotcha_t::revert<3>();
mpi_gotcha_t::revert<4>();
#endif
}
void
mpi_gotcha::audit(const gotcha_data_t& _data, audit::incoming, int*, char***)
{
ROCPROFSYS_BASIC_DEBUG_F("%s(int*, char***)\n", _data.tool_id.c_str());
rocprofsys_push_trace_hidden(_data.tool_id.c_str());
#if !defined(TIMEMORY_USE_MPI) && defined(TIMEMORY_USE_MPI_HEADERS)
tim::mpi::is_initialized_callback() = []() { return true; };
tim::mpi::is_finalized() = false;
#endif
}
void
mpi_gotcha::audit(const gotcha_data_t& _data, audit::incoming, int*, char***, int, int*)
{
ROCPROFSYS_BASIC_DEBUG_F("%s(int*, char***, int, int*)\n", _data.tool_id.c_str());
rocprofsys_push_trace_hidden(_data.tool_id.c_str());
#if !defined(TIMEMORY_USE_MPI) && defined(TIMEMORY_USE_MPI_HEADERS)
tim::mpi::is_initialized_callback() = []() { return true; };
tim::mpi::is_finalized() = false;
#endif
}
void
mpi_gotcha::audit(const gotcha_data_t& _data, audit::incoming)
{
ROCPROFSYS_BASIC_DEBUG_F("%s()\n", _data.tool_id.c_str());
auto _blocked = get_sampling_signals();
if(!_blocked.empty())
tim::signals::block_signals(_blocked, tim::signals::sigmask_scope::process);
if(mpip_index != std::numeric_limits<uint64_t>::max())
comp::deactivate_mpip<mpip_bundle_t, project::rocprofsys>(mpip_index);
#if !defined(TIMEMORY_USE_MPI) && defined(TIMEMORY_USE_MPI_HEADERS)
tim::mpi::is_initialized_callback() = []() { return false; };
tim::mpi::is_finalized() = true;
#else
if(is_root_process() && rocprofsys::get_state() < rocprofsys::State::Finalized)
rocprofsys_finalize_hidden();
#endif
}
void
mpi_gotcha::audit(const gotcha_data_t& _data, audit::incoming, comm_t _comm, int* _val)
{
ROCPROFSYS_BASIC_DEBUG_F("%s()\n", _data.tool_id.c_str());
rocprofsys_push_trace_hidden(_data.tool_id.c_str());
if(_data.tool_id == "MPI_Comm_rank")
{
m_comm_val = (uintptr_t) _comm; // NOLINT
m_rank_ptr = _val;
}
else if(_data.tool_id == "MPI_Comm_size")
{
m_comm_val = (uintptr_t) _comm; // NOLINT
m_size_ptr = _val;
}
else
{
ROCPROFSYS_BASIC_PRINT_F("%s(<comm>, %p) :: unexpected function wrapper\n",
_data.tool_id.c_str(), static_cast<void*>(_val));
}
}
void
mpi_gotcha::audit(const gotcha_data_t& _data, audit::outgoing, int _retval)
{
ROCPROFSYS_BASIC_DEBUG_F("%s() returned %i\n", _data.tool_id.c_str(), (int) _retval);
if(!settings::use_output_suffix()) settings::use_output_suffix() = true;
if(_retval == tim::mpi::success_v && _data.tool_id.find("MPI_Init") == 0)
{
rocprofsys_mpi_set_attr();
// rocprof-sys will set this environement variable to true in binary rewrite mode
// when it detects MPI. Hides this env variable from the user to avoid this
// being activated unwaringly during runtime instrumentation because that
// will result in double instrumenting the MPI functions (unless the MPI functions
// were excluded via a regex expression)
if(get_use_mpip())
{
ROCPROFSYS_BASIC_VERBOSE_F(2, "Activating MPI wrappers...\n");
// use env vars ROCPROFSYS_MPIP_PERMIT_LIST and ROCPROFSYS_MPIP_REJECT_LIST
// to control the gotcha bindings at runtime
comp::configure_mpip<mpip_bundle_t, project::rocprofsys>(permit_bindings,
reject_bindings);
mpip_index = comp::activate_mpip<mpip_bundle_t, project::rocprofsys>();
}
auto_lock_t _lk{ type_mutex<mpi_gotcha>() };
if(!mproc_comm_record.updated())
{
auto _pid = getpid();
auto _ppid = getppid();
auto _size = mproc::get_concurrent_processes(_ppid).size();
if(_size > 0)
{
mproc_comm_record.comm = _ppid;
mproc_comm_record.size = m_size = _size;
auto _rank = mproc::get_process_index(_pid, _ppid);
if(_rank >= 0) mproc_comm_record.rank = m_rank = _rank;
}
}
}
else if(_retval == tim::mpi::success_v && _data.tool_id.find("MPI_Comm_") == 0)
{
auto_lock_t _lk{ type_mutex<mpi_gotcha>() };
if(m_comm_val != null_comm())
{
auto& _comm_entry = mpi_comm_records[m_comm_val];
_comm_entry.comm = m_comm_val;
auto _get_rank = [&]() {
return (m_rank_ptr) ? std::max<int>(*m_rank_ptr, m_rank) : m_rank;
};
auto _get_size = [&]() {
return (m_size_ptr) ? std::max<int>(*m_size_ptr, m_size)
: std::max<int>(m_size, _get_rank() + 1);
};
if(_data.tool_id == "MPI_Comm_rank" || _data.tool_id == "MPI_Comm_size")
{
_comm_entry.rank = m_rank = std::max<int>(_comm_entry.rank, _get_rank());
_comm_entry.size = m_size = std::max<int>(_comm_entry.size, _get_size());
}
else
{
ROCPROFSYS_BASIC_VERBOSE(
0, "%s() returned %i :: unexpected function wrapper\n",
_data.tool_id.c_str(), (int) _retval);
}
if(_comm_entry.updated())
{
static thread_local int _num_updates = 0;
static int _disable_after =
tim::get_env<int>("ROCPROFSYS_MPI_MAX_COMM_UPDATES", 4);
if(_num_updates++ < _disable_after) update();
}
}
}
rocprofsys_pop_trace_hidden(_data.tool_id.c_str());
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_INITIALIZE_STORAGE(rocprofsys::component::mpi_gotcha)
@@ -0,0 +1,85 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <cstdint>
namespace rocprofsys
{
namespace component
{
// this is used to wrap MPI_Init and MPI_Init_thread
struct mpi_gotcha : comp::base<mpi_gotcha, void>
{
using comm_t = tim::mpi::comm_t;
using gotcha_data_t = comp::gotcha_data;
ROCPROFSYS_DEFAULT_OBJECT(mpi_gotcha)
// string id for component
static std::string label() { return "mpi_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
// called right before MPI_Init with that functions arguments
static void audit(const gotcha_data_t& _data, audit::incoming, int*, char***);
// called right before MPI_Init_thread with that functions arguments
static void audit(const gotcha_data_t& _data, audit::incoming, int*, char***, int,
int*);
// called right before MPI_Finalize
static void audit(const gotcha_data_t& _data, audit::incoming);
// called right before MPI_Comm_{rank,size} with that functions arguments
void audit(const gotcha_data_t& _data, audit::incoming, comm_t, int*);
// called right after MPI_{Init,Init_thread,Comm_rank,Comm_size} with the return value
void audit(const gotcha_data_t& _data, audit::outgoing, int _retval);
// without these you will get a verbosity level 1 warning
static void start() {}
static void stop() {}
static bool update();
static uintptr_t null_comm() { return std::numeric_limits<uintptr_t>::max(); }
static void disable_comm_intercept();
private:
int m_rank = 0;
int m_size = 1;
int* m_rank_ptr = nullptr;
int* m_size_ptr = nullptr;
uintptr_t m_comm_val = null_comm();
};
} // namespace component
using mpi_gotcha_t =
comp::gotcha<5, tim::component_tuple<component::mpi_gotcha>, project::rocprofsys>;
} // namespace rocprofsys
@@ -0,0 +1,209 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/numa_gotcha.hpp"
#include "core/common.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include "library/components/category_region.hpp"
#include "library/runtime.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/utility/types.hpp>
#include <cstddef>
#include <cstdlib>
// int
// numa_migrate_pages(int pid, struct bitmask* from, struct bitmask* to);
// int
// numa_move_pages(int pid, unsigned long count, void** pages, const int* nodes, int*
// status,
// int flags);
namespace rocprofsys
{
namespace component
{
namespace
{
auto&
get_numa_gotcha()
{
static auto _v = tim::lightweight_tuple<numa_gotcha_t>{};
return _v;
}
} // namespace
void
numa_gotcha::configure()
{
// don't emit warnings for missing MPI functions unless debug or verbosity >= 3
if(get_verbose_env() < 3 && !get_debug_env())
{
for(size_t i = 0; i < numa_gotcha_t::capacity(); ++i)
{
auto* itr = numa_gotcha_t::at(i);
if(itr) itr->verbose = -1;
}
}
numa_gotcha_t::get_initializer() = []() {
numa_gotcha_t::configure<0, long, void*, unsigned long, int, const unsigned long*,
unsigned long, unsigned>("mbind");
numa_gotcha_t::configure<1, long, int, unsigned long, const unsigned long*,
const unsigned long*>("migrate_pages");
numa_gotcha_t::configure<2, long, int, unsigned long, void**, const int*, int*,
int>("move_pages");
numa_gotcha_t::configure<3, int, int, struct bitmask*, struct bitmask*>(
"numa_migrate_pages");
numa_gotcha_t::configure<4, int, int, unsigned long, void**, const int*, int*,
int>("numa_move_pages");
numa_gotcha_t::configure<5, void*, size_t>("numa_alloc");
numa_gotcha_t::configure<6, void*, size_t>("numa_alloc_local");
numa_gotcha_t::configure<7, void*, size_t>("numa_alloc_interleaved");
numa_gotcha_t::configure<8, void*, size_t, int>("numa_alloc_onnode");
numa_gotcha_t::configure<9, void*, void*, size_t, size_t>("numa_realloc");
numa_gotcha_t::configure<10, void, void*, size_t>("numa_free");
};
}
void
numa_gotcha::shutdown()
{
numa_gotcha_t::disable();
}
void
numa_gotcha::start()
{
if(!get_numa_gotcha().get<numa_gotcha_t>()->get_is_running())
{
configure();
get_numa_gotcha().start();
}
}
void
numa_gotcha::stop()
{
// get_numa_gotcha().stop();
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, void* start,
unsigned long len, int mode, const unsigned long* nmask,
unsigned long maxnode, unsigned flags)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "start",
start, "len", len, "mode", mode, "nmask",
nmask, "maxnode", maxnode, "flags", flags);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, int pid,
unsigned long maxnode, const unsigned long* frommask,
const unsigned long* tomask)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "pid", pid,
"maxnode", maxnode, "frommask", frommask,
"tomask", tomask);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, int pid,
unsigned long count, void** pages, const int* nodes, int* status,
int flags)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "pid", pid,
"count", count, "pages", pages, "nodes", nodes,
"status", status, "flags", flags);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, int pid,
struct bitmask* from, struct bitmask* to)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "pid", pid,
"from", JOIN("", from).c_str(), "to",
JOIN("", to).c_str());
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, size_t _size)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "size",
_size);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, size_t _size, int _node)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "size",
_size, "node", _node);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, void* _addr, size_t _size)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id }, "address",
_addr, "size", _size);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::incoming, void* _old_addr,
size_t _old_size, size_t _new_size)
{
category_region<category::numa>::start(std::string_view{ _data.tool_id },
"old_address", _old_addr, "old_size",
_old_size, "new_size", _new_size);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::outgoing, int ret)
{
category_region<category::numa>::stop(std::string_view{ _data.tool_id }, "return",
ret);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::outgoing, long ret)
{
category_region<category::numa>::stop(std::string_view{ _data.tool_id }, "return",
ret);
}
void
numa_gotcha::audit(const gotcha_data& _data, audit::outgoing, void* ret)
{
category_region<category::numa>::stop(std::string_view{ _data.tool_id }, "return",
ret);
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_STORAGE_INITIALIZER(rocprofsys::component::numa_gotcha)
@@ -0,0 +1,81 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/components/base.hpp>
#include <timemory/components/gotcha/backends.hpp>
#include <cstdint>
#include <cstdlib>
namespace rocprofsys
{
namespace component
{
struct numa_gotcha : tim::component::base<numa_gotcha, void>
{
static constexpr size_t gotcha_capacity = 11;
using gotcha_data = tim::component::gotcha_data;
using exit_func_t = void (*)(int);
using abort_func_t = void (*)();
ROCPROFSYS_DEFAULT_OBJECT(numa_gotcha)
// string id for component
static std::string label() { return "numa_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
static void start();
static void stop();
static void audit(const gotcha_data&, audit::incoming, void* start, unsigned long len,
int mode, const unsigned long* nmask, unsigned long maxnode,
unsigned flags);
static void audit(const gotcha_data&, audit::incoming, int pid, unsigned long maxnode,
const unsigned long* frommask, const unsigned long* tomask);
static void audit(const gotcha_data&, audit::incoming, int pid, unsigned long count,
void** pages, const int* nodes, int* status, int flags);
static void audit(const gotcha_data&, audit::incoming, int pid, struct bitmask* from,
struct bitmask* to);
static void audit(const gotcha_data&, audit::incoming, size_t);
static void audit(const gotcha_data&, audit::incoming, size_t, int);
static void audit(const gotcha_data&, audit::incoming, void*, size_t);
static void audit(const gotcha_data&, audit::incoming, void*, size_t, size_t);
static void audit(const gotcha_data&, audit::outgoing, int);
static void audit(const gotcha_data&, audit::outgoing, long);
static void audit(const gotcha_data&, audit::outgoing, void*);
};
} // namespace component
using numa_bundle_t = tim::component_bundle<category::numa, component::numa_gotcha>;
using numa_gotcha_t = tim::component::gotcha<component::numa_gotcha::gotcha_capacity,
numa_bundle_t, category::numa>;
} // namespace rocprofsys
@@ -0,0 +1,623 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/pthread_create_gotcha.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/locking.hpp"
#include "core/state.hpp"
#include "core/utility.hpp"
#include "library/causal/delay.hpp"
#include "library/components/category_region.hpp"
#include "library/components/roctracer.hpp"
#include "library/runtime.hpp"
#include "library/sampling.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include "library/tracing.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/components/timing/wall_clock.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/sampling/allocator.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/types.hpp>
#include <csignal>
#include <ostream>
#include <pthread.h>
#include <utility>
namespace rocprofsys
{
namespace sampling
{
std::set<int>
setup();
std::set<int>
shutdown();
} // namespace sampling
namespace component
{
using bundle_t = tim::lightweight_tuple<comp::wall_clock, comp::roctracer_data>;
using category_region_t = tim::lightweight_tuple<category_region<category::pthread>>;
namespace
{
auto* is_shutdown = new bool{ false }; // intentional data leak
auto* bundles = new std::map<int64_t, std::shared_ptr<bundle_t>>{};
auto* bundles_mutex = new std::mutex{};
auto bundles_dtor = scope::destructor{ []() {
pthread_create_gotcha::shutdown();
delete bundles;
delete bundles_mutex;
bundles = nullptr;
bundles_mutex = nullptr;
} };
template <typename... Args>
inline void
start_bundle(bundle_t& _bundle, int64_t _tid, Args&&... _args)
{
if(!get_use_timemory() && !get_use_perfetto()) return;
trait::runtime_enabled<comp::roctracer_data>::set(get_use_roctracer());
ROCPROFSYS_BASIC_VERBOSE_F(3, "starting bundle '%s' in thread %li...\n",
_bundle.key().c_str(), _tid);
if constexpr(sizeof...(Args) > 0)
{
const char* _name = nullptr;
if(tim::get_hash_identifier(_bundle.hash(), _name) && _name != nullptr)
{
category_region_t{}.audit(quirk::config<quirk::perfetto>{},
std::string_view{ _name }, _args...);
}
}
else
{
tim::consume_parameters(_args...);
}
if(get_use_timemory())
{
_bundle.push(_tid);
_bundle.start();
}
}
template <typename... Args>
inline void
stop_bundle(bundle_t& _bundle, int64_t _tid, Args&&... _args)
{
if(!get_use_timemory() && !get_use_perfetto()) return;
auto _main_manager = tim::manager::master_instance();
auto _this_manager = tim::manager::instance();
if(!_main_manager || !_this_manager || _main_manager->is_finalized() ||
_this_manager->is_finalized())
return;
ROCPROFSYS_BASIC_VERBOSE_F(3, "stopping bundle '%s' in thread %li...\n",
_bundle.key().c_str(), _tid);
if(get_use_timemory())
{
auto _wc = *_bundle.get<comp::wall_clock>();
_wc.stop();
// update roctracer_data
_bundle.store(std::plus<double>{}, _wc.get() * _wc.unit());
// stop all
_bundle.stop();
// exclude popping wall-clock
_bundle.pop(_tid);
}
if constexpr(sizeof...(Args) > 0)
{
const char* _name = nullptr;
if(tim::get_hash_identifier(_bundle.hash(), _name) && _name != nullptr)
{
category_region_t{}.audit(quirk::config<quirk::perfetto>{},
std::string_view{ _name }, _args...);
}
}
else
{
tim::consume_parameters(_args...);
}
}
using native_handle_set_t = std::set<pthread_create_gotcha::native_handle_t>;
auto native_handles = native_handle_set_t{};
auto internal_native_handles = native_handle_set_t{};
auto native_handles_mutex = locking::atomic_mutex{};
} // namespace
//--------------------------------------------------------------------------------------//
pthread_create_gotcha::wrapper::wrapper(routine_t _routine, void* _arg,
wrapper_config _config)
: m_routine{ _routine }
, m_arg{ _arg }
, m_config{ std::move(_config) }
{}
void*
pthread_create_gotcha::wrapper::operator()() const
{
using thread_bundle_data_t = thread_data<thread_bundle_t>;
if(is_shutdown && *is_shutdown)
{
if(m_config.promise) m_config.promise->set_value();
// execute the original function
return m_routine(m_arg);
}
push_thread_state(ThreadState::Internal);
int64_t _tid = -1;
void* _ret = nullptr;
auto _is_sampling = false;
auto _bundle = std::shared_ptr<bundle_t>{};
auto _signals = std::set<int>{};
auto _coverage = (get_mode() == Mode::Coverage);
const auto& _parent_info = thread_info::get(m_config.parent_tid, InternalTID);
const auto& _info = thread_info::init(m_config.offset);
auto _dtor = [&]() {
set_thread_state(ThreadState::Internal);
if(_is_sampling)
{
if(m_config.enable_causal)
{
causal::sampling::block_signals(_signals);
causal::sampling::shutdown();
}
else if(m_config.enable_sampling)
{
sampling::block_signals(_signals);
sampling::shutdown();
}
}
if(_tid >= 0)
{
auto _active = (get_state() == ::rocprofsys::State::Active &&
bundles != nullptr && bundles_mutex != nullptr);
if(!_active) return;
thread_info::set_stop(comp::wall_clock::record());
auto& _thr_bundle = thread_bundle_data_t::instance();
if(_thr_bundle && _thr_bundle->get<comp::wall_clock>() &&
_thr_bundle->get<comp::wall_clock>()->get_is_running())
_thr_bundle->stop();
if(_bundle) stop_bundle(*_bundle, _tid);
pthread_create_gotcha::shutdown(_tid);
ROCPROFSYS_BASIC_VERBOSE(
1, "[PID=%i][rank=%i] Thread %s (parent: %s) exited\n", process::get_id(),
dmp::rank(), _info->index_data->as_string().c_str(),
_parent_info->index_data->as_string().c_str());
}
};
auto _active = (get_state() == ::rocprofsys::State::Active && bundles != nullptr &&
bundles_mutex != nullptr);
if(m_config.offset)
{
auto _lk = locking::atomic_lock{ native_handles_mutex };
internal_native_handles.emplace(pthread_self());
}
if(_active && !_coverage && !m_config.offset)
{
_tid = _info->index_data->sequent_value;
ROCPROFSYS_BASIC_VERBOSE(1, "[PID=%i][rank=%i] Thread %s (parent: %s) created\n",
process::get_id(), dmp::rank(),
_info->index_data->as_string().c_str(),
_parent_info->index_data->as_string().c_str());
threading::set_thread_name(TIMEMORY_JOIN(" ", "Thread", _tid).c_str());
auto _manager = tim::manager::instance();
if(_manager) _manager->initialize();
if(!thread_bundle_data_t::get()->at(_tid))
{
thread_data<thread_bundle_t>::construct(
TIMEMORY_JOIN('/', "rocprofsys/process", process::get_id(), "thread",
_tid),
quirk::config<quirk::auto_start>{});
thread_bundle_data_t::get()->at(_tid)->start();
}
if(bundles && bundles_mutex)
{
std::unique_lock<std::mutex> _lk{ *bundles_mutex };
_bundle = bundles->emplace(_tid, std::make_shared<bundle_t>("start_thread"))
.first->second;
}
if(_bundle) start_bundle(*_bundle, _tid);
get_cpu_cid_stack(_tid, m_config.parent_tid);
if(m_config.enable_causal)
{
// children inherit the parent delay data
if(_parent_info && _parent_info->index_data)
causal::delay::get_local(_tid) =
causal::delay::get_local(_parent_info->index_data->sequent_value);
_is_sampling = true;
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
_signals = causal::sampling::setup();
causal::sampling::unblock_signals();
}
else if(m_config.enable_sampling)
{
_is_sampling = true;
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
_signals = sampling::setup();
sampling::unblock_signals();
}
}
else if(m_config.offset)
{
ROCPROFSYS_BASIC_VERBOSE(
2,
"[PID=%i][rank=%i] Thread %s (parent: %s) created [started by rocprof-sys]\n",
process::get_id(), dmp::rank(), _info->index_data->as_string().c_str(),
_parent_info->index_data->as_string().c_str());
}
// notify the wrapper that all internal work is completed
if(m_config.promise) m_config.promise->set_value();
// Internal -> Enabled
pop_thread_state();
push_thread_state(ThreadState::Enabled);
// execute the original function
_ret = m_routine(m_arg);
if(get_state() < ::rocprofsys::State::Finalized)
{
pop_thread_state();
// execute the destructor actions
_dtor();
set_thread_state(ThreadState::Completed);
}
return _ret;
}
void*
pthread_create_gotcha::wrapper::wrap(void* _arg)
{
if(_arg == nullptr) return nullptr;
auto _self = pthread_self();
// convert the argument
wrapper* _wrapper = static_cast<wrapper*>(_arg);
// store the handle
{
auto _lk = locking::atomic_lock{ native_handles_mutex };
native_handles.emplace(_self);
}
static thread_local auto _remover = scope::destructor{ []() {
if(get_state() >= rocprofsys::State::Finalized) return;
// remove the handle even if original function aborts
auto _lk = locking::atomic_lock{ native_handles_mutex };
native_handles.erase(pthread_self());
} };
(void) _remover;
// execute the original function
void* _ret = (*_wrapper)();
// remove the handle
if(::pthread_equal(_self, pthread_self()) == 0)
{
auto _lk = locking::atomic_lock{ native_handles_mutex };
native_handles.erase(_self);
}
// eliminate memory leak
if(_ret != _arg) delete _wrapper;
return _ret;
}
namespace
{
const auto shutdown_signal_v = SIGRTMAX - 1;
size_t shutdown_signals_delivered = 0;
void
pthread_create_gotcha_shutdown_handler(int)
{
pthread_create_gotcha::shutdown(threading::get_id());
++shutdown_signals_delivered;
}
} // namespace
void
pthread_create_gotcha::configure()
{
pthread_create_gotcha_t::get_initializer() = []() {
if(!tim::settings::enabled()) return;
pthread_create_gotcha_t::template configure<
0, int, pthread_t*, const pthread_attr_t*, void* (*) (void*), void*>(
"pthread_create");
};
tim::hash::add_hash_id("start_thread");
}
void
pthread_create_gotcha::shutdown()
{
if(is_shutdown)
{
if(*is_shutdown) return;
*is_shutdown = true;
}
if(!bundles_mutex || !bundles) return;
unsigned long _ndangling = 0;
for(const auto& itr : *bundles)
{
if(itr.second) ++_ndangling;
}
tracing::copy_timemory_hash_ids();
// enable the signal handler for when the timeout is reached
struct sigaction _action = {};
struct sigaction _former = {};
memset(&_action, 0, sizeof(_action));
memset(&_former, 0, sizeof(_former));
sigemptyset(&_action.sa_mask);
sigemptyset(&_former.sa_mask);
_action.sa_flags = SA_RESTART;
_action.sa_handler = pthread_create_gotcha_shutdown_handler;
// activate signal handler
sigaction(shutdown_signal_v, &_action, &_former);
size_t _expected_shutdown_signals_delivered = 0;
{
auto _lk = locking::atomic_lock{ native_handles_mutex };
for(auto itr : native_handles)
{
// skip sending signals to internal threads
if(internal_native_handles.count(itr) != 0) continue;
if(pthread_equal(pthread_self(), itr) == 0 && pthread_equal(itr, itr) != 0)
{
::pthread_kill(itr, shutdown_signal_v);
++_expected_shutdown_signals_delivered;
}
}
auto _nattempt = 0U;
constexpr auto nmax_attempt = 20U;
while(shutdown_signals_delivered < _expected_shutdown_signals_delivered &&
_nattempt++ < nmax_attempt)
{
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds{ 50 });
}
ROCPROFSYS_CI_BASIC_FAIL(
shutdown_signals_delivered != _expected_shutdown_signals_delivered,
"Number of signals delivered (%zu) != expected number of signals delievered "
"(%zu)",
shutdown_signals_delivered, _expected_shutdown_signals_delivered);
}
// restore existing signal handler
sigaction(shutdown_signal_v, &_former, nullptr);
// subtract the bundles that had signals delivered
_ndangling -= shutdown_signals_delivered;
// stop any remaining dangling bundles on this thread
std::unique_lock<std::mutex> _lk{ *bundles_mutex };
for(auto itr : *bundles)
{
if(itr.second)
{
stop_bundle(*itr.second, itr.first);
++_ndangling;
}
itr.second.reset();
}
bundles->clear();
if(config::settings_are_configured())
{
ROCPROFSYS_VERBOSE(2 && _ndangling > 0,
"[pthread_create_gotcha] cleaned up %lu dangling bundles\n",
_ndangling);
}
else
{
ROCPROFSYS_BASIC_VERBOSE(
2 && _ndangling > 0,
"[pthread_create_gotcha] cleaned up %lu dangling bundles\n", _ndangling);
}
}
void
pthread_create_gotcha::shutdown(int64_t _tid)
{
if(_tid == 0) shutdown();
if(is_shutdown && *is_shutdown) return;
if(!bundles_mutex || !bundles) return;
std::unique_lock<std::mutex> _lk{ *bundles_mutex };
auto itr = bundles->find(_tid);
if(itr != bundles->end())
{
if(itr->second) stop_bundle(*itr->second, itr->first);
itr->second.reset();
bundles->erase(itr);
}
}
void
pthread_create_gotcha::set_data(wrappee_t _v)
{
m_wrappee = _v;
}
std::set<pthread_create_gotcha::native_handle_t>
pthread_create_gotcha::get_native_handles()
{
auto _v = native_handles;
return _v;
}
// pthread_create
int
pthread_create_gotcha::operator()(pthread_t* thread, const pthread_attr_t* attr,
void* (*func)(void*), void* arg) const
{
auto _tid = utility::get_thread_index();
auto _thr_state = get_thread_state();
auto _glob_state = get_state();
auto _mode = get_mode();
auto _disabled = (_thr_state == ThreadState::Disabled);
auto _enabled = (_thr_state == ThreadState::Enabled);
auto _bundle = std::optional<bundle_t>{};
auto _sample_child = sampling_enabled_on_child_threads();
auto _active = (_glob_state == ::rocprofsys::State::Active && !_disabled);
const auto& _info = thread_info::init(!_active || !_sample_child || _disabled);
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
auto _coverage = (_mode == Mode::Coverage);
auto _use_sampling = config::get_use_sampling();
auto _use_causal = config::get_use_causal();
auto _offset = (!_enabled || !_active || _info->is_offset);
auto _use_bundle = (_active && !_coverage && !_offset);
auto _enable_sampling =
(_use_sampling && _sample_child && _active && !_coverage && !_offset);
auto _enable_causal =
(_use_causal && _sample_child && _active && !_coverage && !_offset);
static bool debug_threading_get_id =
get_env<bool>(TIMEMORY_SETTINGS_PREFIX "DEBUG_THREADING_GET_ID", false);
auto _verbose = (debug_threading_get_id) ? 0 : 3;
ROCPROFSYS_VERBOSE(
_verbose,
"Creating new thread :: global_state=%s, thread_state=%s, mode=%s, active=%s, "
"coverage=%s, use_causal=%s, use_sampling=%s, sample_children=%s, tid=%li, "
"use_bundle=%s, enable_causal=%s, enable_sampling=%s, thread_info=(%s)...\n",
std::to_string(_glob_state).c_str(), std::to_string(_thr_state).c_str(),
std::to_string(_mode).c_str(), std::to_string(_active).c_str(),
std::to_string(_coverage).c_str(), std::to_string(_use_causal).c_str(),
std::to_string(_use_sampling).c_str(), std::to_string(_sample_child).c_str(),
_tid, std::to_string(_use_bundle).c_str(), std::to_string(_enable_causal).c_str(),
std::to_string(_enable_sampling).c_str(), JOIN("", *_info).c_str());
if(debug_threading_get_id)
{
timemory_print_demangled_backtrace<8>(std::cerr, std::string{},
std::string{ "threading::get_id() [id=" } +
std::to_string(_tid) +
std::string{ "]" },
std::string{ " " }, false);
}
if(_active && !_disabled && !_info->is_offset)
{
ROCPROFSYS_BASIC_VERBOSE(2, "[PID=%i][rank=%i] Starting new thread on %s...\n",
process::get_id(), dmp::rank(),
_info->index_data->as_string().c_str());
}
// ensure that cpu cid stack exists on the parent thread if active
if(_active && !_coverage)
{
ROCPROFSYS_DEBUG("blocking signals...\n");
get_cpu_cid_stack();
}
set_thread_state(ThreadState::Disabled);
auto _blocked = get_sampling_signals();
auto _promise = (_active) ? std::make_shared<std::promise<void>>() : promise_t{};
auto _config =
wrapper_config{ _enable_causal, _enable_sampling, _offset, _tid, _promise };
auto* _wrap = new wrapper{ func, arg, _config };
set_thread_state(ThreadState::Internal);
// block the signals in entire process
if(_enable_sampling && !_blocked.empty())
{
ROCPROFSYS_DEBUG("blocking signals...\n");
tim::signals::block_signals(_blocked, tim::signals::sigmask_scope::process);
}
if(_use_bundle)
{
_bundle = bundle_t{ "pthread_create" };
start_bundle(*_bundle, _info->index_data->sequent_value, audit::incoming{},
thread, attr, func, arg);
}
// threads must process their delays before creating a new thread
causal::delay::process();
// create the thread
auto _ret = (*m_wrappee)(thread, attr, &wrapper::wrap, static_cast<void*>(_wrap));
// wait for thread to set promise
if(_promise)
{
ROCPROFSYS_DEBUG("waiting for child to signal it is setup...\n");
_promise->get_future().wait_for(std::chrono::milliseconds{ 500 });
}
if(_use_bundle)
stop_bundle(*_bundle, _info->index_data->sequent_value, audit::outgoing{}, _ret);
// unblock the signals in the entire process
if(_enable_sampling && !_blocked.empty())
{
ROCPROFSYS_DEBUG("unblocking signals...\n");
tim::signals::unblock_signals(_blocked, tim::signals::sigmask_scope::process);
}
ROCPROFSYS_DEBUG("returning success...\n");
return _ret;
}
} // namespace component
} // namespace rocprofsys
TIMEMORY_INITIALIZE_STORAGE(component::roctracer_data)
@@ -0,0 +1,98 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/thread_data.hpp"
#include <cstdint>
#include <future>
namespace rocprofsys
{
struct pthread_gotcha;
namespace component
{
struct pthread_create_gotcha : tim::component::base<pthread_create_gotcha, void>
{
static constexpr size_t gotcha_capacity = 1;
using routine_t = void* (*) (void*);
using wrappee_t = int (*)(pthread_t*, const pthread_attr_t*, routine_t, void*);
using promise_t = std::shared_ptr<std::promise<void>>;
using native_handle_t = std::thread::native_handle_type;
struct wrapper_config
{
bool enable_causal = false;
bool enable_sampling = false;
bool offset = false;
int64_t parent_tid = 0;
promise_t promise = {};
};
struct wrapper
{
wrapper(routine_t _routine, void* _arg, wrapper_config _cfg);
void* operator()() const;
static void* wrap(void* _arg);
private:
routine_t m_routine = nullptr;
void* m_arg = nullptr;
wrapper_config m_config = {};
};
ROCPROFSYS_DEFAULT_OBJECT(pthread_create_gotcha)
// string id for component
static std::string label() { return "pthread_create_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
static void shutdown(int64_t);
// pthread_create
int operator()(pthread_t* thread, const pthread_attr_t* attr,
void* (*start_routine)(void*), void* arg) const;
void set_data(wrappee_t);
private:
friend struct ::rocprofsys::pthread_gotcha;
static std::set<native_handle_t> get_native_handles();
wrappee_t m_wrappee = &pthread_create;
};
using pthread_create_gotcha_t =
tim::component::gotcha<pthread_create_gotcha::gotcha_capacity, std::tuple<>,
pthread_create_gotcha>;
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,123 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/pthread_gotcha.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/utility.hpp"
#include "library/components/pthread_create_gotcha.hpp"
#include "library/components/pthread_mutex_gotcha.hpp"
#include "library/runtime.hpp"
#include "library/thread_data.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/utility/macros.hpp>
#include <timemory/utility/types.hpp>
#include <pthread.h>
#include <array>
#include <vector>
namespace tim
{
namespace operation
{
template <>
struct stop<rocprofsys::component::pthread_create_gotcha_t>
{
using type = rocprofsys::component::pthread_create_gotcha_t;
ROCPROFSYS_DEFAULT_OBJECT(stop)
template <typename... Args>
explicit stop(type&, Args&&...)
{}
template <typename... Args>
void operator()(type&, Args&&...)
{}
};
} // namespace operation
} // namespace tim
namespace rocprofsys
{
namespace
{
using bundle_t = tim::lightweight_tuple<component::pthread_create_gotcha_t,
component::pthread_mutex_gotcha_t>;
auto&
get_bundle()
{
static auto _v = std::unique_ptr<bundle_t>{};
if(!_v) _v = std::make_unique<bundle_t>("pthread_gotcha");
return _v;
}
bool is_configured = false;
} // namespace
//--------------------------------------------------------------------------------------//
void
pthread_gotcha::configure()
{
if(!is_configured)
{
::rocprofsys::component::pthread_create_gotcha::configure();
::rocprofsys::component::pthread_mutex_gotcha::configure();
is_configured = true;
}
}
void
pthread_gotcha::shutdown()
{
if(is_configured)
{
::rocprofsys::component::pthread_mutex_gotcha::shutdown();
::rocprofsys::component::pthread_create_gotcha::shutdown();
is_configured = false;
}
}
void
pthread_gotcha::start()
{
configure();
get_bundle()->start();
}
void
pthread_gotcha::stop()
{
get_bundle()->stop();
}
std::set<pthread_gotcha::native_handle_t>
pthread_gotcha::get_native_handles()
{
return ::rocprofsys::component::pthread_create_gotcha::get_native_handles();
}
} // namespace rocprofsys
@@ -0,0 +1,53 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/thread_info.hpp"
#include <cstdint>
#include <future>
namespace rocprofsys
{
struct pthread_gotcha : tim::component::base<pthread_gotcha, void>
{
using native_handle_t = std::thread::native_handle_type;
ROCPROFSYS_DEFAULT_OBJECT(pthread_gotcha)
// string id for component
static std::string label() { return "pthread_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
static void start();
static void stop();
static std::set<native_handle_t> get_native_handles();
};
} // namespace rocprofsys
@@ -0,0 +1,277 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/pthread_mutex_gotcha.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/utility.hpp"
#include "library/components/category_region.hpp"
#include "library/runtime.hpp"
#include "library/thread_info.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/utility/signals.hpp>
#include <timemory/utility/types.hpp>
#include <cstdint>
#include <pthread.h>
#include <stdexcept>
namespace rocprofsys
{
namespace component
{
pthread_mutex_gotcha::hash_array_t&
pthread_mutex_gotcha::get_hashes()
{
// theoretically, this private function will NEVER be called until it
// is called by a gotcha wrapper, which means the tool ids should be
// fully populated. If that fails to be the case for some reason,
// we could see weird results.
static auto _v = []() {
const auto& _data = pthread_mutex_gotcha_t::get_gotcha_data();
auto _init = hash_array_t{};
auto _skip = std::set<size_t>{};
if(!config::get_trace_thread_locks())
{
for(size_t i = 0; i < 3; ++i)
_skip.emplace(i);
}
if(!config::get_trace_thread_rwlocks())
{
for(size_t i = 3; i < 8; ++i)
_skip.emplace(i);
}
if(!config::get_trace_thread_spin_locks())
{
for(size_t i = 9; i < 12; ++i)
_skip.emplace(i);
}
if(!config::get_trace_thread_barriers()) _skip.emplace(8);
if(!config::get_trace_thread_join()) _skip.emplace(12);
for(size_t i = 0; i < gotcha_capacity; ++i)
{
auto&& _id = _data.at(i).tool_id;
if(!_id.empty())
_init.at(i) = tim::add_hash_id(_id.c_str());
else
{
if(_skip.count(i) > 0) continue;
ROCPROFSYS_VERBOSE(
1,
"WARNING!!! pthread_mutex_gotcha tool id at index %zu was empty!\n",
i);
}
ROCPROFSYS_CI_FAIL(
_id.empty() || _init.at(i) == 0,
"pthread_mutex_gotcha tool id at index %zu has no hash value\n", i);
}
return _init;
}();
return _v;
}
void
pthread_mutex_gotcha::configure()
{
pthread_mutex_gotcha_t::get_initializer() = []() {
if(!tim::settings::enabled() || get_use_causal()) return;
if(config::get_trace_thread_locks())
{
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<0, int, pthread_mutex_t*>{ "pthread_mutex_lock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<1, int, pthread_mutex_t*>{ "pthread_mutex_unlock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<2, int, pthread_mutex_t*>{ "pthread_mutex_trylock" });
}
if(config::get_trace_thread_rwlocks())
{
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<3, int, pthread_rwlock_t*>{
"pthread_rwlock_rdlock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<4, int, pthread_rwlock_t*>{
"pthread_rwlock_wrlock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<5, int, pthread_rwlock_t*>{
"pthread_rwlock_tryrdlock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<6, int, pthread_rwlock_t*>{
"pthread_rwlock_trywrlock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<7, int, pthread_rwlock_t*>{
"pthread_rwlock_unlock" });
}
if(config::get_trace_thread_barriers())
{
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<8, int, pthread_barrier_t*>{
"pthread_barrier_wait" });
}
if(config::get_trace_thread_spin_locks())
{
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<9, int, pthread_spinlock_t*>{ "pthread_spin_lock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<10, int, pthread_spinlock_t*>{
"pthread_spin_trylock" });
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<11, int, pthread_spinlock_t*>{
"pthread_spin_unlock" });
}
if(config::get_trace_thread_join())
{
pthread_mutex_gotcha_t::configure(
comp::gotcha_config<12, int, pthread_t, void**>{ "pthread_join" });
}
};
}
void
pthread_mutex_gotcha::shutdown()
{
pthread_mutex_gotcha_t::disable();
}
pthread_mutex_gotcha::pthread_mutex_gotcha(const gotcha_data_t& _data)
: m_data{ &_data }
{}
template <typename... Args>
auto
pthread_mutex_gotcha::operator()(uintptr_t&&, int (*_callee)(Args...),
Args... _args) const
{
using bundle_t = category_region<category::pthread>;
if(is_disabled() || m_protect)
{
if(!_callee)
{
if(m_data)
{
ROCPROFSYS_PRINT("Warning! nullptr to %s\n", m_data->tool_id.c_str());
}
return EINVAL;
}
return (*_callee)(_args...);
}
struct local_dtor
{
explicit local_dtor(bool& _v)
: _protect{ _v }
{}
~local_dtor() { _protect = false; }
bool& _protect;
} _dtor{ m_protect = true };
bundle_t::audit(std::string_view{ m_data->tool_id }, audit::incoming{}, _args...);
auto _ret = (*_callee)(_args...);
bundle_t::audit(std::string_view{ m_data->tool_id }, audit::outgoing{}, _ret);
return _ret;
}
int
pthread_mutex_gotcha::operator()(int (*_callee)(pthread_mutex_t*),
pthread_mutex_t* _mutex) const
{
if(get_state() != ::rocprofsys::State::Active || m_protect) return (*_callee)(_mutex);
return (*this)(reinterpret_cast<uintptr_t>(_mutex), _callee, _mutex);
}
int
pthread_mutex_gotcha::operator()(int (*_callee)(pthread_spinlock_t*),
pthread_spinlock_t* _lock) const
{
if(get_state() != ::rocprofsys::State::Active || m_protect) return (*_callee)(_lock);
return (*this)(reinterpret_cast<uintptr_t>(_lock), _callee, _lock);
}
int
pthread_mutex_gotcha::operator()(int (*_callee)(pthread_rwlock_t*),
pthread_rwlock_t* _lock) const
{
if(get_state() != ::rocprofsys::State::Active || m_protect) return (*_callee)(_lock);
return (*this)(reinterpret_cast<uintptr_t>(_lock), _callee, _lock);
}
int
pthread_mutex_gotcha::operator()(int (*_callee)(pthread_barrier_t*),
pthread_barrier_t* _barrier) const
{
if(get_state() != ::rocprofsys::State::Active || m_protect)
return (*_callee)(_barrier);
return (*this)(reinterpret_cast<uintptr_t>(_barrier), _callee, _barrier);
}
int
pthread_mutex_gotcha::operator()(int (*_callee)(pthread_t, void**), pthread_t _thr,
void** _tinfo) const
{
if(get_state() != ::rocprofsys::State::Active || m_protect)
return (*_callee)(_thr, _tinfo);
return (*this)(static_cast<uintptr_t>(threading::get_id()), _callee, _thr, _tinfo);
}
bool
pthread_mutex_gotcha::is_disabled()
{
static thread_local const auto& _info = thread_info::get();
return (!_info || _info->is_offset || get_state() != ::rocprofsys::State::Active ||
get_thread_state() != ThreadState::Enabled);
}
} // namespace component
} // namespace rocprofsys
namespace tim
{
namespace policy
{
template <size_t N>
pthread_mutex_gotcha&
static_data<pthread_mutex_gotcha, pthread_mutex_gotcha_t>::operator()(
std::integral_constant<size_t, N>, const component::gotcha_data& _data) const
{
using thread_data_t =
rocprofsys::thread_data<pthread_mutex_gotcha, std::integral_constant<size_t, N>>;
static thread_local auto& _v =
thread_data_t::instance(rocprofsys::construct_on_thread{}, _data);
return *_v;
}
} // namespace policy
} // namespace tim
@@ -0,0 +1,100 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include <timemory/components/gotcha/backends.hpp>
#include <timemory/mpl/macros.hpp>
#include <array>
#include <cstddef>
#include <string>
namespace rocprofsys
{
namespace component
{
// this is used to wrap pthread_mutex()
struct pthread_mutex_gotcha : comp::base<pthread_mutex_gotcha, void>
{
static constexpr size_t gotcha_capacity = 13;
using hash_array_t = std::array<size_t, gotcha_capacity>;
using gotcha_data_t = comp::gotcha_data;
ROCPROFSYS_DEFAULT_OBJECT(pthread_mutex_gotcha)
explicit pthread_mutex_gotcha(const gotcha_data_t&);
// string id for component
static std::string label() { return "pthread_mutex_gotcha"; }
// generate the gotcha wrappers
static void configure();
static void shutdown();
int operator()(int (*)(pthread_mutex_t*), pthread_mutex_t*) const;
int operator()(int (*)(pthread_spinlock_t*), pthread_spinlock_t*) const;
int operator()(int (*)(pthread_rwlock_t*), pthread_rwlock_t*) const;
int operator()(int (*)(pthread_barrier_t*), pthread_barrier_t*) const;
int operator()(int (*)(pthread_t, void**), pthread_t, void**) const;
private:
static bool is_disabled();
static hash_array_t& get_hashes();
template <typename... Args>
auto operator()(uintptr_t&&, int (*)(Args...), Args...) const;
mutable bool m_protect = false;
const gotcha_data_t* m_data = nullptr;
};
using pthread_mutex_gotcha_t = comp::gotcha<pthread_mutex_gotcha::gotcha_capacity,
std::tuple<>, pthread_mutex_gotcha>;
} // namespace component
} // namespace rocprofsys
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(fast_gotcha, component::pthread_mutex_gotcha_t,
true_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(static_data, component::pthread_mutex_gotcha_t,
true_type)
namespace tim
{
namespace policy
{
using pthread_mutex_gotcha = ::rocprofsys::component::pthread_mutex_gotcha;
using pthread_mutex_gotcha_t = ::rocprofsys::component::pthread_mutex_gotcha_t;
template <>
struct static_data<pthread_mutex_gotcha, pthread_mutex_gotcha_t> : std::true_type
{
template <size_t N>
pthread_mutex_gotcha& operator()(std::integral_constant<size_t, N>,
const component::gotcha_data& _data) const;
};
} // namespace policy
} // namespace tim
@@ -0,0 +1,195 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/rcclp.hpp"
#include "library/rcclp.hpp"
#include <timemory/manager.hpp>
std::ostream&
operator<<(std::ostream& _os, const ncclUniqueId& _v)
{
for(auto itr : _v.internal)
_os << itr;
return _os;
}
namespace rocprofsys
{
namespace component
{
uint64_t
activate_rcclp()
{
using handle_t = tim::component::rcclp_handle;
static auto _handle = std::shared_ptr<handle_t>{};
if(!_handle.get())
{
_handle = std::make_shared<handle_t>();
_handle->start();
auto cleanup_functor = [=]() {
if(_handle)
{
_handle->stop();
_handle.reset();
}
};
std::stringstream ss;
ss << "timemory-rcclp-" << demangle<rccl_toolset_t>() << "-"
<< demangle<category::rocm_rccl>();
tim::manager::instance()->add_cleanup(ss.str(), cleanup_functor);
return 1;
}
return 0;
}
//
//======================================================================================//
//
uint64_t
deactivate_rcclp(uint64_t id)
{
if(id > 0)
{
std::stringstream ss;
ss << "timemory-rcclp-" << demangle<rccl_toolset_t>() << "-"
<< demangle<category::rocm_rccl>();
tim::manager::instance()->cleanup(ss.str());
return 0;
}
return 1;
}
//
//======================================================================================//
//
void
configure_rcclp(const std::set<std::string>& permit, const std::set<std::string>& reject)
{
static bool is_initialized = false;
if(!is_initialized)
{
// generate the gotcha wrappers
rcclp_gotcha_t::get_initializer() = []() {
// TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 0, ncclGetVersion);
// TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 1, ncclGetUniqueId);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 2, ncclCommInitRank);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 3, ncclCommInitAll);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 4, ncclCommDestroy);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 5, ncclCommCount);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 6, ncclCommCuDevice);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 7, ncclCommUserRank);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 8, ncclReduce);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 9, ncclBcast);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 10, ncclBroadcast);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 11, ncclAllReduce);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 12, ncclReduceScatter);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 13, ncclAllGather);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 14, ncclGroupStart);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 15, ncclGroupEnd);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 16, ncclSend);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 17, ncclRecv);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 18, ncclGather);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 19, ncclScatter);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 20, ncclAllToAll);
TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 21, ncclAllToAllv);
// TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 22, ncclRedOpCreatePreMulSum);
// TIMEMORY_C_GOTCHA(rcclp_gotcha_t, 23, ncclRedOpDestroy);
};
// provide environment variable for suppressing wrappers
rcclp_gotcha_t::get_reject_list() = [reject]() {
auto _reject = reject;
// check environment
auto reject_list =
tim::get_env<std::string>("ROCPROFSYS_RCCLP_REJECT_LIST", "");
// add environment setting
for(const auto& itr : tim::delimit(reject_list))
_reject.insert(itr);
return _reject;
};
// provide environment variable for selecting wrappers
rcclp_gotcha_t::get_permit_list() = [permit]() {
auto _permit = permit;
// check environment
auto permit_list =
tim::get_env<std::string>("ROCPROFSYS_RCCLP_PERMIT_LIST", "");
// add environment setting
for(const auto& itr : tim::delimit(permit_list))
_permit.insert(itr);
return _permit;
};
is_initialized = true;
}
}
void
rcclp_handle::start()
{
if(get_tool_count()++ == 0)
{
get_tool_instance() = std::make_shared<rcclp_tuple_t>("timemory_rcclp");
get_tool_instance()->start();
}
}
void
rcclp_handle::stop()
{
auto idx = --get_tool_count();
if(get_tool_instance().get())
{
get_tool_instance()->stop();
if(idx == 0) get_tool_instance().reset();
}
}
rcclp_handle::persistent_data&
rcclp_handle::get_persistent_data()
{
static persistent_data _instance;
return _instance;
}
std::atomic<short>&
rcclp_handle::get_configured()
{
return get_persistent_data().m_configured;
}
rcclp_handle::toolset_ptr_t&
rcclp_handle::get_tool_instance()
{
return get_persistent_data().m_tool;
}
std::atomic<int64_t>&
rcclp_handle::get_tool_count()
{
return get_persistent_data().m_count;
}
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,106 @@
// MIT License
//
// Copyright (c) 2020, The Regents of the University of California,
// through Lawrence Berkeley National Laboratory (subject to receipt of any
// required approvals from the U.S. Dept. of Energy). 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/rccl.hpp"
#include "core/timemory.hpp"
#include "library/components/category_region.hpp"
#include "library/components/comm_data.hpp"
#include <timemory/api/macros.hpp>
#include <timemory/components/macros.hpp>
#include <atomic>
#include <functional>
#include <memory>
#include <set>
#include <string>
#include <utility>
#if !defined(ROCPROFSYS_NUM_RCCLP_WRAPPERS)
# define ROCPROFSYS_NUM_RCCLP_WRAPPERS 25
#endif
ROCPROFSYS_COMPONENT_ALIAS(
rccl_toolset_t,
::tim::component_bundle<category::rocm_rccl,
rocprofsys::component::category_region<category::rocm_rccl>,
comm_data>)
ROCPROFSYS_COMPONENT_ALIAS(rcclp_gotcha_t,
::tim::component::gotcha<ROCPROFSYS_NUM_RCCLP_WRAPPERS,
rccl_toolset_t, category::rocm_rccl>)
#if !defined(ROCPROFSYS_USE_RCCL)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(is_available, component::rcclp_gotcha_t, false_type)
#endif
namespace rocprofsys
{
namespace component
{
uint64_t
activate_rcclp();
uint64_t
deactivate_rcclp(uint64_t id);
void
configure_rcclp(const std::set<std::string>& permit = {},
const std::set<std::string>& reject = {});
struct rcclp_handle : base<rcclp_handle, void>
{
static constexpr size_t rcclp_wrapper_count = ROCPROFSYS_NUM_RCCLP_WRAPPERS;
using value_type = void;
using this_type = rcclp_handle;
using base_type = base<this_type, value_type>;
using rcclp_tuple_t = tim::component_tuple<rcclp_gotcha_t>;
using toolset_ptr_t = std::shared_ptr<rcclp_tuple_t>;
static std::string label() { return "rcclp_handle"; }
static std::string description() { return "Handle for activating NCCL wrappers"; }
static void get() {}
static void start();
static void stop();
static int get_count() { return get_tool_count().load(); }
private:
struct persistent_data
{
std::atomic<short> m_configured{ 0 };
std::atomic<int64_t> m_count{ 0 };
toolset_ptr_t m_tool = toolset_ptr_t{};
};
static persistent_data& get_persistent_data();
static std::atomic<short>& get_configured();
static toolset_ptr_t& get_tool_instance();
static std::atomic<int64_t>& get_tool_count();
};
} // namespace component
} // namespace rocprofsys
@@ -0,0 +1,193 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/rocprofiler.hpp"
#include "core/common.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/dynamic_library.hpp"
#include "core/perfetto.hpp"
#include "core/redirect.hpp"
#include "library/rocprofiler.hpp"
#include "library/sampling.hpp"
#include "library/thread_data.hpp"
#include <timemory/storage/types.hpp>
#include <timemory/utility/types.hpp>
#include <timemory/variadic/functional.hpp>
#include <timemory/variadic/lightweight_tuple.hpp>
#include <rocprofiler.h>
#include <cstdint>
#include <string_view>
#include <type_traits>
namespace rocprofsys
{
namespace component
{
namespace
{
auto&
rocprofiler_activity_count()
{
static std::atomic<int64_t> _v{ 0 };
return _v;
}
} // namespace
unique_ptr_t<rocm_data_t>&
rocm_data(int64_t _tid)
{
using thread_data_t = thread_data<rocm_data_t, rocm_event>;
return thread_data_t::instance(construct_on_thread{ _tid });
}
rocm_event::rocm_event(uint32_t _dev, uint32_t _thr, uint32_t _queue,
std::string _event_name, rocm_metric_type _begin,
rocm_metric_type _end, uint32_t _feature_count, void* _features_v)
: device_id{ _dev }
, thread_id{ _thr }
, queue_id{ _queue }
, entry{ _begin }
, exit{ _end }
, name(std::move(_event_name))
{
feature_values.reserve(_feature_count);
feature_names.reserve(_feature_count);
auto* _features = static_cast<rocprofiler_feature_t*>(_features_v);
for(uint32_t i = 0; i < _feature_count; ++i)
{
const rocprofiler_feature_t* p = &_features[i];
feature_names.emplace_back(i);
switch(p->data.kind)
{
// Output metrics results
case ROCPROFILER_DATA_KIND_UNINIT: break;
case ROCPROFILER_DATA_KIND_BYTES:
feature_values.emplace_back(
rocm_feature_value{ p->data.result_bytes.size });
break;
case ROCPROFILER_DATA_KIND_INT32:
feature_values.emplace_back(rocm_feature_value{ p->data.result_int32 });
break;
case ROCPROFILER_DATA_KIND_FLOAT:
feature_values.emplace_back(rocm_feature_value{ p->data.result_float });
break;
case ROCPROFILER_DATA_KIND_DOUBLE:
feature_values.emplace_back(rocm_feature_value{ p->data.result_double });
break;
case ROCPROFILER_DATA_KIND_INT64:
feature_values.emplace_back(rocm_feature_value{ p->data.result_int64 });
break;
}
}
}
std::string
rocm_event::as_string() const
{
std::stringstream _ss{};
_ss << name << ", device: " << device_id << ", queue: " << queue_id
<< ", thread: " << thread_id << ", entry: " << entry << ", exit = " << exit;
_ss.precision(3);
_ss << std::fixed;
for(size_t i = 0; i < feature_names.size(); ++i)
{
auto _name = rocprofsys::rocprofiler::get_data_labels().at(device_id).at(
feature_names.at(i));
_ss << ", " << _name << " = ";
auto _as_string = [&_ss](auto&& itr) { _ss << std::setw(4) << itr; };
std::visit(_as_string, feature_values.at(i));
}
return _ss.str();
}
void
rocprofiler::preinit()
{
rocprofiler_data::label() = "rocprofiler";
rocprofiler_data::description() = "ROCm hardware counters";
}
void
rocprofiler::start()
{
if(tracker_type::start() == 0) setup();
}
void
rocprofiler::stop()
{
if(tracker_type::stop() == 0) shutdown();
}
bool
rocprofiler::is_setup()
{
return rocprofsys::rocprofiler::is_setup();
}
void
rocprofiler::add_setup(const std::string&, std::function<void()>&&)
{}
void
rocprofiler::add_shutdown(const std::string&, std::function<void()>&&)
{}
void
rocprofiler::remove_setup(const std::string&)
{}
void
rocprofiler::remove_shutdown(const std::string&)
{}
void
rocprofiler::setup()
{
ROCPROFSYS_VERBOSE_F(1, "rocprofiler is setup\n");
}
void
rocprofiler::shutdown()
{
rocprofsys::rocprofiler::post_process();
rocprofsys::rocprofiler::rocm_cleanup();
ROCPROFSYS_VERBOSE_F(1, "rocprofiler is shutdown\n");
}
scope::transient_destructor
rocprofiler::protect_flush_activity()
{
return scope::transient_destructor([]() { --rocprofiler_activity_count(); },
[]() { ++rocprofiler_activity_count(); });
}
} // namespace component
} // namespace rocprofsys
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(rocprofiler, false, void)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(rocprofiler_data, true,
tim::component::rocprofiler_value)
@@ -0,0 +1,241 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "library/thread_data.hpp"
#include <timemory/api.hpp>
#include <timemory/backends/hardware_counters.hpp>
#include <timemory/components/base.hpp>
#include <timemory/components/data_tracker/components.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/enum.h>
#include <timemory/macros.hpp>
#include <timemory/macros/os.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/macros.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/utility/transient_function.hpp>
#include <array>
#include <cstdint>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
namespace rocprofsys
{
namespace component
{
using rocm_metric_type = unsigned long long;
using rocm_info_entry = ::tim::hardware_counters::info;
using rocm_feature_value = std::variant<uint32_t, float, uint64_t, double>;
struct rocm_counter
{
std::array<rocm_metric_type, ROCPROFSYS_ROCM_MAX_COUNTERS> counters;
};
struct rocm_event
{
using value_type = rocm_feature_value;
uint32_t device_id = 0;
uint32_t thread_id = 0;
uint32_t queue_id = 0;
rocm_metric_type entry = 0;
rocm_metric_type exit = 0;
std::string name = {};
std::vector<size_t> feature_names = {};
std::vector<rocm_feature_value> feature_values = {};
rocm_event() = default;
rocm_event(uint32_t _dev, uint32_t _thr, uint32_t _queue, std::string _event_name,
rocm_metric_type begin, rocm_metric_type end, uint32_t _feature_count,
void* _features);
std::string as_string() const;
friend std::ostream& operator<<(std::ostream& _os, const rocm_event& _v)
{
return (_os << _v.as_string());
}
friend bool operator<(const rocm_event& _lhs, const rocm_event& _rhs)
{
return std::tie(_lhs.device_id, _lhs.queue_id, _lhs.entry, _lhs.thread_id) <
std::tie(_rhs.device_id, _rhs.queue_id, _rhs.entry, _rhs.thread_id);
}
};
using rocm_data_t = std::vector<rocm_event>;
using rocm_data_tracker = data_tracker<rocm_feature_value, rocm_event>;
rocprofsys::unique_ptr_t<rocm_data_t>&
rocm_data(int64_t _tid = threading::get_id());
using rocprofiler_value = typename rocm_event::value_type;
using rocprofiler_data = data_tracker<rocprofiler_value, rocprofiler>;
struct rocprofiler
: base<rocprofiler, void>
, private policy::instance_tracker<rocprofiler, false>
{
using value_type = void;
using base_type = base<rocprofiler, void>;
using tracker_type = policy::instance_tracker<rocprofiler, false>;
ROCPROFSYS_DEFAULT_OBJECT(rocprofiler)
static void preinit();
static void global_init() { setup(); }
static void global_finalize() { shutdown(); }
static bool is_setup();
static void setup();
static void shutdown();
static void add_setup(const std::string&, std::function<void()>&&);
static void add_shutdown(const std::string&, std::function<void()>&&);
static void remove_setup(const std::string&);
static void remove_shutdown(const std::string&);
void start();
void stop();
// this function protects rocprofiler_flush_activty from being called
// when rocprof-sys exits during a callback
[[nodiscard]] static scope::transient_destructor protect_flush_activity();
};
#if !defined(ROCPROFSYS_USE_ROCPROFILER)
inline void
rocprofiler::setup()
{}
inline void
rocprofiler::shutdown()
{}
inline bool
rocprofiler::is_setup()
{
return false;
}
#endif
} // namespace component
} // namespace rocprofsys
namespace tim
{
namespace component
{
using ::rocprofsys::component::rocm_data_tracker;
using ::rocprofsys::component::rocm_feature_value;
using ::rocprofsys::component::rocprofiler_data;
using ::rocprofsys::component::rocprofiler_value;
} // namespace component
} // namespace tim
namespace tim
{
namespace operation
{
template <>
struct set_storage<component::rocm_data_tracker>
{
using T = component::rocm_data_tracker;
static constexpr size_t max_threads = 4096;
using type = T;
using storage_array_t = std::array<storage<type>*, max_threads>;
friend struct get_storage<component::rocm_data_tracker>;
ROCPROFSYS_DEFAULT_OBJECT(set_storage)
auto operator()(storage<type>*, size_t) const {}
auto operator()(type&, size_t) const {}
auto operator()(storage<type>* _v) const { get().fill(_v); }
private:
static storage_array_t& get()
{
static storage_array_t _v = { nullptr };
return _v;
}
};
template <>
struct get_storage<component::rocm_data_tracker>
{
using type = component::rocm_data_tracker;
ROCPROFSYS_DEFAULT_OBJECT(get_storage)
auto operator()(const type&) const
{
return operation::set_storage<type>::get().at(0);
}
auto operator()() const
{
type _obj{};
return (*this)(_obj);
}
auto operator()(size_t _idx) const
{
return operation::set_storage<type>::get().at(_idx);
}
auto operator()(type&, size_t _idx) const { return (*this)(_idx); }
};
} // namespace operation
} // namespace tim
#if !defined(ROCPROFSYS_USE_ROCPROFILER)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(is_available, component::rocprofiler_data, false_type)
#endif
TIMEMORY_SET_COMPONENT_API(component::rocprofiler_data, project::timemory,
category::timing, os::supports_unix)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(is_timing_category, component::rocprofiler_data,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(uses_timing_units, component::rocprofiler_data,
false_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(report_units, component::rocprofiler_data, false_type)
TIMEMORY_STATISTICS_TYPE(component::rocprofiler_data, component::rocprofiler_value)
TIMEMORY_STATISTICS_TYPE(component::rocm_data_tracker, component::rocm_feature_value)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(report_units, component::rocm_data_tracker, false_type)
#if !defined(ROCPROFSYS_EXTERN_COMPONENTS) || \
(defined(ROCPROFSYS_EXTERN_COMPONENTS) && ROCPROFSYS_EXTERN_COMPONENTS > 0)
# include <timemory/operations.hpp>
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(rocprofiler, false, void)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(rocprofiler_data, true, double)
#endif
@@ -0,0 +1,396 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/components/roctracer.hpp"
#include "core/common.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/dynamic_library.hpp"
#include "core/redirect.hpp"
#include "library/roctracer.hpp"
#include "library/runtime.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include <chrono>
#include <roctracer.h>
#define HIP_PROF_HIP_API_STRING 1
#include <roctracer_ext.h>
#include <roctracer_hip.h>
#if ROCPROFSYS_HIP_VERSION < 50300
# include <roctracer_hcc.h>
#endif
#define AMD_INTERNAL_BUILD 1
#include <roctracer_hsa.h>
namespace rocprofsys
{
namespace component
{
namespace
{
auto&
roctracer_activity_count()
{
static std::atomic<int64_t> _v{ 0 };
return _v;
}
} // namespace
void
roctracer::preinit()
{
roctracer_data::label() = "roctracer";
roctracer_data::description() = "ROCm tracer (activity API)";
}
void
roctracer::start()
{
if(tracker_type::start() == 0) setup(nullptr);
}
void
roctracer::stop()
{
if(tracker_type::stop() == 0) shutdown();
}
bool
roctracer::is_setup()
{
return roctracer_is_setup();
}
void
roctracer::add_setup(const std::string& _lbl, std::function<void()>&& _func)
{
roctracer_setup_routines().emplace_back(_lbl, std::move(_func));
}
void
roctracer::add_shutdown(const std::string& _lbl, std::function<void()>&& _func)
{
roctracer_shutdown_routines().emplace_back(_lbl, std::move(_func));
}
void
roctracer::remove_setup(const std::string& _lbl)
{
auto& _data = roctracer_setup_routines();
for(auto itr = _data.begin(); itr != _data.end(); ++itr)
{
if(itr->first == _lbl)
{
_data.erase(itr);
break;
}
}
}
void
roctracer::remove_shutdown(const std::string& _lbl)
{
auto& _data = roctracer_setup_routines();
for(auto itr = _data.begin(); itr != _data.end(); ++itr)
{
if(itr->first == _lbl)
{
_data.erase(itr);
break;
}
}
}
void
roctracer::setup(void* table, bool on_load_trace)
{
if(!get_use_roctracer()) return;
auto_lock_t _lk{ type_mutex<roctracer>() };
if(roctracer_is_setup()) return;
roctracer_is_setup() = true;
ROCPROFSYS_VERBOSE_F(1, "setting up roctracer...\n");
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
dynamic_library _amdhip64{ "ROCPROFSYS_ROCTRACER_LIBAMDHIP64",
find_library_path("libamdhip64.so",
{ "ROCPROFSYS_ROCM_PATH", "ROCM_PATH" },
{ ROCPROFSYS_DEFAULT_ROCM_PATH }) };
#if ROCPROFSYS_HIP_VERSION_MAJOR == 4 && ROCPROFSYS_HIP_VERSION_MINOR < 4
dynamic_library _kfdwrapper{
"ROCPROFSYS_ROCTRACER_LIBKFDWRAPPER",
find_library_path("libkfdwrapper64.so", { "ROCPROFSYS_ROCM_PATH", "ROCM_PATH" },
{ ROCPROFSYS_DEFAULT_ROCM_PATH },
{ "roctracer/lib", "roctracer/lib64", "lib", "lib64" })
};
#endif
ROCPROFSYS_ROCTRACER_CALL(roctracer_set_properties(ACTIVITY_DOMAIN_HIP_API, nullptr));
// Allocating tracing pool
roctracer_properties_t properties{};
memset(&properties, 0, sizeof(roctracer_properties_t));
// properties.mode = 0x1000;
properties.buffer_size = 0x100;
properties.buffer_callback_fun = hip_activity_callback;
ROCPROFSYS_ROCTRACER_CALL(roctracer_open_pool(&properties));
#if ROCPROFSYS_HIP_VERSION_MAJOR == 4 && ROCPROFSYS_HIP_VERSION_MINOR >= 4
// HIP 4.5.0 has an invalid warning
redirect _rd{ std::cerr, "roctracer_enable_callback(), get_op_end(), invalid domain "
"ID(4) in: roctracer_enable_callback(hip_api_callback, "
"nullptr)roctracer_enable_activity_expl(), get_op_end(), "
"invalid domain ID(4) in: roctracer_enable_activity()" };
#endif
if(get_trace_hip_api())
{
ROCPROFSYS_ROCTRACER_CALL(roctracer_enable_domain_callback(
ACTIVITY_DOMAIN_HIP_API, hip_api_callback, nullptr));
}
if(get_use_roctx())
{
ROCPROFSYS_ROCTRACER_CALL(roctracer_enable_domain_callback(
ACTIVITY_DOMAIN_ROCTX, roctx_api_callback, nullptr));
}
if(get_trace_hip_activity())
{
// Enable HIP activity tracing
ROCPROFSYS_ROCTRACER_CALL(
roctracer_enable_domain_activity(ACTIVITY_DOMAIN_HIP_OPS));
}
if(table != nullptr)
{
ROCPROFSYS_VERBOSE(1 || on_load_trace, "[OnLoad] setting up HSA...\n");
bool trace_hsa_api = get_trace_hsa_api();
// Enable HSA API callbacks/activity
if(trace_hsa_api)
{
std::vector<std::string> hsa_api_vec =
tim::delimit(get_trace_hsa_api_types());
// initialize HSA tracing
roctracer_set_properties(
static_cast<activity_domain_t>(ACTIVITY_DOMAIN_HSA_API), (void*) table);
if(!hsa_api_vec.empty())
{
for(const auto& itr : hsa_api_vec)
{
uint32_t cid = HSA_API_ID_NUMBER;
const char* api = itr.c_str();
ROCPROFSYS_ROCTRACER_CALL(roctracer_op_code(
static_cast<activity_domain_t>(ACTIVITY_DOMAIN_HSA_API), api,
&cid, nullptr));
ROCPROFSYS_ROCTRACER_CALL(roctracer_enable_op_callback(
static_cast<activity_domain_t>(ACTIVITY_DOMAIN_HSA_API), cid,
hsa_api_callback, nullptr));
ROCPROFSYS_VERBOSE(1 || on_load_trace, " HSA-trace(%s)", api);
}
}
else
{
ROCPROFSYS_VERBOSE(1 || on_load_trace, " HSA-trace()\n");
ROCPROFSYS_ROCTRACER_CALL(roctracer_enable_domain_callback(
static_cast<activity_domain_t>(ACTIVITY_DOMAIN_HSA_API),
hsa_api_callback, nullptr));
}
}
bool trace_hsa_activity = get_trace_hsa_activity();
// Enable HSA GPU activity
if(trace_hsa_activity)
{
#if ROCPROFSYS_HIP_VERSION < 50300
using namespace roctracer;
// initialize HSA tracing
const char* output_prefix = nullptr;
hsa_ops_properties_t ops_properties{
table, reinterpret_cast<activity_async_callback_t>(hsa_activity_callback),
nullptr, output_prefix
};
#elif ROCPROFSYS_HIP_VERSION < 50301
hsa_ops_properties_t ops_properties;
ops_properties.table = table;
ops_properties.reserved1[0] = reinterpret_cast<void*>(&hsa_activity_callback);
ops_properties.reserved1[1] = nullptr;
ops_properties.reserved1[2] = nullptr;
#else
hsa_ops_properties_t ops_properties{
table, reinterpret_cast<void*>(&hsa_activity_callback), nullptr, nullptr
};
#endif
roctracer_set_properties(
static_cast<activity_domain_t>(ACTIVITY_DOMAIN_HSA_OPS), &ops_properties);
ROCPROFSYS_VERBOSE(1 || on_load_trace, " HSA-activity-trace()\n");
ROCPROFSYS_ROCTRACER_CALL(roctracer_enable_op_activity(
static_cast<activity_domain_t>(ACTIVITY_DOMAIN_HSA_OPS), HSA_OP_ID_COPY));
}
}
// callback for HSA
for(auto& itr : roctracer_setup_routines())
itr.second();
// make sure all async callbacks are allocated
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
hip_exec_activity_callbacks(i);
ROCPROFSYS_VERBOSE_F(1, "roctracer is setup\n");
}
void
roctracer::flush()
{
auto wait_for_activity_flush_completion = []() {
uint16_t nitr = 0;
while(roctracer_activity_count() > 0 && nitr++ < 10)
std::this_thread::sleep_for(std::chrono::milliseconds{ 100 });
};
// a flush may already be happening
wait_for_activity_flush_completion();
if(roctracer_activity_count() == 0)
{
ROCPROFSYS_VERBOSE_F(2, "executing roctracer_flush_activity()...\n");
ROCPROFSYS_ROCTRACER_CALL(roctracer_flush_activity());
// wait to make sure flush completes
std::this_thread::sleep_for(std::chrono::milliseconds{ 100 });
wait_for_activity_flush_completion();
}
else
{
ROCPROFSYS_CI_FAIL(true,
"roctracer_activity_count() != 0 (== %li). "
"roctracer::shutdown() most likely called during abort",
roctracer_activity_count().load());
}
ROCPROFSYS_VERBOSE_F(2, "executing hip_exec_activity_callbacks(0..%zu)\n",
thread_info::get_peak_num_threads());
// make sure all async operations are executed
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
hip_exec_activity_callbacks(i);
ROCPROFSYS_VERBOSE_F(2, "roctracer flush completed\n");
}
void
roctracer::shutdown()
{
auto_lock_t _lk{ type_mutex<roctracer>() };
if(!roctracer_is_setup()) return;
roctracer_is_setup() = false;
ROCPROFSYS_VERBOSE_F(1, "shutting down roctracer...\n");
// callback for hsa
ROCPROFSYS_VERBOSE_F(2, "executing %zu roctracer_shutdown_routines...\n",
roctracer_shutdown_routines().size());
for(auto& itr : roctracer_shutdown_routines())
itr.second();
#if ROCPROFSYS_HIP_VERSION_MAJOR == 4 && ROCPROFSYS_HIP_VERSION_MINOR >= 4
ROCPROFSYS_DEBUG_F("redirecting roctracer warnings\n");
// HIP 4.5.0 has an invalid warning
redirect _rd{
std::cerr, "roctracer_disable_callback(), get_op_end(), invalid domain ID(4) "
"in: roctracer_disable_callback()roctracer_disable_activity(), "
"get_op_end(), invalid domain ID(4) in: roctracer_disable_activity()"
};
#endif
if(get_trace_hip_api())
{
ROCPROFSYS_VERBOSE_F(
2,
"executing roctracer_disable_domain_callback(ACTIVITY_DOMAIN_HIP_API)...\n");
ROCPROFSYS_ROCTRACER_CALL(
roctracer_disable_domain_callback(ACTIVITY_DOMAIN_HIP_API));
}
if(get_use_roctx())
{
ROCPROFSYS_VERBOSE_F(
2, "executing roctracer_disable_domain_activity(ACTIVITY_DOMAIN_ROCTX)...\n");
ROCPROFSYS_ROCTRACER_CALL(
roctracer_disable_domain_callback(ACTIVITY_DOMAIN_ROCTX));
}
if(get_trace_hip_activity())
{
ROCPROFSYS_VERBOSE_F(
2,
"executing roctracer_disable_domain_activity(ACTIVITY_DOMAIN_HIP_OPS)...\n");
ROCPROFSYS_ROCTRACER_CALL(
roctracer_disable_domain_activity(ACTIVITY_DOMAIN_HIP_OPS));
}
if(get_trace_hsa_api())
{
ROCPROFSYS_VERBOSE_F(
2,
"executing roctracer_disable_domain_activity(ACTIVITY_DOMAIN_HSA_API)...\n");
ROCPROFSYS_ROCTRACER_CALL(
roctracer_disable_domain_callback(ACTIVITY_DOMAIN_HSA_API));
}
if(get_trace_hsa_api())
{
ROCPROFSYS_VERBOSE_F(
2, "executing roctracer_disable_op_activity(ACTIVITY_DOMAIN_HSA_OPS, "
"HSA_OP_ID_COPY)...\n");
ROCPROFSYS_ROCTRACER_CALL(
roctracer_disable_op_activity(ACTIVITY_DOMAIN_HSA_OPS, HSA_OP_ID_COPY));
}
ROCPROFSYS_VERBOSE_F(1, "roctracer is shutdown\n");
}
scope::transient_destructor
roctracer::protect_flush_activity()
{
return scope::transient_destructor([]() { --roctracer_activity_count(); },
[]() { ++roctracer_activity_count(); });
}
} // namespace component
} // namespace rocprofsys
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(roctracer, false, void)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(roctracer_data, true, double)
@@ -0,0 +1,117 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include <timemory/api.hpp>
#include <timemory/components/base.hpp>
#include <timemory/components/data_tracker/components.hpp>
#include <timemory/components/macros.hpp>
#include <timemory/enum.h>
#include <timemory/macros/os.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/utility/transient_function.hpp>
ROCPROFSYS_COMPONENT_ALIAS(roctracer_data,
::tim::component::data_tracker<double, roctracer>)
namespace rocprofsys
{
namespace component
{
struct roctracer
: base<roctracer, void>
, private policy::instance_tracker<roctracer, false>
{
using value_type = void;
using base_type = base<roctracer, void>;
using tracker_type = policy::instance_tracker<roctracer, false>;
ROCPROFSYS_DEFAULT_OBJECT(roctracer)
static void preinit();
static void global_finalize() { shutdown(); }
static bool is_setup();
static void setup(void* hsa_api_table, bool on_load_trace = false);
static void flush();
static void shutdown();
static void add_setup(const std::string&, std::function<void()>&&);
static void add_shutdown(const std::string&, std::function<void()>&&);
static void remove_setup(const std::string&);
static void remove_shutdown(const std::string&);
void start();
void stop();
// this function protects roctracer_flush_activty from being called
// when rocprof-sys exits during a callback
[[nodiscard]] static scope::transient_destructor protect_flush_activity();
};
#if !defined(ROCPROFSYS_USE_ROCTRACER)
inline void
roctracer::setup(void*, bool)
{}
inline void
roctracer::flush()
{}
inline void
roctracer::shutdown()
{}
inline bool
roctracer::is_setup()
{
return false;
}
#endif
} // namespace component
} // namespace rocprofsys
#if !defined(ROCPROFSYS_USE_ROCTRACER)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(is_available, component::roctracer_data, false_type)
#endif
TIMEMORY_SET_COMPONENT_API(rocprofsys::component::roctracer_data, project::timemory,
category::timing, os::supports_unix)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(is_timing_category, component::roctracer_data, true_type)
ROCPROFSYS_DEFINE_CONCRETE_TRAIT(uses_timing_units, component::roctracer_data, true_type)
#if defined(ROCPROFSYS_USE_ROCTRACER) && ROCPROFSYS_USE_ROCTRACER > 0
# if !defined(ROCPROFSYS_EXTERN_COMPONENTS) || \
(defined(ROCPROFSYS_EXTERN_COMPONENTS) && ROCPROFSYS_EXTERN_COMPONENTS > 0)
# include <timemory/operations.hpp>
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(roctracer, false, void)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(roctracer_data, true, double)
# endif
#endif
@@ -0,0 +1,347 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/coverage.hpp"
#include "api.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "library/coverage/impl.hpp"
#include "library/thread_data.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/utility/popen.hpp>
#include <algorithm>
#include <map>
#include <mutex>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#define ROCPROFSYS_SERIALIZE(MEMBER_VARIABLE) \
ar(::tim::cereal::make_nvp(#MEMBER_VARIABLE, MEMBER_VARIABLE))
namespace rocprofsys
{
namespace coverage
{
namespace
{
template <typename... Tp>
using uomap_t = std::unordered_map<Tp...>;
//
using coverage_thread_data_type =
uomap_t<std::string_view, uomap_t<std::string_view, std::map<size_t, size_t>>>;
//
using coverage_data_vector = std::vector<coverage_data>;
//
using coverage_data_map =
uomap_t<std::string_view,
uomap_t<std::string_view, std::map<size_t, coverage_data_vector::iterator>>>;
//
using coverage_thread_data =
rocprofsys::thread_data<coverage_thread_data_type, code_coverage>;
//
auto&
get_code_coverage()
{
static auto _v = code_coverage{};
return _v;
}
//
auto&
get_post_processed()
{
static auto* _v = new bool{ false };
return *_v;
}
//
auto&
get_coverage_data()
{
static auto _v = coverage_data_vector{};
return _v;
}
//
auto&
get_coverage_count(int64_t _tid = tim::threading::get_id())
{
return coverage_thread_data::instance(construct_on_thread{ _tid });
}
} // namespace
//--------------------------------------------------------------------------------------//
void
post_process()
{
using data_tuple_t = coverage_data::data_tuple_t;
if(get_post_processed()) return;
get_post_processed() = true;
if(!config::get_use_code_coverage()) return;
auto& _coverage = get_code_coverage();
auto& _coverage_data = get_coverage_data();
if(_coverage.size == 0)
{
ROCPROFSYS_VERBOSE_F(
0,
"Warning! Code coverage enabled but no code coverage data is available!\n");
return;
}
auto _data = coverage_thread_data_type{};
{
auto _coverage_map = coverage_data_map{};
auto _find = [&_coverage_data, &_coverage_map](data_tuple_t&& _v) {
auto& _cache = _coverage_map[std::get<0>(_v)][std::get<1>(_v)];
auto mitr = _cache.find(std::get<2>(_v));
if(mitr != _cache.end()) return std::make_pair(mitr->second, true);
for(auto itr = _coverage_data.begin(); itr != _coverage_data.end(); ++itr)
{
if(*itr == _v)
{
_cache[std::get<2>(_v)] = itr;
return std::make_pair(itr, true);
}
}
return std::make_pair(_coverage_data.end(), false);
};
for(size_t i = 0; i < coverage_thread_data::size(); ++i)
{
const auto& _thr_data = *get_coverage_count(i);
for(const auto& file : _thr_data)
{
for(const auto& func : file.second)
{
for(const auto& addr : func.second)
{
_data[file.first][func.first][addr.first] += addr.second;
auto&& _v = _find({ file.first, func.first, addr.first });
if(_v.second)
{
_v.first->count += addr.second;
}
else
{
ROCPROFSYS_VERBOSE_F(0,
"Warning! No matching coverage data for "
"%s :: %s (0x%x)\n",
func.first.data(), file.first.data(),
(unsigned int) addr.first);
}
}
}
}
}
}
for(const auto& file : _data)
{
for(const auto& func : file.second)
{
for(const auto& addr : func.second)
{
if(addr.second > 0)
{
_coverage.count += 1;
_coverage.covered.modules.emplace(file.first);
_coverage.covered.functions.emplace(func.first);
_coverage.covered.addresses.emplace(addr.first);
}
}
}
}
std::sort(_coverage_data.begin(), _coverage_data.end(),
std::greater<coverage_data>{});
{
auto _tmp_map = coverage_data_map{};
auto _tmp = std::decay_t<decltype(_coverage_data)>{};
auto _find_in_tmp = [&_tmp, &_tmp_map](const auto& _v) {
auto& _cache = _tmp_map[_v.module][_v.function];
auto mitr = _cache.find(_v.address);
if(mitr != _cache.end()) return std::make_pair(mitr->second, true);
for(auto titr = _tmp.begin(); titr != _tmp.end(); ++titr)
{
if(titr->source == _v.source && titr->address != _v.address &&
titr->count == _v.count)
{
_cache[_v.address] = titr;
return std::make_pair(titr, true);
}
}
return std::make_pair(_tmp.end(), false);
};
for(auto&& itr : _coverage_data)
{
if(!_find_in_tmp(itr).second) _tmp.emplace_back(itr);
}
std::swap(_coverage_data, _tmp);
}
ROCPROFSYS_VERBOSE(0, "code coverage :: %6.2f%s\n", _coverage() * 100.0, "%");
ROCPROFSYS_VERBOSE(0, "module coverage :: %6.2f%s\n",
_coverage(code_coverage::MODULE) * 100.0, "%");
ROCPROFSYS_VERBOSE(0, "function coverage :: %6.2f%s\n",
_coverage(code_coverage::FUNCTION) * 100.0, "%");
if(get_verbose() >= 0) fprintf(stderr, "\n");
std::sort(_coverage_data.begin(), _coverage_data.end(),
std::greater<coverage_data>{});
auto _get_setting = [](const std::string& _v) {
auto&& _b = config::get_setting_value<bool>(_v);
ROCPROFSYS_CI_THROW(!_b, "Error! No configuration setting named '%s'",
_v.c_str());
return _b.value_or(true);
};
auto _text_output = _get_setting("ROCPROFSYS_TEXT_OUTPUT");
auto _json_output = _get_setting("ROCPROFSYS_JSON_OUTPUT");
if(_text_output)
{
auto _fname = tim::settings::compose_output_filename("coverage", ".txt");
std::ofstream ofs{};
if(tim::filepath::open(ofs, _fname))
{
if(get_verbose() >= 0)
operation::file_output_message<code_coverage>{}(
_fname, std::string{ "coverage" });
for(auto& itr : _coverage_data)
{
// if(get_debug() && get_verbose() >= 2)
if(true)
{
auto _addr = TIMEMORY_JOIN("", "0x", std::hex, itr.address);
ofs << std::setw(8) << itr.count << " " << std::setw(8) << _addr
<< " " << itr.source << "\n";
}
else
{
ofs << std::setw(8) << itr.count << " " << itr.source << "\n";
}
}
}
else
{
ROCPROFSYS_THROW("Error opening coverage output file: %s", _fname.c_str());
}
}
if(_json_output)
{
std::stringstream oss{};
{
namespace cereal = tim::cereal;
auto ar =
tim::policy::output_archive<cereal::PrettyJSONOutputArchive>::get(oss);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName("coverage");
ar->startNode();
(*ar)(cereal::make_nvp("summary", _coverage));
(*ar)(cereal::make_nvp("details", _coverage_data));
ar->finishNode();
ar->finishNode();
}
auto _fname = tim::settings::compose_output_filename("coverage", ".json");
std::ofstream ofs{};
if(tim::filepath::open(ofs, _fname))
{
if(get_verbose() >= 0)
operation::file_output_message<code_coverage>{}(
_fname, std::string{ "coverage" });
ofs << oss.str() << "\n";
}
else
{
ROCPROFSYS_THROW("Error opening coverage output file: %s", _fname.c_str());
}
}
if(get_verbose() >= 0) fprintf(stderr, "\n");
}
} // namespace coverage
} // namespace rocprofsys
//--------------------------------------------------------------------------------------//
namespace coverage = rocprofsys::coverage;
extern "C" void
rocprofsys_register_source_hidden(const char* file, const char* func, size_t line,
size_t address, const char* source)
{
if(coverage::get_post_processed()) return;
using coverage_data = coverage::coverage_data;
ROCPROFSYS_BASIC_VERBOSE_F(4, "[0x%x] :: %-20s :: %20s:%zu :: %s\n",
(unsigned int) address, func, file, line, source);
coverage::get_coverage_data().emplace_back(
coverage_data{ size_t{ 0 }, address, line, file, func,
(source && strlen(source) > 0) ? source : func });
coverage::get_code_coverage().size += 1;
coverage::get_code_coverage().possible.modules.emplace(file);
coverage::get_code_coverage().possible.functions.emplace(func);
coverage::get_code_coverage().possible.addresses.emplace(address);
// initialize
for(size_t i = 0; i < coverage::coverage_thread_data::size(); ++i)
{
(*coverage::get_coverage_count(i))[file][func].emplace(address, 0);
}
}
//--------------------------------------------------------------------------------------//
extern "C" void
rocprofsys_register_coverage_hidden(const char* file, const char* func, size_t address)
{
if(coverage::get_post_processed()) return;
if(rocprofsys::get_state() < rocprofsys::State::Active &&
!rocprofsys_init_tooling_hidden())
return;
else if(rocprofsys::get_state() >= rocprofsys::State::Finalized)
return;
ROCPROFSYS_BASIC_VERBOSE_F(3, "[0x%x] %-20s :: %20s\n", (unsigned int) address, func,
file);
(*coverage::get_coverage_count())[file][func][address] += 1;
}
//--------------------------------------------------------------------------------------//
@@ -0,0 +1,173 @@
// MIT License
//
// Copyright (c) 2022-2024 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 <timemory/mpl/concepts.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/tpls/cereal/cereal/cereal.hpp>
#include <cstddef>
#include <set>
#include <string>
#if !defined(ROCPROFSYS_SERIALIZE)
# define ROCPROFSYS_SERIALIZE(MEMBER_VARIABLE) \
ar(::tim::cereal::make_nvp(#MEMBER_VARIABLE, MEMBER_VARIABLE))
#endif
namespace rocprofsys
{
namespace coverage
{
#if !defined(ROCPROFSYS_PYBIND11_SOURCE) || ROCPROFSYS_PYBIND11_SOURCE == 0
void
post_process();
#endif
//--------------------------------------------------------------------------------------//
//
/// \struct code_coverage
/// \brief Summary information about the code coverage
//
//--------------------------------------------------------------------------------------//
struct code_coverage
{
using int_set_t = std::set<size_t>;
using str_set_t = std::set<std::string>;
enum Category
{
STANDARD = 0,
ADDRESS,
MODULE,
FUNCTION
};
struct data
{
int_set_t addresses = {};
str_set_t modules = {};
str_set_t functions = {};
data& operator+=(const data& rhs);
data operator+(const data& rhs) const;
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned version);
};
double operator()(Category _c = STANDARD) const;
double get(Category _c = STANDARD) const { return (*this)(_c); }
int_set_t get_uncovered_addresses() const;
str_set_t get_uncovered_modules() const;
str_set_t get_uncovered_functions() const;
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned version);
size_t count = 0;
size_t size = 0;
data covered = {};
data possible = {};
};
//
template <typename ArchiveT>
void
code_coverage::serialize(ArchiveT& ar, const unsigned version)
{
ROCPROFSYS_SERIALIZE(count);
ROCPROFSYS_SERIALIZE(size);
ROCPROFSYS_SERIALIZE(covered);
ROCPROFSYS_SERIALIZE(possible);
if constexpr(tim::concepts::is_output_archive<ArchiveT>::value)
{
ar.setNextName("coverage");
ar.startNode();
ar(tim::cereal::make_nvp("total", get(STANDARD)));
ar(tim::cereal::make_nvp("addresses", get(ADDRESS)));
ar(tim::cereal::make_nvp("modules", get(MODULE)));
ar(tim::cereal::make_nvp("functions", get(FUNCTION)));
ar.finishNode();
}
(void) version;
}
//
template <typename ArchiveT>
void
code_coverage::data::serialize(ArchiveT& ar, const unsigned version)
{
ROCPROFSYS_SERIALIZE(addresses);
ROCPROFSYS_SERIALIZE(modules);
ROCPROFSYS_SERIALIZE(functions);
(void) version;
}
//--------------------------------------------------------------------------------------//
//
/// \struct coverage_data
/// \brief Detailed information about the code coverage
//
//--------------------------------------------------------------------------------------//
struct coverage_data
{
using data_tuple_t = std::tuple<std::string_view, std::string_view, size_t>;
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned version);
coverage_data& operator+=(const coverage_data& rhs);
coverage_data operator+(const coverage_data& rhs) const;
bool operator==(const coverage_data& rhs) const;
bool operator==(const data_tuple_t& rhs) const;
bool operator!=(const coverage_data& rhs) const;
bool operator<(const coverage_data& rhs) const;
bool operator<=(const coverage_data& rhs) const;
bool operator>(const coverage_data& rhs) const;
bool operator>=(const coverage_data& rhs) const;
size_t count = 0;
size_t address = 0;
size_t line = 0;
std::string module = {};
std::string function = {};
std::string source = {};
};
//
template <typename ArchiveT>
void
coverage_data::serialize(ArchiveT& ar, const unsigned version)
{
ROCPROFSYS_SERIALIZE(count);
ROCPROFSYS_SERIALIZE(line);
ROCPROFSYS_SERIALIZE(address);
ROCPROFSYS_SERIALIZE(module);
ROCPROFSYS_SERIALIZE(function);
ROCPROFSYS_SERIALIZE(source);
(void) version;
}
//
} // namespace coverage
} // namespace rocprofsys
@@ -0,0 +1,6 @@
#
set(coverage_sources)
set(coverage_headers ${CMAKE_CURRENT_LIST_DIR}/impl.hpp)
target_sources(rocprofiler-systems-object-library PRIVATE ${coverage_sources}
${coverage_headers})
@@ -0,0 +1,194 @@
// MIT License
//
// Copyright (c) 2022-2024 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
// IMPORTANT NOTE:
// this may be a header file but will lead to ODR violations if included
// more than once. The reason it is in a header file is so that the python
// bindings can also build these symbols. The absence of inline is intentional!
#include "library/coverage.hpp"
#include <algorithm>
#include <cstddef>
#include <set>
#include <string>
#include <tuple>
#include <vector>
namespace rocprofsys
{
namespace coverage
{
namespace
{
template <typename Tp, typename... Args>
inline std::set<Tp, Args...>
get_uncovered(const std::set<Tp, Args...>& _covered,
const std::set<Tp, Args...>& _possible)
{
std::set<Tp, Args...> _v{};
for(auto&& itr : _possible)
{
if(_covered.count(itr) == 0) _v.emplace(itr);
}
return _v;
}
//
template <typename Tp, typename... Args>
inline std::vector<Tp, Args...>
get_uncovered(const std::vector<Tp, Args...>& _covered,
const std::vector<Tp, Args...>& _possible)
{
std::vector<Tp, Args...> _v{};
for(auto&& itr : _possible)
{
if(!std::any_of(_covered.begin(), _covered.end(),
[itr](auto&& _entry) { return _entry == itr; }))
_v.emplace_back(itr);
}
return _v;
}
} // namespace
code_coverage::data&
code_coverage::data::operator+=(const data& rhs)
{
for(auto&& itr : rhs.addresses)
addresses.emplace(itr);
for(auto&& itr : rhs.modules)
modules.emplace(itr);
for(auto&& itr : rhs.modules)
modules.emplace(itr);
return *this;
}
code_coverage::data
code_coverage::data::operator+(const data& rhs) const
{
return data{ *this } += rhs;
}
double
code_coverage::operator()(Category _c) const
{
switch(_c)
{
case STANDARD: return static_cast<double>(count) / static_cast<double>(size);
case ADDRESS:
return static_cast<double>(covered.addresses.size()) /
static_cast<double>(possible.addresses.size());
case MODULE:
return static_cast<double>(covered.modules.size()) /
static_cast<double>(possible.modules.size());
case FUNCTION:
return static_cast<double>(covered.functions.size()) /
static_cast<double>(possible.functions.size());
}
return 0.0;
}
code_coverage::int_set_t
code_coverage::get_uncovered_addresses() const
{
return get_uncovered(covered.addresses, possible.addresses);
}
code_coverage::str_set_t
code_coverage::get_uncovered_modules() const
{
return get_uncovered(covered.modules, possible.modules);
}
code_coverage::str_set_t
code_coverage::get_uncovered_functions() const
{
return get_uncovered(covered.functions, possible.functions);
}
//--------------------------------------------------------------------------------------//
coverage_data&
coverage_data::operator+=(const coverage_data& rhs)
{
count += rhs.count;
return *this;
}
coverage_data
coverage_data::operator+(const coverage_data& rhs) const
{
return coverage_data{ *this } += rhs;
}
bool
coverage_data::operator==(const coverage_data& rhs) const
{
return std::tie(module, function, address) ==
std::tie(rhs.module, rhs.function, rhs.address);
}
bool
coverage_data::operator==(const data_tuple_t& rhs) const
{
return std::tie(module, function, address) ==
std::tie(std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs));
}
bool
coverage_data::operator!=(const coverage_data& rhs) const
{
return !(*this == rhs);
}
bool
coverage_data::operator<(const coverage_data& rhs) const
{
if(count != rhs.count) return count < rhs.count;
if(module != rhs.module) return module < rhs.module;
if(function != rhs.function) return function < rhs.function;
if(address != rhs.address) return address < rhs.address;
if(line != rhs.line) return line < rhs.line;
return source < rhs.source;
}
bool
coverage_data::operator<=(const coverage_data& rhs) const
{
return (*this == rhs || *this < rhs);
}
bool
coverage_data::operator>(const coverage_data& rhs) const
{
return (*this != rhs && !(*this < rhs));
}
bool
coverage_data::operator>=(const coverage_data& rhs) const
{
return !(*this < rhs);
}
//
} // namespace coverage
} // namespace rocprofsys
@@ -0,0 +1,254 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/cpu_freq.hpp"
#include "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/perfetto.hpp"
#include "core/timemory.hpp"
#include "library/components/cpu_freq.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include <timemory/components/rusage/backends.hpp>
#include <timemory/mpl/types.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/procfs/cpuinfo.hpp>
#include <timemory/utility/type_list.hpp>
#include <cstddef>
#include <cstdlib>
#include <string>
#include <sys/resource.h>
#include <tuple>
#include <utility>
#include <vector>
namespace rocprofsys
{
namespace cpu_freq
{
template <typename... Tp>
using type_list = tim::type_list<Tp...>;
namespace
{
using cpu_data_tuple_t = std::tuple<size_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, component::cpu_freq>;
std::deque<cpu_data_tuple_t> data = {};
template <typename... Types>
void init_perfetto_counter_tracks(type_list<Types...>)
{
(perfetto_counter_track<Types>::init(), ...);
}
} // namespace
} // namespace cpu_freq
} // namespace rocprofsys
namespace rocprofsys
{
namespace cpu_freq
{
void
setup()
{
init_perfetto_counter_tracks(
type_list<category::cpu_freq, category::process_page, category::process_virt,
category::process_peak, category::process_context_switch,
category::process_page_fault, category::process_user_mode_time,
category::process_kernel_mode_time>{});
}
void
config()
{
component::cpu_freq::configure();
}
void
sample()
{
auto _ts = tim::get_clock_real_now<size_t, std::nano>();
auto _rcache = tim::rusage_cache{ RUSAGE_SELF };
auto _freqs = component::cpu_freq{}.sample();
// user and kernel mode times are in microseconds
data.emplace_back(
_ts, tim::get_page_rss(), tim::get_virt_mem(), _rcache.get_peak_rss(),
_rcache.get_num_priority_context_switch() +
_rcache.get_num_voluntary_context_switch(),
_rcache.get_num_major_page_faults() + _rcache.get_num_minor_page_faults(),
_rcache.get_user_mode_time() * 1000, _rcache.get_kernel_mode_time() * 1000,
std::move(_freqs));
}
void
shutdown()
{}
namespace
{
template <typename... Types, size_t N = sizeof...(Types)>
void
config_perfetto_counter_tracks(type_list<Types...>, std::array<const char*, N> _labels,
std::array<const char*, N> _units)
{
static_assert(sizeof...(Types) == N,
"Error! Number of types != number of labels/units");
auto _config = [&](auto _t) {
using type = std::decay_t<decltype(_t)>;
using track = perfetto_counter_track<type>;
constexpr auto _idx = tim::index_of<type, type_list<Types...>>::value;
if(!track::exists(0))
{
auto addendum = [&](const char* _v) { return JOIN(" ", "CPU", _v, "(S)"); };
track::emplace(0, addendum(_labels.at(_idx)), _units.at(_idx));
}
};
(_config(Types{}), ...);
}
struct index
{
size_t value = 0;
};
template <typename Tp, typename... Args>
void
write_perfetto_counter_track(Args... _args)
{
using track = perfetto_counter_track<Tp>;
TRACE_COUNTER(trait::name<Tp>::value, track::at(0, 0), _args...);
}
template <typename Tp, typename... Args>
void
write_perfetto_counter_track(index&& _idx, Args... _args)
{
using track = perfetto_counter_track<Tp>;
TRACE_COUNTER(trait::name<Tp>::value, track::at(_idx.value, 0), _args...);
}
} // namespace
void
post_process()
{
ROCPROFSYS_VERBOSE(1,
"Post-processing %zu cpu frequency and memory usage entries...\n",
data.size());
auto _process_frequencies = [](size_t _idx, size_t _offset) {
using freq_track = perfetto_counter_track<category::cpu_freq>;
const auto& _thread_info = thread_info::get(0, InternalTID);
ROCPROFSYS_CI_THROW(!_thread_info, "Missing thread info for thread 0");
if(!_thread_info) return;
if(!freq_track::exists(_idx))
{
auto addendum = [&](const char* _v) {
return JOIN(" ", "CPU", _v, JOIN("", '[', _idx, ']'), "(S)");
};
freq_track::emplace(_idx, addendum("Frequency"), "MHz");
}
for(auto& itr : data)
{
uint64_t _ts = std::get<0>(itr);
double _freq = static_cast<double>(std::get<8>(itr).at(_offset));
if(!_thread_info->is_valid_time(_ts)) continue;
write_perfetto_counter_track<category::cpu_freq>(index{ _idx }, _ts, _freq);
}
auto _end_ts = _thread_info->get_stop();
write_perfetto_counter_track<category::cpu_freq>(index{ _idx }, _end_ts, 0);
};
auto _process_cpu_rusage = []() {
config_perfetto_counter_tracks(
type_list<category::process_page, category::process_virt,
category::process_peak, category::process_context_switch,
category::process_page_fault, category::process_user_mode_time,
category::process_kernel_mode_time>{},
{ "Memory Usage", "Virtual Memory Usage", "Peak Memory", "Context Switches",
"Page Faults", "User Time", "Kernel Time" },
{ "MB", "MB", "MB", "", "", "sec", "sec" });
const auto& _thread_info = thread_info::get(0, InternalTID);
ROCPROFSYS_CI_THROW(!_thread_info, "Missing thread info for thread 0");
if(!_thread_info) return;
for(auto& itr : data)
{
uint64_t _ts = std::get<0>(itr);
if(!_thread_info->is_valid_time(_ts)) continue;
double _page = std::get<1>(itr);
double _virt = std::get<2>(itr);
double _peak = std::get<3>(itr);
uint64_t _cntx = std::get<4>(itr);
uint64_t _flts = std::get<5>(itr);
double _user = std::get<6>(itr);
double _kern = std::get<7>(itr);
write_perfetto_counter_track<category::process_page>(_ts,
_page / units::megabyte);
write_perfetto_counter_track<category::process_virt>(_ts,
_virt / units::megabyte);
write_perfetto_counter_track<category::process_peak>(_ts,
_peak / units::megabyte);
write_perfetto_counter_track<category::process_context_switch>(_ts, _cntx);
write_perfetto_counter_track<category::process_page_fault>(_ts, _flts);
write_perfetto_counter_track<category::process_user_mode_time>(
_ts, _user / units::sec);
write_perfetto_counter_track<category::process_kernel_mode_time>(
_ts, _kern / units::sec);
}
auto _end_ts = _thread_info->get_stop();
write_perfetto_counter_track<category::process_page>(_end_ts, 0.0);
write_perfetto_counter_track<category::process_virt>(_end_ts, 0.0);
write_perfetto_counter_track<category::process_peak>(_end_ts, 0.0);
write_perfetto_counter_track<category::process_context_switch>(_end_ts, 0);
write_perfetto_counter_track<category::process_page_fault>(_end_ts, 0);
write_perfetto_counter_track<category::process_user_mode_time>(_end_ts, 0.0);
write_perfetto_counter_track<category::process_kernel_mode_time>(_end_ts, 0.0);
};
_process_cpu_rusage();
auto& enabled_cpu_freqs = component::cpu_freq::get_enabled_cpus();
for(auto itr = enabled_cpu_freqs.begin(); itr != enabled_cpu_freqs.end(); ++itr)
{
auto _idx = *itr;
auto _offset = std::distance(enabled_cpu_freqs.begin(), itr);
_process_frequencies(_idx, _offset);
}
enabled_cpu_freqs.clear();
}
} // namespace cpu_freq
} // namespace rocprofsys
@@ -0,0 +1,44 @@
// MIT License
//
// Copyright (c) 2022-2024 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
namespace rocprofsys
{
namespace cpu_freq
{
void
setup();
void
config();
void
sample();
void
shutdown();
void
post_process();
} // namespace cpu_freq
} // namespace rocprofsys
@@ -0,0 +1,589 @@
// MIT License
//
// Copyright (c) 2022-2024 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.
#define TIMEMORY_KOKKOSP_POSTFIX ROCPROFSYS_PUBLIC_API
#include "api.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/perfetto.hpp"
#include "library/components/category_region.hpp"
#include "library/runtime.hpp"
#include <timemory/api/kokkosp.hpp>
#include <timemory/backends/process.hpp>
#include <timemory/hash/types.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/utility/procfs/maps.hpp>
#include <timemory/utility/types.hpp>
#include <cstdlib>
#include <sstream>
#include <string>
namespace kokkosp = ::tim::kokkosp;
namespace category = ::tim::category;
namespace comp = ::rocprofsys::component;
using kokkosp_region = comp::local_category_region<category::kokkos>;
//--------------------------------------------------------------------------------------//
namespace tim
{
template <>
inline auto
invoke_preinit<kokkosp::memory_tracker>(long)
{
kokkosp::memory_tracker::label() = "kokkos_memory";
kokkosp::memory_tracker::description() = "Kokkos Memory tracker";
}
} // namespace tim
//--------------------------------------------------------------------------------------//
namespace
{
std::string kokkos_banner =
"#---------------------------------------------------------------------------#";
//--------------------------------------------------------------------------------------//
inline void
setup_kernel_logger()
{
if((tim::settings::debug() && tim::settings::verbose() >= 3) ||
rocprofsys::config::get_use_kokkosp_kernel_logger())
{
kokkosp::logger_t::get_initializer() = [](kokkosp::logger_t& _obj) {
_obj.initialize<kokkosp::kernel_logger>();
};
}
}
} // namespace
namespace
{
bool _standalone_initialized = false;
bool _kp_deep_copy = false;
size_t _name_len_limit = 0;
std::string _kp_prefix = {};
std::vector<std::string> _initialize_arguments = {};
template <typename Tp>
void
set_invalid_id(Tp* _v)
{
constexpr bool is32 = std::is_same<Tp, uint32_t>::value;
constexpr bool is64 = std::is_same<Tp, uint64_t>::value;
static_assert(is32 || is64, "only support uint32_t or uint64_t");
*_v = std::numeric_limits<Tp>::max();
}
template <typename Tp>
bool
is_invalid_id(Tp _v)
{
constexpr bool is32 = std::is_same<Tp, uint32_t>::value;
constexpr bool is64 = std::is_same<Tp, uint64_t>::value;
static_assert(is32 || is64, "only support uint32_t or uint64_t");
return (_v == std::numeric_limits<Tp>::max());
}
template <typename Tp>
auto
strlength(Tp&& _v)
{
using type = ::tim::concepts::unqualified_type_t<Tp>;
if constexpr(std::is_same<type, std::string_view>::value ||
std::is_same<type, std::string>::value)
return _v.length();
else
return strnlen(_v, std::max<size_t>(_name_len_limit, 1));
}
template <typename Arg, typename... Args>
bool
violates_name_rules(Arg&& _arg, Args&&... _args)
{
// for causal profiling we only consider callbacks which are explicitly named
if(rocprofsys::config::get_use_causal() &&
(std::string_view{ _arg }.find("Kokkos::") == 0 ||
std::string_view{ _arg }.find("Space::") != std::string_view::npos))
return true;
size_t _len =
(strlength(std::forward<Arg>(_arg)) + ... + strlength(std::forward<Args>(_args)));
// ignore labels without names
if(_len == 0)
return true;
else if(_name_len_limit == 0)
return false;
return (_len >= _name_len_limit);
}
} // namespace
//--------------------------------------------------------------------------------------//
extern "C"
{
struct Kokkos_Tools_ToolSettings
{
bool requires_global_fencing;
bool padding[255];
};
void kokkosp_request_tool_settings(const uint32_t,
Kokkos_Tools_ToolSettings*) ROCPROFSYS_PUBLIC_API;
void kokkosp_dual_view_sync(const char*, const void* const,
bool) ROCPROFSYS_PUBLIC_API;
void kokkosp_dual_view_modify(const char*, const void* const,
bool) ROCPROFSYS_PUBLIC_API;
void kokkosp_print_help(char*) {}
void kokkosp_parse_args(int argc, char** argv)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
if(!rocprofsys::config::settings_are_configured() &&
rocprofsys::get_state() < rocprofsys::State::Active)
{
_standalone_initialized = true;
ROCPROFSYS_BASIC_VERBOSE_F(0, "Parsing arguments...\n");
std::string _command_line = {};
for(int i = 0; i < argc; ++i)
{
_initialize_arguments.emplace_back(argv[i]);
_command_line.append(" ").append(argv[i]);
}
if(_command_line.length() > 1) _command_line = _command_line.substr(1);
tim::set_env("ROCPROFSYS_COMMAND_LINE", _command_line, 0);
}
}
void kokkosp_declare_metadata(const char* key, const char* value)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
tim::manager::add_metadata(key, value);
}
void kokkosp_request_tool_settings(const uint32_t _version,
Kokkos_Tools_ToolSettings* _settings)
{
if(_version > 0) _settings->requires_global_fencing = false;
}
void kokkosp_init_library(const int loadSeq, const uint64_t interfaceVer,
const uint32_t devInfoCount, void* deviceInfo)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
tim::consume_parameters(devInfoCount, deviceInfo);
ROCPROFSYS_BASIC_VERBOSE_F(
0,
"Initializing rocprof-sys kokkos connector (sequence %d, version: %llu)... ",
loadSeq, (unsigned long long) interfaceVer);
if(_standalone_initialized ||
(!rocprofsys::config::settings_are_configured() &&
rocprofsys::get_state() < rocprofsys::State::Active))
{
auto _kokkos_profile_lib =
tim::get_env<std::string>("KOKKOS_PROFILE_LIBRARY");
if(_kokkos_profile_lib.find("librocprof-sys.so") != std::string::npos)
{
auto _maps = tim::procfs::read_maps(tim::process::get_id());
auto _libs = std::set<std::string>{};
for(auto& itr : _maps)
{
auto&& _path = itr.pathname;
if(!_path.empty() && _path.at(0) != '[' &&
rocprofsys::filepath::exists(_path))
_libs.emplace(_path);
}
for(const auto& itr : _libs)
{
if(itr.find("librocprof-sys-dl.so") != std::string::npos)
{
std::stringstream _libs_str{};
for(const auto& litr : _libs)
_libs_str << " " << litr << "\n";
ROCPROFSYS_ABORT(
"%s was invoked with librocprof-sys.so as the "
"KOKKOS_PROFILE_LIBRARY.\n"
"However, librocprof-sys-dl.so has already been loaded by "
"the process.\nTo avoid duplicate collections culminating "
"is an error, please set KOKKOS_PROFILE_LIBRARY=%s.\nLoaded "
"libraries:\n%s",
__FUNCTION__, itr.c_str(), _libs_str.str().c_str());
}
}
}
ROCPROFSYS_BASIC_VERBOSE_F(0, "Initializing rocprof-sys (standalone)... ");
auto _mode = tim::get_env<std::string>("ROCPROFSYS_MODE", "trace");
auto _arg0 = (_initialize_arguments.empty()) ? std::string{ "unknown" }
: _initialize_arguments.at(0);
_standalone_initialized = true;
rocprofsys_set_mpi_hidden(false, false);
rocprofsys_init_hidden(_mode.c_str(), false, _arg0.c_str());
rocprofsys_push_trace_hidden("kokkos_main");
}
setup_kernel_logger();
tim::trait::runtime_enabled<kokkosp::memory_tracker>::set(
rocprofsys::config::get_use_timemory());
if(rocprofsys::get_verbose() >= 0)
{
fprintf(stderr, "%sDone\n%s", tim::log::color::info(),
tim::log::color::end());
}
_name_len_limit = rocprofsys::config::get_setting_value<int64_t>(
"ROCPROFSYS_KOKKOSP_NAME_LENGTH_MAX")
.value_or(_name_len_limit);
_kp_prefix = rocprofsys::config::get_setting_value<std::string>(
"ROCPROFSYS_KOKKOSP_PREFIX")
.value_or(_kp_prefix);
_kp_deep_copy =
rocprofsys::config::get_setting_value<bool>("ROCPROFSYS_KOKKOSP_DEEP_COPY")
.value_or(_kp_deep_copy);
}
void kokkosp_finalize_library()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
if(_standalone_initialized)
{
rocprofsys_pop_trace_hidden("kokkos_main");
ROCPROFSYS_VERBOSE_F(
0, "Finalizing kokkos rocprof-sys connector (standalone)...\n");
rocprofsys_finalize_hidden();
}
else
{
ROCPROFSYS_VERBOSE_F(0, "Finalizing kokkos rocprof-sys connector... ");
kokkosp::cleanup();
if(rocprofsys::get_verbose() >= 0) fprintf(stderr, "Done\n");
}
}
//----------------------------------------------------------------------------------//
void kokkosp_begin_parallel_for(const char* name, uint32_t devid, uint64_t* kernid)
{
if(violates_name_rules(name)) return set_invalid_id(kernid);
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
auto pname = (devid > std::numeric_limits<uint16_t>::max()) // junk device number
? JOIN(" ", _kp_prefix, name, "[for]")
: JOIN(" ", _kp_prefix, name, JOIN("", "[for][dev", devid, ']'));
*kernid = kokkosp::get_unique_id();
kokkosp::logger_t{}.mark(1, __FUNCTION__, name, *kernid);
kokkosp::create_profiler<kokkosp_region>(pname, *kernid);
kokkosp::start_profiler<kokkosp_region>(*kernid);
}
void kokkosp_end_parallel_for(uint64_t kernid)
{
if(is_invalid_id(kernid)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__, kernid);
kokkosp::stop_profiler<kokkosp_region>(kernid);
kokkosp::destroy_profiler<kokkosp_region>(kernid);
}
//----------------------------------------------------------------------------------//
void kokkosp_begin_parallel_reduce(const char* name, uint32_t devid, uint64_t* kernid)
{
if(violates_name_rules(name)) return set_invalid_id(kernid);
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
auto pname =
(devid > std::numeric_limits<uint16_t>::max()) // junk device number
? JOIN(" ", _kp_prefix, name, "[reduce]")
: JOIN(" ", _kp_prefix, name, JOIN("", "[reduce][dev", devid, ']'));
*kernid = kokkosp::get_unique_id();
kokkosp::logger_t{}.mark(1, __FUNCTION__, name, *kernid);
kokkosp::create_profiler<kokkosp_region>(pname, *kernid);
kokkosp::start_profiler<kokkosp_region>(*kernid);
}
void kokkosp_end_parallel_reduce(uint64_t kernid)
{
if(is_invalid_id(kernid)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__, kernid);
kokkosp::stop_profiler<kokkosp_region>(kernid);
kokkosp::destroy_profiler<kokkosp_region>(kernid);
}
//----------------------------------------------------------------------------------//
void kokkosp_begin_parallel_scan(const char* name, uint32_t devid, uint64_t* kernid)
{
if(violates_name_rules(name)) return set_invalid_id(kernid);
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
auto pname =
(devid > std::numeric_limits<uint16_t>::max()) // junk device number
? JOIN(" ", _kp_prefix, name, "[scan]")
: JOIN(" ", _kp_prefix, name, JOIN("", "[scan][dev", devid, ']'));
*kernid = kokkosp::get_unique_id();
kokkosp::logger_t{}.mark(1, __FUNCTION__, name, *kernid);
kokkosp::create_profiler<kokkosp_region>(pname, *kernid);
kokkosp::start_profiler<kokkosp_region>(*kernid);
}
void kokkosp_end_parallel_scan(uint64_t kernid)
{
if(is_invalid_id(kernid)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__, kernid);
kokkosp::stop_profiler<kokkosp_region>(kernid);
kokkosp::destroy_profiler<kokkosp_region>(kernid);
}
//----------------------------------------------------------------------------------//
void kokkosp_begin_fence(const char* name, uint32_t devid, uint64_t* kernid)
{
if(violates_name_rules(name)) return set_invalid_id(kernid);
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
auto pname =
(devid > std::numeric_limits<uint16_t>::max()) // junk device number
? JOIN(" ", _kp_prefix, name, "[fence]")
: JOIN(" ", _kp_prefix, name, JOIN("", "[fence][dev", devid, ']'));
*kernid = kokkosp::get_unique_id();
kokkosp::logger_t{}.mark(1, __FUNCTION__, name, *kernid);
kokkosp::create_profiler<kokkosp_region>(pname, *kernid);
kokkosp::start_profiler<kokkosp_region>(*kernid);
}
void kokkosp_end_fence(uint64_t kernid)
{
if(is_invalid_id(kernid)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__, kernid);
kokkosp::stop_profiler<kokkosp_region>(kernid);
kokkosp::destroy_profiler<kokkosp_region>(kernid);
}
//----------------------------------------------------------------------------------//
void kokkosp_push_profile_region(const char* name)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(1, __FUNCTION__, name);
kokkosp::get_profiler_stack<kokkosp_region>()
.emplace_back(kokkosp::profiler_t<kokkosp_region>(name))
.start();
}
void kokkosp_pop_profile_region()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__);
if(kokkosp::get_profiler_stack<kokkosp_region>().empty()) return;
kokkosp::get_profiler_stack<kokkosp_region>().back().stop();
kokkosp::get_profiler_stack<kokkosp_region>().pop_back();
}
//----------------------------------------------------------------------------------//
void kokkosp_create_profile_section(const char* name, uint32_t* secid)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
*secid = kokkosp::get_unique_id();
auto pname = std::string{ name };
kokkosp::create_profiler<kokkosp_region>(name, *secid);
}
void kokkosp_destroy_profile_section(uint32_t secid)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::destroy_profiler<kokkosp_region>(secid);
}
//----------------------------------------------------------------------------------//
void kokkosp_start_profile_section(uint32_t secid)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(1, __FUNCTION__, secid);
kokkosp::start_profiler<kokkosp_region>(secid);
}
void kokkosp_stop_profile_section(uint32_t secid)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__, secid);
kokkosp::stop_profiler<kokkosp_region>(secid);
}
//----------------------------------------------------------------------------------//
void kokkosp_allocate_data(const SpaceHandle space, const char* label,
const void* const ptr, const uint64_t size)
{
if(violates_name_rules(label)) return;
if(rocprofsys::config::get_use_causal()) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(0, __FUNCTION__, space.name, label,
JOIN("", '[', ptr, ']'), size);
auto pname =
JOIN(" ", _kp_prefix, label, JOIN("", '[', space.name, "][allocate]"));
kokkosp::profiler_alloc_t<>{ pname }.store(std::plus<int64_t>{}, size);
kokkosp::profiler_t<kokkosp_region>{ pname }.mark();
}
void kokkosp_deallocate_data(const SpaceHandle space, const char* label,
const void* const ptr, const uint64_t size)
{
if(violates_name_rules(label)) return;
if(rocprofsys::config::get_use_causal()) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(0, __FUNCTION__, space.name, label,
JOIN("", '[', ptr, ']'), size);
auto pname =
JOIN(" ", _kp_prefix, label, JOIN("", '[', space.name, "][deallocate]"));
kokkosp::profiler_alloc_t<>{ pname }.store(std::plus<int64_t>{}, size);
kokkosp::profiler_t<kokkosp_region>{ pname }.mark();
}
//----------------------------------------------------------------------------------//
void kokkosp_begin_deep_copy(SpaceHandle dst_handle, const char* dst_name,
const void* dst_ptr, SpaceHandle src_handle,
const char* src_name, const void* src_ptr, uint64_t size)
{
if(!_kp_deep_copy || rocprofsys::config::get_use_causal()) return;
if(violates_name_rules(dst_name, src_name)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(1, __FUNCTION__, dst_handle.name, dst_name,
JOIN("", '[', dst_ptr, ']'), src_handle.name, src_name,
JOIN("", '[', src_ptr, ']'), size);
auto name = JOIN(" ", _kp_prefix, JOIN('=', dst_handle.name, dst_name), "<-",
JOIN('=', src_handle.name, src_name), "[deep_copy]");
auto& _data = kokkosp::get_profiler_stack<kokkosp_region>();
_data.emplace_back(name);
_data.back().audit(dst_handle, dst_name, dst_ptr, src_handle, src_name, src_ptr,
size);
_data.back().start();
_data.back().store(tim::mpl::piecewise_select<kokkosp::memory_tracker>{},
std::plus<int64_t>{}, size);
}
void kokkosp_end_deep_copy()
{
if(!_kp_deep_copy || rocprofsys::config::get_use_causal()) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
kokkosp::logger_t{}.mark(-1, __FUNCTION__);
auto& _data = kokkosp::get_profiler_stack<kokkosp_region>();
if(_data.empty()) return;
_data.back().store(tim::mpl::piecewise_select<kokkosp::memory_tracker>{},
std::minus<int64_t>{}, 0);
_data.back().stop();
_data.pop_back();
}
//----------------------------------------------------------------------------------//
void kokkosp_profile_event(const char* name)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
auto _name = tim::get_hash_identifier_fast(tim::add_hash_id(name));
kokkosp::profiler_t<kokkosp_region>{ _name }.mark();
}
//----------------------------------------------------------------------------------//
void kokkosp_dual_view_sync(const char* label, const void* const, bool is_device)
{
if(violates_name_rules(label)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
if(rocprofsys::config::get_use_perfetto())
{
auto _name = tim::get_hash_identifier_fast(
tim::add_hash_id(JOIN(" ", _kp_prefix, label, "[dual_view_sync]")));
TRACE_EVENT_INSTANT("user", ::perfetto::StaticString{ _name.data() },
"target", (is_device) ? "device" : "host");
}
else if(rocprofsys::config::get_use_causal())
{
auto _name = tim::get_hash_identifier_fast(tim::add_hash_id(JOIN(
"", label, " [dual_view_sync][", (is_device) ? "device" : "host", "]")));
kokkosp::profiler_t<kokkosp_region>{ _name }.mark();
}
}
void kokkosp_dual_view_modify(const char* label, const void* const, bool is_device)
{
if(violates_name_rules(label)) return;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
if(rocprofsys::config::get_use_perfetto())
{
auto _name = tim::get_hash_identifier_fast(
tim::add_hash_id(JOIN(" ", _kp_prefix, label, "[dual_view_modify]")));
TRACE_EVENT_INSTANT("user", ::perfetto::StaticString{ _name.data() },
"target", (is_device) ? "device" : "host");
}
else if(rocprofsys::config::get_use_causal())
{
auto _name = tim::get_hash_identifier_fast(
tim::add_hash_id(JOIN(" ", _kp_prefix, label, "[dual_view_modify][",
(is_device) ? "device" : "host", "]")));
kokkosp::profiler_t<kokkosp_region>{ _name }.mark();
}
}
//----------------------------------------------------------------------------------//
}
TIMEMORY_INITIALIZE_STORAGE(kokkosp::memory_tracker)
@@ -0,0 +1,175 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "api.hpp"
#include "core/common.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include <timemory/defines.h>
#if defined(ROCPROFSYS_USE_OMPT) && ROCPROFSYS_USE_OMPT > 0
# include "core/components/fwd.hpp"
# include "library/components/category_region.hpp"
# include <timemory/components/ompt.hpp>
# include <timemory/components/ompt/extern.hpp>
# include <timemory/mpl/type_traits.hpp>
# include <timemory/timemory.hpp>
# include <memory>
using api_t = TIMEMORY_API;
using ompt_handle_t = tim::component::ompt_handle<api_t>;
using ompt_context_t = tim::openmp::context_handler<api_t>;
using ompt_toolset_t = typename ompt_handle_t::toolset_type;
using ompt_bundle_t = tim::component_tuple<ompt_handle_t>;
extern "C"
{
ompt_start_tool_result_t* ompt_start_tool(unsigned int,
const char*) ROCPROFSYS_PUBLIC_API;
}
namespace rocprofsys
{
namespace ompt
{
namespace
{
std::unique_ptr<ompt_bundle_t> f_bundle = {};
bool _init_toolset_off = (trait::runtime_enabled<ompt_toolset_t>::set(false),
trait::runtime_enabled<ompt_context_t>::set(false), true);
tim::ompt::finalize_tool_func_t f_finalize = nullptr;
} // namespace
void
setup()
{
if(!tim::settings::enabled()) return;
trait::runtime_enabled<ompt_toolset_t>::set(true);
trait::runtime_enabled<ompt_context_t>::set(true);
comp::user_ompt_bundle::global_init();
comp::user_ompt_bundle::reset();
tim::auto_lock_t lk{ tim::type_mutex<ompt_handle_t>() };
comp::user_ompt_bundle::configure<component::local_category_region<category::ompt>>();
f_bundle = std::make_unique<ompt_bundle_t>("rocprofsys/ompt",
quirk::config<quirk::auto_start>{});
}
void
shutdown()
{
static bool _protect = false;
if(_protect) return;
_protect = true;
if(f_bundle)
{
if(tim::manager::instance()) tim::manager::instance()->cleanup("rocprofsys-ompt");
f_bundle->stop();
ompt_context_t::cleanup();
trait::runtime_enabled<ompt_toolset_t>::set(false);
trait::runtime_enabled<ompt_context_t>::set(false);
comp::user_ompt_bundle::reset();
pthread_gotcha::shutdown();
// call the OMPT finalize callback
if(f_finalize) (*f_finalize)();
}
f_bundle.reset();
_protect = false;
}
namespace
{
bool&
use_tool()
{
static bool _v = false;
return _v;
}
int
tool_initialize(ompt_function_lookup_t lookup, int initial_device_num,
ompt_data_t* tool_data)
{
if(!rocprofsys::settings_are_configured())
{
ROCPROFSYS_BASIC_WARNING(
0,
"[%s] invoked before rocprof-sys was initialized. In instrumentation mode, "
"settings exported to the environment have not been propagated yet...\n",
__FUNCTION__);
rocprofsys::configure_settings();
}
use_tool() = rocprofsys::config::get_use_ompt();
if(use_tool())
{
TIMEMORY_PRINTF(stderr, "OpenMP-tools configuring for initial device %i\n\n",
initial_device_num);
f_finalize = tim::ompt::configure<TIMEMORY_OMPT_API_TAG>(
lookup, initial_device_num, tool_data);
}
return 1; // success
}
void
tool_finalize(ompt_data_t*)
{
shutdown();
}
} // namespace
} // namespace ompt
} // namespace rocprofsys
extern "C" ompt_start_tool_result_t*
ompt_start_tool(unsigned int omp_version, const char* runtime_version)
{
ROCPROFSYS_BASIC_VERBOSE_F(0, "OpenMP version: %u, runtime version: %s\n",
omp_version, runtime_version);
ROCPROFSYS_METADATA("OMP_VERSION", omp_version);
ROCPROFSYS_METADATA("OMP_RUNTIME_VERSION", runtime_version);
static auto* data = new ompt_start_tool_result_t{ &rocprofsys::ompt::tool_initialize,
&rocprofsys::ompt::tool_finalize,
{ 0 } };
return data;
}
#else
namespace rocprofsys
{
namespace ompt
{
void
setup()
{}
void
shutdown()
{}
} // namespace ompt
} // namespace rocprofsys
#endif
@@ -0,0 +1,35 @@
// MIT License
//
// Copyright (c) 2022-2024 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
namespace rocprofsys
{
namespace ompt
{
void
setup();
void
shutdown();
} // namespace ompt
} // namespace rocprofsys
@@ -0,0 +1,666 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/perf.hpp"
#include "core/debug.hpp"
#include "core/locking.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include "core/utility.hpp"
#include "library/thread_data.hpp"
#include <timemory/log/logger.hpp>
#include <timemory/log/macros.hpp>
#include <timemory/units.hpp>
#include <asm/unistd.h>
#include <ctime>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <mutex>
#include <poll.h>
#include <regex>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#if !defined(ROCPROFSYS_RETURN_ERROR_MSG)
# define ROCPROFSYS_RETURN_ERROR_MSG(COND, ...) \
if((COND)) \
{ \
auto _msg_ss = std::stringstream{}; \
_msg_ss << __VA_ARGS__; \
return std::optional<std::string>{ _msg_ss.str() }; \
}
#endif
#if !defined(ROCPROFSYS_FATAL)
# define ROCPROFSYS_FATAL TIMEMORY_FATAL
#endif
#if !defined(ROCPROFSYS_ASSERT)
# define ROCPROFSYS_ASSERT(COND) (COND) ? ::tim::log::base() : TIMEMORY_FATAL
#endif
namespace rocprofsys
{
namespace perf
{
namespace
{
struct SizeParams
{
const size_t num_pages = 2;
const size_t page = units::get_page_size();
const size_t data = num_pages * page;
const size_t mmap = data + page;
};
const SizeParams sizes = {};
} // namespace
long
perf_event_open(struct perf_event_attr* hw_event, pid_t _pid, int _cpu, int group_fd,
unsigned long flags)
{
return syscall(__NR_perf_event_open, hw_event, _pid, _cpu, group_fd, flags);
}
/// Move constructor
perf_event::perf_event(perf_event&& rhs) noexcept
{
// Release resources if the current perf_event is initialized and not equal to this
// one
if(m_fd != -1 && m_fd != rhs.m_fd)
{
::close(m_fd);
ROCPROFSYS_VERBOSE(1, "Closed perf event fd %li\n", m_fd);
}
if(m_mapping != nullptr && m_mapping != rhs.m_mapping) munmap(m_mapping, sizes.mmap);
// take rhs perf event's file descriptor and replace it with -1
m_fd = rhs.m_fd;
rhs.m_fd = -1;
// take rhs perf_event's mapping and replace it with nullptr
m_mapping = rhs.m_mapping;
rhs.m_mapping = nullptr;
// Copy over the sample type and read format
m_sample_type = rhs.m_sample_type;
m_read_format = rhs.m_read_format;
}
/// Close the perf_event file descriptor and unmap the ring buffer
perf_event::~perf_event() { close(); }
/// Move assignment
perf_event&
perf_event::operator=(perf_event&& rhs) noexcept
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
if(&rhs == this) return *this;
// Release resources if the current perf_event is initialized and not equal to this
// one
if(m_fd != -1 && m_fd != rhs.m_fd) ::close(m_fd);
if(m_mapping != nullptr && m_mapping != rhs.m_mapping) munmap(m_mapping, sizes.mmap);
// take rhs perf event's file descriptor and replace it with -1
m_fd = rhs.m_fd;
rhs.m_fd = -1;
// take rhs perf_event's mapping and replace it with nullptr
m_mapping = rhs.m_mapping;
rhs.m_mapping = nullptr;
// Copy over the sample type and read format
m_sample_type = rhs.m_sample_type;
m_read_format = rhs.m_read_format;
return *this;
}
// Open a perf_event file and map it (if sampling is enabled)
std::optional<std::string>
perf_event::open(struct perf_event_attr& _pe, pid_t _pid, int _cpu)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
m_sample_type = _pe.sample_type;
m_read_format = _pe.read_format;
m_batch_size = _pe.wakeup_events;
// Set some mandatory fields
_pe.size = sizeof(struct perf_event_attr);
_pe.disabled = 1;
// Open the file
m_fd = perf_event_open(&_pe, _pid, _cpu, -1, 0);
if(m_fd == -1)
{
std::string path = "/proc/sys/kernel/perf_event_paranoid";
auto file = std::ifstream{ path.c_str() };
ROCPROFSYS_RETURN_ERROR_MSG(!file,
"Failed to open " << path << ": " << strerror(errno));
int value = 4;
file >> value;
ROCPROFSYS_RETURN_ERROR_MSG(file.bad(), "Failed to read from "
<< path << ": " << strerror(errno));
ROCPROFSYS_RETURN_ERROR_MSG(
true, "Failed to open perf event. Consider tweaking "
<< path << " to 2 or less "
<< "(current value is " << value << "), "
<< "or run rocprof-sys as a privileged user (with CAP_SYS_ADMIN).");
}
// If sampling, map the perf event file
if(_pe.sample_type != 0 && _pe.sample_period != 0)
{
void* ring_buffer =
mmap(nullptr, sizes.mmap, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
ROCPROFSYS_RETURN_ERROR_MSG(
ring_buffer == MAP_FAILED,
"Mapping perf_event ring buffer failed. Make sure the current user has "
"permission to invoke the perf tool, and that the program being profiled "
"does not use an excessive number of threads (>1000)");
m_mapping = reinterpret_cast<struct perf_event_mmap_page*>(ring_buffer);
}
return std::optional<std::string>{};
}
std::optional<std::string>
perf_event::open(double _freq, uint32_t _batch_size, pid_t _pid, int _cpu)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
uint64_t _period = (1.0 / _freq) * units::sec;
struct perf_event_attr _pe;
if(_batch_size > 0)
m_batch_size = _batch_size;
else
_batch_size = m_batch_size;
memset(&_pe, 0, sizeof(_pe));
_pe.type = PERF_TYPE_SOFTWARE;
_pe.config = PERF_COUNT_SW_TASK_CLOCK;
_pe.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_CALLCHAIN;
_pe.sample_period = _period;
_pe.wakeup_events = _batch_size;
_pe.exclude_idle = 1;
_pe.exclude_kernel = 1;
_pe.disabled = 1;
// potential additions
_pe.inherit = 0;
_pe.exclude_hv = 1;
_pe.exclude_callchain_kernel = 1;
_pe.use_clockid = 1;
_pe.clockid = CLOCK_REALTIME;
// _pe.precise_ip = 0;
// _pe.exclusive = 1;
// _pe.pinned = 1;
return open(_pe, _pid, _cpu);
}
/// Read event count
long
perf_event::get_fileno() const
{
return m_fd;
}
/// Read event count
uint64_t
perf_event::get_count() const
{
uint64_t count;
ROCPROFSYS_REQUIRE(read(m_fd, &count, sizeof(uint64_t)) == sizeof(uint64_t))
<< "Failed to read event count from perf_event file";
return count;
}
/// Start counting events
bool
perf_event::start() const
{
if(m_fd != -1)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
ROCPROFSYS_REQUIRE(ioctl(m_fd, PERF_EVENT_IOC_ENABLE, 0) != -1)
<< "Failed to start perf event: " << strerror(errno);
}
return (m_fd != -1);
}
/// Stop counting events
bool
perf_event::stop() const
{
if(m_fd != -1)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
ROCPROFSYS_REQUIRE(ioctl(m_fd, PERF_EVENT_IOC_DISABLE, 0) != -1)
<< "Failed to stop perf event: " << strerror(errno) << " (" << m_fd << ")";
}
return (m_fd != -1);
}
bool
perf_event::is_open() const
{
return (m_fd != -1);
}
void
perf_event::close()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
stop();
if(m_fd != -1)
{
::close(m_fd);
m_fd = -1;
}
if(m_mapping != nullptr)
{
munmap(m_mapping, sizes.mmap);
m_mapping = nullptr;
}
}
void
perf_event::set_ready_signal(int sig) const
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
// Set the perf_event file to async
ROCPROFSYS_REQUIRE(fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL, 0) | O_ASYNC) != -1)
<< "failed to set perf_event file to async mode";
// Set the notification signal for the perf file
ROCPROFSYS_REQUIRE(fcntl(m_fd, F_SETSIG, sig) != -1)
<< "failed to set perf_event file signal";
// Set the current thread as the owner of the file (to target signal delivery)
ROCPROFSYS_REQUIRE(fcntl(m_fd, F_SETOWN, gettid()) != -1)
<< "failed to set the owner of the perf_event file";
}
void
perf_event::iterator::next()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
struct perf_event_header _hdr;
// Copy out the record header
perf_event::copy_from_ring_buffer(m_mapping, m_index, &_hdr,
sizeof(struct perf_event_header));
// Advance to the next record
m_index += _hdr.size;
}
perf_event::iterator::iterator(perf_event& _source, struct perf_event_mmap_page* _mapping)
: m_source{ _source }
, m_mapping{ _mapping }
{
if(_mapping != nullptr)
{
m_index = _mapping->data_tail;
m_head = _mapping->data_head;
}
else
{
m_index = 0;
m_head = 0;
}
}
perf_event::iterator::~iterator()
{
if(m_mapping != nullptr)
{
m_mapping->data_tail = m_index;
}
}
perf_event::iterator&
perf_event::iterator::operator++()
{
next();
return *this;
}
bool
perf_event::iterator::operator!=(const iterator& other) const
{
return has_data() != other.has_data();
}
perf_event::record
perf_event::iterator::get()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
// Copy out the record header
perf_event::copy_from_ring_buffer(m_mapping, m_index, _buf,
sizeof(struct perf_event_header));
// Get a pointer to the header
struct perf_event_header* header = reinterpret_cast<struct perf_event_header*>(_buf);
// Copy out the entire record
perf_event::copy_from_ring_buffer(m_mapping, m_index, _buf, header->size);
return perf_event::record(&m_source, header);
}
bool
perf_event::iterator::has_data() const
{
// If there is no ring buffer, there is no data
if(m_mapping == nullptr)
{
return false;
}
// If there isn't enough data in the ring buffer to hold a header, there is no data
if(m_index + sizeof(struct perf_event_header) >= m_head)
{
return false;
}
struct perf_event_header _hdr;
perf_event::copy_from_ring_buffer(m_mapping, m_index, &_hdr,
sizeof(struct perf_event_header));
// If the first record is larger than the available data, nothing can be read
if(m_index + _hdr.size > m_head)
{
return false;
}
return true;
}
void
perf_event::copy_from_ring_buffer(struct perf_event_mmap_page* _mapping, ptrdiff_t _index,
void* _dest, size_t _nbytes)
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
uintptr_t _base = reinterpret_cast<uintptr_t>(_mapping) + sizes.page;
size_t _beg_idx = _index % sizes.data;
size_t _end_idx = _beg_idx + _nbytes;
if(_end_idx <= sizes.data)
{
memcpy(_dest, reinterpret_cast<void*>(_base + _beg_idx), _nbytes);
}
else
{
size_t _chunk_size2 = _end_idx - sizes.data;
size_t _chunk_size1 = _nbytes - _chunk_size2;
void* _dest2 =
reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_dest) + _chunk_size1);
memcpy(_dest, reinterpret_cast<void*>(_base + _beg_idx), _chunk_size1);
memcpy(_dest2, reinterpret_cast<void*>(_base), _chunk_size2);
}
}
uint64_t
perf_event::record::get_ip() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::ip))
<< "Record does not have an ip field (" << is_sample() << "|" << m_source << ")";
return *locate_field<sample::ip, uint64_t*>();
}
uint64_t
perf_event::record::get_pid() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::pid_tid))
<< "Record does not have a `pid` field (" << is_sample() << "|" << m_source
<< ")";
return locate_field<sample::pid_tid, uint32_t*>()[0];
}
uint64_t
perf_event::record::get_tid() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::pid_tid))
<< "Record does not have a `tid` field (" << is_sample() << "|" << m_source
<< ")";
return locate_field<sample::pid_tid, uint32_t*>()[1];
}
uint64_t
perf_event::record::get_time() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::time))
<< "Record does not have a 'time' field (" << is_sample() << "|" << m_source
<< ")";
return *locate_field<sample::time, uint64_t*>();
}
uint64_t
perf_event::record::get_period() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::period))
<< "Record does not have a 'period' field (" << is_sample() << "|" << m_source
<< ")";
return *locate_field<sample::period, uint64_t*>();
}
uint32_t
perf_event::record::get_cpu() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::cpu))
<< "Record does not have a 'cpu' field (" << is_sample() << "|" << m_source
<< ")";
return *locate_field<sample::cpu, uint32_t*>();
}
container::c_array<uint64_t>
perf_event::record::get_callchain() const
{
ROCPROFSYS_ASSERT(is_sample() && m_source != nullptr &&
m_source->is_sampling(sample::callchain))
<< "Record does not have a callchain field (" << is_sample() << "|" << m_source
<< ")";
uint64_t* _base = locate_field<sample::callchain, uint64_t*>();
uint64_t _size = *_base;
// Advance the callchain array pointer past the size
++_base;
return container::wrap_c_array(_base, _size);
}
template <sample SampleT, typename Tp>
Tp
perf_event::record::locate_field() const
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
uintptr_t p =
reinterpret_cast<uintptr_t>(m_header) + sizeof(struct perf_event_header);
// Walk through the fields in the sample structure. Once the requested field is
// reached, return. Skip past any unrequested fields that are included in the sample
// type
// ip
if constexpr(SampleT == sample::ip) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::ip)) p += sizeof(uint64_t);
// pid, tid
if constexpr(SampleT == sample::pid_tid) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::pid_tid))
p += sizeof(uint32_t) + sizeof(uint32_t);
// time
if constexpr(SampleT == sample::time) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::time)) p += sizeof(uint64_t);
// addr
if constexpr(SampleT == sample::addr) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::addr)) p += sizeof(uint64_t);
// id
if constexpr(SampleT == sample::id) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::id)) p += sizeof(uint64_t);
// stream_id
if constexpr(SampleT == sample::stream_id) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::stream_id))
p += sizeof(uint64_t);
// cpu
if constexpr(SampleT == sample::cpu) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::cpu))
p += sizeof(uint32_t) + sizeof(uint32_t);
// period
if constexpr(SampleT == sample::period) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::period))
p += sizeof(uint64_t);
// value
if constexpr(SampleT == sample::read) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::read))
{
uint64_t read_format = m_source->get_read_format();
if(read_format & PERF_FORMAT_GROUP)
{
// Get the number of values in the read format structure
uint64_t nr = *reinterpret_cast<uint64_t*>(p);
// The default size of each entry is a u64
size_t sz = sizeof(uint64_t);
// If requested, the id will be included with each value
if(read_format & PERF_FORMAT_ID) sz += sizeof(uint64_t);
// Skip over the entry count, and each entry
p += sizeof(uint64_t) + nr * sz;
}
else
{
// Skip over the value
p += sizeof(uint64_t);
// Skip over the id, if included
if(read_format & PERF_FORMAT_ID) p += sizeof(uint64_t);
}
// Skip over the time_enabled field
if(read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) p += sizeof(uint64_t);
// Skip over the time_running field
if(read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) p += sizeof(uint64_t);
}
// callchain
if constexpr(SampleT == sample::callchain) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::callchain))
{
uint64_t nr = *reinterpret_cast<uint64_t*>(p);
p += sizeof(uint64_t) + (nr * sizeof(uint64_t));
}
// raw
if constexpr(SampleT == sample::raw) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::raw))
{
uint32_t raw_size = *reinterpret_cast<uint32_t*>(p);
p += sizeof(uint32_t) + raw_size;
}
// branch_stack
if constexpr(SampleT == sample::branch_stack) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::branch_stack))
ROCPROFSYS_FATAL << "Branch stack sampling is not supported";
// regs
if constexpr(SampleT == sample::regs) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::regs))
ROCPROFSYS_FATAL << "Register sampling is not supported";
// stack
if constexpr(SampleT == sample::stack) return reinterpret_cast<Tp>(p);
if(m_source != nullptr && m_source->is_sampling(sample::stack))
ROCPROFSYS_FATAL << "Stack sampling is not supported";
// end
if constexpr(SampleT == sample::last) return reinterpret_cast<Tp>(p);
ROCPROFSYS_FATAL << "Unsupported sample field requested!";
if constexpr(std::is_pointer<Tp>::value)
return nullptr;
else
return Tp{};
}
namespace
{
inline auto&
get_instances()
{
using thread_data_t = thread_data<identity<std::unique_ptr<perf_event>>, perf_event>;
static auto& _v = thread_data_t::instance(construct_on_init{});
return _v;
}
} // namespace
std::unique_ptr<perf_event>&
get_instance(int64_t _tid)
{
auto& _data = get_instances();
if(static_cast<size_t>(_tid) >= _data->size())
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
_data->resize(_tid + 1);
}
return _data->at(_tid);
}
} // namespace perf
} // namespace rocprofsys
@@ -0,0 +1,212 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/containers/c_array.hpp"
#include "core/defines.hpp"
#include "core/locking.hpp"
#include "core/perf.hpp"
#include <timemory/backends/papi.hpp>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <linux/perf_event.h>
#include <regex>
#include <set>
#include <string>
#include <sys/types.h>
namespace rocprofsys
{
namespace perf
{
struct perf_event
{
static constexpr uint32_t max_batch_size = 32;
struct record;
struct sample_record;
class iterator;
/// Default constructor
perf_event() = default;
/// Move constructor
perf_event(perf_event&& other) noexcept;
/// Close the perf event file and unmap the ring buffer
~perf_event();
/// Move assignment is supported
perf_event& operator=(perf_event&& other) noexcept;
perf_event(const perf_event&) = delete;
perf_event& operator=(const perf_event&) = delete;
/// Open a perf_event file using the given options structure
std::optional<std::string> open(struct perf_event_attr& pe, pid_t pid = 0,
int cpu = -1);
std::optional<std::string> open(double, uint32_t = 0, pid_t pid = 0, int cpu = -1);
/// Return file descriptor
long get_fileno() const;
/// Read event count
uint64_t get_count() const;
/// Get the batch size
uint32_t get_batch_size() const { return m_batch_size; }
/// Start counting events and collecting samples
bool start() const;
/// Stop counting events
bool stop() const;
/// Check if counting events and collecting samples
bool is_open() const;
/// Close the perf_event file and unmap the ring buffer
void close();
/// Configure the perf_event file to deliver a signal when samples are ready to be
/// processed
void set_ready_signal(int sig) const;
/// Check if this perf_event was configured to collect a type of sample data
inline bool is_sampling(sample s) const
{
return (m_sample_type & static_cast<uint64_t>(s)) != 0u;
}
/// Get the configuration for this perf_event's read format
inline uint64_t get_read_format() const { return m_read_format; }
/// A generic record type
struct record
{
friend class perf_event::iterator;
record() = default;
~record() = default;
record(const record&) = default;
record(record&&) noexcept = default;
record& operator=(const record&) = default;
record& operator=(record&&) noexcept = default;
bool is_valid() const { return (m_source != nullptr && m_header != nullptr); }
operator bool() const { return is_valid(); }
record_type get_type() const { return static_cast<record_type>(m_header->type); }
inline bool is_mmap() const { return get_type() == record_type::mmap; }
inline bool is_lost() const { return get_type() == record_type::lost; }
inline bool is_comm() const { return get_type() == record_type::comm; }
inline bool is_exit() const { return get_type() == record_type::exit; }
inline bool is_throttle() const { return get_type() == record_type::throttle; }
inline bool is_unthrottle() const
{
return get_type() == record_type::unthrottle;
}
inline bool is_fork() const { return get_type() == record_type::fork; }
inline bool is_read() const { return get_type() == record_type::read; }
inline bool is_sample() const { return get_type() == record_type::sample; }
inline bool is_mmap2() const { return get_type() == record_type::mmap2; }
uint64_t get_ip() const;
uint64_t get_pid() const;
uint64_t get_tid() const;
uint64_t get_time() const;
uint64_t get_period() const;
uint32_t get_cpu() const;
container::c_array<uint64_t> get_callchain() const;
private:
record(const perf_event* source, struct perf_event_header* header)
: m_source(source)
, m_header(header)
{}
template <sample SampleT, typename Tp = void*>
Tp locate_field() const;
const perf_event* m_source = nullptr;
struct perf_event_header* m_header = nullptr;
};
class iterator
{
public:
iterator(perf_event& source, struct perf_event_mmap_page* mapping);
~iterator();
void next();
record get();
bool has_data() const;
iterator& operator++();
record operator*() { return get(); }
bool operator!=(const iterator& other) const;
private:
perf_event& m_source;
size_t m_index = 0;
size_t m_head = 0;
struct perf_event_mmap_page* m_mapping = nullptr;
// Buffer to hold the current record. Just a hack until records play nice with the
// ring buffer
uint8_t _buf[4096];
};
/// Get an iterator to the beginning of the memory mapped ring buffer
iterator begin() { return iterator(*this, m_mapping); }
/// Get an iterator to the end of the memory mapped ring buffer
iterator end() { return iterator(*this, nullptr); }
private:
// Copy data out of the mmap ring buffer
static void copy_from_ring_buffer(struct perf_event_mmap_page* mapping,
ptrdiff_t index, void* dest, size_t bytes);
uint32_t m_batch_size = 10;
/// File descriptor for the perf event
long m_fd = -1;
/// Memory mapped perf event region
struct perf_event_mmap_page* m_mapping = nullptr;
/// The sample type from this perf_event's configuration
uint64_t m_sample_type = 0;
/// The read format from this perf event's configuration
uint64_t m_read_format = 0;
};
/// provides thread-local instance of perf_event
std::unique_ptr<perf_event>&
get_instance(int64_t _tid);
} // namespace perf
} // namespace rocprofsys
@@ -0,0 +1,241 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/process_sampler.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "library/cpu_freq.hpp"
#include "library/rocm_smi.hpp"
#include "library/runtime.hpp"
#include <memory>
#include <vector>
namespace rocprofsys
{
namespace process_sampler
{
namespace
{
using promise_t = std::promise<void>;
std::unique_ptr<promise_t> polling_finished = {};
std::vector<std::unique_ptr<instance>> instances = {};
bool&
is_initialized()
{
static bool _v = false;
return _v;
}
std::unique_ptr<std::thread>&
get_thread()
{
static std::unique_ptr<std::thread> _v;
return _v;
}
std::atomic<State>&
get_sampler_state()
{
static std::atomic<State> _v{ State::PreInit };
return _v;
}
std::atomic<bool>&
get_sampler_is_sampling()
{
static std::atomic<bool> _v{ false };
return _v;
}
} // namespace
void
sampler::poll(std::atomic<State>* _state, nsec_t _interval, promise_t* _ready)
{
threading::offset_this_id(true);
threading::set_thread_name("omni.sampler");
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
// notify thread started
if(_ready) _ready->set_value();
for(auto& itr : instances)
itr->config();
ROCPROFSYS_VERBOSE(
1, "Background process sampling polling at an interval of %f seconds...\n",
std::chrono::duration_cast<std::chrono::duration<double>>(_interval).count());
auto _duration = config::get_process_sampling_duration();
if(_duration < 0.0) _duration = config::get_sampling_duration();
bool _has_duration = (_duration > 0.0);
auto _now = std::chrono::steady_clock::now();
auto _end =
_now + std::chrono::nanoseconds{ static_cast<uint64_t>(_duration * units::sec) };
while(_state && _state->load() < State::Finalized && get_state() < State::Finalized)
{
std::this_thread::sleep_until(_now);
if(_state->load() != State::Active) continue;
if(get_state() >= State::Finalized) break;
if(get_state() != State::Active) continue;
get_sampler_is_sampling().store(true);
for(auto& itr : instances)
itr->sample();
get_sampler_is_sampling().store(false);
if(_has_duration && _now >= _end) break;
_now = std::chrono::steady_clock::now() + _interval;
}
// ensure this is always false
get_sampler_is_sampling().store(false);
if(_has_duration && _now >= _end && get_state() < State::Finalized)
{
ROCPROFSYS_VERBOSE(
1,
"Background process sampling duration of %f seconds has elapsed. "
"Shutting down process sampling...\n",
_duration);
}
ROCPROFSYS_CONDITIONAL_BASIC_PRINT(get_debug(),
"Thread sampler polling completed...\n");
if(polling_finished) polling_finished->set_value();
}
void
sampler::setup()
{
if(!get_use_process_sampling())
{
ROCPROFSYS_DEBUG("Background sampler is disabled...\n");
return;
}
ROCPROFSYS_VERBOSE(1, "Setting up background sampler...\n");
// shutdown if already running
shutdown();
if(get_use_rocm_smi())
{
auto& _rocm_smi = instances.emplace_back(std::make_unique<instance>());
_rocm_smi->setup = []() { rocm_smi::setup(); };
_rocm_smi->shutdown = []() { rocm_smi::shutdown(); };
_rocm_smi->post_process = []() { rocm_smi::post_process(); };
_rocm_smi->config = []() { rocm_smi::config(); };
_rocm_smi->sample = []() { rocm_smi::sample(); };
}
auto& _cpu_freq = instances.emplace_back(std::make_unique<instance>());
_cpu_freq->setup = []() { cpu_freq::setup(); };
_cpu_freq->shutdown = []() { cpu_freq::shutdown(); };
_cpu_freq->post_process = []() { cpu_freq::post_process(); };
_cpu_freq->config = []() { cpu_freq::config(); };
_cpu_freq->sample = []() { cpu_freq::sample(); };
for(auto& itr : instances)
itr->setup();
polling_finished = std::make_unique<promise_t>();
auto _freq = get_process_sampling_freq();
uint64_t _msec_freq = (1.0 / _freq) * 1.0e3;
polling_finished = std::make_unique<promise_t>();
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
set_state(State::PreInit);
get_thread() = std::make_unique<std::thread>(&poll<msec_t>, &get_sampler_state(),
msec_t{ _msec_freq }, nullptr);
set_state(State::Active);
}
void
sampler::shutdown()
{
// set the local sampler state to finalized
set_state(State::Finalized);
// shutdown all components
for(auto& itr : instances)
itr->shutdown();
auto& _thread = get_thread();
if(_thread)
{
size_t _nitr = 0;
constexpr size_t _nitr_max = 100;
uint64_t _freq = (1.0 / get_process_sampling_freq()) * 1.0e3;
// wait until the sampler is no longer sampling
std::this_thread::sleep_for(msec_t{ _freq });
while(get_sampler_is_sampling().load())
{
if(_nitr++ > _nitr_max) break;
}
// during CI, throw an error if polling_finished is not valid
ROCPROFSYS_CI_THROW(!polling_finished, "polling_finished is not valid\n");
if(polling_finished)
{
// wait for the thread to finish
auto _fut = polling_finished->get_future();
_fut.wait_for(msec_t{ 10 * _freq });
_thread->join();
}
else
{
// cancel the thread and detach
std::this_thread::sleep_for(msec_t{ 10 * _freq });
pthread_cancel(_thread->native_handle());
_thread->detach();
}
_thread = std::unique_ptr<std::thread>{ nullptr };
polling_finished = std::unique_ptr<promise_t>{};
}
is_initialized() = false;
}
void
sampler::post_process()
{
for(auto& itr : instances)
itr->post_process();
instances.clear();
}
void
sampler::set_state(state_t _state)
{
get_sampler_state().store(_state);
}
} // namespace process_sampler
} // namespace rocprofsys
@@ -0,0 +1,103 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/state.hpp"
#include "library/thread_data.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <future>
#include <memory>
#include <type_traits>
#include <vector>
namespace rocprofsys
{
namespace process_sampler
{
struct instance
{
std::function<void()> setup = []() {};
std::function<void()> shutdown = []() {};
std::function<void()> config = []() {};
std::function<void()> sample = []() {};
std::function<void()> post_process = []() {};
};
//
struct sampler
{
using msec_t = std::chrono::milliseconds;
using usec_t = std::chrono::microseconds;
using nsec_t = std::chrono::nanoseconds;
using promise_t = std::promise<void>;
using future_t = std::future<void>;
using state_t = State;
using timestamp_t = int64_t;
template <typename Tp = nsec_t,
std::enable_if_t<!std::is_same_v<std::decay_t<Tp>, nsec_t>, int> = 0>
static void poll(std::atomic<state_t>* _state, Tp&& _interval, promise_t*);
static void setup();
static void shutdown();
static void post_process();
static void set_state(state_t);
static void poll(std::atomic<state_t>* _state, nsec_t _interval, promise_t*);
};
//
template <
typename Tp,
std::enable_if_t<!std::is_same_v<std::decay_t<Tp>, std::chrono::nanoseconds>, int>>
void
sampler::poll(std::atomic<state_t>* _state, Tp&& _interval, promise_t* _prom)
{
poll(_state, std::chrono::duration_cast<nsec_t>(_interval), _prom);
}
//
inline void
setup()
{
sampler::setup();
}
inline void
shutdown()
{
sampler::shutdown();
}
inline void
post_process()
{
sampler::post_process();
}
//
} // namespace process_sampler
} // namespace rocprofsys
@@ -0,0 +1,234 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/ptl.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/state.hpp"
#include "library/runtime.hpp"
#include "library/sampling.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include <PTL/ThreadPool.hh>
#include <PTL/UserTaskQueue.hh>
#include <timemory/backends/threading.hpp>
#include <timemory/utility/declaration.hpp>
namespace rocprofsys
{
namespace tasking
{
namespace
{
auto _thread_pool_cfg = []() {
int64_t _nthreads = 0;
if(config::settings_are_configured())
{
_nthreads = config::get_thread_pool_size();
}
else
{
const int64_t _max_threads = std::thread::hardware_concurrency() / 2;
const int64_t _min_threads = 1;
_nthreads = get_env<int64_t>("ROCPROFSYS_THREAD_POOL_SIZE", -1, false);
if(_nthreads == -1)
{
_nthreads = 4;
if(_nthreads > _max_threads) _nthreads = _max_threads;
if(_nthreads < _min_threads) _nthreads = _min_threads;
tim::set_env("ROCPROFSYS_THREAD_POOL_SIZE", _nthreads, 0);
}
}
static char buffer[sizeof(PTL::UserTaskQueue)];
static auto* _task_queue = new((void*) buffer) PTL::UserTaskQueue(_nthreads);
PTL::ThreadPool::Config _v{};
_v.init = true;
_v.use_affinity = false;
_v.use_tbb = false;
_v.verbose = -1;
_v.initializer = []() {
thread_info::init(true);
threading::set_thread_name(
JOIN('.', "ptl", PTL::Threading::GetThreadId()).c_str());
set_thread_state(ThreadState::Disabled);
sampling::block_signals();
};
_v.finalizer = []() {};
_v.priority = 5;
_v.pool_size = _nthreads;
_v.task_queue = _task_queue;
return _v;
};
auto&
get_thread_pool_state()
{
static auto _v = State::PreInit;
return _v;
}
PTL::ThreadPool&
get_thread_pool()
{
static auto _cfg = _thread_pool_cfg();
static auto* _v =
(get_thread_pool_state() = State::Active, new PTL::ThreadPool{ _cfg });
return *_v;
}
} // namespace
namespace general
{
namespace
{
auto&
get_thread_pool_state()
{
static auto _v = State::PreInit;
return _v;
}
} // namespace
} // namespace general
namespace roctracer
{
namespace
{
auto&
get_thread_pool_state()
{
static auto _v = State::PreInit;
return _v;
}
} // namespace
} // namespace roctracer
void
setup()
{
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
(void) get_thread_pool();
}
void
join()
{
if(roctracer::get_thread_pool_state() == State::Active)
{
ROCPROFSYS_DEBUG_F("waiting for all roctracer tasks to complete...\n");
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
roctracer::get_task_group(i).join();
}
else
{
ROCPROFSYS_DEBUG_F("roctracer thread-pool is not active...\n");
}
if(general::get_thread_pool_state() == State::Active)
{
ROCPROFSYS_DEBUG_F("waiting for all general tasks to complete...\n");
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
general::get_task_group(i).join();
}
}
void
shutdown()
{
if(roctracer::get_thread_pool_state() == State::Active)
{
ROCPROFSYS_DEBUG_F("Waiting on completion of roctracer tasks...\n");
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
{
roctracer::get_task_group(i).join();
roctracer::get_task_group(i).clear();
roctracer::get_task_group(i).set_pool(nullptr);
}
roctracer::get_thread_pool_state() = State::Finalized;
}
else
{
ROCPROFSYS_DEBUG_F("roctracer thread-pool is not active...\n");
}
if(general::get_thread_pool_state() == State::Active)
{
ROCPROFSYS_DEBUG_F("Waiting on completion of general tasks...\n");
for(size_t i = 0; i < thread_info::get_peak_num_threads(); ++i)
{
general::get_task_group(i).join();
general::get_task_group(i).clear();
general::get_task_group(i).set_pool(nullptr);
}
general::get_thread_pool_state() = State::Finalized;
}
if(get_thread_pool_state() == State::Active)
{
ROCPROFSYS_DEBUG_F("Destroying the rocprof-sys thread pool...\n");
get_thread_pool().destroy_threadpool();
get_thread_pool_state() = State::Finalized;
}
else
{
ROCPROFSYS_DEBUG_F("thread-pool is not active...\n");
}
}
size_t
initialize_threadpool(size_t _v)
{
return get_thread_pool().initialize_threadpool(_v);
}
PTL::TaskGroup<void>&
general::get_task_group(int64_t _tid)
{
struct local
{};
using thread_data_t = thread_data<PTL::TaskGroup<void>, local>;
static thread_local auto& _v =
thread_data_t::instance(construct_on_thread{ _tid }, &tasking::get_thread_pool());
return *_v;
}
PTL::TaskGroup<void>&
roctracer::get_task_group(int64_t _tid)
{
struct local
{};
using thread_data_t = thread_data<PTL::TaskGroup<void>, local>;
static thread_local auto& _v = (roctracer::get_thread_pool_state() = State::Active,
thread_data_t::instance(construct_on_thread{ _tid },
&tasking::get_thread_pool()));
return *_v;
}
} // namespace tasking
} // namespace rocprofsys
@@ -0,0 +1,71 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/defines.hpp"
#include "core/utility.hpp"
#include <PTL/PTL.hh>
#include <mutex>
namespace rocprofsys
{
namespace tasking
{
void
setup();
void
join();
void
shutdown();
size_t initialize_threadpool(size_t);
//--------------------------------------------------------------------------------------//
//
// general
//
//--------------------------------------------------------------------------------------//
namespace general
{
PTL::TaskGroup<void>&
get_task_group(int64_t _tid = utility::get_thread_index());
}
//--------------------------------------------------------------------------------------//
//
// roctracer
//
//--------------------------------------------------------------------------------------//
namespace roctracer
{
PTL::TaskGroup<void>&
get_task_group(int64_t _tid = utility::get_thread_index());
} // namespace roctracer
} // namespace tasking
} // namespace rocprofsys
@@ -0,0 +1,87 @@
// MIT License
//
// Copyright (c) 2020, The Regents of the University of California,
// through Lawrence Berkeley National Laboratory (subject to receipt of any
// required approvals from the U.S. Dept. of Energy). 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 "library/components/rcclp.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/dynamic_library.hpp"
#include "core/rccl.hpp"
#include "core/timemory.hpp"
#include "library/components/category_region.hpp"
#include <timemory/timemory.hpp>
#include <dlfcn.h>
#include <limits>
#include <memory>
#include <set>
#include <unordered_map>
namespace
{
uint64_t global_id = std::numeric_limits<uint64_t>::max();
}
namespace rocprofsys
{
namespace rcclp
{
void
configure()
{}
void
setup()
{
configure();
// make sure the symbols are loaded to be wrapped
dynamic_library _librccl{
"ROCPROFSYS_RCCL_LIBRARY", "librccl.so", RTLD_NOW | RTLD_GLOBAL, true, true, true
};
auto _use_data = tim::get_env("ROCPROFSYS_RCCLP_COMM_DATA", get_use_timemory());
if(!get_use_timemory())
{
trait::runtime_enabled<component::comm_data>::set(false);
trait::runtime_enabled<component::comm_data_tracker_t>::set(false);
}
else
{
trait::runtime_enabled<component::comm_data>::set(_use_data);
trait::runtime_enabled<component::comm_data_tracker_t>::set(_use_data);
}
component::configure_rcclp();
global_id = component::activate_rcclp();
}
void
shutdown()
{
if(global_id < std::numeric_limits<uint64_t>::max())
component::deactivate_rcclp(global_id);
}
} // namespace rcclp
} // namespace rocprofsys
@@ -0,0 +1,57 @@
// MIT License
//
// Copyright (c) 2020, The Regents of the University of California,
// through Lawrence Berkeley National Laboratory (subject to receipt of any
// required approvals from the U.S. Dept. of Energy). 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 "core/defines.hpp"
namespace rocprofsys
{
namespace rcclp
{
void
configure();
void
setup();
void
shutdown();
#if !defined(ROCPROFSYS_USE_RCCL) || \
(defined(ROCPROFSYS_USE_RCCL) && ROCPROFSYS_USE_RCCL == 0)
inline void
configure()
{}
inline void
setup()
{}
inline void
shutdown()
{}
#endif
} // namespace rcclp
} // namespace rocprofsys
@@ -0,0 +1,253 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/rocm.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/dynamic_library.hpp"
#include "core/gpu.hpp"
#include "library/components/rocprofiler.hpp"
#include "library/components/roctracer.hpp"
#include "library/rocm/hsa_rsrc_factory.hpp"
#include "library/rocm_smi.hpp"
#include "library/rocprofiler.hpp"
#include "library/roctracer.hpp"
#include "library/runtime.hpp"
#include "library/thread_data.hpp"
#include "library/tracing.hpp"
#include <timemory/backends/cpu.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/utility/types.hpp>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <mutex>
#include <tuple>
#if defined(ROCPROFSYS_USE_ROCPROFILER) && ROCPROFSYS_USE_ROCPROFILER > 0
# include <rocprofiler.h>
#endif
using namespace rocprofsys;
namespace rocprofsys
{
namespace rocm
{
std::mutex rocm_mutex = {};
bool is_loaded = false;
bool on_load_trace = (get_env<int>("ROCP_ONLOAD_TRACE", 0) > 0);
} // namespace rocm
} // namespace rocprofsys
#if defined(ROCPROFSYS_USE_ROCPROFILER) && ROCPROFSYS_USE_ROCPROFILER > 0
std::ostream&
operator<<(std::ostream& _os, const rocprofiler_settings_t& _v)
{
# define ROCPROF_SETTING_FIELD_STR(NAME) JOIN('=', # NAME, _v.NAME)
_os << JOIN(
", ", ROCPROF_SETTING_FIELD_STR(intercept_mode),
ROCPROF_SETTING_FIELD_STR(code_obj_tracking),
ROCPROF_SETTING_FIELD_STR(memcopy_tracking),
ROCPROF_SETTING_FIELD_STR(trace_size), ROCPROF_SETTING_FIELD_STR(trace_local),
ROCPROF_SETTING_FIELD_STR(timeout), ROCPROF_SETTING_FIELD_STR(timestamp_on),
ROCPROF_SETTING_FIELD_STR(hsa_intercepting),
ROCPROF_SETTING_FIELD_STR(k_concurrent), ROCPROF_SETTING_FIELD_STR(opt_mode),
ROCPROF_SETTING_FIELD_STR(obj_dumping));
return _os;
}
#endif
// HSA-runtime tool on-load method
extern "C"
{
#if defined(ROCPROFSYS_USE_ROCPROFILER) && ROCPROFSYS_USE_ROCPROFILER > 0
void OnUnloadTool()
{
ROCPROFSYS_BASIC_VERBOSE_F(2 || rocm::on_load_trace, "Unloading...\n");
rocm::lock_t _lk{ rocm::rocm_mutex, std::defer_lock };
if(!_lk.owns_lock()) _lk.lock();
if(!rocm::is_loaded)
{
ROCPROFSYS_BASIC_VERBOSE_F(1 || rocm::on_load_trace,
"rocprofiler is not loaded\n");
return;
}
rocm::is_loaded = false;
_lk.unlock();
// stop_top_level_timer_if_necessary();
// Final resources cleanup
rocprofsys::rocprofiler::rocm_cleanup();
}
void OnLoadToolProp(rocprofiler_settings_t* settings)
{
using ::rocprofiler::util::HsaRsrcFactory;
if(!config::get_use_rocprofiler() || config::get_rocm_events().empty()) return;
ROCPROFSYS_BASIC_VERBOSE_F(2 || rocm::on_load_trace, "Loading...\n");
rocm::lock_t _lk{ rocm::rocm_mutex, std::defer_lock };
if(!_lk.owns_lock()) _lk.lock();
if(rocm::is_loaded)
{
ROCPROFSYS_BASIC_VERBOSE_F(1 || rocm::on_load_trace,
"rocprofiler is already loaded\n");
return;
}
rocm::is_loaded = true;
_lk.unlock();
// Enable timestamping
settings->timestamp_on = 1;
settings->intercept_mode = 1;
settings->hsa_intercepting = 1;
settings->k_concurrent = 0;
settings->obj_dumping = 0;
// settings->code_obj_tracking = 0;
// settings->memcopy_tracking = 0;
// settings->trace_local = 1;
// settings->opt_mode = 1;
// settings->trace_size = 0;
// settings->timeout = 0;
ROCPROFSYS_BASIC_VERBOSE_F(1 || rocm::on_load_trace, "rocprofiler settings: %s\n",
JOIN("", *settings).c_str());
// Initialize profiling
rocprofsys::rocprofiler::rocm_initialize();
HsaRsrcFactory::Instance().PrintGpuAgents("ROCm");
}
#endif
bool OnLoad(HsaApiTable* table, uint64_t runtime_version, uint64_t failed_tool_count,
const char* const* failed_tool_names)
{
tim::consume_parameters(table, runtime_version, failed_tool_count,
failed_tool_names);
static bool _once = false;
if(_once) return true;
_once = true;
ROCPROFSYS_BASIC_VERBOSE_F(2 || rocm::on_load_trace, "Loading...\n");
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
if(!tim::get_env("ROCPROFSYS_INIT_TOOLING", true)) return true;
if(!tim::settings::enabled()) return true;
roctracer_is_init() = true;
ROCPROFSYS_BASIC_VERBOSE_F(1 || rocm::on_load_trace, "Loading ROCm tooling...\n");
if(!config::settings_are_configured() && get_state() < State::Active)
rocprofsys_init_tooling_hidden();
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
#if ROCPROFSYS_HIP_VERSION < 50300
ROCPROFSYS_VERBOSE_F(1 || rocm::on_load_trace,
"Computing the roctracer clock skew...\n");
(void) rocprofsys::get_clock_skew();
#endif
if(get_use_process_sampling() && get_use_rocm_smi())
{
ROCPROFSYS_VERBOSE_F(1 || rocm::on_load_trace,
"Setting rocm_smi state to active...\n");
rocm_smi::set_state(State::Active);
}
comp::roctracer::setup(static_cast<void*>(table), rocm::on_load_trace);
#if defined(ROCPROFSYS_USE_ROCPROFILER) && ROCPROFSYS_USE_ROCPROFILER > 0
bool _force_rocprofiler_init =
tim::get_env("ROCPROFSYS_FORCE_ROCPROFILER_INIT", false, false);
#else
bool _force_rocprofiler_init = false;
#endif
bool _success = true;
bool _is_empty =
(config::settings_are_configured() && config::get_rocm_events().empty());
if(_force_rocprofiler_init || (get_use_rocprofiler() && !_is_empty))
{
#if ROCPROFSYS_HIP_VERSION < 50500
auto _rocprof = dynamic_library{
"ROCPROFSYS_ROCPROFILER_LIBRARY",
find_library_path(
"librocprofiler64.so", { "ROCPROFSYS_ROCM_PATH", "ROCM_PATH" },
{ ROCPROFSYS_DEFAULT_ROCM_PATH },
{ "lib", "lib64", "rocprofiler/lib", "rocprofiler/lib64" }),
(RTLD_LAZY | RTLD_GLOBAL), false
};
ROCPROFSYS_VERBOSE_F(1 || rocm::on_load_trace,
"Loading rocprofiler library (%s=%s)...\n",
_rocprof.envname.c_str(), _rocprof.filename.c_str());
_rocprof.open();
on_load_t _rocprof_load = nullptr;
_success = _rocprof.invoke("OnLoad", _rocprof_load, table, runtime_version,
failed_tool_count, failed_tool_names);
ROCPROFSYS_CONDITIONAL_PRINT_F(!_success,
"Warning! Invoking rocprofiler's OnLoad "
"failed! ROCPROFSYS_ROCPROFILER_LIBRARY=%s\n",
_rocprof.filename.c_str());
ROCPROFSYS_CI_THROW(!_success,
"Warning! Invoking rocprofiler's OnLoad "
"failed! ROCPROFSYS_ROCPROFILER_LIBRARY=%s\n",
_rocprof.filename.c_str());
#endif
}
else
{
using ::rocprofiler::util::HsaRsrcFactory;
HsaRsrcFactory::Instance().PrintGpuAgents("ROCm");
}
gpu::add_hip_device_metadata();
ROCPROFSYS_BASIC_VERBOSE_F(2 || rocm::on_load_trace, "Loading... %s\n",
(_success) ? "Done" : "Failed");
return _success;
}
// HSA-runtime on-unload method
void OnUnload()
{
ROCPROFSYS_BASIC_VERBOSE_F(2 || rocm::on_load_trace, "Unloading...\n");
rocprofsys_finalize_hidden();
ROCPROFSYS_BASIC_VERBOSE_F(2 || rocm::on_load_trace, "Unloading... Done\n");
}
}
@@ -0,0 +1,58 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/defines.hpp"
#if defined(ROCPROFSYS_USE_ROCPROFILER) && ROCPROFSYS_USE_ROCPROFILER > 0
# include <rocprofiler.h>
#endif
#include <cstdint>
#include <mutex>
namespace rocprofsys
{
namespace rocm
{
using lock_t = std::unique_lock<std::mutex>;
extern std::mutex rocm_mutex;
extern bool is_loaded;
} // namespace rocm
} // namespace rocprofsys
extern "C"
{
struct HsaApiTable;
using on_load_t = bool (*)(HsaApiTable*, uint64_t, uint64_t, const char* const*);
bool OnLoad(HsaApiTable* table, uint64_t runtime_version, uint64_t failed_tool_count,
const char* const* failed_tool_names) ROCPROFSYS_PUBLIC_API;
void OnUnload() ROCPROFSYS_PUBLIC_API;
#if defined(ROCPROFSYS_USE_ROCPROFILER) && ROCPROFSYS_USE_ROCPROFILER > 0
void OnLoadToolProp(rocprofiler_settings_t* settings) ROCPROFSYS_PUBLIC_API;
void OnUnloadTool() ROCPROFSYS_PUBLIC_API;
#endif
}
@@ -0,0 +1,7 @@
#
if(ROCPROFSYS_USE_ROCPROFILER OR ROCPROFSYS_USE_ROCTRACER)
target_sources(
rocprofiler-systems-object-library
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hsa_rsrc_factory.hpp
${CMAKE_CURRENT_LIST_DIR}/hsa_rsrc_factory.cpp)
endif()
@@ -0,0 +1,582 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/exception.hpp"
#define AMD_INTERNAL_BUILD 1
#include <hsa.h>
#include <hsa_api_trace.h>
#include <hsa_ext_amd.h>
#include <hsa_ext_finalize.h>
#include <hsa_ven_amd_aqlprofile.h>
#include <hsa_ven_amd_loader.h>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <vector>
#define HSA_ARGUMENT_ALIGN_BYTES 16
#define HSA_QUEUE_ALIGN_BYTES 64
#define HSA_PACKET_ALIGN_BYTES 64
#define HSA_MESSAGE_LENGTH 4096
#define CHECK_STATUS(msg, status) \
do \
{ \
if((status) != HSA_STATUS_SUCCESS) \
{ \
const char* emsg = 0; \
hsa_status_string(status, &emsg); \
char _buffer[HSA_MESSAGE_LENGTH]; \
snprintf(_buffer, HSA_MESSAGE_LENGTH - 1, "%s: %s", msg, \
emsg ? emsg : "<unknown error>"); \
throw ::rocprofsys::exception<std::runtime_error>(_buffer); \
} \
} while(0)
#define CHECK_ITER_STATUS(msg, status) \
do \
{ \
if((status) != HSA_STATUS_INFO_BREAK) \
{ \
const char* emsg = 0; \
hsa_status_string(status, &emsg); \
char _buffer[HSA_MESSAGE_LENGTH]; \
snprintf(_buffer, HSA_MESSAGE_LENGTH - 1, "%s: %s", msg, \
emsg ? emsg : "<unknown error>"); \
throw ::rocprofsys::exception<std::runtime_error>(_buffer); \
} \
} while(0)
namespace rocprofiler
{
namespace util
{
static const size_t MEM_PAGE_BYTES = 0x1000;
static const size_t MEM_PAGE_MASK = MEM_PAGE_BYTES - 1;
typedef decltype(hsa_agent_t::handle) hsa_agent_handle_t;
struct hsa_pfn_t
{
decltype(::hsa_init)* hsa_init;
decltype(::hsa_shut_down)* hsa_shut_down;
decltype(::hsa_agent_get_info)* hsa_agent_get_info;
decltype(::hsa_iterate_agents)* hsa_iterate_agents;
decltype(::hsa_queue_create)* hsa_queue_create;
decltype(::hsa_queue_destroy)* hsa_queue_destroy;
decltype(::hsa_queue_load_read_index_relaxed)* hsa_queue_load_read_index_relaxed;
decltype(::hsa_queue_load_write_index_relaxed)* hsa_queue_load_write_index_relaxed;
decltype(
::hsa_queue_add_write_index_scacq_screl)* hsa_queue_add_write_index_scacq_screl;
decltype(::hsa_signal_create)* hsa_signal_create;
decltype(::hsa_signal_destroy)* hsa_signal_destroy;
decltype(::hsa_signal_load_relaxed)* hsa_signal_load_relaxed;
decltype(::hsa_signal_store_relaxed)* hsa_signal_store_relaxed;
decltype(::hsa_signal_wait_scacquire)* hsa_signal_wait_scacquire;
decltype(::hsa_signal_store_screlease)* hsa_signal_store_screlease;
decltype(::hsa_code_object_reader_create_from_file)*
hsa_code_object_reader_create_from_file;
decltype(::hsa_executable_create_alt)* hsa_executable_create_alt;
decltype(
::hsa_executable_load_agent_code_object)* hsa_executable_load_agent_code_object;
decltype(::hsa_executable_freeze)* hsa_executable_freeze;
decltype(::hsa_executable_destroy)* hsa_executable_destroy;
decltype(::hsa_executable_get_symbol)* hsa_executable_get_symbol;
decltype(::hsa_executable_symbol_get_info)* hsa_executable_symbol_get_info;
decltype(::hsa_executable_iterate_symbols)* hsa_executable_iterate_symbols;
decltype(::hsa_system_get_info)* hsa_system_get_info;
decltype(
::hsa_system_get_major_extension_table)* hsa_system_get_major_extension_table;
decltype(::hsa_amd_agent_iterate_memory_pools)* hsa_amd_agent_iterate_memory_pools;
decltype(::hsa_amd_memory_pool_get_info)* hsa_amd_memory_pool_get_info;
decltype(::hsa_amd_memory_pool_allocate)* hsa_amd_memory_pool_allocate;
decltype(::hsa_amd_agents_allow_access)* hsa_amd_agents_allow_access;
decltype(::hsa_amd_memory_async_copy)* hsa_amd_memory_async_copy;
decltype(::hsa_amd_signal_async_handler)* hsa_amd_signal_async_handler;
decltype(
::hsa_amd_profiling_set_profiler_enabled)* hsa_amd_profiling_set_profiler_enabled;
decltype(
::hsa_amd_profiling_get_async_copy_time)* hsa_amd_profiling_get_async_copy_time;
decltype(::hsa_amd_profiling_get_dispatch_time)* hsa_amd_profiling_get_dispatch_time;
};
// Encapsulates information about a Hsa Agent such as its
// handle, name, max queue size, max wavefront size, etc.
struct AgentInfo
{
// Handle of Agent
hsa_agent_t dev_id;
// Agent type - Cpu = 0, Gpu = 1 or Dsp = 2
uint32_t dev_type;
// APU flag
bool is_apu;
// Agent system index
uint32_t dev_index;
// GFXIP name
char gfxip[64];
// Name of Agent whose length is less than 64
char name[64];
// Max size of Wavefront size
uint32_t max_wave_size;
// Max size of Queue buffer
uint32_t max_queue_size;
// Hsail profile supported by agent
hsa_profile_t profile;
// CPU/GPU/kern-arg memory pools
hsa_amd_memory_pool_t cpu_pool;
hsa_amd_memory_pool_t gpu_pool;
hsa_amd_memory_pool_t kern_arg_pool;
// The number of compute unit available in the agent.
uint32_t cu_num;
// Maximum number of waves possible in a Compute Unit.
uint32_t waves_per_cu;
// Number of SIMD's per compute unit CU
uint32_t simds_per_cu;
// Number of Shader Engines (SE) in Gpu
uint32_t se_num;
// Number of Shader Arrays Per Shader Engines in Gpu
uint32_t shader_arrays_per_se;
// SGPR/VGPR/LDS block sizes
uint32_t sgpr_block_dflt;
uint32_t sgpr_block_size;
uint32_t vgpr_block_size;
static const uint32_t lds_block_size = 128 * 4;
};
// HSA timer class
// Provides current HSA timestampa and system-clock/ns conversion API
class HsaTimer
{
public:
typedef uint64_t timestamp_t;
static const timestamp_t TIMESTAMP_MAX = UINT64_MAX;
typedef long double freq_t;
enum time_id_t
{
TIME_ID_CLOCK_REALTIME = 0,
TIME_ID_CLOCK_REALTIME_COARSE = 1,
TIME_ID_CLOCK_MONOTONIC = 2,
TIME_ID_CLOCK_MONOTONIC_COARSE = 3,
TIME_ID_CLOCK_MONOTONIC_RAW = 4,
TIME_ID_NUMBER
};
HsaTimer(const hsa_pfn_t* hsa_api)
: hsa_api_(hsa_api)
{
timestamp_t sysclock_hz = 0;
hsa_status_t status = hsa_api_->hsa_system_get_info(
HSA_SYSTEM_INFO_TIMESTAMP_FREQUENCY, &sysclock_hz);
CHECK_STATUS("hsa_system_get_info(HSA_SYSTEM_INFO_TIMESTAMP_FREQUENCY)", status);
sysclock_factor_ = (freq_t) 1000000000 / (freq_t) sysclock_hz;
}
// Methods for system-clock/ns conversion
timestamp_t sysclock_to_ns(const timestamp_t& sysclock) const
{
return timestamp_t((freq_t) sysclock * sysclock_factor_);
}
timestamp_t ns_to_sysclock(const timestamp_t& time) const
{
return timestamp_t((freq_t) time / sysclock_factor_);
}
// Method for timespec/ns conversion
static timestamp_t timespec_to_ns(const timespec& time)
{
return ((timestamp_t) time.tv_sec * 1000000000) + time.tv_nsec;
}
// Return timestamp in 'ns'
timestamp_t timestamp_ns() const
{
timestamp_t sysclock;
hsa_status_t status =
hsa_api_->hsa_system_get_info(HSA_SYSTEM_INFO_TIMESTAMP, &sysclock);
CHECK_STATUS("hsa_system_get_info(HSA_SYSTEM_INFO_TIMESTAMP)", status);
return sysclock_to_ns(sysclock);
}
// Return time in 'ns'
timestamp_t clocktime_ns(clockid_t clock_id) const
{
timespec time;
clock_gettime(clock_id, &time);
return timespec_to_ns(time);
}
// Return pair of correlated values of profiling timestamp and time with
// correlation error for a given time ID and number of iterations
void correlated_pair_ns(time_id_t time_id, uint32_t iters, timestamp_t* timestamp_v,
timestamp_t* time_v, timestamp_t* error_v)
{
clockid_t clock_id = 0;
switch(time_id)
{
case TIME_ID_CLOCK_REALTIME: clock_id = CLOCK_REALTIME; break;
case TIME_ID_CLOCK_REALTIME_COARSE: clock_id = CLOCK_REALTIME_COARSE; break;
case TIME_ID_CLOCK_MONOTONIC: clock_id = CLOCK_MONOTONIC; break;
case TIME_ID_CLOCK_MONOTONIC_COARSE: clock_id = CLOCK_MONOTONIC_COARSE; break;
case TIME_ID_CLOCK_MONOTONIC_RAW: clock_id = CLOCK_MONOTONIC_RAW; break;
default: CHECK_STATUS("internal error: invalid time_id", HSA_STATUS_ERROR);
}
std::vector<timestamp_t> ts_vec(iters);
std::vector<timespec> tm_vec(iters);
const uint32_t steps = iters - 1;
for(uint32_t i = 0; i < iters; ++i)
{
hsa_api_->hsa_system_get_info(HSA_SYSTEM_INFO_TIMESTAMP, &ts_vec[i]);
clock_gettime(clock_id, &tm_vec[i]);
}
const timestamp_t ts_base = sysclock_to_ns(ts_vec.front());
const timestamp_t tm_base = timespec_to_ns(tm_vec.front());
const timestamp_t error = (ts_vec.back() - ts_vec.front()) / (2 * steps);
timestamp_t ts_accum = 0;
timestamp_t tm_accum = 0;
for(uint32_t i = 0; i < iters; ++i)
{
ts_accum += (ts_vec[i] - ts_base);
tm_accum += (timespec_to_ns(tm_vec[i]) - tm_base);
}
*timestamp_v = (ts_accum / iters) + ts_base + error;
*time_v = (tm_accum / iters) + tm_base;
*error_v = error;
}
private:
// Timestamp frequency factor
freq_t sysclock_factor_;
// HSA API table
const hsa_pfn_t* const hsa_api_;
};
class HsaRsrcFactory
{
public:
static const size_t CMD_SLOT_SIZE_B = 0x40;
typedef std::recursive_mutex mutex_t;
typedef HsaTimer::timestamp_t timestamp_t;
static HsaRsrcFactory* Create(bool initialize_hsa = true)
{
std::lock_guard<mutex_t> lck(mutex_);
HsaRsrcFactory* obj = instance_.load(std::memory_order_relaxed);
if(obj == nullptr)
{
obj = new HsaRsrcFactory(initialize_hsa);
instance_.store(obj, std::memory_order_release);
}
return obj;
}
static HsaRsrcFactory& Instance()
{
HsaRsrcFactory* obj = instance_.load(std::memory_order_acquire);
if(obj == nullptr) obj = Create(false);
hsa_status_t status = (obj != nullptr) ? HSA_STATUS_SUCCESS : HSA_STATUS_ERROR;
CHECK_STATUS("HsaRsrcFactory::Instance() failed", status);
return *obj;
}
static void Destroy()
{
std::lock_guard<mutex_t> lck(mutex_);
if(instance_) delete instance_.load();
instance_ = nullptr;
}
// Return system agent info
const AgentInfo* GetAgentInfo(const hsa_agent_t agent);
// Get the count of Hsa Gpu Agents available on the platform
// @return uint32_t Number of Gpu agents on platform
uint32_t GetCountOfGpuAgents();
// Get the count of Hsa Cpu Agents available on the platform
// @return uint32_t Number of Cpu agents on platform
uint32_t GetCountOfCpuAgents();
// Get the AgentInfo handle of a Gpu device
// @param idx Gpu Agent at specified index
// @param agent_info Output parameter updated with AgentInfo
// @return bool true if successful, false otherwise
bool GetGpuAgentInfo(uint32_t idx, const AgentInfo** agent_info);
// Get the AgentInfo handle of a Cpu device
// @param idx Cpu Agent at specified index
// @param agent_info Output parameter updated with AgentInfo
// @return bool true if successful, false otherwise
bool GetCpuAgentInfo(uint32_t idx, const AgentInfo** agent_info);
// Create a Queue object and return its handle. The queue object is expected
// to support user requested number of Aql dispatch packets.
// @param agent_info Gpu Agent on which to create a queue object
// @param num_Pkts Number of packets to be held by queue
// @param queue Output parameter updated with handle of queue object
// @return bool true if successful, false otherwise
bool CreateQueue(const AgentInfo* agent_info, uint32_t num_pkts, hsa_queue_t** queue);
// Create a Signal object and return its handle.
// @param value Initial value of signal object
// @param signal Output parameter updated with handle of signal object
// @return bool true if successful, false otherwise
bool CreateSignal(uint32_t value, hsa_signal_t* signal);
// Allocate local GPU memory
// @param agent_info Agent from whose memory region to allocate
// @param size Size of memory in terms of bytes
// @return uint8_t* Pointer to buffer, null if allocation fails.
uint8_t* AllocateLocalMemory(const AgentInfo* agent_info, size_t size);
// Allocate memory tp pass kernel parameters
// Memory is alocated accessible for all CPU agents and for GPU given by AgentInfo
// parameter.
// @param agent_info Agent from whose memory region to allocate
// @param size Size of memory in terms of bytes
// @return uint8_t* Pointer to buffer, null if allocation fails.
uint8_t* AllocateKernArgMemory(const AgentInfo* agent_info, size_t size);
// Allocate system memory accessible from both CPU and GPU
// Memory is alocated accessible to all CPU agents and AgentInfo parameter is ignored.
// @param agent_info Agent from whose memory region to allocate
// @param size Size of memory in terms of bytes
// @return uint8_t* Pointer to buffer, null if allocation fails.
uint8_t* AllocateSysMemory(const AgentInfo* agent_info, size_t size);
// Allocate memory for command buffer.
// @param agent_info Agent from whose memory region to allocate
// @param size Size of memory in terms of bytes
// @return uint8_t* Pointer to buffer, null if allocation fails.
uint8_t* AllocateCmdMemory(const AgentInfo* agent_info, size_t size);
// Wait signal
hsa_signal_value_t SignalWait(const hsa_signal_t& signal,
const hsa_signal_value_t& signal_value) const;
// Wait signal with signal value restore
void SignalWaitRestore(const hsa_signal_t& signal,
const hsa_signal_value_t& signal_value) const;
// Copy data from GPU to host memory
bool Memcpy(const hsa_agent_t& agent, void* dst, const void* src, size_t size);
bool Memcpy(const AgentInfo* agent_info, void* dst, const void* src, size_t size);
// Memory free method
static bool FreeMemory(void* ptr);
// Loads an Assembled Brig file and Finalizes it into Device Isa
// @param agent_info Gpu device for which to finalize
// @param brig_path File path of the Assembled Brig file
// @param kernel_name Name of the kernel to finalize
// @param code_desc Handle of finalized Code Descriptor that could
// be used to submit for execution
// @return true if successful, false otherwise
bool LoadAndFinalize(const AgentInfo* agent_info, const char* brig_path,
const char* kernel_name, hsa_executable_t* hsa_exec,
hsa_executable_symbol_t* code_desc);
// Print the various fields of Hsa Gpu Agents
bool PrintGpuAgents(const std::string& header);
// Utils for submitting AQL packet to a given queue
static void* GetSlotPointer(hsa_queue_t* queue, const uint64_t& idx);
static void* GetReadPointer(hsa_queue_t* queue);
static uint64_t Submit(hsa_queue_t* queue, const void* packet);
static uint64_t Submit(hsa_queue_t* queue, const void* packet, size_t size_bytes);
// Enable executables loading tracking
static bool IsExecutableTracking() { return executable_tracking_on_; }
static void EnableExecutableTracking(HsaApiTable* table);
static const char* GetKernelNameRef(uint64_t addr);
// Initialize HSA API table
void static InitHsaApiTable(HsaApiTable* table);
static const hsa_pfn_t* HsaApi() { return &hsa_api_; }
// Return AqlProfile API table
typedef hsa_ven_amd_aqlprofile_pfn_t aqlprofile_pfn_t;
const aqlprofile_pfn_t* AqlProfileApi() const { return &aqlprofile_api_; }
// Return Loader API table
const hsa_ven_amd_loader_1_00_pfn_t* LoaderApi() const { return &loader_api_; }
// Methods for system-clock/ns conversion and timestamp in 'ns'
timestamp_t SysclockToNs(const timestamp_t& sysclock) const
{
return timer_->sysclock_to_ns(sysclock);
}
timestamp_t NsToSysclock(const timestamp_t& time) const
{
return timer_->ns_to_sysclock(time);
}
timestamp_t TimestampNs() const { return timer_->timestamp_ns(); }
timestamp_t GetSysTimeout() const { return timeout_; }
static timestamp_t GetTimeoutNs() { return timeout_ns_; }
static void SetTimeoutNs(const timestamp_t& time)
{
std::lock_guard<mutex_t> lck(mutex_);
timeout_ns_ = time;
if(instance_ != nullptr)
Instance().timeout_ = Instance().timer_->ns_to_sysclock(time);
}
void CorrelateTime(HsaTimer::time_id_t time_id, uint32_t iters)
{
timestamp_t timestamp_v = 0;
timestamp_t time_v = 0;
timestamp_t error_v = 0;
timer_->correlated_pair_ns(time_id, iters, &timestamp_v, &time_v, &error_v);
time_shift_[time_id] = time_v - timestamp_v;
time_error_[time_id] = error_v;
}
hsa_status_t GetTimeVal(uint32_t time_id, uint64_t time_stamp, uint64_t* time_value)
{
if(time_id >= HsaTimer::TIME_ID_NUMBER) return HSA_STATUS_ERROR;
*time_value = time_stamp + time_shift_[time_id];
return HSA_STATUS_SUCCESS;
}
hsa_status_t GetTimeErr(uint32_t time_id, uint64_t* err)
{
*err = time_error_[time_id];
return HSA_STATUS_SUCCESS;
}
private:
// System agents iterating callback
static hsa_status_t GetHsaAgentsCallback(hsa_agent_t agent, void* data);
// Callback function to find and bind kernarg region of an agent
static hsa_status_t FindMemRegionsCallback(hsa_region_t region, void* data);
// Load AQL profile HSA extension library directly
static hsa_status_t LoadAqlProfileLib(aqlprofile_pfn_t* api);
// Constructor of the class. Will initialize the Hsa Runtime and
// query the system topology to get the list of Cpu and Gpu devices
explicit HsaRsrcFactory(bool initialize_hsa);
// Destructor of the class
~HsaRsrcFactory();
// Add an instance of AgentInfo representing a Hsa Gpu agent
const AgentInfo* AddAgentInfo(const hsa_agent_t agent);
// To mmap command buffer memory
static const bool CMD_MEMORY_MMAP = false;
// HSA was initialized
const bool initialize_hsa_;
static std::atomic<HsaRsrcFactory*> instance_;
static mutex_t mutex_;
// Used to maintain a list of Hsa Gpu Agent Info
std::vector<const AgentInfo*> gpu_list_;
std::vector<hsa_agent_t> gpu_agents_;
// Used to maintain a list of Hsa Cpu Agent Info
std::vector<const AgentInfo*> cpu_list_;
std::vector<hsa_agent_t> cpu_agents_;
// System agents map
std::map<hsa_agent_handle_t, const AgentInfo*> agent_map_;
// Executables loading tracking
typedef std::map<uint64_t, const char*> symbols_map_t;
static symbols_map_t* symbols_map_;
static bool executable_tracking_on_;
static void* to_dump_code_obj_;
static hsa_status_t hsa_executable_freeze_interceptor(hsa_executable_t executable,
const char* options);
static hsa_status_t hsa_executable_destroy_interceptor(hsa_executable_t executable);
static hsa_status_t executable_symbols_cb(hsa_executable_t exec,
hsa_executable_symbol_t symbol, void* data);
// HSA runtime API table
static hsa_pfn_t hsa_api_;
// AqlProfile API table
aqlprofile_pfn_t aqlprofile_api_;
// Loader API table
hsa_ven_amd_loader_1_00_pfn_t loader_api_;
// System timeout, ns
static timestamp_t timeout_ns_;
// System timeout, sysclock
timestamp_t timeout_;
// HSA timer
HsaTimer* timer_;
// Time shift array to support time conversion
timestamp_t time_shift_[HsaTimer::TIME_ID_NUMBER];
timestamp_t time_error_[HsaTimer::TIME_ID_NUMBER];
// CPU/kern-arg memory pools
hsa_amd_memory_pool_t* cpu_pool_;
hsa_amd_memory_pool_t* kern_arg_pool_;
};
} // namespace util
} // namespace rocprofiler
@@ -0,0 +1,492 @@
// Copyright (c) 2018-2024 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
// with 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:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimers.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimers in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the names of Advanced Micro Devices, Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this Software without specific prior written permission.
//
// 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
// CONTRIBUTORS 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 WITH
// THE SOFTWARE.
#if defined(NDEBUG)
# undef NDEBUG
#endif
#include "library/rocm_smi.hpp"
#include "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/gpu.hpp"
#include "core/perfetto.hpp"
#include "core/state.hpp"
#include "library/runtime.hpp"
#include "library/thread_info.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/components/timing/backends.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/units.hpp>
#include <timemory/utility/delimit.hpp>
#include <timemory/utility/locking.hpp>
#include <rocm_smi/rocm_smi.h>
#include <cassert>
#include <chrono>
#include <ios>
#include <sstream>
#include <stdexcept>
#include <string>
#include <sys/resource.h>
#include <thread>
#define ROCPROFSYS_ROCM_SMI_CALL(...) \
::rocprofsys::rocm_smi::check_error(__FILE__, __LINE__, __VA_ARGS__)
namespace rocprofsys
{
namespace rocm_smi
{
using bundle_t = std::deque<data>;
using sampler_instances = thread_data<bundle_t, category::rocm_smi>;
namespace
{
auto&
get_settings(uint32_t _dev_id)
{
static auto _v = std::unordered_map<uint32_t, rocm_smi::settings>{};
return _v[_dev_id];
}
bool&
is_initialized()
{
static bool _v = false;
return _v;
}
void
check_error(const char* _file, int _line, rsmi_status_t _code, bool* _option = nullptr)
{
if(_code == RSMI_STATUS_SUCCESS)
return;
else if(_code == RSMI_STATUS_NOT_SUPPORTED && _option)
{
*_option = false;
return;
}
const char* _msg = nullptr;
auto _err = rsmi_status_string(_code, &_msg);
if(_err != RSMI_STATUS_SUCCESS)
ROCPROFSYS_THROW("rsmi_status_string failed. No error message available. "
"Error code %i originated at %s:%i\n",
static_cast<int>(_code), _file, _line);
ROCPROFSYS_THROW("[%s:%i] Error code %i :: %s", _file, _line, static_cast<int>(_code),
_msg);
}
std::atomic<State>&
get_state()
{
static std::atomic<State> _v{ State::PreInit };
return _v;
}
} // namespace
//--------------------------------------------------------------------------------------//
size_t data::device_count = 0;
std::set<uint32_t> data::device_list = {};
std::unique_ptr<data::promise_t> data::polling_finished = {};
data::data(uint32_t _dev_id) { sample(_dev_id); }
void
data::sample(uint32_t _dev_id)
{
auto _ts = tim::get_clock_real_now<size_t, std::nano>();
assert(_ts < std::numeric_limits<int64_t>::max());
auto _state = get_state().load();
if(_state != State::Active) return;
m_dev_id = _dev_id;
m_ts = _ts;
#define ROCPROFSYS_RSMI_GET(OPTION, FUNCTION, ...) \
if(OPTION) \
{ \
try \
{ \
ROCPROFSYS_ROCM_SMI_CALL(FUNCTION(__VA_ARGS__), &OPTION); \
} catch(std::runtime_error & _e) \
{ \
ROCPROFSYS_VERBOSE_F( \
0, "[%s] Exception: %s. Disabling future samples from rocm-smi...\n", \
#FUNCTION, _e.what()); \
get_state().store(State::Disabled); \
} \
}
ROCPROFSYS_RSMI_GET(get_settings(m_dev_id).busy, rsmi_dev_busy_percent_get, _dev_id,
&m_busy_perc);
ROCPROFSYS_RSMI_GET(get_settings(m_dev_id).temp, rsmi_dev_temp_metric_get, _dev_id,
RSMI_TEMP_TYPE_EDGE, RSMI_TEMP_CURRENT, &m_temp);
ROCPROFSYS_RSMI_GET(get_settings(m_dev_id).power, rsmi_dev_power_ave_get, _dev_id, 0,
&m_power);
ROCPROFSYS_RSMI_GET(get_settings(m_dev_id).mem_usage, rsmi_dev_memory_usage_get,
_dev_id, RSMI_MEM_TYPE_VRAM, &m_mem_usage);
#undef ROCPROFSYS_RSMI_GET
}
void
data::print(std::ostream& _os) const
{
std::stringstream _ss{};
_ss << "device: " << m_dev_id << ", busy = " << m_busy_perc << "%, temp = " << m_temp
<< ", power = " << m_power << ", memory usage = " << m_mem_usage;
_os << _ss.str();
}
namespace
{
std::vector<unique_ptr_t<bundle_t>*> _bundle_data{};
}
void
config()
{
_bundle_data.resize(data::device_count, nullptr);
for(size_t i = 0; i < data::device_count; ++i)
{
if(data::device_list.count(i) > 0)
{
_bundle_data.at(i) = &sampler_instances::get()->at(i);
if(!*_bundle_data.at(i))
*_bundle_data.at(i) = unique_ptr_t<bundle_t>{ new bundle_t{} };
}
}
data::get_initial().resize(data::device_count);
for(auto itr : data::device_list)
data::get_initial().at(itr).sample(itr);
}
void
sample()
{
for(auto itr : data::device_list)
{
if(rocm_smi::get_state() != State::Active) continue;
ROCPROFSYS_DEBUG_F("Polling rocm-smi for device %u...\n", itr);
auto& _data = *_bundle_data.at(itr);
if(!_data) continue;
_data->emplace_back(data{ itr });
ROCPROFSYS_DEBUG_F(" %s\n", TIMEMORY_JOIN("", _data->back()).c_str());
}
}
void
set_state(State _v)
{
rocm_smi::get_state().store(_v);
}
std::vector<data>&
data::get_initial()
{
static std::vector<data> _v{};
return _v;
}
bool
data::setup()
{
perfetto_counter_track<data>::init();
rocm_smi::set_state(State::PreInit);
return true;
}
bool
data::shutdown()
{
ROCPROFSYS_DEBUG("Shutting down rocm-smi...\n");
rocm_smi::set_state(State::Finalized);
return true;
}
#define GPU_METRIC(COMPONENT, ...) \
if constexpr(tim::trait::is_available<COMPONENT>::value) \
{ \
auto* _val = _v.get<COMPONENT>(); \
if(_val) \
{ \
_val->set_value(itr.__VA_ARGS__); \
_val->set_accum(itr.__VA_ARGS__); \
} \
}
void
data::post_process(uint32_t _dev_id)
{
using component::sampling_gpu_busy;
using component::sampling_gpu_memory;
using component::sampling_gpu_power;
using component::sampling_gpu_temp;
if(device_count < _dev_id) return;
auto& _rocm_smi_v = sampler_instances::get()->at(_dev_id);
auto _rocm_smi = (_rocm_smi_v) ? *_rocm_smi_v : std::deque<rocm_smi::data>{};
const auto& _thread_info = thread_info::get(0, InternalTID);
ROCPROFSYS_VERBOSE(1, "Post-processing %zu rocm-smi samples from device %u\n",
_rocm_smi.size(), _dev_id);
ROCPROFSYS_CI_THROW(!_thread_info, "Missing thread info for thread 0");
if(!_thread_info) return;
auto _settings = get_settings(_dev_id);
auto _process_perfetto = [&]() {
auto _idx = std::array<uint64_t, 4>{};
{
_idx.fill(_idx.size());
uint64_t nidx = 0;
if(_settings.busy) _idx.at(0) = nidx++;
if(_settings.temp) _idx.at(1) = nidx++;
if(_settings.power) _idx.at(2) = nidx++;
if(_settings.mem_usage) _idx.at(3) = nidx++;
}
for(auto& itr : _rocm_smi)
{
using counter_track = perfetto_counter_track<data>;
if(itr.m_dev_id != _dev_id) continue;
if(!counter_track::exists(_dev_id))
{
auto addendum = [&](const char* _v) {
return JOIN(" ", "GPU", _v, JOIN("", '[', _dev_id, ']'), "(S)");
};
if(_settings.busy) counter_track::emplace(_dev_id, addendum("Busy"), "%");
if(_settings.temp)
counter_track::emplace(_dev_id, addendum("Temperature"), "deg C");
if(_settings.power)
counter_track::emplace(_dev_id, addendum("Power"), "watts");
if(_settings.mem_usage)
counter_track::emplace(_dev_id, addendum("Memory Usage"),
"megabytes");
}
uint64_t _ts = itr.m_ts;
if(!_thread_info->is_valid_time(_ts)) continue;
double _busy = itr.m_busy_perc;
double _temp = itr.m_temp / 1.0e3;
double _power = itr.m_power / 1.0e6;
double _usage = itr.m_mem_usage / static_cast<double>(units::megabyte);
if(_settings.busy)
TRACE_COUNTER("device_busy", counter_track::at(_dev_id, _idx.at(0)), _ts,
_busy);
if(_settings.temp)
TRACE_COUNTER("device_temp", counter_track::at(_dev_id, _idx.at(1)), _ts,
_temp);
if(_settings.power)
TRACE_COUNTER("device_power", counter_track::at(_dev_id, _idx.at(2)), _ts,
_power);
if(_settings.mem_usage)
TRACE_COUNTER("device_memory_usage",
counter_track::at(_dev_id, _idx.at(3)), _ts, _usage);
}
};
if(get_use_perfetto()) _process_perfetto();
}
//--------------------------------------------------------------------------------------//
void
setup()
{
auto_lock_t _lk{ type_mutex<category::rocm_smi>() };
if(is_initialized() || !get_use_rocm_smi()) return;
ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false);
// assign the data value to determined by rocm-smi
data::device_count = device_count();
auto _devices_v = get_sampling_gpus();
for(auto& itr : _devices_v)
itr = tolower(itr);
if(_devices_v == "off")
_devices_v = "none";
else if(_devices_v == "on")
_devices_v = "all";
bool _all_devices = _devices_v.find("all") != std::string::npos || _devices_v.empty();
bool _no_devices = _devices_v.find("none") != std::string::npos;
std::set<uint32_t> _devices = {};
auto _emplace = [&_devices](auto idx) {
if(idx < data::device_count) _devices.emplace(idx);
};
if(_all_devices)
{
for(uint32_t i = 0; i < data::device_count; ++i)
_emplace(i);
}
else if(!_no_devices)
{
auto _enabled = tim::delimit(_devices_v, ",; \t");
for(auto&& itr : _enabled)
{
if(itr.find_first_not_of("0123456789-") != std::string::npos)
{
ROCPROFSYS_THROW("Invalid GPU specification: '%s'. Only numerical values "
"(e.g., 0) or ranges (e.g., 0-7) are permitted.",
itr.c_str());
}
if(itr.find('-') != std::string::npos)
{
auto _v = tim::delimit(itr, "-");
ROCPROFSYS_CONDITIONAL_THROW(_v.size() != 2,
"Invalid GPU range specification: '%s'. "
"Required format N-M, e.g. 0-4",
itr.c_str());
for(auto i = std::stoul(_v.at(0)); i < std::stoul(_v.at(1)); ++i)
_emplace(i);
}
else
{
_emplace(std::stoul(itr));
}
}
}
data::device_list = _devices;
auto _metrics = get_setting_value<std::string>("ROCPROFSYS_ROCM_SMI_METRICS");
try
{
for(auto itr : _devices)
{
uint16_t dev_id = 0;
ROCPROFSYS_ROCM_SMI_CALL(rsmi_dev_id_get(itr, &dev_id));
// dev_id holds the device ID of device i, upon a successful call
if(_metrics && !_metrics->empty())
{
using key_pair_t = std::pair<std::string_view, bool&>;
const auto supported = std::unordered_map<std::string_view, bool&>{
key_pair_t{ "busy", get_settings(dev_id).busy },
key_pair_t{ "temp", get_settings(dev_id).temp },
key_pair_t{ "power", get_settings(dev_id).power },
key_pair_t{ "mem_usage", get_settings(dev_id).mem_usage },
};
get_settings(dev_id) = { false, false, false, false };
for(const auto& metric : tim::delimit(*_metrics, ",;:\t\n "))
{
auto iitr = supported.find(metric);
if(iitr == supported.end())
ROCPROFSYS_FAIL_F("unsupported rocm-smi metric: %s\n",
metric.c_str());
ROCPROFSYS_VERBOSE_F(1, "Enabling rocm-smi metric '%s'\n",
metric.c_str());
iitr->second = true;
}
}
}
is_initialized() = true;
data::setup();
} catch(std::runtime_error& _e)
{
ROCPROFSYS_VERBOSE(0, "Exception thrown when initializing rocm-smi: %s\n",
_e.what());
data::device_list = {};
}
}
void
shutdown()
{
auto_lock_t _lk{ type_mutex<category::rocm_smi>() };
if(!is_initialized()) return;
try
{
if(data::shutdown())
{
ROCPROFSYS_ROCM_SMI_CALL(rsmi_shut_down());
}
} catch(std::runtime_error& _e)
{
ROCPROFSYS_VERBOSE(0, "Exception thrown when shutting down rocm-smi: %s\n",
_e.what());
}
is_initialized() = false;
}
void
post_process()
{
for(auto itr : data::device_list)
data::post_process(itr);
}
uint32_t
device_count()
{
return gpu::rsmi_device_count();
}
} // namespace rocm_smi
} // namespace rocprofsys
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_busy>), true,
double)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_temp>), true,
double)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_power>), true,
double)
ROCPROFSYS_INSTANTIATE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_memory>), true,
double)
@@ -0,0 +1,182 @@
// Copyright (c) 2018-2024 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
// with 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:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimers.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimers in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the names of Advanced Micro Devices, Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this Software without specific prior written permission.
//
// 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
// CONTRIBUTORS 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 WITH
// THE SOFTWARE.
#pragma once
#include "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/state.hpp"
#include "library/thread_data.hpp"
#include <chrono>
#include <cstdint>
#include <deque>
#include <future>
#include <limits>
#include <memory>
#include <ratio>
#include <thread>
#include <tuple>
#include <type_traits>
namespace rocprofsys
{
namespace rocm_smi
{
void
setup();
void
config();
void
sample();
void
shutdown();
void
post_process();
void set_state(State);
uint32_t
device_count();
struct settings
{
bool busy = true;
bool temp = true;
bool power = true;
bool mem_usage = true;
};
struct data
{
using msec_t = std::chrono::milliseconds;
using usec_t = std::chrono::microseconds;
using nsec_t = std::chrono::nanoseconds;
using promise_t = std::promise<void>;
using timestamp_t = int64_t;
using power_t = uint64_t;
using busy_perc_t = uint32_t;
using mem_usage_t = uint64_t;
using temp_t = int64_t;
ROCPROFSYS_DEFAULT_OBJECT(data)
explicit data(uint32_t _dev_id);
void sample(uint32_t _dev_id);
void print(std::ostream& _os) const;
static void post_process(uint32_t _dev_id);
uint32_t m_dev_id = std::numeric_limits<uint32_t>::max();
timestamp_t m_ts = 0;
busy_perc_t m_busy_perc = 0;
temp_t m_temp = 0;
power_t m_power = 0;
mem_usage_t m_mem_usage = 0;
friend std::ostream& operator<<(std::ostream& _os, const data& _v)
{
_v.print(_os);
return _os;
}
private:
friend void rocprofsys::rocm_smi::setup();
friend void rocprofsys::rocm_smi::config();
friend void rocprofsys::rocm_smi::sample();
friend void rocprofsys::rocm_smi::shutdown();
friend void rocprofsys::rocm_smi::post_process();
static size_t device_count;
static std::set<uint32_t> device_list;
static std::unique_ptr<promise_t> polling_finished;
static std::vector<data>& get_initial();
static std::unique_ptr<std::thread>& get_thread();
static bool setup();
static bool shutdown();
};
#if !defined(ROCPROFSYS_USE_ROCM_SMI)
inline void
setup()
{}
inline void
config()
{}
inline void
sample()
{}
inline void
shutdown()
{}
inline void
post_process()
{}
inline void set_state(State) {}
#endif
} // namespace rocm_smi
} // namespace rocprofsys
#if defined(ROCPROFSYS_USE_ROCM_SMI) && ROCPROFSYS_USE_ROCM_SMI > 0
# if !defined(ROCPROFSYS_EXTERN_COMPONENTS) || \
(defined(ROCPROFSYS_EXTERN_COMPONENTS) && ROCPROFSYS_EXTERN_COMPONENTS > 0)
# include <timemory/components/base.hpp>
# include <timemory/components/data_tracker/components.hpp>
# include <timemory/operations.hpp>
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_busy>), true,
double)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_temp>), true,
double)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_power>), true,
double)
ROCPROFSYS_DECLARE_EXTERN_COMPONENT(
TIMEMORY_ESC(data_tracker<double, rocprofsys::component::backtrace_gpu_memory>), true,
double)
# endif
#endif
@@ -0,0 +1,834 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/rocprofiler.hpp"
#include "core/common.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/gpu.hpp"
#include "core/perfetto.hpp"
#include "library/rocm.hpp"
#include "library/rocm/hsa_rsrc_factory.hpp"
#include <timemory/backends/hardware_counters.hpp>
#include <timemory/manager.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/storage/types.hpp>
#include <timemory/utility/types.hpp>
#include <rocprofiler.h>
#include <atomic>
#include <cstdlib>
#include <dlfcn.h>
#include <hsa.h>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string.h>
#include <string_view>
#include <type_traits>
#include <unistd.h>
#include <vector>
namespace rocprofsys
{
namespace rocprofiler
{
namespace
{
using ::rocprofiler::util::AgentInfo;
using ::rocprofiler::util::HsaRsrcFactory;
auto&
get_event_names()
{
static auto _v = std::map<uint32_t, std::vector<rocprofiler_feature_t>>{};
return _v;
}
} // namespace
// Error handler
void
fatal(const std::string& msg)
{
ROCPROFSYS_PRINT_F("\n");
ROCPROFSYS_PRINT_F("%s\n", msg.c_str());
abort();
}
// Check returned HSA API status
const char*
rocm_error_string(hsa_status_t _status)
{
const char* _err_string = nullptr;
if(_status != HSA_STATUS_SUCCESS) rocprofiler_error_string(&_err_string);
return _err_string;
}
// Check returned HSA API status
bool
rocm_check_status(hsa_status_t _status, const std::set<hsa_status_t>& _nonfatal = {})
{
if(_status != HSA_STATUS_SUCCESS)
{
if(_nonfatal.count(_status) == 0)
fatal(JOIN(" :: ", "ERROR", rocm_error_string(_status)));
ROCPROFSYS_PRINT_F("Warning! %s\n", rocm_error_string(_status));
return false;
}
return true;
}
// Context stored entry type
struct context_entry_t
{
bool valid;
hsa_agent_t agent;
rocprofiler_group_t group;
rocprofiler_callback_data_t data;
};
// Context callback arg
struct callbacks_arg_t
{
rocprofiler_pool_t** pools;
};
// Handler callback arg
struct handler_arg_t
{
rocprofiler_feature_t* features;
unsigned feature_count;
};
bool&
is_setup()
{
static bool _v = false;
return _v;
}
std::map<uint32_t, std::vector<std::string_view>>
get_data_labels()
{
auto _v = std::map<uint32_t, std::vector<std::string_view>>{};
for(const auto& itr : get_event_names())
{
_v[itr.first] = {};
for(auto vitr : itr.second)
_v[itr.first].emplace_back(std::string_view{ vitr.name });
}
return _v;
}
// Dump stored context entry
void
rocm_dump_context_entry(context_entry_t* entry, rocprofiler_feature_t* features,
unsigned feature_count)
{
volatile std::atomic<bool>* valid =
reinterpret_cast<std::atomic<bool>*>(&entry->valid);
while(valid->load() == false)
sched_yield();
const rocprofiler_dispatch_record_t* record = entry->data.record;
if(!record) return; // there is nothing to do here.
auto _queue_id = entry->data.queue_id;
auto _thread_id = entry->data.thread_id;
auto _dev_id = HsaRsrcFactory::Instance().GetAgentInfo(entry->agent)->dev_index;
auto _kernel_name = std::string{ entry->data.kernel_name };
auto _pos = _kernel_name.find_last_of(')');
if(_pos != std::string::npos) _kernel_name = _kernel_name.substr(0, _pos + 1);
rocprofiler_group_t& group = entry->group;
if(group.context == nullptr)
{
fatal("context is nullptr\n");
}
if(feature_count > 0)
{
rocm_check_status(rocprofiler_group_get_data(&group));
rocm_check_status(rocprofiler_get_metrics(group.context));
}
auto _evt =
component::rocm_event{ _dev_id, _thread_id, _queue_id, _kernel_name,
record->begin, record->end, feature_count, features };
component::rocm_data()->emplace_back(_evt);
}
// Profiling completion handler
// Dump and delete the context entry
// Return true if the context was dumped successfully
bool
rocm_context_handler(const rocprofiler_pool_entry_t* entry, void* arg)
{
// Context entry
context_entry_t* ctx_entry = reinterpret_cast<context_entry_t*>(entry->payload);
handler_arg_t* handler_arg = reinterpret_cast<handler_arg_t*>(arg);
// rocm::lock_t _lk{ rocm::rocm_mutex, std::defer_lock };
// if(!_lk.owns_lock()) _lk.lock();
rocm_dump_context_entry(ctx_entry, handler_arg->features, handler_arg->feature_count);
return true;
}
// Kernel disoatch callback
hsa_status_t
rocm_dispatch_callback(const rocprofiler_callback_data_t* callback_data, void* arg,
rocprofiler_group_t* group)
{
// Passed tool data
hsa_agent_t agent = callback_data->agent;
// Open profiling context
const unsigned gpu_id = HsaRsrcFactory::Instance().GetAgentInfo(agent)->dev_index;
callbacks_arg_t* callbacks_arg = reinterpret_cast<callbacks_arg_t*>(arg);
rocprofiler_pool_t* pool = callbacks_arg->pools[gpu_id];
rocprofiler_pool_entry_t pool_entry{};
rocm_check_status(rocprofiler_pool_fetch(pool, &pool_entry));
// Profiling context entry
rocprofiler_t* context = pool_entry.context;
context_entry_t* entry = reinterpret_cast<context_entry_t*>(pool_entry.payload);
// Get group[0]
rocm_check_status(rocprofiler_get_group(context, 0, group));
// Fill profiling context entry
entry->agent = agent;
entry->group = *group;
entry->data = *callback_data;
entry->data.kernel_name = strdup(callback_data->kernel_name);
reinterpret_cast<std::atomic<bool>*>(&entry->valid)->store(true);
return HSA_STATUS_SUCCESS;
}
unsigned
metrics_input(unsigned _device, rocprofiler_feature_t** ret)
{
// Profiling feature objects
auto _events = tim::delimit(config::get_rocm_events(), ", ;\t\n");
std::vector<std::string> _features = {};
auto _this_device = JOIN("", ":device=", _device);
for(auto itr : _events)
{
ROCPROFSYS_VERBOSE_F(3, "Processing feature '%s' for device %u...\n", itr.c_str(),
_device);
auto _pos = itr.find(":device=");
if(_pos != std::string::npos)
{
if(itr.find(_this_device) != std::string::npos)
{
_features.emplace_back(itr.substr(0, _pos));
}
}
else
{
_features.emplace_back(itr);
}
}
const unsigned feature_count = _features.size();
rocprofiler_feature_t* features = new rocprofiler_feature_t[feature_count];
memset(features, 0, feature_count * sizeof(rocprofiler_feature_t));
// PMC events
for(unsigned i = 0; i < feature_count; ++i)
{
ROCPROFSYS_VERBOSE_F(3, "Adding feature '%s' for device %u...\n",
_features.at(i).c_str(), _device);
features[i].kind = ROCPROFILER_FEATURE_KIND_METRIC;
features[i].name = strdup(_features.at(i).c_str());
features[i].parameters = nullptr;
features[i].parameter_count = 0;
}
*ret = features;
return feature_count;
}
using info_data = std::vector<component::rocm_info_entry>;
hsa_status_t
info_data_callback(const rocprofiler_info_data_t info, void* arg)
{
using qualifier_t = tim::hardware_counters::qualifier;
using qualifier_vec_t = std::vector<qualifier_t>;
auto* _data = static_cast<info_data*>(arg);
auto _dev_index = info.agent_index;
switch(info.kind)
{
case ROCPROFILER_INFO_KIND_METRIC:
{
auto _device_qualifier_sym = JOIN("", ":device=", _dev_index);
auto _device_qualifier =
tim::hardware_counters::qualifier{ true, static_cast<int>(_dev_index),
_device_qualifier_sym,
JOIN(" ", "Device", _dev_index) };
auto _long_desc = std::string{ info.metric.description };
auto _units = std::string{};
auto _pysym = std::string{};
if(info.metric.expr != nullptr)
{
auto _sym = JOIN("", info.metric.name, _device_qualifier_sym);
auto _short_desc = JOIN("", "Derived counter: ", info.metric.expr);
_data->emplace_back(component::rocm_info_entry(
true, tim::hardware_counters::api::rocm, _data->size(), 0, _sym,
_pysym, _short_desc, _long_desc, _units,
qualifier_vec_t{ _device_qualifier }));
}
else
{
if(info.metric.instances == 1)
{
auto _sym = JOIN("", info.metric.name, _device_qualifier_sym);
auto _short_desc =
JOIN("", info.metric.name, " on device ", _dev_index);
_data->emplace_back(component::rocm_info_entry(
true, tim::hardware_counters::api::rocm, _data->size(), 0, _sym,
_pysym, _short_desc, _long_desc, _units,
qualifier_vec_t{ _device_qualifier }));
}
else
{
for(uint32_t i = 0; i < info.metric.instances; ++i)
{
auto _instance_qualifier_sym = JOIN("", '[', i, ']');
auto _instance_qualifier =
tim::hardware_counters::qualifier{ true, static_cast<int>(i),
_instance_qualifier_sym,
JOIN(" ", "Instance", i) };
auto _sym = JOIN("", info.metric.name, _instance_qualifier_sym,
_device_qualifier_sym);
auto _short_desc = JOIN("", info.metric.name, " instance ", i,
" on device ", _dev_index);
_data->emplace_back(component::rocm_info_entry(
true, tim::hardware_counters::api::rocm, _data->size(), 0,
_sym, _pysym, _short_desc, _long_desc, _units,
qualifier_vec_t{ _device_qualifier, _instance_qualifier }));
}
}
}
break;
}
default: printf("wrong info kind %u\n", info.kind); return HSA_STATUS_ERROR;
}
return HSA_STATUS_SUCCESS;
}
std::vector<component::rocm_info_entry>
rocm_metrics()
{
std::vector<component::rocm_info_entry> _data = {};
try
{
(void) HsaRsrcFactory::Instance();
} catch(std::runtime_error& _e)
{
ROCPROFSYS_VERBOSE_F(0, "%s\n", _e.what());
return _data;
}
// Available GPU agents
const unsigned gpu_count = HsaRsrcFactory::Instance().GetCountOfGpuAgents();
std::vector<AgentInfo*> _gpu_agents(gpu_count, nullptr);
for(unsigned i = 0; i < gpu_count; ++i)
{
const AgentInfo* _agent = _gpu_agents[i];
const AgentInfo** _agent_p = &_agent;
HsaRsrcFactory::Instance().GetGpuAgentInfo(i, _agent_p);
if(!rocm_check_status(rocprofiler_iterate_info(
&_agent->dev_id, ROCPROFILER_INFO_KIND_METRIC,
info_data_callback, reinterpret_cast<void*>(&_data)),
{ HSA_STATUS_ERROR_NOT_INITIALIZED }))
{
ROCPROFSYS_WARNING_F(-1, "rocprofiler_iterate_info failed for gpu agent %u\n",
i);
}
}
if(gpu_count > 0 && _data.empty())
{
if(!rocm_check_status(rocprofiler_iterate_info(
nullptr, ROCPROFILER_INFO_KIND_METRIC,
info_data_callback, reinterpret_cast<void*>(&_data)),
{ HSA_STATUS_ERROR_NOT_INITIALIZED }))
{
ROCPROFSYS_WARNING_F(
-1, "rocprofiler_iterate_info failed for %i gpu agents\n", gpu_count);
}
}
auto _settings = tim::settings::shared_instance();
if(_settings)
{
auto ritr = _settings->find("ROCPROFSYS_ROCM_EVENTS");
if(ritr != _settings->end())
{
auto _rocm_events = ritr->second;
if(_rocm_events->get_choices().empty())
{
std::vector<std::string> _choices = {};
_choices.reserve(_data.size());
for(auto itr : _data)
{
if(!itr.symbol().empty()) _choices.emplace_back(itr.symbol());
}
_rocm_events->set_choices(_choices);
}
}
}
return _data;
}
void
rocm_initialize()
{
// Available GPU agents
const unsigned gpu_count = HsaRsrcFactory::Instance().GetCountOfGpuAgents();
(void) rocm_metrics();
// Adding dispatch observer
callbacks_arg_t* callbacks_arg = new callbacks_arg_t{};
callbacks_arg->pools = new rocprofiler_pool_t*[gpu_count];
for(unsigned gpu_id = 0; gpu_id < gpu_count; gpu_id++)
{
// Getting profiling features
rocprofiler_feature_t* features = nullptr;
unsigned feature_count = metrics_input(gpu_id, &features);
if(features)
{
get_event_names()[gpu_id].clear();
get_event_names()[gpu_id].reserve(feature_count);
for(unsigned i = 0; i < feature_count; ++i)
get_event_names().at(gpu_id).emplace_back(features[i]);
}
// Handler arg
handler_arg_t* handler_arg = new handler_arg_t{};
handler_arg->features = features;
handler_arg->feature_count = feature_count;
// Context properties
rocprofiler_pool_properties_t properties{};
properties.num_entries = 100;
properties.payload_bytes = sizeof(context_entry_t);
properties.handler = rocm_context_handler;
properties.handler_arg = handler_arg;
// Getting GPU device info
const AgentInfo* agent_info = nullptr;
if(HsaRsrcFactory::Instance().GetGpuAgentInfo(gpu_id, &agent_info) == false)
{
fprintf(stderr, "GetGpuAgentInfo failed\n");
abort();
}
// Open profiling pool
rocprofiler_pool_t* pool = nullptr;
uint32_t mode = 0; // ROCPROFILER_MODE_SINGLEGROUP
rocm_check_status(rocprofiler_pool_open(agent_info->dev_id, features,
feature_count, &pool, mode, &properties));
callbacks_arg->pools[gpu_id] = pool;
}
rocprofiler_queue_callbacks_t callbacks_ptrs{};
callbacks_ptrs.dispatch = rocm_dispatch_callback;
int err = rocprofiler_set_queue_callbacks(callbacks_ptrs, callbacks_arg);
ROCPROFSYS_VERBOSE_F(3, "err=%d, rocprofiler_set_queue_callbacks\n", err);
is_setup() = true;
}
void
rocm_cleanup()
{
// Unregister dispatch callback
rocm_check_status(rocprofiler_remove_queue_callbacks());
// close profiling pool
// rocm_check_status(rocprofiler_pool_flush(pool));
// rocm_check_status(rocprofiler_pool_close(pool));
}
namespace
{
using rocm_event = component::rocm_event;
using rocm_data_t = component::rocm_data_t;
using rocm_metric_type = component::rocm_metric_type;
using rocm_feature_value = component::rocm_feature_value;
using rocm_data_tracker = component::rocm_data_tracker;
void
post_process_perfetto()
{
using counter_track = perfetto_counter_track<rocm_event>;
static bool _once = false;
if(_once) return;
auto _data = rocm_data_t{};
auto _device_data = std::map<uint32_t, std::vector<rocm_event*>>{};
auto _device_fields = std::map<uint32_t, std::vector<std::string_view>>{};
auto _device_range = std::map<uint32_t, std::set<rocm_metric_type>>{};
for(size_t i = 0; i < ROCPROFSYS_MAX_THREADS; ++i)
{
auto& _v = component::rocm_data(i);
if(_v)
{
_data.reserve(_data.size() + _v->size());
for(auto& itr : *_v)
_data.emplace_back(itr);
}
}
if(_data.empty()) return;
_once = true;
std::sort(_data.begin(), _data.end());
auto _get_events = [](std::vector<rocm_event*>& _inp, rocm_metric_type _ts) {
auto _v = std::vector<rocm_event*>{};
for(const auto& itr : _inp)
{
if(_ts >= itr->entry && _ts <= itr->exit) _v.emplace_back(itr);
if(_ts > itr->exit) break;
}
return _v;
};
{
auto _device_time = std::map<uint32_t, std::set<rocm_metric_type>>{};
for(auto& itr : _data)
{
_device_data[itr.device_id].emplace_back(&itr);
_device_time[itr.device_id].emplace(itr.entry);
_device_time[itr.device_id].emplace(itr.exit);
auto _dev_id = itr.device_id;
if(get_use_perfetto() && !counter_track::exists(_dev_id))
{
auto addendum = [&](auto&& _v) {
return JOIN(" ", "Device", _v, JOIN("", '[', _dev_id, ']'));
};
for(auto nitr : itr.feature_names)
{
auto _name = get_data_labels().at(itr.device_id).at(nitr);
counter_track::emplace(_dev_id, addendum(_name));
}
}
}
for(auto& ditr : _device_time)
{
for(auto itr = ditr.second.begin(); itr != ditr.second.end(); ++itr)
{
auto _next = std::next(itr);
if(_next == ditr.second.end()) continue;
_device_range[ditr.first].emplace(((*_next / 2) + (*itr / 2)));
}
}
}
for(auto& ditr : _device_range)
{
auto _dev_id = ditr.first;
auto _values = std::vector<rocm_feature_value>{};
auto _ts_sorted_data = _device_data[_dev_id];
std::sort(_ts_sorted_data.begin(), _ts_sorted_data.end(),
[](auto* _l, auto* _r) { return _l->exit < _r->exit; });
for(const auto& itr : ditr.second)
{
auto _v = _get_events(_ts_sorted_data, itr);
uint64_t _ts = itr;
for(auto* vitr : _v)
{
size_t _n = vitr->feature_values.size();
if(_values.empty())
{
_values.reserve(_n);
for(size_t i = 0; i < _n; ++i)
{
_values.emplace_back(vitr->feature_values.at(i));
}
}
else
{
for(size_t i = 0; i < _n; ++i)
{
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
auto _plus = [](auto& _lhs, auto&& _rhs) { _lhs += _rhs; };
std::visit(_plus, _values.at(i), vitr->feature_values.at(i));
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
}
}
}
for(size_t i = 0; i < _values.size(); ++i)
{
auto _trace_counter = [_dev_id, i, _ts](auto&& _val) {
TRACE_COUNTER("kernel_hardware_counter",
counter_track::at(_dev_id, i), _ts, _val);
};
std::visit(_trace_counter, _values.at(i));
}
}
}
}
void
post_process_timemory()
{
static bool _once = false;
if(_once) return;
auto _data = rocm_data_t{};
auto _device_data = std::map<uint32_t, std::vector<rocm_event*>>{};
auto _device_fields = std::map<uint32_t, std::vector<std::string_view>>{};
auto _device_range = std::map<uint32_t, std::set<rocm_metric_type>>{};
for(size_t i = 0; i < ROCPROFSYS_MAX_THREADS; ++i)
{
auto& _v = component::rocm_data(i);
if(_v)
{
_data.reserve(_data.size() + _v->size());
for(auto& itr : *_v)
_data.emplace_back(itr);
}
}
if(_data.empty()) return;
_once = true;
std::sort(_data.begin(), _data.end());
for(auto& itr : _data)
{
_device_data[itr.device_id].emplace_back(&itr);
}
for(auto& itr : _device_data)
{
// sort according to when it exited
std::sort(itr.second.begin(), itr.second.end(),
[](auto* _lhs, auto* _rhs) { return _lhs->exit < _rhs->exit; });
}
using storage_type = typename rocm_data_tracker::storage_type;
using bundle_type = tim::lightweight_tuple<rocm_data_tracker>;
auto _info = rocm_metrics();
static auto _get_description = [&_info](std::string_view _v) {
for(auto& itr : _info)
{
if(itr.symbol().find(_v) == 0 || itr.short_description().find(_v) == 0)
{
return itr.long_description();
}
}
return std::string{};
};
struct local_event
{
rocm_event* parent = nullptr;
mutable std::vector<local_event> children = {};
ROCPROFSYS_DEFAULT_OBJECT(local_event)
explicit local_event(rocm_event* _v)
: parent{ _v }
{}
bool operator()(rocm_event* _v)
{
if(!parent) return false;
if(_v->device_id != parent->device_id) return false;
if(_v->entry > parent->entry && _v->exit <= parent->exit)
{
children.emplace_back(_v);
return true;
}
return false;
}
bool operator<(const local_event& _v) const
{
if(!parent && _v.parent) return true;
if(parent && !_v.parent) return false;
return *parent < *_v.parent;
}
void operator()(int64_t _index, scope::config _scope) const
{
if(!parent) return;
bundle_type _bundle{ parent->name, _scope };
_bundle.push(parent->queue_id)
.start()
.store(parent->feature_values.at(_index));
std::sort(children.begin(), children.end());
for(const auto& itr : children)
itr(_index, _scope);
_bundle.stop().pop(parent->queue_id);
}
};
struct local_storage
{
int64_t index = 0;
std::string metric_name = {};
std::string metric_description = {};
std::unique_ptr<storage_type> storage = {};
local_storage(uint32_t _devid, size_t _idx, std::string_view _name)
: index{ static_cast<int64_t>(_idx) }
, metric_name{ _name }
, metric_description{ _get_description(metric_name) }
{
auto _metric_name = std::string{ _name };
_metric_name = std::regex_replace(
_metric_name, std::regex{ "(.*)\\[([0-9]+)\\]" }, "$1_$2");
storage = std::make_unique<storage_type>(
tim::standalone_storage{}, index,
JOIN('-', "rocprof", "device", _devid, _metric_name));
}
void operator()(const local_event& _event, scope::config _scope) const
{
operation::set_storage<rocm_data_tracker>{}(storage.get());
_event(index, _scope);
}
void write() const
{
rocm_data_tracker::label() = metric_name;
rocm_data_tracker::description() = metric_description;
storage->write();
}
};
auto _local_data = std::map<uint32_t, std::vector<local_event>>{};
auto _scope = scope::get_default();
for(auto& ditr : _device_data)
{
ROCPROFSYS_VERBOSE_F(1, "Post-processing %zu entries for device %u...\n",
ditr.second.size(), ditr.first);
auto _storage = std::vector<local_storage>{};
for(auto& itr : ditr.second)
{
auto _n = itr->feature_names.size();
if(_n > _storage.size())
{
_storage.reserve(_n);
for(size_t i = _storage.size(); i < _n; ++i)
_storage.emplace_back(
ditr.first, i,
get_data_labels().at(ditr.first).at(itr->feature_names.at(i)));
}
}
auto& _local = _local_data[ditr.first];
_local.reserve(ditr.second.size());
double _avg = 0.0;
for(auto& itr : ditr.second)
{
if(_local.empty() || itr->entry >= _local.back().parent->exit)
{
_local.emplace_back(itr);
}
else
{
size_t _n = 0;
bool _found = false;
for(auto litr = _local.rbegin(); litr != _local.rend(); ++litr)
{
++_n;
if((*litr)(itr))
{
_found = true;
break;
}
}
if(!_found) _local.emplace_back(itr);
_avg += _n;
}
}
ROCPROFSYS_VERBOSE_F(3, "Average # of iterations before match: %.1f\n",
_avg / ditr.second.size() * 100.0);
for(auto& sitr : _storage)
{
for(auto& itr : _local)
sitr(itr, _scope);
}
for(auto& itr : _storage)
itr.write();
}
tim::trait::runtime_enabled<rocprofsys::rocprofiler::rocm_data_tracker>::set(false);
}
} // namespace
void
post_process()
{
if(get_use_perfetto()) post_process_perfetto();
if(get_use_timemory())
{
auto _manager = tim::manager::master_instance();
if(_manager)
{
_manager->add_cleanup("rocprofiler", &post_process_timemory);
}
else
{
post_process_timemory();
}
}
}
} // namespace rocprofiler
} // namespace rocprofsys
@@ -0,0 +1,88 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/components/rocprofiler.hpp"
#include <timemory/backends/hardware_counters.hpp>
#include <timemory/macros.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/macros.hpp>
#include <array>
#include <atomic>
#include <cstring>
#include <dlfcn.h>
#include <iostream>
#include <list>
#include <map>
#include <string>
#include <string_view>
#include <tuple>
#include <unistd.h>
#include <utility>
#include <variant>
#include <vector>
namespace rocprofsys
{
namespace rocprofiler
{
std::map<uint32_t, std::vector<std::string_view>>
get_data_labels();
void
rocm_initialize();
void
rocm_cleanup();
bool&
is_setup();
void
post_process();
std::vector<component::rocm_info_entry>
rocm_metrics();
#if !defined(ROCPROFSYS_USE_ROCPROFILER) || ROCPROFSYS_USE_ROCPROFILER == 0
inline void
post_process()
{}
inline void
rocm_cleanup()
{}
inline std::vector<component::rocm_info_entry>
rocm_metrics()
{
return std::vector<component::rocm_info_entry>{};
}
#endif
} // namespace rocprofiler
} // namespace rocprofsys
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,89 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/config.hpp"
#include "core/debug.hpp"
#include "core/hip_runtime.hpp"
#include "core/perfetto.hpp"
#include "library/components/roctracer.hpp"
#include "library/ptl.hpp"
#include <iostream>
#include <memory>
// Macro to check ROC-tracer calls status
#define ROCPROFSYS_ROCTRACER_CALL(call) \
{ \
ROCPROFSYS_DEBUG_F(#call); \
int err = call; \
if(err != 0) \
{ \
ROCPROFSYS_PRINT_F("%s in: %s\n", roctracer_error_string(), #call); \
} \
}
namespace rocprofsys
{
using roctracer_hip_bundle_t =
tim::component_bundle<category::rocm_hip, comp::roctracer_data, comp::wall_clock>;
using roctracer_hsa_bundle_t =
tim::component_bundle<category::rocm_hsa, comp::roctracer_data>;
using roctracer_functions_t = std::vector<std::pair<std::string, std::function<void()>>>;
// HSA API callback function
void
hsa_api_callback(uint32_t domain, uint32_t cid, const void* callback_data, void* arg);
void
hsa_activity_callback(uint32_t op, const void* record, void* arg);
void
hip_exec_activity_callbacks(int64_t _tid);
// HIP API callback function
void
hip_api_callback(uint32_t domain, uint32_t cid, const void* callback_data, void* arg);
void
roctx_api_callback(uint32_t domain, uint32_t cid, const void* callback_data, void* arg);
// Activity tracing callback
void
hip_activity_callback(const char* begin, const char* end, void*);
bool&
roctracer_is_init();
bool&
roctracer_is_setup();
int64_t
get_clock_skew();
roctracer_functions_t&
roctracer_setup_routines();
roctracer_functions_t&
roctracer_shutdown_routines();
} // namespace rocprofsys
@@ -0,0 +1,290 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/runtime.hpp"
#include "api.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/utility.hpp"
#include "library/thread_data.hpp"
#include "library/thread_info.hpp"
#include <timemory/backends/dmp.hpp>
#include <timemory/backends/mpi.hpp>
#include <timemory/backends/process.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/components/rusage/backends.hpp>
#include <timemory/environment.hpp>
#include <timemory/process/process.hpp>
#include <timemory/sampling/allocator.hpp>
#include <timemory/settings.hpp>
#include <timemory/settings/types.hpp>
#include <timemory/utility/argparse.hpp>
#include <timemory/utility/declaration.hpp>
#include <timemory/utility/signals.hpp>
#include <array>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <numeric>
#include <ostream>
#include <string>
#include <unistd.h>
namespace rocprofsys
{
namespace
{
auto root_process_id =
get_env<pid_t>("ROCPROFSYS_ROOT_PROCESS", process::get_id(), false);
auto&
get_sampling_on_child_threads_history(int64_t _idx = utility::get_thread_index())
{
static auto _v = utility::get_filled_array<ROCPROFSYS_MAX_THREADS>(
[]() { return utility::get_reserved_vector<bool>(32); });
if(_idx >= ROCPROFSYS_MAX_THREADS)
{
static thread_local auto _tl_v = utility::get_reserved_vector<bool>(32);
return _tl_v;
}
return _v.at(_idx);
}
bool&
sampling_on_child_threads()
{
static const auto& _thr_info = thread_info::get();
// if the thread is offset, disable by default
// if the global state is not active or the thread state is not enabled, disable by
// default if there is no history, disable by default (first thread) otherwise,
// inherit the last state
static thread_local bool _v =
(_thr_info) ? !_thr_info->is_offset
: (get_state() != State::Active || get_thread_state() != ThreadState::Enabled)
? false
: (get_sampling_on_child_threads_history().empty()
? false
: get_sampling_on_child_threads_history().back());
return _v;
}
} // namespace
std::atomic<uint64_t>&
get_cpu_cid()
{
static std::atomic<uint64_t> _v{ 0 };
return _v;
}
unique_ptr_t<std::vector<uint64_t>>&
get_cpu_cid_stack(int64_t _tid, int64_t _parent)
{
struct rocprofsys_cpu_cid_stack
{};
using init_data_t = thread_data<bool, rocprofsys_cpu_cid_stack>;
using thread_data_t = thread_data<std::vector<uint64_t>, rocprofsys_cpu_cid_stack>;
auto& _v_tid = thread_data_t::instance(construct_on_thread{ _tid });
auto& _b_tid = init_data_t::instance(construct_on_thread{ _tid }, false);
if(_b_tid && !(*_b_tid))
{
*_b_tid = true;
auto _parent_tid = _parent;
auto& _p_tid = thread_data_t::instance(construct_on_thread{ _parent_tid });
// if tid != parent and there is not a valid pointer for the provided parent
// thread id set it to zero since that will always be valid
if(_tid != _parent_tid && !_p_tid) _parent_tid = 0;
// copy over the thread ids from the parent if tid != parent
if(_tid != _parent_tid) *_v_tid = *_p_tid;
}
return _v_tid;
}
unique_ptr_t<cpu_cid_parent_map_t>&
get_cpu_cid_parents(int64_t _tid)
{
struct rocprofsys_cpu_cid_stack
{};
using thread_data_t = thread_data<cpu_cid_parent_map_t, rocprofsys_cpu_cid_stack>;
return thread_data_t::instance(construct_on_thread{ _tid }, cpu_cid_parent_map_t{});
}
std::tuple<uint64_t, uint64_t, uint32_t>
create_cpu_cid_entry(int64_t _tid)
{
using tim::auto_lock_t;
ROCPROFSYS_SCOPED_THREAD_STATE(ThreadState::Internal);
// unique lock for _tid
auto& _mtx = get_cpu_cid_stack_lock(_tid);
auto_lock_t _lk{ _mtx, std::defer_lock };
if(!_lk.owns_lock()) _lk.lock();
int64_t _p_idx = (get_cpu_cid_stack(_tid)->empty()) ? 0 : _tid;
auto& _p_mtx = get_cpu_cid_stack_lock(_p_idx);
auto_lock_t _p_lk{ _p_mtx, std::defer_lock };
if(!_p_lk.owns_lock()) _p_lk.lock();
auto&& _cid = get_cpu_cid()++;
// auto&& _parent_cid = get_cpu_cid_stack(_p_idx)->back();
uint64_t _parent_cid = 0;
auto& cid_stack = get_cpu_cid_stack(_p_idx);
if(!cid_stack->empty())
{
_parent_cid = cid_stack->back();
}
uint32_t&& _depth = get_cpu_cid_stack(_p_idx)->size() - ((_p_idx == _tid) ? 1 : 0);
get_cpu_cid_parents(_tid)->emplace(_cid, std::make_tuple(_parent_cid, _depth));
return std::make_tuple(_cid, _parent_cid, _depth);
}
cpu_cid_pair_t
get_cpu_cid_entry(uint64_t _cid, int64_t _tid)
{
return get_cpu_cid_parents(_tid)->at(_cid);
}
tim::mutex_t&
get_cpu_cid_stack_lock(int64_t _tid)
{
struct cpu_cid_stack_s
{};
return tim::type_mutex<cpu_cid_stack_s, project::rocprofsys, max_supported_threads>(
_tid);
}
namespace
{
void
setup_gotchas()
{
static bool _initialized = false;
if(_initialized) return;
_initialized = true;
ROCPROFSYS_BASIC_DEBUG(
"Configuring gotcha wrapper around fork, MPI_Init, and MPI_Init_thread\n");
component::mpi_gotcha::configure();
component::exit_gotcha::configure();
component::fork_gotcha::configure();
}
} // namespace
std::unique_ptr<main_bundle_t>&
get_main_bundle()
{
static auto _v = []() {
auto _self = RUSAGE_SELF;
std::swap(_self, tim::get_rusage_type());
auto _tmp = std::make_unique<main_bundle_t>(
JOIN('/', "rocprofsys/process", process::get_id()),
quirk::config<quirk::auto_start>{});
std::swap(_self, tim::get_rusage_type());
return _tmp;
}();
return _v;
}
std::unique_ptr<init_bundle_t>&
get_init_bundle()
{
static auto _v = std::make_unique<init_bundle_t>(
JOIN('/', "rocprofsys/process", process::get_id()));
return _v;
}
std::unique_ptr<preinit_bundle_t>&
get_preinit_bundle()
{
static auto _v =
(setup_gotchas(), std::make_unique<preinit_bundle_t>(
JOIN('/', "rocprofsys/process", process::get_id()),
quirk::config<quirk::auto_start>{}));
return _v;
}
bool
sampling_enabled_on_child_threads()
{
return sampling_on_child_threads();
}
bool
push_enable_sampling_on_child_threads(bool _v)
{
bool _last = sampling_on_child_threads();
sampling_on_child_threads() = _v;
auto& _hist = get_sampling_on_child_threads_history();
_hist.emplace_back(_last);
return _last;
}
bool
pop_enable_sampling_on_child_threads()
{
auto& _hist = get_sampling_on_child_threads_history();
if(!_hist.empty())
{
bool _restored = _hist.back();
_hist.pop_back();
sampling_on_child_threads() = _restored;
}
return sampling_on_child_threads();
}
void
set_sampling_on_all_future_threads(bool _v)
{
for(size_t i = 0; i < max_supported_threads; ++i)
get_sampling_on_child_threads_history(i).emplace_back(_v);
}
pid_t
get_root_process_id()
{
return root_process_id;
}
bool
is_root_process()
{
return (root_process_id == process::get_id());
}
bool
is_child_process()
{
return (root_process_id != process::get_id());
}
} // namespace rocprofsys
@@ -0,0 +1,140 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "api.hpp"
#include "core/common.hpp"
#include "core/defines.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include "library/causal/components/causal_gotcha.hpp"
#include "library/components/exit_gotcha.hpp"
#include "library/components/fork_gotcha.hpp"
#include "library/components/mpi_gotcha.hpp"
#include "library/components/numa_gotcha.hpp"
#include "library/components/pthread_gotcha.hpp"
#include "library/components/roctracer.hpp"
#include "library/thread_data.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/macros/language.hpp>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
namespace rocprofsys
{
// started during preinit phase
using preinit_bundle_t =
tim::lightweight_tuple<exit_gotcha_t, fork_gotcha_t, mpi_gotcha_t>;
// started during init phase
using init_bundle_t = tim::lightweight_tuple<causal::component::causal_gotcha,
pthread_gotcha, component::numa_gotcha>;
// bundle of components around rocprofsys_init and rocprofsys_finalize
using main_bundle_t =
tim::lightweight_tuple<comp::wall_clock, comp::peak_rss, comp::page_rss,
comp::cpu_clock, comp::cpu_util>;
// bundle of components around each thread
#if defined(TIMEMORY_RUSAGE_THREAD) && TIMEMORY_RUSAGE_THREAD > 0
using thread_bundle_t = tim::lightweight_tuple<comp::wall_clock, comp::thread_cpu_clock,
comp::thread_cpu_util, comp::peak_rss>;
#else
using thread_bundle_t = tim::lightweight_tuple<comp::wall_clock, comp::thread_cpu_clock,
comp::thread_cpu_util>;
#endif
std::unique_ptr<main_bundle_t>&
get_main_bundle();
std::unique_ptr<init_bundle_t>&
get_init_bundle();
std::unique_ptr<preinit_bundle_t>&
get_preinit_bundle();
std::atomic<uint64_t>&
get_cpu_cid() TIMEMORY_HOT;
unique_ptr_t<std::vector<uint64_t>>&
get_cpu_cid_stack(int64_t _tid = threading::get_id(), int64_t _parent = 0) TIMEMORY_HOT;
using cpu_cid_data_t = std::tuple<uint64_t, uint64_t, uint32_t>;
using cpu_cid_pair_t = std::tuple<uint64_t, uint32_t>;
using cpu_cid_parent_map_t = std::unordered_map<uint64_t, cpu_cid_pair_t>;
unique_ptr_t<cpu_cid_parent_map_t>&
get_cpu_cid_parents(int64_t _tid = threading::get_id()) TIMEMORY_HOT;
cpu_cid_data_t
create_cpu_cid_entry(int64_t _tid = threading::get_id()) TIMEMORY_HOT;
cpu_cid_pair_t
get_cpu_cid_entry(uint64_t _cid, int64_t _tid = threading::get_id()) TIMEMORY_HOT;
tim::mutex_t&
get_cpu_cid_stack_lock(int64_t _tid = threading::get_id()) TIMEMORY_HOT;
// query current value
bool
sampling_enabled_on_child_threads();
// use this to disable sampling in a region (e.g. right before thread creation)
bool
push_enable_sampling_on_child_threads(bool _v);
// use this to restore previous setting
bool
pop_enable_sampling_on_child_threads();
// make sure every newly created thead starts with this value
void
set_sampling_on_all_future_threads(bool _v);
struct scoped_child_sampling
{
scoped_child_sampling(bool _v) { push_enable_sampling_on_child_threads(_v); }
~scoped_child_sampling() { pop_enable_sampling_on_child_threads(); }
};
pid_t
get_root_process_id();
bool
is_root_process();
bool
is_child_process();
} // namespace rocprofsys
#define ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(VALUE) \
::rocprofsys::scoped_child_sampling ROCPROFSYS_VARIABLE(_scoped_child_sampling_, \
__LINE__) \
{ \
VALUE \
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/components/fwd.hpp"
#include "core/defines.hpp"
#include "core/timemory.hpp"
#include "library/components/backtrace.hpp"
#include "library/components/backtrace_metrics.hpp"
#include "library/components/backtrace_timestamp.hpp"
#include "library/components/callchain.hpp"
#include "library/thread_data.hpp"
#include <timemory/macros/language.hpp>
#include <timemory/variadic/types.hpp>
#include <cstdint>
#include <memory>
#include <set>
#include <type_traits>
namespace rocprofsys
{
namespace sampling
{
unique_ptr_t<std::set<int>>&
get_signal_types(int64_t _tid);
std::set<int>
setup();
std::set<int>
shutdown();
void
block_samples();
void
unblock_samples();
void block_signals(std::set<int> = {});
void unblock_signals(std::set<int> = {});
void
post_process();
} // namespace sampling
} // namespace rocprofsys
@@ -0,0 +1,687 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/common.hpp"
#include "core/concepts.hpp"
#include "core/config.hpp"
#include "core/containers/stable_vector.hpp"
#include "core/debug.hpp"
#include "core/defines.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include "core/utility.hpp"
#include "library/thread_deleter.hpp"
#include <timemory/utility/macros.hpp>
#include <timemory/utility/types.hpp>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <optional>
#include <type_traits>
#include <vector>
namespace rocprofsys
{
// bundle of components used in instrumentation
using instrumentation_bundle_t =
tim::component_bundle<project::rocprofsys, comp::wall_clock*,
comp::user_global_bundle*>;
// allocator for instrumentation_bundle_t
using bundle_allocator_t = tim::data::ring_buffer_allocator<instrumentation_bundle_t>;
using grow_functor_t = int64_t (*)(int64_t);
inline auto&
grow_functors()
{
static auto _v = container::stable_vector<grow_functor_t>{};
return _v;
}
template <typename Tp>
struct base_thread_data
{
base_thread_data()
{
auto _func = [](int64_t _sz) -> int64_t {
decltype(auto) _v = Tp::private_instance();
if(_v && _v->capacity() < static_cast<size_t>(_sz + 1))
{
_v->reserve(_v->capacity() + 1);
_v->resize(_v->capacity());
}
return (_v) ? _v->capacity() : 0;
};
grow_functors().emplace_back(std::move(_func));
}
};
template <typename Tp, typename Tag = void, size_t MaxThreads = max_supported_threads>
struct thread_data;
template <typename Tp, typename Tag, size_t MaxThreads>
struct use_placement_new_when_generating_unique_ptr<thread_data<Tp, Tag, MaxThreads>>
: std::true_type
{};
template <typename Tp, typename Tag, size_t MaxThreads>
struct use_placement_new_when_generating_unique_ptr<
thread_data<std::optional<Tp>, Tag, MaxThreads>> : std::true_type
{};
template <typename Tp, typename Tag, size_t MaxThreads>
struct use_placement_new_when_generating_unique_ptr<
thread_data<identity<Tp>, Tag, MaxThreads>> : std::true_type
{};
template <typename Tp, typename Tag, size_t MaxThreads>
struct thread_data : base_thread_data<thread_data<Tp, Tag, MaxThreads>>
{
using this_type = thread_data<Tp, Tag, MaxThreads>;
using value_type = unique_ptr_t<Tp>;
using array_type =
container::stable_vector<value_type, MaxThreads, container::cacheline_align_v>;
using functor_type = std::function<value_type()>;
template <typename... Args>
static void construct(construct_on_thread&&, Args&&...);
static value_type& instance();
template <typename... Args>
static value_type& instance(construct_on_thread&&, Args&&...);
template <typename... Args>
static void construct(Args&&... args)
{
construct(construct_on_thread{}, std::forward<Args>(args)...);
}
template <typename... Args>
static value_type& instance(Args&&... args)
{
return instance(construct_on_thread{}, std::forward<Args>(args)...);
}
static size_t size() { return private_instance()->m_data.size(); }
decltype(auto) data() { return m_data; }
decltype(auto) data() const { return m_data; }
decltype(auto) begin() { return m_data.begin(); }
decltype(auto) end() { return m_data.end(); }
decltype(auto) begin() const { return m_data.begin(); }
decltype(auto) end() const { return m_data.end(); }
decltype(auto) at(size_t _idx) { return m_data.at(_idx); }
decltype(auto) at(size_t _idx) const { return m_data.at(_idx); }
decltype(auto) operator[](size_t _idx) { return m_data[_idx]; }
decltype(auto) operator[](size_t _idx) const { return m_data[_idx]; }
decltype(auto) reserve(size_t _n) { return m_data.reserve(_n); }
decltype(auto) capacity() const { return m_data.capacity(); }
decltype(auto) empty() const { return m_data.empty(); }
void resize(size_t _n) { container::resize(m_data, _n, m_init()); }
template <typename Up>
void resize(size_t _n, Up&& _v)
{
static_assert(std::is_assignable<value_type, Up>::value,
"value is not assignable to optional<Tp>");
container::resize(m_data, _n, std::forward<Up>(_v));
}
static array_type* get()
{
return (private_instance()) ? &private_instance()->m_data : nullptr;
}
private:
friend struct base_thread_data<this_type>;
static unique_ptr_t<this_type>& private_instance();
static array_type& instances();
template <typename... Args>
static array_type& instances(construct_on_init, Args&&...);
array_type m_data = array_type(MaxThreads);
functor_type m_init = []() { return value_type{}; };
};
template <typename Tp, typename Tag, size_t MaxThreads>
unique_ptr_t<thread_data<Tp, Tag, MaxThreads>>&
thread_data<Tp, Tag, MaxThreads>::private_instance()
{
static auto _v = unique_ptr_t<this_type>{ new this_type{} };
return _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
void
thread_data<Tp, Tag, MaxThreads>::construct(construct_on_thread&& _t, Args&&... _args)
{
// construct outside of lambda to prevent data-race
static auto& _instances = instances();
if(!_instances.at(_t.index))
_instances.at(_t.index) =
utility::generate<value_type>{}(std::forward<Args>(_args)...);
}
template <typename Tp, typename Tag, size_t MaxThreads>
unique_ptr_t<Tp>&
thread_data<Tp, Tag, MaxThreads>::instance()
{
return instances().at(threading::get_id());
}
template <typename Tp, typename Tag, size_t MaxThreads>
typename thread_data<Tp, Tag, MaxThreads>::array_type&
thread_data<Tp, Tag, MaxThreads>::instances()
{
return private_instance()->m_data;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
unique_ptr_t<Tp>&
thread_data<Tp, Tag, MaxThreads>::instance(construct_on_thread&& _t, Args&&... _args)
{
construct(construct_on_thread{ _t }, std::forward<Args>(_args)...);
return instances().at(_t.index);
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
typename thread_data<Tp, Tag, MaxThreads>::array_type&
thread_data<Tp, Tag, MaxThreads>::instances(construct_on_init, Args&&... _args)
{
static auto& _v = [&]() -> array_type& {
auto& _internal = instances();
for(size_t i = 0; i < MaxThreads; ++i)
_internal.at(i) =
utility::generate<value_type>{}(std::forward<Args>(_args)...);
private_instance()->m_init = [_args...]() {
return utility::generate<value_type>{}(_args...);
};
return _internal;
}();
return _v;
}
//--------------------------------------------------------------------------------------//
//
// thread_data with std::optional
//
//--------------------------------------------------------------------------------------//
template <typename Tp, typename Tag, size_t MaxThreads>
struct thread_data<std::optional<Tp>, Tag, MaxThreads>
: base_thread_data<thread_data<std::optional<Tp>, Tag, MaxThreads>>
{
using this_type = thread_data<std::optional<Tp>, Tag, MaxThreads>;
using value_type = std::optional<Tp>;
using functor_type = std::function<value_type()>;
using array_type =
container::stable_vector<value_type, MaxThreads, container::cacheline_align_v>;
thread_data() = default;
~thread_data() = default;
explicit thread_data(functor_type&& _init)
: m_init{ std::move(_init) }
{}
thread_data(const thread_data&) = default;
thread_data(thread_data&&) noexcept = default;
thread_data& operator=(const thread_data&) = default;
thread_data& operator=(thread_data&&) noexcept = default;
static unique_ptr_t<this_type>& instance();
template <typename... Args>
static unique_ptr_t<this_type>& instance(construct_on_init, Args&&...);
template <typename... Args>
static value_type& instance(construct_on_thread&&, Args&&...);
template <typename... Args>
static unique_ptr_t<this_type>& construct(construct_on_init, Args&&...);
template <typename... Args>
static value_type& construct(construct_on_thread&&, Args&&...);
size_t size() { return m_data.size(); }
decltype(auto) data() { return m_data; }
decltype(auto) data() const { return m_data; }
decltype(auto) begin() { return m_data.begin(); }
decltype(auto) end() { return m_data.end(); }
decltype(auto) begin() const { return m_data.begin(); }
decltype(auto) end() const { return m_data.end(); }
decltype(auto) at(size_t _idx) { return m_data.at(_idx); }
decltype(auto) at(size_t _idx) const { return m_data.at(_idx); }
decltype(auto) operator[](size_t _idx) { return m_data[_idx]; }
decltype(auto) operator[](size_t _idx) const { return m_data[_idx]; }
decltype(auto) reserve(size_t _n) { return m_data.reserve(_n); }
decltype(auto) capacity() const { return m_data.capacity(); }
decltype(auto) empty() const { return m_data.empty(); }
void resize(size_t _n) { container::resize(m_data, _n, m_init()); }
template <typename Up>
void resize(size_t _n, Up&& _v)
{
static_assert(std::is_assignable<value_type, Up>::value,
"value is not assignable to optional<Tp>");
container::resize(m_data, _n, std::forward<Up>(_v));
}
private:
friend struct base_thread_data<this_type>;
static decltype(auto) private_instance() { return instance(); }
array_type m_data = {};
functor_type m_init = []() { return value_type{}; };
};
template <typename Tp, typename Tag, size_t MaxThreads>
unique_ptr_t<thread_data<std::optional<Tp>, Tag, MaxThreads>>&
thread_data<std::optional<Tp>, Tag, MaxThreads>::instance()
{
static auto _v = unique_ptr_t<this_type>{};
return _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
unique_ptr_t<thread_data<std::optional<Tp>, Tag, MaxThreads>>&
thread_data<std::optional<Tp>, Tag, MaxThreads>::instance(construct_on_init,
Args&&... _args)
{
static auto& _v = [&]() -> unique_ptr_t<this_type>& {
auto& _ref = instance();
if(!_ref)
_ref = utility::generate<unique_ptr_t<this_type>>{}(
std::forward<Args>(_args)...);
if(_ref->size() < MaxThreads) _ref->resize(MaxThreads);
return _ref;
}();
return _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
unique_ptr_t<thread_data<std::optional<Tp>, Tag, MaxThreads>>&
thread_data<std::optional<Tp>, Tag, MaxThreads>::construct(construct_on_init,
Args&&... _args)
{
// construct outside of lambda to prevent data-race
static auto& _ref = instance(construct_on_init{});
static auto _v = [&]() {
if(_ref)
{
for(auto& itr : *_ref)
itr = utility::generate<value_type>{}(std::forward<Args>(_args)...);
}
return (_ref != nullptr);
}();
return _ref;
(void) _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
std::optional<Tp>&
thread_data<std::optional<Tp>, Tag, MaxThreads>::construct(construct_on_thread&& _t,
Args&&... _args)
{
// construct outside of lambda to prevent data-race
static auto& _instance = instance(construct_on_init{});
static auto _constructed =
container::stable_vector<bool, MaxThreads, container::cacheline_align_v>{};
static auto _grow = []() {
container::resize(_constructed, MaxThreads, false);
grow_functors().emplace_back([](int64_t _n) -> int64_t {
if(static_cast<size_t>(_n) >= _constructed.size())
{
_constructed.reserve(_constructed.capacity() + 1);
container::resize(_constructed, _constructed.capacity(), false);
}
return _constructed.size();
});
return true;
}();
if(!_constructed.at(_t.index))
_constructed.at(_t.index) =
(_instance->at(_t.index) =
utility::generate<value_type>{}(std::forward<Args>(_args)...),
true);
return _instance->at(_t.index);
(void) _grow;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
std::optional<Tp>&
thread_data<std::optional<Tp>, Tag, MaxThreads>::instance(construct_on_thread&& _t,
Args&&... _args)
{
construct(construct_on_thread{ _t }, std::forward<Args>(_args)...);
return instance()->at(_t.index);
}
//--------------------------------------------------------------------------------------//
//
// thread_data with raw data (no pointer)
//
//--------------------------------------------------------------------------------------//
template <typename Tp, typename Tag, size_t MaxThreads>
struct thread_data<identity<Tp>, Tag, MaxThreads>
: base_thread_data<thread_data<identity<Tp>, Tag, MaxThreads>>
{
using this_type = thread_data<identity<Tp>, Tag, MaxThreads>;
using value_type = Tp;
using array_type =
container::stable_vector<value_type, MaxThreads, container::cacheline_align_v>;
using functor_type = std::function<value_type()>;
thread_data() = default;
~thread_data() = default;
explicit thread_data(functor_type&& _init)
: m_init{ std::move(_init) }
{}
thread_data(const thread_data&) = default;
thread_data(thread_data&&) noexcept = default;
thread_data& operator=(const thread_data&) = default;
thread_data& operator=(thread_data&&) noexcept = default;
static unique_ptr_t<this_type>& instance();
template <typename... Args>
static unique_ptr_t<this_type>& instance(construct_on_init, Args&&...);
template <typename... Args>
static value_type& instance(construct_on_thread&&, Args&&...);
template <typename... Args>
static unique_ptr_t<this_type>& construct(construct_on_init, Args&&...);
template <typename... Args>
static value_type& construct(construct_on_thread&&, Args&&...);
size_t size() { return m_data.size(); }
decltype(auto) data() { return m_data; }
decltype(auto) data() const { return m_data; }
decltype(auto) begin() { return m_data.begin(); }
decltype(auto) end() { return m_data.end(); }
decltype(auto) begin() const { return m_data.begin(); }
decltype(auto) end() const { return m_data.end(); }
decltype(auto) at(size_t _idx) { return m_data.at(_idx); }
decltype(auto) at(size_t _idx) const { return m_data.at(_idx); }
decltype(auto) operator[](size_t _idx) { return m_data[_idx]; }
decltype(auto) operator[](size_t _idx) const { return m_data[_idx]; }
decltype(auto) reserve(size_t _n) { return m_data.reserve(_n); }
decltype(auto) capacity() const { return m_data.capacity(); }
decltype(auto) empty() const { return m_data.empty(); }
void resize(size_t _n) { container::resize(m_data, _n, m_init()); }
void resize(size_t _n, value_type&& _v) { container::resize(m_data, _n, _v); }
void fill(value_type _v)
{
for(auto& itr : m_data)
itr = _v;
}
private:
friend struct base_thread_data<this_type>;
static decltype(auto) private_instance() { return instance(); }
array_type m_data = {};
functor_type m_init = []() { return value_type{}; };
};
template <typename Tp, typename Tag, size_t MaxThreads>
unique_ptr_t<thread_data<identity<Tp>, Tag, MaxThreads>>&
thread_data<identity<Tp>, Tag, MaxThreads>::instance()
{
static auto _v = unique_ptr_t<this_type>{};
return _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
unique_ptr_t<thread_data<identity<Tp>, Tag, MaxThreads>>&
thread_data<identity<Tp>, Tag, MaxThreads>::instance(construct_on_init, Args&&... _args)
{
static auto& _v = [&]() -> unique_ptr_t<this_type>& {
auto& _ref = instance();
if(!_ref)
_ref = utility::generate<unique_ptr_t<this_type>>{}(
std::forward<Args>(_args)...);
if(_ref->size() < MaxThreads) _ref->resize(MaxThreads);
return _ref;
}();
return _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
unique_ptr_t<thread_data<identity<Tp>, Tag, MaxThreads>>&
thread_data<identity<Tp>, Tag, MaxThreads>::construct(construct_on_init, Args&&... _args)
{
// construct outside of lambda to prevent data-race
static auto& _ref = instance(construct_on_init{});
static auto _v = [&]() {
if(_ref)
{
for(auto& itr : *_ref)
itr = utility::generate<value_type>{}(std::forward<Args>(_args)...);
}
return (_ref != nullptr);
}();
return _ref;
(void) _v;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
Tp&
thread_data<identity<Tp>, Tag, MaxThreads>::construct(construct_on_thread&& _t,
Args&&... _args)
{
// construct outside of lambda to prevent data-race
static auto& _instance = instance(construct_on_init{});
static auto _constructed =
container::stable_vector<bool, MaxThreads, container::cacheline_align_v>{};
static auto _grow = []() {
container::resize(_constructed, MaxThreads, false);
grow_functors().emplace_back([](int64_t _n) -> int64_t {
if(static_cast<size_t>(_n) >= _constructed.size())
{
_constructed.reserve(_constructed.capacity() + 1);
container::resize(_constructed, _constructed.capacity(), false);
}
return _constructed.size();
});
return true;
}();
if(!_constructed.at(_t.index))
_constructed.at(_t.index) =
(_instance->at(_t.index) =
utility::generate<value_type>{}(std::forward<Args>(_args)...),
true);
return _instance->at(_t.index);
(void) _grow;
}
template <typename Tp, typename Tag, size_t MaxThreads>
template <typename... Args>
Tp&
thread_data<identity<Tp>, Tag, MaxThreads>::instance(construct_on_thread&& _t,
Args&&... _args)
{
construct(construct_on_thread{ _t }, std::forward<Args>(_args)...);
return instance()->at(_t.index);
}
//--------------------------------------------------------------------------------------//
// there are currently some strange things that happen with
// vector<instrumentation_bundle_t> so using vector<instrumentation_bundle_t*> and
// timemory's ring_buffer_allocator to create contiguous memory-page aligned instances of
// the bundle
template <typename... Tp>
struct component_bundle_cache_impl
{
using this_type = component_bundle_cache_impl<Tp...>;
using bundle_type = tim::component_bundle<project::rocprofsys, Tp...>;
using allocator_type = tim::data::ring_buffer_allocator<bundle_type>;
using array_type = std::vector<bundle_type*>;
using iterator = typename array_type::iterator;
using const_iterator = typename array_type::const_iterator;
using reverse_iterator = typename array_type::reverse_iterator;
component_bundle_cache_impl() = default;
~component_bundle_cache_impl() = default;
component_bundle_cache_impl(const component_bundle_cache_impl&) = delete;
component_bundle_cache_impl(component_bundle_cache_impl&&) noexcept = delete;
component_bundle_cache_impl& operator=(const component_bundle_cache_impl&) = delete;
component_bundle_cache_impl& operator=(component_bundle_cache_impl&&) noexcept =
delete;
bool empty() const { return m_bundles.empty(); }
auto& front() { return m_bundles.front(); }
auto& front() const { return m_bundles.front(); }
auto& back() { return m_bundles.back(); }
auto& back() const { return m_bundles.back(); }
auto begin() { return m_bundles.begin(); }
auto end() { return m_bundles.end(); }
auto rbegin() { return m_bundles.rbegin(); }
auto rend() { return m_bundles.rend(); }
auto begin() const { return m_bundles.begin(); }
auto end() const { return m_bundles.end(); }
auto size() const { return m_bundles.size(); }
auto& at(size_t _idx) { return m_bundles.at(_idx); }
const auto& at(size_t _idx) const { return m_bundles.at(_idx); }
template <typename... Args>
bundle_type* construct(Args&&... args)
{
bundle_type* _v = m_allocator.allocate(1);
m_allocator.construct(_v, std::forward<Args>(args)...);
return m_bundles.emplace_back(_v);
}
void destroy(bundle_type* _v, size_t _idx)
{
m_allocator.destroy(_v);
m_allocator.deallocate(_v, 1);
m_bundles.erase(m_bundles.begin() + _idx);
}
void pop_back()
{
bundle_type* _v = m_bundles.back();
m_allocator.destroy(_v);
m_allocator.deallocate(_v, 1);
m_bundles.pop_back();
}
template <typename IterT>
void destroy(IterT _v)
{
iterator itr = begin();
if constexpr(std::is_same<IterT, reverse_iterator>::value)
{
if(_v == rend()) return;
std::advance(itr, std::distance(rbegin(), _v));
}
else
{
if(_v == end()) return;
itr = _v;
}
m_allocator.destroy(*itr);
m_allocator.deallocate(*itr, 1);
m_bundles.erase(itr);
}
private:
allocator_type m_allocator = {};
array_type m_bundles = {};
};
template <typename... Tp>
struct component_bundle_cache_impl<tim::component_bundle<project::rocprofsys, Tp...>>
: component_bundle_cache_impl<Tp...>
{
using base_type = component_bundle_cache_impl<Tp...>;
};
//--------------------------------------------------------------------------------------//
template <typename... Tp>
using component_bundle_cache = thread_data<component_bundle_cache_impl<Tp...>>;
using instrumentation_bundles = component_bundle_cache<instrumentation_bundle_t>;
extern template struct component_bundle_cache_impl<instrumentation_bundle_t>;
} // namespace rocprofsys
@@ -0,0 +1,58 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "library/thread_deleter.hpp"
#include "api.hpp"
#include "core/utility.hpp"
#include "library/components/pthread_create_gotcha.hpp"
#include "library/thread_info.hpp"
#include <timemory/backends/threading.hpp>
#include <timemory/components/timing/backends.hpp>
namespace rocprofsys
{
template struct component_bundle_cache_impl<instrumentation_bundle_t>;
void
thread_deleter<void>::operator()() const
{
// called after thread info is deleted
if(!thread_info::exists()) return;
const auto& _info = thread_info::get();
if(_info && _info->index_data)
{
auto _tid = _info->index_data->sequent_value;
component::pthread_create_gotcha::shutdown(_tid);
set_thread_state(ThreadState::Completed);
if(get_state() < State::Finalized && _tid == 0) rocprofsys_finalize_hidden();
}
else
{
set_thread_state(ThreadState::Completed);
}
}
template struct thread_deleter<void>;
} // namespace rocprofsys
@@ -0,0 +1,52 @@
// MIT License
//
// Copyright (c) 2022-2024 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 "core/concepts.hpp"
#include "core/defines.hpp"
namespace rocprofsys
{
template <>
struct thread_deleter<void>
{
void operator()() const;
};
extern template struct thread_deleter<void>;
template <typename Tp>
struct thread_deleter
{
void operator()(Tp* ptr) const
{
constexpr bool delete_pointer =
(use_placement_new_when_generating_unique_ptr<Tp>::value == false);
thread_deleter<void>{}();
if constexpr(delete_pointer) delete ptr;
(void) ptr;
}
};
} // namespace rocprofsys

Some files were not shown because too many files have changed in this diff Show More