Record loaded runtimes (#6)

* Provide rocprofiler_register_iterate_registration_info function

- stores the runtime arguments for later reference

* Fix compilation error

* Removed unused variable

* Store api tables in vector

* Update license

* Replace global_mutex usage with scoped_count

- in rocp_invoke_registrations

* Formatting

---------

Co-authored-by: Jonathan R. Madsen <jonathanrmadsen@gmail.com>

[ROCm/rocprofiler-register commit: fc712350ef]
This commit is contained in:
Madsen, Jonathan
2025-03-21 04:36:58 -05:00
committed by GitHub
parent e820b8862a
commit 8f1cb6417a
5 changed files with 386 additions and 28 deletions
@@ -129,6 +129,50 @@ rocprofiler_register_library_api_table(
const char* rocprofiler_register_error_string(rocprofiler_register_error_code_t)
ROCPROFILER_REGISTER_PUBLIC_API;
/// @brief Struct containing the information about the libraries which have registered
/// with rocprofiler-register. @see rocprofiler_register_iterate_registration_info
typedef struct rocprofiler_register_registration_info_t
{
size_t size; ///< in case of future extensions
const char* common_name; ///< name of the library
uint32_t lib_version; ///< version
uint64_t api_table_length; ///< number of API tables
} rocprofiler_register_registration_info_t;
/**
* @brief Callback function for iterating over the libraries which have registered
* with rocprofiler-register. @see rocprofiler_register_iterate_registration_info
*
* @param [in] info Pointer to library registration instance. Invokee should make a copy
* for reference outside of callback.
* @param [in] data User data passed to ::rocprofiler_register_iterate_registration_info
* @return int
* @retval 0 If zero is returned from callback, rocprofiler-register will continue to next
* registration info, if one exists
* @retval -1 If -1 (or any value != 0) is returned from callback, rocprofiler-register
* will cease to iterate over the remaining registration info, if any exists
*/
typedef int (*rocprofiler_register_registration_info_cb_t)(
rocprofiler_register_registration_info_t* info,
void* data);
/**
* @brief Iterates over all the (valid) libraries which registered their API tables with
* rocprofiler-register. Any libraries which do not have an accepted common name, have an
* invalid import function address (in secure mode), or have registered too many instances
* are not reported by this function.
*
* @param [in] callback Callback function to invoke for each valid registered library
* @param [in] data User data to pass to the callback function
* @return ::rocprofiler_register_error_code_t
* @retval ::ROCP_REG_SUCCESS Always returned
*/
rocprofiler_register_error_code_t
rocprofiler_register_iterate_registration_info(
rocprofiler_register_registration_info_cb_t callback,
void* data)
ROCPROFILER_REGISTER_ATTRIBUTE(nonnull(1)) ROCPROFILER_REGISTER_PUBLIC_API;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,87 @@
// MIT License
//
// Copyright (c) 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 "fmt/core.h"
#include "glog/logging.h"
#include <functional>
#include <utility>
namespace rocprofiler_register
{
namespace common
{
struct scope_destructor
{
/// \fn scope_destructor(FuncT&& _fini, InitT&& _init)
/// \tparam FuncT "std::function<void()> or void (*)()"
/// \tparam InitT "std::function<void()> or void (*)()"
/// \param _fini Function to execute when object is destroyed
/// \param _init Function to execute when object is created (optional)
///
/// \brief Provides a utility to perform an operation when exiting a scope.
template <typename FuncT, typename InitT = void (*)()>
scope_destructor(
FuncT&& _fini,
InitT&& _init = []() {});
~scope_destructor() { m_functor(); }
// delete copy operations
scope_destructor(const scope_destructor&) = delete;
scope_destructor& operator=(const scope_destructor&) = delete;
// allow move operations
scope_destructor(scope_destructor&& rhs) noexcept;
scope_destructor& operator=(scope_destructor&& rhs) noexcept;
private:
std::function<void()> m_functor = []() {};
};
template <typename FuncT, typename InitT>
scope_destructor::scope_destructor(FuncT&& _fini, InitT&& _init)
: m_functor{ std::forward<FuncT>(_fini) }
{
_init();
}
inline scope_destructor::scope_destructor(scope_destructor&& rhs) noexcept
: m_functor{ std::move(rhs.m_functor) }
{
rhs.m_functor = []() {};
}
inline scope_destructor&
scope_destructor::operator=(scope_destructor&& rhs) noexcept
{
if(this != &rhs)
{
m_functor = std::move(rhs.m_functor);
rhs.m_functor = []() {};
}
return *this;
}
} // namespace common
} // namespace rocprofiler_register
@@ -26,6 +26,7 @@
#include "details/environment.hpp"
#include "details/filesystem.hpp"
#include "details/logging.hpp"
#include "details/scope_destructor.hpp"
#include <fmt/format.h>
#include <glog/logging.h>
@@ -33,6 +34,7 @@
#include <array>
#include <atomic>
#include <bitset>
#include <fstream>
#include <mutex>
#include <regex>
#include <stdexcept>
@@ -40,6 +42,7 @@
#include <utility>
#include <dlfcn.h>
#include <unistd.h>
extern "C" {
#pragma weak rocprofiler_configure
@@ -247,7 +250,13 @@ auto rocp_reg_get_imports(std::index_sequence<Idx...>)
rocp_set_api_table_data_t
rocp_load_rocprofiler_lib(std::string _rocp_reg_lib);
auto
struct rocp_scan_data
{
void* handle = nullptr;
rocprofiler_set_api_table_t set_api_table_fn = nullptr;
};
rocp_scan_data
rocp_reg_scan_for_tools()
{
auto* _configure_func = dlsym(RTLD_DEFAULT, "rocprofiler_configure");
@@ -265,7 +274,7 @@ rocp_reg_scan_for_tools()
if(_found_tool)
{
if(rocprofiler_lib_handle && rocprofiler_lib_config_fn)
return std::make_pair(rocprofiler_lib_handle, rocprofiler_lib_config_fn);
return rocp_scan_data{ rocprofiler_lib_handle, rocprofiler_lib_config_fn };
if(_rocp_reg_lib.empty()) _rocp_reg_lib = rocprofiler_lib_name;
@@ -281,7 +290,7 @@ rocp_reg_scan_for_tools()
rocprofiler_lib_config_fn = &rocprofiler_set_api_table;
}
return std::make_pair(rocprofiler_lib_handle, rocprofiler_lib_config_fn);
return rocp_scan_data{ rocprofiler_lib_handle, rocprofiler_lib_config_fn };
}
rocp_set_api_table_data_t
@@ -355,10 +364,105 @@ rocp_load_rocprofiler_lib(std::string _rocp_reg_lib)
return std::make_tuple(rocprofiler_lib_handle, rocprofiler_lib_config_fn);
}
struct registered_library_api_table
{
bool propagated = false;
const char* common_name = nullptr;
rocprofiler_register_import_func_t import_func = nullptr;
uint32_t lib_version = 0;
std::vector<void*> api_tables = {};
uint64_t instance_value = 0;
};
constexpr auto instance_bits = sizeof(uint64_t) * 8; // bits in instance_counters
constexpr auto max_instances = instance_bits * ROCP_REG_LAST;
constexpr auto library_seq = std::make_index_sequence<ROCP_REG_LAST>{};
auto global_count = std::atomic<uint32_t>{ 0 };
auto import_info = rocp_reg_get_imports(library_seq);
auto instance_counters = std::array<std::atomic_uint64_t, ROCP_REG_LAST>{};
auto registered =
std::array<std::optional<registered_library_api_table>, max_instances>{};
struct scoped_count
{
scoped_count()
: value{ ++global_count }
{ }
~scoped_count() { --global_count; }
scoped_count(const scoped_count&) = delete;
scoped_count(scoped_count&&) noexcept = delete;
scoped_count& operator=(const scoped_count&) = delete;
scoped_count& operator=(scoped_count&&) noexcept = delete;
uint32_t value = 0;
};
std::optional<registered_library_api_table>*
rocp_add_registered_library_api_table(const char* common_name,
rocprofiler_register_import_func_t import_func,
uint32_t lib_version,
void** api_tables,
uint64_t api_tables_len,
uint64_t instance_val)
{
LOG(INFO) << fmt::format("rocprofiler-register library api table registration:\n\t-"
"name: {}\n\t- version: {}\n\t- # tables: {}",
common_name,
lib_version,
api_tables_len);
for(auto& itr : registered)
{
if(!itr)
{
auto _tables = std::vector<void*>{};
_tables.reserve(api_tables_len);
for(uint64_t i = 0; i < api_tables_len; ++i)
_tables.emplace_back(api_tables[i]);
itr = registered_library_api_table{
false, common_name, import_func,
lib_version, std::move(_tables), instance_val
};
return &itr;
}
}
return nullptr;
}
rocprofiler_register_error_code_t
rocp_invoke_registrations(bool invoke_all)
{
auto _count = scoped_count{};
if(_count.value > 1) return ROCP_REG_DEADLOCK;
for(auto& itr : registered)
{
if(itr && (!itr->propagated || invoke_all))
{
auto _scan_result = rocp_reg_scan_for_tools();
// rocprofiler_set_api_table has been found and we have pass the API data
auto _activate_rocprofiler = (_scan_result.set_api_table_fn != nullptr);
if(_activate_rocprofiler)
{
auto _ret = _scan_result.set_api_table_fn(itr->common_name,
itr->lib_version,
itr->instance_value,
itr->api_tables.data(),
itr->api_tables.size());
if(_ret != 0) return ROCP_REG_ROCPROFILER_ERROR;
itr->propagated = true;
}
}
}
return ROCP_REG_SUCCESS;
}
} // namespace
extern "C" {
@@ -382,22 +486,6 @@ rocprofiler_register_library_api_table(
return ROCP_REG_NO_TOOLS;
}
struct scoped_count
{
scoped_count()
: value{ ++global_count }
{ }
~scoped_count() { --global_count; }
scoped_count(const scoped_count&) = delete;
scoped_count(scoped_count&&) noexcept = delete;
scoped_count& operator=(const scoped_count&) = delete;
scoped_count& operator=(scoped_count&&) noexcept = delete;
uint32_t value = 0;
};
auto _count = scoped_count{};
if(_count.value > 1) return ROCP_REG_DEADLOCK;
@@ -464,17 +552,33 @@ rocprofiler_register_library_api_table(
auto& _bits = *reinterpret_cast<bitset_t*>(&register_id->handle);
_bits = bitset_t{ (offset_factor * _import_match->library_idx) + _instance_val };
auto* reginfo = rocp_add_registered_library_api_table(common_name,
import_func,
lib_version,
api_tables,
api_table_length,
_instance_val);
LOG_IF(WARNING, !reginfo) << fmt::format(
"rocprofiler-register failed to create registration info for "
"{} version {} (instance {})",
common_name,
lib_version,
_instance_val);
if(_bits.to_ulong() != register_id->handle)
throw std::runtime_error("error encoding register_id");
// rocprofiler library is dlopened and we have the functor to pass the API data
auto _activate_rocprofiler = (_scan_result.second != nullptr);
auto _activate_rocprofiler = (_scan_result.set_api_table_fn != nullptr);
if(_activate_rocprofiler)
{
auto _ret = _scan_result.second(
auto _ret = _scan_result.set_api_table_fn(
common_name, lib_version, _instance_val, api_tables, api_table_length);
if(_ret != 0) return ROCP_REG_ROCPROFILER_ERROR;
if(reginfo) (*reginfo)->propagated = true;
}
else
{
@@ -490,4 +594,49 @@ rocprofiler_register_error_string(rocprofiler_register_error_code_t _ec)
return rocprofiler_register_error_string(
_ec, std::make_index_sequence<ROCP_REG_ERROR_CODE_END>{});
}
rocprofiler_register_error_code_t
rocprofiler_register_iterate_registration_info(
rocprofiler_register_registration_info_cb_t callback,
void* data)
{
for(const auto& itr : registered)
{
if(itr)
{
auto _info = rocprofiler_register_registration_info_t{
.size = sizeof(rocprofiler_register_registration_info_t),
.common_name = itr->common_name,
.lib_version = itr->lib_version,
.api_table_length = itr->api_tables.size()
};
// invoke callback and break if the caller does not return zero
if(callback(&_info, data) != ROCP_REG_SUCCESS) break;
}
}
return ROCP_REG_SUCCESS;
}
rocprofiler_register_error_code_t
rocprofiler_register_invoke_nonpropagated_registrations() ROCPROFILER_REGISTER_PUBLIC_API;
//
// This function can be invoked by ptrace
rocprofiler_register_error_code_t
rocprofiler_register_invoke_nonpropagated_registrations()
{
return rocp_invoke_registrations(false);
}
rocprofiler_register_error_code_t
rocprofiler_register_invoke_all_registrations() ROCPROFILER_REGISTER_PUBLIC_API;
//
// This function can be invoked by ptrace
rocprofiler_register_error_code_t
rocprofiler_register_invoke_all_registrations()
{
return rocp_invoke_registrations(true);
}
}