Add 'projects/rocprofiler-systems/' from commit '92e1d84c72c9321d79a1866e0090fae0215e6557'

git-subtree-dir: projects/rocprofiler-systems
git-subtree-mainline: ee9e74df21
git-subtree-split: 92e1d84c72
This commit is contained in:
systems-assistant[bot]
2025-07-17 18:13:44 +00:00
melakukan 6755fa3a36
699 mengubah file dengan 179131 tambahan dan 0 penghapusan
@@ -0,0 +1,17 @@
# executable RPATH
set(ROCPROFSYS_EXE_INSTALL_RPATH
"\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}"
)
# executables
add_subdirectory(rocprof-sys-avail)
add_subdirectory(rocprof-sys-causal)
add_subdirectory(rocprof-sys-sample)
add_subdirectory(rocprof-sys-instrument)
add_subdirectory(rocprof-sys-run)
# tests
if(ROCPROFSYS_BUILD_TESTING OR "$ENV{ROCPROFSYS_CI}" MATCHES "[1-9]+|ON|on|y|yes")
add_subdirectory(tests)
endif()
@@ -0,0 +1,49 @@
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems-avail target
#
# ------------------------------------------------------------------------------#
add_executable(rocprofiler-systems-avail)
target_sources(
rocprofiler-systems-avail
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/avail.cpp
${CMAKE_CURRENT_LIST_DIR}/avail.hpp
${CMAKE_CURRENT_LIST_DIR}/common.cpp
${CMAKE_CURRENT_LIST_DIR}/common.hpp
${CMAKE_CURRENT_LIST_DIR}/component_categories.hpp
${CMAKE_CURRENT_LIST_DIR}/defines.hpp
${CMAKE_CURRENT_LIST_DIR}/enumerated_list.hpp
${CMAKE_CURRENT_LIST_DIR}/generate_config.cpp
${CMAKE_CURRENT_LIST_DIR}/generate_config.hpp
${CMAKE_CURRENT_LIST_DIR}/get_availability.hpp
${CMAKE_CURRENT_LIST_DIR}/get_categories.hpp
${CMAKE_CURRENT_LIST_DIR}/info_type.cpp
${CMAKE_CURRENT_LIST_DIR}/info_type.hpp
)
target_include_directories(rocprofiler-systems-avail PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_definitions(
rocprofiler-systems-avail
PRIVATE ROCPROFSYS_EXTERN_COMPONENTS=0
)
target_link_libraries(
rocprofiler-systems-avail
PRIVATE
rocprofiler-systems::rocprofiler-systems-compile-definitions
rocprofiler-systems::rocprofiler-systems-interface-library
rocprofiler-systems::librocprofiler-systems-static
)
set_target_properties(
rocprofiler-systems-avail
PROPERTIES
BUILD_RPATH "\$ORIGIN:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH "${ROCPROFSYS_EXE_INSTALL_RPATH}"
OUTPUT_NAME ${BINARY_NAME_PREFIX}-avail
)
rocprofiler_systems_strip_target(rocprofiler-systems-avail)
install(TARGETS rocprofiler-systems-avail DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
File diff ditekan karena terlalu besar Load Diff
@@ -0,0 +1,329 @@
// MIT License
//
// Copyright (c) 2022 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 "defines.hpp"
#include <timemory/settings/macros.hpp>
#include <timemory/tpls/cereal/archives.hpp>
#include <timemory/tpls/cereal/cereal/external/base64.hpp>
#include <timemory/utility/demangle.hpp>
#include <algorithm>
#include <array>
#include <functional>
#include <iomanip>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#if !defined(TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME)
# define TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME epilogue
#endif
#if !defined(TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME)
# define TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME prologue
#endif
//======================================================================================//
namespace tim
{
namespace cereal
{
class SettingsTextArchive
: public OutputArchive<SettingsTextArchive>
, public traits::TextArchive
{
public:
using width_type = std::vector<uint64_t>;
using value_type = std::string;
using entry_type = std::map<std::string, value_type>;
using array_type = std::vector<entry_type>;
using unique_set = std::set<std::string>;
using int_stack = std::stack<uint32_t>;
public:
//! Construct, outputting to the provided stream
/// \param stream The array of output data
SettingsTextArchive(array_type& stream, unique_set exclude)
: OutputArchive<SettingsTextArchive>(this)
, output_stream(&stream)
, exclude_stream(std::move(exclude))
{
name_counter.push(0);
}
~SettingsTextArchive() override = default;
void saveBinaryValue(const void* data, size_t size, const char* name = nullptr)
{
setNextName(name);
writeName();
auto base64string =
base64::encode(reinterpret_cast<const unsigned char*>(data), size);
saveValue(base64string);
}
void startNode() { name_counter.push(0); }
void finishNode() { name_counter.pop(); }
//! Sets the name for the next node created with startNode
void setNextName(const char* name)
{
if(exclude_stream.count(name) > 0) return;
if((current_entry != nullptr) && value_keys.count(name) > 0)
{
current_entry->insert({ name, "" });
current_value = &((*current_entry)[name]);
return;
}
if(value_keys.count(name) > 0)
{
return;
}
current_value = nullptr;
output_stream->push_back(entry_type{});
current_entry = &(output_stream->back());
current_entry->insert({ "identifier", name });
std::string func = name;
const std::string prefix = TIMEMORY_SETTINGS_PREFIX;
func = func.erase(0, prefix.length());
std::transform(func.begin(), func.end(), func.begin(),
[](char& c) { return tolower(c); });
{
std::stringstream ss;
ss << "settings::" << func << "()";
current_entry->insert({ "static_accessor", ss.str() });
}
{
std::stringstream ss;
ss << "settings::instance()->get_" << func << "()";
current_entry->insert({ "member_accessor", ss.str() });
}
{
std::stringstream ss;
ss << "settings." << func;
current_entry->insert({ "python_accessor", ss.str() });
}
}
void setNextType(const char*) {}
public:
template <typename Tp>
inline void saveValue(Tp _val)
{
std::stringstream ssval;
ssval << std::boolalpha << _val;
if(current_value)
{
*current_value = ssval.str();
}
}
void writeName() {}
void makeArray() {}
private:
value_type* current_value = nullptr;
entry_type* current_entry = nullptr;
array_type* output_stream = nullptr;
unique_set exclude_stream = {};
int_stack name_counter;
unique_set value_keys = { "name", "value", "description", "count",
"environ", "max_count", "cmdline", "data_type",
"initial", "categories" };
};
//======================================================================================//
//
// prologue and epilogue functions
//
//======================================================================================//
//--------------------------------------------------------------------------------------//
//! Prologue for NVPs for settings archive
/*! NVPs do not start or finish nodes - they just set up the names */
template <typename T>
inline void
TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME(SettingsTextArchive&, const NameValuePair<T>&)
{}
//--------------------------------------------------------------------------------------//
//! Epilogue for NVPs for settings archive
/*! NVPs do not start or finish nodes - they just set up the names */
template <typename T>
inline void
TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME(SettingsTextArchive&, const NameValuePair<T>&)
{}
//--------------------------------------------------------------------------------------//
//! Prologue for deferred data for settings archive
/*! Do nothing for the defer wrapper */
template <typename T>
inline void
TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME(SettingsTextArchive&, const DeferredData<T>&)
{}
//--------------------------------------------------------------------------------------//
//! Epilogue for deferred for settings archive
/*! NVPs do not start or finish nodes - they just set up the names */
template <typename T>
inline void
TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME(SettingsTextArchive&, const DeferredData<T>&)
{}
//--------------------------------------------------------------------------------------//
//! Prologue for SizeTags for settings archive
/*! SizeTags are ignored */
template <typename T>
inline void
TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME(SettingsTextArchive& ar, const SizeTag<T>&)
{
ar.makeArray();
}
//--------------------------------------------------------------------------------------//
//! Epilogue for SizeTags for settings archive
/*! SizeTags are ignored */
template <typename T>
inline void
TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME(SettingsTextArchive&, const SizeTag<T>&)
{}
//--------------------------------------------------------------------------------------//
//! Prologue for all other types for settings archive
/*! Starts a new node, named either automatically or by some NVP,
that may be given data by the type about to be archived*/
template <typename T>
inline void
TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME(SettingsTextArchive& ar, const T&)
{
ar.startNode();
}
//--------------------------------------------------------------------------------------//
//! Epilogue for all other types other for settings archive
/*! Finishes the node created in the prologue*/
template <typename T>
inline void
TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME(SettingsTextArchive& ar, const T&)
{
ar.finishNode();
}
//--------------------------------------------------------------------------------------//
//! Prologue for arithmetic types for settings archive
inline void
TIMEMORY_CEREAL_PROLOGUE_FUNCTION_NAME(SettingsTextArchive&, const std::nullptr_t&)
{}
//--------------------------------------------------------------------------------------//
//! Epilogue for arithmetic types for settings archive
inline void
TIMEMORY_CEREAL_EPILOGUE_FUNCTION_NAME(SettingsTextArchive&, const std::nullptr_t&)
{}
//======================================================================================//
//
// Common serialization functions
//
//======================================================================================//
//! Serializing NVP types
template <typename T>
inline void
TIMEMORY_CEREAL_SAVE_FUNCTION_NAME(SettingsTextArchive& ar, const NameValuePair<T>& t)
{
ar.setNextName(t.name);
if(std::is_same<T, std::string>::value)
{
ar.setNextType("string");
}
else
{
ar.setNextType(tim::demangle<T>().c_str());
}
ar(t.value);
}
template <typename CharT, typename Traits, typename Alloc>
inline void
TIMEMORY_CEREAL_SAVE_FUNCTION_NAME(
SettingsTextArchive& ar,
const NameValuePair<std::basic_string<CharT, Traits, Alloc>>& t)
{
ar.setNextName(t.name);
ar.setNextType("string");
ar(t.value);
}
//! Saving for nullptr
inline void
TIMEMORY_CEREAL_SAVE_FUNCTION_NAME(SettingsTextArchive&, const std::nullptr_t&)
{}
//! Saving for arithmetic
template <typename T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae>
inline void
TIMEMORY_CEREAL_SAVE_FUNCTION_NAME(SettingsTextArchive& ar, const T& t)
{
if(std::is_same<T, std::string>::value) ar.setNextType("string");
ar.saveValue(t);
}
//! saving string
template <typename CharT, typename Traits, typename Alloc>
inline void
TIMEMORY_CEREAL_SAVE_FUNCTION_NAME(SettingsTextArchive& ar,
const std::basic_string<CharT, Traits, Alloc>& str)
{
ar.setNextType("string");
ar.saveValue(str);
}
//--------------------------------------------------------------------------------------//
//! Saving SizeTags
template <typename T>
inline void
TIMEMORY_CEREAL_SAVE_FUNCTION_NAME(SettingsTextArchive&, const SizeTag<T>&)
{
// nothing to do here, we don't explicitly save the size
}
} // namespace cereal
} // namespace tim
// register archives for polymorphic support
TIMEMORY_CEREAL_REGISTER_ARCHIVE(SettingsTextArchive)
@@ -0,0 +1,398 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "common.hpp"
#include <timemory/mpl/apply.hpp>
#include <timemory/settings/settings.hpp>
#include <timemory/variadic/macros.hpp>
#include <string>
#include <sys/stat.h>
using settings = ::tim::settings;
std::string global_delim = std::string{ "|" };
bool csv = false;
bool markdown = false;
bool alphabetical = false;
bool available_only = false;
bool all_info = false;
bool force_brief = false;
bool case_insensitive = false;
bool regex_hl = false;
bool expand_keys = false;
bool force_config = false;
bool print_advanced = false;
int32_t max_width = 0;
int32_t num_cols = 0;
int32_t min_width = 40;
int32_t padding = 4;
str_vec_t regex_keys = {};
str_vec_t category_regex_keys = {};
str_set_t category_view = {};
std::stringstream lerr{};
bool debug_msg = tim::get_env<bool>("ROCPROFSYS_DEBUG_AVAIL", settings::debug());
int32_t verbose_level =
tim::get_env<int32_t>("ROCPROFSYS_VERBOSE_AVAIL", settings::verbose());
// explicit setting names to exclude
std::set<std::string> settings_exclude = {
"ROCPROFSYS_ENVIRONMENT",
"ROCPROFSYS_COMMAND_LINE",
"cereal_class_version",
"settings",
};
//--------------------------------------------------------------------------------------//
namespace
{
const auto&
get_regex_constants()
{
static auto _constants = []() {
auto _v = regex_const::egrep | regex_const::optimize;
if(case_insensitive) _v |= regex_const::icase;
return _v;
}();
return _constants;
}
const auto&
get_regex_pattern()
{
static auto _pattern = []() {
std::array<std::string, 2> _v{};
for(const auto& itr : regex_keys)
{
if(itr.empty()) continue;
std::string _local_pattern = {};
if(itr.at(0) == '~')
{
_local_pattern = itr.substr(1);
_v.at(1) += "|" + _local_pattern;
}
else
{
_local_pattern = itr;
_v.at(0) += "|" + _local_pattern;
}
lerr << "Adding regex key: '" << _local_pattern << "'...\n";
}
for(auto& itr : _v)
if(!itr.empty()) itr = itr.substr(1);
return _v;
}();
return _pattern;
}
auto
get_regex()
{
static auto _rc = std::array<std::regex, 2>{
std::regex(get_regex_pattern().at(0), get_regex_constants()),
std::regex(get_regex_pattern().at(1), get_regex_constants())
};
return _rc;
}
bool
regex_match(const std::string& _line)
{
if(get_regex_pattern().at(0).empty() && get_regex_pattern().at(1).empty())
return true;
static size_t lerr_width = 0;
lerr_width = std::max<size_t>(lerr_width, _line.length());
std::stringstream _line_ss;
_line_ss << "'" << _line << "'";
if(!get_regex_pattern().at(1).empty())
{
if(std::regex_match(_line, get_regex().at(1)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " matched negating pattern '" << get_regex_pattern().at(1)
<< "'...\n";
return false;
}
if(std::regex_search(_line, get_regex().at(1)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " found negating pattern '" << get_regex_pattern().at(1) << "'...\n";
return false;
}
}
if(!get_regex_pattern().at(0).empty())
{
if(std::regex_match(_line, get_regex().at(0)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " matched pattern '" << get_regex_pattern().at(0) << "'...\n";
return true;
}
if(std::regex_search(_line, get_regex().at(0)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " found pattern '" << get_regex_pattern().at(0) << "'...\n";
return true;
}
}
lerr << std::left << std::setw(lerr_width) << _line_ss.str() << " missing pattern '"
<< get_regex_pattern().at(0) << "'...\n";
return false;
}
std::string
regex_replace(const std::string& _line)
{
#if defined(TIMEMORY_UNIX)
if(get_regex_pattern().empty()) return _line;
if(regex_match(_line))
return std::regex_replace(_line, get_regex().at(0), "\33[01;04;36;40m$&\33[0m");
#endif
return _line;
}
const auto&
get_category_regex_pattern()
{
static auto _pattern = []() {
std::array<std::string, 2> _v{};
for(const auto& itr : category_regex_keys)
{
if(itr.empty()) continue;
std::string _local_pattern = {};
if(itr.at(0) == '~')
{
_local_pattern = itr.substr(1);
_v.at(1) += "|" + _local_pattern;
}
else
{
_local_pattern = itr;
_v.at(0) += "|" + _local_pattern;
}
lerr << "Adding category regex key: '" << _local_pattern << "'...\n";
}
for(auto& itr : _v)
if(!itr.empty()) itr = itr.substr(1);
return _v;
}();
return _pattern;
}
auto
get_category_regex()
{
static auto _rc = std::array<std::regex, 2>{
std::regex(get_category_regex_pattern().at(0), get_regex_constants()),
std::regex(get_category_regex_pattern().at(1), get_regex_constants())
};
return _rc;
}
bool
category_regex_match(const std::string& _line)
{
if(get_category_regex_pattern().at(0).empty() &&
get_category_regex_pattern().at(1).empty())
return true;
static size_t lerr_width = 0;
lerr_width = std::max<size_t>(lerr_width, _line.length());
std::stringstream _line_ss;
_line_ss << "'" << _line << "'";
if(!get_category_regex_pattern().at(1).empty())
{
if(std::regex_match(_line, get_category_regex().at(1)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " matched negating category pattern '"
<< get_category_regex_pattern().at(1) << "'...\n";
return false;
}
if(std::regex_search(_line, get_category_regex().at(1)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " found negating category pattern '"
<< get_category_regex_pattern().at(1) << "'...\n";
return false;
}
}
if(!get_category_regex_pattern().at(0).empty())
{
if(std::regex_match(_line, get_category_regex().at(0)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " matched category pattern '" << get_category_regex_pattern().at(0)
<< "'...\n";
return true;
}
if(std::regex_search(_line, get_category_regex().at(0)))
{
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " found category pattern '" << get_category_regex_pattern().at(0)
<< "'...\n";
return true;
}
}
lerr << std::left << std::setw(lerr_width) << _line_ss.str()
<< " missing category pattern '" << get_category_regex_pattern().at(0)
<< "'...\n";
return false;
}
} // namespace
//--------------------------------------------------------------------------------------//
bool
is_selected(const std::string& _line)
{
return regex_match(_line);
}
//--------------------------------------------------------------------------------------//
bool
is_category_selected(const std::string& _line)
{
return category_regex_match(_line);
}
//--------------------------------------------------------------------------------------//
std::string
hl_selected(const std::string& _line)
{
return (regex_hl) ? regex_replace(_line) : _line;
}
//--------------------------------------------------------------------------------------//
void
process_categories(parser_t& p, const str_set_t& _category_options)
{
category_view = p.get<str_set_t>("categories");
std::vector<std::function<void()>> _shorthand_patches{};
for(const auto& itr : category_view)
{
auto _is_shorthand = [&_shorthand_patches, &_category_options,
itr](const std::string& _prefix) {
auto _opt = TIMEMORY_JOIN("::", _prefix, itr);
if(_category_options.count(_opt) > 0)
{
_shorthand_patches.emplace_back([itr, _opt]() {
category_view.erase(itr);
category_view.emplace(_opt);
});
return true;
}
return false;
};
if(_category_options.count(itr) == 0)
{
if(!_is_shorthand("component") && !_is_shorthand("settings") &&
!_is_shorthand("hw_counters"))
throw std::runtime_error(
itr + " is not a valid category. Use --list-categories to view "
"valid categories");
}
}
for(auto&& itr : _shorthand_patches)
itr();
}
//--------------------------------------------------------------------------------------//
bool
exclude_setting(const std::string& _v)
{
if(settings_exclude.find(_v) != settings_exclude.end()) return true;
auto itr = settings::instance()->find(_v, false);
if(itr == settings::instance()->end()) return true;
return itr->second->get_hidden();
}
//--------------------------------------------------------------------------------------//
void
dump_log()
{
if(debug_msg)
{
std::cerr << lerr.str() << std::flush;
lerr = std::stringstream{};
}
}
void
dump_log_abort(int _v)
{
fprintf(stderr, "\n[rocprof-sys-avail] Exiting with signal %i...\n", _v);
debug_msg = true;
dump_log();
}
//--------------------------------------------------------------------------------------//
std::string
remove(std::string inp, const std::set<std::string>& entries)
{
for(const auto& itr : entries)
{
auto idx = inp.find(itr);
while(idx != std::string::npos)
{
inp.erase(idx, itr.length());
idx = inp.find(itr);
}
}
return inp;
}
//--------------------------------------------------------------------------------------//
bool
file_exists(const std::string& _fname)
{
struct stat _buffer;
if(stat(_fname.c_str(), &_buffer) == 0)
return (S_ISREG(_buffer.st_mode) != 0 || S_ISLNK(_buffer.st_mode) != 0);
return false;
}
//--------------------------------------------------------------------------------------//
@@ -0,0 +1,198 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include "defines.hpp"
#include <timemory/components/types.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/settings/types.hpp>
#include <timemory/utility/argparse.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/type_list.hpp>
#include <array>
#include <cstddef>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <vector>
//--------------------------------------------------------------------------------------//
// namespaces
namespace regex_const = ::std::regex_constants; // NOLINT
namespace comp = ::tim::component; // NOLINT
using settings = ::tim::settings; // NOLINT
using tim::demangle; // NOLINT
using tim::type_list; // NOLINT
//--------------------------------------------------------------------------------------//
// aliases
template <typename Tp, size_t N>
using array_t = ::std::array<Tp, N>;
using string_t = ::std::string;
using stringstream_t = ::std::stringstream;
using str_vec_t = ::std::vector<string_t>;
using str_set_t = ::std::set<string_t>;
using info_type_base = ::std::tuple<string_t, bool, str_vec_t>;
using parser_t = ::tim::argparse::argument_parser;
//--------------------------------------------------------------------------------------//
// enums
enum : int
{
VAL = 0,
ENUM = 1,
LANG = 2,
CID = 3,
FNAME = 4,
DESC = 5,
CATEGORY = 6,
TOTAL = 7
};
//--------------------------------------------------------------------------------------//
// variables
constexpr size_t num_component_options = 7;
constexpr size_t num_settings_options = 4;
constexpr size_t num_hw_counter_options = 5;
constexpr size_t num_dump_config_options = TOTAL;
extern std::string global_delim;
extern bool csv;
extern bool markdown;
extern bool alphabetical;
extern bool available_only;
extern bool all_info;
extern bool force_brief;
extern bool debug_msg;
extern bool case_insensitive;
extern bool regex_hl;
extern bool expand_keys;
extern bool force_config;
extern bool print_advanced;
extern int32_t max_width;
extern int32_t num_cols;
extern int32_t min_width;
extern int32_t padding;
extern int32_t verbose_level;
extern str_vec_t regex_keys;
extern str_vec_t category_regex_keys;
extern str_set_t category_view;
extern std::stringstream lerr;
// explicit setting names to exclude
extern std::set<std::string> settings_exclude;
// exclude some timemory settings which are not relevant to rocprof-sys
// exact matches, e.g. ROCPROFSYS_BANNER
extern std::string settings_rexclude_exact;
// leading matches, e.g. ROCPROFSYS_MPI_[A-Z_]+
extern std::string settings_rexclude_begin;
constexpr size_t max_error_message_buffer_length = 4096;
//--------------------------------------------------------------------------------------//
// functions
bool
is_selected(const std::string& line);
bool
is_category_selected(const std::string& _line);
std::string
hl_selected(const std::string& line);
void
process_categories(parser_t&, const str_set_t&);
bool
exclude_setting(const std::string&);
void
dump_log();
void
dump_log_abort(int _v);
std::string
remove(std::string inp, const std::set<std::string>& entries);
bool
file_exists(const std::string&);
// control debug printf statements
#define errprintf(LEVEL, ...) \
{ \
if(LEVEL < verbose_level) \
{ \
if(debug_msg || verbose_level >= LEVEL) \
{ \
fprintf(stderr, "%s", tim::log::color::fatal()); \
fprintf(stderr, "[rocprof-sys][avail] Error! " __VA_ARGS__); \
fprintf(stderr, "%s", tim::log::color::end()); \
} \
char _buff[max_error_message_buffer_length]; \
snprintf(_buff, max_error_message_buffer_length, \
"[rocprof-sys][avail] Error! " __VA_ARGS__); \
throw std::runtime_error(std::string{ _buff }); \
} \
else \
{ \
if(debug_msg || verbose_level >= LEVEL) \
{ \
fprintf(stderr, "%s", tim::log::color::warning()); \
fprintf(stderr, "[rocprof-sys][avail] Warning! " __VA_ARGS__); \
fprintf(stderr, "%s", tim::log::color::end()); \
} \
} \
fflush(stderr); \
}
// control verbose printf statements
#define verbprintf(LEVEL, ...) \
{ \
if(debug_msg || verbose_level >= LEVEL) \
{ \
fprintf(stderr, "%s", tim::log::color::info()); \
fprintf(stderr, "[rocprof-sys][avail] " __VA_ARGS__); \
fprintf(stderr, "%s", tim::log::color::end()); \
} \
fflush(stderr); \
}
#define verbprintf_bare(LEVEL, ...) \
{ \
if(debug_msg || verbose_level >= LEVEL) \
{ \
fprintf(stderr, __VA_ARGS__); \
} \
fflush(stderr); \
}
@@ -0,0 +1,84 @@
// MIT License
//
// Copyright (c) 2022 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.hpp"
#include "defines.hpp"
#include <timemory/components/types.hpp>
#include <timemory/enum.h>
#include <timemory/utility/types.hpp>
#include <set>
#include <string>
template <typename Type = void>
struct component_categories;
template <typename Type>
struct component_categories
{
template <typename... Tp>
void operator()(std::set<std::string>& _v, type_list<Tp...>) const
{
//
auto _cleanup = [](std::string _type, const std::string& _pattern) {
auto _pos = std::string::npos;
while((_pos = _type.find(_pattern)) != std::string::npos)
_type = _type.erase(_pos, _pattern.length());
return _type;
};
(void) _cleanup; // unused but set if sizeof...(Tp) == 0
TIMEMORY_FOLD_EXPRESSION(_v.emplace(TIMEMORY_JOIN(
"::", "component", _cleanup(tim::try_demangle<Tp>(), "tim::"))));
}
void operator()(std::set<std::string>& _v) const
{
if constexpr(!tim::concepts::is_placeholder<Type>::value)
(*this)(_v, tim::trait::component_apis_t<Type>{});
}
};
template <>
struct component_categories<void>
{
template <size_t... Idx>
void operator()(std::set<std::string>& _v, std::index_sequence<Idx...>) const
{
TIMEMORY_FOLD_EXPRESSION(component_categories<comp::enumerator_t<Idx>>{}(_v));
}
void operator()(std::set<std::string>& _v) const
{
(*this)(_v, std::make_index_sequence<TIMEMORY_COMPONENTS_END>{});
}
auto operator()() const
{
std::set<std::string> _categories{};
(*this)(_categories);
return _categories;
}
};
@@ -0,0 +1,30 @@
// MIT License
//
// Copyright (c) 2022 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
#define TIMEMORY_DISABLE_BANNER
#define TIMEMORY_DISABLE_COMPONENT_STORAGE_INIT
#include "common/defines.h"
#include "core/config.hpp"
#include "core/defines.hpp"
@@ -0,0 +1,53 @@
// MIT License
//
// Copyright (c) 2022 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.hpp"
#include "defines.hpp"
#include <timemory/components/properties.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/utility/type_list.hpp>
#include <cstddef>
#include <tuple>
#include <type_traits>
template <typename T, typename I>
struct enumerated_list;
template <template <typename...> class TupT, typename... T>
struct enumerated_list<TupT<T...>, std::index_sequence<>>
{
using type = type_list<T...>;
};
template <template <typename...> class TupT, size_t I, typename... T, size_t... Idx>
struct enumerated_list<TupT<T...>, std::index_sequence<I, Idx...>>
{
using Tp = tim::component::enumerator_t<I>;
static constexpr bool is_nothing = tim::concepts::is_placeholder<Tp>::value;
using type = typename enumerated_list<
std::conditional_t<is_nothing, type_list<T...>, type_list<T..., Tp>>,
std::index_sequence<Idx...>>::type;
};
@@ -0,0 +1,473 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "generate_config.hpp"
#include "common.hpp"
#include "defines.hpp"
#include "info_type.hpp"
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/policy.hpp>
#include <timemory/settings.hpp>
#include <timemory/settings/types.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/cereal/archives/xml.hpp>
#include <timemory/tpls/cereal/cereal/cereal.hpp>
#include <timemory/utility/filepath.hpp>
#include <timemory/utility/types.hpp>
#include <cstddef>
#include <cstdlib>
#include <sstream>
#include <string>
namespace cereal = ::tim::cereal;
namespace filepath = ::tim::filepath;
using settings = ::tim::settings;
using ::tim::tsettings;
using ::tim::type_list;
using ::tim::policy::output_archive;
namespace
{
struct custom_setting_serializer
{
static std::array<bool, TOTAL> options;
};
template <typename Tp>
bool
ignore_setting(const Tp& _v)
{
if(_v->get_hidden()) return true;
if(exclude_setting(_v->get_env_name())) return true;
if(_v->get_config_updated() || _v->get_environ_updated()) return false;
if(!is_selected(_v->get_env_name()) && !is_selected(_v->get_name())) return true;
if(available_only && !_v->get_enabled()) return true;
if(!category_view.empty())
{
bool _found = false;
for(auto& itr : _v->get_categories())
{
if(category_view.count(itr) > 0 ||
category_view.count(TIMEMORY_JOIN("::", "settings", itr)) > 0)
{
_found = true;
break;
}
}
if(!_found) return true;
}
if(category_view.count("deprecated") == 0 &&
category_view.count("settings::deprecated") == 0 &&
_v->get_categories().count("deprecated") > 0)
return true;
if(!print_advanced && category_view.count("advanced") == 0 &&
category_view.count("settings::advanced") == 0 &&
_v->get_categories().count("advanced") > 0)
return true;
return false;
}
} // namespace
std::array<bool, TOTAL> custom_setting_serializer::options = { false };
namespace tim
{
namespace operation
{
template <typename Tp>
struct setting_serialization<Tp, custom_setting_serializer>
{
template <typename ArchiveT>
void operator()(ArchiveT&, const char*, const Tp&) const
{}
template <typename ArchiveT>
void operator()(ArchiveT&, const char*, Tp&&) const
{}
};
//
template <typename Tp>
struct setting_serialization<tsettings<Tp>, custom_setting_serializer>
{
using value_type = tsettings<Tp>;
template <typename ArchiveT>
void operator()(ArchiveT& _ar, value_type& _val) const
{
static_assert(concepts::is_output_archive<ArchiveT>::value,
"Requires an output archive");
if(ignore_setting(&_val)) return;
auto _save = std::shared_ptr<value_type>{};
if constexpr(concepts::is_string_type<Tp>::value)
{
if(_val.get_name() != "time_format")
_val.set(settings::format(_val.get(), settings::instance()->get_tag()));
if(_val.get_name() == "config_file")
{
_save = std::make_shared<value_type>(_val);
_val.set(Tp{});
}
}
if(all_info)
{
_ar(cereal::make_nvp(_val.get_env_name().c_str(), _val));
}
else
{
Tp _v = _val.get();
_ar.setNextName(_val.get_env_name().c_str());
_ar.startNode();
_ar(cereal::make_nvp("name", _val.get_name()));
_ar(cereal::make_nvp("value", _v));
if(custom_setting_serializer::options[DESC])
_ar(cereal::make_nvp("description", _val.get_description()));
if(custom_setting_serializer::options[CATEGORY])
_ar(cereal::make_nvp("description", _val.get_categories()));
if(custom_setting_serializer::options[VAL])
_ar(cereal::make_nvp("choices", _val.get_choices()));
_ar.finishNode();
}
if(_save) _val.set(_save->get());
}
};
} // namespace operation
} // namespace tim
template <typename... Tp>
void
push(type_list<Tp...>)
{
ROCPROFSYS_FOLD_EXPRESSION(
settings::push_serialize_map_callback<Tp, custom_setting_serializer>());
ROCPROFSYS_FOLD_EXPRESSION(
settings::push_serialize_data_callback<Tp, custom_setting_serializer>(
type_list<std::string>{}));
}
template <typename... Tp>
void
pop(type_list<Tp...>)
{
ROCPROFSYS_FOLD_EXPRESSION(
settings::pop_serialize_map_callback<Tp, custom_setting_serializer>());
ROCPROFSYS_FOLD_EXPRESSION(
settings::pop_serialize_data_callback<Tp, custom_setting_serializer>(
type_list<std::string>{}));
}
void
update_choices(const std::shared_ptr<settings>&);
void
generate_config(std::string _config_file, const std::set<std::string>& _config_fmts,
const std::array<bool, TOTAL>& _options)
{
custom_setting_serializer::options = _options;
auto _settings = tim::settings::shared_instance();
tim::settings::push();
_settings->find("suppress_config")->second->reset();
_settings->find("suppress_parsing")->second->reset();
_config_file = settings::format(_config_file, _settings->get_tag());
bool _absolute = _config_file.at(0) == '/';
auto _dirs = tim::delimit(_config_file, "/\\/");
_config_file = _dirs.back();
_dirs.pop_back();
std::string _output_dir = ".";
if(!_dirs.empty() && !(_dirs.size() == 1 && _dirs.at(0) == "."))
{
_output_dir = std::string{ (_absolute) ? "/" : "" } + _dirs.front();
_dirs.erase(_dirs.begin());
for(const auto& itr : _dirs)
_output_dir = TIMEMORY_JOIN('/', _output_dir, itr);
}
_output_dir += "/";
auto _fmts = std::set<std::string>{};
std::string _txt_ext = ".cfg";
for(std::string itr : { ".cfg", ".txt", ".json", ".xml" })
{
if(_config_file.length() <= itr.length()) continue;
auto _pos = _config_file.rfind(itr);
if(_pos == _config_file.length() - itr.length())
{
if(itr == ".cfg" || itr == ".txt") _txt_ext = itr;
_fmts.emplace(itr.substr(1));
_config_file = _config_file.substr(0, _pos);
}
}
if(_fmts.empty() && _config_fmts.size() == 1)
_fmts = _config_fmts;
else if(!_fmts.empty())
{
for(auto& itr : _config_fmts)
_fmts.emplace(itr);
}
update_choices(_settings);
using json_t = cereal::PrettyJSONOutputArchive;
using xml_t = cereal::XMLOutputArchive;
// stores the original serializer and replaces it with the custom one
push(type_list<json_t, xml_t>{});
static std::time_t _time{ std::time(nullptr) };
auto _serialize = [_settings](auto&& _ar) {
_ar->setNextName(TIMEMORY_PROJECT_NAME);
_ar->startNode();
(*_ar)(cereal::make_nvp("version", std::string{ ROCPROFSYS_VERSION_STRING }));
(*_ar)(cereal::make_nvp("date", tim::get_local_datetime("%F_%H.%M", &_time)));
settings::serialize_settings(*_ar, *_settings);
_ar->finishNode();
};
auto _nout = 0;
auto _open = [&_nout](std::ofstream& _ofs, const std::string& _fname,
const std::string& _type) -> std::ofstream& {
++_nout;
if(file_exists(_fname))
{
if(force_config)
{
if(settings::verbose() >= 1)
std::cout << "[rocprof-sys-avail] File '" << _fname
<< "' exists. Overwrite force...\n";
}
else
{
std::cout << "[rocprof-sys-avail] File '" << _fname
<< "' exists. Overwrite? " << std::flush;
std::string _response = {};
std::cin >> _response;
if(!tim::get_bool(_response, false)) std::exit(EXIT_FAILURE);
}
}
if(filepath::open(_ofs, _fname))
{
if(settings::verbose() >= 0)
printf("[rocprof-sys-avail] Outputting %s configuration file '%s'...\n",
_type.c_str(), _fname.c_str());
}
else
{
throw std::runtime_error(
TIMEMORY_JOIN(" ", "Error opening", _type, "output file:", _fname));
}
return _ofs;
};
if(_fmts.count("json") > 0)
{
std::stringstream _ss{};
output_archive<cereal::PrettyJSONOutputArchive>::indent_length() = 4;
_serialize(output_archive<cereal::PrettyJSONOutputArchive>::get(_ss));
auto _fname = settings::compose_output_filename(_config_file, ".json", false, -1,
true, _output_dir);
std::ofstream ofs{};
_open(ofs, _fname, "JSON") << _ss.str() << "\n";
}
if(_fmts.count("xml") > 0)
{
std::stringstream _ss{};
output_archive<cereal::XMLOutputArchive>::indent() = true;
_serialize(output_archive<cereal::XMLOutputArchive>::get(_ss));
auto _fname = settings::compose_output_filename(_config_file, ".xml", false, -1,
true, _output_dir);
std::ofstream ofs{};
_open(ofs, _fname, "XML") << _ss.str() << "\n";
}
if(_fmts.count("txt") > 0 || _fmts.count("cfg") > 0 || _nout == 0)
{
std::stringstream _ss{};
size_t _w = min_width;
std::vector<std::shared_ptr<tim::vsettings>> _data{};
for(const auto& itr : *_settings)
{
if(exclude_setting(itr.second->get_env_name())) continue;
for(const auto& citr : itr.second->get_categories())
if(citr == "deprecated") continue;
if(ignore_setting(itr.second)) continue;
_data.emplace_back(itr.second);
}
if(alphabetical)
std::sort(_data.begin(), _data.end(), [](auto _lhs, auto _rhs) {
return _lhs->get_name() < _rhs->get_name();
});
else
{
_settings->ordering();
std::sort(_data.begin(), _data.end(), [](auto _lhs, auto _rhs) {
auto _lomni = _lhs->get_categories().count("rocprofsys") > 0;
auto _romni = _rhs->get_categories().count("rocprofsys") > 0;
if(_lomni && !_romni) return true;
if(_romni && !_lomni) return false;
for(const auto* itr :
{ "ROCPROFSYS_CONFIG", "ROCPROFSYS_MODE", "ROCPROFSYS_TRACE",
"ROCPROFSYS_PROFILE", "ROCPROFSYS_USE_SAMPLING",
"ROCPROFSYS_USE_PROCESS_SAMPLING", "ROCPROFSYS_USE_ROCM",
"ROCPROFSYS_USE_AMD_SMI", "ROCPROFSYS_USE_KOKKOSP",
"ROCPROFSYS_USE_OMPT", "ROCPROFSYS_USE", "ROCPROFSYS_OUTPUT" })
{
if(_lhs->get_env_name().find(itr) == 0 &&
_rhs->get_env_name().find(itr) != 0)
return true;
if(_rhs->get_env_name().find(itr) == 0 &&
_lhs->get_env_name().find(itr) != 0)
return false;
}
for(const auto* itr :
{ "ROCPROFSYS_SUPPRESS_PARSING", "ROCPROFSYS_SUPPRESS_CONFIG" })
{
if(_lhs->get_env_name().find(itr) == 0 &&
_rhs->get_env_name().find(itr) != 0)
return false;
if(_rhs->get_env_name().find(itr) == 0 &&
_lhs->get_env_name().find(itr) != 0)
return true;
}
return _lhs->get_name() < _rhs->get_name();
});
}
for(const auto& itr : _data)
{
_w = std::max(_w, itr->get_env_name().length());
}
for(const auto& itr : _data)
{
if(exclude_setting(itr->get_env_name())) continue;
auto _has_info =
(all_info || _options[DESC] || _options[CATEGORY] || _options[VAL]);
if(_has_info) _ss << "\n# name:\n# " << itr->get_name() << "\n#\n";
if(_options[DESC] || all_info)
{
_ss << "# description:\n";
auto _desc = tim::delimit(itr->get_description(), " \n");
std::stringstream _line{};
_line << "# ";
auto _write = [&_line, &_ss, _w](std::string_view _str) {
if(_line.str().length() + _str.length() + 1 >= _w)
{
_ss << _line.str() << "\n";
_line = std::stringstream{};
_line << "# ";
}
_line << " " << _str;
};
for(auto& iitr : _desc)
_write(iitr);
_ss << _line.str() << "\n#\n";
}
if(_options[CATEGORY] || all_info)
{
_ss << "# categories:\n";
for(const auto& iitr : itr->get_categories())
_ss << "# " << iitr << "\n";
_ss << "#\n";
}
if((_options[VAL] || all_info) && !itr->get_choices().empty())
{
_ss << "# choices:\n";
for(const auto& iitr : itr->get_choices())
_ss << "# " << iitr << "\n";
_ss << "#\n";
}
if(_has_info) _ss << "\n";
_ss << std::left << std::setw(_w + 10) << itr->get_env_name() << " = ";
auto _v = itr->as_string();
if(itr->get_name() == "config_file") _v = {};
if(!_v.empty() && expand_keys && itr->get_name() != "time_format")
_v = settings::format(_v, _settings->get_tag());
_ss << _v << "\n";
}
auto _fname = settings::compose_output_filename(_config_file, _txt_ext, false, -1,
true, _output_dir);
std::ofstream ofs{};
_open(ofs, _fname, "text")
<< "# auto-generated by rocprof-sys-avail (version "
<< ROCPROFSYS_VERSION_STRING << ") on "
<< tim::get_local_datetime("%F @ %H:%M", &_time) << "\n\n"
<< _ss.str();
}
// restores the original serializer
pop(type_list<json_t, xml_t>{});
tim::settings::pop();
}
void
update_choices(const std::shared_ptr<settings>& _settings)
{
std::vector<info_type> _info = get_component_info<TIMEMORY_NATIVE_COMPONENTS_END>();
if(_settings->get_verbose() >= 2 || _settings->get_debug())
printf("[rocprof-sys-avail] # of component found: %zu\n", _info.size());
_info.erase(std::remove_if(_info.begin(), _info.end(),
[](const auto& itr) {
if(!itr.is_available()) return true;
// NOLINTNEXTLINE
for(const auto& nitr :
{ "cuda", "cupti", "nvtx", "roofline", "_bundle",
"data_integer", "data_unsigned", "data_floating",
"printer" })
{
if(itr.name().find(nitr) != std::string::npos)
return true;
}
return false;
}),
_info.end());
std::vector<std::string> _component_choices = {};
_component_choices.reserve(_info.size());
for(const auto& itr : _info)
_component_choices.emplace_back(itr.id_type());
if(_settings->get_verbose() >= 2 || _settings->get_debug())
printf("[rocprof-sys-avail] # of component choices: %zu\n",
_component_choices.size());
_settings->find("ROCPROFSYS_TIMEMORY_COMPONENTS")
->second->set_choices(_component_choices);
}
@@ -0,0 +1,32 @@
// MIT License
//
// Copyright (c) 2022 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.hpp"
#include <set>
#include <string>
void
generate_config(std::string _config_file, const std::set<std::string>& _config_fmts,
const std::array<bool, TOTAL>&);
@@ -0,0 +1,208 @@
// MIT License
//
// Copyright (c) 2022 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.hpp"
#include "defines.hpp"
#include "get_categories.hpp"
#include "info_type.hpp"
#include <timemory/components/metadata.hpp>
#include <timemory/components/properties.hpp>
#include <timemory/defines.h>
#include <timemory/enum.h>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/type_list.hpp>
#include <timemory/variadic/macros.hpp>
//--------------------------------------------------------------------------------------//
struct unknown
{};
template <typename T, typename U = typename T::value_type>
constexpr bool
available_value_type_alias(int)
{
return true;
}
template <typename T, typename U = unknown>
constexpr bool
available_value_type_alias(long)
{
return false;
}
template <typename Type, bool>
struct component_value_type;
template <typename Type>
struct component_value_type<Type, true>
{
using type = typename Type::value_type;
};
template <typename Type>
struct component_value_type<Type, false>
{
using type = unknown;
};
template <typename Type>
using component_value_type_t =
typename component_value_type<Type, available_value_type_alias<Type>(0)>::type;
//--------------------------------------------------------------------------------------//
template <typename Type = void>
struct get_availability;
//--------------------------------------------------------------------------------------//
template <typename Type>
struct get_availability
{
using this_type = get_availability<Type>;
using metadata_t = ::tim::component::metadata<Type>;
using property_t = ::tim::component::properties<Type>;
static info_type get_info();
auto operator()() const { return get_info(); }
};
//--------------------------------------------------------------------------------------//
template <typename... Types>
struct get_availability<type_list<Types...>>
{
using data_type = std::vector<info_type>;
static data_type get_info(data_type& _v)
{
TIMEMORY_FOLD_EXPRESSION(_v.emplace_back(get_availability<Types>::get_info()));
return _v;
}
static data_type get_info()
{
data_type _v{};
return get_info(_v);
}
template <typename... Args>
decltype(auto) operator()(Args&&... _args)
{
return get_info(std::forward<Args>(_args)...);
}
};
//--------------------------------------------------------------------------------------//
template <typename Type>
info_type
get_availability<Type>::get_info()
{
using namespace tim;
using value_type = component_value_type_t<Type>;
using category_types = typename trait::component_apis<Type>::type;
auto _cleanup = [](std::string _type, const std::string& _pattern) {
auto _pos = std::string::npos;
while((_pos = _type.find(_pattern)) != std::string::npos)
_type.erase(_pos, _pattern.length());
return _type;
};
auto _replace = [](std::string _type, const std::string& _pattern,
const std::string& _with) {
auto _pos = std::string::npos;
while((_pos = _type.find(_pattern)) != std::string::npos)
_type.replace(_pos, _pattern.length(), _with);
return _type;
};
bool has_metadata = metadata_t::specialized();
bool has_properties = property_t::specialized();
bool is_available = trait::is_available<Type>::value;
bool file_output = trait::generates_output<Type>::value;
auto name = component::metadata<Type>::name();
auto label = (file_output)
? ((has_metadata) ? metadata_t::label() : Type::get_label())
: std::string("");
auto description =
(has_metadata) ? metadata_t::description() : Type::get_description();
auto data_type = demangle<value_type>();
string_t enum_type = property_t::enum_string();
string_t id_type = property_t::id();
auto ids_set = property_t::ids();
if(!has_properties)
{
enum_type = "";
id_type = "";
ids_set.clear();
}
string_t ids_str = {};
{
auto itr = ids_set.begin();
string_t db = (markdown) ? "`\"" : (csv) ? "" : "\"";
string_t de = (markdown) ? "\"`" : (csv) ? "" : "\"";
if(has_metadata) description += ". " + metadata_t::extra_description();
description += ".";
while(itr->empty())
++itr;
if(itr != ids_set.end())
ids_str = TIMEMORY_JOIN("", TIMEMORY_JOIN("", db, *itr++, de));
for(; itr != ids_set.end(); ++itr)
{
if(!itr->empty())
ids_str = TIMEMORY_JOIN(", ", ids_str, TIMEMORY_JOIN("", db, *itr, de));
}
}
string_t categories = get_categories(category_types{});
description = _replace(_replace(description, ". .", "."), "..", ".");
data_type = _replace(_cleanup(data_type, "::__1"), "> >", ">>");
return info_type{ name, is_available,
str_vec_t{ data_type, enum_type, id_type, ids_str, label,
description, categories } };
}
//--------------------------------------------------------------------------------------//
template <>
struct get_availability<void>
{
template <typename... Tp, typename... Args>
decltype(auto) operator()(tim::type_list<Tp...>, Args&&... _args) const
{
return get_availability<tim::type_list<Tp...>>{}(std::forward<Args>(_args)...);
}
template <typename Tp, typename... Args>
decltype(auto) operator()(Args&&... _args) const
{
return get_availability<tim::type_list<Tp>>{}(std::forward<Args>(_args)...);
}
};
@@ -0,0 +1,61 @@
// MIT License
//
// Copyright (c) 2022 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.hpp"
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/type_list.hpp>
#include <algorithm>
#include <sstream>
#include <string>
template <typename... Tp>
auto
get_categories(type_list<Tp...>)
{
auto _cleanup = [](std::string _type, const std::string& _pattern) {
auto _pos = std::string::npos;
while((_pos = _type.find(_pattern)) != std::string::npos)
_type.erase(_pos, _pattern.length());
return _type;
};
(void) _cleanup; // unused but set if sizeof...(Tp) == 0
auto _vec = str_vec_t{ _cleanup(demangle<Tp>(), "tim::")... };
std::sort(_vec.begin(), _vec.end(), [](const auto& lhs, const auto& rhs) {
// prioritize project category
auto lpos = lhs.find("project::");
auto rpos = rhs.find("project::");
return (lpos == rpos) ? (lhs < rhs) : (lpos < rpos);
});
std::stringstream _ss{};
for(auto&& itr : _vec)
{
_ss << ", " << itr;
}
std::string _v = _ss.str();
if(!_v.empty()) return _v.substr(2);
return _v;
}
@@ -0,0 +1,54 @@
// MIT License
//
// Copyright (c) 2022 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 "info_type.hpp"
#include "enumerated_list.hpp"
#include "get_availability.hpp"
#include "api.hpp"
#include "library/components/backtrace.hpp"
#include "library/components/fork_gotcha.hpp"
#include "library/components/mpi_gotcha.hpp"
#include "library/components/pthread_gotcha.hpp"
#include <timemory/components/definition.hpp>
#include <timemory/enum.h>
#include <timemory/utility/macros.hpp>
#include <utility>
template <size_t EndV>
std::vector<info_type>
get_component_info()
{
using index_seq_t = std::make_index_sequence<EndV>;
using enum_list_t = typename enumerated_list<tim::type_list<>, index_seq_t>::type;
auto _info = std::vector<info_type>{};
return get_availability<>{}(enum_list_t{}, _info);
}
template std::vector<info_type>
get_component_info<TIMEMORY_NATIVE_COMPONENTS_END>();
template std::vector<info_type>
get_component_info<TIMEMORY_COMPONENTS_END>();
@@ -0,0 +1,75 @@
// MIT License
//
// Copyright (c) 2022 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.hpp"
#include <timemory/enum.h>
#include <timemory/utility/macros.hpp>
#include <utility>
struct info_type : info_type_base
{
TIMEMORY_DEFAULT_OBJECT(info_type)
template <typename... Args>
info_type(Args&&... _args)
: info_type_base{ std::forward<Args>(_args)... }
{}
const auto& name() const { return std::get<0>(*this); }
auto is_available() const { return std::get<1>(*this); }
const auto& info() const { return std::get<2>(*this); }
const auto& data_type() const { return info().at(0); }
const auto& enum_type() const { return info().at(1); }
const auto& id_type() const { return info().at(2); }
const auto& id_strings() const { return info().at(3); }
const auto& label() const { return info().at(4); }
const auto& description() const { return info().at(5); }
const auto& categories() const { return info().at(6); }
bool valid() const { return !name().empty() && info().size() >= 6; }
bool operator<(const info_type& rhs) const { return name() < rhs.name(); }
bool operator!=(const info_type& rhs) const { return !(*this == rhs); }
bool operator==(const info_type& rhs) const
{
if(info().size() != rhs.info().size()) return false;
for(size_t i = 0; i < info().size(); ++i)
{
if(info().at(i) != rhs.info().at(i)) return false;
}
return name() == rhs.name() && is_available() == rhs.is_available();
}
};
template <size_t EndV>
std::vector<info_type>
get_component_info();
extern template std::vector<info_type>
get_component_info<TIMEMORY_NATIVE_COMPONENTS_END>();
extern template std::vector<info_type>
get_component_info<TIMEMORY_COMPONENTS_END>();
@@ -0,0 +1,34 @@
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems-causal target
#
# ------------------------------------------------------------------------------#
add_executable(
rocprofiler-systems-causal
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-causal.cpp
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-causal.hpp
${CMAKE_CURRENT_LIST_DIR}/impl.cpp
)
target_compile_definitions(rocprofiler-systems-causal PRIVATE TIMEMORY_CMAKE=1)
target_include_directories(rocprofiler-systems-causal PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(
rocprofiler-systems-causal
PRIVATE
rocprofiler-systems::rocprofiler-systems-compile-definitions
rocprofiler-systems::rocprofiler-systems-headers
rocprofiler-systems::rocprofiler-systems-common-library
rocprofiler-systems::rocprofiler-systems-core
)
set_target_properties(
rocprofiler-systems-causal
PROPERTIES
BUILD_RPATH "\$ORIGIN:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH "${ROCPROFSYS_EXE_INSTALL_RPATH}"
OUTPUT_NAME ${BINARY_NAME_PREFIX}-causal
)
rocprofiler_systems_strip_target(rocprofiler-systems-causal)
install(TARGETS rocprofiler-systems-causal DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
@@ -0,0 +1,949 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "rocprof-sys-causal.hpp"
#include "common/defines.h"
#include "common/delimit.hpp"
#include "common/environment.hpp"
#include "common/join.hpp"
#include "common/setup.hpp"
#include "core/mproc.hpp"
#include "core/utility.hpp"
#include <timemory/environment.hpp>
#include <timemory/log/color.hpp>
#include <timemory/utility/argparse.hpp>
#include <timemory/utility/console.hpp>
#include <timemory/utility/delimit.hpp>
#include <timemory/utility/filepath.hpp>
#include <timemory/utility/join.hpp>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <gnu/lib-names.h>
#include <iostream>
#include <regex>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
#include <vector>
namespace color = ::tim::log::color;
namespace filepath = ::tim::filepath;
namespace console = ::tim::utility::console;
namespace argparse = ::tim::argparse;
using namespace ::timemory::join;
using ::rocprofsys::utility::parse_numeric_range;
using ::tim::get_env;
using ::tim::log::monochrome;
using ::tim::log::stream;
namespace std
{
std::string
to_string(bool _v)
{
return (_v) ? "true" : "false";
}
} // namespace std
namespace
{
int verbose = 0;
auto updated_envs = std::set<std::string_view>{};
auto original_envs = std::set<std::string>{};
auto child_pids = std::set<pid_t>{};
auto launcher = std::string{};
inline signal_handler&
get_signal_handler(int _sig)
{
static auto _v = std::unordered_map<int, signal_handler>{};
auto itr = _v.emplace(_sig, signal_handler{});
return itr.first->second;
}
void
create_signal_handler(int sig, signal_handler& sh, void (*func)(int))
{
if(sig < 1) return;
sh.m_custom_sigaction.sa_handler = func;
sigemptyset(&sh.m_custom_sigaction.sa_mask);
sh.m_custom_sigaction.sa_flags = SA_RESTART;
if(sigaction(sig, &sh.m_custom_sigaction, &sh.m_original_sigaction) == -1)
{
std::cerr << "Failed to create signal handler for " << sig << std::endl;
}
}
void
forward_signal(int sig)
{
for(auto itr : child_pids)
{
TIMEMORY_PRINTF_WARNING(stderr, "Killing pid=%i with signal %i...\n", itr, sig);
kill(itr, sig);
diagnose_status(itr, wait_pid(itr));
}
signal(sig, SIG_DFL);
kill(getpid(), sig);
}
} // namespace
int
get_verbose()
{
verbose = get_env("ROCPROFSYS_CAUSAL_VERBOSE",
get_env<int>("ROCPROFSYS_VERBOSE", verbose, false));
auto _debug = get_env("ROCPROFSYS_CAUSAL_DEBUG",
get_env<bool>("ROCPROFSYS_DEBUG", false, false));
if(_debug) verbose += 8;
return verbose;
}
void
forward_signals(const std::set<int>& _signals)
{
for(auto itr : _signals)
create_signal_handler(itr, get_signal_handler(itr), &forward_signal);
}
void
add_child_pid(pid_t _v)
{
child_pids.emplace(_v);
}
void
remove_child_pid(pid_t _v)
{
child_pids.erase(_v);
}
int
wait_pid(pid_t _pid, int _opts)
{
return ::rocprofsys::mproc::wait_pid(_pid, _opts);
}
int
diagnose_status(pid_t _pid, int _status)
{
return ::rocprofsys::mproc::diagnose_status(_pid, _status, get_verbose());
}
std::string
get_realpath(const std::string& _v)
{
auto* _tmp = realpath(_v.c_str(), nullptr);
auto _ret = std::string{ _tmp };
free(_tmp);
return _ret;
}
void
print_command(const std::vector<char*>& _argv, std::string_view _prefix)
{
if(verbose >= 1)
stream(std::cout, color::info())
<< _prefix << "Executing '" << join(array_config{ " " }, _argv) << "'...\n";
std::cerr << color::end() << std::flush;
}
std::vector<char*>
get_initial_environment()
{
auto _env = std::vector<char*>{};
if(environ != nullptr)
{
int idx = 0;
while(environ[idx] != nullptr)
{
auto* _v = environ[idx++];
original_envs.emplace(_v);
_env.emplace_back(strdup(_v));
}
}
update_env(_env, "ROCPROFSYS_MODE", "causal");
update_env(_env, "ROCPROFSYS_USE_CAUSAL", true);
update_env(_env, "ROCPROFSYS_USE_SAMPLING", false);
update_env(_env, "ROCPROFSYS_TRACE", false);
update_env(_env, "ROCPROFSYS_PROFILE", false);
update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", false);
update_env(_env, "ROCPROFSYS_THREAD_POOL_SIZE",
get_env<int>("ROCPROFSYS_THREAD_POOL_SIZE", 0));
update_env(_env, "ROCPROFSYS_LAUNCHER", "rocprof-sys-causal");
return _env;
}
void
prepare_command_for_run(char* _exe, std::vector<char*>& _argv)
{
if(!launcher.empty())
{
bool _injected = false;
auto _new_argv = std::vector<char*>{};
for(auto* itr : _argv)
{
if(!_injected && std::regex_search(itr, std::regex{ launcher }))
{
_new_argv.emplace_back(_exe);
_new_argv.emplace_back(strdup("--"));
_injected = true;
}
_new_argv.emplace_back(itr);
}
if(!_injected)
{
throw std::runtime_error(
join("", "rocprof-sys-causal was unable to match \"", launcher,
"\" to any arguments on the command line: \"",
join(array_config{ " ", "", "" }, _argv), "\""));
}
std::swap(_argv, _new_argv);
}
}
void
prepare_environment_for_run(std::vector<char*>& _env)
{
if(launcher.empty())
{
update_env(_env, "LD_PRELOAD",
join(":", LIBPTHREAD_SO,
get_realpath(get_internal_libpath("librocprof-sys-dl.so"))),
true);
update_env(_env, "ROCPROFSYS_SCRIPT_DIR", get_internal_script_path());
}
}
std::string
get_internal_libpath(const std::string& _lib)
{
auto _exe = std::string_view{ realpath("/proc/self/exe", nullptr) };
auto _pos = _exe.find_last_of('/');
auto _dir = std::string{ "./" };
if(_pos != std::string_view::npos) _dir = _exe.substr(0, _pos);
return rocprofsys::common::join("/", _dir, "..", "lib", _lib);
}
std::string
get_internal_script_path(void)
{
auto _exe = std::string_view{ realpath("/proc/self/exe", nullptr) };
auto _pos = _exe.find_last_of('/');
auto _dir = std::string{ "./" };
if(_pos != std::string_view::npos) _dir = _exe.substr(0, _pos);
auto _script_dir = get_realpath(
rocprofsys::common::join("/", _dir, "..", "libexec", "rocprofiler-systems"));
return _script_dir;
}
void
print_updated_environment(std::vector<char*> _env, std::string_view _prefix)
{
if(get_verbose() < 0) return;
std::sort(_env.begin(), _env.end(), [](auto* _lhs, auto* _rhs) {
if(!_lhs) return false;
if(!_rhs) return true;
return std::string_view{ _lhs } < std::string_view{ _rhs };
});
std::vector<std::string_view> _updates = {};
std::vector<std::string_view> _general = {};
for(auto* itr : _env)
{
if(itr == nullptr) continue;
auto _is_omni = (std::string_view{ itr }.find("ROCPROFSYS") == 0);
auto _updated = false;
for(const auto& vitr : updated_envs)
{
if(std::string_view{ itr }.find(vitr) == 0)
{
_updated = true;
break;
}
}
if(_updated)
_updates.emplace_back(itr);
else if(verbose >= 1 && _is_omni)
_general.emplace_back(itr);
}
if(_general.size() + _updates.size() == 0 || verbose < 0) return;
std::cerr << std::endl;
for(auto& itr : _general)
stream(std::cerr, color::source()) << _prefix << itr << "\n";
for(auto& itr : _updates)
stream(std::cerr, color::source()) << _prefix << itr << "\n";
std::cerr << color::end() << std::flush;
}
template <typename Tp>
void
update_env(std::vector<char*>& _environ, std::string_view _env_var, Tp&& _env_val,
bool _append, std::string_view _join_delim)
{
updated_envs.emplace(_env_var);
auto _key = join("", _env_var, "=");
for(auto& itr : _environ)
{
if(!itr) continue;
if(std::string_view{ itr }.find(_key) == 0)
{
if(_append)
{
if(std::string_view{ itr }.find(join("", _env_val)) ==
std::string_view::npos)
{
auto _val = std::string{ itr }.substr(_key.length());
free(itr);
if(_env_var == "LD_PRELOAD")
{
itr =
strdup(join('=', _env_var, join(_join_delim, _val, _env_val))
.c_str());
}
else
{
itr =
strdup(join('=', _env_var, join(_join_delim, _env_val, _val))
.c_str());
}
}
}
else
{
free(itr);
itr = strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str());
}
return;
}
}
_environ.emplace_back(
strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()));
}
template <typename Tp>
void
add_default_env(std::vector<char*>& _environ, std::string_view _env_var, Tp&& _env_val)
{
auto _key = join("", _env_var, "=");
for(auto& itr : _environ)
{
if(!itr) continue;
if(std::string_view{ itr }.find(_key) == 0) return;
}
updated_envs.emplace(_env_var);
_environ.emplace_back(
strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()));
}
void
remove_env(std::vector<char*>& _environ, std::string_view _env_var)
{
auto _key = join("", _env_var, "=");
auto _match = [&_key](auto itr) { return std::string_view{ itr }.find(_key) == 0; };
_environ.erase(std::remove_if(_environ.begin(), _environ.end(), _match),
_environ.end());
for(const auto& itr : original_envs)
{
if(std::string_view{ itr }.find(_key) == 0)
_environ.emplace_back(strdup(itr.c_str()));
}
}
std::vector<char*>
parse_args(int argc, char** argv, std::vector<char*>& _env,
std::vector<std::map<std::string_view, std::string>>& _causal_envs)
{
using parser_t = argparse::argument_parser;
using parser_err_t = typename parser_t::result_type;
auto help_check = [](parser_t& p, int _argc, char** _argv) {
std::set<std::string> help_args = { "-h", "--help", "-?" };
return (p.exists("help") || _argc == 1 ||
(_argc > 1 && help_args.find(_argv[1]) != help_args.end()));
};
auto _pec = EXIT_SUCCESS;
auto help_action = [&_pec, argc, argv](parser_t& p) {
if(_pec != EXIT_SUCCESS)
{
std::stringstream msg;
msg << "Error in command:";
for(int i = 0; i < argc; ++i)
msg << " " << argv[i];
msg << "\n\n";
stream(std::cerr, color::fatal()) << msg.str();
std::cerr << std::flush;
}
p.print_help();
exit(_pec);
};
const auto* _desc = R"desc(
Causal profiling usually requires multiple runs to reliably resolve the speedup estimates.
This executable is designed to streamline that process.
For example (assume all commands end with '-- <exe> <args>'):
rocprof-sys-causal -n 5 -- <exe> # runs <exe> 5x with causal profiling enabled
rocprof-sys-causal -s 0 5,10,15,20 # runs <exe> 2x with virtual speedups:
# - 0
# - randomly selected from 5, 10, 15, and 20
rocprof-sys-causal -F func_A func_B func_(A|B) # runs <exe> 3x with the function scope limited to:
# 1. func_A
# 2. func_B
# 3. func_A or func_B
General tips:
- Insert progress points at hotspots in your code or use rocprof-sys's runtime instrumentation
- Note: binary rewrite will produce a incompatible new binary
- Collect a flat profile via sampling
- E.g., rocprof-sys-sample -F -- <exe> <args>
- Inspect sampling_wall_clock.txt and sampling_cpu_clock.txt for functions to target
- Run rocprof-sys-causal in "function" mode first (does not require debug info)
- Run rocprof-sys-causal in "line" mode when you are targeting one function (requires debug info)
- Preferably, use predictions from the "function" mode to determine which function to target
- Limit the virtual speedups to a smaller pool, e.g., 0,5,10,25,50, to get reliable predictions quicker
- Make use of the binary, source, and function scope to limit the functions/lines selected for experiments
- Note: source scope requires debug info
)desc";
auto parser = parser_t{ basename(argv[0]), _desc };
parser.on_error([](parser_t&, const parser_err_t& _err) {
stream(std::cerr, color::fatal()) << _err << "\n";
exit(EXIT_FAILURE);
});
parser.enable_help();
parser.enable_version("rocprof-sys-causal", ROCPROFSYS_ARGPARSE_VERSION_INFO);
auto _cols = std::get<0>(console::get_columns());
if(_cols > parser.get_help_width() + 8)
parser.set_description_width(
std::min<int>(_cols - parser.get_help_width() - 8, 120));
parser.start_group("DEBUG OPTIONS", "");
parser.add_argument({ "--monochrome" }, "Disable colorized output")
.max_count(1)
.dtype("bool")
.action([&](parser_t& p) {
auto _monochrome = p.get<bool>("monochrome");
monochrome() = _monochrome;
p.set_use_color(!_monochrome);
update_env(_env, "ROCPROFSYS_MONOCHROME", (_monochrome) ? "1" : "0");
update_env(_env, "MONOCHROME", (_monochrome) ? "1" : "0");
});
parser.add_argument({ "--debug" }, "Debug output")
.max_count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_DEBUG", p.get<bool>("debug"));
});
parser.add_argument({ "-v", "--verbose" }, "Verbose output")
.count(1)
.action([&](parser_t& p) {
auto _v = p.get<int>("verbose");
verbose = _v;
update_env(_env, "ROCPROFSYS_VERBOSE", _v);
});
std::string _config_file = {};
std::string _config_folder = "rocprof-sys-causal-config";
bool _generate_configs = false;
bool _add_defaults = true;
parser.start_group("GENERAL OPTIONS", "");
parser.add_argument({ "-c", "--config" }, "Base configuration file")
.min_count(0)
.dtype("filepath")
.action([&](parser_t& p) {
_config_file =
join(array_config{ ":" }, p.get<std::vector<std::string>>("config"));
});
parser
.add_argument(
{ "-l", "--launcher" },
"When running MPI jobs, rocprof-sys-causal needs to be *before* the "
"executable "
"which launches the MPI processes (i.e. before `mpirun`, `srun`, etc.). Pass "
"the name of the target executable (or a regex for matching to the name of "
"the target) for causal profiling, e.g., `rocprof-sys-causal -l foo -- "
"mpirun "
"-n 4 foo`. This ensures that the rocprof-sys library is LD_PRELOADed on the "
"proper target")
.count(1)
.dtype("executable")
.action([&](parser_t& p) { launcher = p.get<std::string>("launcher"); });
parser
.add_argument({ "-g", "--generate-configs" },
"Generate config files instead of passing environment variables "
"directly. If no arguments are provided, the config files will be "
"placed in ${PWD}/rocprof-sys-causal-config folder")
.min_count(0)
.max_count(1)
.dtype("folder")
.action([&](parser_t& p) {
_generate_configs = true;
auto _dir = p.get<std::string>("generate-configs");
if(!_dir.empty()) _config_folder = std::move(_dir);
if(!filepath::exists(_config_folder)) filepath::makedir(_config_folder);
});
parser
.add_argument({ "--no-defaults" },
"Do not activate default features which are recommended for causal "
"profiling. For example: PID-tagging of output files and "
"timestamped subdirectories are disabled by default. Kokkos tools "
"support is added by default (ROCPROFSYS_USE_KOKKOSP=ON) because, "
"for Kokkos applications, the Kokkos-Tools callbacks are used for "
"progress points. Activation of OpenMP tools support is similar")
.min_count(0)
.max_count(1)
.dtype("bool")
.action([&](parser_t& p) { _add_defaults = !p.get<bool>("no-defaults"); });
parser.start_group("CAUSAL PROFILING OPTIONS (General)",
"These settings will be applied to all causal profiling runs");
parser
.add_argument({ "-m", "--mode" },
"Causal profiling mode. Function mode tends to resolve statistics "
"faster than line mode (due to smaller sampling space). Ideally, "
"use function mode first to identify a function to target and then "
"switch to line mode + function scope setting")
.count(1)
.dtype("string")
.choices({ "function", "line" })
.choice_alias("function", { "func" })
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_CAUSAL_MODE", p.get<std::string>("mode"));
});
parser.add_argument({ "-b", "--backend" }, "Causal profiling sampling backend.")
.count(1)
.dtype("string")
.choices({ "auto", "perf", "timer" })
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_CAUSAL_BACKEND", p.get<std::string>("backend"));
});
parser
.add_argument({ "-o", "--output-name" },
"Output filename of causal profiling data w/o extension")
.min_count(1)
.dtype("filename")
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_CAUSAL_FILE", p.get<std::string>("output-name"));
});
bool _reset = false;
parser
.add_argument({ "-r", "--reset" },
"Overwrite any existing experiment results during the first run")
.max_count(1)
.dtype("bool")
.action([&](parser_t& p) { _reset = p.get<bool>("reset"); });
parser
.add_argument({ "-e", "--end-to-end" },
"Single causal experiment for the entire application runtime")
.max_count(1)
.dtype("bool")
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_CAUSAL_END_TO_END", p.get<bool>("end-to-end"));
});
parser
.add_argument({ "-w", "--wait" },
"Set the wait time (i.e. delay) before starting the first causal "
"experiment (in seconds)")
.count(1)
.dtype("seconds")
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_CAUSAL_DELAY", p.get<double>("wait"));
});
parser
.add_argument(
{ "-d", "--duration" },
"Set the length of time (in seconds) to perform causal experimentationafter "
"the first experiment is started. Once this amount of time has elapsed, no "
"more causal experiments will be started but any currently running "
"experiment will be allowed to finish.")
.count(1)
.dtype("seconds")
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_CAUSAL_DURATION", p.get<double>("duration"));
});
int64_t _niterations = 1;
auto _virtual_speedups = std::vector<std::string>{};
auto _function_scopes = std::vector<std::string>{};
auto _binary_scopes = std::vector<std::string>{};
auto _source_scopes = std::vector<std::string>{};
auto _function_excludes = std::vector<std::string>{};
auto _binary_excludes = std::vector<std::string>{};
auto _source_excludes = std::vector<std::string>{};
parser
.add_argument({ "-n", "--iterations" },
"Number of times to repeat the combination of run configurations")
.count(1)
.dtype("int")
.action([&](parser_t& p) { _niterations = p.get<int64_t>("iterations"); });
parser.start_group(
"CAUSAL PROFILING OPTIONS (Combinatorial)",
"Each individual argument to these options will multiply the number runs by the "
"number of arguments and the number of iterations. E.g. -n 2 -B \"MAIN\" -F "
"\"foo\" \"bar\" will produce 4 runs: 2 iterations x 1 binary scope x 2 function "
"scopes (MAIN+foo, MAIN+bar, MAIN+foo, MAIN+bar)");
parser
.add_argument(
{ "-s", "--speedups" },
"Pool of virtual speedups to sample from during experimentation. "
"Each space designates a group and multiple speedups can be "
"grouped together by commas, e.g. '-s 0 0,10,20-50' is two groups: "
"group #1 is '0' and group #2 is '0 10 20 25 30 35 40 45 50' -- "
"unless end-to-end mode is activated: in end-to-end mode, only one "
"speedup is selected for the entire run so all groups are "
"expanded. If a range is specified, the default increment is 5, "
"however, this can be overridden by suffixing the range with a colon and the "
"desired increment, e.g., '0-40:10' would expand to '0 10 20 30 40'")
.min_count(1)
.max_count(-1)
.dtype("integer | range | range:increment")
.action([&](parser_t& p) {
auto _val = p.get<std::vector<std::string>>("speedups");
if(p.get<bool>("end-to-end"))
{
_virtual_speedups.clear();
for(const auto& itr : _val)
{
for(const auto& ditr : tim::delimit(itr, ",; \t\n\r"))
{
for(auto nitr :
parse_numeric_range<int64_t, std::vector<int64_t>>(
ditr, "virtual speedup", 5L))
{
_virtual_speedups.emplace_back(std::to_string(nitr));
}
}
}
}
else
{
_virtual_speedups = _val;
}
});
parser
.add_argument({ "-B", "--binary-scope" },
"Restricts causal experiments to the binaries matching the list of "
"regular expressions. Each space designates a group and multiple "
"scopes can be grouped together with a semi-colon")
.min_count(0)
.max_count(-1)
.dtype("integers")
.action([&](parser_t& p) {
_binary_scopes = p.get<std::vector<std::string>>("binary-scope");
});
parser
.add_argument({ "-S", "--source-scope" },
"Restricts causal experiments to the source files or source file + "
"lineno pairs (i.e. <file> or <file>:<line>) matching the list of "
"regular expressions. Each space designates a group and multiple "
"scopes can be grouped together with a semi-colon")
.min_count(0)
.max_count(-1)
.dtype("integers")
.action([&](parser_t& p) {
_source_scopes = p.get<std::vector<std::string>>("source-scope");
});
parser
.add_argument(
{ "-F", "--function-scope" },
"Restricts causal experiments to the functions matching the list of "
"regular expressions. Each space designates a group and multiple "
"scopes can be grouped together with a semi-colon")
.min_count(0)
.max_count(-1)
.dtype("regex-list")
.action([&](parser_t& p) {
_function_scopes = p.get<std::vector<std::string>>("function-scope");
});
parser
.add_argument(
{ "-BE", "--binary-exclude" },
"Excludes causal experiments from being performed on the binaries matching "
"the list of regular expressions. Each space designates a group and multiple "
"excludes can be grouped together with a semi-colon")
.min_count(0)
.max_count(-1)
.dtype("integers")
.action([&](parser_t& p) {
_binary_excludes = p.get<std::vector<std::string>>("binary-exclude");
});
parser
.add_argument(
{ "-SE", "--source-exclude" },
"Excludes causal experiments from being performed on the code from the "
"source files or source file + lineno pair (i.e. <file> or <file>:<line>) "
"matching the list of regular expressions. Each space designates a group and "
"multiple excludes can be grouped together with a semi-colon")
.min_count(0)
.max_count(-1)
.dtype("integers")
.action([&](parser_t& p) {
_source_excludes = p.get<std::vector<std::string>>("source-exclude");
});
parser
.add_argument(
{ "-FE", "--function-exclude" },
"Excludes causal experiments from being performed on the functions matching "
"the list of regular expressions. Each space designates a group and multiple "
"excludes can be grouped together with a semi-colon")
.min_count(0)
.max_count(-1)
.dtype("regex-list")
.action([&](parser_t& p) {
_function_excludes = p.get<std::vector<std::string>>("function-exclude");
});
parser.end_group();
auto _inpv = std::vector<char*>{};
auto _outv = std::vector<char*>{};
bool _hash = false;
for(int i = 0; i < argc; ++i)
{
if(_hash)
{
_outv.emplace_back(argv[i]);
}
else if(std::string_view{ argv[i] } == "--")
{
_hash = true;
}
else
{
_inpv.emplace_back(argv[i]);
}
}
auto _cerr = parser.parse_args(_inpv.size(), _inpv.data());
if(help_check(parser, argc, argv))
help_action(parser);
else if(_cerr)
throw std::runtime_error(_cerr.what());
if(_niterations < 1) _niterations = 1;
auto _get_size = [](const auto& _v) { return std::max<size_t>(_v.size(), 1); };
auto _causal_envs_tmp = std::vector<std::map<std::string_view, std::string>>{};
auto _fill = [&_causal_envs_tmp](std::string_view _env_var, const auto& _data,
bool _quote) {
if(_data.empty()) return;
if(_causal_envs_tmp.empty()) _causal_envs_tmp.emplace_back();
auto _tmp = _causal_envs_tmp;
_causal_envs_tmp.clear();
_causal_envs_tmp.reserve(_data.size() * _tmp.size());
for(auto ditr : _data)
{
if(_quote)
{
ditr.insert(0, "\"");
ditr += "\"";
}
// duplicate the env, add the env variable, emplace back
for(auto itr : _tmp)
{
itr[_env_var] = ditr;
_causal_envs_tmp.emplace_back(itr);
}
}
};
if(_add_defaults)
{
add_default_env(_env, "ROCPROFSYS_TIME_OUTPUT", false);
add_default_env(_env, "ROCPROFSYS_USE_PID", false);
add_default_env(_env, "ROCPROFSYS_USE_KOKKOSP", true);
#if defined(ROCPROFSYS_USE_OMPT) && ROCPROFSYS_USE_OMPT > 0
add_default_env(_env, "ROCPROFSYS_USE_OMPT", true);
#endif
#if(defined(ROCPROFSYS_USE_MPI) && ROCPROFSYS_USE_MPI > 0) || \
(defined(ROCPROFSYS_USE_MPI_HEADERS) && ROCPROFSYS_USE_MPI_HEADERS > 0)
add_default_env(_env, "ROCPROFSYS_USE_MPIP", true);
#endif
}
_fill("ROCPROFSYS_CAUSAL_BINARY_EXCLUDE", _binary_excludes, _generate_configs);
_fill("ROCPROFSYS_CAUSAL_SOURCE_EXCLUDE", _source_excludes, _generate_configs);
_fill("ROCPROFSYS_CAUSAL_FUNCTION_EXCLUDE", _function_excludes, _generate_configs);
_fill("ROCPROFSYS_CAUSAL_BINARY_SCOPE", _binary_scopes, _generate_configs);
_fill("ROCPROFSYS_CAUSAL_SOURCE_SCOPE", _source_scopes, _generate_configs);
_fill("ROCPROFSYS_CAUSAL_FUNCTION_SCOPE", _function_scopes, _generate_configs);
_fill("ROCPROFSYS_CAUSAL_FIXED_SPEEDUP", _virtual_speedups, false);
// make sure at least one env exists
if(_causal_envs_tmp.empty()) _causal_envs_tmp.emplace_back();
// duplicate for the number of iterations
_causal_envs.clear();
_causal_envs.reserve(_niterations * _causal_envs_tmp.size());
for(int64_t i = 0; i < _niterations; ++i)
{
for(const auto& itr : _causal_envs_tmp)
_causal_envs.emplace_back(itr);
}
if(_generate_configs)
{
auto _is_omni_cfg = [](std::string_view itr) {
return (itr.find("ROCPROFSYS") == 0 && itr.find("ROCPROFSYS_MODE") != 0 &&
itr.find("ROCPROFSYS_DEBUG_") != 0 && itr.find('=') < itr.length());
// rocprof-sys has miscellaneous env options starting with ROCPROFSYS_DEBUG_
// that are not official options
};
auto _omni_env_m = std::map<std::string, std::string>{};
for(auto* itr : _env)
{
if(_is_omni_cfg(itr))
{
auto _env_var = std::string{ itr };
auto _pos = _env_var.find('=');
auto _env_val = _env_var.substr(_pos + 1);
_env_var = _env_var.substr(0, _pos);
_omni_env_m.emplace(_env_var, _env_val);
}
}
_env.erase(std::remove_if(_env.begin(), _env.end(), _is_omni_cfg), _env.end());
auto _omni_env = std::vector<std::pair<std::string, std::string>>{};
// make sure that ROCPROFSYS_CONFIG_FILE is the first entry
{
auto citr = _omni_env_m.find("ROCPROFSYS_CONFIG_FILE");
if(citr != _omni_env_m.end())
{
_omni_env.emplace_back(citr->first, citr->second);
_omni_env_m.erase(citr);
}
}
for(const auto& itr : _omni_env_m)
_omni_env.emplace_back(itr.first, itr.second);
_causal_envs_tmp = std::move(_causal_envs);
_causal_envs.clear();
auto _write_config =
[_omni_env](std::ostream& _os,
const std::map<std::string_view, std::string>& _data) {
size_t _width = 0;
for(const auto& itr : _omni_env)
_width = std::max(_width, itr.first.length());
for(const auto& itr : _data)
_width = std::max(_width, itr.first.length());
_os << "# rocprofsys common settings\n";
for(const auto& itr : _omni_env)
_os << std::setw(_width + 1) << std::left << itr.first << " = "
<< itr.second << "\n";
_os << "\n# rocprofsys causal settings\n";
for(const auto& itr : _data)
_os << std::setw(_width + 1) << std::left << itr.first << " = "
<< itr.second << "\n";
};
int nwidth = (std::log10(_causal_envs_tmp.size()) + 1);
for(size_t i = 0; i < _causal_envs_tmp.size(); ++i)
{
std::stringstream fname{};
fname.fill('0');
fname << _config_folder << "/causal-" << std::setw(nwidth) << i << ".cfg";
std::ofstream _ofs{ fname.str() };
_write_config(_ofs, _causal_envs_tmp.at(i));
auto _cfg_name = (_config_file.empty())
? fname.str()
: join(array_config{ ":" }, _config_file, fname.str());
auto _cfg =
std::map<std::string_view, std::string>{ { "ROCPROFSYS_CONFIG_FILE",
_cfg_name } };
_causal_envs.emplace_back(_cfg);
}
}
if(_reset)
_causal_envs.front().emplace(std::string_view{ "ROCPROFSYS_CAUSAL_FILE_RESET" },
std::string{ "true" });
return _outv;
}
// explicit instantiation for usage in rocprof-sys-causal.cpp
template void
update_env(std::vector<char*>&, std::string_view, const std::string& _env_val,
bool _append, std::string_view);
@@ -0,0 +1,136 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "rocprof-sys-causal.hpp"
#include <timemory/log/macros.hpp>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string_view>
#include <unistd.h>
int
main(int argc, char** argv)
{
auto _base_env = get_initial_environment();
auto _causal_env = std::vector<std::map<std::string_view, std::string>>{};
bool _has_double_hyphen = false;
for(int i = 1; i < argc; ++i)
{
auto _arg = std::string_view{ argv[i] };
if(_arg == "--" || _arg == "-?" || _arg == "-h" || _arg == "--help" ||
_arg == "--version")
_has_double_hyphen = true;
}
std::vector<char*> _argv = {};
if(_has_double_hyphen)
{
_argv = parse_args(argc, argv, _base_env, _causal_env);
}
else
{
_argv.reserve(argc);
for(int i = 1; i < argc; ++i)
_argv.emplace_back(argv[i]);
_causal_env.resize(1);
}
prepare_command_for_run(argv[0], _argv);
prepare_environment_for_run(_base_env);
if(get_verbose() >= 3)
{
TIMEMORY_PRINTF_INFO(stderr, "causal environments to be executed:\n");
size_t _n = 0;
for(auto& citr : _causal_env)
{
auto _env = _base_env;
for(const auto& eitr : citr)
update_env(_env, eitr.first, eitr.second);
auto _prefix = std::to_string(_n++) + ": ";
print_updated_environment(_env, _prefix);
}
}
if(!_argv.empty())
{
if(_causal_env.size() == 1)
{
auto _env = _base_env;
for(const auto& eitr : _causal_env.front())
update_env(_env, eitr.first, eitr.second);
print_updated_environment(_env, "0: ");
print_command(_argv, "0: ");
_argv.emplace_back(nullptr);
_env.emplace_back(nullptr);
return execvpe(_argv.front(), _argv.data(), _env.data());
}
forward_signals({ SIGINT, SIGTERM, SIGQUIT });
size_t _ncount = 0;
size_t _width = std::log10(_causal_env.size()) + 1;
for(auto& citr : _causal_env)
{
auto _n = _ncount++;
auto _main_pid = getpid();
auto _pid = fork();
if(get_verbose() >= 3)
{
TIMEMORY_PRINTF_INFO(stderr, "process %i returned %i from fork...\n",
getpid(), _pid);
}
if(_pid == 0)
{
auto _prefix = std::stringstream{};
_prefix << std::setw(_width) << std::right << _n << "/"
<< std::setw(_width) << std::left << _causal_env.size() << ": ["
<< _main_pid << " -> " << getpid() << "] ";
auto _env = _base_env;
for(const auto& eitr : citr)
update_env(_env, eitr.first, eitr.second);
print_updated_environment(_env, _prefix.str());
print_command(_argv, _prefix.str());
_argv.emplace_back(nullptr);
_env.emplace_back(nullptr);
return execvpe(_argv.front(), _argv.data(), _env.data());
}
else
{
add_child_pid(_pid);
auto _status = wait_pid(_pid);
auto _ret = diagnose_status(_pid, _status);
remove_child_pid(_pid);
if(_ret != 0) return _ret;
}
}
}
}
@@ -0,0 +1,96 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#define TIMEMORY_PROJECT_NAME "rocprof-sys-causal"
#include <csignal>
#include <map>
#include <sched.h>
#include <set>
#include <string>
#include <string_view>
#include <vector>
int
get_verbose();
std::string
get_realpath(const std::string&);
void
print_command(const std::vector<char*>& _argv, std::string_view);
void print_updated_environment(std::vector<char*>, std::string_view);
std::vector<char*>
get_initial_environment();
void
prepare_command_for_run(char*, std::vector<char*>&);
void
prepare_environment_for_run(std::vector<char*>&);
std::string
get_internal_libpath(const std::string& _lib);
std::string
get_internal_script_path(void);
template <typename Tp>
void
update_env(std::vector<char*>&, std::string_view, Tp&&, bool _append = false,
std::string_view _join_delim = ":");
template <typename Tp>
void
add_default_env(std::vector<char*>&, std::string_view, Tp&&);
void
remove_env(std::vector<char*>&, std::string_view);
std::vector<char*>
parse_args(int argc, char** argv, std::vector<char*>&,
std::vector<std::map<std::string_view, std::string>>&);
using sigaction_t = struct sigaction;
struct signal_handler
{
sigaction_t m_custom_sigaction = {};
sigaction_t m_original_sigaction = {};
};
void
forward_signals(const std::set<int>&);
void add_child_pid(pid_t);
void remove_child_pid(pid_t);
int
wait_pid(pid_t, int = 0);
int
diagnose_status(pid_t, int);
@@ -0,0 +1,26 @@
# ------------------------------------------------------------------------------#
#
# TODO: [DFG] Remove this file after 'rocprofsys' rebranding is complete
# rocprofiler-systems-exe target (deprecated 'omnitrace' executable, now 'rocprofiler-systems-instrument')
#
# ------------------------------------------------------------------------------#
add_executable(rocprofiler-systems-exe ${CMAKE_CURRENT_LIST_DIR}/rocprof-sys.cpp)
target_link_libraries(
rocprofiler-systems-exe
PRIVATE rocprofiler-systems::rocprofiler-systems-threading
)
set_target_properties(
rocprofiler-systems-exe
PROPERTIES
OUTPUT_NAME rocprof-sys
BUILD_RPATH "\$ORIGIN:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH "${ROCPROFSYS_EXE_INSTALL_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
)
rocprofiler_systems_strip_target(rocprofiler-systems-exe)
install(TARGETS rocprofiler-systems-exe DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
@@ -0,0 +1,162 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <future>
#include <iomanip>
#include <iostream>
#include <map>
#include <pthread.h>
#include <regex>
#include <signal.h>
#include <sstream>
#include <string_view>
#include <thread>
#include <unistd.h>
int
main(int argc, char** argv)
{
static const char* _warning = R"warning(
WWWWWWWW WWWWWWWW iiii !!!
W::::::W W::::::W i::::i !!:!!
W::::::W W::::::W iiii !:::!
W::::::W W::::::W !:::!
W:::::W WWWWW W:::::Waaaaaaaaaaaaa rrrrr rrrrrrrrr nnnn nnnnnnnn iiiiiiinnnn nnnnnnnn ggggggggg ggggg!:::!
W:::::W W:::::W W:::::W a::::::::::::a r::::rrr:::::::::r n:::nn::::::::nn i:::::in:::nn::::::::nn g:::::::::ggg::::g!:::!
W:::::W W:::::::W W:::::W aaaaaaaaa:::::ar:::::::::::::::::r n::::::::::::::nn i::::in::::::::::::::nn g:::::::::::::::::g!:::!
W:::::W W:::::::::W W:::::W a::::arr::::::rrrrr::::::rnn:::::::::::::::n i::::inn:::::::::::::::ng::::::ggggg::::::gg!:::!
W:::::W W:::::W:::::W W:::::W aaaaaaa:::::a r:::::r r:::::r n:::::nnnn:::::n i::::i n:::::nnnn:::::ng:::::g g:::::g !:::!
W:::::W W:::::W W:::::W W:::::W aa::::::::::::a r:::::r rrrrrrr n::::n n::::n i::::i n::::n n::::ng:::::g g:::::g !:::!
W:::::W:::::W W:::::W:::::W a::::aaaa::::::a r:::::r n::::n n::::n i::::i n::::n n::::ng:::::g g:::::g !!:!!
W:::::::::W W:::::::::W a::::a a:::::a r:::::r n::::n n::::n i::::i n::::n n::::ng::::::g g:::::g !!!
W:::::::W W:::::::W a::::a a:::::a r:::::r n::::n n::::ni::::::i n::::n n::::ng:::::::ggggg:::::g
W:::::W W:::::W a:::::aaaa::::::a r:::::r n::::n n::::ni::::::i n::::n n::::n g::::::::::::::::g !!!
W:::W W:::W a::::::::::aa:::ar:::::r n::::n n::::ni::::::i n::::n n::::n gg::::::::::::::g !!:!!
WWW WWW aaaaaaaaaa aaaarrrrrrr nnnnnn nnnnnniiiiiiii nnnnnn nnnnnn gggggggg::::::g !!!
g:::::g
gggggg g:::::g
g:::::gg gg:::::g
g::::::ggg:::::::g
gg:::::::::::::g
ggg::::::ggg
gggggg
ROCm Systems Profiler has renamed the "rocprof-sys" executable to "rocprof-sys-instrument".
This executable only exists to provide this deprecation warning and maintain backwards compatibility for a few releases.
This executable will soon invoke "rocprof-sys-instrument" with the arguments you just provided after we've given you
a chance to read this message.
If you are running this job interactively, please acknowledge that you've read this message and whether you want to continue.
If you are running this job non-interactively, we will resume executing after ~1 minute unless CI or ROCPROFSYS_CI is defined
in the environment, in which case, we will throw an error.
Thanks for using ROCm Systems Profiler and happy optimizing!
)warning";
auto _completed = std::promise<void>{};
bool _acknowledged = false;
auto _emit_warning = []() {
// emit warning
std::cerr << _warning << std::endl;
};
auto _get_env = [](const char* _var) {
auto* _val = getenv(_var);
if(_val == nullptr) return false;
return !std::regex_match(
_val, std::regex{ "0|false|off|no", std::regex_constants::icase });
};
auto _env_failure = [_emit_warning, argv](std::string_view _env_var) {
// emit warning
_emit_warning();
std::cerr
<< "[" << argv[0] << "] Detected " << _env_var
<< " environment variable. Exiting to prevent consuming CI resources. "
"Use \"rocprof-sys-instrument\" executable instead of \"rocprof-sys\" "
"to prevent this error."
<< std::endl;
std::exit(EXIT_FAILURE);
};
auto _wait_for_input = [&_completed, &_acknowledged, _emit_warning]() {
// emit warning and wait for input
_emit_warning();
std::cerr << "Do you want to continue? [Y/n] " << std::flush;
auto _input = char{};
std::cin.get(_input);
_input = tolower(_input);
if(_input == 'n') std::exit(EXIT_SUCCESS);
_acknowledged = true;
_completed.set_value();
};
for(const auto* itr : { "CI", "ROCPROFSYS_CI" })
{
if(_get_env(itr)) _env_failure(itr);
}
{
auto _thr = std::thread{ _wait_for_input };
_completed.get_future().wait_for(std::chrono::seconds{ 60 });
if(!_acknowledged)
{
std::cerr << "[" << argv[0]
<< "] No acknowledgement after 1 minute. Continuing..."
<< std::endl;
_thr.detach();
}
else
_thr.join();
}
// generate the new command
auto _argv = std::vector<char*>{};
_argv.emplace_back(
strdup(std::string{ std::string{ argv[0] } + "-instrument" }.c_str()));
for(int i = 1; i < argc; ++i)
_argv.emplace_back(argv[i]);
// echo the new command for diagnostic purposes
auto _cmdss = std::stringstream{};
for(const auto& itr : _argv)
if(itr) _cmdss << " " << itr;
auto _cmd = _cmdss.str();
if(!_cmd.empty())
std::cerr << "[" << argv[0] << "] Executing: \"" << _cmd.substr(1) << "\"...\n"
<< std::endl;
// make sure command ends with nullptr
_argv.emplace_back(nullptr);
return execvp(_argv.front(), _argv.data());
}
@@ -0,0 +1,73 @@
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems-instrument target (formerly rocprofiler-systems-exe target prior to 1.8.1)
#
# ------------------------------------------------------------------------------#
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF)
add_executable(rocprofiler-systems-instrument)
target_sources(
rocprofiler-systems-instrument
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/details.cpp
${CMAKE_CURRENT_LIST_DIR}/function_signature.cpp
${CMAKE_CURRENT_LIST_DIR}/function_signature.hpp
${CMAKE_CURRENT_LIST_DIR}/fwd.hpp
${CMAKE_CURRENT_LIST_DIR}/info.hpp
${CMAKE_CURRENT_LIST_DIR}/internal_libs.cpp
${CMAKE_CURRENT_LIST_DIR}/internal_libs.hpp
${CMAKE_CURRENT_LIST_DIR}/log.cpp
${CMAKE_CURRENT_LIST_DIR}/log.hpp
${CMAKE_CURRENT_LIST_DIR}/module_function.cpp
${CMAKE_CURRENT_LIST_DIR}/module_function.hpp
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-instrument.cpp
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-instrument.hpp
)
target_link_libraries(
rocprofiler-systems-instrument
PRIVATE
rocprofiler-systems::rocprofiler-systems-headers
rocprofiler-systems::rocprofiler-systems-dyninst
rocprofiler-systems::rocprofiler-systems-compile-options
rocprofiler-systems::rocprofiler-systems-compile-definitions
rocprofiler-systems::rocprofiler-systems-sanitizer
timemory::timemory-headers
timemory::timemory-extensions
timemory::timemory-core
)
add_target_flag_if_avail(rocprofiler-systems-instrument "-Wno-deprecated-declarations")
set_target_properties(
rocprofiler-systems-instrument
PROPERTIES
BUILD_RPATH "\$ORIGIN:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH "${ROCPROFSYS_EXE_INSTALL_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
OUTPUT_NAME ${BINARY_NAME_PREFIX}-instrument
)
if(ROCPROFSYS_BUILD_DYNINST)
target_compile_definitions(
rocprofiler-systems-instrument
PRIVATE ROCPROFSYS_BUILD_DYNINST=1
)
endif()
add_target_flag_if_avail(rocprofiler-systems-instrument "-Wno-deprecated-declarations")
rocprofiler_systems_strip_target(rocprofiler-systems-instrument)
if(CMAKE_BUILD_TYPE MATCHES "^(DEBUG|Debug)")
string(REPLACE " " ";" _FLAGS "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
target_compile_options(rocprofiler-systems-instrument PRIVATE ${_FLAGS})
endif()
install(
TARGETS rocprofiler-systems-instrument
DESTINATION ${CMAKE_INSTALL_BINDIR}
OPTIONAL
)
@@ -0,0 +1,984 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "function_signature.hpp"
#include "fwd.hpp"
#include "log.hpp"
#include "rocprof-sys-instrument.hpp"
#include <timemory/components/rusage/components.hpp>
#include <timemory/components/timing/wall_clock.hpp>
#include <timemory/utility/join.hpp>
#include <algorithm>
#include <link.h>
#include <linux/limits.h>
#include <string>
#include <vector>
static int expect_error = NO_ERROR;
static int error_print = 0;
// set of whole function names to exclude
strset_t
get_whole_function_names()
{
return strset_t{
"sem_init", "sem_destroy", "sem_open", "sem_close", "sem_post", "sem_wait",
"sem_getvalue", "sem_clockwait", "sem_timedwait", "sem_trywait", "sem_unlink",
"fork", "do_futex_wait", "dl_iterate_phdr", "dlinfo", "dlopen", "dlmopen",
"dlvsym", "dlsym", "dlerror", "dladdr", "_dl_sym", "_dl_vsym", "_dl_addr",
"_dl_relocate_static_pie", "getenv", "setenv", "unsetenv", "printf", "fprintf",
"vprintf", "buffered_vfprintf", "vfprintf", "printf_positional", "puts", "fputs",
"vfputs", "fflush", "fwrite", "malloc", "malloc_stats", "malloc_trim", "mallopt",
"calloc", "free", "pvalloc", "valloc", "sysmalloc", "posix_memalign", "freehook",
"mallochook", "memalignhook", "mprobe", "reallochook", "mmap", "munmap", "fopen",
"fclose", "fmemopen", "fmemclose", "backtrace", "backtrace_symbols",
"backtrace_symbols_fd", "sigaddset", "sigandset", "sigdelset", "sigemptyset",
"sigfillset", "sighold", "sigisemptyset", "sigismember", "sigorset", "sigrelse",
"sigvec", "strtok", "strstr", "sbrk", "strxfrm", "atexit", "ompt_start_tool",
"nanosleep", "cfree", "tolower", "toupper", "fileno", "fileno_unlocked", "exit",
"quick_exit", "abort", "mbind", "migrate_pages", "move_pages",
"numa_migrate_pages", "numa_move_pages", "numa_alloc", "numa_alloc_local",
"numa_alloc_interleaved", "numa_alloc_onnode", "numa_realloc", "numa_free",
"round_and_return", "_init", "_fini", "_start", "__do_global_dtors_aux",
"__libc_csu_init", "__libc_csu_fini", "__hip_module_ctor", "__hip_module_dtor",
"__hipRegisterManagedVar", "__hipRegisterFunction", "__hipPushCallConfiguration",
"__hipPopCallConfiguration", "hipApiName", "enlarge_userbuf",
// below are functions which never terminate
"rocr::core::Signal::WaitAny", "rocr::core::Runtime::AsyncEventsLoop",
"rocr::core::BusyWaitSignal::WaitAcquire",
"rocr::core::BusyWaitSignal::WaitRelaxed", "rocr::HSA::hsa_signal_wait_scacquire",
"rocr::os::ThreadTrampoline", "rocr::image::ImageRuntime::CreateImageManager",
"rocr::AMD::GpuAgent::GetInfo", "rocr::HSA::hsa_agent_get_info",
"event_base_loop", "bootstrapRoot", "bootstrapNetAccept", "ncclCommInitRank",
"ncclCommInitAll", "ncclCommDestroy", "ncclCommCount", "ncclCommCuDevice",
"ncclCommUserRank", "ncclReduce", "ncclBcast", "ncclBroadcast", "ncclAllReduce",
"ncclReduceScatter", "ncclAllGather", "ncclGroupStart", "ncclGroupEnd",
"ncclSend", "ncclRecv", "ncclGather", "ncclScatter", "ncclAllToAll",
"ncclAllToAllv", "ncclSocketAccept", "vaBeginPicture", "vaCreateBuffer",
"vaCreateConfig", "vaCreateContext", "vaCreateSurfaces", "vaDestroySurfaces",
"vaSyncSurface", "vaDestroyBuffer", "vaDestroyConfig", "vaDestroyContext",
"vaEndPicture", "vaExportSurfaceHandle", "vaGetConfigAttributes", "vaInitialize",
"vaQueryConfigEntrypoints", "vaQuerySurfaceAttributes", "vaQuerySurfaceStatus",
"vaRenderPicture", "vaTerminate", "vaDisplayIsValid"
};
}
//======================================================================================//
//
// Helper functions because the syntax for getting a function or module name is unwieldy
//
std::string_view
get_name(procedure_t* _func)
{
static auto _v = std::unordered_map<procedure_t*, std::string>{};
auto itr = _v.find(_func);
if(itr == _v.end())
{
_v.emplace(_func, (_func) ? _func->getDemangledName() : std::string{});
}
return _v.at(_func);
}
std::string_view
get_name(module_t* _module)
{
static auto _v = std::unordered_map<module_t*, std::string>{};
auto itr = _v.find(_module);
if(itr == _v.end())
{
char _name[FUNCNAMELEN + 1];
memset(_name, '\0', FUNCNAMELEN + 1);
if(_module)
{
_module->getFullName(_name, FUNCNAMELEN);
_v.emplace(_module, std::string{ _name });
}
else
{
_v.emplace(nullptr, std::string{});
}
}
return _v.at(_module);
}
symtab_func_t*
get_symtab_function(procedure_t* _func)
{
static auto _v = std::unordered_map<procedure_t*, symtab_func_t*>{};
auto itr = _v.find(_func);
if(itr == _v.end())
{
auto _name = _func->getName();
{
auto nitr = symtab_data.mangled_symbol_names.find(_name);
if(nitr != symtab_data.mangled_symbol_names.end())
{
_v.emplace(_func, nitr->second->getFunction());
return _v.at(_func);
}
}
for(auto& fitr : symtab_data.symbols)
{
if(_name == fitr.first->getName())
{
_v.emplace(_func, fitr.first);
return _v.at(_func);
}
}
auto _dname = _func->getDemangledName();
{
auto nitr = symtab_data.typed_func_names.find(_dname);
if(nitr != symtab_data.typed_func_names.end())
{
_v.emplace(_func, nitr->second);
return _v.at(_func);
}
}
{
auto nitr = symtab_data.typed_symbol_names.find(_dname);
if(nitr != symtab_data.typed_symbol_names.end())
{
_v.emplace(_func, nitr->second->getFunction());
return _v.at(_func);
}
}
if(_v.find(_func) == _v.end()) _v.emplace(_func, nullptr);
}
return _v.at(_func);
}
namespace
{
std::string
get_return_type(procedure_t* func)
{
if(func && func->isInstrumentable() && func->getReturnType())
return func->getReturnType()->getName();
return std::string{};
}
auto
get_parameter_types(procedure_t* func)
{
auto _param_names = std::vector<std::string>{};
if(func && func->isInstrumentable())
{
auto* _params = func->getParams();
if(_params)
{
_param_names.reserve(_params->size());
for(auto* itr : *_params)
{
std::string _name = itr->getType()->getName();
if(_name.empty()) _name = itr->getName();
_param_names.emplace_back(_name);
}
}
}
return _param_names;
}
} // namespace
//======================================================================================//
//
// We create a new name that embeds the file and line information in the name
//
function_signature
get_func_file_line_info(module_t* module, procedure_t* func)
{
using address_t = Dyninst::Address;
ROCPROFSYS_ADD_LOG_ENTRY("Getting function line info for", get_name(func));
auto _file_name = get_name(module);
auto _func_name = get_name(func);
auto _return_type = get_return_type(func);
auto _param_types = get_parameter_types(func);
auto _base_addr = address_t{};
auto _last_addr = address_t{};
auto _src_lines = std::vector<statement_t>{};
if(func->getAddressRange(_base_addr, _last_addr) &&
module->getSourceLines(_base_addr, _src_lines) && !_src_lines.empty())
{
auto _row = _src_lines.front().lineNumber();
return function_signature(_return_type, _func_name, _file_name, _param_types,
{ _row, 0 }, { 0, 0 }, false, true, false);
}
else
{
return function_signature(_return_type, _func_name, _file_name, _param_types,
{ 0, 0 }, { 0, 0 }, false, false, false);
}
}
//======================================================================================//
//
// Gets information (line number, filename, and column number) about
// the instrumented loop and formats it properly.
//
function_signature
get_loop_file_line_info(module_t* module, procedure_t* func, flow_graph_t*,
basic_loop_t* loopToInstrument)
{
ROCPROFSYS_ADD_LOG_ENTRY("Getting loop line info for", get_name(func));
auto basic_blocks = std::vector<BPatch_basicBlock*>{};
loopToInstrument->getLoopBasicBlocksExclusive(basic_blocks);
if(basic_blocks.empty()) return function_signature{ "", "", "" };
auto* _block = basic_blocks.front();
auto _base_addr = _block->getStartAddress();
auto _last_addr = _block->getEndAddress();
for(const auto& itr : basic_blocks)
{
if(itr == _block) continue;
if(itr->dominates(_block))
{
_base_addr = itr->getStartAddress();
_last_addr = itr->getEndAddress();
_block = itr;
}
}
auto _file_name = get_name(module);
auto _func_name = get_name(func);
auto _return_type = get_return_type(func);
auto _param_types = get_parameter_types(func);
auto _lines_beg = std::vector<statement_t>{};
auto _lines_end = std::vector<statement_t>{};
if(module->getSourceLines(_base_addr, _lines_beg))
{
// filename = lines[0].fileName();
int _row1 = 0;
int _col1 = 0;
for(auto& itr : _lines_beg)
{
if(itr.lineNumber() > 0)
{
_row1 = itr.lineNumber();
_col1 = itr.lineOffset();
break;
}
}
if(_row1 == 0 && _col1 == 0)
return function_signature(_return_type, _func_name, _file_name, _param_types);
int _row2 = 0;
int _col2 = 0;
for(auto& itr : _lines_beg)
{
_row2 = std::max(_row2, itr.lineNumber());
_col2 = std::max(_col2, itr.lineOffset());
}
if(_col1 < 0) _col1 = 0;
if(module->getSourceLines(_last_addr, _lines_end))
{
for(auto& itr : _lines_end)
{
_row2 = std::max(_row2, itr.lineNumber());
_col2 = std::max(_col2, itr.lineOffset());
}
if(_col2 < 0) _col2 = 0;
if(_row2 < _row1) _row1 = _row2; // Fix for wrong line numbers
return function_signature(_return_type, _func_name, _file_name, _param_types,
{ _row1, _row2 }, { _col1, _col2 }, true, true,
true);
}
else
{
return function_signature(_return_type, _func_name, _file_name, _param_types,
{ _row1, 0 }, { _col1, 0 }, true, true, false);
}
}
else
{
return function_signature(_return_type, _func_name, _file_name, _param_types,
{ 0, 0 }, { 0, 0 }, true, false, false);
}
}
//======================================================================================//
//
// Gets information (line number, filename, and column number) about
// the instrumented loop and formats it properly.
//
std::map<basic_block_t*, basic_block_signature>
get_basic_block_file_line_info(module_t* module, procedure_t* func)
{
std::map<basic_block_t*, basic_block_signature> _data{};
if(!func) return _data;
ROCPROFSYS_ADD_LOG_ENTRY("Getting basic block line info for", get_name(func));
auto* _cfg = func->getCFG();
auto _basic_blocks = std::set<BPatch_basicBlock*>{};
_cfg->getAllBasicBlocks(_basic_blocks);
if(_basic_blocks.empty()) return _data;
auto _file_name = get_name(module);
auto _func_name = get_name(func);
auto _return_type = get_return_type(func);
auto _param_types = get_parameter_types(func);
for(auto&& itr : _basic_blocks)
{
auto _base_addr = itr->getStartAddress();
auto _last_addr = itr->getEndAddress();
verbprintf(4,
"[%s][%s] basic_block: size = %lu: base_addr = %lu, last_addr = %lu\n",
_file_name.data(), _func_name.data(),
(unsigned long) (_last_addr - _base_addr), _base_addr, _last_addr);
auto _lines_beg = std::vector<statement_t>{};
auto _lines_end = std::vector<statement_t>{};
if(module->getSourceLines(_base_addr, _lines_beg) && !_lines_beg.empty())
{
int _row1 = _lines_beg.front().lineNumber();
int _col1 = _lines_beg.front().lineOffset();
verbprintf(4, "size of _lines_end = %lu\n",
(unsigned long) _lines_end.size());
if(module->getSourceLines(_last_addr, _lines_end) && !_lines_end.empty())
{
int _row2 = _lines_end.back().lineNumber();
int _col2 = _lines_end.back().lineOffset();
if(_row2 < _row1) std::swap(_row1, _row2);
if(_row1 == _row2 && _col2 < _col1) std::swap(_col1, _col2);
_data.emplace(
itr, basic_block_signature{
_base_addr, _last_addr,
function_signature(_return_type, _func_name, _file_name,
_param_types, { _row1, _row2 },
{ _col1, _col2 }, true, true, true) });
}
else
{
_data.emplace(itr,
basic_block_signature{
_base_addr, _last_addr,
function_signature(_return_type, _func_name, _file_name,
_param_types, { _row1, 0 },
{ _col1, 0 }, true, true, false) });
}
}
else
{
_data.emplace(itr, basic_block_signature{
_base_addr, _last_addr,
function_signature(_return_type, _func_name,
_file_name, _param_types) });
}
}
return _data;
}
//======================================================================================//
//
// We create a new name that embeds the file and line information in the name
//
std::vector<statement_t>
get_source_code(module_t* module, procedure_t* func)
{
ROCPROFSYS_ADD_LOG_ENTRY("Getting source code for", get_name(func));
std::vector<statement_t> _lines{};
if(!module || !func) return _lines;
auto* _cfg = func->getCFG();
std::set<BPatch_basicBlock*> _basic_blocks{};
_cfg->getAllBasicBlocks(_basic_blocks);
for(auto&& itr : _basic_blocks)
{
auto _base_addr = itr->getStartAddress();
auto _last_addr = itr->getEndAddress();
for(decltype(_base_addr) _addr = _base_addr; _addr <= _last_addr; ++_addr)
{
std::vector<statement_t> _src{};
if(module->getSourceLines(_addr, _src))
{
for(auto&& iitr : _src)
_lines.emplace_back(iitr);
}
}
}
return _lines;
}
//======================================================================================//
//
// For compatibility purposes
//
procedure_t*
find_function(image_t* app_image, const std::string& _name, const strset_t& _extra)
{
if(_name.empty()) return nullptr;
auto _find = [app_image](const std::string& _f) -> procedure_t* {
// Extract the vector of functions
std::vector<procedure_t*> _found;
auto* ret = app_image->findFunction(_f.c_str(), _found, false, true, true);
if(ret == nullptr || _found.empty()) return nullptr;
return _found.at(0);
};
procedure_t* _func = _find(_name);
auto itr = _extra.begin();
while(_func == nullptr && itr != _extra.end())
{
_func = _find(*itr);
++itr;
}
if(!_func)
{
verbprintf(1, "function: '%s' ... not found\n", _name.c_str());
}
else
{
verbprintf(1, "function: '%s' ... found\n", _name.c_str());
}
return _func;
}
//======================================================================================//
//
// Get the realpath to this exe
//
bool
is_text_file(const std::string& filename)
{
std::ifstream _file{ filename, std::ios::in | std::ios::binary };
if(!_file.is_open())
{
errprintf(-1, "Error! '%s' could not be opened...\n", filename.c_str());
return false;
}
constexpr size_t buffer_size = 1024;
char buffer[buffer_size];
while(_file.read(buffer, sizeof(buffer)))
{
for(char itr : buffer)
{
if(itr == '\0') return false;
}
}
if(_file.gcount() > 0)
{
for(std::streamsize i = 0; i < _file.gcount(); ++i)
{
if(buffer[i] == '\0') return false;
}
}
return true;
}
//======================================================================================//
//
// Get the realpath to this exe
//
std::string&
rocprofsys_get_exe_realpath()
{
static std::string _v = []() {
auto _cmd_line = tim::read_command_line(tim::process::get_id());
if(!_cmd_line.empty())
{
using array_config_t = timemory::join::array_config;
ROCPROFSYS_ADD_DETAILED_LOG_ENTRY(array_config_t{ " ", "[ ", " ]" },
"cmdline:: ", _cmd_line);
return _cmd_line.front();
// return tim::filepath::realpath(_cmd_line.front(), nullptr, false);
}
return std::string{};
}();
return _v;
}
//======================================================================================//
//
// Error callback routine.
//
std::vector<std::string>
rocprofsys_get_link_map(const char* _lib, const std::string& _exclude_linked_by,
const std::string& _exclude_re, std::vector<int>&& _open_modes)
{
if(_open_modes.empty()) _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) };
auto _get_chain = [&_open_modes](const char* _name) {
void* _handle = nullptr;
bool _noload = false;
for(auto _mode : _open_modes)
{
_handle = dlopen(_name, _mode);
_noload = (_mode & RTLD_NOLOAD) == RTLD_NOLOAD;
if(_handle) break;
}
auto _chain = std::vector<std::string>{};
if(_handle)
{
struct link_map* _link_map = nullptr;
dlinfo(_handle, RTLD_DI_LINKMAP, &_link_map);
struct link_map* _next = _link_map;
while(_next)
{
if(_name == nullptr && _next == _link_map &&
std::string_view{ _next->l_name }.empty())
{
// only insert exe name if dlopened the exe and
// empty name is first entry
_chain.emplace_back(rocprofsys_get_exe_realpath());
}
else if(!std::string_view{ _next->l_name }.empty())
{
_chain.emplace_back(_next->l_name);
}
_next = _next->l_next;
}
if(_noload == false) dlclose(_handle);
}
return _chain;
};
auto _full_chain = _get_chain(_lib);
auto _excl_chain = (_exclude_linked_by.empty())
? std::vector<std::string>{}
: _get_chain(_exclude_linked_by.c_str());
auto _fini_chain = std::vector<std::string>{};
_fini_chain.reserve(_full_chain.size());
for(const auto& itr : _full_chain)
{
auto _found = std::any_of(_excl_chain.begin(), _excl_chain.end(),
[itr](const auto& _v) { return (itr == _v); });
if(!_found)
{
if(_exclude_re.empty() || !std::regex_search(itr, std::regex{ _exclude_re }))
_fini_chain.emplace_back(itr);
else
_excl_chain.emplace_back(itr);
}
}
return _fini_chain;
}
//======================================================================================//
//
// Get the path of a loaded dynamic binary
//
std::optional<std::string>
rocprofsys_get_loaded_path(const char* _name, std::vector<int>&& _open_modes)
{
if(_open_modes.empty()) _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) };
void* _handle = nullptr;
bool _noload = false;
for(auto _mode : _open_modes)
{
_handle = dlopen(_name, _mode);
_noload = (_mode & RTLD_NOLOAD) == RTLD_NOLOAD;
if(_handle) break;
}
if(_handle)
{
struct link_map* _link_map = nullptr;
dlinfo(_handle, RTLD_DI_LINKMAP, &_link_map);
if(_link_map != nullptr && !std::string_view{ _link_map->l_name }.empty())
{
return tim::filepath::realpath(_link_map->l_name, nullptr, false);
}
if(_noload == false) dlclose(_handle);
}
return std::optional<std::string>{};
}
//======================================================================================//
//
// Get the path of a loaded dynamic binary
//
std::optional<std::string>
rocprofsys_get_origin(const char* _name, std::vector<int>&& _open_modes)
{
if(_open_modes.empty()) _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) };
void* _handle = nullptr;
bool _noload = false;
for(auto _mode : _open_modes)
{
_handle = dlopen(_name, _mode);
_noload = (_mode & RTLD_NOLOAD) == RTLD_NOLOAD;
if(_handle) break;
}
if(_handle)
{
char _buffer[PATH_MAX + 1];
memset(_buffer, '\0', PATH_MAX * sizeof(char));
dlinfo(_handle, RTLD_DI_ORIGIN, _buffer);
if(strnlen(_buffer, PATH_MAX + 1) <= PATH_MAX)
{
return tim::filepath::realpath(_buffer, nullptr, false);
}
if(_noload == false) dlclose(_handle);
}
return std::optional<std::string>{};
}
//======================================================================================//
//
// Error callback routine.
//
void
errorFunc(error_level_t level, int num, const char** params)
{
error_func_real(level, num, params);
}
//======================================================================================//
//
void
error_func_real(error_level_t level, int num, const char* const* params)
{
char line[4096];
const char* msg = bpatch->getEnglishErrorString(num);
bpatch->formatErrorString(line, sizeof(line), msg, params);
ROCPROFSYS_ADD_LOG_ENTRY("Dyninst error function called with level", level,
":: ID# =", num, "::", line)
.force(level < BPatchInfo);
if(num == 0)
{
// conditional reporting of warnings and informational messages
if(error_print > 0)
{
if(level == BPatchInfo)
{
errprintf(2, "%s :: %i :: %s\n%s", std::to_string(level).c_str(), num,
line, tim::log::color::end());
}
else
{
verbprintf(0, "%s :: %i :: %s\n%s", std::to_string(level).c_str(), num,
line, tim::log::color::end());
}
}
}
else
{
// reporting of actual errors
if(num != expect_error)
{
verbprintf(-1, "%s :: %i :: %s\n%s", std::to_string(level).c_str(), num, line,
tim::log::color::end());
// We consider some errors fatal.
if(num == 101) throw std::runtime_error(msg);
}
}
}
//======================================================================================//
//
// Just log it
//
void
error_func_fake(error_level_t level, int num, const char* const* params)
{
char line[4096];
const char* msg = bpatch->getEnglishErrorString(num);
bpatch->formatErrorString(line, sizeof(line), msg, params);
// just log it
ROCPROFSYS_ADD_LOG_ENTRY("Dyninst error function called with level", level,
":: ID# =", num, "::", line)
.force(level < BPatchInfo);
}
#include "internal_libs.hpp"
#include <timemory/components/timing/wall_clock.hpp>
#include <timemory/utility/join.hpp>
using ::timemory::join::join;
//======================================================================================//
//
// Read the symtab data from Dyninst
//
void
process_modules(const std::vector<module_t*>& _app_modules)
{
parse_internal_libs_data();
auto _erase_nullptrs = [](auto& _vec) {
_vec.erase(std::remove_if(_vec.begin(), _vec.end(),
[](const auto* itr) { return (itr == nullptr); }),
_vec.end());
};
auto _wc = tim::component::wall_clock{};
auto _pr = tim::component::peak_rss{};
_wc.start();
_pr.start();
for(auto* itr : _app_modules)
{
auto* _module = SymTab::convert(itr);
if(_module) symtab_data.modules.emplace_back(_module);
}
_erase_nullptrs(symtab_data.modules);
verbprintf(0, "Processing %zu modules...\n", symtab_data.modules.size());
if(symtab_data.modules.empty()) return;
const auto& _data = get_internal_libs_data();
auto _names = std::set<std::string_view>{};
for(const auto& itr : _data)
{
if(!itr.first.empty())
{
_names.emplace(itr.first);
for(const auto& ditr : itr.second)
_names.emplace(ditr.first);
}
}
for(auto* itr : symtab_data.modules)
{
const auto* _base_name = tim::filepath::basename(itr->fullName());
auto _real_name = tim::filepath::realpath(itr->fullName(), nullptr, false);
if(!_base_name) continue;
if(_names.count(_base_name) == 0 && _names.count(_real_name) == 0)
{
verbprintf(2, "Processing symbol table for module '%s'...\n",
itr->fullName().c_str());
}
symtab_data.functions.emplace(itr, std::vector<symtab_func_t*>{});
if(itr->getAllFunctions().empty()) continue;
_erase_nullptrs(symtab_data.functions.at(itr));
for(auto* fitr : symtab_data.functions.at(itr))
{
symtab_data.typed_func_names[tim::demangle(fitr->getName())] = fitr;
symtab_data.symbols.emplace(fitr, std::vector<symtab_symbol_t*>{});
if(!fitr->getSymbols(symtab_data.symbols.at(fitr))) continue;
_erase_nullptrs(symtab_data.symbols.at(fitr));
for(auto* sitr : symtab_data.symbols.at(fitr))
{
symtab_data.mangled_symbol_names[sitr->getMangledName()] = sitr;
symtab_data.typed_symbol_names[sitr->getTypedName()] = sitr;
}
}
}
_pr.stop();
_wc.stop();
verbprintf(0, "Processing %zu modules... Done (%.3f %s, %.3f %s)\n",
_app_modules.size(), _wc.get(), _wc.display_unit().c_str(), _pr.get(),
_pr.display_unit().c_str());
}
//======================================================================================//
//
// I/O assistance
//
namespace std
{
std::string
to_string(instruction_category_t _category)
{
using namespace Dyninst::InstructionAPI;
switch(_category)
{
case c_CallInsn: return "function_call";
case c_ReturnInsn: return "return";
case c_BranchInsn: return "branch";
case c_CompareInsn: return "compare";
case c_PrefetchInsn: return "prefetch";
case c_SysEnterInsn: return "sys_enter";
case c_SyscallInsn: return "sys_call";
case c_VectorInsn: return "vector";
case c_GPUKernelExitInsn: return "gpu_kernel_exit";
case c_NoCategory: return "no_category";
}
return std::string{ "unknown_category_id_" } +
std::to_string(static_cast<int>(_category));
}
std::string
to_string(error_level_t _level)
{
switch(_level)
{
case BPatchFatal:
{
return JOIN("", tim::log::color::fatal(), "FatalError");
}
case BPatchSerious:
{
return JOIN("", tim::log::color::fatal(), "SeriousError");
}
case BPatchWarning:
{
return JOIN("", tim::log::color::warning(), "Warning");
}
case BPatchInfo:
{
return JOIN("", tim::log::color::info(), "Info");
}
default: break;
}
return JOIN("", tim::log::color::warning(), "UnknownErrorLevel",
static_cast<int>(_level));
}
namespace
{
std::string&&
to_lower(std::string&& _v)
{
for(auto& itr : std::move(_v))
itr = tolower(itr);
return std::move(_v);
}
} // namespace
std::string
to_string(symbol_visibility_t _v)
{
return to_lower(SymTab::Symbol::symbolVisibility2Str(_v) + 3);
}
std::string
to_string(symbol_linkage_t _v)
{
return to_lower(SymTab::Symbol::symbolLinkage2Str(_v) + 3);
}
} // namespace std
template <typename Tp>
Tp
from_string(std::string_view _v)
{
if constexpr(std::is_same<Tp, symbol_visibility_t>::value)
{
for(const auto& itr :
{ SV_UNKNOWN, SV_DEFAULT, SV_INTERNAL, SV_HIDDEN, SV_PROTECTED })
if(_v == std::to_string(itr)) return itr;
return SV_UNKNOWN;
}
else if constexpr(std::is_same<Tp, symbol_linkage_t>::value)
{
for(const auto& itr : { SL_UNKNOWN, SL_GLOBAL, SL_LOCAL, SL_WEAK, SL_UNIQUE })
if(_v == std::to_string(itr)) return itr;
return SL_UNKNOWN;
}
else
{
static_assert(std::is_empty<Tp>::value, "Error! not defined");
return Tp{};
}
}
template symbol_visibility_t
from_string<symbol_visibility_t>(std::string_view _v);
template symbol_linkage_t
from_string<symbol_linkage_t>(std::string_view _v);
std::ostream&
operator<<(std::ostream& _os, symbol_linkage_t _v)
{
return (_os << std::to_string(_v));
}
std::ostream&
operator<<(std::ostream& _os, symbol_visibility_t _v)
{
return (_os << std::to_string(_v));
}
std::istream&
operator>>(std::istream& _is, symbol_linkage_t& _v)
{
auto _v_s = std::string{};
_is >> _v_s;
_v = from_string<symbol_linkage_t>(_v_s);
return _is;
}
std::istream&
operator>>(std::istream& _is, symbol_visibility_t& _v)
{
auto _v_s = std::string{};
_is >> _v_s;
_v = from_string<symbol_visibility_t>(_v_s);
return _is;
}
@@ -0,0 +1,147 @@
// MIT License
//
// Copyright (c) 2022 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 "function_signature.hpp"
function_signature::function_signature(std::string_view _ret, std::string_view _name,
std::string_view _file, location_t _row,
location_t _col, bool _loop, bool _info_beg,
bool _info_end)
: m_loop(_loop)
, m_info_beg(_info_beg)
, m_info_end(_info_end)
, m_row(std::move(_row))
, m_col(std::move(_col))
, m_return(_ret)
, m_name(tim::demangle(_name.data()))
, m_file(_file)
{
if(m_file.find('/') != std::string_view::npos)
m_file = m_file.substr(m_file.find_last_of('/') + 1);
}
function_signature::function_signature(std::string_view _ret, std::string_view _name,
std::string_view _file,
const std::vector<std::string>& _params,
location_t _row, location_t _col, bool _loop,
bool _info_beg, bool _info_end)
: function_signature(_ret, _name, _file, _row, _col, _loop, _info_beg, _info_end)
{
m_params = "(";
for(const auto& itr : _params)
m_params.append(itr + ", ");
if(!_params.empty()) m_params = m_params.substr(0, m_params.length() - 2);
m_params += ")";
}
std::string
function_signature::get(function_signature& sig)
{
return sig.get();
}
std::string
function_signature::get(bool _all, bool _save) const
{
if(!_all && _save && !m_signature.empty()) return m_signature;
std::stringstream ss;
if((_all || use_return_info) && !m_return.empty()) ss << m_return << " ";
ss << m_name;
if(_all || use_args_info) ss << m_params;
if(m_loop)
{
auto _row_col_str = [](unsigned long _row, unsigned long _col) {
std::stringstream _ss{};
if(_row == 0 && _col == 0) return std::string{};
if(_col > 0)
_ss << "{" << _row << "," << _col << "}";
else
_ss << "{" << _row << "}";
return _ss.str();
};
auto _rc1 = _row_col_str(m_row.first, m_col.first);
auto _rc2 = _row_col_str(m_row.second, m_col.second);
if(m_info_end && !_rc1.empty() && !_rc2.empty() && _rc1 != _rc2)
ss << " [" << _rc1 << "-" << _rc2 << "]";
else if(m_info_end && !_rc1.empty() && !_rc2.empty() && _rc1 == _rc2)
ss << " [" << _rc1 << "]";
else if(m_info_end && !_rc1.empty() && _rc2.empty())
ss << " [" << _rc1 << "]";
else if(!m_info_end && !_rc1.empty())
ss << " [" << _rc1 << "]";
else if(m_loop_num < std::numeric_limits<uint32_t>::max())
ss << " [loop#" << m_loop_num << "]";
else
errprintf(3, "line info for %s is empty! [{%s}] [{%s}]\n", m_name.c_str(),
_rc1.c_str(), _rc2.c_str());
}
if((_all || use_file_info) && m_file.length() > 0) ss << " [" << m_file;
if((_all || use_line_info) && m_row.first > 0) ss << ":" << m_row.first;
if((_all || use_file_info) && m_file.length() > 0) ss << "]";
if(_save) m_signature = ss.str();
return ss.str();
}
std::string
function_signature::get_coverage(bool _basic_block) const
{
std::stringstream ss;
if(!m_return.empty()) ss << m_return << " ";
ss << m_name << m_params;
if(_basic_block && m_loop && m_info_beg)
{
if(m_file.length() > 0) ss << " [" << m_file << "]";
auto _row_col_str = [](unsigned long _row, unsigned long _col) {
std::stringstream _ss{};
if(_row == 0 && _col == 0) return std::string{};
if(_col > 0)
_ss << "{" << _row << "," << _col << "}";
else
_ss << "{" << _row << "}";
return _ss.str();
};
auto _rc1 = _row_col_str(m_row.first, m_col.first);
auto _rc2 = _row_col_str(m_row.second, m_col.second);
if(m_info_end && !_rc1.empty() && !_rc2.empty() && _rc1 != _rc2)
ss << " [" << _rc1 << "-" << _rc2 << "]";
else if(m_info_end && !_rc1.empty() && !_rc2.empty() && _rc1 == _rc2)
ss << " [" << _rc1 << "]";
else if(m_info_end && !_rc1.empty() && _rc2.empty())
ss << " [" << _rc1 << "]";
else if(!m_info_end && !_rc1.empty())
ss << " [" << _rc1 << "]";
else
errprintf(3, "line info for %s is empty!\n", m_name.c_str());
}
else
{
if(m_file.length() > 0) ss << " [" << m_file;
if(m_row.first > 0) ss << ":" << m_row.first;
if(m_file.length() > 0) ss << "]";
}
return ss.str();
}
@@ -0,0 +1,107 @@
// MIT License
//
// Copyright (c) 2022 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 "fwd.hpp"
#include <tuple>
struct function_signature
{
using location_t = std::pair<unsigned long, unsigned long>;
TIMEMORY_DEFAULT_OBJECT(function_signature)
function_signature(std::string_view _ret, std::string_view _name,
std::string_view _file, location_t _row = { 0, 0 },
location_t _col = { 0, 0 }, bool _loop = false,
bool _info_beg = false, bool _info_end = false);
function_signature(std::string_view _ret, std::string_view _name,
std::string_view _file, const std::vector<std::string>& _params,
location_t _row = { 0, 0 }, location_t _col = { 0, 0 },
bool _loop = false, bool _info_beg = false,
bool _info_end = false);
function_signature& set_loop_number(uint32_t _n)
{
m_loop_num = _n;
return *this;
}
static std::string get(function_signature& sig);
std::string get(bool _all = false, bool _save = true) const;
std::string get_coverage(bool _is_basic_block) const;
bool m_loop = false;
bool m_info_beg = false;
bool m_info_end = false;
uint32_t m_loop_num = std::numeric_limits<uint32_t>::max();
location_t m_row = { 0, 0 };
location_t m_col = { 0, 0 };
std::string m_return = {};
std::string m_name = {};
std::string m_params = "()";
std::string m_file = {};
mutable std::string m_signature = {};
friend bool operator==(const function_signature& lhs, const function_signature& rhs)
{
return lhs.get() == rhs.get();
}
friend bool operator<(const function_signature& lhs, const function_signature& rhs)
{
const auto loop_max = std::numeric_limits<uint32_t>::max();
if(lhs.m_loop && !rhs.m_loop) return false;
if(!lhs.m_loop && rhs.m_loop) return true;
if(lhs.m_loop_num < loop_max && rhs.m_loop_num == loop_max) return false;
if(lhs.m_loop_num == loop_max && rhs.m_loop_num < loop_max) return true;
return std::tie(lhs.m_file, lhs.m_name, lhs.m_return, lhs.m_params,
lhs.m_row.first, lhs.m_col.first, lhs.m_loop_num) <
std::tie(rhs.m_file, rhs.m_name, rhs.m_return, rhs.m_params,
rhs.m_row.first, rhs.m_col.first, rhs.m_loop_num);
}
template <typename ArchiveT>
void serialize(ArchiveT& _ar, const unsigned)
{
namespace cereal = tim::cereal;
(void) get();
_ar(cereal::make_nvp("loop", m_loop), cereal::make_nvp("info_beg", m_info_beg),
cereal::make_nvp("info_end", m_info_end), cereal::make_nvp("row", m_row),
cereal::make_nvp("col", m_col), cereal::make_nvp("return", m_return),
cereal::make_nvp("name", m_name), cereal::make_nvp("params", m_params),
cereal::make_nvp("file", m_file), cereal::make_nvp("signature", m_signature));
(void) get();
}
};
struct basic_block_signature
{
using address_t = Dyninst::Address;
address_t start_address = {};
address_t last_address = {};
function_signature signature = {};
};
@@ -0,0 +1,399 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include "log.hpp"
#include <timemory/backends/process.hpp>
#include <timemory/environment.hpp>
#include <timemory/mpl/apply.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/policy.hpp>
#include <timemory/tpls/cereal/archives.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/utility/argparse.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/popen.hpp>
#include <timemory/variadic/macros.hpp>
#include <BPatch.h>
#include <BPatch_Vector.h>
#include <BPatch_addressSpace.h>
#include <BPatch_basicBlock.h>
#include <BPatch_basicBlockLoop.h>
#include <BPatch_callbacks.h>
#include <BPatch_function.h>
#include <BPatch_instruction.h>
#include <BPatch_object.h>
#include <BPatch_point.h>
#include <BPatch_process.h>
#include <BPatch_snippet.h>
#include <BPatch_statement.h>
#include <Function.h>
#include <Instruction.h>
#include <InstructionCategories.h>
#include <Module.h>
#include <Symtab.h>
#include <SymtabReader.h>
#include <dyntypes.h>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <exception>
#include <fstream>
#include <istream>
#include <limits>
#include <memory>
#include <numeric>
#include <ostream>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#define MUTNAMELEN 1024
#define FUNCNAMELEN 32 * 1024
#define NO_ERROR -1
#define TIMEMORY_BIN_DIR "bin"
#if !defined(PATH_MAX)
# define PATH_MAX std::numeric_limits<int>::max();
#endif
struct function_signature;
struct basic_block_signature;
struct module_function;
using string_t = std::string;
using string_view_t = std::string_view;
using stringstream_t = std::stringstream;
using strvec_t = std::vector<string_t>;
using strset_t = std::set<string_t>;
using regexvec_t = std::vector<std::regex>;
using fmodset_t = std::set<module_function>;
using fixed_modset_t = std::map<fmodset_t*, bool>;
using exec_callback_t = BPatchExecCallback;
using exit_callback_t = BPatchExitCallback;
using fork_callback_t = BPatchForkCallback;
using patch_t = BPatch;
using process_t = BPatch_process;
using thread_t = BPatch_thread;
using binary_edit_t = BPatch_binaryEdit;
using image_t = BPatch_image;
using module_t = BPatch_module;
using procedure_t = BPatch_function;
using snippet_t = BPatch_snippet;
using call_expr_t = BPatch_funcCallExpr;
using address_space_t = BPatch_addressSpace;
using flow_graph_t = BPatch_flowGraph;
using statement_t = BPatch_statement;
using basic_block_t = BPatch_basicBlock;
using basic_loop_t = BPatch_basicBlockLoop;
using procedure_loc_t = BPatch_procedureLocation;
using point_t = BPatch_point;
using object_t = BPatch_object;
using local_var_t = BPatch_localVar;
using sequence_t = BPatch_sequence;
using const_expr_t = BPatch_constExpr;
using error_level_t = BPatchErrorLevel;
using snippet_handle_t = BPatchSnippetHandle;
using patch_pointer_t = std::shared_ptr<patch_t>;
using snippet_pointer_t = std::shared_ptr<snippet_t>;
using call_expr_pointer_t = std::shared_ptr<call_expr_t>;
using snippet_vec_t = std::vector<snippet_t*>;
using procedure_vec_t = std::vector<procedure_t*>;
using basic_block_set_t = std::set<basic_block_t*>;
using basic_loop_vec_t = std::vector<basic_loop_t*>;
using snippet_pointer_vec_t = std::vector<snippet_pointer_t>;
using instruction_t = Dyninst::InstructionAPI::Instruction;
using instruction_category_t = Dyninst::InstructionAPI::InsnCategory;
namespace SymTab = ::Dyninst::SymtabAPI;
using symtab_t = SymTab::Symtab;
using symtab_module_t = SymTab::Module;
using symtab_symbol_t = SymTab::Symbol;
using symtab_func_t = SymTab::Function;
using symbol_linkage_t = SymTab::Symbol::SymbolLinkage;
using symbol_visibility_t = SymTab::Symbol::SymbolVisibility;
constexpr auto SL_UNKNOWN = symtab_symbol_t::SL_UNKNOWN;
constexpr auto SL_GLOBAL = symtab_symbol_t::SL_GLOBAL;
constexpr auto SL_LOCAL = symtab_symbol_t::SL_LOCAL;
constexpr auto SL_WEAK = symtab_symbol_t::SL_WEAK;
constexpr auto SL_UNIQUE = symtab_symbol_t::SL_UNIQUE;
constexpr auto SV_UNKNOWN = symtab_symbol_t::SV_UNKNOWN;
constexpr auto SV_DEFAULT = symtab_symbol_t::SV_DEFAULT;
constexpr auto SV_INTERNAL = symtab_symbol_t::SV_INTERNAL;
constexpr auto SV_HIDDEN = symtab_symbol_t::SV_HIDDEN;
constexpr auto SV_PROTECTED = symtab_symbol_t::SV_PROTECTED;
constexpr auto SL_END_V =
std::max({ SL_UNKNOWN, SL_GLOBAL, SL_LOCAL, SL_WEAK, SL_UNIQUE }) + 1;
constexpr auto SV_END_V =
std::max({ SV_UNKNOWN, SV_DEFAULT, SV_INTERNAL, SV_HIDDEN, SV_PROTECTED }) + 1;
void
rocprofsys_prefork_callback(thread_t* parent, thread_t* child);
enum CodeCoverageMode
{
CODECOV_NONE = 0,
CODECOV_FUNCTION,
CODECOV_BASIC_BLOCK
};
//======================================================================================//
//
// Global Variables
//
//======================================================================================//
//
// label settings
//
extern bool use_return_info;
extern bool use_args_info;
extern bool use_file_info;
extern bool use_line_info;
//
// heuristic settings
//
extern bool allow_overlapping;
extern bool loop_level_instr;
extern bool instr_dynamic_callsites;
extern bool instr_traps;
extern bool instr_loop_traps;
extern bool parse_all_modules;
extern size_t min_address_range;
extern size_t min_loop_address_range;
extern size_t min_instructions;
extern size_t min_loop_instructions;
//
// debug settings
//
extern bool werror;
extern bool debug_print;
extern bool instr_print;
extern int verbose_level;
extern int num_log_entries;
//
// instrumentation settings
//
extern bool simulate;
extern bool include_uninstr;
extern bool include_internal_linked_libs;
//
// string settings
//
extern string_t main_fname;
extern string_t argv0;
extern string_t cmdv0;
extern string_t default_components;
extern string_t prefer_library;
//
// global variables
//
extern patch_pointer_t bpatch;
extern call_expr_t* terminate_expr;
extern snippet_vec_t init_names;
extern snippet_vec_t fini_names;
extern fmodset_t available_module_functions;
extern fmodset_t instrumented_module_functions;
extern fmodset_t overlapping_module_functions;
extern fmodset_t excluded_module_functions;
extern fixed_modset_t fixed_module_functions;
extern regexvec_t func_include;
extern regexvec_t func_exclude;
extern regexvec_t file_include;
extern regexvec_t file_exclude;
extern regexvec_t file_restrict;
extern regexvec_t func_restrict;
extern regexvec_t caller_include;
extern regexvec_t func_internal_include;
extern regexvec_t file_internal_include;
extern regexvec_t instruction_exclude;
extern CodeCoverageMode coverage_mode;
//
// symtab variables
//
struct symtab_data_s
{
std::vector<symtab_module_t*> modules = {};
std::map<symtab_module_t*, std::vector<symtab_func_t*>> functions = {};
std::map<symtab_func_t*, std::vector<symtab_symbol_t*>> symbols = {};
std::unordered_map<std::string, symtab_symbol_t*> mangled_symbol_names = {};
std::unordered_map<std::string, symtab_func_t*> typed_func_names = {};
std::unordered_map<std::string, symtab_symbol_t*> typed_symbol_names = {};
};
extern symtab_data_s symtab_data;
extern std::set<symbol_linkage_t> enabled_linkage;
extern std::set<symbol_visibility_t> enabled_visibility;
// logging
extern std::unique_ptr<std::ofstream> log_ofs;
//
//======================================================================================//
// control debug printf statements
#define errprintf(LEVEL, ...) \
{ \
char _logmsgbuff[FUNCNAMELEN]; \
snprintf(_logmsgbuff, FUNCNAMELEN, __VA_ARGS__); \
ROCPROFSYS_ADD_LOG_ENTRY(_logmsgbuff); \
if(werror || LEVEL < 0) \
{ \
if(debug_print || verbose_level >= LEVEL) \
fprintf(stderr, "[rocprof-sys][exe] Error! " __VA_ARGS__); \
char _buff[FUNCNAMELEN]; \
sprintf(_buff, "[rocprof-sys][exe] Error! " __VA_ARGS__); \
throw std::runtime_error(std::string{ _buff }); \
} \
else \
{ \
if(debug_print || verbose_level >= LEVEL) \
fprintf(stderr, "[rocprof-sys][exe] Warning! " __VA_ARGS__); \
} \
fflush(stderr); \
}
// control verbose printf statements
#define verbprintf(LEVEL, ...) \
{ \
char _logmsgbuff[FUNCNAMELEN]; \
snprintf(_logmsgbuff, FUNCNAMELEN, __VA_ARGS__); \
ROCPROFSYS_ADD_LOG_ENTRY(_logmsgbuff); \
if(debug_print || verbose_level >= LEVEL) \
fprintf(stdout, "[rocprof-sys][exe] " __VA_ARGS__); \
fflush(stdout); \
}
#define verbprintf_bare(LEVEL, ...) \
{ \
char _logmsgbuff[FUNCNAMELEN]; \
snprintf(_logmsgbuff, FUNCNAMELEN, __VA_ARGS__); \
ROCPROFSYS_ADD_LOG_ENTRY(_logmsgbuff); \
if(debug_print || verbose_level >= LEVEL) fprintf(stdout, __VA_ARGS__); \
fflush(stdout); \
}
//======================================================================================//
template <typename... T>
void
consume_parameters(T&&...)
{}
//======================================================================================//
void
process_modules(const std::vector<module_t*>&);
strset_t
get_whole_function_names();
function_signature
get_func_file_line_info(module_t* mutatee_module, procedure_t* f);
function_signature
get_loop_file_line_info(module_t* mutatee_module, procedure_t* f, flow_graph_t* cfGraph,
basic_loop_t* loopToInstrument);
std::map<basic_block_t*, basic_block_signature>
get_basic_block_file_line_info(module_t* module, procedure_t* func);
std::vector<statement_t>
get_source_code(module_t* module, procedure_t* func);
std::tuple<size_t, size_t>
query_instr(procedure_t* funcToInstr, procedure_loc_t traceLoc,
flow_graph_t* cfGraph = nullptr, basic_loop_t* loopToInstrument = nullptr);
bool
query_instr(procedure_t* funcToInstr, procedure_loc_t traceLoc, flow_graph_t* cfGraph,
basic_loop_t* loopToInstrument, bool allow_traps);
template <typename Tp>
bool
insert_instr(address_space_t* mutatee, const std::vector<point_t*>& _points, Tp traceFunc,
procedure_loc_t traceLoc, bool allow_traps = instr_traps);
template <typename Tp>
bool
insert_instr(address_space_t* mutatee, procedure_t* funcToInstr, Tp traceFunc,
procedure_loc_t traceLoc, flow_graph_t* cfGraph = nullptr,
basic_loop_t* loopToInstrument = nullptr, bool allow_traps = instr_traps);
template <typename Tp>
bool
insert_instr(address_space_t* mutatee, Tp traceFunc, procedure_loc_t traceLoc,
basic_block_t* basicBlock, bool allow_traps = instr_traps);
procedure_t*
find_function(image_t* appImage, const string_t& functionName, const strset_t& = {});
void
error_func_real(error_level_t level, int num, const char* const* params);
void
error_func_fake(error_level_t level, int num, const char* const* params);
std::string_view
get_name(procedure_t*);
std::string_view
get_name(module_t*);
symtab_func_t*
get_symtab_function(procedure_t*);
namespace std
{
std::string to_string(instruction_category_t);
std::string to_string(error_level_t);
std::string to_string(symbol_visibility_t);
std::string to_string(symbol_linkage_t);
} // namespace std
template <typename Tp>
Tp from_string(std::string_view);
std::ostream&
operator<<(std::ostream&, symbol_linkage_t);
std::ostream&
operator<<(std::ostream&, symbol_visibility_t);
std::istream&
operator>>(std::istream&, symbol_linkage_t&);
std::istream&
operator>>(std::istream&, symbol_visibility_t&);
@@ -0,0 +1,264 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include "fwd.hpp"
#include "module_function.hpp"
#include <timemory/log/color.hpp>
#include <timemory/mpl/policy.hpp>
#include <timemory/settings.hpp>
#include <timemory/settings/types.hpp>
#include <timemory/tpls/cereal/cereal.hpp>
#include <timemory/utility/delimit.hpp>
#include <timemory/utility/filepath.hpp>
static inline void
dump_info(std::ostream& _os, const fmodset_t& _data)
{
module_function::reset_width();
for(const auto& itr : _data)
module_function::update_width(itr);
module_function::write_header(_os);
for(const auto& itr : _data)
_os << itr << '\n';
module_function::reset_width();
}
//
template <typename ArchiveT,
std::enable_if_t<tim::concepts::is_archive<ArchiveT>::value, int> = 0>
static inline void
dump_info(ArchiveT& _ar, const fmodset_t& _data)
{
_ar(tim::cereal::make_nvp("module_functions", _data));
}
//
static inline void
dump_info(const string_t& _label, string_t _oname, const string_t& _ext,
const fmodset_t& _data, int _level, bool _fail)
{
namespace cereal = tim::cereal;
namespace policy = tim::policy;
auto _cfg = tim::settings::compose_filename_config{};
_cfg.subdirectory = "instrumentation";
_oname = tim::settings::compose_output_filename(_oname, _ext, _cfg);
auto _handle_error = [&]() {
std::stringstream _msg{};
_msg << "[dump_info] Error opening '" << _oname << " for output";
verbprintf(_level, "%s\n", _msg.str().c_str());
if(_fail)
throw std::runtime_error(std::string{ "[rocprof-sys][exe]" } + _msg.str());
};
if(!debug_print && verbose_level < _level) return;
if(_ext == "txt")
{
std::ofstream ofs{};
if(!tim::filepath::open(ofs, _oname))
_handle_error();
else
{
verbprintf_bare(_level, "%s", ::tim::log::color::source());
verbprintf(_level, "Outputting '%s'... ", _oname.c_str());
dump_info(ofs, _data);
verbprintf_bare(_level, "Done\n%s", ::tim::log::color::end());
}
ofs.close();
}
else if(_ext == "xml")
{
std::stringstream oss{};
{
using output_policy = policy::output_archive<cereal::XMLOutputArchive>;
output_policy::indent() = true;
auto ar = output_policy::get(oss);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName(_label.c_str());
ar->startNode();
(*ar)(cereal::make_nvp("module_functions", _data));
ar->finishNode();
ar->finishNode();
}
std::ofstream ofs{};
if(!tim::filepath::open(ofs, _oname))
_handle_error();
else
{
verbprintf_bare(_level, "%s", ::tim::log::color::source());
verbprintf(_level, "Outputting '%s'... ", _oname.c_str());
ofs << oss.str() << std::endl;
verbprintf_bare(_level, "Done\n%s", ::tim::log::color::end());
}
ofs.close();
}
else if(_ext == "json")
{
std::stringstream oss{};
{
using output_policy = policy::output_archive<cereal::PrettyJSONOutputArchive>;
auto ar = output_policy::get(oss);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName(_label.c_str());
ar->startNode();
(*ar)(cereal::make_nvp("module_functions", _data));
ar->finishNode();
ar->finishNode();
}
std::ofstream ofs{};
if(!tim::filepath::open(ofs, _oname))
_handle_error();
else
{
verbprintf_bare(_level, "%s", ::tim::log::color::source());
verbprintf(_level, "Outputting '%s'... ", _oname.c_str());
ofs << oss.str() << std::endl;
verbprintf_bare(_level, "Done\n%s", ::tim::log::color::end());
}
ofs.close();
}
else
{
throw std::runtime_error(TIMEMORY_JOIN(
"", "[rocprof-sys][exe] Error in ", __FUNCTION__, " :: filename '", _oname,
"' does not have one of recognized file extensions: txt, json, xml"));
}
}
//
static inline void
dump_info(const string_t& _oname, const fmodset_t& _data, int _level, bool _fail,
const string_t& _type, const strset_t& _ext)
{
for(const auto& itr : _ext)
dump_info(_type, _oname, itr, _data, _level, _fail);
}
//
static inline void
load_info(const string_t& _label, const string_t& _iname, fmodset_t& _data, int _level)
{
namespace cereal = tim::cereal;
namespace policy = tim::policy;
auto _pos = _iname.find_last_of('.');
std::string _ext = {};
if(_pos != std::string::npos) _ext = _iname.substr(_pos + 1);
auto _handle_error = [&]() {
std::stringstream _msg{};
_msg << "[load_info] Error opening '" << _iname << " for input";
verbprintf(_level, "%s\n", _msg.str().c_str());
throw std::runtime_error(std::string{ "[rocprof-sys][exe]" } + _msg.str());
};
if(_ext == "xml")
{
verbprintf(_level, "Reading '%s'... ", _iname.c_str());
std::ifstream ifs{ _iname };
if(!ifs)
_handle_error();
else
{
using input_policy = policy::input_archive<cereal::XMLInputArchive>;
auto ar = input_policy::get(ifs);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName(_label.c_str());
ar->startNode();
(*ar)(cereal::make_nvp("module_functions", _data));
ar->finishNode();
ar->finishNode();
}
verbprintf_bare(_level, "Done\n");
ifs.close();
}
else if(_ext == "json")
{
verbprintf(_level, "Reading '%s'... ", _iname.c_str());
std::ifstream ifs{ _iname };
if(!ifs)
_handle_error();
else
{
using input_policy = policy::input_archive<cereal::JSONInputArchive>;
auto ar = input_policy::get(ifs);
ar->setNextName("rocprofsys");
ar->startNode();
ar->setNextName(_label.c_str());
ar->startNode();
(*ar)(cereal::make_nvp("module_functions", _data));
ar->finishNode();
ar->finishNode();
}
verbprintf_bare(_level, "Done\n");
ifs.close();
}
else
{
throw std::runtime_error(TIMEMORY_JOIN(
"", "[rocprof-sys][exe] Error in ", __FUNCTION__, " :: filename '", _iname,
"' does not have one of recognized extentions: txt, json, xml :: ", _ext));
}
}
//
static inline void
load_info(const string_t& _inp, std::map<std::string, fmodset_t*>& _data, int _level)
{
std::vector<std::string> _exceptions{};
_exceptions.reserve(_data.size());
for(auto& itr : _data)
{
try
{
fmodset_t _tmp{};
load_info(itr.first, _inp, _tmp, _level);
// add to the existing
itr.second->insert(_tmp.begin(), _tmp.end());
// if it did not throw it was successfully loaded
_exceptions.clear();
break;
} catch(std::exception& _e)
{
_exceptions.emplace_back(_e.what());
}
}
if(!_exceptions.empty())
{
std::stringstream _msg{};
for(auto& itr : _exceptions)
{
_msg << "[rocprof-sys][exe] " << itr << "\n";
}
throw std::runtime_error(_msg.str());
}
}
@@ -0,0 +1,604 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "internal_libs.hpp"
#include "binary/analysis.hpp"
#include "binary/binary_info.hpp"
#include "binary/link_map.hpp"
#include "binary/scope_filter.hpp"
#include "binary/symbol.hpp"
#include "common/defines.h"
#include "core/utility.hpp"
#include "fwd.hpp"
#include "log.hpp"
#include <timemory/components/rusage/components.hpp>
#include <timemory/components/timing/wall_clock.hpp>
#include <timemory/environment/types.hpp>
#include <timemory/log/macros.hpp>
#include <timemory/utility/demangle.hpp>
#include <timemory/utility/filepath.hpp>
#include <timemory/utility/join.hpp>
#include <timemory/utility/types.hpp>
#include <algorithm>
#include <dlfcn.h>
#include <initializer_list>
#include <set>
#include <string>
#include <vector>
namespace
{
namespace filepath = ::tim::filepath;
using ::tim::delimit;
using ::tim::get_env;
using ::timemory::join::join;
using strview_init_t = std::initializer_list<std::string_view>;
using strview_set_t = std::set<std::string_view>;
using open_modes_vec_t = std::vector<int>;
auto
get_exe_realpath()
{
return filepath::realpath("/proc/self/exe", nullptr, false);
}
auto&
get_symtab_file_cache()
{
static auto _cache = std::unordered_map<std::string, std::pair<symtab_t*, bool>>{};
return _cache;
}
symtab_t*
get_symtab_file(const std::string& _name)
{
auto& _cache = get_symtab_file_cache();
auto itr = _cache.find(_name);
if(itr == _cache.end())
{
symtab_t* _v = SymTab::Symtab::findOpenSymtab(_name);
bool _closable = (_v == nullptr);
if(!_v) SymTab::Symtab::openFile(_v, _name);
TIMEMORY_PREFER(_v != nullptr)
<< "Warning! Dyninst could not open a Symtab instance for file '" << _name
<< "'\n";
_cache.emplace(_name, std::make_pair(_v, _closable));
}
return _cache.at(_name).first;
}
bool
close_symtab_file(const std::string& _name)
{
auto& _cache = get_symtab_file_cache();
auto itr = _cache.find(_name);
if(itr != _cache.end())
{
symtab_t* _symtab = itr->second.first;
bool _closable = itr->second.second;
if(_symtab && _closable) SymTab::Symtab::closeSymtab(_symtab);
_cache.erase(itr);
return true;
}
return false;
}
template <template <typename, typename...> class ContainerT, typename... TailT>
bool
check_regex_restrictions(const ContainerT<std::string, TailT...>& _names,
const regexvec_t& _regexes)
{
for(const auto& nitr : _names)
for(const auto& ritr : _regexes)
if(std::regex_search(nitr, ritr)) return true;
return false;
}
std::optional<std::string>
get_linked_path(const char* _name,
open_modes_vec_t&& _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) })
{
void* _handle = nullptr;
bool _noload = false;
for(auto _mode : _open_modes)
{
_handle = dlopen(_name, _mode);
_noload = (_mode & RTLD_NOLOAD) == RTLD_NOLOAD;
if(_handle) break;
}
tim::scope::destructor _dtor{ [&_noload, &_handle]() {
if(_noload == false) dlclose(_handle);
} };
if(_handle)
{
struct link_map* _link_map = nullptr;
dlinfo(_handle, RTLD_DI_LINKMAP, &_link_map);
if(_link_map != nullptr && !std::string_view{ _link_map->l_name }.empty())
{
return filepath::realpath(_link_map->l_name, nullptr, false);
}
}
return std::optional<std::string>{};
}
std::set<std::string>
get_link_map(const std::string& _lib,
open_modes_vec_t&& _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) })
{
void* _handle = nullptr;
bool _noload = false;
for(auto _mode : _open_modes)
{
_handle = dlopen(_lib.c_str(), _mode);
_noload = (_mode & RTLD_NOLOAD) == RTLD_NOLOAD;
if(_handle) break;
}
auto _chain = std::set<std::string>{};
if(_handle)
{
struct link_map* _link_map = nullptr;
dlinfo(_handle, RTLD_DI_LINKMAP, &_link_map);
struct link_map* _next = _link_map;
while(_next)
{
if(!std::string_view{ _next->l_name }.empty() &&
std::string_view{ _next->l_name } != _lib)
{
_chain.emplace(filepath::realpath(_next->l_name, nullptr, false));
}
_next = _next->l_next;
}
if(_noload == false) dlclose(_handle);
}
return _chain;
}
std::vector<std::string>
get_library_search_paths_impl()
{
auto _paths = std::vector<std::string>{};
auto _path_exists = [](const std::string& _filename) {
struct stat dummy;
return (_filename.empty()) ? false : (stat(_filename.c_str(), &dummy) == 0);
};
auto _emplace_if_exists = [&_paths, _path_exists](const std::string& _directory) {
if(_path_exists(_directory)) _paths.emplace_back(_directory);
};
// search paths from environment variables
for(const auto& itr : delimit(get_env("LD_LIBRARY_PATH", std::string{}, false), ":"))
_emplace_if_exists(itr);
for(const auto& itr : { get_env<std::string>("ROCPROFSYS_ROCM_PATH", ""),
get_env<std::string>("ROCM_PATH", ""),
std::string{ ROCPROFSYS_DEFAULT_ROCM_PATH } })
{
if(!itr.empty())
{
for(const auto& ditr : delimit(itr, ":"))
{
_emplace_if_exists(join('/', ditr, "lib"));
}
}
}
// search ld.so.cache
// apparently ubuntu doesn't like pclosing NULL, so a shared pointer custom
// destructor is out. Ugh.
FILE* ldconfig = popen("/sbin/ldconfig -p", "r");
if(ldconfig)
{
constexpr size_t buffer_size = 512;
char buffer[buffer_size];
// ignore first line
if(fgets(buffer, buffer_size, ldconfig))
{
// each line constaining relevant info should be in form:
// <LIBRARY_BASENAME> (...) => <RESOLVED_ABSOLUTE_PATH>
// example:
// libz.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so
auto _get_entry = [](const std::string& _inp) {
auto _paren_pos = _inp.find('(');
auto _arrow_pos = _inp.find("=>", _paren_pos);
if(_arrow_pos == std::string::npos || _paren_pos == std::string::npos)
return std::string{};
if(_arrow_pos + 2 < _inp.length())
{
auto _pos = _inp.find_first_not_of(" \t", _arrow_pos + 2);
if(_pos < _inp.length()) return _inp.substr(_pos);
}
return std::string{};
};
auto _data = std::stringstream{};
while(fgets(buffer, buffer_size, ldconfig) != nullptr)
{
_data << buffer;
auto _len = strnlen(buffer, buffer_size);
if(_len > 0 && buffer[_len - 1] == '\n')
{
auto _v = _data.str();
if(!_v.empty())
{
_v = _v.substr(_v.find_first_not_of(" \t"));
if(_v.length() > 1)
{
auto _entry = _get_entry(_v.substr(0, _v.length() - 1));
if(!_entry.empty()) _emplace_if_exists(_entry);
}
}
_data = std::stringstream{};
}
}
}
pclose(ldconfig);
}
// search hard-coded system paths
for(const char* itr :
{ "/usr/local/lib", "/usr/share/lib", "/usr/lib", "/usr/lib64",
"/usr/lib/x86_64-linux-gnu", "/lib", "/lib64", "/lib/x86_64-linux-gnu" })
{
_emplace_if_exists(itr);
}
return _paths;
}
std::set<std::string>
get_internal_basic_libs_impl()
{
auto _libs = std::set<std::string>{};
const auto _exclude = strview_set_t{ LIBM_SO, LIBMVEC_SO };
// GNU libraries likely to be used by instrumentation
const auto _gnu_libs = strview_init_t{ LD_LINUX_X86_64_SO, LD_SO,
LIBANL_SO, LIBBROKENLOCALE_SO,
LIBCRYPT_SO, LIBC_SO,
LIBDL_SO, LIBGCC_S_SO,
LIBMVEC_SO, LIBM_SO,
LIBNSL_SO, LIBNSS_COMPAT_SO,
LIBNSS_DB_SO, LIBNSS_DNS_SO,
LIBNSS_FILES_SO, LIBNSS_HESIOD_SO,
LIBNSS_LDAP_SO, LIBNSS_NISPLUS_SO,
LIBNSS_NIS_SO, LIBNSS_TEST1_SO,
LIBNSS_TEST2_SO, LIBPTHREAD_SO,
LIBRESOLV_SO, LIBRT_SO,
LIBTHREAD_DB_SO, LIBUTIL_SO };
// shared libraries used by or provided by dyninst
const auto _dyn_libs = strview_init_t{ "libdyninstAPI_RT.so",
"libcommon.so",
"libbfd.so",
"libelf.so",
"libdwarf.so",
"libdw.so",
"libtbb.so",
"libtbbmalloc.so",
"libtbbmalloc_proxy.so",
"libz.so",
"libzstd.so",
"libbz2.so",
"liblzma.so" };
// shared libraries used by rocprof-sys
const auto _omni_libs = strview_init_t{ "libstdc++.so.6",
"libgotcha.so",
"libunwind-coredump.so",
"libunwind-generic.so",
"libunwind-ptrace.so",
"libunwind-setjmp.so",
"libunwind.so",
"libunwind-x86_64.so",
"libpapi.so",
"libpfm.so",
"librocm_smi64.so",
"libroctx64.so",
"librocmtools.so",
"libroctracer64.so",
"librocprofiler64.so",
"librocprofiler-register.so",
"librocprofiler-sdk.so",
"librocprofiler-sdk-roctx.so",
"libamd_smi.so",
"libamd_comgr.so" };
// shared libraries potentially used by timemory
const auto _3rdparty_libs = strview_init_t{ "libcaliper.so",
"liblikwid.so",
"libprofiler.so",
"libtcmalloc.so",
"libtcmalloc_and_profiler.so",
"libtcmalloc_debug.so",
"libtcmalloc_minimal.so",
"libtcmalloc_minimal_debug.so" };
for(const auto& gitr : { _gnu_libs, _dyn_libs, _omni_libs, _3rdparty_libs })
{
for(auto itr : gitr)
{
if(!itr.empty() && _exclude.count(itr) == 0) _libs.emplace(itr);
}
}
// auto _link_map = binary::get_link_map(nullptr, "", "", { (RTLD_LAZY | RTLD_NOLOAD)
// }); for(const auto& itr : _link_map)
// _libs.emplace(itr.real());
return _libs;
}
std::set<std::string>
get_internal_libs_impl()
{
auto _libs = get_internal_basic_libs();
for(auto itr : get_internal_basic_libs())
{
if(!itr.empty())
{
if(parse_all_modules)
{
auto _lib_v = find_libraries(itr);
if(!_lib_v.empty())
{
for(const auto& litr : _lib_v)
{
verbprintf(2, "Library '%s' found: %s\n", itr.data(),
litr.c_str());
_libs.emplace(litr);
}
}
else
{
verbprintf(2, "Library '%s' not found\n", itr.data());
}
}
else
{
auto _lib_v = find_library(itr);
if(_lib_v)
{
verbprintf(2, "Library '%s' found: '%s'\n", itr.data(),
_lib_v->c_str());
_libs.emplace(*_lib_v);
if(include_internal_linked_libs)
{
for(auto&& litr : get_link_map(*_lib_v))
{
verbprintf(2, "Library '%s' found: '%s' (linked by '%s')\n",
itr.data(), litr.c_str(), _lib_v->c_str());
_libs.emplace(litr);
}
}
}
else
{
verbprintf(2, "Library '%s' not found\n", itr.data());
}
}
}
}
return _libs;
}
library_module_map_t
get_internal_libs_data_impl()
{
auto _wc = tim::component::wall_clock{};
auto _pr = tim::component::peak_rss{};
_wc.start();
_pr.start();
auto _libs_v = get_internal_libs();
auto _libs = std::vector<std::string>{};
_libs.assign(_libs_v.begin(), _libs_v.end());
auto _rocprofsys_base_path = filepath::dirname(
filepath::dirname(filepath::realpath("/proc/self/exe", nullptr, false)));
auto _rocprofsys_lib_path = std::string{};
for(const auto* itr : { "lib", "lib64" })
{
for(const auto* litr :
{ "librocprof-sys-dl.so", "librocprof-sys-user.so", "librocprof-sys-rt.so" })
{
auto _libpath = join('/', _rocprofsys_base_path, itr, litr);
if(filepath::exists(_libpath))
{
_libs.emplace_back(filepath::realpath(_libpath, nullptr, false));
}
}
}
rocprofsys::utility::filter_sort_unique(
_libs, [](const auto& itr) { return itr.empty() || !filepath::exists(itr); });
auto _data = library_module_map_t{};
for(const auto& itr : _libs)
{
auto _fpath = filepath::realpath(itr, nullptr, false);
// allow the user to request this library be considered for instrumentation
if(check_regex_restrictions(strvec_t{ itr, _fpath }, file_internal_include))
continue;
_data.emplace(_fpath, module_func_map_t{});
}
auto _odata = ordered(_data);
for(const auto& itr : _odata)
{
symtab_t* _symtab = get_symtab_file(itr.first);
if(!_symtab) continue;
verbprintf(0, "[internal] parsing library: '%s'...\n", itr.first.c_str());
auto _wc_v = tim::component::wall_clock{};
auto _pr_v = tim::component::peak_rss{};
_wc_v.start();
_pr_v.start();
auto _modules = std::vector<symtab_module_t*>{};
_symtab->getAllModules(_modules);
for(const auto& mitr : _modules)
{
const auto& _mname = mitr->fileName();
const auto& _mpath = mitr->fullName();
// allow the user to request this library be considered for instrumentation
if(check_regex_restrictions(strvec_t{ _mname, _mpath },
file_internal_include))
continue;
verbprintf(3, "[internal] parsing module: '%s' (via '%s')...\n",
_mname.c_str(), filepath::basename(itr.first));
_data[itr.first].emplace(_mpath, func_set_t{});
_data[itr.first].emplace(_mname, func_set_t{});
auto _funcs = mitr->getAllFunctions();
for(const auto& fitr : _funcs)
{
auto _fname = fitr->getName();
auto _dname = tim::demangle(_fname);
_data[itr.first][_mpath].emplace(_fname);
_data[itr.first][_mpath].emplace(_dname);
}
}
_pr_v.stop();
_wc_v.stop();
verbprintf(1, "[internal] parsing library: '%s'... Done (%.3f %s, %.3f %s)\n",
itr.first.c_str(), _wc_v.get(), _wc_v.display_unit().c_str(),
_pr_v.get(), _pr_v.display_unit().c_str());
// close_symtab_file(itr.first);
}
_pr.stop();
_wc.stop();
verbprintf(0, "[internal] binary info processing required %.3f %s and %.3f %s\n",
_wc.get(), _wc.display_unit().c_str(), _pr.get(),
_pr.display_unit().c_str());
return _data;
}
} // namespace
template <typename Tp, typename... TailT>
std::set<Tp>
ordered(const std::unordered_set<Tp, TailT...>& _unordered)
{
auto _ordered = std::set<Tp>{};
for(const auto& itr : _unordered)
_ordered.emplace(itr);
return _ordered;
}
template <typename KeyT, typename MappedT, typename... TailT>
std::map<KeyT, MappedT>
ordered(const std::unordered_map<KeyT, MappedT, TailT...>& _unordered)
{
auto _ordered = std::map<KeyT, MappedT>{};
for(const auto& itr : _unordered)
_ordered.emplace(itr.first, itr.second);
return _ordered;
}
std::optional<std::string>
find_library(std::string_view _lib_v)
{
auto _lib = get_linked_path(_lib_v.data(), { (RTLD_LAZY | RTLD_NOLOAD) });
if(_lib) return _lib;
for(const auto& itr : get_library_search_paths())
{
auto _path = join('/', itr, _lib_v);
if(filepath::exists(_path)) return std::optional<std::string>{ _path };
}
return std::optional<std::string>{};
}
std::vector<std::string>
find_libraries(std::string_view _lib_v)
{
auto _libs = std::vector<std::string>{};
auto _lib = get_linked_path(_lib_v.data(), { (RTLD_LAZY | RTLD_NOLOAD) });
if(_lib) _libs.emplace_back(*_lib);
for(const auto& itr : get_library_search_paths())
{
auto _path = join('/', itr, _lib_v);
if(filepath::exists(_path)) _libs.emplace_back(_path);
}
return _libs;
}
const std::vector<std::string>&
get_library_search_paths()
{
static auto _v = get_library_search_paths_impl();
return _v;
}
std::set<std::string>&
get_internal_basic_libs()
{
static auto _v = get_internal_basic_libs_impl();
return _v;
}
std::set<std::string>&
get_internal_libs()
{
static auto _v = get_internal_libs_impl();
return _v;
}
const library_module_map_t&
get_internal_libs_data()
{
static auto _v = get_internal_libs_data_impl();
return _v;
}
void
parse_internal_libs_data()
{
(void) get_internal_libs_data();
}
@@ -0,0 +1,167 @@
// MIT License
//
// Copyright (c) 2022 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 <gnu/lib-names.h>
#if !defined(LD_LINUX_X86_64_SO)
# define LD_LINUX_X86_64_SO ""
#endif
#if !defined(LD_SO)
# define LD_SO ""
#endif
#if !defined(LIBANL_SO)
# define LIBANL_SO ""
#endif
#if !defined(LIBBROKENLOCALE_SO)
# define LIBBROKENLOCALE_SO ""
#endif
#if !defined(LIBCRYPT_SO)
# define LIBCRYPT_SO ""
#endif
#if !defined(LIBC_SO)
# define LIBC_SO ""
#endif
#if !defined(LIBDL_SO)
# define LIBDL_SO ""
#endif
#if !defined(LIBGCC_S_SO)
# define LIBGCC_S_SO ""
#endif
#if !defined(LIBMVEC_SO)
# define LIBMVEC_SO ""
#endif
#if !defined(LIBM_SO)
# define LIBM_SO ""
#endif
#if !defined(LIBNSL_SO)
# define LIBNSL_SO ""
#endif
#if !defined(LIBNSS_COMPAT_SO)
# define LIBNSS_COMPAT_SO ""
#endif
#if !defined(LIBNSS_DB_SO)
# define LIBNSS_DB_SO ""
#endif
#if !defined(LIBNSS_DNS_SO)
# define LIBNSS_DNS_SO ""
#endif
#if !defined(LIBNSS_FILES_SO)
# define LIBNSS_FILES_SO ""
#endif
#if !defined(LIBNSS_HESIOD_SO)
# define LIBNSS_HESIOD_SO ""
#endif
#if !defined(LIBNSS_LDAP_SO)
# define LIBNSS_LDAP_SO ""
#endif
#if !defined(LIBNSS_NISPLUS_SO)
# define LIBNSS_NISPLUS_SO ""
#endif
#if !defined(LIBNSS_NIS_SO)
# define LIBNSS_NIS_SO ""
#endif
#if !defined(LIBNSS_TEST1_SO)
# define LIBNSS_TEST1_SO ""
#endif
#if !defined(LIBNSS_TEST2_SO)
# define LIBNSS_TEST2_SO ""
#endif
#if !defined(LIBPTHREAD_SO)
# define LIBPTHREAD_SO ""
#endif
#if !defined(LIBRESOLV_SO)
# define LIBRESOLV_SO ""
#endif
#if !defined(LIBRT_SO)
# define LIBRT_SO ""
#endif
#if !defined(LIBTHREAD_DB_SO)
# define LIBTHREAD_DB_SO ""
#endif
#if !defined(LIBUTIL_SO)
# define LIBUTIL_SO ""
#endif
#include <map>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using func_set_t = std::unordered_set<std::string>;
using module_func_map_t = std::unordered_map<std::string, func_set_t>;
using library_module_map_t = std::unordered_map<std::string, module_func_map_t>;
template <typename Tp, typename... TailT>
std::set<Tp>
ordered(const std::unordered_set<Tp, TailT...>&);
template <typename KeyT, typename MappedT, typename... TailT>
std::map<KeyT, MappedT>
ordered(const std::unordered_map<KeyT, MappedT, TailT...>&);
std::optional<std::string> find_library(std::string_view);
std::vector<std::string> find_libraries(std::string_view);
const std::vector<std::string>&
get_library_search_paths();
std::set<std::string>&
get_internal_basic_libs();
std::set<std::string>&
get_internal_libs();
const library_module_map_t&
get_internal_libs_data();
void
parse_internal_libs_data();
@@ -0,0 +1,160 @@
// MIT License
//
// Copyright (c) 2022 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 "log.hpp"
#include "fwd.hpp"
#include <cmath>
#include <iomanip>
#include <regex>
#include <vector>
namespace color = tim::log::color;
namespace
{
std::vector<log_entry> log_entries = {};
auto
get_color_regex(std::string _v)
{
auto _p = _v.find("[");
if(_p != std::string::npos) _v.insert(_p, "\\");
return JOIN("", "\\", _v);
}
auto _color_regex = std::regex{ JOIN("", "(", get_color_regex(tim::log::color::info()),
"|", get_color_regex(tim::log::color::source()), "|",
get_color_regex(tim::log::color::warning()), "|",
get_color_regex(tim::log::color::fatal()), "|",
get_color_regex(tim::log::color::end()), ")"),
std::regex_constants::optimize };
} // namespace
log_entry::log_entry(std::string _msg)
: m_message{ std::move(_msg) }
, m_backtrace{ tim::get_unw_stack<4, 1>() }
{
if(log_ofs) *log_ofs << as_string("", "", "") << "\n";
}
log_entry::log_entry(source_location _loc, std::string _msg)
: m_location{ _loc }
, m_message{ std::move(_msg) }
, m_backtrace{ tim::get_unw_stack<4, 1>() }
{
if(log_ofs) *log_ofs << as_string("", "", "") << "\n";
}
std::string
log_entry::as_string(const char* _color, const char* _src, const char* _end) const
{
std::stringstream _ss;
if(m_location.function && m_location.file)
{
_ss << "[" << _src << m_location.file << ":" << m_location.line << _end << "]["
<< _src << m_location.function << _end << "]";
}
bool _remove_color = (strlen(_color) + strlen(_src) + strlen(_end) == 0);
_ss << " " << _color << std::regex_replace(m_message, std::regex{ "\n" }, " ... ")
<< _end;
return (_remove_color) ? std::regex_replace(_ss.str(), _color_regex, "") : _ss.str();
}
log_entry&
log_entry::add_log_entry(log_entry&& _v)
{
return log_entries.emplace_back(std::move(_v));
}
void
print_log_entries(std::ostream& _os, int64_t _count,
const std::function<bool(const log_entry&)>& _condition,
const std::function<void()>& _prelude, const char* _color,
bool _color_entries)
{
size_t i0 = (_count < 0) ? 0 : std::max<int64_t>(log_entries.size() - _count, 0);
size_t _w = std::log10(log_entries.size()) + 1;
if(dynamic_cast<std::ofstream*>(&_os) ||
(&_os != &std::cout && &_os != &std::cerr && &_os != &std::clog))
{
_color = "";
_color_entries = false;
}
const char* _end =
(strlen(_color) > 0 || _color_entries) ? tim::log::color::end() : "";
if(_prelude)
{
for(size_t i = i0; i < log_entries.size(); ++i)
{
if(!_condition || _condition(log_entries.at(i)))
{
_prelude();
break;
}
}
}
// the requested number of log entries
auto _last = std::string{};
size_t _last_i = 0;
size_t _last_n = 0;
for(size_t i = i0; i < log_entries.size(); ++i)
{
auto& itr = log_entries.at(i);
if(!_condition || _condition(itr))
{
auto _msg = ((_color_entries) ? itr.as_string() : itr.as_string("", "", ""));
if(_msg != _last)
{
if(_last_n > 0 && !_last.empty())
{
_os << "[" << _color << std::setw(_w) << _last_i << "/"
<< log_entries.size() << _end << "] ... repeated " << _last_n
<< " times ...\n";
}
_last_n = 0;
_last_i = i;
_last = _msg;
_os << "[" << _color << std::setw(_w) << i << "/" << log_entries.size()
<< _end << "]" << _msg << "\n";
}
else
{
++_last_n;
}
}
}
if(_last_n > 0)
{
_os << "[" << _color << std::setw(_w) << _last_i << "/" << log_entries.size()
<< _end << "] ... repeated " << _last_n << " times ...\n";
}
}
@@ -0,0 +1,92 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <timemory/log/color.hpp>
#include <timemory/utility/backtrace.hpp>
#include <timemory/utility/join.hpp>
#include <iosfwd>
#include <ostream>
#include <string>
#include <tuple>
#if !defined(JOIN)
# define JOIN(...) ::timemory::join::join(__VA_ARGS__)
#endif
struct log_entry;
void
print_log_entries(std::ostream& = std::cerr, int64_t _count = 10,
const std::function<bool(const log_entry&)>& _cond = {},
const std::function<void()>& _prelude = {},
const char* _color = tim::log::color::warning(),
bool _color_entries = true);
struct log_entry
{
struct source_location
{
const char* function = nullptr;
const char* file = nullptr;
int line = 0;
};
log_entry(std::string _msg);
log_entry(source_location _loc, std::string _msg);
std::string as_string(const char* _color = tim::log::color::info(),
const char* _src = tim::log::color::source(),
const char* _end = tim::log::color::end()) const;
static log_entry& add_log_entry(log_entry&&);
log_entry& force(bool _v = true)
{
m_forced = _v;
return *this;
}
bool forced() const { return m_forced; }
private:
bool m_forced = false; // if should always be displayed
source_location m_location = {};
std::string m_message = {};
tim::unwind::stack<4> m_backtrace = {};
friend void print_log_entries(std::ostream&, int64_t,
std::function<bool(const log_entry&)>, const char*,
bool);
};
#define ROCPROFSYS_ADD_LOG_ENTRY(...) \
log_entry::add_log_entry( \
{ log_entry::source_location{ __FUNCTION__, __FILE__, __LINE__ }, \
timemory::join::join(' ', __VA_ARGS__) })
#define ROCPROFSYS_ADD_DETAILED_LOG_ENTRY(DELIM, ...) \
log_entry::add_log_entry( \
{ log_entry::source_location{ __FUNCTION__, __FILE__, __LINE__ }, \
timemory::join::join(DELIM, __VA_ARGS__) })
@@ -0,0 +1,261 @@
// MIT License
//
// Copyright (c) 2022 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 "function_signature.hpp"
#include "fwd.hpp"
#include <timemory/mpl/concepts.hpp>
#include <timemory/tpls/cereal/cereal/cereal.hpp>
#include <sstream>
#include <string>
#include <tuple>
struct module_function
{
using width_t = std::array<size_t, 4>;
using address_t = Dyninst::Address;
using instr_addr_pair_t = std::pair<instruction_t, address_t>;
using str_msg_t = std::tuple<int, string_t, string_t, string_t, string_t>;
using str_msg_vec_t = std::vector<str_msg_t>;
static constexpr size_t absolute_max_width = 80;
static width_t& get_width();
static void reset_width();
static void update_width(const module_function& rhs);
static void write_header(std::ostream& os);
TIMEMORY_DEFAULT_OBJECT(module_function)
module_function(module_t* mod, procedure_t* proc);
// code coverage
void register_source(address_space_t* _addr_space, procedure_t* _entr_trace,
const std::vector<point_t*>&) const;
std::pair<size_t, size_t> register_coverage(address_space_t* _addr_space,
procedure_t* _entr_trace) const;
// instrumentation
std::pair<size_t, size_t> operator()(address_space_t* _addr_space,
procedure_t* _entr_trace,
procedure_t* _exit_trace) const;
// applies logic for all "is_*" and "can_*" checks below
bool should_instrument() const;
bool should_coverage_instrument() const;
// hard constraints
bool is_instrumentable() const; // checks whether can instrument
bool can_instrument_entry() const; // checks for entry points
bool can_instrument_exit() const; // checks for exit points
bool is_internal_constrained() const; // checks internal usage constraint
bool is_module_constrained() const; // checks module constraints
bool is_routine_constrained() const; // checks function constraints
// user bypass of heuristics
bool is_user_restricted() const; // checks user restrict regexes
bool is_user_included() const; // checks user include regexes
bool is_user_excluded() const; // checks user exclude regexes
// applied before dynamic-callsite constraint
bool is_overlapping_constrained() const; // checks overlapping constrains
bool is_entry_trap_constrained() const; // checks entry trap constraint
bool is_exit_trap_constrained() const; // checks exit trap constraint
// applied before address range and # instruction constraints
bool is_dynamic_callsite_forced() const; // checks dynamic callsites
// user exclusion based on instructions
bool is_instruction_constrained() const;
// estimate the size/work of the function
bool is_address_range_constrained() const; // checks address range constraint
bool is_num_instructions_constrained() const; // check # instructions constraint
bool is_visibility_constrained() const;
bool is_linkage_constrained() const;
size_t start_address = 0;
uint64_t address_range = 0;
uint64_t num_instructions = 0;
module_t* module = nullptr;
procedure_t* function = nullptr;
symtab_func_t* symtab_function = nullptr;
flow_graph_t* flow_graph = nullptr;
string_t module_name = {};
string_t function_name = {};
function_signature signature = {};
basic_block_set_t basic_blocks = {};
basic_loop_vec_t loop_blocks = {};
std::map<instruction_category_t, int64_t> instruction_types = {};
std::vector<std::vector<instr_addr_pair_t>> instructions = {};
mutable str_msg_vec_t messages = {};
bool is_overlapping() const; // checks if func overlaps
private:
symbol_linkage_t get_linkage() const;
symbol_visibility_t get_visibility() const;
bool is_loop_num_instructions_constrained() const; // checks loop instr constraint
bool is_loop_address_range_constrained() const; // checks loop addr range constraint
bool contains_dynamic_callsites() const;
bool should_instrument(bool _coverage) const;
bool contains_user_callsite() const; // checks user caller regexes
public:
template <typename ArchiveT>
void serialize(ArchiveT& ar, const unsigned);
friend bool operator<(const module_function& lhs, const module_function& rhs)
{
return std::tie(lhs.module_name, lhs.function_name, lhs.start_address,
lhs.address_range, lhs.num_instructions) <
std::tie(rhs.module_name, rhs.function_name, rhs.start_address,
rhs.address_range, rhs.num_instructions);
}
friend bool operator==(const module_function& lhs, const module_function& rhs)
{
return std::tie(lhs.start_address, lhs.address_range, lhs.num_instructions,
lhs.module_name, lhs.function_name) ==
std::tie(rhs.start_address, rhs.address_range, rhs.num_instructions,
rhs.module_name, rhs.function_name);
}
friend std::ostream& operator<<(std::ostream& os, const module_function& rhs)
{
std::stringstream ss;
auto w0 = std::min<size_t>(get_width()[0], absolute_max_width);
auto w1 = std::min<size_t>(get_width()[1], absolute_max_width);
auto w2 = std::min<size_t>(get_width()[2], absolute_max_width);
auto _get_str = [](const std::string& _inc) {
if(_inc.length() > absolute_max_width)
return _inc.substr(0, absolute_max_width - 3) + "...";
return _inc;
};
std::stringstream _addr{};
_addr << "0x" << std::hex << rhs.start_address;
// clang-format off
ss << std::setw(14) << _addr.str() << " "
<< std::setw(14) << rhs.address_range << " "
<< std::setw(14) << rhs.num_instructions << " "
<< std::setw(6) << std::setprecision(2) << std::fixed << (rhs.address_range / static_cast<double>(rhs.num_instructions)) << " "
<< std::setw(7) << std::to_string(rhs.get_linkage()) << " "
<< std::setw(10) << std::to_string(rhs.get_visibility()) << " "
<< std::setw(w0 + 8) << std::left << _get_str(rhs.module_name) << " "
<< std::setw(w1 + 8) << std::left << _get_str(rhs.function_name) << " "
<< std::setw(w2 + 8) << std::left << _get_str(rhs.signature.get());
// clang-format on
os << ss.str();
return os;
}
};
template <typename ArchiveT>
void
module_function::serialize(ArchiveT& ar, const unsigned)
{
namespace cereal = tim::cereal;
if constexpr(tim::concepts::is_output_archive<ArchiveT>::value)
{
std::stringstream _addr{};
_addr << "0x" << std::hex << start_address;
ar(cereal::make_nvp("start_address", _addr.str()));
}
ar(cereal::make_nvp("address_range", address_range),
cereal::make_nvp("num_instructions", num_instructions),
cereal::make_nvp("module", module_name),
cereal::make_nvp("function", function_name),
cereal::make_nvp("signature", signature));
if constexpr(tim::concepts::is_output_archive<ArchiveT>::value)
{
ar(cereal::make_nvp("linkage", std::to_string(get_linkage())),
cereal::make_nvp("visibility", std::to_string(get_visibility())),
cereal::make_nvp("num_basic_blocks", basic_blocks.size()),
cereal::make_nvp("num_outer_loops", loop_blocks.size()));
ar.setNextName("heuristics");
ar.startNode();
ar(cereal::make_nvp("should_instrument", should_instrument()),
cereal::make_nvp("should_coverage_instrument", should_coverage_instrument()),
cereal::make_nvp("is_instrumentable", is_instrumentable()),
cereal::make_nvp("can_instrument_entry", can_instrument_entry()),
cereal::make_nvp("can_instrument_exit", can_instrument_exit()),
cereal::make_nvp("contains_dynamic_callsites", contains_dynamic_callsites()),
cereal::make_nvp("is_internal_constrained", is_internal_constrained()),
cereal::make_nvp("is_module_constrained", is_module_constrained()),
cereal::make_nvp("is_routine_constrained", is_routine_constrained()),
cereal::make_nvp("is_user_restricted", is_user_restricted()),
cereal::make_nvp("is_user_included", is_user_included()),
cereal::make_nvp("contains_user_callsite", contains_user_callsite()),
cereal::make_nvp("is_user_excluded", is_user_excluded()),
cereal::make_nvp("is_overlapping_constrained", is_overlapping_constrained()),
cereal::make_nvp("is_entry_trap_constrained", is_entry_trap_constrained()),
cereal::make_nvp("is_exit_trap_constrained", is_exit_trap_constrained()),
cereal::make_nvp("is_dynamic_callsite_forced", is_dynamic_callsite_forced()),
cereal::make_nvp("is_linkage_constrained", is_linkage_constrained()),
cereal::make_nvp("is_visibility_constrained", is_visibility_constrained()),
cereal::make_nvp("is_address_range_constrained",
is_address_range_constrained()),
cereal::make_nvp("is_num_instructions_constrained",
is_num_instructions_constrained()),
cereal::make_nvp("is_instruction_constrained", is_instruction_constrained()),
cereal::make_nvp("is_loop_address_range_constrained",
is_loop_address_range_constrained()),
cereal::make_nvp("is_loop_num_instructions_constrained",
is_loop_num_instructions_constrained()));
ar.finishNode();
ar.setNextName("instruction_breakdown");
ar.startNode();
for(auto itr : instruction_types)
ar(cereal::make_nvp(std::to_string(itr.first).c_str(), itr.second));
ar.finishNode();
// instructions can inflate JSON size so only output when verbosity is increased
// above default
if(debug_print || verbose_level > 3 || instr_print)
{
ar.setNextName("instructions");
ar.startNode();
ar.makeArray();
for(auto&& itr : instructions)
{
ar.startNode();
for(auto&& iitr : itr)
{
std::stringstream _addr{};
_addr << "0x" << std::hex << iitr.second;
ar(cereal::make_nvp(_addr.str().c_str(), iitr.first.format()));
}
ar.finishNode();
}
ar.finishNode();
}
}
}
@@ -0,0 +1,542 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include "function_signature.hpp"
#include "fwd.hpp"
#include "info.hpp"
#include "log.hpp"
#include "module_function.hpp"
#include <timemory/utility/filepath.hpp>
#include <timemory/utility/join.hpp>
#include <dlfcn.h>
#include <ios>
#include <string>
#include <sys/stat.h>
#include <unistd.h>
//======================================================================================//
bool
is_text_file(const std::string& filename);
//======================================================================================//
inline string_t
to_lower(string_t s)
{
for(auto& itr : s)
itr = tolower(itr);
return s;
}
//
//======================================================================================//
//
template <typename Tp, std::enable_if_t<!std::is_same<Tp, std::string>::value, int> = 0>
snippet_pointer_t
get_snippet(Tp arg)
{
return std::make_shared<snippet_t>(const_expr_t{ arg });
}
//
//======================================================================================//
//
template <typename Tp, std::enable_if_t<std::is_same<Tp, std::string>::value, int> = 0>
snippet_pointer_t
get_snippet(const Tp& arg)
{
return std::make_shared<snippet_t>(const_expr_t{ arg.c_str() });
}
//
//======================================================================================//
//
template <typename... Args>
snippet_pointer_vec_t
get_snippets(Args&&... args)
{
snippet_pointer_vec_t _tmp{};
TIMEMORY_FOLD_EXPRESSION(_tmp.push_back(get_snippet(std::forward<Args>(args))));
return _tmp;
}
//
//======================================================================================//
//
struct rocprofsys_call_expr
{
using snippet_pointer_t = std::shared_ptr<snippet_t>;
template <typename... Args>
rocprofsys_call_expr(Args&&... args)
: m_params(get_snippets(std::forward<Args>(args)...))
{}
snippet_vec_t get_params()
{
snippet_vec_t _ret;
for(auto& itr : m_params)
_ret.push_back(itr.get());
return _ret;
}
inline call_expr_pointer_t get(procedure_t* func)
{
return call_expr_pointer_t((func) ? new call_expr_t(*func, get_params())
: nullptr);
}
private:
snippet_pointer_vec_t m_params;
};
//
//======================================================================================//
//
struct rocprofsys_snippet_vec
{
using entry_type = std::vector<rocprofsys_call_expr>;
using value_type = std::vector<call_expr_pointer_t>;
template <typename... Args>
void generate(procedure_t* func, Args&&... args)
{
auto _expr = rocprofsys_call_expr(std::forward<Args>(args)...);
auto _call = _expr.get(func);
if(_call)
{
m_entries.push_back(_expr);
m_data.push_back(_call);
// m_data.push_back(entry_type{ _call, _expr });
}
}
void append(snippet_vec_t& _obj)
{
for(auto& itr : m_data)
_obj.push_back(itr.get());
}
private:
entry_type m_entries;
value_type m_data;
};
//
//======================================================================================//
//
static inline bool
rocprofsys_get_is_executable(std::string_view _cmd, bool _default_v)
{
bool _is_executable = _default_v;
if(_cmd.empty())
{
if(!tim::filepath::exists(std::string{ _cmd }))
{
verbprintf(
0,
"Warning! '%s' was not found. Dyninst may fail to open the binary for "
"instrumentation...\n",
_cmd.data());
}
Dyninst::SymtabAPI::Symtab* _symtab = nullptr;
if(Dyninst::SymtabAPI::Symtab::openFile(_symtab, _cmd.data()))
{
_is_executable = _symtab->isExecutable() && _symtab->isExec();
Dyninst::SymtabAPI::Symtab::closeSymtab(_symtab);
}
}
return _is_executable;
}
//
//======================================================================================//
//
static inline address_space_t*
rocprofsys_get_address_space(patch_pointer_t& _bpatch, int _cmdc, char** _cmdv,
const std::vector<std::string>& _cmdenv, bool _rewrite,
int _pid = -1, const std::string& _name = {})
{
address_space_t* mutatee = nullptr;
if(_rewrite)
{
if(is_text_file(_name))
{
errprintf(-127,
"'%s' is a text file. rocprof-sys only supports instrumenting "
"binary files",
_name.c_str());
}
verbprintf(1, "Opening '%s' for binary rewrite... ", _name.c_str());
fflush(stderr);
if(!_name.empty()) mutatee = _bpatch->openBinary(_name.c_str(), false);
if(!mutatee)
{
verbprintf(-1, "Failed to open binary '%s'\n", _name.c_str());
throw std::runtime_error("Failed to open binary");
}
verbprintf_bare(1, "Done\n");
}
else
{
bool _attach = (_pid >= 0);
// override the current environment create/attach to process, revert environment
using strpair_t = std::pair<std::string, std::string>;
auto _imported = std::vector<strpair_t>{};
auto _exported = std::vector<strpair_t>{};
auto _get_env_pair = [](const std::string& _full) {
auto _pos = _full.find('=');
if(_pos < _full.length())
return std::make_pair(_full.substr(0, _pos), _full.substr(_pos + 1));
return strpair_t{};
};
if(environ)
{
size_t _idx = 0;
while(environ[_idx] != nullptr)
_imported.emplace_back(_get_env_pair(environ[_idx++]));
}
for(const auto& itr : _cmdenv)
{
_exported.emplace_back(_get_env_pair(itr));
}
for(const auto& itr : _exported)
{
setenv(itr.first.c_str(), itr.second.c_str(), 1);
verbprintf(4, "[env] %s=%s\n", itr.first.c_str(), itr.second.c_str());
}
if(_attach)
{
verbprintf(1, "Attaching to process %i... ", _pid);
fflush(stderr);
char* _cmdv0 = (_cmdc > 0) ? _cmdv[0] : nullptr;
mutatee = _bpatch->processAttach(_cmdv0, _pid);
if(!mutatee)
{
verbprintf(-1, "Failed to connect to process %i\n", (int) _pid);
throw std::runtime_error("Failed to attach to process");
}
verbprintf_bare(1, "Done\n");
}
else
{
if(_cmdc < 1) errprintf(-127, "No command provided");
if(is_text_file(_cmdv[0]))
{
errprintf(-1,
"'%s' is a text file. rocprof-sys only supports instrumenting "
"binary files",
_cmdv[0]);
}
std::stringstream ss;
for(int i = 0; i < _cmdc; ++i)
{
if(!_cmdv || !_cmdv[i]) continue;
ss << " " << _cmdv[i];
}
auto _cmd_msg = ss.str();
if(_cmd_msg.length() > 1) _cmd_msg = _cmd_msg.substr(1);
verbprintf(1, "Creating process '%s'... ", _cmd_msg.c_str());
fflush(stderr);
mutatee = _bpatch->processCreate(_cmdv[0], (const char**) _cmdv, nullptr);
if(!mutatee)
{
verbprintf(-1, "Failed to create process: '%s'\n", _cmd_msg.c_str());
throw std::runtime_error("Failed to create process");
}
verbprintf_bare(1, "Done\n");
}
}
return mutatee;
}
//
//======================================================================================//
//
TIMEMORY_NOINLINE inline void
rocprofsys_thread_exit(thread_t* thread, BPatch_exitType exit_type)
{
if(!thread) return;
ROCPROFSYS_ADD_LOG_ENTRY("Executing the thread callback");
BPatch_process* app = thread->getProcess();
if(!terminate_expr)
{
fprintf(stderr, "[rocprof-sys][exe] continuing execution\n");
app->continueExecution();
return;
}
switch(exit_type)
{
case ExitedNormally:
{
fprintf(stderr, "[rocprof-sys][exe] Thread exited normally\n");
break;
}
case ExitedViaSignal:
{
fprintf(stderr, "[rocprof-sys][exe] Thread terminated unexpectedly\n");
break;
}
case NoExit:
default:
{
fprintf(stderr, "[rocprof-sys][exe] %s invoked with NoExit\n", __FUNCTION__);
break;
}
}
// terminate_expr = nullptr;
thread->oneTimeCode(*terminate_expr);
fprintf(stderr, "[rocprof-sys][exe] continuing execution\n");
app->continueExecution();
}
//
//======================================================================================//
//
TIMEMORY_NOINLINE inline void
rocprofsys_fork_callback(thread_t* parent, thread_t* child)
{
ROCPROFSYS_ADD_LOG_ENTRY("Executing the fork callback");
if(child)
{
auto* app = child->getProcess();
if(app)
{
verbprintf(4, "Stopping execution and detaching child fork...\n");
app->stopExecution();
app->detach(true);
// app->terminateExecution();
// app->continueExecution();
}
}
if(parent)
{
auto* app = parent->getProcess();
if(app)
{
verbprintf(4, "Continuing execution on parent after fork callback...\n");
app->continueExecution();
}
}
}
//
//======================================================================================//
// path resolution helpers
//
std::string&
rocprofsys_get_exe_realpath();
//
std::optional<std::string>
rocprofsys_get_origin(const char* _name,
std::vector<int>&& _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) });
//
std::vector<std::string>
rocprofsys_get_link_map(const char* _lib, const std::string& _exclude_linked_by = {},
const std::string& _exclude_re = {},
std::vector<int>&& _open_modes = { (RTLD_LAZY | RTLD_NOLOAD) });
//
//======================================================================================//
// insert_instr -- insert instrumentation into a function
//
template <typename Tp>
bool
insert_instr(address_space_t* mutatee, const std::vector<point_t*>& _points, Tp traceFunc,
procedure_loc_t, bool allow_traps)
{
if(!traceFunc || _points.empty()) return false;
auto _names = [&_points]() {
std::set<std::string> _v{};
for(const auto& itr : _points)
if(itr && itr->getFunction()) _v.emplace(get_name(itr->getFunction()));
return _v;
}();
ROCPROFSYS_ADD_LOG_ENTRY("Inserting", _points.size(),
"instrumentation points into function(s)", _names);
auto _trace = traceFunc.get();
auto _traps = std::set<point_t*>{};
if(!allow_traps)
{
for(const auto& itr : _points)
{
if(itr && itr->usesTrap_NP()) _traps.insert(itr);
}
}
ROCPROFSYS_ADD_LOG_ENTRY("Found", _traps.size(),
"instrumentation points using traps in function(s)", _names);
size_t _n = 0;
for(const auto& itr : _points)
{
if(!itr || _traps.count(itr) > 0) continue;
mutatee->insertSnippet(*_trace, *itr);
++_n;
}
ROCPROFSYS_ADD_LOG_ENTRY("Inserted", _n, "instrumentation points in function(s)",
_names);
return (_n > 0);
}
//
//======================================================================================//
// insert_instr -- insert instrumentation into loops
//
template <typename Tp>
bool
insert_instr(address_space_t* mutatee, procedure_t* funcToInstr, Tp traceFunc,
procedure_loc_t traceLoc, flow_graph_t* cfGraph,
basic_loop_t* loopToInstrument, bool allow_traps)
{
if(!funcToInstr) return false;
module_t* module = funcToInstr->getModule();
if(!module || !traceFunc) return false;
std::vector<point_t*>* _points = nullptr;
auto _trace = traceFunc.get();
ROCPROFSYS_ADD_LOG_ENTRY("Searching for loop instrumentation points in function",
get_name(funcToInstr));
if(!cfGraph) funcToInstr->getCFG();
if(cfGraph && loopToInstrument)
{
if(traceLoc == BPatch_entry)
_points = cfGraph->findLoopInstPoints(BPatch_locLoopEntry, loopToInstrument);
else if(traceLoc == BPatch_exit)
_points = cfGraph->findLoopInstPoints(BPatch_locLoopExit, loopToInstrument);
}
else
{
_points = funcToInstr->findPoint(traceLoc);
}
if(_points == nullptr) return false;
if(_points->empty()) return false;
ROCPROFSYS_ADD_LOG_ENTRY("Inserting max of", _points->size(),
"loop instrumentation points in function",
get_name(funcToInstr));
std::set<point_t*> _traps{};
if(!allow_traps)
{
for(auto& itr : *_points)
{
if(itr && itr->usesTrap_NP()) _traps.insert(itr);
}
}
ROCPROFSYS_ADD_LOG_ENTRY("Found", _traps.size(),
"loop instrumentation points using traps in function",
get_name(funcToInstr));
size_t _n = 0;
for(auto& itr : *_points)
{
if(!itr || _traps.count(itr) > 0) continue;
mutatee->insertSnippet(*_trace, *itr);
++_n;
}
ROCPROFSYS_ADD_LOG_ENTRY("Inserted", _n, "loop instrumentation points in function",
get_name(funcToInstr));
return (_n > 0);
}
//
//======================================================================================//
// insert_instr -- insert instrumentation into basic blocks
//
template <typename Tp>
bool
insert_instr(address_space_t* mutatee, Tp traceFunc, procedure_loc_t traceLoc,
basic_block_t* basicBlock, bool allow_traps)
{
if(!basicBlock) return false;
point_t* _point = nullptr;
auto _trace = traceFunc.get();
ROCPROFSYS_ADD_LOG_ENTRY(
"Searching for basic-block entry and exit instrumentation points ::",
*basicBlock);
basic_block_t* _bb = basicBlock;
switch(traceLoc)
{
case BPatch_entry: _point = _bb->findEntryPoint(); break;
case BPatch_exit: _point = _bb->findExitPoint(); break;
default:
verbprintf(0, "Warning! trace location type %i not supported\n",
(int) traceLoc);
return false;
}
if(_point == nullptr)
{
ROCPROFSYS_ADD_LOG_ENTRY("No instrumentation points were found in basic-block ",
*basicBlock);
return false;
}
if(!allow_traps && _point->usesTrap_NP())
{
ROCPROFSYS_ADD_LOG_ENTRY("Basic-block", *basicBlock,
"uses traps and traps are disallowed");
return false;
}
switch(traceLoc)
{
case BPatch_entry:
case BPatch_exit: return (mutatee->insertSnippet(*_trace, *_point) != nullptr);
default:
{
verbprintf(0, "Warning! trace location type %i not supported\n",
(int) traceLoc);
return false;
}
}
return false;
}
@@ -0,0 +1,35 @@
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems-run target
#
# ------------------------------------------------------------------------------#
add_executable(
rocprofiler-systems-run
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-run.cpp
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-run.hpp
${CMAKE_CURRENT_LIST_DIR}/impl.cpp
)
target_compile_definitions(rocprofiler-systems-run PRIVATE TIMEMORY_CMAKE=1)
target_include_directories(rocprofiler-systems-run PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(
rocprofiler-systems-run
PRIVATE
rocprofiler-systems::rocprofiler-systems-compile-definitions
rocprofiler-systems::rocprofiler-systems-headers
rocprofiler-systems::rocprofiler-systems-common-library
rocprofiler-systems::rocprofiler-systems-core
rocprofiler-systems::rocprofiler-systems-sanitizer
)
set_target_properties(
rocprofiler-systems-run
PROPERTIES
BUILD_RPATH "\$ORIGIN:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH "${ROCPROFSYS_EXE_INSTALL_RPATH}"
OUTPUT_NAME ${BINARY_NAME_PREFIX}-run
)
rocprofiler_systems_strip_target(rocprofiler-systems-run)
install(TARGETS rocprofiler-systems-run DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
@@ -0,0 +1,368 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "rocprof-sys-run.hpp"
#include "common/defines.h"
#include "common/delimit.hpp"
#include "common/environment.hpp"
#include "common/join.hpp"
#include "common/setup.hpp"
#include "core/argparse.hpp"
#include "core/config.hpp"
#include "core/state.hpp"
#include "core/timemory.hpp"
#include <timemory/environment.hpp>
#include <timemory/environment/types.hpp>
#include <timemory/log/color.hpp>
#include <timemory/settings/types.hpp>
#include <timemory/settings/vsettings.hpp>
#include <timemory/signals/signal_handlers.hpp>
#include <timemory/utility/argparse.hpp>
#include <timemory/utility/console.hpp>
#include <timemory/utility/filepath.hpp>
#include <timemory/utility/join.hpp>
#include <array>
#include <cctype>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <regex>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
#include <vector>
namespace color = ::tim::log::color;
namespace filepath = ::tim::filepath; // NOLINT
namespace console = ::tim::utility::console;
namespace argparse = ::tim::argparse;
namespace signals = ::tim::signals;
using settings = ::rocprofsys::settings;
using namespace ::timemory::join;
using ::tim::get_env;
using ::tim::log::stream;
namespace std
{
std::string
to_string(bool _v)
{
return (_v) ? "true" : "false";
}
} // namespace std
namespace
{
std::string
get_internal_libpath(const std::string& _lib)
{
auto _exe = std::string_view{ realpath("/proc/self/exe", nullptr) };
auto _pos = _exe.find_last_of('/');
auto _dir = std::string{ "./" };
if(_pos != std::string_view::npos) _dir = _exe.substr(0, _pos);
return rocprofsys::common::join("/", _dir, "..", "lib", _lib);
}
parser_data_t&
get_initial_environment(parser_data_t& _data)
{
if(environ != nullptr)
{
int idx = 0;
while(environ[idx] != nullptr)
{
auto* _v = environ[idx++];
_data.initial.emplace(_v);
_data.current.emplace_back(strdup(_v));
}
}
return _data;
}
int
get_verbose(parser_data_t& _data)
{
auto& verbose = _data.verbose;
verbose = get_env("ROCPROFSYS_CAUSAL_VERBOSE",
get_env<int>("ROCPROFSYS_VERBOSE", verbose, false));
auto _debug = get_env("ROCPROFSYS_CAUSAL_DEBUG",
get_env<bool>("ROCPROFSYS_DEBUG", false, false));
if(_debug) verbose += 8;
return verbose;
}
std::string
get_realpath(const std::string& _v)
{
auto* _tmp = realpath(_v.c_str(), nullptr);
auto _ret = std::string{ _tmp };
free(_tmp);
return _ret;
}
auto
toggle_suppression(std::tuple<bool, bool> _inp)
{
auto _out =
std::make_tuple(settings::suppress_config(), settings::suppress_parsing());
std::tie(settings::suppress_config(), settings::suppress_parsing()) = _inp;
return _out;
}
// disable suppression when exe loads but store original values for restoration later
auto initial_suppression = toggle_suppression({ true, true });
} // namespace
void
print_command(const parser_data_t& _data, std::string_view _prefix)
{
auto verbose = _data.verbose;
const auto& _argv = _data.command;
if(verbose >= 1)
stream(std::cout, color::info())
<< _prefix << "Executing '" << join(array_config{ " " }, _argv) << "'...\n";
std::cerr << color::end() << std::flush;
}
void
prepare_command_for_run(char* _exe, parser_data_t& _data)
{
if(!_data.launcher.empty())
{
bool _injected = false;
auto _new_argv = std::vector<char*>{};
for(auto* itr : _data.command)
{
if(!_injected && std::regex_search(itr, std::regex{ _data.launcher }))
{
_new_argv.emplace_back(_exe);
_new_argv.emplace_back(strdup("--"));
_injected = true;
}
_new_argv.emplace_back(itr);
}
if(!_injected)
{
throw std::runtime_error(
join("", "rocprof-sys-run was unable to match \"", _data.launcher,
"\" to any arguments on the command line: \"",
join(array_config{ " ", "", "" }, _data.command), "\""));
}
std::swap(_data.command, _new_argv);
}
}
void
prepare_environment_for_run(parser_data_t& _data)
{
if(_data.launcher.empty())
{
rocprofsys::argparse::add_ld_preload(_data);
rocprofsys::argparse::add_ld_library_path(_data);
}
}
void
print_updated_environment(parser_data_t& _data, std::string_view _prefix)
{
auto _verbose = get_verbose(_data);
if(_verbose < 0) return;
auto _env = _data.current;
const auto& _updated_envs = _data.updated;
std::sort(_env.begin(), _env.end(), [](auto* _lhs, auto* _rhs) {
if(!_lhs) return false;
if(!_rhs) return true;
return std::string_view{ _lhs } < std::string_view{ _rhs };
});
std::vector<std::string_view> _updates = {};
std::vector<std::string_view> _general = {};
for(auto* itr : _env)
{
if(itr == nullptr) continue;
auto _is_omni = (std::string_view{ itr }.find("ROCPROFSYS") == 0);
auto _updated = false;
for(const auto& vitr : _updated_envs)
{
if(std::string_view{ itr }.find(vitr) == 0)
{
_updated = true;
break;
}
}
if(_updated)
_updates.emplace_back(itr);
else if(_verbose >= 1 && _is_omni)
_general.emplace_back(itr);
}
if(_general.size() + _updates.size() == 0 || _verbose < 0) return;
std::cerr << std::endl;
for(auto& itr : _general)
stream(std::cerr, color::source()) << _prefix << itr << "\n";
for(auto& itr : _updates)
stream(std::cerr, color::source()) << _prefix << itr << "\n";
std::cerr << color::end() << std::flush;
}
parser_data_t&
parse_args(int argc, char** argv, parser_data_t& _parser_data, bool& _fork_exec)
{
get_initial_environment(_parser_data);
bool _do_parse_args = false;
for(int i = 1; i < argc; ++i)
{
auto _arg = std::string_view{ argv[i] };
if(_arg == "--" || _arg == "-?" || _arg == "-h" || _arg == "--help" ||
_arg == "--version")
_do_parse_args = true;
}
if(!_do_parse_args && argc > 1 && std::string_view{ argv[1] }.find('-') == 0)
_do_parse_args = true;
if(!_do_parse_args) return parse_command(argc, argv, _parser_data);
using parser_t = argparse::argument_parser;
using parser_err_t = typename parser_t::result_type;
toggle_suppression(initial_suppression);
rocprofsys::argparse::init_parser(_parser_data);
// no need for backtraces
signals::disable_signal_detection(signals::signal_settings::get_enabled());
auto help_check = [](parser_t& p, int _argc, char** _argv) {
std::set<std::string> help_args = { "-h", "--help", "-?" };
return (p.exists("help") || _argc == 1 ||
(_argc > 1 && help_args.find(_argv[1]) != help_args.end()));
};
const auto* _desc = R"desc(
Command line interface to rocprof-sys configuration.
)desc";
auto parser = parser_t{ basename(argv[0]), _desc };
parser.on_error([](parser_t&, const parser_err_t& _err) {
stream(std::cerr, color::fatal()) << _err << "\n";
exit(EXIT_FAILURE);
});
parser.enable_help("", "Usage: rocprof-sys-run <OPTIONS> -- <COMMAND> <ARGS>");
parser.enable_version("rocprof-sys-run", ROCPROFSYS_ARGPARSE_VERSION_INFO);
auto _cols = std::get<0>(console::get_columns());
if(_cols > parser.get_help_width() + 8)
parser.set_description_width(
std::min<int>(_cols - parser.get_help_width() - 8, 120));
// disable options related to causal profiling
_parser_data.processed_groups.emplace("causal");
rocprofsys::argparse::add_core_arguments(parser, _parser_data);
rocprofsys::argparse::add_extended_arguments(parser, _parser_data);
parser.start_group("EXECUTION OPTIONS", "");
parser.add_argument({ "--fork" }, "Execute via fork + execvpe instead of execvpe")
.min_count(0)
.max_count(1)
.dtype("boolean")
.action([&](parser_t& p) { _fork_exec = p.get<bool>("fork"); });
auto _inpv = std::vector<char*>{};
auto& _outv = _parser_data.command;
bool _hash = false;
for(int i = 0; i < argc; ++i)
{
if(argv[i] == nullptr)
{
continue;
}
else if(_hash)
{
_outv.emplace_back(strdup(argv[i]));
}
else if(std::string_view{ argv[i] } == "--")
{
_hash = true;
}
else
{
_inpv.emplace_back(strdup(argv[i]));
}
}
auto _cerr = parser.parse_args(_inpv.size(), _inpv.data());
if(_cerr)
{
std::cerr << _cerr.what() << std::endl;
exit(EXIT_FAILURE);
}
tim::log::monochrome() = _parser_data.monochrome;
return _parser_data;
}
parser_data_t&
parse_command(int argc, char** argv, parser_data_t& _parser_data)
{
toggle_suppression(initial_suppression);
rocprofsys::argparse::init_parser(_parser_data);
// no need for backtraces
signals::disable_signal_detection(signals::signal_settings::get_enabled());
auto& _outv = _parser_data.command;
bool _hash = false;
for(int i = 1; i < argc; ++i)
{
_outv.emplace_back(strdup(argv[i]));
}
return _parser_data;
}
@@ -0,0 +1,117 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "rocprof-sys-run.hpp"
#include "core/mproc.hpp"
#include <timemory/log/color.hpp>
#include <timemory/log/macros.hpp>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string_view>
#include <unistd.h>
namespace
{
auto* _getenv_at_load = getenv("TIMEMORY_LIBRARY_CTOR");
auto _setenv_at_load = setenv("TIMEMORY_LIBRARY_CTOR", "0", 0);
} // namespace
int
main(int argc, char** argv)
{
if(!_getenv_at_load)
unsetenv("TIMEMORY_LIBRARY_CTOR");
else
setenv("TIMEMORY_LIBRARY_CTOR", _getenv_at_load, 1);
auto _print_usage = [argv]() {
std::cerr << tim::log::color::fatal() << "Usage: " << argv[0]
<< " <OPTIONS> -- <COMMAND> <ARGS>" << tim::log::color::end()
<< std::endl;
};
if(argc == 1)
{
_print_usage();
return EXIT_FAILURE;
}
auto _parse_data = parser_data_t{};
auto _fork_exec = false;
parse_args(argc, argv, _parse_data, _fork_exec);
prepare_command_for_run(argv[0], _parse_data);
prepare_environment_for_run(_parse_data);
auto& _argv = _parse_data.command;
auto& _envp = _parse_data.current;
if(!_argv.empty())
{
print_updated_environment(_parse_data, "ROCPROFSYS: ");
print_command(_parse_data, "ROCPROFSYS: ");
_argv.emplace_back(nullptr);
_envp.emplace_back(nullptr);
if(_fork_exec)
{
auto _main_pid = getpid();
auto _pid = fork();
if(_pid == 0)
{
return execvpe(_argv.front(), _argv.data(), _envp.data());
}
else
{
auto _status = rocprofsys::mproc::wait_pid(_pid);
auto _ec = rocprofsys::mproc::diagnose_status(_pid, _status);
if(_ec != 0 && _parse_data.verbose >= 0)
{
TIMEMORY_PRINTF_FATAL(
stderr, "process %i exiting with non-zero exit code: %i\n", _pid,
_ec);
}
else if(_parse_data.verbose >= 2)
{
TIMEMORY_PRINTF_FATAL(
stderr,
"rocprof-sys run in process %i completed. exit code: %i\n", _pid,
_ec);
}
return _ec;
}
}
else
{
return execvpe(_argv.front(), _argv.data(), _envp.data());
}
}
_print_usage();
return EXIT_FAILURE;
}
@@ -0,0 +1,53 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include "core/argparse.hpp"
#include <csignal>
#include <map>
#include <sched.h>
#include <set>
#include <string>
#include <string_view>
#include <vector>
using parser_data_t = rocprofsys::argparse::parser_data;
void
print_command(const parser_data_t&, std::string_view);
void
print_updated_environment(parser_data_t&, std::string_view);
void
prepare_command_for_run(char*, parser_data_t&);
void
prepare_environment_for_run(parser_data_t&);
parser_data_t&
parse_args(int argc, char** argv, parser_data_t&, bool&);
parser_data_t&
parse_command(int argc, char** argv, parser_data_t&);
@@ -0,0 +1,32 @@
# ------------------------------------------------------------------------------#
#
# rocprofiler-systems-sample target
#
# ------------------------------------------------------------------------------#
add_executable(
rocprofiler-systems-sample
${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-sample.cpp
${CMAKE_CURRENT_LIST_DIR}/impl.cpp
)
target_compile_definitions(rocprofiler-systems-sample PRIVATE TIMEMORY_CMAKE=1)
target_include_directories(rocprofiler-systems-sample PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(
rocprofiler-systems-sample
PRIVATE
rocprofiler-systems::rocprofiler-systems-compile-definitions
rocprofiler-systems::rocprofiler-systems-headers
rocprofiler-systems::rocprofiler-systems-common-library
)
set_target_properties(
rocprofiler-systems-sample
PROPERTIES
BUILD_RPATH "\$ORIGIN:\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH "${ROCPROFSYS_EXE_INSTALL_RPATH}"
OUTPUT_NAME ${BINARY_NAME_PREFIX}-sample
)
rocprofiler_systems_strip_target(rocprofiler-systems-sample)
install(TARGETS rocprofiler-systems-sample DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
@@ -0,0 +1,875 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "rocprof-sys-sample.hpp"
#include "common/delimit.hpp"
#include "common/environment.hpp"
#include "common/join.hpp"
#include "common/setup.hpp"
#include <timemory/environment.hpp>
#include <timemory/log/color.hpp>
#include <timemory/utility/argparse.hpp>
#include <timemory/utility/console.hpp>
#include <timemory/utility/filepath.hpp>
#include <timemory/utility/join.hpp>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string_view>
#include <unistd.h>
#include <vector>
namespace color = tim::log::color;
using namespace timemory::join;
using tim::get_env;
using tim::log::monochrome;
using tim::log::stream;
namespace
{
int verbose = 0;
auto updated_envs = std::set<std::string_view>{};
auto original_envs = std::set<std::string>{};
auto clock_id_choices = []() {
auto clock_name = [](std::string _v) {
constexpr auto _clock_prefix = std::string_view{ "clock_" };
for(auto& itr : _v)
itr = tolower(itr);
auto _pos = _v.find(_clock_prefix);
if(_pos == 0) _v = _v.substr(_pos + _clock_prefix.length());
if(_v == "process_cputime_id") _v = "cputime";
return _v;
};
#define ROCPROFSYS_CLOCK_IDENTIFIER(VAL) \
std::make_tuple(clock_name(#VAL), VAL, std::string_view{ #VAL })
auto _choices = std::vector<std::string>{};
auto _aliases = std::map<std::string, std::vector<std::string>>{};
for(auto itr : { ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_REALTIME),
ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_MONOTONIC),
ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_PROCESS_CPUTIME_ID),
ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_MONOTONIC_RAW),
ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_REALTIME_COARSE),
ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_MONOTONIC_COARSE),
ROCPROFSYS_CLOCK_IDENTIFIER(CLOCK_BOOTTIME) })
{
auto _choice = std::to_string(std::get<1>(itr));
_choices.emplace_back(_choice);
_aliases[_choice] = { std::get<0>(itr), std::string{ std::get<2>(itr) } };
}
#undef ROCPROFSYS_CLOCK_IDENTIFIER
return std::make_pair(_choices, _aliases);
}();
} // namespace
std::string
get_realpath(const std::string& _v)
{
auto* _tmp = realpath(_v.c_str(), nullptr);
auto _ret = std::string{ _tmp };
free(_tmp);
return _ret;
}
void
print_command(const std::vector<char*>& _argv)
{
if(verbose >= 1)
stream(std::cout, color::info())
<< "Executing '" << join(array_config{ " " }, _argv) << "'...\n";
}
std::vector<char*>
get_initial_environment()
{
auto _env = std::vector<char*>{};
if(environ != nullptr)
{
int idx = 0;
while(environ[idx] != nullptr)
{
auto* _v = environ[idx++];
original_envs.emplace(_v);
_env.emplace_back(strdup(_v));
}
}
auto _dl_libpath = get_realpath(get_internal_libpath("librocprof-sys-dl.so"));
auto _omni_libpath = get_realpath(get_internal_libpath("librocprof-sys.so"));
auto _libexecpath = get_realpath(get_internal_script_path());
update_env(_env, "LD_PRELOAD", _dl_libpath, UPD_APPEND);
update_env(_env, "LD_LIBRARY_PATH", tim::filepath::dirname(_dl_libpath), UPD_APPEND);
update_env(_env, "ROCPROFSYS_SCRIPT_PATH", _libexecpath, UPD_REPLACE);
auto _mode = get_env<std::string>("ROCPROFSYS_MODE", "sampling", false);
update_env(_env, "ROCPROFSYS_USE_SAMPLING", (_mode != "causal"));
#if defined(ROCPROFSYS_USE_OMPT)
if(!getenv("OMP_TOOL_LIBRARIES"))
update_env(_env, "OMP_TOOL_LIBRARIES", _dl_libpath, UPD_APPEND);
#endif
return _env;
}
std::string
get_internal_libpath(const std::string& _lib)
{
auto _exe = std::string_view{ realpath("/proc/self/exe", nullptr) };
auto _pos = _exe.find_last_of('/');
auto _dir = std::string{ "./" };
if(_pos != std::string_view::npos) _dir = _exe.substr(0, _pos);
return rocprofsys::common::join("/", _dir, "..", "lib", _lib);
}
std::string
get_internal_script_path(void)
{
auto _exe = std::string_view{ realpath("/proc/self/exe", nullptr) };
auto _pos = _exe.find_last_of('/');
auto _dir = std::string{ "./" };
if(_pos != std::string_view::npos) _dir = _exe.substr(0, _pos);
auto _script_dir =
rocprofsys::common::join("/", _dir, "..", "libexec", "rocprofiler-systems");
return _script_dir;
}
void
print_updated_environment(std::vector<char*> _env)
{
if(get_env<int>("ROCPROFSYS_VERBOSE", 0) < 0) return;
std::sort(_env.begin(), _env.end(), [](auto* _lhs, auto* _rhs) {
if(!_lhs) return false;
if(!_rhs) return true;
return std::string_view{ _lhs } < std::string_view{ _rhs };
});
std::vector<char*> _updates = {};
std::vector<char*> _general = {};
for(auto* itr : _env)
{
if(itr == nullptr) continue;
auto _is_omni = (std::string_view{ itr }.find("ROCPROFSYS") == 0);
auto _updated = false;
for(const auto& vitr : updated_envs)
{
if(std::string_view{ itr }.find(vitr) == 0)
{
_updated = true;
break;
}
}
if(_updated)
_updates.emplace_back(itr);
else if(verbose >= 1 && _is_omni)
_general.emplace_back(itr);
}
if(_general.size() + _updates.size() == 0 || verbose < 0) return;
std::cerr << std::endl;
for(auto& itr : _general)
stream(std::cerr, color::source()) << itr << "\n";
for(auto& itr : _updates)
stream(std::cerr, color::source()) << itr << "\n";
std::cerr << std::endl;
}
template <typename Tp>
void
update_env(std::vector<char*>& _environ, std::string_view _env_var, Tp&& _env_val,
update_mode&& _mode, std::string_view _join_delim)
{
updated_envs.emplace(_env_var);
auto _prepend = (_mode & UPD_PREPEND) == UPD_PREPEND;
auto _append = (_mode & UPD_APPEND) == UPD_APPEND;
auto _weak_upd = (_mode & UPD_WEAK) == UPD_WEAK;
auto _key = join("", _env_var, "=");
for(auto& itr : _environ)
{
if(!itr) continue;
if(std::string_view{ itr }.find(_key) == 0)
{
if(_weak_upd)
{
// if the value has changed, do not update but allow overridding the value
// inherited from the initial env
if(original_envs.find(std::string{ itr }) == original_envs.end()) return;
}
if(_prepend || _append)
{
if(std::string_view{ itr }.find(join("", _env_val)) ==
std::string_view::npos)
{
auto _val = std::string{ itr }.substr(_key.length());
free(itr);
if(_prepend)
itr =
strdup(join('=', _env_var, join(_join_delim, _val, _env_val))
.c_str());
else
itr =
strdup(join('=', _env_var, join(_join_delim, _env_val, _val))
.c_str());
}
}
else
{
free(itr);
itr = strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str());
}
return;
}
}
_environ.emplace_back(
strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()));
}
void
remove_env(std::vector<char*>& _environ, std::string_view _env_var)
{
auto _key = join("", _env_var, "=");
auto _match = [&_key](auto itr) { return std::string_view{ itr }.find(_key) == 0; };
_environ.erase(std::remove_if(_environ.begin(), _environ.end(), _match),
_environ.end());
for(const auto& itr : original_envs)
{
if(std::string_view{ itr }.find(_key) == 0)
_environ.emplace_back(strdup(itr.c_str()));
}
}
std::vector<char*>
parse_args(int argc, char** argv, std::vector<char*>& _env)
{
using parser_t = tim::argparse::argument_parser;
using parser_err_t = typename parser_t::result_type;
auto help_check = [](parser_t& p, int _argc, char** _argv) {
std::set<std::string> help_args = { "-h", "--help", "-?" };
return (p.exists("help") || _argc == 1 ||
(_argc > 1 && help_args.find(_argv[1]) != help_args.end()));
};
auto _pec = EXIT_SUCCESS;
auto help_action = [&_pec, argc, argv](parser_t& p) {
if(_pec != EXIT_SUCCESS)
{
std::stringstream msg;
msg << "Error in command:";
for(int i = 0; i < argc; ++i)
msg << " " << argv[i];
msg << "\n\n";
stream(std::cerr, color::fatal()) << msg.str();
std::cerr << std::flush;
}
p.print_help();
exit(_pec);
};
auto* _dl_libpath =
realpath(get_internal_libpath("librocprof-sys-dl.so").c_str(), nullptr);
auto* _omni_libpath =
realpath(get_internal_libpath("librocprof-sys.so").c_str(), nullptr);
auto parser = parser_t(argv[0]);
parser.on_error([](parser_t&, const parser_err_t& _err) {
stream(std::cerr, color::fatal()) << _err << "\n";
exit(EXIT_FAILURE);
});
const auto* _cputime_desc =
R"(Sample based on a CPU-clock timer (default). Accepts zero or more arguments:
%{INDENT}%0. Enables sampling based on CPU-clock timer.
%{INDENT}%1. Interrupts per second. E.g., 100 == sample every 10 milliseconds of CPU-time.
%{INDENT}%2. Delay (in seconds of CPU-clock time). I.e., how long each thread should wait before taking first sample.
%{INDENT}%3+ Thread IDs to target for sampling, starting at 0 (the main thread).
%{INDENT}% May be specified as index or range, e.g., '0 2-4' will be interpreted as:
%{INDENT}% sample the main thread (0), do not sample the first child thread but sample the 2nd, 3rd, and 4th child threads)";
const auto* _realtime_desc =
R"(Sample based on a real-clock timer. Accepts zero or more arguments:
%{INDENT}%0. Enables sampling based on real-clock timer.
%{INDENT}%1. Interrupts per second. E.g., 100 == sample every 10 milliseconds of realtime.
%{INDENT}%2. Delay (in seconds of real-clock time). I.e., how long each thread should wait before taking first sample.
%{INDENT}%3+ Thread IDs to target for sampling, starting at 0 (the main thread).
%{INDENT}% May be specified as index or range, e.g., '0 2-4' will be interpreted as:
%{INDENT}% sample the main thread (0), do not sample the first child thread but sample the 2nd, 3rd, and 4th child threads
%{INDENT}% When sampling with a real-clock timer, please note that enabling this will cause threads which are typically "idle"
%{INDENT}% to consume more resources since, while idle, the real-clock time increases (and therefore triggers taking samples)
%{INDENT}% whereas the CPU-clock time does not.)";
const auto* _hsa_interrupt_desc =
R"(Set the value of the HSA_ENABLE_INTERRUPT environment variable.
%{INDENT}% ROCm version 5.2 and older have a bug which will cause a deadlock if a sample is taken while waiting for the signal
%{INDENT}% that a kernel completed -- which happens when sampling with a real-clock timer. We require this option to be set to
%{INDENT}% when --realtime is specified to make users aware that, while this may fix the bug, it can have a negative impact on
%{INDENT}% performance.
%{INDENT}% Values:
%{INDENT}% 0 avoid triggering the bug, potentially at the cost of reduced performance
%{INDENT}% 1 do not modify how ROCm is notified about kernel completion)";
const auto* _trace_policy_desc =
R"(Policy for new data when the buffer size limit is reached:
%{INDENT}%- discard : new data is ignored
%{INDENT}%- ring_buffer : new data overwrites oldest data)";
parser.set_use_color(true);
parser.enable_help();
parser.enable_version("rocprof-sys-sample", ROCPROFSYS_ARGPARSE_VERSION_INFO);
auto _cols = std::get<0>(tim::utility::console::get_columns());
if(_cols > parser.get_help_width() + 8)
parser.set_description_width(
std::min<int>(_cols - parser.get_help_width() - 8, 120));
parser.start_group("DEBUG OPTIONS", "");
parser.add_argument({ "--monochrome" }, "Disable colorized output")
.max_count(1)
.dtype("bool")
.action([&](parser_t& p) {
auto _monochrome = p.get<bool>("monochrome");
monochrome() = _monochrome;
p.set_use_color(!_monochrome);
update_env(_env, "ROCPROFSYS_MONOCHROME", (_monochrome) ? "1" : "0");
update_env(_env, "MONOCHROME", (_monochrome) ? "1" : "0");
});
parser.add_argument({ "--debug" }, "Debug output")
.max_count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_DEBUG", p.get<bool>("debug"));
});
parser.add_argument({ "-v", "--verbose" }, "Verbose output")
.count(1)
.action([&](parser_t& p) {
auto _v = p.get<int>("verbose");
verbose = _v;
update_env(_env, "ROCPROFSYS_VERBOSE", _v);
});
parser.start_group("GENERAL OPTIONS",
"These are options which are ubiquitously applied");
parser.add_argument({ "-c", "--config" }, "Configuration file")
.min_count(0)
.dtype("filepath")
.action([&](parser_t& p) {
update_env(
_env, "ROCPROFSYS_CONFIG_FILE",
join(array_config{ ":" }, p.get<std::vector<std::string>>("config")));
});
parser
.add_argument({ "-o", "--output" },
"Output path. Accepts 1-2 parameters corresponding to the output "
"path and the output prefix")
.min_count(1)
.max_count(2)
.action([&](parser_t& p) {
auto _v = p.get<std::vector<std::string>>("output");
update_env(_env, "ROCPROFSYS_OUTPUT_PATH", _v.at(0));
if(_v.size() > 1) update_env(_env, "ROCPROFSYS_OUTPUT_PREFIX", _v.at(1));
});
parser
.add_argument({ "-T", "--trace" }, "Generate a detailed trace (perfetto output)")
.max_count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE", p.get<bool>("trace"));
});
parser
.add_argument(
{ "-P", "--profile" },
"Generate a call-stack-based profile (conflicts with --flat-profile)")
.max_count(1)
.conflicts({ "flat-profile" })
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PROFILE", p.get<bool>("profile"));
});
parser
.add_argument({ "-F", "--flat-profile" },
"Generate a flat profile (conflicts with --profile)")
.max_count(1)
.conflicts({ "profile" })
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PROFILE", p.get<bool>("flat-profile"));
update_env(_env, "ROCPROFSYS_FLAT_PROFILE", p.get<bool>("flat-profile"));
});
parser
.add_argument({ "-H", "--host" },
"Enable sampling host-based metrics for the process. E.g. CPU "
"frequency, memory usage, etc.")
.max_count(1)
.action([&](parser_t& p) {
auto _h = p.get<bool>("host");
auto _d = p.get<bool>("device");
update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", _h || _d);
update_env(_env, "ROCPROFSYS_CPU_FREQ_ENABLED", _h);
if(_h) update_env(_env, "ROCPROFSYS_USE_AMD_SMI", _d);
});
parser
.add_argument({ "-D", "--device" },
"Enable sampling device-based metrics for the process. E.g. GPU "
"temperature, memory usage, etc.")
.max_count(1)
.action([&](parser_t& p) {
auto _h = p.get<bool>("host");
auto _d = p.get<bool>("device");
update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", _h || _d);
update_env(_env, "ROCPROFSYS_USE_AMD_SMI", _d);
if(_d) update_env(_env, "ROCPROFSYS_CPU_FREQ_ENABLED", _h);
});
parser
.add_argument({ "-w", "--wait" },
"This option is a combination of '--trace-wait' and "
"'--sampling-wait'. See the descriptions for those two options.")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE_DELAY", p.get<double>("wait"));
update_env(_env, "ROCPROFSYS_SAMPLING_DELAY", p.get<double>("wait"));
});
parser
.add_argument(
{ "-d", "--duration" },
"This option is a combination of '--trace-duration' and "
"'--sampling-duration'. See the descriptions for those two options.")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE_DURATION", p.get<double>("duration"));
update_env(_env, "ROCPROFSYS_SAMPLING_DURATION", p.get<double>("duration"));
});
parser.start_group("TRACING OPTIONS", "Specific options controlling tracing (i.e. "
"deterministic measurements of every event)");
parser
.add_argument({ "--trace-file" },
"Specify the trace output filename. Relative filepath will be with "
"respect to output path and output prefix.")
.count(1)
.dtype("filepath")
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PERFETTO_FILE",
p.get<std::string>("trace-file"));
});
parser
.add_argument({ "--trace-buffer-size" },
"Size limit for the trace output (in KB)")
.count(1)
.dtype("KB")
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PERFETTO_BUFFER_SIZE_KB",
p.get<int64_t>("trace-buffer-size"));
});
parser.add_argument({ "--trace-fill-policy" }, _trace_policy_desc)
.count(1)
.choices({ "discard", "ring_buffer" })
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PERFETTO_FILL_POLICY",
p.get<std::string>("trace-fill-policy"));
});
parser
.add_argument({ "--trace-wait" },
"Set the wait time (in seconds) "
"before collecting trace and/or profiling data"
"(in seconds). By default, the duration is in seconds of realtime "
"but that can changed via --trace-clock-id.")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE_DELAY", p.get<double>("trace-wait"));
});
parser
.add_argument({ "--trace-duration" },
"Set the duration of the trace and/or profile data collection (in "
"seconds). By default, the duration is in seconds of realtime but "
"that can changed via --trace-clock-id.")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE_DURATION",
p.get<double>("trace-duration"));
});
parser
.add_argument(
{ "--trace-periods" },
"More powerful version of specifying trace delay and/or duration. Format is "
"one or more groups of: <DELAY>:<DURATION>, <DELAY>:<DURATION>:<REPEAT>, "
"and/or <DELAY>:<DURATION>:<REPEAT>:<CLOCK_ID>.")
.min_count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE_PERIODS",
join(array_config{ ",", "", "" },
p.get<std::vector<std::string>>("trace-periods")));
});
parser
.add_argument(
{ "--trace-clock-id" },
"Set the default clock ID for for trace delay/duration. Note: \"cputime\" is "
"the *process* CPU time and might need to be scaled based on the number of "
"threads, i.e. 4 seconds of CPU-time for an application with 4 fully active "
"threads would equate to ~1 second of realtime. If this proves to be "
"difficult to handle in practice, please file a feature request for "
"rocprof-sys to auto-scale based on the number of threads.")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_TRACE_PERIOD_CLOCK_ID",
p.get<double>("trace-clock-id"));
})
.choices(clock_id_choices.first)
.choice_aliases(clock_id_choices.second);
parser.start_group("PROFILE OPTIONS",
"Specific options controlling profiling (i.e. deterministic "
"measurements which are aggregated into a summary)");
parser.add_argument({ "--profile-format" }, "Data formats for profiling results")
.min_count(1)
.max_count(3)
.required({ "profile|flat-profile" })
.choices({ "text", "json", "console" })
.action([&](parser_t& p) {
auto _v = p.get<std::set<std::string>>("profile");
update_env(_env, "ROCPROFSYS_PROFILE", true);
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_TEXT_OUTPUT", _v.count("text") != 0);
update_env(_env, "ROCPROFSYS_JSON_OUTPUT", _v.count("json") != 0);
update_env(_env, "ROCPROFSYS_COUT_OUTPUT", _v.count("console") != 0);
}
});
parser
.add_argument({ "--profile-diff" },
"Generate a diff output b/t the profile collected and an existing "
"profile from another run Accepts 1-2 parameters corresponding to "
"the input path and the input prefix")
.min_count(1)
.max_count(2)
.action([&](parser_t& p) {
auto _v = p.get<std::vector<std::string>>("profile-diff");
update_env(_env, "ROCPROFSYS_DIFF_OUTPUT", true);
update_env(_env, "ROCPROFSYS_INPUT_PATH", _v.at(0));
if(_v.size() > 1) update_env(_env, "ROCPROFSYS_INPUT_PREFIX", _v.at(1));
});
parser.start_group(
"HOST/DEVICE (PROCESS SAMPLING) OPTIONS",
"Process sampling is background measurements for resources available to the "
"entire process. These samples are not tied to specific lines/regions of code");
parser
.add_argument({ "--process-freq" },
"Set the default host/device sampling frequency "
"(number of interrupts per second)")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PROCESS_SAMPLING_FREQ",
p.get<double>("process-freq"));
});
parser
.add_argument({ "--process-wait" }, "Set the default wait time (i.e. delay) "
"before taking first host/device sample "
"(in seconds of realtime)")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_PROCESS_SAMPLING_DELAY",
p.get<double>("process-wait"));
});
parser
.add_argument(
{ "--process-duration" },
"Set the duration of the host/device sampling (in seconds of realtime)")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_SAMPLING_PROCESS_DURATION",
p.get<double>("process-duration"));
});
parser
.add_argument({ "--cpus" },
"CPU IDs for frequency sampling. Supports integers and/or ranges")
.dtype("int or range")
.required({ "host" })
.action([&](parser_t& p) {
update_env(
_env, "ROCPROFSYS_SAMPLING_CPUS",
join(array_config{ "," }, p.get<std::vector<std::string>>("cpus")));
});
parser
.add_argument({ "--gpus" },
"GPU IDs for SMI queries. Supports integers and/or ranges")
.dtype("int or range")
.required({ "device" })
.action([&](parser_t& p) {
update_env(
_env, "ROCPROFSYS_SAMPLING_GPUS",
join(array_config{ "," }, p.get<std::vector<std::string>>("gpus")));
});
parser.start_group("GENERAL SAMPLING OPTIONS",
"General options for timer-based sampling per-thread");
parser
.add_argument({ "-f", "--freq" }, "Set the default sampling frequency "
"(number of interrupts per second)")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_SAMPLING_FREQ", p.get<double>("freq"));
});
parser
.add_argument(
{ "--sampling-wait" },
"Set the default wait time (i.e. delay) before taking first sample "
"(in seconds). This delay time is based on the clock of the sampler, i.e., a "
"delay of 1 second for CPU-clock sampler may not equal 1 second of realtime")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_SAMPLING_DELAY", p.get<double>("sampling-wait"));
});
parser
.add_argument(
{ "--sampling-duration" },
"Set the duration of the sampling (in seconds of realtime). I.e., it is "
"possible (currently) to set a CPU-clock time delay that exceeds the "
"real-time duration... resulting in zero samples being taken")
.count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_SAMPLING_DURATION",
p.get<double>("sampling-duration"));
});
parser
.add_argument({ "-t", "--tids" },
"Specify the default thread IDs for sampling, where 0 (zero) is "
"the main thread and each thread created by the target application "
"is assigned an atomically incrementing value.")
.min_count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_SAMPLING_TIDS",
join(array_config{ ", " }, p.get<std::vector<int64_t>>("tids")));
});
parser.start_group(
"SAMPLING TIMER OPTIONS",
"These options determine the heuristic for deciding when to take a sample");
parser.add_argument({ "--cputime" }, _cputime_desc)
.min_count(0)
.action([&](parser_t& p) {
auto _v = p.get<std::deque<std::string>>("cputime");
update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME", true);
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_FREQ", _v.front());
_v.pop_front();
}
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_DELAY", _v.front());
_v.pop_front();
}
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_TIDS",
join(array_config{ "," }, _v));
}
});
parser.add_argument({ "--realtime" }, _realtime_desc)
.min_count(0)
.action([&](parser_t& p) {
auto _v = p.get<std::deque<std::string>>("realtime");
update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME", true);
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_FREQ", _v.front());
_v.pop_front();
}
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_DELAY", _v.front());
_v.pop_front();
}
if(!_v.empty())
{
update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_TIDS",
join(array_config{ "," }, _v));
}
});
std::set<std::string> _backend_choices = { "all", "kokkosp", "mpip",
"ompt", "rcclp", "amd-smi",
"mutex-locks", "spin-locks", "rw-locks",
"rocm" };
#if !defined(ROCPROFSYS_USE_MPI) && !defined(ROCPROFSYS_USE_MPI_HEADERS)
_backend_choices.erase("mpip");
#endif
#if !defined(ROCPROFSYS_USE_OMPT)
_backend_choices.erase("ompt");
#endif
#if !defined(ROCPROFSYS_USE_ROCM)
_backend_choices.erase("rocm");
_backend_choices.erase("amd-smi");
_backend_choices.erase("rcclp");
#endif
parser.start_group("BACKEND OPTIONS",
"These options control region information captured "
"w/o sampling or instrumentation");
parser.add_argument({ "-I", "--include" }, "Include data from these backends")
.choices(_backend_choices)
.action([&](parser_t& p) {
auto _v = p.get<std::set<std::string>>("include");
auto _update = [&](const auto& _opt, bool _cond) {
if(_cond || _v.count("all") > 0) update_env(_env, _opt, true);
};
_update("ROCPROFSYS_USE_KOKKOSP", _v.count("kokkosp") > 0);
_update("ROCPROFSYS_USE_MPIP", _v.count("mpip") > 0);
_update("ROCPROFSYS_USE_OMPT", _v.count("ompt") > 0);
_update("ROCPROFSYS_USE_ROCM", _v.count("rocm") > 0);
_update("ROCPROFSYS_USE_RCCLP", _v.count("rcclp") > 0);
_update("ROCPROFSYS_USE_AMD_SMI", _v.count("amd-smi") > 0);
_update("ROCPROFSYS_TRACE_THREAD_LOCKS", _v.count("mutex-locks") > 0);
_update("ROCPROFSYS_TRACE_THREAD_RW_LOCKS", _v.count("rw-locks") > 0);
_update("ROCPROFSYS_TRACE_THREAD_SPIN_LOCKS", _v.count("spin-locks") > 0);
if(_v.count("all") > 0 || _v.count("ompt") > 0)
update_env(_env, "OMP_TOOL_LIBRARIES", _dl_libpath, UPD_APPEND);
if(_v.count("all") > 0 || _v.count("kokkosp") > 0)
update_env(_env, "KOKKOS_TOOLS_LIBS", _omni_libpath, UPD_APPEND);
});
parser.add_argument({ "-E", "--exclude" }, "Exclude data from these backends")
.choices(_backend_choices)
.action([&](parser_t& p) {
auto _v = p.get<std::set<std::string>>("exclude");
auto _update = [&](const auto& _opt, bool _cond) {
if(_cond || _v.count("all") > 0) update_env(_env, _opt, false);
};
_update("ROCPROFSYS_USE_KOKKOSP", _v.count("kokkosp") > 0);
_update("ROCPROFSYS_USE_MPIP", _v.count("mpip") > 0);
_update("ROCPROFSYS_USE_OMPT", _v.count("ompt") > 0);
_update("ROCPROFSYS_USE_ROCM", _v.count("rocm") > 0);
_update("ROCPROFSYS_USE_RCCLP", _v.count("rcclp") > 0);
_update("ROCPROFSYS_USE_AMD_SMI", _v.count("amd-smi") > 0);
_update("ROCPROFSYS_TRACE_THREAD_LOCKS", _v.count("mutex-locks") > 0);
_update("ROCPROFSYS_TRACE_THREAD_RW_LOCKS", _v.count("rw-locks") > 0);
_update("ROCPROFSYS_TRACE_THREAD_SPIN_LOCKS", _v.count("spin-locks") > 0);
// if(_v.count("all") > 0 || _v.count("rocprofiler") > 0)
// {
// remove_env(_env, "ROCP_TOOL_LIB");
// remove_env(_env, "ROCP_HSA_INTERCEPT");
// }
if(_v.count("all") > 0 || _v.count("ompt") > 0)
remove_env(_env, "OMP_TOOL_LIBRARIES");
if(_v.count("all") > 0 || _v.count("kokkosp") > 0)
remove_env(_env, "KOKKOS_TOOLS_LIBS");
});
parser.start_group("HARDWARE COUNTER OPTIONS", "See also: rocprof-sys-avail -H");
parser
.add_argument({ "-C", "--cpu-events" },
"Set the CPU hardware counter events to record (ref: "
"`rocprof-sys-avail -H -c CPU`)")
.action([&](parser_t& p) {
auto _events =
join(array_config{ "," }, p.get<std::vector<std::string>>("cpu-events"));
update_env(_env, "ROCPROFSYS_PAPI_EVENTS", _events);
});
parser.start_group("MISCELLANEOUS OPTIONS", "");
parser
.add_argument({ "-i", "--inlines" },
"Include inline info in output when available")
.max_count(1)
.action([&](parser_t& p) {
update_env(_env, "ROCPROFSYS_SAMPLING_INCLUDE_INLINES",
p.get<bool>("inlines"));
});
parser.add_argument({ "--hsa-interrupt" }, _hsa_interrupt_desc)
.count(1)
.dtype("int")
.choices({ 0, 1 })
.action([&](parser_t& p) {
update_env(_env, "HSA_ENABLE_INTERRUPT", p.get<int>("hsa-interrupt"));
});
parser.end_group();
auto _inpv = std::vector<char*>{};
auto _outv = std::vector<char*>{};
bool _hash = false;
for(int i = 0; i < argc; ++i)
{
if(_hash)
{
_outv.emplace_back(argv[i]);
}
else if(std::string_view{ argv[i] } == "--")
{
_hash = true;
}
else
{
_inpv.emplace_back(argv[i]);
}
}
auto _cerr = parser.parse_args(_inpv.size(), _inpv.data());
if(help_check(parser, argc, argv))
help_action(parser);
else if(_cerr)
throw std::runtime_error(_cerr.what());
if(parser.exists("realtime") && !parser.exists("cputime"))
update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME", false);
if(parser.exists("profile") && parser.exists("flat-profile"))
throw std::runtime_error(
"Error! '--profile' argument conflicts with '--flat-profile' argument");
free(_dl_libpath);
free(_omni_libpath);
return _outv;
}
@@ -0,0 +1,66 @@
// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "rocprof-sys-sample.hpp"
#include <algorithm>
#include <iostream>
#include <string_view>
#include <unistd.h>
int
main(int argc, char** argv)
{
auto _env = get_initial_environment();
bool _has_double_hyphen = false;
for(int i = 1; i < argc; ++i)
{
auto _arg = std::string_view{ argv[i] };
if(_arg == "--" || _arg == "-?" || _arg == "-h" || _arg == "--help" ||
_arg == "--version")
_has_double_hyphen = true;
}
std::vector<char*> _argv = {};
if(_has_double_hyphen)
{
_argv = parse_args(argc, argv, _env);
}
else
{
_argv.reserve(argc);
for(int i = 1; i < argc; ++i)
_argv.emplace_back(argv[i]);
}
print_updated_environment(_env);
if(!_argv.empty())
{
print_command(_argv);
_argv.emplace_back(nullptr);
_env.emplace_back(nullptr);
return execvpe(_argv.front(), _argv.data(), _env.data());
}
}
@@ -0,0 +1,64 @@
// MIT License
//
// Copyright (c) 2022 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 <string>
#include <string_view>
#include <vector>
enum update_mode : int
{
UPD_REPLACE = 0x1,
UPD_PREPEND = 0x2,
UPD_APPEND = 0x3,
UPD_WEAK = 0x4,
};
std::string
get_realpath(const std::string& _fpath);
void
print_command(const std::vector<char*>& _argv);
void
print_updated_environment(std::vector<char*> _env);
std::vector<char*>
get_initial_environment();
std::string
get_internal_libpath(const std::string& _lib);
std::string
get_internal_script_path(void);
template <typename Tp>
void
update_env(std::vector<char*>& _environ, std::string_view _env_var, Tp&& _env_val,
update_mode&& _mode = UPD_REPLACE, std::string_view _join_delim = ":");
void
remove_env(std::vector<char*>& _environ, std::string_view _env_var);
std::vector<char*>
parse_args(int argc, char** argv, std::vector<char*>& envp);
@@ -0,0 +1,523 @@
set(ROCPROFSYS_ABORT_FAIL_REGEX
"### ERROR ###|unknown-hash=|address of faulting memory reference|exiting with non-zero exit code|terminate called after throwing an instance|calling abort.. in |Exit code: [1-9]"
CACHE INTERNAL
"Regex to catch abnormal exits when a PASS_REGULAR_EXPRESSION is set"
FORCE
)
# adds a ctest for executable
function(ROCPROFILER_SYSTEMS_ADD_BIN_TEST)
cmake_parse_arguments(
TEST
"" # options
"NAME;TARGET;TIMEOUT;WORKING_DIRECTORY" # single value args
"ARGS;ENVIRONMENT;LABELS;PROPERTIES;PASS_REGEX;FAIL_REGEX;SKIP_REGEX;DEPENDS;COMMAND" # multiple
# value args
${ARGN}
)
if(NOT TEST_WORKING_DIRECTORY)
set(TEST_WORKING_DIRECTORY ${PROJECT_BINARY_DIR})
endif()
if(NOT TEST_ENVIRONMENT)
set(TEST_ENVIRONMENT
"ROCPROFSYS_TRACE=ON"
"ROCPROFSYS_PROFILE=ON"
"ROCPROFSYS_USE_SAMPLING=ON"
"ROCPROFSYS_TIME_OUTPUT=OFF"
"LD_LIBRARY_PATH=${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}:$ENV{LD_LIBRARY_PATH}"
)
endif()
# common
list(
APPEND
TEST_ENVIRONMENT
"ROCPROFSYS_CI=ON"
"ROCPROFSYS_CI_TIMEOUT=${TEST_TIMEOUT}"
"ROCPROFSYS_CONFIG_FILE="
"ROCPROFSYS_OUTPUT_PATH=${PROJECT_BINARY_DIR}/rocprof-sys-tests-output"
"TWD=${TEST_WORKING_DIRECTORY}"
)
# copy for inverse
set(TEST_ENVIRONMENT_INV "${TEST_ENVIRONMENT}")
# different for regular test and inverse test
list(APPEND TEST_ENVIRONMENT "ROCPROFSYS_OUTPUT_PREFIX=${TEST_NAME}/")
list(APPEND TEST_ENVIRONMENT_INV "ROCPROFSYS_OUTPUT_PREFIX=${TEST_NAME}-inverse/")
if(
NOT "${TEST_PASS_REGEX}" STREQUAL ""
AND NOT "${TEST_FAIL_REGEX}" STREQUAL ""
AND NOT "${TEST_FAIL_REGEX}" MATCHES "\\|ROCPROFSYS_ABORT_FAIL_REGEX"
)
rocprofiler_systems_message(
FATAL_ERROR
"${TEST_NAME} has set pass and fail regexes but fail regex does not include '|ROCPROFSYS_ABORT_FAIL_REGEX'"
)
endif()
if("${TEST_FAIL_REGEX}" STREQUAL "")
set(TEST_FAIL_REGEX "(${ROCPROFSYS_ABORT_FAIL_REGEX})")
else()
string(
REPLACE
"|ROCPROFSYS_ABORT_FAIL_REGEX"
"|${ROCPROFSYS_ABORT_FAIL_REGEX}"
TEST_FAIL_REGEX
"${TEST_FAIL_REGEX}"
)
endif()
if(TEST_COMMAND)
add_test(
NAME ${TEST_NAME}
COMMAND ${TEST_COMMAND} ${TEST_ARGS}
WORKING_DIRECTORY ${TEST_WORKING_DIRECTORY}
)
set_tests_properties(
${TEST_NAME}
PROPERTIES
ENVIRONMENT "${TEST_ENVIRONMENT}"
TIMEOUT ${TEST_TIMEOUT}
DEPENDS "${TEST_DEPENDS}"
LABELS "rocprofiler-systems-bin;${TEST_LABELS}"
PASS_REGULAR_EXPRESSION "${TEST_PASS_REGEX}"
FAIL_REGULAR_EXPRESSION "${TEST_FAIL_REGEX}"
SKIP_REGULAR_EXPRESSION "${TEST_SKIP_REGEX}"
${TEST_PROPERTIES}
)
elseif(TARGET ${TEST_TARGET})
add_test(
NAME ${TEST_NAME}
COMMAND $<TARGET_FILE:${TEST_TARGET}> ${TEST_ARGS}
WORKING_DIRECTORY ${TEST_WORKING_DIRECTORY}
)
set_tests_properties(
${TEST_NAME}
PROPERTIES
ENVIRONMENT "${TEST_ENVIRONMENT}"
TIMEOUT ${TEST_TIMEOUT}
DEPENDS "${TEST_DEPENDS}"
LABELS "rocprofiler-systems-bin;${TEST_LABELS}"
PASS_REGULAR_EXPRESSION "${TEST_PASS_REGEX}"
FAIL_REGULAR_EXPRESSION "${TEST_FAIL_REGEX}"
SKIP_REGULAR_EXPRESSION "${TEST_SKIP_REGEX}"
${TEST_PROPERTIES}
)
elseif(ROCPROFSYS_BUILD_TESTING)
message(FATAL_ERROR "Error! ${TEST_TARGET} does not exist")
endif()
endfunction()
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-help
TARGET rocprofiler-systems-instrument
ARGS --help
LABELS rocprofiler-systems-instrument
TIMEOUT 45
PASS_REGEX
".*\\\[rocprof-sys-instrument\\\] Usage:.*\\\[DEBUG OPTIONS\\\].*\\\[MODE OPTIONS\\\].*\\\[LIBRARY OPTIONS\\\].*\\\[SYMBOL SELECTION OPTIONS\\\].*\\\[RUNTIME OPTIONS\\\].*\\\[GRANULARITY OPTIONS\\\].*\\\[DYNINST OPTIONS\\\].*"
)
# on RedHat, /usr/bin/ls is a script for `coreutils --coreutils-prog=ls`
if(EXISTS /usr/bin/coreutils)
set(LS_NAME "coreutils")
set(LS_ARGS "--coreutils-prog=ls")
else()
set(LS_NAME ls)
set(LS_ARGS)
endif()
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-simulate-ls
TARGET rocprofiler-systems-instrument
ARGS --simulate
--print-format
json
txt
xml
-v
2
--all-functions
--
${LS_NAME}
${LS_ARGS}
LABELS "simulate"
TIMEOUT 240
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-simulate-ls-check
DEPENDS rocprofiler-systems-instrument-simulate-ls
COMMAND
ls
rocprof-sys-tests-output/rocprofiler-systems-instrument-simulate-ls/instrumentation
TIMEOUT 60
PASS_REGEX
".*available.json.*available.txt.*available.xml.*excluded.json.*excluded.txt.*excluded.xml.*instrumented.json.*instrumented.txt.*instrumented.xml.*overlapping.json.*overlapping.txt.*overlapping.xml.*"
FAIL_REGEX "No such file or directory|not found|ROCPROFSYS_ABORT_FAIL_REGEX"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-simulate-lib
TARGET rocprofiler-systems-instrument
ARGS --print-available functions -v 2 --
$<TARGET_FILE:rocprofiler-systems-user-library>
LABELS "simulate"
TIMEOUT 120
PASS_REGEX
"\\\[rocprof-sys\\\]\\\[exe\\\] Runtime instrumentation is not possible!(.*)\n(.*)\\\[rocprof-sys\\\]\\\[exe\\\] Switching to binary rewrite mode and assuming '--simulate --all-functions'"
)
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/rocprof-sys-tests-output/tmp)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-simulate-lib-basename
TARGET rocprofiler-systems-instrument
ARGS --print-available
functions
-v
2
-o
${PROJECT_BINARY_DIR}/rocprof-sys-tests-output/rocprof-sys-instrument-simulate-lib-basename/${CMAKE_SHARED_LIBRARY_PREFIX}$<TARGET_FILE_BASE_NAME:rocprofiler-systems-user-library>${CMAKE_SHARED_LIBRARY_SUFFIX}
--
${CMAKE_SHARED_LIBRARY_PREFIX}$<TARGET_FILE_BASE_NAME:rocprofiler-systems-user-library>${CMAKE_SHARED_LIBRARY_SUFFIX}
LABELS "simulate"
TIMEOUT 120
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/rocprof-sys-tests-output/tmp
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-write-log
TARGET rocprofiler-systems-instrument
ARGS --print-instrumented
functions
-v
1
--log-file
user.log
--
${LS_NAME}
${LS_ARGS}
LABELS "log"
TIMEOUT 120
PASS_REGEX "Opening .*/instrumentation/user.log"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-instrument-write-log-check
DEPENDS rocprofiler-systems-instrument-write-log
COMMAND
ls
rocprof-sys-tests-output/rocprofiler-systems-instrument-write-log/instrumentation/user.log
LABELS "log"
TIMEOUT 60
PASS_REGEX "user.log"
FAIL_REGEX "No such file or directory|not found|ROCPROFSYS_ABORT_FAIL_REGEX"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-help
TARGET rocprofiler-systems-avail
ARGS --help
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PASS_REGEX
".*\\\[rocprof-sys-avail\\\] Usage:.*\\\[DEBUG OPTIONS\\\].*\\\[INFO OPTIONS\\\].*\\\[FILTER OPTIONS\\\].*\\\[COLUMN OPTIONS\\\].*\\\[DISPLAY OPTIONS\\\].*\\\[OUTPUT OPTIONS\\\].*"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-all
TARGET rocprofiler-systems-avail
ARGS --all
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-all-expand-keys
TARGET rocprofiler-systems-avail
ARGS --all --expand-keys
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
FAIL_REGEX "%[a-zA-Z_]%|ROCPROFSYS_ABORT_FAIL_REGEX"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-all-only-available-alphabetical
TARGET rocprofiler-systems-avail
ARGS --all --available --alphabetical --debug --output
${CMAKE_CURRENT_BINARY_DIR}/rocprof-sys-avail-all-only-available-alphabetical.log
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PROPERTIES
ATTACHED_FILES
${CMAKE_CURRENT_BINARY_DIR}/rocprof-sys-avail-all-only-available-alphabetical.log
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-all-csv
TARGET rocprofiler-systems-avail
ARGS --all --csv --csv-separator "#"
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PASS_REGEX
"COMPONENT#AVAILABLE#VALUE_TYPE#STRING_IDS#FILENAME#DESCRIPTION#CATEGORY#.*ENVIRONMENT VARIABLE#VALUE#DATA TYPE#DESCRIPTION#CATEGORIES#.*HARDWARE COUNTER#DEVICE#AVAILABLE#DESCRIPTION#"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-filter-wall-clock-available
TARGET rocprofiler-systems-avail
ARGS -r wall_clock -C --available
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PASS_REGEX
"\\\|[-]+\\\|\n\\\|[ ]+COMPONENT[ ]+\\\|\n\\\|[-]+\\\|\n\\\| (wall_clock)[ ]+\\\|\n\\\|[-]+\\\|"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-category-filter-rocprofiler-systems
TARGET rocprofiler-systems-avail
ARGS --categories settings::rocprofsys --brief
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PASS_REGEX "ROCPROFSYS_(SETTINGS_DESC|OUTPUT_FILE|OUTPUT_PREFIX)"
FAIL_REGEX
"ROCPROFSYS_(ADD_SECONDARY|SCIENTIFIC|PRECISION|MEMORY_PRECISION|TIMING_PRECISION)|ROCPROFSYS_ABORT_FAIL_REGEX"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-category-filter-timemory
TARGET rocprofiler-systems-avail
ARGS --categories settings::timemory --brief --advanced
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PASS_REGEX
"ROCPROFSYS_(ADD_SECONDARY|SCIENTIFIC|PRECISION|MEMORY_PRECISION|TIMING_PRECISION)"
FAIL_REGEX "ROCPROFSYS_(SETTINGS_DESC|OUTPUT_FILE)|ROCPROFSYS_ABORT_FAIL_REGEX"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-regex-negation
TARGET rocprofiler-systems-avail
ARGS -R
rocprofsys
~timemory
-r
_P
~PERFETTO
~PROCESS_SAMPLING
~KOKKOSP
--csv
--brief
--advanced
LABELS "rocprofiler-systems-avail"
TIMEOUT 45
PASS_REGEX
"ENVIRONMENT VARIABLE,[ \n]+ROCPROFSYS_THREAD_POOL_SIZE,[ \n]+ROCPROFSYS_USE_PID,[ \n]+"
FAIL_REGEX "ROCPROFSYS_TRACE|ROCPROFSYS_ABORT_FAIL_REGEX"
)
string(
REPLACE
"+"
"\\\+"
_AVAIL_CFG_PATH
"${PROJECT_BINARY_DIR}/rocprof-sys-tests-output/rocprof-sys-avail/rocprof-sys-"
)
# use of TWD == Test Working Directory (added by function)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-write-config
TARGET rocprofiler-systems-avail
ARGS -G
%env{TWD}%/rocprof-sys-tests-output/rocprof-sys-avail/rocprof-sys-test.cfg
-F
txt
json
xml
--force
--all
-c
rocprofsys
TIMEOUT 45
LABELS "rocprofiler-systems-avail"
PASS_REGEX
"Outputting JSON configuration file '${_AVAIL_CFG_PATH}test\\\.json'(.*)Outputting XML configuration file '${_AVAIL_CFG_PATH}test\\\.xml'(.*)Outputting text configuration file '${_AVAIL_CFG_PATH}test\\\.cfg'(.*)"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-write-config-tweak
TARGET rocprofiler-systems-avail
ARGS -G %env{TWD}%/rocprof-sys-tests-output/rocprof-sys-avail/rocprof-sys-tweak.cfg -F
txt json xml --force
TIMEOUT 45
LABELS "rocprofiler-systems-avail"
ENVIRONMENT "ROCPROFSYS_TRACE=OFF;ROCPROFSYS_PROFILE=ON"
PASS_REGEX
"Outputting JSON configuration file '${_AVAIL_CFG_PATH}tweak\\\.json'(.*)Outputting XML configuration file '${_AVAIL_CFG_PATH}tweak\\\.xml'(.*)Outputting text configuration file '${_AVAIL_CFG_PATH}tweak\\\.cfg'(.*)"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-list-keys
TARGET rocprofiler-systems-avail
ARGS --list-keys --expand-keys
TIMEOUT 45
LABELS "rocprofiler-systems-avail"
PASS_REGEX "Output Keys:\n(.*)%argv%(.*)%argv_hash%"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-list-keys-markdown
TARGET rocprofiler-systems-avail
ARGS --list-keys --expand-keys --markdown
TIMEOUT 45
LABELS "rocprofiler-systems-avail;markdown"
PASS_REGEX "(.*)`%argv%`(.*)`%argv_hash%`"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-list-categories
TARGET rocprofiler-systems-avail
ARGS --list-categories
TIMEOUT 45
LABELS "rocprofiler-systems-avail"
PASS_REGEX " component::(.*) hw_counters::(.*) settings::"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-avail-core-categories
TARGET rocprofiler-systems-avail
ARGS -c core
TIMEOUT 45
LABELS "rocprofiler-systems-avail"
PASS_REGEX
"ROCPROFSYS_CONFIG_FILE(.*)ROCPROFSYS_ENABLED(.*)ROCPROFSYS_SUPPRESS_CONFIG(.*)ROCPROFSYS_SUPPRESS_PARSING(.*)ROCPROFSYS_VERBOSE"
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-run-help
TARGET rocprofiler-systems-run
ARGS --help
TIMEOUT 45
LABELS "rocprofiler-systems-run"
)
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/rocprof-sys-tests-config")
file(
WRITE
"${PROJECT_BINARY_DIR}/rocprof-sys-tests-config/empty.cfg"
"
#
# empty config file
#
"
)
add_executable(sleeper ${CMAKE_CURRENT_SOURCE_DIR}/sleeper.cpp)
set_target_properties(
sleeper
PROPERTIES
BUILD_TYPE RelWithDebInfo
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/testing
)
rocprofiler_systems_add_bin_test(
NAME rocprofiler-systems-run-args
TARGET rocprofiler-systems-run
ARGS --monochrome
--debug=false
-v
1
-c
%env{TWD}%/rocprof-sys-tests-config/empty.cfg
-o
rocprof-sys-tests-output
rocprofiler-systems-run-args-output/
-TPHD
-S
cputime
realtime
--trace-wait=1.0e-12
--trace-duration=5.0
--wait=1.0
--duration=3.0
--trace-file=perfetto-run-args-trace.proto
--trace-buffer-size=100
--trace-fill-policy=ring_buffer
--profile-format
console
json
text
--process-freq
1000
--process-wait
0.0
--process-duration
10
--cpus
0-4
--gpus
0
-f
1000
--sampling-wait
1.0
--sampling-duration
10
-t
0-3
--sample-cputime
1000
1.0
0-3
--sample-realtime
10
0.5
0-3
-I
all
-E
mutex-locks
rw-locks
spin-locks
-C
perf::INSTRUCTIONS
--inlines
--hsa-interrupt
0
--use-causal=false
--use-kokkosp
--num-threads-hint=4
--sampling-allocator-size=32
--ci
--dl-verbose=3
--perfetto-annotations=off
--kokkosp-kernel-logger
--kokkosp-name-length-max=1024
--kokkosp-prefix="[kokkos]"
--tmpdir
${CMAKE_BINARY_DIR}/rocprof-sys-tests-config/tmpdir
--perfetto-backend
inprocess
--use-pid
false
--time-output
off
--thread-pool-size
0
--timemory-components
wall_clock
cpu_clock
peak_rss
page_rss
--fork
--
$<TARGET_FILE:sleeper>
5
TIMEOUT 45
LABELS "rocprofiler-systems-run"
)
@@ -0,0 +1,28 @@
#include <chrono>
#include <ratio>
#include <sstream>
#include <thread>
using clock_type = std::chrono::steady_clock;
int
main(int argc, char** argv)
{
double _val = 0.0;
if(argc > 0)
{
auto _ss = std::stringstream{};
_ss << argv[1];
_ss >> _val;
}
intmax_t _nsec = _val * std::nano::den;
auto _end = clock_type::now() + std::chrono::nanoseconds{ _nsec };
while(clock_type::now() < _end)
{
std::this_thread::sleep_for(std::chrono::nanoseconds{ _nsec / 10 });
}
return 0;
}