Add 'projects/rocprofiler-sdk/' from commit 'bf0fad1d5406fbc51403ba1aa9621a9d4a9bce2b'

git-subtree-dir: projects/rocprofiler-sdk
git-subtree-mainline: 50a90550e9
git-subtree-split: bf0fad1d54
Αυτή η υποβολή περιλαμβάνεται σε:
systems-assistant[bot]
2025-07-22 22:52:46 +00:00
γονέας 50a90550e9 bf0fad1d54
υποβολή ad0fb25ed5
1272 αρχεία άλλαξαν με 230117 προσθήκες και 0 διαγραφές
@@ -0,0 +1,72 @@
#
# Builds common utilities into a static library
#
rocprofiler_activate_clang_tidy()
set(common_sources
demangle.cpp
elf_utils.cpp
environment.cpp
logging.cpp
md5sum.cpp
sha256.cpp
simple_timer.cpp
static_object.cpp
static_tl_object.cpp
string_entry.cpp
utility.cpp
uuid_v7.cpp)
set(common_headers
abi.hpp
defines.hpp
demangle.hpp
elf_utils.hpp
environment.hpp
filesystem.hpp
hasher.hpp
logging.hpp
md5sum.hpp
mpl.hpp
scope_destructor.hpp
sha256.hpp
simple_timer.hpp
static_object.hpp
static_tl_object.hpp
string_entry.hpp
stringize_arg.hpp
synchronized.hpp
units.hpp
utility.hpp
uuid_v7.hpp)
add_library(rocprofiler-sdk-common-library STATIC)
add_library(rocprofiler-sdk::rocprofiler-sdk-common-library ALIAS
rocprofiler-sdk-common-library)
add_subdirectory(container)
add_subdirectory(memory)
target_sources(rocprofiler-sdk-common-library PRIVATE ${common_sources} ${common_headers})
target_include_directories(rocprofiler-sdk-common-library
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/source>)
target_link_libraries(
rocprofiler-sdk-common-library
PUBLIC $<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-headers>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-build-flags>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-threading>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-memcheck>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-cxx-filesystem>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-glog>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-fmt>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-yaml-cpp>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-dl>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-ptl>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-atomic>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-hsakmt-nolink>
$<BUILD_INTERFACE:rocprofiler-sdk::rocprofiler-sdk-elfio>)
set_target_properties(rocprofiler-sdk-common-library PROPERTIES OUTPUT_NAME
rocprofiler-sdk-common)
add_subdirectory(details)
@@ -0,0 +1,92 @@
// MIT License
//
// Copyright (c) 2023-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 <rocprofiler-sdk/ext_version.h>
#include "lib/common/defines.hpp"
#include <cstddef>
namespace rocprofiler
{
namespace common
{
namespace abi
{
constexpr auto
compute_table_offset(size_t num_funcs)
{
return (num_funcs * sizeof(void*)) + sizeof(size_t);
}
} // namespace abi
} // namespace common
} // namespace rocprofiler
// ROCP_SDK_ENFORCE_ABI_VERSIONING will cause a compiler error if the size of the API table
// changed (most likely due to addition of new dispatch table entry) to make sure the developer is
// reminded to update the table versioning value before changing the value in
// ROCP_SDK_ENFORCE_ABI_VERSIONING to make this static assert pass.
//
// ROCP_SDK_ENFORCE_ABI will cause a compiler error if the order of the members in the API table
// change. Do not reorder member variables and change existing ROCP_SDK_ENFORCE_ABI values --
// always
//
// Please note: rocprofiler will do very strict compile time checks to make
// sure these versioning values are appropriately updated -- so commenting out this check, only
// updating the size field in ROCP_SDK_ENFORCE_ABI_VERSIONING, etc. will result in the
// rocprofiler-sdk failing to build and you will be forced to do the work anyway.
#if !defined(ROCPROFILER_UNSAFE_NO_VERSION_CHECK)
# define ROCP_SDK_ENFORCE_ABI_VERSIONING(TABLE, NUM) \
static_assert( \
sizeof(TABLE) == ::rocprofiler::common::abi::compute_table_offset(NUM), \
"size of the API table struct has changed. Update the STEP_VERSION number (or " \
"in rare cases, the MAJOR_VERSION number)");
# define ROCP_SDK_ENFORCE_ABI(TABLE, ENTRY, NUM) \
static_assert( \
offsetof(TABLE, ENTRY) == ::rocprofiler::common::abi::compute_table_offset(NUM), \
"ABI break for " #TABLE "." #ENTRY \
". Only add new function pointers to end of struct and do not rearrange them");
#else
# define ROCP_SDK_ENFORCE_ABI_VERSIONING(TABLE, NUM)
# define ROCP_SDK_ENFORCE_ABI(TABLE, ENTRY, NUM)
#endif
// These are guarded by ROCPROFILER_CI=1
#if !defined(ROCPROFILER_UNSAFE_NO_VERSION_CHECK) && (defined(ROCPROFILER_CI) && ROCPROFILER_CI > 0)
# define INTERNAL_CI_ROCP_SDK_ENFORCE_ABI_VERSIONING(TABLE, NUM) \
static_assert( \
sizeof(TABLE) == ::rocprofiler::common::abi::compute_table_offset(NUM), \
"size of the API table struct has changed. Update the STEP_VERSION number (or " \
"in rare cases, the MAJOR_VERSION number)");
# define INTERNAL_CI_ROCP_SDK_ENFORCE_ABI(TABLE, ENTRY, NUM) \
static_assert( \
offsetof(TABLE, ENTRY) == ::rocprofiler::common::abi::compute_table_offset(NUM), \
"ABI break for " #TABLE "." #ENTRY \
". Only add new function pointers to end of struct and do not rearrange them");
#else
# define INTERNAL_CI_ROCP_SDK_ENFORCE_ABI_VERSIONING(TABLE, NUM)
# define INTERNAL_CI_ROCP_SDK_ENFORCE_ABI(TABLE, ENTRY, NUM)
#endif
@@ -0,0 +1,11 @@
#
# add container sources and headers to common library target
#
set(containers_headers
ring_buffer.hpp c_array.hpp operators.hpp record_header_buffer.hpp ring_buffer.hpp
small_vector.hpp stable_vector.hpp static_vector.hpp)
set(containers_sources ring_buffer.cpp record_header_buffer.cpp ring_buffer.cpp
small_vector.cpp)
target_sources(rocprofiler-sdk-common-library PRIVATE ${containers_sources}
${containers_headers})
@@ -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.
#pragma once
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace rocprofiler
{
namespace common
{
namespace container
{
template <typename Tp>
struct c_array
{
// Construct an array wrapper from a base pointer and array size
c_array(Tp* _base, size_t _size)
: m_base{_base}
, m_size{_size}
{}
~c_array() = default;
c_array(const c_array&) = default;
c_array& operator=(const c_array&) = default;
c_array& operator=(c_array&&) noexcept = default;
// Get the size of the wrapped array
size_t size() const { return m_size; }
// Access an element by index
Tp& operator[](size_t i) { return m_base[i]; }
// Access an element by index
const Tp& operator[](size_t i) const { return m_base[i]; }
// Access an element by index with bounds check
Tp& at(size_t i)
{
if(i < m_size) return m_base[i];
throw std::out_of_range(std::string{typeid(*this).name()} + std::to_string(i) +
" exceeds size " + std::to_string(m_size));
}
// Access an element by index with bounds check
const Tp& at(size_t i) const
{
if(i < m_size) return m_base[i];
throw std::out_of_range(std::string{typeid(*this).name()} + std::to_string(i) +
" exceeds size " + std::to_string(m_size));
}
// Get a slice of this array, from a start index (inclusive) to end index (exclusive)
c_array<Tp> slice(size_t start, size_t end) { return c_array<Tp>(&m_base[start], end - start); }
void pop_front()
{
++m_base;
--m_size;
}
void pop_back() { --m_size; }
operator Tp*() const { return m_base; }
// Iterator class for convenient range-based for loop support
template <typename Up>
struct iterator
{
// Start the iterator at a given pointer
explicit iterator(Tp* p)
: m_ptr{p}
{}
// Advance to the next element
void operator++() { ++m_ptr; }
void operator++(int) { m_ptr++; }
// Get the current element
Up& operator*() const { return *m_ptr; }
// Compare iterators
bool operator==(const iterator& rhs) const { return m_ptr == rhs.m_ptr; }
bool operator!=(const iterator& rhs) const { return m_ptr != rhs.m_ptr; }
private:
Tp* m_ptr = nullptr;
};
// Get an iterator positioned at the beginning of the wrapped array
iterator<Tp> begin() { return iterator<Tp>{m_base}; }
iterator<const Tp> begin() const { return iterator<const Tp>{m_base}; }
// Get an iterator positioned at the end of the wrapped array
iterator<Tp> end() { return iterator<Tp>{&m_base[m_size]}; }
iterator<const Tp> end() const { return iterator<const Tp>{&m_base[m_size]}; }
private:
Tp* m_base = nullptr;
size_t m_size = 0;
};
// Function for automatic template argument deduction
template <typename Tp>
c_array<Tp>
wrap_c_array(Tp* base, size_t size)
{
return c_array<Tp>(base, size);
}
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,239 @@
// 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 <iterator>
#include <type_traits>
#define ROCPROFILER_IMPORT_TEMPLATE2(template_name)
#define ROCPROFILER_IMPORT_TEMPLATE1(template_name)
// Import a 2-type-argument operator template into boost (if necessary) and
// provide a specialization of 'is_chained_base<>' for it.
#define ROCPROFILER_OPERATOR_TEMPLATE2(template_name2) \
ROCPROFILER_IMPORT_TEMPLATE2(template_name2) \
template <typename T, typename U, typename B> \
struct is_chained_base<::rocprofiler::common::container::template_name2<T, U, B>> \
{ \
using value = ::rocprofiler::common::container::true_t; \
};
// Import a 1-type-argument operator template into boost (if necessary) and
// provide a specialization of 'is_chained_base<>' for it.
#define ROCPROFILER_OPERATOR_TEMPLATE1(template_name1) \
ROCPROFILER_IMPORT_TEMPLATE1(template_name1) \
template <typename T, typename B> \
struct is_chained_base<::rocprofiler::common::container::template_name1<T, B>> \
{ \
using value = ::rocprofiler::common::container::true_t; \
};
#define ROCPROFILER_OPERATOR_TEMPLATE(template_name) \
template <typename T, \
typename U = T, \
typename B = empty_base<T>, \
typename O = typename is_chained_base<U>::value> \
struct template_name; \
\
template <typename T, typename U, typename B> \
struct template_name<T, U, B, false_t> : template_name##2 < T \
, U \
, B > \
{}; \
\
template <typename T, typename U> \
struct template_name<T, U, empty_base<T>, true_t> : template_name##1 < T \
, U > \
{}; \
\
template <typename T, typename B> \
struct template_name<T, T, B, false_t> : template_name##1 < T \
, B > \
{}; \
\
template <typename T, typename U, typename B, typename O> \
struct is_chained_base<template_name<T, U, B, O>> \
{ \
using value = ::rocprofiler::common::container::true_t; \
}; \
\
ROCPROFILER_OPERATOR_TEMPLATE2(template_name##2) \
ROCPROFILER_OPERATOR_TEMPLATE1(template_name##1)
#define ROCPROFILER_BINARY_OPERATOR_COMMUTATIVE(NAME, OP) \
template <typename T, typename U, typename B = empty_base<T>> \
struct NAME##2 : B{friend T operator OP(T lhs, const U& rhs){return lhs OP## = rhs; \
} \
friend T operator OP(const U& lhs, T rhs) { return rhs OP## = lhs; } \
} \
; \
\
template <typename T, typename B = empty_base<T>> \
struct NAME##1 : B{friend T operator OP(T lhs, const T& rhs){return lhs OP## = rhs; \
} \
} \
;
#define ROCPROFILER_BINARY_OPERATOR_NON_COMMUTATIVE(NAME, OP) \
template <typename T, typename U, typename B = empty_base<T>> \
struct NAME##2 : B{friend T operator OP(T lhs, const U& rhs){return lhs OP## = rhs; \
} \
} \
;
namespace rocprofiler
{
namespace common
{
namespace container
{
struct true_t
{};
struct false_t
{};
template <typename T>
class empty_base
{};
template <typename T>
struct is_chained_base
{
using value = true_t;
};
ROCPROFILER_BINARY_OPERATOR_COMMUTATIVE(addable, +)
ROCPROFILER_BINARY_OPERATOR_NON_COMMUTATIVE(subtractable, -)
ROCPROFILER_OPERATOR_TEMPLATE(addable)
template <typename T, typename B = empty_base<T>>
struct incrementable : B
{
friend T operator++(T& x, int)
{
incrementable_type nrv(x);
++x;
return nrv;
}
private: // The use of this typedef works around a Borland bug
typedef T incrementable_type;
};
template <typename T, typename B = empty_base<T>>
struct decrementable : B
{
friend T operator--(T& x, int)
{
decrementable_type nrv(x);
--x;
return nrv;
}
private: // The use of this typedef works around a Borland bug
typedef T decrementable_type;
};
template <typename T, typename P, typename B = empty_base<T>>
struct dereferenceable : B
{
P operator->() const { return ::std::addressof(*static_cast<const T&>(*this)); }
};
template <typename T, typename N, typename R, typename B = empty_base<T>>
struct indexable : B
{
R operator[](N n) const { return *(static_cast<const T&>(*this) + n); }
};
template <typename T, typename B = empty_base<T>>
struct equality_comparable1 : B
{
friend bool operator!=(const T& x, const T& y) { return !static_cast<bool>(x == y); }
};
template <typename T, typename P, typename B = empty_base<T>>
struct input_iteratable : equality_comparable1<T, incrementable<T, dereferenceable<T, P, B>>>
{};
template <typename T, typename B = empty_base<T>>
struct output_iteratable : incrementable<T, B>
{};
template <typename T, typename P, typename B = empty_base<T>>
struct forward_iteratable : input_iteratable<T, P, B>
{};
template <typename T, typename P, typename B = empty_base<T>>
struct bidirectional_iteratable : forward_iteratable<T, P, decrementable<T, B>>
{};
// template <typename T, typename U, typename B = empty_base<T>>
// struct subtractable2;
template <typename T, typename U, typename B = empty_base<T>>
struct additive2 : addable2<T, U, subtractable2<T, U, B>>
{};
template <typename T, typename B = empty_base<T>>
struct less_than_comparable1 : B
{
friend bool operator>(const T& x, const T& y) { return y < x; }
friend bool operator<=(const T& x, const T& y) { return !static_cast<bool>(y < x); }
friend bool operator>=(const T& x, const T& y) { return !static_cast<bool>(x < y); }
};
// To avoid repeated derivation from equality_comparable,
// which is an indirect base typename of bidirectional_iterable,
// random_access_iteratable must not be derived from totally_ordered1
// but from less_than_comparable1 only. (Helmut Zeisel, 02-Dec-2001)
template <typename T, typename P, typename D, typename R, typename B = empty_base<T>>
struct random_access_iteratable
: bidirectional_iteratable<T, P, less_than_comparable1<T, additive2<T, D, indexable<T, D, R, B>>>>
{};
template <typename CategoryT,
typename Tp,
typename DistanceT = std::ptrdiff_t,
typename PointerT = Tp*,
typename ReferenceT = Tp&>
struct iterator_helper
{
using iterator_category = CategoryT;
using value_type = Tp;
using difference_type = DistanceT;
using pointer = PointerT;
using reference = ReferenceT;
};
template <typename T, typename V, typename D = std::ptrdiff_t, typename P = V*, typename R = V&>
struct random_access_iterator_helper
: random_access_iteratable<T, P, D, R, iterator_helper<std::random_access_iterator_tag, V, D, P, R>>
{
friend D requires_difference_operator(const T& x, const T& y) { return x - y; }
}; // random_access_iterator_helper
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,173 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/container/record_header_buffer.hpp"
#include <rocprofiler-sdk/fwd.h>
#include <algorithm>
#include <atomic>
#include <new>
namespace rocprofiler::common::container
{
namespace
{
// record_header_buffer RAII locker
struct rhb_raii_lock
{
explicit rhb_raii_lock(record_header_buffer& _rhb)
: m_rhb{_rhb}
{
m_rhb.lock();
}
~rhb_raii_lock() { m_rhb.unlock(); }
record_header_buffer& m_rhb;
};
} // namespace
record_header_buffer::record_header_buffer(size_t num_bytes) { allocate(num_bytes); }
record_header_buffer::record_header_buffer(record_header_buffer&& _rhs) noexcept
{
this->operator=(std::move(_rhs));
}
record_header_buffer&
record_header_buffer::operator=(record_header_buffer&& _rhs) noexcept
{
if(this != &_rhs)
{
auto _lk = rhb_raii_lock{_rhs};
m_index = _rhs.m_index.load(std::memory_order_acquire);
m_buffer = std::move(_rhs.m_buffer);
m_headers = std::move(_rhs.m_headers);
_rhs.reset();
}
return *this;
}
bool
record_header_buffer::allocate(size_t num_bytes)
{
if(m_buffer.is_initialized()) return false;
auto _lk = rhb_raii_lock{*this};
m_buffer.init(num_bytes);
rocprofiler_record_header_t record = {};
record.hash = 0;
record.payload = nullptr;
m_headers.resize(m_buffer.capacity(), record);
return true;
}
size_t
record_header_buffer::get_num_record_headers()
{
auto _lk = rhb_raii_lock{*this};
auto _size = m_index.load(std::memory_order_acquire);
size_t _ret = 0;
for(size_t i = 0; i < _size; ++i)
{
if(auto& itr = m_headers.at(i); itr.hash > 0 && itr.payload != nullptr) ++_ret;
}
return _ret;
}
size_t
record_header_buffer::clear()
{
auto _lk = rhb_raii_lock{*this};
auto _n = m_index.load(std::memory_order_acquire);
{
auto _sz = m_buffer.capacity();
if(!m_buffer.clear(std::nothrow_t{})) return 0;
std::for_each(m_headers.begin(), m_headers.end(), [](auto& itr) {
rocprofiler_record_header_t record = {};
record.hash = 0;
record.payload = nullptr;
itr = record;
});
rocprofiler_record_header_t record = {};
record.hash = 0;
record.payload = nullptr;
m_headers.resize(_sz, record);
m_index.store(0, std::memory_order_release);
}
return _n;
}
size_t
record_header_buffer::reset()
{
auto _lk = rhb_raii_lock{*this};
auto _n = m_index.load(std::memory_order_acquire);
m_buffer.destroy();
m_buffer.clear();
m_headers.clear();
m_index.store(0, std::memory_order_release);
return _n;
}
void
record_header_buffer::save(std::fstream& _fs)
{
auto _lk = rhb_raii_lock{*this};
auto _idx = m_index.load(std::memory_order_acquire);
auto _sz = m_headers.size();
_fs.write(reinterpret_cast<char*>(&_idx), sizeof(_idx));
_fs.write(reinterpret_cast<char*>(&_sz), sizeof(_sz));
_fs.write(reinterpret_cast<char*>(m_headers.data()), sizeof(rocprofiler_record_header_t) * _sz);
m_buffer.save(_fs);
}
void
record_header_buffer::load(std::fstream& _fs)
{
auto _lk = rhb_raii_lock{*this};
{
auto _idx = size_t{0};
_fs.read(reinterpret_cast<char*>(&_idx), sizeof(_idx));
m_index.store(_idx, std::memory_order_release);
}
{
auto _sz = size_t{0};
_fs.read(reinterpret_cast<char*>(&_sz), sizeof(_sz));
m_headers.resize(_sz);
_fs.read(reinterpret_cast<char*>(m_headers.data()),
sizeof(rocprofiler_record_header_t) * _sz);
}
m_buffer.load(_fs);
}
} // namespace rocprofiler::common::container
@@ -0,0 +1,362 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/container/ring_buffer.hpp"
#include "lib/common/scope_destructor.hpp"
#include <rocprofiler-sdk/fwd.h>
#include <atomic>
#include <functional>
#include <limits>
#include <mutex>
#include <shared_mutex>
#include <type_traits>
#include <vector>
namespace rocprofiler
{
namespace common
{
namespace container
{
/// @brief this struct stores all the record information in an ring_buffer.
/// It is thread-safe to have multiple threads emplace records into the buffer.
struct record_header_buffer
{
using base_buffer_t = base::ring_buffer;
using record_vec_t = std::vector<rocprofiler_record_header_t>;
using record_ptr_vec_t = std::vector<rocprofiler_record_header_t*>;
record_header_buffer() = default;
explicit record_header_buffer(size_t nbytes);
~record_header_buffer() = default;
record_header_buffer(const record_header_buffer&) = delete;
record_header_buffer(record_header_buffer&&) noexcept;
record_header_buffer& operator=(const record_header_buffer&) = delete;
record_header_buffer& operator =(record_header_buffer&&) noexcept;
// allocate the buffer if it is not already allocated. Will return false if buffer is already
// allocated
bool allocate(size_t nbytes);
// return whether the buffer has been allocated
bool is_allocated() const;
/// place an object in the buffer using its typeid hash code
template <typename Tp>
bool emplace(Tp&);
/// place an object in the buffer using the specified numerical identifier
template <typename Tp>
bool emplace(uint64_t, Tp&);
/// place an object in the buffer using the specified numerical identifier
template <typename Tp>
bool emplace(uint32_t, uint32_t, Tp&);
/// this function will return the number of record headers
size_t get_num_record_headers();
/// this function will invoke functor with a vector of pointers to the record headers.
/// if ClearRecordsV is true, the container will be cleared after invoking the functor
template <typename ClearRecordsT, typename FuncT, typename... Args>
size_t process_record_headers(ClearRecordsT, FuncT&& functor, Args&&... args);
/// record_header_buffer is a multiple writer, single reader data structure so
/// this function prevents writing via emplace
void lock();
/// potentially re-enable emplace if no other readers have locked
void unlock();
/// record_header_buffer is a multiple writer, single reader data structure so
/// this function prevents reading while emplacing
void read_lock();
/// potentially allow reading after writing via emplace
void read_unlock();
/// check if writing is available
bool is_locked() const;
/// restores to original empty state
size_t clear();
/// binary save to file
void save(std::fstream& _fs);
/// binary load from file
void load(std::fstream& _fs);
/// full deallocation
size_t reset();
/// the number of header entries
auto size() const;
/// the number of bytes in the buffer
auto capacity() const;
/// the number of used bytes in the buffer
auto count() const;
/// the number of free bytes in the buffer
auto free() const;
/// true if no bytes are used in the buffer
auto is_empty() const;
/// true if all the bytes are used in the buffer or there is no buffer allocation
auto is_full() const;
private:
/// this is an explicit write lock that does not guard against deadlocking like lock()
void write_lock();
/// this is an explicit write unlock that does not guard against deadlocking like unlock()
void write_unlock();
private:
std::atomic<int64_t> m_requested = {0};
std::atomic<int64_t> m_locked = {0};
std::atomic<size_t> m_index = {};
std::shared_mutex m_shared = {};
base_buffer_t m_buffer = {};
record_vec_t m_headers = {};
};
inline bool
record_header_buffer::is_locked() const
{
return m_locked.load(std::memory_order_acquire) > 0;
}
inline void
record_header_buffer::lock()
{
auto n = m_locked.fetch_add(1, std::memory_order_release);
if(n == 0) write_lock();
}
inline void
record_header_buffer::unlock()
{
auto n = m_locked.fetch_sub(1, std::memory_order_release);
if(n <= 1) write_unlock();
}
inline void
record_header_buffer::read_lock()
{
m_shared.lock_shared();
}
inline void
record_header_buffer::read_unlock()
{
m_shared.unlock_shared();
}
inline void
record_header_buffer::write_lock()
{
m_shared.lock();
}
inline void
record_header_buffer::write_unlock()
{
m_shared.unlock();
}
inline bool
record_header_buffer::is_allocated() const
{
return m_buffer.is_initialized();
}
inline auto
record_header_buffer::size() const
{
return m_index.load(std::memory_order_acquire);
}
inline auto
record_header_buffer::capacity() const
{
return std::min<size_t>(m_headers.size(), m_buffer.capacity());
}
inline auto
record_header_buffer::count() const
{
return m_buffer.count();
}
inline auto
record_header_buffer::free() const
{
return m_buffer.free();
}
inline auto
record_header_buffer::is_empty() const
{
return (m_buffer.is_empty() && m_requested.load() == 0) || m_headers.empty();
}
inline auto
record_header_buffer::is_full() const
{
return m_buffer.is_full() || size() == m_headers.size();
}
template <typename Tp>
bool
record_header_buffer::emplace(uint64_t _hash, Tp& _v)
{
if(m_headers.empty()) return false;
constexpr auto request_size = sizeof(Tp);
constexpr auto align_size = alignof(Tp);
// notify there was a request
m_requested.fetch_add(1);
// in theory, we shouldn't need to lock here but the thread sanitizer says there is a race.
// the lock will be short-lived so hopefully, it will scale fine
write_lock();
auto* _addr = m_buffer.request(request_size, align_size, false);
write_unlock();
read_lock();
if(_addr)
{
// if there is space in the buffer, atomically get an index
// for where the header record should be placed.
// NOTE: m_headers was resized to be large enough to accomodate
// sizeof(Tp) == 1 for every entry in buffer
auto idx = m_index.fetch_add(1, std::memory_order_release);
// placement new
new(_addr) Tp{_v};
auto record = rocprofiler_record_header_t{};
record.hash = _hash;
record.payload = _addr;
m_headers.at(idx) = record;
}
read_unlock();
// remove notification of request
m_requested.fetch_sub(1);
return (_addr != nullptr);
}
template <typename Tp>
bool
record_header_buffer::emplace(uint32_t _category, uint32_t _kind, Tp& _v)
{
if(m_headers.empty()) return false;
constexpr auto request_size = sizeof(Tp);
constexpr auto align_size = alignof(Tp);
// notify there was a request
m_requested.fetch_add(1);
// in theory, we shouldn't need to lock here but the thread sanitizer says there is a race.
// the lock will be short-lived so hopefully, it will scale fine
write_lock();
auto* _addr = m_buffer.request(request_size, align_size, false);
write_unlock();
read_lock();
if(_addr)
{
// if there is space in the buffer, atomically get an index
// for where the header record should be placed.
// NOTE: m_headers was resized to be large enough to accomodate
// sizeof(Tp) == 1 for every entry in buffer
auto idx = m_index.fetch_add(1, std::memory_order_release);
// placement new
new(_addr) Tp{_v};
auto record = rocprofiler_record_header_t{};
record.category = _category;
record.kind = _kind;
record.payload = _addr;
m_headers.at(idx) = record;
}
read_unlock();
// remove notification of request
m_requested.fetch_sub(1);
return (_addr != nullptr);
}
template <typename Tp>
bool
record_header_buffer::emplace(Tp& _v)
{
// if enumerations are not used, use the typeid hash code
return emplace(typeid(Tp).hash_code(), _v);
}
template <typename ClearRecordsT, typename FuncT, typename... Args>
size_t
record_header_buffer::process_record_headers(ClearRecordsT, FuncT&& _functor, Args&&... _args)
{
// RAII for lock/unlock
auto _lk = scope_destructor{[&]() { unlock(); }, [&]() { lock(); }};
auto _n = m_index.load(std::memory_order_acquire);
auto _records = record_ptr_vec_t{};
_records.reserve(_n);
for(size_t i = 0; i < _n; ++i)
{
if(auto& itr = m_headers.at(i); itr.hash > 0 && itr.payload != nullptr)
_records.emplace_back(&itr);
}
// get number of records before vector is moved
auto _num_records = _records.size();
// invoke the callback
std::forward<FuncT>(_functor)(std::move(_records), std::forward<Args>(_args)...);
// clear the container
if constexpr(ClearRecordsT::value) clear();
return _num_records;
}
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,279 @@
// MIT License
//
// Copyright (c) 2023-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 "ring_buffer.hpp"
#include "lib/common/environment.hpp"
#include "lib/common/units.hpp"
#include <fmt/format.h>
#include <sys/mman.h>
#include <atomic>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <new>
namespace rocprofiler
{
namespace common
{
namespace container
{
namespace base
{
ring_buffer::~ring_buffer() { destroy(); }
ring_buffer::ring_buffer(ring_buffer&& rhs) noexcept
: m_init{rhs.m_init}
, m_ptr{rhs.m_ptr}
, m_size{rhs.m_size}
, m_read_count{rhs.m_read_count.load()}
, m_write_count{rhs.m_write_count.load()}
{
rhs.reset();
}
ring_buffer&
ring_buffer::operator=(ring_buffer&& rhs) noexcept
{
if(this == &rhs) return *this;
destroy();
m_init = rhs.m_init;
m_ptr = rhs.m_ptr;
m_size = rhs.m_size;
m_read_count = rhs.m_read_count.load();
m_write_count = rhs.m_write_count.load();
rhs.reset();
return *this;
}
void
ring_buffer::init(size_t _size)
{
ROCP_FATAL_IF(m_init)
<< "rocprofiler::common::container::base::ring_buffer::init(size_t) :: already initialized";
m_init = true;
// Round up to multiple of page size.
_size += units::get_page_size() - ((_size % units::get_page_size() > 0)
? (_size % units::get_page_size())
: units::get_page_size());
if((_size % units::get_page_size()) > 0)
{
std::ostringstream _oss{};
ROCP_FATAL << fmt::format("Error! size is not a multiple of page size: {} % {} = {}",
_size,
units::get_page_size(),
(_size % units::get_page_size()));
}
m_size = _size;
m_read_count = 0;
m_write_count = 0;
// Map twice the buffer size.
if((m_ptr =
mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)) ==
MAP_FAILED)
{
destroy();
auto _err = errno;
ROCP_FATAL << fmt::format("mmap failed with errno {} :: {}", _err, strerror(_err));
}
}
void
ring_buffer::destroy()
{
if(m_ptr && m_init)
{
// Unmap the mapped virtual memmory.
auto ret = munmap(m_ptr, m_size);
if(ret != 0) perror("ring_buffer: munmap failed");
}
m_init = false;
m_size = 0;
m_read_count = 0;
m_write_count = 0;
m_ptr = nullptr;
}
std::string
ring_buffer::as_string() const
{
std::ostringstream ss{};
ss << std::boolalpha << "is_initialized: " << is_initialized() << ", capacity: " << capacity()
<< ", count: " << count() << ", free: " << free() << ", is_empty: " << is_empty()
<< ", is_full: " << is_full() << ", pointer: " << m_ptr << ", read count: " << m_read_count
<< ", write count: " << m_write_count;
return ss.str();
}
//
void*
ring_buffer::request(size_t _length, size_t _align, bool _wrap)
{
if(m_ptr == nullptr || m_size == 0) return nullptr;
if(is_full()) return (_wrap) ? retrieve(_length, _align) : nullptr;
LOG_IF(FATAL, _align == 0) << "alignment must be non-zero";
// if write count is at the tail of buffer, bump to the end of buffer
size_t _write_count = 0;
size_t _offset = 0;
size_t _write_pos = 0;
do
{
// Make sure we don't put in more than there's room for, by writing no
// more than there is free.
if(_length > free()) return nullptr;
_offset = 0;
_write_count = m_write_count.load(std::memory_order_acquire);
auto _modulo = m_size - (_write_count % m_size);
if(_modulo < _length) _offset = _modulo;
auto _align_modulo = (_write_count % _align);
auto _align_offset = (_align_modulo > 0) ? (_align - _align_modulo) : 0;
_write_pos = _write_count + _align_offset;
} while(!m_write_count.compare_exchange_strong(
_write_count, _write_pos + _length + _offset, std::memory_order_seq_cst));
// pointer in buffer
void* _out = write_ptr(_write_pos);
return _out;
}
//
void*
ring_buffer::retrieve(size_t _length, size_t _align) const
{
if(m_ptr == nullptr || m_size == 0) return nullptr;
// Make sure we don't put in more than there's room for, by writing no
// more than there is free.
// if read count is at the tail of buffer, bump to the end of buffer
size_t _read_count = 0;
size_t _offset = 0;
size_t _read_pos = 0;
do
{
if(_length > count()) return nullptr;
_offset = 0;
_read_count = m_read_count.load(std::memory_order_acquire);
auto _modulo = m_size - (_read_count % m_size);
if(_modulo < _length) _offset = _modulo;
auto _align_modulo = (_read_count % _align);
auto _align_offset = (_align_modulo > 0) ? (_align - _align_modulo) : 0;
_read_pos = _read_count + _align_offset;
} while(!m_read_count.compare_exchange_strong(
_read_count, _read_pos + _length + _offset, std::memory_order_seq_cst));
// pointer in buffer
void* _out = read_ptr(_read_pos);
return _out;
}
//
void
ring_buffer::reset()
{
m_init = false;
m_size = 0;
m_ptr = nullptr;
m_read_count.store(0);
m_write_count.store(0);
}
//
void
ring_buffer::save(std::fstream& _fs)
{
auto _read_count = m_read_count.load();
auto _write_count = m_write_count.load();
_fs.write(reinterpret_cast<char*>(&m_size), sizeof(m_size));
_fs.write(reinterpret_cast<char*>(&_read_count), sizeof(_read_count));
_fs.write(reinterpret_cast<char*>(&_write_count), sizeof(_write_count));
_fs.write(reinterpret_cast<char*>(m_ptr), m_size * sizeof(char));
}
//
void
ring_buffer::load(std::fstream& _fs)
{
destroy();
size_t _read_count = 0;
size_t _write_count = 0;
size_t _size = 0;
_fs.read(reinterpret_cast<char*>(&_size), sizeof(_size));
init(_size);
if(!m_ptr) throw std::bad_alloc{};
_fs.read(reinterpret_cast<char*>(&_read_count), sizeof(_read_count));
_fs.read(reinterpret_cast<char*>(&_write_count), sizeof(_write_count));
_fs.read(reinterpret_cast<char*>(m_ptr), m_size * sizeof(char));
m_read_count.store(_read_count, std::memory_order_release);
m_write_count.store(_write_count, std::memory_order_release);
}
bool
ring_buffer::can_clear() const
{
auto _read_count = m_read_count.load(std::memory_order_acquire);
return (_read_count == 0);
}
bool
ring_buffer::clear()
{
ROCP_CI_LOG_IF(WARNING, !can_clear())
<< "ring_buffer does not permit invoking clear() member function when the read pointer is "
"non-zero because this introduces thread-safety issues";
m_write_count.store(0, std::memory_order_release);
return true;
}
bool ring_buffer::clear(std::nothrow_t)
{
if(!can_clear()) return false;
m_write_count.store(0, std::memory_order_release);
return true;
}
} // namespace base
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,451 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/units.hpp"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <new>
#include <sstream>
#include <utility>
namespace rocprofiler
{
namespace common
{
namespace container
{
template <typename Tp>
struct ring_buffer;
//
namespace base
{
/// \struct rocprofiler::common::container::base::ring_buffer
/// \brief Ring buffer implementation, with support for mmap as backend (Linux only).
struct ring_buffer
{
template <typename Tp>
friend struct container::ring_buffer;
ring_buffer() = default;
explicit ring_buffer(size_t _size) { init(_size); }
~ring_buffer();
ring_buffer(ring_buffer&&) noexcept;
ring_buffer& operator=(ring_buffer&&) noexcept;
/// Returns whether the buffer has been allocated
bool is_initialized() const { return m_init; }
/// Get the total number of bytes supported
size_t capacity() const { return m_size; }
/// Creates new ring buffer.
void init(size_t size);
/// Destroy ring buffer.
void destroy();
/// Request a pointer for writing at least \param n bytes. If the current write pointer is not
/// perfectly divisible by \param align (i.e. if write_addr % align != 0), the returned address
/// will be shifted to an address that is a multiple of that alignment to prevent undefined
/// behavior.
void* request(size_t n, size_t align, bool wrap = true);
/// Retrieve a pointer for reading at least \param n bytes. If the current read pointer is not
/// perfectly divisible by \param align (i.e. if read_addr % align != 0), the returned address
/// will be shifted to an address that is a multiple of that alignment to prevent undefined
/// behavior.
void* retrieve(size_t n, size_t align) const;
/// Write class-type data to buffer (uses placement new).
template <typename Tp>
std::pair<size_t, Tp*> write(Tp* in, std::enable_if_t<std::is_class<Tp>::value, int> = 0);
/// Write non-class-type data to buffer (uses memcpy).
template <typename Tp>
std::pair<size_t, Tp*> write(Tp* in, std::enable_if_t<!std::is_class<Tp>::value, int> = 0);
/// Request a pointer to an allocation. This is similar to a "write" except the
/// memory is uninitialized. Typically used by allocators. If Tp is a class type,
/// be sure to use a placement new instead of a memcpy.
template <typename Tp>
Tp* request(bool wrap = true);
/// Read class-type data from buffer (uses placement new).
template <typename Tp>
std::pair<size_t, Tp*> read(Tp* _dest,
std::enable_if_t<std::is_class<Tp>::value, int> = 0) const;
/// Read non-class-type data from buffer (uses memcpy).
template <typename Tp>
std::pair<size_t, Tp*> read(Tp* _dest,
std::enable_if_t<!std::is_class<Tp>::value, int> = 0) const;
/// Retrieve a pointer to the head allocation (read).
template <typename Tp>
Tp* retrieve() const;
/// Returns number of bytes currently held by the buffer.
size_t count() const { return (m_write_count - m_read_count); }
/// Returns how many bytes are availiable in the buffer.
size_t free() const { return (m_size - count()); }
/// Returns if the buffer is empty.
bool is_empty() const { return (count() == 0); }
/// Returns if the buffer is full.
bool is_full() const { return (count() == m_size); }
/// Display info about buffer
std::string as_string() const;
/// save the entire buffer to a filestream
void save(std::fstream& _fs);
/// load the entire buffer from a filestream
void load(std::fstream& _fs);
/// query whether the read pointer is zero and thus clearing is supported
bool can_clear() const;
/// reset the read and write pointer to their initial values.
/// effectively, wiping and existing memory. Please note,
/// this should be used with care in a double buffer system
/// where you are not actually using the read pointer.
/// If the read pointer is non-zero, this will throw an exception
bool clear();
/// reset the read and write pointer to their initial values.
/// effectively, wiping and existing memory. Please note,
/// this should be used with care in a double buffer system
/// where you are not actually using the read pointer.
bool clear(std::nothrow_t);
private:
/// Returns the current write pointer.
void* write_ptr(size_t _write_count) const
{
return static_cast<char*>(m_ptr) + (_write_count % m_size);
}
/// Returns the current read pointer.
void* read_ptr(size_t _read_count) const
{
return static_cast<char*>(m_ptr) + (_read_count % m_size);
}
void reset();
private:
bool m_init = false;
void* m_ptr = nullptr;
size_t m_size = 0;
mutable std::atomic<size_t> m_read_count = 0;
std::atomic<size_t> m_write_count = 0;
};
//
template <typename Tp>
std::pair<size_t, Tp*>
ring_buffer::write(Tp* in, std::enable_if_t<std::is_class<Tp>::value, int>)
{
if(in == nullptr || m_ptr == nullptr) return {0, nullptr};
constexpr auto _length = sizeof(Tp);
constexpr auto _align = alignof(Tp);
void* _out_p = request(_length, _align, true);
if(_out_p == nullptr) return {0, nullptr};
// Copy in.
new(_out_p) Tp{std::move(*in)};
// pointer in buffer
Tp* _out = reinterpret_cast<Tp*>(_out_p);
return {_length, _out};
}
//
template <typename Tp>
std::pair<size_t, Tp*>
ring_buffer::write(Tp* in, std::enable_if_t<!std::is_class<Tp>::value, int>)
{
if(in == nullptr || m_ptr == nullptr) return {0, nullptr};
constexpr auto _length = sizeof(Tp);
constexpr auto _align = alignof(Tp);
void* _out_p = request(_length, _align, true);
if(_out_p == nullptr) return {0, nullptr};
// Copy in.
memcpy(_out_p, in, _length);
// pointer in buffer
Tp* _out = reinterpret_cast<Tp*>(_out_p);
return {_length, _out};
}
//
template <typename Tp>
Tp*
ring_buffer::request(bool wrap)
{
if(m_ptr == nullptr) return nullptr;
return reinterpret_cast<Tp*>(request(sizeof(Tp), alignof(Tp), wrap));
}
//
template <typename Tp>
std::pair<size_t, Tp*>
ring_buffer::read(Tp* _dest, std::enable_if_t<std::is_class<Tp>::value, int>) const
{
if(is_empty() || _dest == nullptr) return {0, nullptr};
constexpr auto _length = sizeof(Tp);
constexpr auto _align = alignof(Tp);
void* _out_p = retrieve(_length, _align);
if(_out_p == nullptr) return {0, nullptr};
// pointer in buffer
Tp* in = reinterpret_cast<Tp*>(_out_p);
// Copy out for BYTE, nothing magic here.
*_dest = *in;
return {_length, in};
}
//
template <typename Tp>
std::pair<size_t, Tp*>
ring_buffer::read(Tp* _dest, std::enable_if_t<!std::is_class<Tp>::value, int>) const
{
if(is_empty() || _dest == nullptr) return {0, nullptr};
constexpr auto _length = sizeof(Tp);
constexpr auto _align = alignof(Tp);
void* _out_p = retrieve(_length, _align);
if(_out_p == nullptr) return {0, nullptr};
// pointer in buffer
Tp* in = reinterpret_cast<Tp*>(_out_p);
using Up = typename std::remove_const<Tp>::type;
// Copy out for BYTE, nothing magic here.
Up* _out = const_cast<Up*>(_dest);
memcpy(_out, in, _length);
return {_length, in};
}
//
template <typename Tp>
Tp*
ring_buffer::retrieve() const
{
if(m_ptr == nullptr) return nullptr;
return reinterpret_cast<Tp*>(retrieve(sizeof(Tp), alignof(Tp)));
}
//
} // namespace base
//
/// \struct rocprofiler::common::container::ring_buffer
/// \brief Ring buffer wrapper around \ref rocprofiler::common::container::base::ring_buffer for
/// data of type Tp. If the data object size is larger than the page size (typically 4KB), behavior
/// is undefined. During initialization, one requests a minimum number of objects and the buffer
/// will support that number of object + the remainder of the page, e.g. if a page is 1000 bytes,
/// the object is 1 byte, and the buffer is requested to support 1500 objects, then an allocation
/// supporting 2000 objects (i.e. 2 pages) will be created.
template <typename Tp>
struct ring_buffer : private base::ring_buffer
{
using base_type = base::ring_buffer;
using value_type = Tp;
static size_t get_items_per_page();
ring_buffer() = default;
~ring_buffer() = default;
explicit ring_buffer(size_t _size)
: base_type{_size * aligned_data_size()}
{}
ring_buffer(const ring_buffer&);
ring_buffer(ring_buffer&&) noexcept = default;
ring_buffer& operator=(const ring_buffer&);
ring_buffer& operator=(ring_buffer&&) noexcept = default;
/// Returns whether the buffer has been allocated
bool is_initialized() const { return base_type::is_initialized(); }
/// Get the total number of Tp instances supported
size_t capacity() const { return (base_type::capacity()) / aligned_data_size(); }
/// Creates new ring buffer.
void init(size_t _size) { base_type::init(_size * aligned_data_size()); }
/// Destroy ring buffer.
void destroy() { base_type::destroy(); }
/// Size of the data type
static constexpr size_t data_size() { return sizeof(Tp); }
/// Size of the data type + padding
static constexpr size_t aligned_data_size();
/// Write data to buffer. Return pointer to location of write
Tp* write(Tp* in) { return base_type::write<Tp>(in).second; }
/// Read data from buffer. Return pointer to location of read
Tp* read(Tp* _dest) const { return base_type::read<Tp>(_dest).second; }
/// Get an uninitialized address at tail of buffer.
Tp* request(bool wrap = true) { return base_type::request<Tp>(wrap); }
/// Read data from head of buffer.
Tp* retrieve() { return base_type::retrieve<Tp>(); }
/// Returns number of Tp instances currently held by the buffer.
size_t count() const { return (base_type::count()) / aligned_data_size(); }
/// Returns how many Tp instances are availiable in the buffer.
size_t free() const { return (base_type::free()) / aligned_data_size(); }
/// Returns if the buffer is empty.
bool is_empty() const { return base_type::is_empty(); }
/// Returns if the buffer is full.
bool is_full() const { return (base_type::free() < aligned_data_size()); }
bool clear() { return base_type::clear(); }
template <typename... Args>
auto emplace(Args&&... args)
{
Tp _obj{std::forward<Args>(args)...};
return write(&_obj);
}
using base_type::load;
using base_type::save;
std::string as_string() const
{
std::ostringstream ss{};
size_t _w = std::log10(base_type::capacity()) + 1;
ss << std::boolalpha << std::right << "data size: " << std::setw(_w) << data_size()
<< "B, aligned data size: " << std::setw(_w) << aligned_data_size()
<< " B, is_initialized: " << std::setw(5) << is_initialized()
<< ", is_empty: " << std::setw(5) << is_empty() << ", is_full: " << std::setw(5)
<< is_full() << ", capacity: " << std::setw(_w) << capacity()
<< ", count: " << std::setw(_w) << count() << ", free: " << std::setw(_w) << free()
<< ", raw capacity: " << std::setw(_w) << base_type::capacity()
<< " B, raw count: " << std::setw(_w) << base_type::count()
<< " B, raw free: " << std::setw(_w) << base_type::free()
<< " B, pointer: " << std::setw(15) << base_type::m_ptr
<< ", raw read count: " << std::setw(_w) << base_type::m_read_count
<< ", raw write count: " << std::setw(_w) << base_type::m_write_count;
return ss.str();
}
friend std::ostream& operator<<(std::ostream& os, const ring_buffer& obj)
{
return os << obj.as_string();
}
};
//
template <typename Tp>
size_t
ring_buffer<Tp>::get_items_per_page()
{
return std::max<size_t>(units::get_page_size() / sizeof(Tp), 1);
}
//
template <typename Tp>
constexpr size_t
ring_buffer<Tp>::aligned_data_size()
{
constexpr auto _data_size = sizeof(Tp);
constexpr auto _data_align = alignof(Tp);
constexpr auto _align_modulo = _data_size % _data_align;
constexpr auto _result =
(_align_modulo == 0) ? _data_size : (_data_size + (_data_align - _align_modulo));
static_assert(_result >= _data_size && _result < (_data_size + _data_align),
"should neither be < sizeof(Tp) nor > sizeof(Tp) + alignof(Tp)");
return _result;
}
//
template <typename Tp>
ring_buffer<Tp>::ring_buffer(const ring_buffer<Tp>& rhs)
: base_type{rhs}
{
size_t _n = rhs.count();
char* _end = static_cast<char*>(rhs.m_ptr) + rhs.m_size;
for(size_t i = 0; i < _n; ++i)
{
char* _addr = static_cast<char*>(rhs.read_ptr(m_read_count)) + (i * sizeof(Tp));
if((_addr + sizeof(Tp)) > _end) _addr = static_cast<char*>(rhs.m_ptr);
Tp* _in = static_cast<Tp*>(static_cast<void*>(_addr));
write(_in);
}
}
//
template <typename Tp>
ring_buffer<Tp>&
ring_buffer<Tp>::operator=(const ring_buffer<Tp>& rhs)
{
if(this == &rhs) return *this;
base_type::operator=(rhs);
size_t _n = rhs.count();
char* _end = static_cast<char*>(rhs.m_ptr) + rhs.m_size;
for(size_t i = 0; i < _n; ++i)
{
char* _addr = static_cast<char*>(rhs.read_ptr(m_read_count)) + (i * sizeof(Tp));
if((_addr + sizeof(Tp)) > _end) _addr = static_cast<char*>(rhs.m_ptr);
Tp* _in = static_cast<Tp*>(static_cast<void*>(_addr));
write(_in);
}
return *this;
}
//
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,146 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/container/small_vector.hpp"
namespace rocprofiler
{
namespace common
{
namespace container
{
namespace
{
[[noreturn]] void
report_size_overflow(size_t min_size, size_t max_size);
[[noreturn]] void
report_at_maximum_capacity(size_t max_size);
/// Report that min_size doesn't fit into this vector's size type. Throws
/// std::length_error
void
report_size_overflow(size_t min_size, size_t max_size)
{
std::string Reason =
"small_vector unable to grow. Requested capacity (" + std::to_string(min_size) +
") is larger than maximum value for size type (" + std::to_string(max_size) + ")";
throw std::length_error(Reason);
}
/// Report that this vector is already at maximum capacity. Throws
/// std::length_error
void
report_at_maximum_capacity(size_t max_size)
{
std::string Reason =
"small_vector capacity unable to grow. Already at maximum size " + std::to_string(max_size);
throw std::length_error(Reason);
}
template <typename SizeT>
size_t
get_new_capacity(size_t min_size, size_t /*t_size*/, size_t old_capacity)
{
constexpr size_t max_size = std::numeric_limits<SizeT>::max();
// Ensure we can fit the new capacity.
// This is only going to be applicable when the capacity is 32 bit.
if(min_size > max_size) report_size_overflow(min_size, max_size);
// Ensure we can meet the guarantee of space for at least one more element.
// The above check alone will not catch the case where grow is called with a
// default min_size of 0, but the current capacity cannot be increased.
// This is only going to be applicable when the capacity is 32 bit.
if(old_capacity == max_size) report_at_maximum_capacity(max_size);
// In theory 2*capacity can overflow if the capacity is 64 bit, but the
// original capacity would never be large enough for this to be a problem.
size_t new_capacity = (2 * old_capacity) + 1; // Always grow.
return std::clamp(new_capacity, min_size, max_size);
}
} // namespace
template <typename SizeT>
void*
small_vector_base<SizeT>::replace_allocation(void* new_elts,
size_t t_size,
size_t new_capacity,
size_t v_size)
{
void* new_eltsReplace = ::malloc(new_capacity * t_size);
if(v_size != 0u) memcpy(new_eltsReplace, new_elts, v_size * t_size);
free(new_elts);
return new_eltsReplace;
}
// Note: Moving this function into the header may cause performance regression.
template <typename SizeT>
void*
small_vector_base<SizeT>::malloc_for_grow(void* first_el,
size_t min_size,
size_t t_size,
size_t& new_capacity)
{
new_capacity = get_new_capacity<SizeT>(min_size, t_size, this->capacity());
// Even if capacity is not 0 now, if the vector was originally created with
// capacity 0, it's possible for the malloc to return first_el.
void* new_elts = ::malloc(new_capacity * t_size);
if(new_elts == first_el) new_elts = replace_allocation(new_elts, t_size, new_capacity);
return new_elts;
}
// Note: Moving this function into the header may cause performance regression.
template <typename SizeT>
void
small_vector_base<SizeT>::grow_pod(void* first_el, size_t min_size, size_t t_size)
{
size_t new_capacity = get_new_capacity<SizeT>(min_size, t_size, this->capacity());
void* new_elts;
if(m_begin_x == first_el)
{
new_elts = ::malloc(new_capacity * t_size);
if(new_elts == first_el) new_elts = replace_allocation(new_elts, t_size, new_capacity);
// Copy the elements over. No need to run dtors on PODs.
memcpy(new_elts, this->m_begin_x, size() * t_size);
}
else
{
// If this wasn't grown from the inline copy, grow the allocated space.
new_elts = ::realloc(this->m_begin_x, new_capacity * t_size);
if(new_elts == first_el)
new_elts = replace_allocation(new_elts, t_size, new_capacity, size());
}
this->m_begin_x = new_elts;
this->m_capacity = new_capacity;
}
} // namespace container
} // namespace common
} // namespace rocprofiler
// explicit instantiations
template class rocprofiler::common::container::small_vector_base<uint32_t>;
#if SIZE_MAX > UINT32_MAX
template class rocprofiler::common::container::small_vector_base<uint64_t>;
#endif
Το diff αρχείου καταστέλλεται επειδή είναι πολύ μεγάλο Φόρτωση Διαφορών
@@ -0,0 +1,407 @@
// 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 "lib/common/container/operators.hpp"
#include "lib/common/container/static_vector.hpp"
#include "lib/common/defines.hpp"
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <memory>
#include <numeric>
#include <type_traits>
#include <vector>
namespace rocprofiler
{
namespace common
{
namespace container
{
struct reserve_size
{
explicit reserve_size(size_t _v)
: value{_v}
{}
size_t value;
};
template <typename Tp, size_t ChunkSizeV = 64>
class stable_vector
{
public:
using value_type = Tp;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
static constexpr const size_t chunk_size = ChunkSizeV;
private:
template <size_t N>
struct is_pow2
{
static constexpr bool value = (N & (N - 1)) == 0;
};
static_assert(ChunkSizeV > 0, "ChunkSize needs to be greater than zero");
static_assert(is_pow2<ChunkSizeV>::value, "ChunkSize needs to be a power of 2");
using this_type = stable_vector<Tp, ChunkSizeV>;
using const_this_type = const stable_vector<Tp, ChunkSizeV>;
template <typename ContainerT>
struct iterator_base
{
iterator_base(ContainerT* c = nullptr, size_type i = 0)
: m_container(c)
, m_index(i)
{}
iterator_base& operator+=(size_type i)
{
m_index += i;
return *this;
}
iterator_base& operator-=(size_type i)
{
m_index -= i;
return *this;
}
iterator_base& operator++()
{
++m_index;
return *this;
}
iterator_base& operator--()
{
--m_index;
return *this;
}
difference_type operator-(const iterator_base& it)
{
assert(m_container == it.m_container);
return m_index - it.m_index;
}
bool operator<(const iterator_base& it) const
{
assert(m_container == it.m_container);
return m_index < it.m_index;
}
bool operator==(const iterator_base& it) const
{
return m_container == it.m_container && m_index == it.m_index;
}
protected:
ContainerT* m_container;
size_type m_index;
};
public:
struct const_iterator;
struct iterator
: public iterator_base<this_type>
//, std::iterator<std::random_access_iterator_tag, value_type>
, public random_access_iterator_helper<iterator, value_type>
{
using iterator_base<this_type>::iterator_base;
friend struct const_iterator;
reference operator*() { return (*this->m_container)[this->m_index]; }
};
struct const_iterator
: public iterator_base<const_this_type>
//, std::iterator<std::random_access_iterator_tag, const value_type>
, public random_access_iterator_helper<const_iterator, const value_type>
{
using iterator_base<const_this_type>::iterator_base;
explicit const_iterator(const iterator& it)
: iterator_base<const_this_type>(it.m_container, it.m_index)
{}
const_reference operator*() const { return (*this->m_container)[this->m_index]; }
bool operator==(const const_iterator& it) const
{
return iterator_base<const_this_type>::operator==(it);
}
friend bool operator==(const iterator& l, const const_iterator& r) { return r == l; }
};
stable_vector() = default;
explicit stable_vector(size_type count, const Tp& value);
explicit stable_vector(size_type count);
explicit stable_vector(reserve_size&& reserve_count);
template <typename InputItrT,
typename = std::enable_if_t<
std::is_convertible<typename std::iterator_traits<InputItrT>::iterator_category,
std::input_iterator_tag>::value>>
stable_vector(InputItrT first, InputItrT last);
explicit stable_vector(std::initializer_list<Tp>);
stable_vector(const stable_vector& other);
stable_vector(stable_vector&& other) noexcept;
stable_vector& operator=(stable_vector v);
iterator begin() noexcept { return {this, 0}; }
const_iterator begin() const noexcept { return {this, 0}; }
const_iterator cbegin() const noexcept { return begin(); }
iterator end() noexcept { return {this, size()}; }
const_iterator end() const noexcept { return {this, size()}; }
const_iterator cend() const noexcept { return end(); }
size_type size() const noexcept
{
return empty() ? 0 : (m_chunks.size() - 1) * ChunkSizeV + m_chunks.back()->size();
}
size_type max_size() const noexcept { return std::numeric_limits<size_type>::max(); }
size_type capacity() const noexcept { return m_chunks.size() * ChunkSizeV; }
bool empty() const noexcept { return m_chunks.empty(); }
void reserve(size_type new_capacity);
void shrink_to_fit() noexcept {}
bool operator==(const this_type& c) const
{
return size() == c.size() && std::equal(cbegin(), cend(), c.cbegin());
}
bool operator!=(const this_type& c) const { return !operator==(c); }
void swap(this_type& v) noexcept { std::swap(m_chunks, v.m_chunks); }
friend void swap(this_type& l, this_type& r) noexcept { l.swap(r); }
reference front() { return m_chunks.front()->front(); }
const_reference front() const { return front(); }
reference back() { return m_chunks.back()->back(); }
const_reference back() const { return back(); }
void push_back(const Tp& t);
void push_back(Tp&& t);
template <typename... Args>
reference emplace_back(Args&&... args);
reference operator[](size_type i);
const_reference operator[](size_type i) const;
reference at(size_type i);
const_reference at(size_type i) const;
private:
using chunk_type = container::static_vector<Tp, ChunkSizeV, true>;
using storage_type = std::vector<std::unique_ptr<chunk_type>>;
void add_chunk();
chunk_type& last_chunk();
storage_type m_chunks;
};
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>::stable_vector(size_type count, const Tp& value)
{
for(size_type i = 0; i < count; ++i)
{
push_back(value);
}
}
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>::stable_vector(size_type count)
{
for(size_type i = 0; i < count; ++i)
{
emplace_back();
}
}
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>::stable_vector(reserve_size&& reserve_count)
{
reserve(reserve_count.value);
}
template <typename Tp, size_t ChunkSizeV>
template <typename InputItrT, typename>
stable_vector<Tp, ChunkSizeV>::stable_vector(InputItrT first, InputItrT last)
{
for(; first != last; ++first)
{
push_back(*first);
}
}
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>::stable_vector(const stable_vector& other)
{
for(const auto& chunk : other.m_chunks)
{
m_chunks.emplace_back(std::make_unique<chunk_type>(*chunk));
}
}
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>::stable_vector(stable_vector&& other) noexcept
: m_chunks(std::move(other.m_chunks))
{}
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>::stable_vector(std::initializer_list<Tp> ilist)
{
for(const auto& t : ilist)
{
push_back(t);
}
}
template <typename Tp, size_t ChunkSizeV>
stable_vector<Tp, ChunkSizeV>&
stable_vector<Tp, ChunkSizeV>::operator=(stable_vector v)
{
swap(v);
return *this;
}
template <typename Tp, size_t ChunkSizeV>
void
stable_vector<Tp, ChunkSizeV>::add_chunk()
{
m_chunks.emplace_back(std::make_unique<chunk_type>());
}
template <typename Tp, size_t ChunkSizeV>
typename stable_vector<Tp, ChunkSizeV>::chunk_type&
stable_vector<Tp, ChunkSizeV>::last_chunk()
{
if(ROCPROFILER_UNLIKELY(m_chunks.empty() || m_chunks.back()->size() == ChunkSizeV))
{
add_chunk();
}
return *m_chunks.back();
}
template <typename Tp, size_t ChunkSizeV>
void
stable_vector<Tp, ChunkSizeV>::reserve(size_type new_capacity)
{
const size_t initial_capacity = capacity();
for(difference_type i = new_capacity - initial_capacity; i > 0; i -= ChunkSizeV)
{
add_chunk();
}
}
template <typename Tp, size_t ChunkSizeV>
void
stable_vector<Tp, ChunkSizeV>::push_back(const Tp& t)
{
last_chunk().push_back(t);
}
template <typename Tp, size_t ChunkSizeV>
void
stable_vector<Tp, ChunkSizeV>::push_back(Tp&& t)
{
last_chunk().push_back(std::move(t));
}
template <typename Tp, size_t ChunkSizeV>
template <typename... Args>
typename stable_vector<Tp, ChunkSizeV>::reference
stable_vector<Tp, ChunkSizeV>::emplace_back(Args&&... args)
{
return last_chunk().emplace_back(std::forward<Args>(args)...);
}
template <typename Tp, size_t ChunkSizeV>
typename stable_vector<Tp, ChunkSizeV>::reference
stable_vector<Tp, ChunkSizeV>::operator[](size_type i)
{
return (*m_chunks[i / ChunkSizeV])[i % ChunkSizeV];
}
template <typename Tp, size_t ChunkSizeV>
typename stable_vector<Tp, ChunkSizeV>::const_reference
stable_vector<Tp, ChunkSizeV>::operator[](size_type i) const
{
return const_cast<this_type&>(*this)[i];
}
template <typename Tp, size_t ChunkSizeV>
typename stable_vector<Tp, ChunkSizeV>::reference
stable_vector<Tp, ChunkSizeV>::at(size_type i)
{
if(ROCPROFILER_UNLIKELY(i >= size()))
{
throw std::out_of_range("stable_vector::at(" + std::to_string(i) + "). size is " +
std::to_string(size()));
}
return operator[](i);
}
template <typename Tp, size_t ChunkSizeV>
typename stable_vector<Tp, ChunkSizeV>::const_reference
stable_vector<Tp, ChunkSizeV>::at(size_type i) const
{
return const_cast<this_type&>(*this).at(i);
}
template <typename Tp, size_t ChunkSizeV, typename... Args>
auto
resize(stable_vector<Tp, ChunkSizeV>& _v, size_t _n, Args&&... args)
{
if(_n > _v.capacity()) _v.reserve(_n);
while(_v.size() < _n)
_v.emplace_back(std::forward<Args>(args)...);
return _v.size();
}
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,230 @@
// 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 "lib/common/container/c_array.hpp"
#include "lib/common/defines.hpp"
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdlib>
#include <initializer_list>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
namespace container
{
template <typename Tp, size_t N, bool AtomicSizeV = false>
struct static_vector
{
using count_type = std::conditional_t<AtomicSizeV, std::atomic<size_t>, size_t>;
using this_type = static_vector<Tp, N>;
using value_type = Tp;
static_vector() = default;
static_vector(const static_vector&) = default;
static_vector(static_vector&&) noexcept = default;
static_vector& operator=(const static_vector&) = default;
static_vector& operator=(static_vector&&) noexcept = default;
explicit static_vector(size_t _n, Tp _v = {});
explicit static_vector(c_array<Tp>&&);
template <size_t M>
explicit static_vector(std::array<Tp, M>&&);
static_vector& operator=(std::initializer_list<Tp>&& _v);
static_vector& operator=(std::pair<std::array<Tp, N>, size_t>&&);
template <typename... Args>
value_type& emplace_back(Args&&... _v);
template <typename Up>
decltype(auto) push_back(Up&& _v)
{
return emplace_back(Tp{std::forward<Up>(_v)});
}
void pop_back() { --m_size; }
void clear();
void reserve(size_t) noexcept {}
void shrink_to_fit() noexcept {}
auto capacity() noexcept { return N; }
size_t size() const { return m_size; }
bool empty() const { return (size() == 0); }
auto begin() { return m_data.begin(); }
auto begin() const { return m_data.begin(); }
auto cbegin() const { return m_data.cbegin(); }
auto end() { return m_data.begin() + size(); }
auto end() const { return m_data.begin() + size(); }
auto cend() const { return m_data.cbegin() + size(); }
decltype(auto) operator[](size_t _idx) { return m_data[_idx]; }
decltype(auto) operator[](size_t _idx) const { return m_data[_idx]; }
decltype(auto) at(size_t _idx) { return m_data.at(_idx); }
decltype(auto) at(size_t _idx) const { return m_data.at(_idx); }
decltype(auto) front() { return m_data.front(); }
decltype(auto) front() const { return m_data.front(); }
decltype(auto) back() { return *(m_data.begin() + size() - 1); }
decltype(auto) back() const { return *(m_data.begin() + size() - 1); }
auto* data() { return m_data.data(); }
const auto* data() const { return m_data.data(); }
void swap(this_type& _v) noexcept;
friend void swap(this_type& _lhs, this_type& _rhs) noexcept { _lhs.swap(_rhs); }
private:
void update_size(size_t);
private:
count_type m_size = count_type{0};
std::array<Tp, N> m_data = {};
};
template <typename Tp, size_t N, bool AtomicSizeV>
static_vector<Tp, N, AtomicSizeV>::static_vector(size_t _n, Tp _v)
{
m_data.fill(_v);
update_size(_n);
}
template <typename Tp, size_t N, bool AtomicSizeV>
static_vector<Tp, N, AtomicSizeV>::static_vector(c_array<Tp>&& _v)
{
auto _n = std::min<size_t>(N, _v.size());
for(size_t i = 0; i < _n; ++i, ++m_size)
m_data[i] = _v[i];
}
template <typename Tp, size_t N, bool AtomicSizeV>
template <size_t M>
static_vector<Tp, N, AtomicSizeV>::static_vector(std::array<Tp, M>&& _v)
{
auto _n = std::min<size_t>(N, M);
for(size_t i = 0; i < _n; ++i, ++m_size)
m_data[i] = _v[i];
}
template <typename Tp, size_t N, bool AtomicSizeV>
static_vector<Tp, N, AtomicSizeV>&
static_vector<Tp, N, AtomicSizeV>::operator=(std::initializer_list<Tp>&& _v)
{
if(ROCPROFILER_UNLIKELY(_v.size() > N))
{
throw std::out_of_range(std::string{"static_vector::operator=(initializer_list) size > "} +
std::to_string(N));
}
clear();
for(auto&& itr : _v)
m_data[m_size++] = itr;
return *this;
}
template <typename Tp, size_t N, bool AtomicSizeV>
static_vector<Tp, N, AtomicSizeV>&
static_vector<Tp, N, AtomicSizeV>::operator=(std::pair<std::array<Tp, N>, size_t>&& _v)
{
update_size(0);
m_data = std::move(_v.first);
update_size(_v.second);
return *this;
}
template <typename Tp, size_t N, bool AtomicSizeV>
void
static_vector<Tp, N, AtomicSizeV>::clear()
{
update_size(0);
}
template <typename Tp, size_t N, bool AtomicSizeV>
void
static_vector<Tp, N, AtomicSizeV>::swap(this_type& _v) noexcept
{
if constexpr(AtomicSizeV)
{
auto _t_size = m_size;
auto _v_size = _v.m_size;
std::swap(m_data, _v.m_data);
update_size(_v_size);
_v.update_size(_t_size);
}
else
{
std::swap(m_size, _v.m_size);
std::swap(m_data, _v.m_data);
}
}
template <typename Tp, size_t N, bool AtomicSizeV>
template <typename... Args>
Tp&
static_vector<Tp, N, AtomicSizeV>::emplace_back(Args&&... _v)
{
auto _idx = m_size++;
if(_idx >= N)
{
throw std::out_of_range(std::string{"static_vector::emplace_back - reached capacity "} +
std::to_string(N));
}
if constexpr(sizeof...(Args) > 0)
{
if constexpr(std::is_assignable<Tp, decltype(std::forward<Args>(_v))...>::value)
m_data[_idx] = {std::forward<Args>(_v)...};
else
m_data[_idx] = Tp{std::forward<Args>(_v)...};
}
else if constexpr(std::is_move_assignable<Tp>::value || std::is_copy_assignable<Tp>::value)
{
m_data[_idx] = {};
}
return m_data[_idx];
}
template <typename Tp, size_t N, bool AtomicSizeV>
void
static_vector<Tp, N, AtomicSizeV>::update_size(size_t _n)
{
if constexpr(AtomicSizeV)
m_size.store(_n);
else
m_size = _n;
}
} // namespace container
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,144 @@
// MIT License
//
// Copyright (c) 2023-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 <rocprofiler-sdk/defines.h>
#define ROCPROFILER_VISIBILITY(MODE) ROCPROFILER_ATTRIBUTE(visibility(MODE))
#define ROCPROFILER_INTERNAL_API ROCPROFILER_VISIBILITY("internal")
#define ROCPROFILER_INLINE ROCPROFILER_ATTRIBUTE(always_inline) inline
#define ROCPROFILER_NOINLINE ROCPROFILER_ATTRIBUTE(noinline)
#define ROCPROFILER_HOT ROCPROFILER_ATTRIBUTE(hot)
#define ROCPROFILER_COLD ROCPROFILER_ATTRIBUTE(cold)
#define ROCPROFILER_CONST ROCPROFILER_ATTRIBUTE(const)
#define ROCPROFILER_PURE ROCPROFILER_ATTRIBUTE(pure)
#define ROCPROFILER_WEAK ROCPROFILER_ATTRIBUTE(weak)
#define ROCPROFILER_PACKED ROCPROFILER_ATTRIBUTE(__packed__)
#define ROCPROFILER_PACKED_ALIGN(VAL) ROCPROFILER_PACKED ROCPROFILER_ATTRIBUTE(__aligned__(VAL))
#define ROCPROFILER_LIKELY(...) __builtin_expect((__VA_ARGS__), 1)
#define ROCPROFILER_UNLIKELY(...) __builtin_expect((__VA_ARGS__), 0)
#if defined(ROCPROFILER_CI) && ROCPROFILER_CI > 0
# if defined(NDEBUG)
# undef NDEBUG
# endif
# if !defined(DEBUG)
# define DEBUG 1
# endif
# if defined(__cplusplus)
# include <cassert>
# else
# include <assert.h>
# endif
#endif
#define ROCPROFILER_STRINGIZE(X) ROCPROFILER_STRINGIZE2(X)
#define ROCPROFILER_STRINGIZE2(X) #X
#define ROCPROFILER_VAR_NAME_COMBINE(X, Y) X##Y
#define ROCPROFILER_VARIABLE(X, Y) ROCPROFILER_VAR_NAME_COMBINE(X, Y)
#define ROCPROFILER_LINESTR ROCPROFILER_STRINGIZE(__LINE__)
#define ROCPROFILER_ESC(...) __VA_ARGS__
#if defined(__cplusplus)
# if !defined(ROCPROFILER_FOLD_EXPRESSION)
# define ROCPROFILER_FOLD_EXPRESSION(...) ((__VA_ARGS__), ...)
# endif
#endif
#define ROCPROFILER_COMPUTE_VERSION(MAJOR, MINOR, PATCH) \
ROCPROFILER_SDK_COMPUTE_VERSION(MAJOR, MINOR, PATCH)
// Below are used in HSA, HIP, and Marker API tracing
#define IMPL_DETAIL_EXPAND(X) X
#define IMPL_DETAIL_FOR_EACH_NARG(...) \
IMPL_DETAIL_FOR_EACH_NARG_(__VA_ARGS__, IMPL_DETAIL_FOR_EACH_RSEQ_N())
#define IMPL_DETAIL_FOR_EACH_NARG_(...) IMPL_DETAIL_EXPAND(IMPL_DETAIL_FOR_EACH_ARG_N(__VA_ARGS__))
#define IMPL_DETAIL_FOR_EACH_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, N, ...) \
N
#define IMPL_DETAIL_FOR_EACH_RSEQ_N() 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#define IMPL_DETAIL_CONCATENATE(X, Y) X##Y
#define IMPL_DETAIL_FOR_EACH_(N, MACRO, PREFIX, ...) \
IMPL_DETAIL_EXPAND(IMPL_DETAIL_CONCATENATE(MACRO, N)(PREFIX, __VA_ARGS__))
#define IMPL_DETAIL_FOR_EACH(MACRO, PREFIX, ...) \
IMPL_DETAIL_FOR_EACH_(IMPL_DETAIL_FOR_EACH_NARG(__VA_ARGS__), MACRO, PREFIX, __VA_ARGS__)
#define ADDR_MEMBER_0(...)
#define ADDR_MEMBER_1(PREFIX, FIELD) static_cast<void*>(&PREFIX.FIELD)
#define ADDR_MEMBER_2(PREFIX, A, B) ADDR_MEMBER_1(PREFIX, A), ADDR_MEMBER_1(PREFIX, B)
#define ADDR_MEMBER_3(PREFIX, A, B, C) ADDR_MEMBER_2(PREFIX, A, B), ADDR_MEMBER_1(PREFIX, C)
#define ADDR_MEMBER_4(PREFIX, A, B, C, D) ADDR_MEMBER_3(PREFIX, A, B, C), ADDR_MEMBER_1(PREFIX, D)
#define ADDR_MEMBER_5(PREFIX, A, B, C, D, E) \
ADDR_MEMBER_4(PREFIX, A, B, C, D), ADDR_MEMBER_1(PREFIX, E)
#define ADDR_MEMBER_6(PREFIX, A, B, C, D, E, F) \
ADDR_MEMBER_5(PREFIX, A, B, C, D, E), ADDR_MEMBER_1(PREFIX, F)
#define ADDR_MEMBER_7(PREFIX, A, B, C, D, E, F, G) \
ADDR_MEMBER_6(PREFIX, A, B, C, D, E, F), ADDR_MEMBER_1(PREFIX, G)
#define ADDR_MEMBER_8(PREFIX, A, B, C, D, E, F, G, H) \
ADDR_MEMBER_7(PREFIX, A, B, C, D, E, F, G), ADDR_MEMBER_1(PREFIX, H)
#define ADDR_MEMBER_9(PREFIX, A, B, C, D, E, F, G, H, I) \
ADDR_MEMBER_8(PREFIX, A, B, C, D, E, F, G, H), ADDR_MEMBER_1(PREFIX, I)
#define ADDR_MEMBER_10(PREFIX, A, B, C, D, E, F, G, H, I, J) \
ADDR_MEMBER_9(PREFIX, A, B, C, D, E, F, G, H, I), ADDR_MEMBER_1(PREFIX, J)
#define ADDR_MEMBER_11(PREFIX, A, B, C, D, E, F, G, H, I, J, K) \
ADDR_MEMBER_10(PREFIX, A, B, C, D, E, F, G, H, I, J), ADDR_MEMBER_1(PREFIX, K)
#define ADDR_MEMBER_12(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L) \
ADDR_MEMBER_11(PREFIX, A, B, C, D, E, F, G, H, I, J, K), ADDR_MEMBER_1(PREFIX, L)
#define ADDR_MEMBER_13(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M) \
ADDR_MEMBER_12(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L), ADDR_MEMBER_1(PREFIX, M)
#define ADDR_MEMBER_14(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M, N) \
ADDR_MEMBER_13(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M), ADDR_MEMBER_1(PREFIX, N)
#define ADDR_MEMBER_15(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) \
ADDR_MEMBER_14(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M, N), ADDR_MEMBER_1(PREFIX, O)
#define NAMED_MEMBER_0(...)
#define NAMED_MEMBER_1(PREFIX, FIELD) std::make_pair(#FIELD, PREFIX.FIELD)
#define NAMED_MEMBER_2(PREFIX, A, B) NAMED_MEMBER_1(PREFIX, A), NAMED_MEMBER_1(PREFIX, B)
#define NAMED_MEMBER_3(PREFIX, A, B, C) NAMED_MEMBER_2(PREFIX, A, B), NAMED_MEMBER_1(PREFIX, C)
#define NAMED_MEMBER_4(PREFIX, A, B, C, D) \
NAMED_MEMBER_3(PREFIX, A, B, C), NAMED_MEMBER_1(PREFIX, D)
#define NAMED_MEMBER_5(PREFIX, A, B, C, D, E) \
NAMED_MEMBER_4(PREFIX, A, B, C, D), NAMED_MEMBER_1(PREFIX, E)
#define NAMED_MEMBER_6(PREFIX, A, B, C, D, E, F) \
NAMED_MEMBER_5(PREFIX, A, B, C, D, E), NAMED_MEMBER_1(PREFIX, F)
#define NAMED_MEMBER_7(PREFIX, A, B, C, D, E, F, G) \
NAMED_MEMBER_6(PREFIX, A, B, C, D, E, F), NAMED_MEMBER_1(PREFIX, G)
#define NAMED_MEMBER_8(PREFIX, A, B, C, D, E, F, G, H) \
NAMED_MEMBER_7(PREFIX, A, B, C, D, E, F, G), NAMED_MEMBER_1(PREFIX, H)
#define NAMED_MEMBER_9(PREFIX, A, B, C, D, E, F, G, H, I) \
NAMED_MEMBER_8(PREFIX, A, B, C, D, E, F, G, H), NAMED_MEMBER_1(PREFIX, I)
#define NAMED_MEMBER_10(PREFIX, A, B, C, D, E, F, G, H, I, J) \
NAMED_MEMBER_9(PREFIX, A, B, C, D, E, F, G, H, I), NAMED_MEMBER_1(PREFIX, J)
#define NAMED_MEMBER_11(PREFIX, A, B, C, D, E, F, G, H, I, J, K) \
NAMED_MEMBER_10(PREFIX, A, B, C, D, E, F, G, H, I, J), NAMED_MEMBER_1(PREFIX, K)
#define NAMED_MEMBER_12(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L) \
NAMED_MEMBER_11(PREFIX, A, B, C, D, E, F, G, H, I, J, K), NAMED_MEMBER_1(PREFIX, L)
#define NAMED_MEMBER_13(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M) \
NAMED_MEMBER_12(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L), NAMED_MEMBER_1(PREFIX, M)
#define NAMED_MEMBER_14(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M, N) \
NAMED_MEMBER_13(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M), NAMED_MEMBER_1(PREFIX, N)
#define NAMED_MEMBER_15(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) \
NAMED_MEMBER_14(PREFIX, A, B, C, D, E, F, G, H, I, J, K, L, M, N), NAMED_MEMBER_1(PREFIX, O)
#define GET_ADDR_MEMBER_FIELDS(VAR, ...) IMPL_DETAIL_FOR_EACH(ADDR_MEMBER_, VAR, __VA_ARGS__)
#define GET_NAMED_MEMBER_FIELDS(VAR, ...) IMPL_DETAIL_FOR_EACH(NAMED_MEMBER_, VAR, __VA_ARGS__)
@@ -0,0 +1,164 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/demangle.hpp"
#include "lib/common/logging.hpp"
#include <cxxabi.h>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
namespace rocprofiler
{
namespace common
{
std::string
cxa_demangle(std::string_view _mangled_name, int* _status)
{
// return the mangled since there is no buffer
if(_mangled_name.empty())
{
*_status = -2;
return std::string{};
}
auto _demangled_name = std::string{_mangled_name};
// PARAMETERS to __cxa_demangle
// mangled_name:
// A NULL-terminated character string containing the name to be demangled.
// buffer:
// A region of memory, allocated with malloc, of *length bytes, into which the
// demangled name is stored. If output_buffer is not long enough, it is expanded
// using realloc. output_buffer may instead be NULL; in that case, the demangled
// name is placed in a region of memory allocated with malloc.
// _buflen:
// If length is non-NULL, the length of the buffer containing the demangled name
// is placed in *length.
// status:
// *status is set to one of the following values
size_t _demang_len = 0;
char* _demang = abi::__cxa_demangle(_demangled_name.c_str(), nullptr, &_demang_len, _status);
switch(*_status)
{
// 0 : The demangling operation succeeded.
// -1 : A memory allocation failure occurred.
// -2 : mangled_name is not a valid name under the C++ ABI mangling rules.
// -3 : One of the arguments is invalid.
case 0:
{
if(_demang) _demangled_name = std::string{_demang};
break;
}
case -1:
{
ROCP_ERROR << "memory allocation failure occurred demangling " << _demangled_name;
break;
}
case -2: break;
case -3:
{
ROCP_ERROR << "Invalid argument in: (\"" << _demangled_name << "\", nullptr, nullptr, "
<< _status << ")";
break;
}
default: break;
};
// if it "demangled" but the length is zero, set the status to -2
if(_demang_len == 0 && *_status == 0) *_status = -2;
// free allocated buffer
::free(_demang);
return _demangled_name;
}
// C++ symbol demangle
std::string
cxx_demangle(std::string_view symbol)
{
int _status = 0;
auto demangled_str = cxa_demangle(symbol, &_status);
if(_status == 0) return demangled_str;
return std::string{symbol};
}
// The function extracts the kernel name from
// input string. By using the iterators it finds the
// window in the string which contains only the kernel name.
// For example 'Foo<int, float>::foo(a[], int (int))' -> 'foo'
std::string
truncate_name(std::string_view name)
{
auto rit = name.rbegin();
auto rend = name.rend();
uint32_t counter = 0;
char open_token = 0;
char close_token = 0;
while(rit != rend)
{
if(counter == 0)
{
switch(*rit)
{
case ')':
counter = 1;
open_token = ')';
close_token = '(';
break;
case '>':
counter = 1;
open_token = '>';
close_token = '<';
break;
case ']':
counter = 1;
open_token = ']';
close_token = '[';
break;
case ' ': ++rit; continue;
}
if(counter == 0) break;
}
else
{
if(*rit == open_token) counter++;
if(*rit == close_token) counter--;
}
++rit;
}
auto rbeg = rit;
while((rit != rend) && (*rit != ' ') && (*rit != ':'))
rit++;
return std::string{name.substr(rend - rit, rit - rbeg)};
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,53 @@
// MIT License
//
// Copyright (c) 2023-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 <cxxabi.h>
#include <algorithm>
#include <cstdarg>
#include <cstdio>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace rocprofiler
{
namespace common
{
[[nodiscard]] std::string
cxa_demangle(std::string_view _mangled_name, int* _status) __attribute__((nonnull(2)));
// C++ symbol demangle
std::string
cxx_demangle(std::string_view symbol);
// The function extracts the kernel name from
// input string. By using the iterators it finds the
// window in the string which contains only the kernel name.
// For example 'Foo<int, float>::foo(a[], int (int))' -> 'foo'
std::string
truncate_name(std::string_view name);
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,30 @@
# MIT License
#
# Copyright (c) 2023-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.
#
# add details sources and headers to common library target
#
set(details_headers mpl.hpp)
set(details_sources)
target_sources(rocprofiler-sdk-common-library PRIVATE ${details_sources}
${details_headers})
@@ -0,0 +1,211 @@
// MIT License
//
// Copyright (c) 2023 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 <cstddef>
#include <functional>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
namespace mpl
{
namespace impl
{
template <typename... Tp>
struct type_list
{
static constexpr auto size() { return sizeof...(Tp); }
};
template <typename InTuple, typename OutTuple>
struct reverse;
template <template <typename...> class InTuple,
typename InT,
typename... InTail,
template <typename...>
class OutTuple,
typename... OutTail>
struct reverse<InTuple<InT, InTail...>, OutTuple<OutTail...>>
: reverse<InTuple<InTail...>, OutTuple<InT, OutTail...>>
{};
template <template <typename...> class InTuple,
template <typename...>
class OutTuple,
typename... OutTail>
struct reverse<InTuple<>, OutTuple<OutTail...>>
{
using type = OutTuple<OutTail...>;
};
template <template <typename...> class InTuple, typename... InTail>
struct reverse<InTuple<InTail...>, void> : reverse<InTuple<InTail...>, InTuple<>>
{};
template <typename T>
struct function_traits;
template <typename T>
struct function_traits<T&> : function_traits<T>
{};
template <typename R, typename... Args>
struct function_traits<std::function<R(Args...)>>
{
static constexpr bool is_memfun = false;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = args_type;
};
template <typename R, typename... Args>
struct function_traits<R (*)(Args...)>
{
static constexpr bool is_memfun = false;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = args_type;
};
template <typename R, typename... Args>
struct function_traits<R(Args...)>
{
static constexpr bool is_memfun = false;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = args_type;
};
// member function pointer
template <typename C, typename R, typename... Args>
struct function_traits<R (C::*)(Args...)>
{
static constexpr bool is_memfun = true;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = type_list<C&, Args...>;
};
// const member function pointer
template <typename C, typename R, typename... Args>
struct function_traits<R (C::*)(Args...) const>
{
static constexpr bool is_memfun = true;
static constexpr bool is_const = true;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = type_list<C&, Args...>;
};
// member object pointer
template <typename C, typename R>
struct function_traits<R(C::*)>
{
static constexpr bool is_memfun = true;
static constexpr bool is_const = false;
static const size_t nargs = 0;
using result_type = R;
using args_type = type_list<>;
using call_type = type_list<C&>;
};
#if __cplusplus >= 201703L
template <typename R, typename... Args>
struct function_traits<std::function<R(Args...) noexcept>>
{
static constexpr bool is_memfun = false;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = args_type;
};
template <typename R, typename... Args>
struct function_traits<R (*)(Args...) noexcept>
{
static constexpr bool is_memfun = false;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = args_type;
};
template <typename R, typename... Args>
struct function_traits<R(Args...) noexcept>
{
static constexpr bool is_memfun = false;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = args_type;
};
// member function pointer
template <typename C, typename R, typename... Args>
struct function_traits<R (C::*)(Args...) noexcept>
{
static constexpr bool is_memfun = true;
static constexpr bool is_const = false;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = type_list<C&, Args...>;
};
// const member function pointer
template <typename C, typename R, typename... Args>
struct function_traits<R (C::*)(Args...) const noexcept>
{
static constexpr bool is_memfun = true;
static constexpr bool is_const = true;
static constexpr size_t nargs = sizeof...(Args);
using result_type = R;
using args_type = type_list<Args...>;
using call_type = type_list<C&, Args...>;
};
#endif
} // namespace impl
} // namespace mpl
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,228 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/elf_utils.hpp"
#include <rocprofiler-sdk/cxx/utility.hpp>
#include <fmt/format.h>
#include <elfio/elfio.hpp>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "lib/common/logging.hpp"
namespace rocprofiler
{
namespace common
{
namespace elf_utils
{
namespace
{
const ELFIO::Elf_Xword PAGE_SIZE = sysconf(_SC_PAGESIZE);
using ::rocprofiler::sdk::utility::as_hex;
} // namespace
SymbolEntry::SymbolEntry(unsigned int _idx, const accessor_type& _accessor)
: index{_idx}
{
if(!_accessor.get_symbol(index, name, value, size, bind, type, section_index, other))
ROCP_WARNING << "ELFIO::symbol_section_accessor::get_symbol failed of symbol " << _idx;
}
DynamicEntry::DynamicEntry(unsigned int _idx, const accessor_type& _accessor)
: index{_idx}
{
if(!_accessor.get_entry(_idx, tag, value, name)) return;
}
RelocationEntry::RelocationEntry(unsigned int _idx, const accessor_type& _accessor)
: index{_idx}
{
if(!_accessor.get_entry(_idx, offset, symbol, type, addend))
ROCP_WARNING << "ELFIO::relocation_section_accessor::get_entry failed for symbol " << _idx;
}
ElfInfo::ElfInfo(std::string _fname)
: filename{std::move(_fname)}
{}
bool
ElfInfo::has_symbol(const std::function<bool(std::string_view)>& _checker) const
{
for(const auto& itr : symbol_entries)
{
if(!itr.name.empty() && _checker(itr.name)) return true;
}
// For stripped binaries
for(const auto& itr : dynamic_symbol_entries)
{
if(!itr.name.empty() && _checker(itr.name)) return true;
}
return false;
}
ElfInfo
read(const std::string& _inp)
{
auto _info = ElfInfo{_inp};
auto& reader = _info.reader;
auto& sections = _info.sections;
auto& symbol_entries = _info.symbol_entries;
auto& dynamic_symbol_entries = _info.dynamic_symbol_entries;
auto& dynamic_entries = _info.dynamic_entries;
auto& reloc_entries = _info.reloc_entries;
ROCP_TRACE << "\nReading " << _inp;
if(!reader.load(_inp))
ROCP_WARNING << fmt::format("ELF parsing for '{}' did not succeed", _inp);
if(reader.get_class() == ELFIO::ELFCLASS32)
ROCP_TRACE << " - ELF 32-bit";
else
ROCP_TRACE << " - ELF 64-bit";
ROCP_TRACE << " - ELF file encoding: "
<< ((reader.get_encoding() == ELFIO::ELFDATA2LSB) ? std::string_view{"Little endian"}
: std::string_view{"Big endian"});
ROCP_TRACE << " - ELF version: " << reader.get_elf_version();
ROCP_TRACE << " - ELF header size: " << reader.get_header_size();
ROCP_TRACE << " - ELF OS ABI: " << reader.get_os_abi();
// Print ELF file sections info
ELFIO::Elf_Half sec_num = reader.sections.size();
ROCP_TRACE << " - Number of sections: " << sec_num;
for(ELFIO::Elf_Half j = 0; j < sec_num; ++j)
{
ELFIO::section* psec = reader.sections[j];
sections.emplace_back(psec);
}
std::sort(sections.begin(), sections.end(), [](const Section* lhs, const Section* rhs) {
return std::string_view{lhs->get_name()} < std::string_view{rhs->get_name()};
});
for(ELFIO::Elf_Half j = 0; j < sec_num; ++j)
{
Section* psec = sections.at(j);
ROCP_TRACE << " [" << j << "] \t" << std::setw(20) << psec->get_name() << "\t : \t"
<< "size / entry-size = " << std::setw(6) << psec->get_size() << " / "
<< std::setw(3) << psec->get_entry_size()
<< " | addr: " << as_hex(psec->get_address(), 16)
<< " | offset: " << as_hex(psec->get_offset(), 16);
if(psec->get_size() == 0) continue;
if(psec->get_type() == ELFIO::SHT_SYMTAB)
{
const ELFIO::symbol_section_accessor _symbols(reader, psec);
ROCP_TRACE << " - Number of symbol entries: " << _symbols.get_symbols_num();
for(ELFIO::Elf_Xword k = 0; k < _symbols.get_symbols_num(); ++k)
symbol_entries.emplace_back(k, _symbols);
}
else if(psec->get_type() == ELFIO::SHT_DYNSYM)
{
const ELFIO::symbol_section_accessor _symbols(reader, psec);
ROCP_TRACE << " - Number of dynamic symbol entries: " << _symbols.get_symbols_num();
for(ELFIO::Elf_Xword k = 0; k < _symbols.get_symbols_num(); ++k)
dynamic_symbol_entries.emplace_back(k, _symbols);
}
else if(psec->get_type() == ELFIO::SHT_DYNAMIC)
{
const ELFIO::dynamic_section_accessor dynamic{reader, psec};
ROCP_TRACE << " - Number of dynamic entries: " << dynamic.get_entries_num();
for(ELFIO::Elf_Xword k = 0; k < dynamic.get_entries_num(); ++k)
dynamic_entries.emplace_back(k, dynamic);
}
else if(psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA)
{
const ELFIO::relocation_section_accessor reloc{reader, psec};
ROCP_TRACE << " - Number of relocation entries: " << reloc.get_entries_num();
for(ELFIO::Elf_Xword k = 0; k < reloc.get_entries_num(); ++k)
reloc_entries.emplace_back(k, reloc);
}
}
ROCP_TRACE << " - Symbols:";
for(size_t k = 0; k < symbol_entries.size(); ++k)
{
if(!symbol_entries.at(k).name.empty())
ROCP_TRACE << " [" << k << "] " << symbol_entries.at(k).name;
}
ROCP_TRACE << " - Dynamic Symbols:";
for(size_t k = 0; k < dynamic_symbol_entries.size(); ++k)
{
if(!dynamic_symbol_entries.at(k).name.empty())
ROCP_TRACE << " [" << k << "] " << dynamic_symbol_entries.at(k).name;
}
ROCP_TRACE << " - Dynamic entries:";
for(size_t k = 0; k < dynamic_entries.size(); ++k)
{
if(!dynamic_entries.at(k).name.empty())
ROCP_TRACE << " [" << k << "] " << dynamic_entries.at(k).name;
}
ROCP_TRACE << " - Relocation entries:";
for(size_t k = 0; k < reloc_entries.size(); ++k)
{
auto _sym_idx = reloc_entries.at(k).symbol;
auto _name = std::string{};
if(_sym_idx < symbol_entries.size()) _name = symbol_entries.at(_sym_idx).name;
if(!_name.empty()) ROCP_TRACE << " [" << k << "] " << _name;
}
// Print ELF file segments info
ELFIO::Elf_Half seg_num = reader.segments.size();
ROCP_TRACE << " - Number of segments: " << seg_num;
for(ELFIO::Elf_Half j = 0; j < seg_num; ++j)
{
const ELFIO::segment* pseg = reader.segments[j];
ROCP_TRACE << " [" << std::setw(2) << j << "] flags: " << as_hex(pseg->get_flags(), 16)
<< " offset: " << as_hex(pseg->get_offset(), 16)
<< " align: " << as_hex(pseg->get_align(), 16)
<< " virt: " << as_hex(pseg->get_virtual_address(), 16)
<< " phys: " << as_hex(pseg->get_physical_address(), 16)
<< " fsize: " << std::setw(8) << pseg->get_file_size()
<< " msize: " << std::setw(8) << pseg->get_memory_size();
}
return _info;
}
} // namespace elf_utils
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,122 @@
// MIT License
//
// Copyright (c) 2023-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 <elfio/elfio.hpp>
#include <cstdint>
#include <functional>
#include <map>
#include <ostream>
#include <set>
#include <sstream>
#include <string>
namespace rocprofiler
{
namespace common
{
namespace elf_utils
{
using Section = ELFIO::section;
using Segment = ELFIO::segment;
struct SymbolEntry
{
using accessor_type = ELFIO::symbol_section_accessor;
SymbolEntry(unsigned int _idx, const accessor_type& _accessor);
unsigned int index = 0;
std::string name = {};
ELFIO::Elf64_Addr value = {};
ELFIO::Elf_Xword size = {};
unsigned char bind = {};
unsigned char type = {};
ELFIO::Elf_Half section_index = {};
unsigned char other = {};
};
struct DynamicEntry
{
using accessor_type = ELFIO::dynamic_section_accessor;
DynamicEntry(unsigned int _idx, const accessor_type& _accessor);
unsigned int index = 0;
std::string name = {};
ELFIO::Elf_Xword tag = {};
ELFIO::Elf_Xword value = {};
};
struct RelocationEntry
{
using accessor_type = ELFIO::relocation_section_accessor;
RelocationEntry(unsigned int _idx, const accessor_type& _accessor);
unsigned int index = 0;
ELFIO::Elf64_Addr offset = {};
ELFIO::Elf_Word symbol = {};
ELFIO::Elf_Word type = {};
ELFIO::Elf_Sxword addend = {};
};
struct ElfInfo
{
explicit ElfInfo(std::string);
std::string filename = {};
ELFIO::elfio reader = {};
std::vector<Section*> sections = {};
std::vector<SymbolEntry> symbol_entries = {};
std::vector<SymbolEntry> dynamic_symbol_entries = {};
std::vector<DynamicEntry> dynamic_entries = {};
std::vector<RelocationEntry> reloc_entries = {};
bool has_symbol(const std::function<bool(std::string_view)>&) const;
friend bool operator==(const ElfInfo& lhs, const ElfInfo& rhs)
{
return (lhs.filename == rhs.filename);
}
friend bool operator<(const ElfInfo& lhs, const ElfInfo& rhs)
{
return (lhs.filename < rhs.filename);
}
friend bool operator>(const ElfInfo& lhs, const ElfInfo& rhs)
{
return !(lhs == rhs || lhs < rhs);
}
friend bool operator<=(const ElfInfo& lhs, const ElfInfo& rhs) { return !(lhs > rhs); }
friend bool operator>=(const ElfInfo& lhs, const ElfInfo& rhs) { return !(lhs < rhs); }
};
ElfInfo
read(const std::string& _inp);
} // namespace elf_utils
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,206 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/environment.hpp"
#include "lib/common/demangle.hpp"
#include "lib/common/logging.hpp"
#include <fmt/format.h>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <string_view>
namespace rocprofiler
{
namespace common
{
namespace impl
{
std::string
get_env(std::string_view env_id, std::string_view _default)
{
if(env_id.empty()) return std::string{_default};
char* env_var = ::std::getenv(env_id.data());
if(env_var) return std::string{env_var};
return std::string{_default};
}
std::string
get_env(std::string_view env_id, const char* _default)
{
return get_env(env_id, std::string_view{_default});
}
bool
get_env(std::string_view env_id, bool _default)
{
if(env_id.empty()) return _default;
char* env_var = ::std::getenv(env_id.data());
if(env_var)
{
if(std::string_view{env_var}.empty())
{
ROCP_FATAL << fmt::format("No boolean value provided for {}", env_id);
}
if(std::string_view{env_var}.find_first_not_of("0123456789") == std::string_view::npos)
{
return static_cast<bool>(std::stoi(env_var));
}
for(size_t i = 0; i < std::string_view{env_var}.length(); ++i)
env_var[i] = tolower(env_var[i]);
for(const auto& itr : {"off", "false", "no", "n", "f", "0"})
if(std::string_view{env_var} == itr) return false;
return true;
}
return _default;
}
template <typename Tp>
Tp
get_env(std::string_view env_id, Tp _default, std::enable_if_t<std::is_integral<Tp>::value, sfinae>)
{
static_assert(!std::is_same<Tp, bool>::value, "unexpected! should be using bool overload");
static_assert(
sizeof(Tp) <= sizeof(uint64_t),
"change use of stol/stoul if instantiating for type larger than a 64-bit integer");
if(env_id.empty()) return _default;
char* env_var = ::std::getenv(env_id.data());
if(env_var)
{
try
{
// use stol/stoul
if constexpr(std::is_signed<Tp>::value)
return static_cast<Tp>(std::stol(env_var));
else
return static_cast<Tp>(std::stoul(env_var));
} catch(std::exception& _e)
{
ROCP_ERROR << "[rocprofiler][get_env] Exception thrown converting getenv(\"" << env_id
<< "\") = " << env_var << " to " << cxx_demangle(typeid(Tp).name())
<< " :: " << _e.what() << ". Using default value of " << _default << "\n";
}
return _default;
}
return _default;
}
int
set_env(std::string_view env_id, bool value, int override)
{
return ::setenv(env_id.data(), (value) ? "1" : "0", override);
}
template <typename Tp>
int
set_env(std::string_view env_id, Tp value, int override)
{
auto str_value = std::stringstream{};
str_value << value;
return ::setenv(env_id.data(), str_value.str().c_str(), override);
}
#define SPECIALIZE_GET_ENV(TYPE) \
template TYPE get_env<TYPE>( \
std::string_view, TYPE, std::enable_if_t<std::is_integral<TYPE>::value, sfinae>); \
template int set_env<TYPE>(std::string_view, TYPE, int);
#define SPECIALIZE_SET_ENV(TYPE) template int set_env<TYPE>(std::string_view, TYPE, int);
SPECIALIZE_GET_ENV(int8_t)
SPECIALIZE_GET_ENV(int16_t)
SPECIALIZE_GET_ENV(int32_t)
SPECIALIZE_GET_ENV(int64_t)
SPECIALIZE_GET_ENV(uint8_t)
SPECIALIZE_GET_ENV(uint16_t)
SPECIALIZE_GET_ENV(uint32_t)
SPECIALIZE_GET_ENV(uint64_t)
SPECIALIZE_SET_ENV(const char*)
SPECIALIZE_SET_ENV(std::string)
SPECIALIZE_SET_ENV(std::string_view)
SPECIALIZE_SET_ENV(float)
SPECIALIZE_SET_ENV(double)
} // namespace impl
env_store::env_store(std::initializer_list<env_config>&& _container)
{
for(const auto& itr : _container)
{
m_original.emplace_back(env_config{itr.env_name, get_env(itr.env_name, ""), 1});
m_modified.emplace_back(env_config{itr.env_name, itr.env_value, 1});
}
}
env_store::~env_store() { pop(); }
bool
env_store::push()
{
// not that push ignored bc already pushed
if(m_pushed) return false;
for(const auto& itr : m_modified)
itr();
m_pushed = true;
return true;
}
bool
env_store::pop(bool unset_if_empty)
{
if(!m_pushed) return false;
for(const auto& itr : m_original)
{
auto _current = get_env(itr.env_name, "");
if(!unset_if_empty && itr.env_value.empty())
continue;
else if(_current == itr.env_value)
continue;
else if(_current != itr.env_value)
{
ROCP_INFO << fmt::format("[rocprofiler][env][pop] {}=\"{}\" => {}=\"{}\"",
itr.env_name,
_current,
itr.env_name,
itr.env_value);
}
itr();
}
m_pushed = false;
return true;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,136 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/logging.hpp"
#include <unistd.h>
#include <string>
#include <string_view>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
namespace impl
{
struct sfinae
{};
std::string get_env(std::string_view, std::string_view);
std::string
get_env(std::string_view, const char*);
bool
get_env(std::string_view, bool);
template <typename Tp>
Tp get_env(std::string_view, Tp, std::enable_if_t<std::is_integral<Tp>::value, sfinae> = {});
int
set_env(std::string_view, bool, int override = 0);
template <typename Tp>
int
set_env(std::string_view, Tp, int override = 0);
} // namespace impl
template <typename Tp>
inline auto
get_env(std::string_view env_id, Tp&& _default)
{
if constexpr(std::is_enum<Tp>::value)
{
using Up = std::underlying_type_t<Tp>;
// cast to underlying type -> get_env -> cast to enum type
return static_cast<Tp>(impl::get_env(env_id, static_cast<Up>(_default)));
}
else
{
return impl::get_env(env_id, std::forward<Tp>(_default));
}
}
template <typename Tp>
inline auto
set_env(std::string_view env_id, Tp&& value, int override = 0)
{
return impl::set_env(env_id, std::forward<Tp>(value), override);
}
struct env_config
{
std::string env_name = {};
std::string env_value = {};
int overwrite = 0;
auto operator()(bool _verbose = false) const
{
if(env_name.empty())
return -1;
else if(_verbose)
{
ROCP_INFO << "[rocprofiler][set_env] setenv(\"" << env_name << "\", \"" << env_value
<< "\", " << overwrite << ")\n";
}
return (env_value.empty() && overwrite > 0)
? unsetenv(env_name.c_str())
: setenv(env_name.c_str(), env_value.c_str(), overwrite);
}
};
struct env_store
{
template <template <typename, typename...> class ContainerT, typename... TailT>
explicit env_store(ContainerT<env_config, TailT...>&& _container);
explicit env_store(std::initializer_list<env_config>&& _container);
~env_store();
env_store(const env_store&) = default;
env_store(env_store&&) noexcept = default;
env_store& operator=(const env_store&) = default;
env_store& operator=(env_store&&) noexcept = default;
bool push();
bool pop(bool unset_if_empty = true);
bool is_pushed() const { return m_pushed; }
private:
bool m_pushed = false;
std::vector<env_config> m_original = {};
std::vector<env_config> m_modified = {};
};
template <template <typename, typename...> class ContainerT, typename... TailT>
env_store::env_store(ContainerT<env_config, TailT...>&& _container)
{
for(const auto& itr : _container)
{
m_original.emplace_back(env_config{itr.env_name, get_env(itr.env_name, ""), 1});
m_modified.emplace_back(env_config{itr.env_name, itr.env_value, 1});
}
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,77 @@
// MIT License
//
// Copyright (c) 2023-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
#if !defined(ROCPROFILER_HAS_GHC_LIB_FILESYSTEM)
# if defined __has_include
# if __has_include(<ghc/filesystem.hpp>)
# define ROCPROFILER_HAS_GHC_LIB_FILESYSTEM 1
# else
# define ROCPROFILER_HAS_GHC_LIB_FILESYSTEM 0
# endif
# else
# define ROCPROFILER_HAS_GHC_LIB_FILESYSTEM 0
# endif
#endif
#if ROCPROFILER_HAS_GHC_LIB_FILESYSTEM == 0
# if defined __has_include
# if __has_include(<version>)
# include <version>
# endif
# endif
# if defined(__cpp_lib_filesystem)
# define ROCPROFILER_HAS_CPP_LIB_FILESYSTEM 1
# else
# if defined __has_include
# if __has_include(<filesystem>)
# define ROCPROFILER_HAS_CPP_LIB_FILESYSTEM 1
# endif
# endif
# endif
#endif
// include the correct filesystem header
#if defined(ROCPROFILER_HAS_GHC_LIB_FILESYSTEM) && ROCPROFILER_HAS_GHC_LIB_FILESYSTEM > 0
# include <ghc/filesystem.hpp>
#elif defined(ROCPROFILER_HAS_CPP_LIB_FILESYSTEM) && ROCPROFILER_HAS_CPP_LIB_FILESYSTEM > 0
# include <filesystem>
#else
# include <experimental/filesystem>
#endif
// create a namespace alias
namespace rocprofiler
{
namespace common
{
#if defined(ROCPROFILER_HAS_GHC_LIB_FILESYSTEM) && ROCPROFILER_HAS_GHC_LIB_FILESYSTEM > 0
namespace filesystem = ::ghc::filesystem; // NOLINT(misc-unused-alias-decls)
#elif defined(ROCPROFILER_HAS_CPP_LIB_FILESYSTEM) && ROCPROFILER_HAS_CPP_LIB_FILESYSTEM > 0
namespace filesystem = ::std::filesystem; // NOLINT(misc-unused-alias-decls)
#else
namespace filesystem = ::std::experimental::filesystem; // NOLINT(misc-unused-alias-decls)
#endif
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,148 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include <rocprofiler-sdk/cxx/details/mpl.hpp>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
// A helper class which computes a 64-bit hash of the input data.
// The algorithm used is FNV-1a as it is fast and easy to implement and has
// relatively few collisions.
// WARNING: This hash function should not be used for any cryptographic purpose.
struct fnv1a_hasher
{
// Creates an empty hash object
fnv1a_hasher() = default;
~fnv1a_hasher() = default;
fnv1a_hasher(const fnv1a_hasher&) = default;
fnv1a_hasher(fnv1a_hasher&&) noexcept = default;
fnv1a_hasher& operator=(const fnv1a_hasher&) = default;
fnv1a_hasher& operator=(fnv1a_hasher&&) noexcept = default;
// Hashes a numeric value.
template <typename Tp, typename std::enable_if_t<std::is_arithmetic<Tp>::value, bool> = true>
fnv1a_hasher& update(Tp data);
template <typename Tp>
fnv1a_hasher& update(const std::optional<Tp>& data);
// Using the loop instead of "update(str, strlen(str))" to avoid looping twice
fnv1a_hasher& update(const char* str);
// Hashes a byte array.
fnv1a_hasher& update(const char* data, size_t size);
fnv1a_hasher& update(std::string_view s) { return update(s.data(), s.size()); }
fnv1a_hasher& update(const std::string& s) { return update(s.data(), s.size()); }
// Usage: uint64_t hashed_value = Hash::combine(33, false, "ABC", 458L, 3u, 'x');
template <typename Tp, typename... TailT>
static uint64_t combine(Tp&& arg, TailT&&... args);
// fnv1a_hasher.update_all(33, false, "ABC")` is shorthand for calling fnv1a_hasher.update(...)
// for each value in the same order
template <typename Tp, typename... TailT>
fnv1a_hasher& update_all(Tp&& arg, TailT&&... args);
uint64_t digest() const { return m_result; }
private:
static constexpr uint64_t kFnv1a64OffsetBasis = 0xcbf29ce484222325;
static constexpr uint64_t kFnv1a64Prime = 0x100000001b3;
uint64_t m_result = kFnv1a64OffsetBasis;
};
template <typename Tp, typename std::enable_if_t<std::is_arithmetic<Tp>::value, bool>>
fnv1a_hasher&
fnv1a_hasher::update(Tp data)
{
return update(reinterpret_cast<const char*>(&data), sizeof(data));
}
template <typename Tp>
fnv1a_hasher&
fnv1a_hasher::update(const std::optional<Tp>& data)
{
return (data) ? update(*data) : *this;
}
inline fnv1a_hasher&
fnv1a_hasher::update(const char* str)
{
constexpr auto max_n = 4096;
size_t n = 0;
for(const auto* p = str; *p != 0; ++p)
{
update(*p);
if(++n >= max_n) break; // prevent infinite loop
}
return *this;
}
inline fnv1a_hasher&
fnv1a_hasher::update(const char* data, size_t size)
{
for(size_t i = 0; i < size; ++i)
{
m_result ^= static_cast<uint8_t>(data[i]);
// Note: Arithmetic overflow of unsigned integers is well defined in C++ standard unlike
// signed integers.
m_result *= kFnv1a64Prime;
}
return *this;
}
template <typename Tp, typename... TailT>
uint64_t
fnv1a_hasher::combine(Tp&& arg, TailT&&... args)
{
return fnv1a_hasher{}.update_all(std::forward<Tp>(arg), std::forward<TailT>(args)...).digest();
}
template <typename Tp, typename... TailT>
fnv1a_hasher&
fnv1a_hasher::update_all(Tp&& arg, TailT&&... args)
{
update(arg);
if constexpr(sizeof...(TailT) > 0) update_all(std::forward<TailT>(args)...);
return *this;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,230 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/logging.hpp"
#include "lib/common/environment.hpp"
#include "lib/common/filesystem.hpp"
#include <fmt/format.h>
#include <glog/logging.h>
#include <glog/vlog_is_on.h>
#include <fstream>
#include <mutex>
#include <string>
#include <unordered_map>
namespace rocprofiler
{
namespace common
{
namespace
{
namespace fs = ::rocprofiler::common::filesystem;
void
install_failure_signal_handler()
{
static auto _once = std::once_flag{};
std::call_once(_once, []() { google::InstallFailureSignalHandler(); });
}
struct log_level_info
{
int32_t google_level = 0;
int32_t verbose_level = 0;
};
env_store
get_glog_env_config(const logging_config& cfg)
{
auto as_env_config = [](std::string_view _var, auto _val) {
return env_config{std::string{_var}, fmt::format("{}", _val), 1};
};
auto _data = std::vector<env_config>{
as_env_config("GLOG_minloglevel", cfg.loglevel),
as_env_config("GLOG_logtostderr", cfg.logtostderr ? 1 : 0),
as_env_config("GLOG_alsologtostderr", cfg.alsologtostderr ? 1 : 0),
as_env_config("GLOG_stderrthreshold", cfg.loglevel),
as_env_config("GLOG_v", cfg.vlog_level),
};
if(!cfg.logdir.empty())
{
_data.emplace_back(as_env_config("GOOGLE_LOG_DIR", cfg.logdir));
_data.emplace_back(as_env_config("GLOG_log_dir", cfg.logdir));
}
if(!cfg.vlog_modules.empty())
{
_data.emplace_back(as_env_config("GLOG_vmodule", cfg.vlog_modules));
}
return env_store{std::move(_data)};
}
} // namespace
void
init_logging(std::string_view env_prefix, logging_config cfg)
{
static auto _once = std::once_flag{};
std::call_once(_once, [env_prefix, &cfg]() {
auto get_argv0 = []() {
auto ifs = std::ifstream{"/proc/self/cmdline"};
auto sarg = std::string{};
while(ifs && !ifs.eof())
{
ifs >> sarg;
if(!sarg.empty()) break;
}
return sarg;
};
auto to_lower = [](std::string val) {
for(auto& itr : val)
itr = tolower(itr);
return val;
};
const auto env_opts = std::unordered_map<std::string_view, log_level_info>{
{"trace", {google::INFO, ROCP_LOG_LEVEL_TRACE}},
{"info", {google::INFO, ROCP_LOG_LEVEL_INFO}},
{"warning", {google::WARNING, ROCP_LOG_LEVEL_WARNING}},
{"error", {google::ERROR, ROCP_LOG_LEVEL_ERROR}},
{"fatal", {google::FATAL, ROCP_LOG_LEVEL_NONE}}};
auto supported = std::vector<std::string>{};
supported.reserve(env_opts.size());
for(auto itr : env_opts)
supported.emplace_back(itr.first);
if(cfg.name.empty()) cfg.name = to_lower(std::string{env_prefix});
cfg.logdir = get_env(fmt::format("{}_LOG_DIR", env_prefix), cfg.logdir);
cfg.vlog_modules = get_env(fmt::format("{}_vmodule", env_prefix), cfg.vlog_modules);
cfg.logtostderr = cfg.logdir.empty(); // log to stderr if no log dir set
// cfg.alsologtostderr = !cfg.logdir.empty(); // log to file if log dir set
auto loglvl = to_lower(common::get_env(fmt::format("{}_LOG_LEVEL", env_prefix), ""));
// default to warning
auto& loglvl_v = cfg.loglevel;
auto& vlog_level = cfg.vlog_level;
if(!loglvl.empty() && loglvl.find_first_not_of("-0123456789") == std::string::npos)
{
auto val = std::stol(loglvl);
if(val < 0)
{
loglvl_v = google::FATAL;
vlog_level = val;
}
else
{
// default to trace in case val > ROCP_LOG_LEVEL_TRACE
auto itr = env_opts.at("trace");
for(auto oitr : env_opts)
{
if(oitr.second.verbose_level == val)
{
itr = oitr.second;
break;
}
}
loglvl_v = itr.google_level;
vlog_level = itr.verbose_level;
}
}
else if(!loglvl.empty())
{
if(env_opts.find(loglvl) == env_opts.end())
throw std::runtime_error{fmt::format(
"invalid specifier for {}_LOG_LEVEL: {}. Supported: {}",
env_prefix,
loglvl,
fmt::format("{}", fmt::join(supported.begin(), supported.end(), ", ")))};
else
{
loglvl_v = env_opts.at(loglvl).google_level;
vlog_level = env_opts.at(loglvl).verbose_level;
}
}
auto _env_store = get_glog_env_config(cfg);
update_logging(cfg);
_env_store.push();
if(!google::IsGoogleLoggingInitialized())
{
static auto argv0 = get_argv0();
// Prevent glog from crashing if vmodule is empty
if(FLAGS_vmodule.empty()) FLAGS_vmodule = " ";
google::InitGoogleLogging(argv0.c_str());
// Swap out memory to avoid leaking the string
if(!FLAGS_vmodule.empty()) std::string{}.swap(FLAGS_vmodule);
if(!FLAGS_log_dir.empty()) std::string{}.swap(FLAGS_log_dir);
}
update_logging(cfg);
ROCP_INFO << "logging initialized via " << fmt::format("{}_LOG_LEVEL", env_prefix)
<< ". Log Level: " << loglvl << ". Verbose Log Level: " << vlog_level;
_env_store.pop(false);
});
}
void
update_logging(const logging_config& cfg)
{
static auto _mtx = std::mutex{};
auto _lk = std::unique_lock<std::mutex>{_mtx};
FLAGS_timestamp_in_logfile_name = false;
FLAGS_logtostderr = cfg.logtostderr;
FLAGS_minloglevel = cfg.loglevel;
FLAGS_stderrthreshold = cfg.loglevel;
FLAGS_alsologtostderr = cfg.alsologtostderr;
FLAGS_v = cfg.vlog_level;
// if(!cfg.logdir.empty()) FLAGS_log_dir = cfg.logdir.c_str();
if(cfg.install_failure_handler) install_failure_signal_handler();
if(!cfg.logdir.empty() && !fs::exists(cfg.logdir))
{
fs::create_directories(cfg.logdir);
if(cfg.logdir_gitignore)
{
auto ignore = fs::path{cfg.logdir} / ".gitignore";
if(!fs::exists(ignore))
{
std::ofstream ofs{ignore.string()};
ofs << "/**" << std::flush;
}
}
}
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,84 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include <glog/logging.h>
#include <cstdint>
#include <optional>
#include <string_view>
#define ROCP_LOG_LEVEL_TRACE 4
#define ROCP_LOG_LEVEL_INFO 3
#define ROCP_LOG_LEVEL_WARNING 2
#define ROCP_LOG_LEVEL_ERROR 1
#define ROCP_LOG_LEVEL_NONE 0
#define ROCP_TRACE VLOG(ROCP_LOG_LEVEL_TRACE)
#define ROCP_INFO LOG(INFO)
#define ROCP_WARNING LOG(WARNING)
#define ROCP_ERROR LOG(ERROR)
#define ROCP_FATAL LOG(FATAL)
#define ROCP_DFATAL DLOG(FATAL)
#define ROCP_TRACE_IF(CONDITION) VLOG_IF(ROCP_LOG_LEVEL_TRACE, (CONDITION))
#define ROCP_INFO_IF(CONDITION) LOG_IF(INFO, (CONDITION))
#define ROCP_WARNING_IF(CONDITION) LOG_IF(WARNING, (CONDITION))
#define ROCP_ERROR_IF(CONDITION) LOG_IF(ERROR, (CONDITION))
#define ROCP_FATAL_IF(CONDITION) LOG_IF(FATAL, (CONDITION))
#define ROCP_DFATAL_IF(CONDITION) DLOG_IF(FATAL, (CONDITION))
#if defined(ROCPROFILER_CI)
# define ROCP_CI_LOG_IF(NON_CI_LEVEL, ...) ROCP_FATAL_IF(__VA_ARGS__)
# define ROCP_CI_LOG(NON_CI_LEVEL, ...) ROCP_FATAL
#else
# define ROCP_CI_LOG_IF(NON_CI_LEVEL, ...) ROCP_##NON_CI_LEVEL##_IF(__VA_ARGS__)
# define ROCP_CI_LOG(NON_CI_LEVEL, ...) ROCP_##NON_CI_LEVEL
#endif
namespace rocprofiler
{
namespace common
{
struct logging_config
{
bool install_failure_handler = false;
bool logtostderr = true;
bool alsologtostderr = false;
bool logdir_gitignore = false; // add .gitignore to logdir
int32_t loglevel = google::WARNING;
int32_t vlog_level = ROCP_LOG_LEVEL_WARNING;
std::string vlog_modules = {};
std::string name = {};
std::string logdir = {};
};
void
init_logging(std::string_view env_prefix, logging_config cfg = logging_config{});
void
update_logging(const logging_config& cfg);
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,378 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/md5sum.hpp"
#include <cstdio>
#include <iomanip>
#include <string_view>
namespace rocprofiler
{
namespace common
{
namespace
{
using size_type = typename md5sum::size_type;
// Constants for md5sumTransform routine.
constexpr uint32_t S11 = 7;
constexpr uint32_t S12 = 12;
constexpr uint32_t S13 = 17;
constexpr uint32_t S14 = 22;
constexpr uint32_t S21 = 5;
constexpr uint32_t S22 = 9;
constexpr uint32_t S23 = 14;
constexpr uint32_t S24 = 20;
constexpr uint32_t S31 = 4;
constexpr uint32_t S32 = 11;
constexpr uint32_t S33 = 16;
constexpr uint32_t S34 = 23;
constexpr uint32_t S41 = 6;
constexpr uint32_t S42 = 10;
constexpr uint32_t S43 = 15;
constexpr uint32_t S44 = 21;
// low level logic operations
static inline uint32_t
F(uint32_t x, uint32_t y, uint32_t z);
static inline uint32_t
G(uint32_t x, uint32_t y, uint32_t z);
static inline uint32_t
H(uint32_t x, uint32_t y, uint32_t z);
static inline uint32_t
I(uint32_t x, uint32_t y, uint32_t z);
static inline uint32_t
rotate_left(uint32_t x, int n);
static inline void
FF(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac);
static inline void
GG(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac);
static inline void
HH(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac);
static inline void
II(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac);
// F, G, H and I are basic md5sum functions.
inline uint32_t
F(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) | (~x & z);
}
inline uint32_t
G(uint32_t x, uint32_t y, uint32_t z)
{
return (x & z) | (y & ~z);
}
inline uint32_t
H(uint32_t x, uint32_t y, uint32_t z)
{
return x ^ y ^ z;
}
inline uint32_t
I(uint32_t x, uint32_t y, uint32_t z)
{
return y ^ (x | ~z);
}
// rotate_left rotates x left n bits.
inline uint32_t
rotate_left(uint32_t x, int n)
{
return (x << n) | (x >> (32 - n));
}
// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
inline void
FF(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac)
{
a = rotate_left(a + F(b, c, d) + x + ac, s) + b;
}
inline void
GG(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac)
{
a = rotate_left(a + G(b, c, d) + x + ac, s) + b;
}
inline void
HH(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac)
{
a = rotate_left(a + H(b, c, d) + x + ac, s) + b;
}
inline void
II(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac)
{
a = rotate_left(a + I(b, c, d) + x + ac, s) + b;
}
// decodes input (unsigned char) into output (uint32_t). Assumes len is a multiple of 4.
void
decode(uint32_t output[], const uint8_t input[], size_type len)
{
for(unsigned int i = 0, j = 0; j < len; i++, j += 4)
output[i] = ((uint32_t) input[j]) | (((uint32_t) input[j + 1]) << 8) |
(((uint32_t) input[j + 2]) << 16) | (((uint32_t) input[j + 3]) << 24);
}
// encodes input (uint32_t) into output (unsigned char). Assumes len is
// a multiple of 4.
void
encode(uint8_t output[], const uint32_t input[], size_type len)
{
for(size_type i = 0, j = 0; j < len; i++, j += 4)
{
output[j] = input[i] & 0xff;
output[j + 1] = (input[i] >> 8) & 0xff;
output[j + 2] = (input[i] >> 16) & 0xff;
output[j + 3] = (input[i] >> 24) & 0xff;
}
}
} // namespace
// apply md5sum algo on a block
void
md5sum::transform(const uint8_t block[blocksize])
{
uint32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16];
decode(x, block, blocksize);
/* Round 1 */
FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
// Zeroize sensitive information.
memset(x, 0, sizeof x);
}
md5sum&
md5sum::update(std::string_view inp)
{
return update(inp.data(), inp.length());
}
// md5sum block update operation. Continues an md5sum message-digest
// operation, processing another message block
md5sum&
md5sum::update(const unsigned char input[], size_type length)
{
// compute number of bytes mod 64
size_type index = count[0] / 8 % blocksize;
// Update number of bits
if((count[0] += (length << 3)) < (length << 3)) count[1]++;
count[1] += (length >> 29);
// number of bytes we need to fill in buffer
size_type firstpart = 64 - index;
size_type i = 0;
// transform as many times as possible.
if(length >= firstpart)
{
// fill buffer first, transform
memcpy(&buffer[index], input, firstpart);
transform(buffer.data());
// transform chunks of blocksize (64 bytes)
for(i = firstpart; i + blocksize <= length; i += blocksize)
transform(&input[i]);
index = 0;
}
// buffer remaining input
memcpy(&buffer[index], &input[i], length - i);
return *this;
}
// for convenience provide a verson with signed char
md5sum&
md5sum::update(const char input[], size_type length)
{
return update((const unsigned char*) input, length);
}
// md5sum finalization. Ends an md5sum message-digest operation, writing the
// the message digest and zeroizing the context.
md5sum&
md5sum::finalize()
{
static unsigned char padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if(!finalized)
{
// Save number of bits
unsigned char bits[8];
encode(bits, count.data(), 8);
// pad out to 56 mod 64.
size_type index = count[0] / 8 % 64;
size_type padLen = (index < 56) ? (56 - index) : (120 - index);
update(padding, padLen);
// Append length (before padding)
update(bits, 8);
// Store state in digest
encode(digest.data(), state.data(), 16);
// Zeroize sensitive information.
memset(buffer.data(), 0, sizeof buffer);
memset(count.data(), 0, sizeof count);
finalized = true;
}
return *this;
}
// return hex representation of digest as string
std::string
md5sum::hexdigest() const
{
if(!finalized) return std::string{};
char buf[33];
for(int i = 0; i < 16; i++)
snprintf(buf + i * 2, 3, "%02x", digest[i]);
buf[32] = '\0';
return std::string(buf);
}
std::string
md5sum::hexliteral() const
{
if(!finalized) return std::string{};
auto _oss = std::ostringstream{};
_oss << "X'";
for(auto itr : rawdigest())
_oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(itr);
_oss << "'";
return _oss.str();
}
std::ostream&
operator<<(std::ostream& out, md5sum md5)
{
return out << md5.hexdigest();
}
std::string
compute_md5sum(std::string_view inp)
{
return md5sum{inp}.finalize().hexdigest();
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,138 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/mpl.hpp"
#include <array>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
// helper function
std::string
compute_md5sum(std::string_view inp);
// helper function for array of string, string_view, or const char*
template <template <typename, typename...> class ContainerT, typename Tp, typename... TailT>
std::string
compute_md5sum(const ContainerT<Tp, TailT...>& inp,
std::enable_if_t<mpl::is_string_type<Tp>::value, int> = 0);
// a small class for calculating MD5 hashes of strings or byte arrays
//
// usage:
// 1) feed it blocks of uchars with update(...)
// 2) finalize()
// 3) get hexdigest() string
// or
// md5sum{...}.hexdigest()
//
// assumes that char is 8 bit and int is 32 bit
class md5sum
{
public:
using size_type = uint32_t; // must be 32bit
using raw_digest_t = std::array<uint8_t, 16>;
static constexpr int blocksize = 64;
template <typename Tp, typename... Args>
explicit md5sum(Tp&& arg, Args&&... args);
md5sum() = default;
~md5sum() = default;
md5sum(const md5sum&) = default;
md5sum(md5sum&&) = default;
md5sum& operator=(const md5sum&) = default;
md5sum& operator=(md5sum&&) = default;
md5sum& update(std::string_view inp);
md5sum& update(const unsigned char* buf, size_type length);
md5sum& update(const char* buf, size_type length);
md5sum& finalize();
std::string hexdigest() const;
std::string hexliteral() const;
raw_digest_t rawdigest() const { return digest; }
template <typename Tp, typename Up = std::enable_if_t<std::is_arithmetic<Tp>::value, int>>
md5sum& update(Tp inp);
friend std::ostream& operator<<(std::ostream&, md5sum md5);
private:
void transform(const uint8_t block[blocksize]);
bool finalized = false;
// 64bit counter for number of bits (lo, hi)
std::array<uint32_t, 2> count = {0, 0};
std::array<uint8_t, blocksize> buffer{}; // overflow bytes from last 64 byte chunk
// digest so far, initialized to magic initialization constants.
std::array<uint32_t, 4> state = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
std::array<uint8_t, 16> digest{}; // result
};
template <typename Tp, typename... Args>
md5sum::md5sum(Tp&& arg, Args&&... args)
{
auto _update = [&](auto&& _val) {
using value_type = common::mpl::unqualified_type_t<decltype(_val)>;
static_assert(!std::is_pointer<value_type>::value,
"constructor cannot be called with pointer argument");
update(std::forward<decltype(_val)>(_val));
};
_update(std::forward<Tp>(arg));
(_update(std::forward<Args>(args)), ...);
finalize();
}
template <typename Tp, typename Up>
md5sum&
md5sum::update(Tp inp)
{
static_assert(std::is_arithmetic<Tp>::value, "expected arithmetic type");
return update(reinterpret_cast<const char*>(&inp), sizeof(Tp));
}
template <template <typename, typename...> class ContainerT, typename Tp, typename... TailT>
std::string
compute_md5sum(const ContainerT<Tp, TailT...>& inp,
std::enable_if_t<mpl::is_string_type<Tp>::value, int>)
{
auto _val = md5sum{};
for(const auto& itr : inp)
_val.update(std::string_view{inp});
_val.finalize();
return _val.hexdigest();
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,7 @@
#
# add container sources and headers to common library target
#
set(memory_headers deleter.hpp pool.hpp pool_allocator.hpp stateless_allocator.hpp)
set(memory_sources)
target_sources(rocprofiler-sdk-common-library PRIVATE ${memory_sources} ${memory_headers})
@@ -0,0 +1,48 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
namespace rocprofiler
{
namespace common
{
namespace memory
{
// this type is a template parameter for allocators to execute a function when the allocator
// destroys an object. In the rocprofiler library, this is used to ensure
// rocprofiler::registration::finalize is called on the first instance of a data structure being
// destroyed after main exits
template <typename Tp>
struct deleter;
// default deleter type
template <>
struct deleter<void>
{
constexpr void operator()() const {}
};
} // namespace memory
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,103 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <stack>
#include <stdexcept>
namespace rocprofiler
{
namespace common
{
namespace memory
{
template <size_t BlockSize, size_t ReservedBlocks = 0>
class pool
{
public:
explicit pool(size_t size)
: m_size(size)
{
for(size_t i = 0; i < ReservedBlocks; i++)
{
append();
}
}
void* allocate()
{
if(m_addrs.empty())
{
append();
}
auto* ptr = m_addrs.top();
m_addrs.pop();
return ptr;
}
void deallocate(void* ptr) { m_addrs.push(ptr); }
void rebind(size_t size)
{
if(!(m_addrs.empty() && m_blocks.empty()))
{
ROCP_FATAL << "cannot call pool::rebind() after alloc";
}
m_size = size;
}
private:
// Refill the address stack by allocating another block of memory
void append()
{
auto block = std::make_unique<uint8_t[]>(BlockSize);
auto total_size = BlockSize % m_size == 0 ? BlockSize : BlockSize - m_size;
// Divide the block into chunks of m_size bytes, and add their addrs
for(size_t i = 0; i < total_size; i += m_size)
{
m_addrs.push(&block.get()[i]);
}
// Keep the memory of the block alive by adding it to our stack
m_blocks.push(std::move(block));
}
private:
size_t m_size = {};
std::stack<void*> m_addrs = {};
std::stack<std::unique_ptr<uint8_t[]>> m_blocks = {};
};
} // namespace memory
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,176 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/memory/deleter.hpp"
#include "lib/common/memory/pool.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <memory>
namespace rocprofiler
{
namespace common
{
namespace memory
{
// template <typename Tp, size_t Alignment, size_t BlockSize, size_t ReservedBlocks, typename
// DeleterT> pool_allocator<Tp, Alignment, BlockSize, ReservedBlocks, DeleterT>::
template <typename Tp,
size_t Alignment = 64,
size_t BlockSize = 4096,
size_t ReservedBlocks = 0,
typename DeleterT = deleter<void>>
class pool_allocator
{
public:
using value_type = Tp;
using pointer = Tp*;
using const_pointer = const Tp*;
using reference = Tp&;
using const_reference = const Tp&;
using size_type = size_t;
using difference_type = ptrdiff_t;
using propagate_on_container_move_assignment = std::false_type;
using is_always_equal = value_type;
pool_allocator() = default;
// Rebind copy constructor
template <typename Up>
pool_allocator(const pool_allocator<Up>& rhs);
pool_allocator(const pool_allocator& rhs) = default;
pool_allocator(pool_allocator&& rhs) noexcept = default;
pool_allocator& operator=(const pool_allocator& rhs) = default;
pool_allocator& operator=(pool_allocator&& rhs) noexcept = default;
value_type* allocate(size_t n);
void deallocate(value_type* ptr, size_t n);
void construct(value_type* const _p, const value_type& _v) const;
void construct(value_type* const _p, value_type&& _v) const;
void construct_at(value_type* const _p, const value_type& _v) const;
void construct_at(value_type* const _p, value_type&& _v) const;
void destroy(value_type* const _p) const;
void destroy_at(value_type* const _p) const;
template <typename Up>
struct rebind
{
using other = pool_allocator<Up, Alignment, BlockSize, ReservedBlocks>;
};
private:
using pool_type = pool<BlockSize, ReservedBlocks>;
std::shared_ptr<pool_type> m_pool = std::make_shared<pool_type>(sizeof(value_type));
};
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedBlocks, typename DeleterT>
template <typename Up>
pool_allocator<Tp, AlignV, BlockSz, ReservedBlocks, DeleterT>::pool_allocator(
const pool_allocator<Up>& rhs)
: m_pool{rhs.m_pool}
{
m_pool->rebind(sizeof(value_type));
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedBlocks, typename DeleterT>
typename pool_allocator<Tp, AlignV, BlockSz, ReservedBlocks, DeleterT>::value_type*
pool_allocator<Tp, AlignV, BlockSz, ReservedBlocks, DeleterT>::allocate(size_t n)
{
if(n > 1)
{
return static_cast<value_type*>(::aligned_alloc(AlignV, sizeof(value_type) * n));
}
return static_cast<value_type*>(m_pool->allocate());
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedBlocks, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedBlocks, DeleterT>::deallocate(value_type* ptr, size_t n)
{
DeleterT{}();
if(n > 1)
{
::free(ptr);
return;
}
m_pool->deallocate(ptr);
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedV, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedV, DeleterT>::construct(value_type* const _p,
const value_type& _v) const
{
::new((void*) _p) value_type{_v};
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedV, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedV, DeleterT>::construct(value_type* const _p,
value_type&& _v) const
{
::new((void*) _p) value_type{std::move(_v)};
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedV, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedV, DeleterT>::construct_at(value_type* const _p,
const value_type& _v) const
{
::new((void*) _p) value_type{_v};
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedV, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedV, DeleterT>::construct_at(value_type* const _p,
value_type&& _v) const
{
::new((void*) _p) value_type{std::move(_v)};
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedV, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedV, DeleterT>::destroy(value_type* const _p) const
{
DeleterT{}();
_p->~value_type();
}
template <typename Tp, size_t AlignV, size_t BlockSz, size_t ReservedV, typename DeleterT>
void
pool_allocator<Tp, AlignV, BlockSz, ReservedV, DeleterT>::destroy_at(value_type* const _p) const
{
DeleterT{}();
_p->~value_type();
}
} // namespace memory
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,183 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/memory/deleter.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <new>
#include <stdexcept>
namespace rocprofiler
{
namespace common
{
namespace memory
{
template <typename Tp, size_t Alignment = 64, typename DeleterT = deleter<void>>
class stateless_allocator
{
public:
using value_type = Tp;
using pointer = Tp*;
using const_pointer = const Tp*;
using reference = Tp&;
using const_reference = const Tp&;
using size_type = size_t;
using difference_type = ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
template <typename Up>
struct rebind
{
using other = stateless_allocator<Up, Alignment, DeleterT>;
};
stateless_allocator() = default;
stateless_allocator(const stateless_allocator& rhs) = default;
stateless_allocator(stateless_allocator&& rhs) noexcept = default;
stateless_allocator& operator=(const stateless_allocator& rhs) = default;
stateless_allocator& operator=(stateless_allocator&& rhs) noexcept = default;
template <typename Up>
stateless_allocator(const stateless_allocator<Up, Alignment, DeleterT>& rhs);
static Tp* allocate(size_t n);
static void deallocate(Tp* ptr, size_t n);
static void construct(value_type* const _p, const value_type& _v);
static void construct(value_type* const _p, value_type&& _v);
static void construct_at(value_type* const _p, const value_type& _v);
static void construct_at(value_type* const _p, value_type&& _v);
static void destroy(value_type* const _p);
static void destroy_at(value_type* const _p);
};
template <typename Tp, size_t Alignment, typename DeleterT>
template <typename Up>
stateless_allocator<Tp, Alignment, DeleterT>::stateless_allocator(
const stateless_allocator<Up, Alignment, DeleterT>& rhs)
{
(void) rhs;
}
template <typename Tp, size_t Alignment, typename DeleterT>
Tp*
stateless_allocator<Tp, Alignment, DeleterT>::allocate(size_t n)
{
constexpr auto alignment_v = Alignment / sizeof(void*);
Tp* ptr = nullptr;
if constexpr(sizeof(Tp) >= alignment_v && sizeof(Tp) % alignment_v == 0)
ptr = static_cast<Tp*>(::aligned_alloc(Alignment / sizeof(void*), sizeof(Tp) * n));
else
ptr = static_cast<Tp*>(::malloc(sizeof(Tp) * n));
if(ptr) return ptr;
throw std::bad_alloc{};
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::deallocate(Tp* ptr, size_t n)
{
(void) n;
::free(ptr);
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::construct(value_type* const _p, const value_type& _v)
{
::new((void*) _p) value_type{_v};
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::construct(value_type* const _p, value_type&& _v)
{
::new((void*) _p) value_type{std::move(_v)};
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::construct_at(value_type* const _p,
const value_type& _v)
{
::new((void*) _p) value_type{_v};
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::construct_at(value_type* const _p, value_type&& _v)
{
::new((void*) _p) value_type{std::move(_v)};
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::destroy(value_type* const _p)
{
DeleterT{}();
_p->~value_type();
}
template <typename Tp, size_t Alignment, typename DeleterT>
void
stateless_allocator<Tp, Alignment, DeleterT>::destroy_at(value_type* const _p)
{
DeleterT{}();
_p->~value_type();
}
template <typename LhsTp,
size_t LhsAlignment,
typename LhsDeleterT,
typename RhsTp,
size_t RhsAlignment,
typename RhsDeleterT>
constexpr bool
operator==(const stateless_allocator<LhsTp, LhsAlignment, LhsDeleterT>&,
const stateless_allocator<RhsTp, RhsAlignment, RhsDeleterT>&)
{
return true;
}
template <typename LhsTp,
size_t LhsAlignment,
typename LhsDeleterT,
typename RhsTp,
size_t RhsAlignment,
typename RhsDeleterT>
constexpr bool
operator!=(const stateless_allocator<LhsTp, LhsAlignment, LhsDeleterT>&,
const stateless_allocator<RhsTp, RhsAlignment, RhsDeleterT>&)
{
return false;
}
} // namespace memory
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,175 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/details/mpl.hpp"
#include <cstddef>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
namespace mpl
{
// dummy tuple with low instantiation cost
template <typename... Tp>
using type_list = impl::type_list<Tp...>;
/// get the index of a type in expansion
template <typename Tp, typename Type>
struct index_of;
template <typename Tp, template <typename...> class Tuple, typename... Types>
struct index_of<Tp, Tuple<Tp, Types...>>
{
static constexpr size_t value = 0;
};
template <typename Tp, typename Head, template <typename...> class Tuple, typename... Tail>
struct index_of<Tp, Tuple<Head, Tail...>>
{
static constexpr size_t value = 1 + index_of<Tp, Tuple<Tail...>>::value;
};
/// get the index of a type in expansion
template <typename Tp>
struct size_of;
template <typename... Tp>
struct size_of<type_list<Tp...>>
{
static constexpr size_t value = sizeof...(Tp);
};
template <typename... Tp>
struct size_of<std::tuple<Tp...>>
{
static constexpr size_t value = sizeof...(Tp);
};
// check if type is in expansion
//
template <typename... Tp>
struct is_one_of
{
static constexpr bool value = false;
};
template <typename F, typename S, template <typename...> class Tuple, typename... T>
struct is_one_of<F, S, Tuple<T...>>
{
static constexpr bool value = std::is_same<F, S>::value || is_one_of<F, Tuple<T...>>::value;
};
template <typename F, typename S, template <typename...> class Tuple, typename... T>
struct is_one_of<F, Tuple<S, T...>>
{
static constexpr bool value = is_one_of<F, S, Tuple<T...>>::value;
};
template <typename Tp>
struct is_pair_impl
{
static constexpr auto value = false;
};
template <typename LhsT, typename RhsT>
struct is_pair_impl<std::pair<LhsT, RhsT>>
{
static constexpr auto value = true;
};
template <typename Tp>
struct is_pair : is_pair_impl<std::remove_cv_t<std::remove_reference_t<std::decay_t<Tp>>>>
{};
template <typename Tp>
struct is_string_type_impl
{
static constexpr auto value =
is_one_of<Tp, type_list<const char*, char*, std::string, std::string_view>>::value;
};
template <typename Tp>
struct is_string_type
: is_string_type_impl<std::remove_cv_t<std::remove_reference_t<std::decay_t<Tp>>>>
{};
template <typename, typename = void>
constexpr bool is_type_complete_v = false; // NOLINT(misc-definitions-in-headers)
template <typename T> // NOLINTNEXTLINE(misc-definitions-in-headers)
constexpr bool is_type_complete_v<T, std::void_t<decltype(sizeof(T))>> = true;
template <typename Tp, size_t N>
struct indirection_level_impl_n
{
using value_type = std::conditional_t<std::is_function<Tp>::value, Tp, std::decay_t<Tp>>;
static_assert(!std::is_pointer<value_type>::value, "missing overload");
static constexpr size_t value = N;
};
template <typename Tp, size_t N>
struct indirection_level_impl_n<Tp*, N> : indirection_level_impl_n<Tp, N + 1>
{};
template <typename Tp, size_t N>
struct indirection_level_impl_n<Tp* const, N> : indirection_level_impl_n<Tp, N + 1>
{};
template <typename Tp>
struct indirection_level
: indirection_level_impl_n<std::remove_cv_t<std::remove_reference_t<std::decay_t<Tp>>>, 0>
{};
template <typename Tp>
struct unqualified_type
{
using type = std::remove_reference_t<std::remove_cv_t<std::decay_t<Tp>>>;
};
template <typename Tp>
using unqualified_type_t = typename unqualified_type<Tp>::type;
template <typename Tp>
struct assert_false
{
static constexpr auto value = false;
};
template <typename InTuple>
using reverse = typename impl::reverse<InTuple, void>::type;
template <typename Tp>
using function_traits = impl::function_traits<Tp>;
template <typename Tp>
using function_args_t = typename impl::function_traits<Tp>::args_type;
} // namespace mpl
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,86 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/defines.hpp"
#include <functional>
#include <utility>
namespace rocprofiler
{
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
@@ -0,0 +1,229 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/sha256.hpp"
#include "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include "lib/common/mpl.hpp"
#include <unistd.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
namespace rocprofiler
{
namespace common
{
sha256::sha256() { reset(); }
sha256::sha256(const std::string& data)
{
reset();
update(data);
finalize();
}
void
sha256::update(const uint8_t* data, size_t len)
{
ROCP_CI_LOG_IF(INFO, m_finalized) << "attempt to update sha256 after finalized";
if(m_finalized) return;
for(size_t i = 0; i < len; ++i)
{
m_data[m_datalen++] = data[i];
if(m_datalen == 64)
{
transform();
m_bitlen += 512;
m_datalen = 0;
}
}
}
void
sha256::update(const std::string& data)
{
ROCP_CI_LOG_IF(INFO, m_finalized) << "attempt to update sha256 after finalized";
if(m_finalized) return;
update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
}
void
sha256::finalize()
{
if(m_finalized) return;
uint32_t idx = m_datalen;
if(m_datalen < 56)
{
m_data[idx++] = 0x80;
while(idx < 56)
m_data[idx++] = 0x00;
}
else
{
m_data[idx++] = 0x80;
while(idx < 64)
m_data[idx++] = 0x00;
transform();
std::memset(m_data.data(), 0, 56);
}
m_bitlen += m_datalen * 8;
for(int j = 0; j < 8; ++j)
m_data[63 - j] = static_cast<uint8_t>((m_bitlen >> (8 * j)) & 0xFF);
transform();
m_finalized = true;
}
std::string
sha256::hexdigest()
{
finalize();
auto oss = std::ostringstream{};
for(int j = 0; j < 8; ++j)
oss << std::hex << std::setfill('0') << std::setw(8) << m_state[j];
return oss.str();
}
std::array<uint32_t, 8>
sha256::rawdigest()
{
finalize();
return m_state;
}
uint32_t
sha256::rotr(uint32_t x, uint32_t n)
{
return (x >> n) | (x << (32 - n));
}
uint32_t
sha256::ch(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) ^ (~x & z);
}
uint32_t
sha256::maj(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) ^ (x & z) ^ (y & z);
}
uint32_t
sha256::sig0(uint32_t x)
{
return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
}
uint32_t
sha256::sig1(uint32_t x)
{
return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
}
uint32_t
sha256::theta0(uint32_t x)
{
return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3);
}
uint32_t
sha256::theta1(uint32_t x)
{
return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10);
}
void
sha256::transform()
{
uint32_t m[64];
for(int i = 0; i < 16; ++i)
{
m[i] = (m_data[i * 4] << 24) | (m_data[i * 4 + 1] << 16) | (m_data[i * 4 + 2] << 8) |
(m_data[i * 4 + 3]);
}
for(int i = 16; i < 64; ++i)
{
m[i] = theta1(m[i - 2]) + m[i - 7] + theta0(m[i - 15]) + m[i - 16];
}
uint32_t a = m_state[0];
uint32_t b = m_state[1];
uint32_t c = m_state[2];
uint32_t d = m_state[3];
uint32_t e = m_state[4];
uint32_t f = m_state[5];
uint32_t g = m_state[6];
uint32_t h = m_state[7];
for(int i = 0; i < 64; ++i)
{
uint32_t t1 = h + sig1(e) + ch(e, f, g) + m_k[i] + m[i];
uint32_t t2 = sig0(a) + maj(a, b, c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
m_state[0] += a;
m_state[1] += b;
m_state[2] += c;
m_state[3] += d;
m_state[4] += e;
m_state[5] += f;
m_state[6] += g;
m_state[7] += h;
}
void
sha256::reset()
{
m_state = {0x6a09e667,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19};
m_datalen = 0;
m_bitlen = 0;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,81 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/mpl.hpp"
#include <array>
#include <cstdint>
#include <string>
namespace rocprofiler
{
namespace common
{
// --- SHA-256 Implementation ---
class sha256
{
public:
sha256();
explicit sha256(const std::string& data);
void update(const uint8_t* data, size_t len);
void update(const std::string& data);
void finalize();
std::string hexdigest();
std::array<uint32_t, 8> rawdigest();
private:
bool m_finalized = false;
std::array<uint8_t, 64> m_data = {};
std::array<uint32_t, 8> m_state = {};
uint32_t m_datalen = 0;
uint64_t m_bitlen = 0;
static constexpr std::array<uint32_t, 64> m_k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2};
static uint32_t rotr(uint32_t x, uint32_t n);
static uint32_t ch(uint32_t x, uint32_t y, uint32_t z);
static uint32_t maj(uint32_t x, uint32_t y, uint32_t z);
static uint32_t sig0(uint32_t x);
static uint32_t sig1(uint32_t x);
static uint32_t theta0(uint32_t x);
static uint32_t theta1(uint32_t x);
void transform();
void reset();
};
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,93 @@
// 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.
#include "lib/common/simple_timer.hpp"
#include "lib/common/logging.hpp"
#include <fmt/format.h>
#include <chrono>
#include <cstdint>
#include <string>
#include <string_view>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
simple_timer::simple_timer(std::string&& label)
: m_label{std::move(label)}
{
start();
}
simple_timer::simple_timer(std::string&& label, defer_start)
: m_label{std::move(label)}
{}
simple_timer::~simple_timer()
{
if(m_quiet)
return;
else if(m_end <= m_beg)
stop();
ROCP_WARNING << fmt::format("{} :: {:12.6f} sec", m_label, get());
}
void
simple_timer::start()
{
m_beg = clock_type::now();
}
void
simple_timer::stop()
{
m_end = clock_type::now();
}
double
simple_timer::get() const
{
if(m_end <= m_beg) return {};
return std::chrono::duration_cast<std::chrono::duration<double>>(m_end - m_beg).count();
}
size_t
simple_timer::get_nsec() const
{
if(m_end <= m_beg) return {};
return std::chrono::duration_cast<std::chrono::nanoseconds>(m_end - m_beg).count();
}
std::ostream&
operator<<(std::ostream& _os, const simple_timer& _val)
{
_val.set_quiet(true);
_os << fmt::format("{} :: {:12.6f} sec", _val.label(), _val.get());
return _os;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,67 @@
// 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 "lib/common/defines.hpp"
#include <chrono>
#include <cstdint>
#include <string>
#include <string_view>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
struct defer_start
{};
struct simple_timer
{
using duration_t = std::chrono::duration<double>;
explicit simple_timer(std::string&& label);
explicit simple_timer(std::string&& label, defer_start);
~simple_timer();
void start();
void stop();
double get() const;
size_t get_nsec() const;
std::string_view label() const { return std::string_view{m_label}; }
void set_quiet(bool v) const { m_quiet = v; }
friend std::ostream& operator<<(std::ostream& _os, const simple_timer& _val);
private:
using clock_type = std::chrono::steady_clock;
using time_point_t = std::chrono::time_point<clock_type, std::chrono::nanoseconds>;
std::string m_label = {};
time_point_t m_beg = {};
time_point_t m_end = {};
mutable bool m_quiet = false;
};
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,79 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/static_object.hpp"
#include <array>
#include <cstddef>
#include <functional>
#include <mutex>
#include <stack>
namespace rocprofiler
{
namespace common
{
namespace
{
auto*&
get_static_object_stack()
{
static auto* _v = new std::stack<static_dtor_func_t>{};
return _v;
}
} // namespace
void
destroy_static_objects()
{
static auto _sync = std::mutex{};
auto _lk = std::unique_lock<std::mutex>{_sync};
auto*& _stack = get_static_object_stack();
if(_stack)
{
while(!_stack->empty())
{
auto& itr = _stack->top();
if(itr) itr();
_stack->pop();
}
delete _stack;
_stack = nullptr;
}
}
void
register_static_dtor(static_dtor_func_t&& _func)
{
static auto _sync = std::mutex{};
auto _lk = std::unique_lock<std::mutex>{_sync};
auto*& _stack = get_static_object_stack();
if(_stack)
{
_stack->push(_func);
}
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,136 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include <array>
#include <cstddef>
#include <functional>
#include <mutex>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
using static_dtor_func_t = void (*)();
void
destroy_static_objects();
void
register_static_dtor(static_dtor_func_t&&);
namespace
{
struct anonymous
{};
} // namespace
template <typename Tp>
constexpr size_t
static_buffer_size()
{
constexpr auto type_sz = sizeof(Tp);
constexpr auto void_sz = sizeof(void*);
if constexpr(void_sz > type_sz) return void_sz;
return type_sz;
}
/**
* @brief This struct is used to create static singleton objects which have the properties of a
* heap-allocated static object without a memory leak.
*
* @tparam Tp Data type of singleton
* @tparam ContextT Use to differentiate singletons in different translation units (if using default
* parameter) or ensure the singleton can be accessed in different translation units (not
* recommended) as long as this type is not in an anonymous namespace
*
* This template works by creating a buffer of at least `sizeof(Tp)` bytes in the binary and does a
* placement new into that buffer. The object created is NOT heap allocated, the address of the
* object is an address in between the library load address and the load address + size of library.
*/
template <typename Tp, typename ContextT = anonymous>
struct static_object
{
static_object() = delete;
~static_object() = delete;
static_object(const static_object&) = delete;
static_object(static_object&&) noexcept = delete;
static_object& operator=(const static_object&) = delete;
static_object& operator=(static_object&&) noexcept = delete;
template <typename... Args>
static Tp*& construct(Args&&... args);
static Tp* get() { return m_object; }
static constexpr bool is_trivial_standard_layout();
private:
static Tp* m_object;
static std::array<std::byte, static_buffer_size<Tp>()> m_buffer;
};
template <typename Tp, typename ContextT>
Tp* static_object<Tp, ContextT>::m_object = nullptr;
template <typename Tp, typename ContextT>
std::array<std::byte, static_buffer_size<Tp>()> static_object<Tp, ContextT>::m_buffer = {};
template <typename Tp, typename ContextT>
constexpr bool
static_object<Tp, ContextT>::is_trivial_standard_layout()
{
return (std::is_standard_layout<Tp>::value && std::is_trivially_destructible<Tp>::value);
}
template <typename Tp, typename ContextT>
template <typename... Args>
Tp*&
static_object<Tp, ContextT>::construct(Args&&... args)
{
if constexpr(!is_trivial_standard_layout())
{
static auto _once = std::once_flag{};
std::call_once(_once, []() {
register_static_dtor([]() {
if(static_object<Tp, ContextT>::m_object)
{
static_object<Tp, ContextT>::m_object->~Tp();
static_object<Tp, ContextT>::m_object = nullptr;
}
});
});
}
ROCP_FATAL_IF(m_object)
<< "reconstructing static object. Use get() function to retrieve pointer";
m_object = new(m_buffer.data()) Tp{std::forward<Args>(args)...};
return m_object;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,78 @@
// MIT License
//
// Copyright (c) 2023 Advanced Micro Devices, Inc.
//
// 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 "lib/common/static_tl_object.hpp"
#include "lib/common/scope_destructor.hpp"
#include <array>
#include <cstddef>
#include <functional>
#include <mutex>
#include <stack>
namespace rocprofiler
{
namespace common
{
namespace
{
auto*&
get_static_tl_object_stack()
{
static thread_local auto* _v = new std::stack<static_dtor_func_t>{};
return _v;
}
} // namespace
thread_local auto _static_tl_object_dtor = std::optional<scope_destructor>{};
void
destroy_static_tl_objects()
{
if(auto*& _stack = get_static_tl_object_stack(); _stack)
{
while(!_stack->empty())
{
auto& itr = _stack->top();
if(itr) itr();
_stack->pop();
}
delete _stack;
_stack = nullptr;
}
}
void
register_static_tl_dtor(static_dtor_func_t&& _func)
{
// make sure the thread-local scope destructor exists
if(!_static_tl_object_dtor)
_static_tl_object_dtor = scope_destructor{[]() { destroy_static_tl_objects(); }};
if(auto*& _stack = get_static_tl_object_stack(); _stack)
{
_stack->push(_func);
}
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,120 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include "lib/common/static_object.hpp"
#include <array>
#include <cstddef>
#include <functional>
#include <mutex>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
void
destroy_static_tl_objects();
void
register_static_tl_dtor(static_dtor_func_t&&);
/**
* @brief This struct is used to create static singleton objects which have the properties of a
* heap-allocated static object without a memory leak.
*
* @tparam Tp Data type of singleton
* @tparam ContextT Use to differentiate singletons in different translation units (if using default
* parameter) or ensure the singleton can be accessed in different translation units (not
* recommended) as long as this type is not in an anonymous namespace
*
* This template works by creating a buffer of at least `sizeof(Tp)` bytes in the binary and does a
* placement new into that buffer. The object created is NOT heap allocated, the address of the
* object is an address in between the library load address and the load address + size of library.
*/
template <typename Tp, typename ContextT = anonymous>
struct static_tl_object
{
static_tl_object() = delete;
~static_tl_object() = delete;
static_tl_object(const static_tl_object&) = delete;
static_tl_object(static_tl_object&&) noexcept = delete;
static_tl_object& operator=(const static_tl_object&) = delete;
static_tl_object& operator=(static_tl_object&&) noexcept = delete;
template <typename... Args>
static Tp*& construct(Args&&... args);
static Tp* get() { return m_object; }
static constexpr bool is_trivial_standard_layout();
private:
static thread_local Tp* m_object;
static thread_local std::array<std::byte, static_buffer_size<Tp>()> m_buffer;
};
template <typename Tp, typename ContextT>
thread_local Tp* static_tl_object<Tp, ContextT>::m_object = nullptr;
template <typename Tp, typename ContextT>
thread_local std::array<std::byte, static_buffer_size<Tp>()>
static_tl_object<Tp, ContextT>::m_buffer = {};
template <typename Tp, typename ContextT>
constexpr bool
static_tl_object<Tp, ContextT>::is_trivial_standard_layout()
{
return (std::is_standard_layout<Tp>::value && std::is_trivially_destructible<Tp>::value);
}
template <typename Tp, typename ContextT>
template <typename... Args>
Tp*&
static_tl_object<Tp, ContextT>::construct(Args&&... args)
{
if constexpr(!is_trivial_standard_layout())
{
static thread_local auto _once = std::once_flag{};
std::call_once(_once, []() {
register_static_tl_dtor([]() {
if(static_tl_object<Tp, ContextT>::m_object)
{
static_tl_object<Tp, ContextT>::m_object->~Tp();
static_tl_object<Tp, ContextT>::m_object = nullptr;
}
});
});
}
ROCP_FATAL_IF(m_object)
<< "reconstructing static object. Use get() function to retrieve pointer";
m_object = new(m_buffer.data()) Tp{std::forward<Args>(args)...};
return m_object;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,102 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/string_entry.hpp"
#include "lib/common/scope_destructor.hpp"
#include "lib/common/static_object.hpp"
#include <memory>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
namespace rocprofiler
{
namespace common
{
namespace
{
using name_array_t = std::unordered_map<size_t, std::unique_ptr<std::string>>;
auto&
get_sync()
{
static auto*& _v = static_object<std::shared_mutex>::construct();
return *CHECK_NOTNULL(_v);
}
name_array_t*
get_string_array()
{
static auto*& _v = static_object<name_array_t>::construct();
return _v;
}
} // namespace
const std::string*
get_string_entry(std::string_view name)
{
if(!get_string_array()) return nullptr;
auto _hash_v = std::hash<std::string_view>{}(name);
{
auto _lk = std::shared_lock<std::shared_mutex>{get_sync()};
if(get_string_array()->count(_hash_v) > 0) return get_string_array()->at(_hash_v).get();
}
auto _lk = std::unique_lock<std::shared_mutex>{get_sync()};
return get_string_array()
->emplace(_hash_v, std::make_unique<std::string>(name))
.first->second.get();
}
const std::string*
get_string_entry(size_t _hash_v)
{
if(!get_string_array()) return nullptr;
auto _lk = std::shared_lock<std::shared_mutex>{get_sync()};
if(get_string_array()->count(_hash_v) > 0) return get_string_array()->at(_hash_v).get();
return nullptr;
}
size_t
add_string_entry(std::string_view name)
{
if(!get_string_array()) return 0;
auto _hash_v = std::hash<std::string_view>{}(name);
{
auto _lk = std::shared_lock<std::shared_mutex>{get_sync()};
if(get_string_array()->count(_hash_v) > 0) return _hash_v;
}
auto _lk = std::unique_lock<std::shared_mutex>{get_sync()};
get_string_array()->emplace(_hash_v, std::make_unique<std::string>(name));
return _hash_v;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,42 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 <cstdint>
#include <string>
#include <string_view>
namespace rocprofiler
{
namespace common
{
const std::string*
get_string_entry(std::string_view name);
const std::string*
get_string_entry(size_t hash);
size_t
add_string_entry(std::string_view name);
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,128 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/container/small_vector.hpp"
#include "lib/common/mpl.hpp"
#include <fmt/core.h>
#include <fmt/ranges.h>
#include <cstdint>
#include <string>
#include <string_view>
namespace rocprofiler
{
namespace common
{
struct stringified_argument
{
int32_t indirection_level = 0;
int32_t dereference_count = 0;
const char* type = nullptr;
const char* name = nullptr;
std::string value = {};
};
template <size_t N>
using stringified_argument_array_t =
container::small_vector<stringified_argument, std::min<size_t>(N, 6)>;
template <typename Tp, typename FuncT>
auto
stringize_arg_impl(const Tp& _v, const int32_t max_deref, int32_t& deref_cnt, FuncT&& impl)
{
using value_type = std::decay_t<Tp>;
using nonpointer_type = std::remove_pointer_t<Tp>;
if constexpr(common::mpl::is_string_type<value_type>::value &&
!std::is_pointer<nonpointer_type>::value)
{
if constexpr(std::is_pointer<value_type>::value)
{
if(!_v) return std::string{"(null)"};
}
return std::string{_v};
}
else if constexpr(fmt::is_formattable<value_type>::value && !std::is_pointer<value_type>::value)
{
return fmt::format("{}", _v);
}
else if constexpr(std::is_pointer<value_type>::value &&
!std::is_pointer<nonpointer_type>::value &&
common::mpl::is_type_complete_v<nonpointer_type> &&
!std::is_void<nonpointer_type>::value)
{
if(_v && deref_cnt < max_deref)
return stringize_arg_impl(*_v, max_deref, ++deref_cnt, std::forward<FuncT>(impl));
else if(_v)
return std::forward<FuncT>(impl)(_v);
else
return std::string{"(null)"};
}
else if constexpr(std::is_pointer<value_type>::value && std::is_pointer<nonpointer_type>::value)
{
using next_nonpointer_type = std::remove_pointer_t<nonpointer_type>;
if(_v)
{
if constexpr(!std::is_void<next_nonpointer_type>::value)
{
if(deref_cnt < max_deref)
return stringize_arg_impl(
*_v, max_deref, ++deref_cnt, std::forward<FuncT>(impl));
else
return std::forward<FuncT>(impl)(_v);
}
else
{
return std::forward<FuncT>(impl)(_v);
}
}
else
{
return std::string{"(null)"};
}
}
else
{
return std::forward<FuncT>(impl)(_v);
}
}
template <typename Tp, typename FuncT>
common::stringified_argument
stringize_arg(int32_t max_deref, const std::pair<const char*, Tp>& arg, FuncT&& impl)
{
auto _arg = common::stringified_argument{};
_arg.indirection_level = mpl::indirection_level<Tp>::value;
_arg.type = typeid(Tp).name();
_arg.name = arg.first;
_arg.value = stringize_arg_impl(
arg.second, max_deref, _arg.dereference_count, std::forward<FuncT>(impl));
return _arg;
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,181 @@
// MIT License
//
// Copyright (c) 2023-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 <cstddef>
#include <functional>
#include <mutex>
#include <shared_mutex>
#include <type_traits>
namespace rocprofiler
{
namespace common
{
/**
* Sychronized is a wrapper that adds lock based write/read
* protection around a datatype. The protected data is accessed
* only by rlock/wlock. rlock(lambda) gets a reader lock of the
* protected value, passing the protected value to the lambda as a
* const. wlock(lambda) gets a writer lock on the protective value
* and does the same. The reason for this class is to make it less
* error prone to access shared data and more obvious when a lock
* is being held.
*
* Example usage:
*
* Synchronized<int> x(9);
* x.rlock([](const auto& data){
* // data = 9
* });
*
* x.wlock([](auto& data){
* // set data to new value
* });
*/
template <typename LockedType, bool IsMappedTypeV = false>
class Synchronized
{
public:
using value_type = LockedType;
using this_type = Synchronized<value_type, IsMappedTypeV>;
Synchronized() = default;
~Synchronized() = default;
explicit Synchronized(value_type&& data)
: m_data{std::move(data)}
{}
Synchronized(Synchronized&& data) noexcept = default;
Synchronized& operator=(Synchronized&& data) noexcept = default;
// Do not allow this data structure to be copied, std::move only.
Synchronized(const Synchronized&) = delete;
// return a copy of the data
value_type get() const;
template <typename FuncT, typename... Args>
decltype(auto) rlock(FuncT&& lambda, Args&&... args) const;
template <typename FuncT, typename... Args>
decltype(auto) wlock(FuncT&& lambda, Args&&... args);
// This overload to wlock allows a synchronized map whose keys map to synchronized data to
// use a read lock on the key data and then a write lock on the mapped data.
template <typename FuncT,
typename... Args,
bool EnableForMappedType = IsMappedTypeV,
std::enable_if_t<EnableForMappedType, int> = 0>
decltype(auto) wlock(FuncT&& lambda, Args&&... args) const;
// Upgradable lock. If read returns false, write will be called with a unique_lock.
// Essentially a helper function that does .rlock() followed by .wlock().
template <typename ReadFuncT, typename WriteFuncT, typename... Args>
bool ulock(ReadFuncT&& read, WriteFuncT&& write, Args&&... args);
private:
mutable std::shared_mutex m_mutex = {};
value_type m_data = {};
};
//
// member definitions
//
template <typename LockedType, bool IsMappedTypeV>
typename Synchronized<LockedType, IsMappedTypeV>::value_type
Synchronized<LockedType, IsMappedTypeV>::get() const
{
auto lock = std::shared_lock{m_mutex};
return m_data;
}
template <typename LockedType, bool IsMappedTypeV>
template <typename FuncT, typename... Args>
decltype(auto)
Synchronized<LockedType, IsMappedTypeV>::rlock(FuncT&& lambda, Args&&... args) const
{
static_assert(std::is_invocable<FuncT, const value_type&, Args...>::value,
"function must accept const reference to locked type");
auto lock = std::shared_lock{m_mutex};
return std::forward<FuncT>(lambda)(m_data, std::forward<Args>(args)...);
}
template <typename LockedType, bool IsMappedTypeV>
template <typename FuncT, typename... Args>
decltype(auto)
Synchronized<LockedType, IsMappedTypeV>::wlock(FuncT&& lambda, Args&&... args)
{
static_assert(std::is_invocable<FuncT, value_type&, Args...>::value,
"function must accept reference to locked type");
auto lock = std::unique_lock{m_mutex};
return std::forward<FuncT>(lambda)(m_data, std::forward<Args>(args)...);
}
// This overload to wlock allows a synchronized map whose keys map to synchronized data to
// use a read lock on the key data and then a write lock on the mapped data.
template <typename LockedType, bool IsMappedTypeV>
template <typename FuncT,
typename... Args,
bool EnableForMappedType,
std::enable_if_t<EnableForMappedType, int>>
decltype(auto)
Synchronized<LockedType, IsMappedTypeV>::wlock(FuncT&& lambda, Args&&... args) const
{
return const_cast<this_type*>(this)->wlock(std::forward<FuncT>(lambda),
std::forward<Args>(args)...);
}
// Upgradable lock. If read returns false, write will be called with a unique_lock.
// Essentially a helper function that does .rlock() followed by .wlock().
template <typename LockedType, bool IsMappedTypeV>
template <typename ReadFuncT, typename WriteFuncT, typename... Args>
bool
Synchronized<LockedType, IsMappedTypeV>::ulock(ReadFuncT&& read, WriteFuncT&& write, Args&&... args)
{
static_assert(std::is_invocable<ReadFuncT, const value_type&, Args...>::value,
"read function must accept const reference to locked type");
static_assert(std::is_invocable<WriteFuncT, value_type&, Args...>::value,
"write function must accept reference to locked type");
using read_return_type = std::invoke_result_t<ReadFuncT, const value_type&, Args...>;
using write_return_type = std::invoke_result_t<WriteFuncT, value_type&, Args...>;
static_assert(std::is_same<read_return_type, write_return_type>::value,
"read and write functions must return same type");
static_assert(std::is_same<read_return_type, bool>::value,
"read/write functions must return bool");
{
auto lock = std::shared_lock{m_mutex};
if(read(m_data, std::forward<Args>(args)...)) return true;
}
auto lock = std::unique_lock{m_mutex};
return write(m_data, std::forward<Args>(args)...);
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,371 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/environment.hpp"
#include "lib/common/logging.hpp"
#include <unistd.h>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <string>
#include <tuple>
#include <unordered_set>
namespace rocprofiler
{
namespace common
{
namespace units
{
static constexpr int64_t nsec = 1;
static constexpr int64_t usec = 1000 * nsec;
static constexpr int64_t msec = 1000 * usec;
static constexpr int64_t csec = 10 * msec;
static constexpr int64_t dsec = 10 * csec;
static constexpr int64_t sec = 10 * dsec;
static constexpr int64_t minute = 60 * sec;
static constexpr int64_t hour = 60 * minute;
static constexpr int64_t byte = 1;
static constexpr int64_t kilobyte = 1000 * byte;
static constexpr int64_t megabyte = 1000 * kilobyte;
static constexpr int64_t gigabyte = 1000 * megabyte;
static constexpr int64_t terabyte = 1000 * gigabyte;
static constexpr int64_t petabyte = 1000 * terabyte;
static constexpr int64_t kibibyte = 1024 * byte;
static constexpr int64_t mebibyte = 1024 * kibibyte;
static constexpr int64_t gibibyte = 1024 * mebibyte;
static constexpr int64_t tebibyte = 1024 * gibibyte;
static constexpr int64_t pebibyte = 1024 * tebibyte;
static constexpr int64_t B = 1;
static constexpr int64_t KB = 1000 * B;
static constexpr int64_t MB = 1000 * KB;
static constexpr int64_t GB = 1000 * MB;
static constexpr int64_t TB = 1000 * GB;
static constexpr int64_t PB = 1000 * TB;
static constexpr int64_t Bi = 1;
static constexpr int64_t KiB = 1024 * Bi;
static constexpr int64_t MiB = 1024 * KiB;
static constexpr int64_t GiB = 1024 * MiB;
static constexpr int64_t TiB = 1024 * GiB;
static constexpr int64_t PiB = 1024 * TiB;
static constexpr int64_t nanowatt = 1;
static constexpr int64_t microwatt = 1000 * nanowatt;
static constexpr int64_t milliwatt = 1000 * microwatt;
static constexpr int64_t watt = 1000 * milliwatt;
static constexpr int64_t kilowatt = 1000 * watt;
static constexpr int64_t megawatt = 1000 * kilowatt;
static constexpr int64_t gigawatt = 1000 * megawatt;
static constexpr int64_t hertz = 1;
static constexpr int64_t kilohertz = 1000 * hertz;
static constexpr int64_t megahertz = 1000 * kilohertz;
static constexpr int64_t gigahertz = 1000 * megahertz;
static constexpr int64_t Hz = 1;
static constexpr int64_t KHz = 1000 * Hz;
static constexpr int64_t MHz = 1000 * KHz;
static constexpr int64_t GHz = 1000 * MHz;
inline int64_t
get_page_size()
{
static auto _pagesz = sysconf(_SC_PAGESIZE);
return _pagesz;
}
const int64_t clocks_per_sec = sysconf(_SC_CLK_TCK);
//--------------------------------------------------------------------------------------//
inline std::string
time_repr(int64_t _unit)
{
switch(_unit)
{
case nsec: return "nsec"; break;
case usec: return "usec"; break;
case msec: return "msec"; break;
case csec: return "csec"; break;
case dsec: return "dsec"; break;
case sec: return "sec"; break;
default: return "UNK"; break;
}
return std::string{};
}
//--------------------------------------------------------------------------------------//
inline std::string
mem_repr(int64_t _unit)
{
switch(_unit)
{
case byte: return "B"; break;
case kilobyte: return "KB"; break;
case megabyte: return "MB"; break;
case gigabyte: return "GB"; break;
case terabyte: return "TB"; break;
case petabyte: return "PB"; break;
case kibibyte: return "KiB"; break;
case mebibyte: return "MiB"; break;
case gibibyte: return "GiB"; break;
case tebibyte: return "TiB"; break;
case pebibyte: return "PiB"; break;
default: return "UNK"; break;
}
return std::string{};
}
//--------------------------------------------------------------------------------------//
inline std::string
freq_repr(int64_t _unit)
{
switch(_unit)
{
case hertz: return "Hz"; break;
case kilohertz: return "KHz"; break;
case megahertz: return "MHz"; break;
case gigahertz: return "GHz"; break;
default: return "UNK"; break;
}
return std::string{};
}
//--------------------------------------------------------------------------------------//
inline std::string
power_repr(int64_t _unit)
{
switch(_unit)
{
case nanowatt: return "nanowatts"; break;
case microwatt: return "microwatts"; break;
case milliwatt: return "milliwatts"; break;
case watt: return "watts"; break;
case kilowatt: return "kilowatts"; break;
case megawatt: return "megawatts"; break;
case gigawatt: return "gigawatts"; break;
default: return "UNK"; break;
}
return std::string{};
}
//--------------------------------------------------------------------------------------//
inline std::tuple<std::string, int64_t>
get_memory_unit(std::string _unit)
{
using string_t = std::string;
using return_type = std::tuple<string_t, int64_t>;
using inner_t = std::tuple<string_t, string_t, int64_t>;
if(_unit.empty()) return return_type{"MB", units::megabyte};
for(auto& itr : _unit)
itr = tolower(itr);
for(const auto& itr : {inner_t{"byte", "b", units::byte},
inner_t{"kilobyte", "kb", units::kilobyte},
inner_t{"megabyte", "mb", units::megabyte},
inner_t{"gigabyte", "gb", units::gigabyte},
inner_t{"terabyte", "tb", units::terabyte},
inner_t{"petabyte", "pb", units::petabyte},
inner_t{"kibibyte", "kib", units::KiB},
inner_t{"mebibyte", "mib", units::MiB},
inner_t{"gibibyte", "gib", units::GiB},
inner_t{"tebibyte", "tib", units::TiB},
inner_t{"pebibyte", "pib", units::PiB}})
{
if(_unit == std::get<0>(itr) || _unit == std::get<1>(itr))
{
if(std::get<2>(itr) == units::byte)
return return_type{std::get<0>(itr), std::get<2>(itr)};
return return_type{mem_repr(std::get<2>(itr)), std::get<2>(itr)};
}
}
ROCP_WARNING << "Warning!! No memory unit matching \"" << _unit << "\". Using default...\n";
return return_type{"MB", units::megabyte};
}
//--------------------------------------------------------------------------------------//
inline std::tuple<std::string, int64_t>
get_timing_unit(std::string _unit)
{
using string_t = std::string;
using strset_t = std::unordered_set<string_t>;
using return_type = std::tuple<string_t, int64_t>;
using inner_t = std::tuple<string_t, strset_t, int64_t>;
if(_unit.empty()) return return_type{"sec", units::sec};
for(auto& itr : _unit)
itr = tolower(itr);
for(const auto& itr :
{inner_t{"nsec", strset_t{"ns", "nanosecond", "nanoseconds"}, units::nsec},
inner_t{"usec", strset_t{"us", "microsecond", "microseconds"}, units::usec},
inner_t{"msec", strset_t{"ms", "millisecond", "milliseconds"}, units::msec},
inner_t{"csec", strset_t{"cs", "centisecond", "centiseconds"}, units::csec},
inner_t{"dsec", strset_t{"ds", "decisecond", "deciseconds"}, units::dsec},
inner_t{"sec", strset_t{"s", "second", "seconds"}, units::sec},
inner_t{"min", strset_t{"minute", "minutes"}, units::minute},
inner_t{"hr", strset_t{"hr", "hour", "hours"}, units::hour}})
{
if(_unit == std::get<0>(itr) || std::get<1>(itr).find(_unit) != std::get<1>(itr).end())
{
return return_type{time_repr(std::get<2>(itr)), std::get<2>(itr)};
}
}
ROCP_WARNING << "Warning!! No timing unit matching \"" << _unit << "\". Using default...\n";
return return_type{"sec", units::sec};
}
//--------------------------------------------------------------------------------------//
inline std::tuple<std::string, int64_t>
get_frequncy_unit(std::string _unit)
{
using string_t = std::string;
using return_type = std::tuple<string_t, int64_t>;
using inner_t = std::tuple<string_t, string_t, int64_t>;
if(_unit.empty()) return return_type{"MHz", units::megahertz};
for(auto& itr : _unit)
itr = tolower(itr);
for(const auto& itr : {inner_t{"hertz", "hz", units::hertz},
inner_t{"kilohertz", "khz", units::kilohertz},
inner_t{"megahertz", "mhz", units::megahertz},
inner_t{"gigahertz", "ghz", units::gigahertz}})
{
if(_unit == std::get<0>(itr) || _unit == std::get<1>(itr))
{
return return_type{freq_repr(std::get<2>(itr)), std::get<2>(itr)};
}
}
ROCP_WARNING << "Warning!! No frequency unit matching \"" << _unit << "\". Using default...\n";
return return_type{"MHz", units::megahertz};
}
//--------------------------------------------------------------------------------------//
inline std::tuple<std::string, int64_t>
get_power_unit(const std::string& _unit)
{
using string_t = std::string;
using return_type = std::tuple<string_t, int64_t>;
using inner_t = std::tuple<string_t, string_t, int64_t>;
if(_unit.empty()) return return_type{"watts", units::watt};
auto _lunit = _unit;
for(auto& itr : _lunit)
itr = tolower(itr);
for(const auto& itr : {inner_t{"nanowatt", "nW", units::nanowatt},
inner_t{"microwatt", "uW", units::microwatt},
inner_t{"milliwatt", "mW", units::milliwatt},
inner_t{"watt", "W", units::watt},
inner_t{"kilowatt", "KW", units::kilowatt},
inner_t{"megawatt", "MW", units::megawatt},
inner_t{"gigawatt", "GW", units::gigawatt}})
{
if(_lunit == std::get<0>(itr) || _lunit + "s" == std::get<0>(itr) ||
_unit == std::get<1>(itr))
{
return return_type{power_repr(std::get<2>(itr)), std::get<2>(itr)};
}
}
ROCP_WARNING << "Warning!! No power unit matching \"" << _unit << "\". Using default...\n";
return return_type{"watts", units::watt};
}
//--------------------------------------------------------------------------------------//
namespace temperature
{
enum unit_system : int8_t
{
Celsius = 0,
Fahrenheit,
Kelvin
};
template <typename Tp>
Tp
convert(Tp _v, unit_system _from, unit_system _to)
{
switch(_from)
{
case Celsius:
{
switch(_to)
{
case Celsius: return _v;
case Fahrenheit: return static_cast<Tp>((_v * 1.8) + 32);
case Kelvin: return (_v - 273);
}
}
case Fahrenheit:
{
switch(_to)
{
case Celsius: return static_cast<Tp>((_v - 32) / 1.8);
case Fahrenheit: return _v;
case Kelvin: return (_v - 273);
}
}
case Kelvin:
{
switch(_to)
{
case Celsius: return (_v + 273);
case Fahrenheit: return static_cast<Tp>(((_v + 273) * 1.8) + 32);
case Kelvin: return _v;
}
}
}
}
} // namespace temperature
} // namespace units
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,160 @@
// MIT License
//
// Copyright (c) 2023-2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/utility.hpp"
#include "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <ctime>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
namespace rocprofiler
{
namespace common
{
namespace
{
std::string_view
get_clock_name(clockid_t _id)
{
#define CLOCK_NAME_CASE_STATEMENT(NAME) \
case NAME: return #NAME;
switch(_id)
{
CLOCK_NAME_CASE_STATEMENT(CLOCK_REALTIME)
CLOCK_NAME_CASE_STATEMENT(CLOCK_MONOTONIC)
CLOCK_NAME_CASE_STATEMENT(CLOCK_PROCESS_CPUTIME_ID)
CLOCK_NAME_CASE_STATEMENT(CLOCK_THREAD_CPUTIME_ID)
CLOCK_NAME_CASE_STATEMENT(CLOCK_MONOTONIC_RAW)
CLOCK_NAME_CASE_STATEMENT(CLOCK_REALTIME_COARSE)
CLOCK_NAME_CASE_STATEMENT(CLOCK_MONOTONIC_COARSE)
CLOCK_NAME_CASE_STATEMENT(CLOCK_BOOTTIME)
CLOCK_NAME_CASE_STATEMENT(CLOCK_REALTIME_ALARM)
CLOCK_NAME_CASE_STATEMENT(CLOCK_BOOTTIME_ALARM)
CLOCK_NAME_CASE_STATEMENT(CLOCK_TAI)
default: break;
}
return "CLOCK_UNKNOWN";
#undef CLOCK_NAME_CASE_STATEMENT
}
auto _process_init_ns = timestamp_ns();
} // namespace
uint64_t
get_clock_period_ns_impl(clockid_t _clk_id)
{
constexpr auto nanosec = std::nano::den;
struct timespec ts;
auto ret = clock_getres(_clk_id, &ts);
if(ROCPROFILER_UNLIKELY(ret != 0))
{
auto _err = errno;
ROCP_FATAL << "error getting clock resolution for " << get_clock_name(_clk_id) << ": "
<< strerror(_err);
}
else if(ROCPROFILER_UNLIKELY(ts.tv_sec != 0 ||
ts.tv_nsec >= std::numeric_limits<uint32_t>::max()))
{
ROCP_FATAL << "clock_getres(" << get_clock_name(_clk_id)
<< ") returned very low frequency (<1Hz)";
}
return (static_cast<uint64_t>(ts.tv_sec) * nanosec) + static_cast<uint64_t>(ts.tv_nsec);
}
uint64_t
get_process_start_time_ns(pid_t _pid)
{
if(_pid == getpid()) return _process_init_ns;
return 0;
}
std::vector<std::string>
read_command_line(pid_t _pid)
{
auto _cmdline = std::vector<std::string>{};
auto fcmdline = std::stringstream{};
fcmdline << "/proc/" << _pid << "/cmdline";
auto ifs = std::ifstream{fcmdline.str().c_str()};
if(ifs)
{
char cstr;
std::string sarg;
while(!ifs.eof())
{
ifs >> cstr;
if(!ifs.eof())
{
if(cstr != '\0')
{
sarg += cstr;
}
else
{
_cmdline.push_back(sarg);
sarg = "";
}
}
}
ifs.close();
}
return _cmdline;
}
} // namespace common
} // namespace rocprofiler
namespace
{
std::atomic<bool>&
debugger_block()
{
static std::atomic<bool> block = {true};
return block;
}
} // namespace
extern "C" {
void
rocprofiler_debugger_block()
{
while(debugger_block().load() == true)
{};
// debugger_block().exchange(true);
}
void
rocprofiler_debugger_continue()
{
debugger_block().exchange(false);
}
}
@@ -0,0 +1,301 @@
// MIT License
//
// Copyright (c) 2023-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 "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include <sys/syscall.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <ctime>
#include <functional>
#include <mutex>
#include <ratio>
#include <stdexcept>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
namespace rocprofiler
{
namespace common
{
template <typename... Tp>
void
consume_args(Tp&&...)
{}
uint64_t
get_clock_period_ns_impl(clockid_t _clk_id);
inline uint64_t
get_tid()
{
// system calls are expensive so store this in a thread-local
static thread_local uint64_t _v = ::syscall(__NR_gettid);
return _v;
}
inline uint64_t
get_ticks(clockid_t clk_id_v) noexcept
{
constexpr auto nanosec = std::nano::den;
auto&& ts = timespec{};
auto ret = clock_gettime(clk_id_v, &ts);
if(ROCPROFILER_UNLIKELY(ret != 0))
{
auto _err = errno;
ROCP_FATAL << "clock_gettime failed: " << strerror(_err);
}
return (static_cast<uint64_t>(ts.tv_sec) * nanosec) + static_cast<uint64_t>(ts.tv_nsec);
}
static constexpr int default_clock_id = CLOCK_BOOTTIME;
// CLOCK_MONOTONIC_RAW equates to HSA-runtime library implementation of os::ReadAccurateClock()
// CLOCK_BOOTTIME equates to HSA-runtime library implementation of os::ReadSystemClock()
template <int ClockT = default_clock_id>
inline uint64_t
timestamp_ns()
{
constexpr auto _clk = ClockT;
static auto _clk_period = get_clock_period_ns_impl(_clk);
if(ROCPROFILER_LIKELY(_clk_period == 1)) return get_ticks(_clk);
return get_ticks(_clk) / _clk_period;
}
// returns the process start time (in CLOCK_BOOTTIME nanoseconds) via /proc/<pid>/stat
uint64_t
get_process_start_time_ns(pid_t _pid);
std::vector<std::string>
read_command_line(pid_t _pid);
template <class Container, typename Key = typename Container::key_type>
const auto*
get_val(const Container& map, const Key& key)
{
auto pos = map.find(key);
return (pos != map.end() ? &pos->second : nullptr);
}
template <class Container, typename Key = typename Container::key_type>
auto*
get_val(Container& map, const Key& key)
{
auto pos = map.find(key);
return (pos != map.end() ? &pos->second : nullptr);
}
template <typename Tp>
constexpr void
assert_public_data_type_properties()
{
static_assert(std::is_standard_layout<Tp>::value,
"public data type struct should have a standard layout");
static_assert(std::is_trivial<Tp>::value, "public data type should be trivial");
static_assert(std::is_default_constructible<Tp>::value,
"public data type struct should be default constructible");
static_assert(std::is_trivially_copy_constructible<Tp>::value,
"public data type struct should be trivially copy constructible");
static_assert(std::is_trivially_move_constructible<Tp>::value,
"public data type struct should be trivially move constructible");
static_assert(std::is_trivially_copy_assignable<Tp>::value,
"public data type struct should be trivially move assignable");
static_assert(std::is_trivially_move_assignable<Tp>::value,
"public data type struct should be trivially move assignable");
static_assert(std::is_trivially_copyable<Tp>::value,
"public data type struct should be trivially move assignable");
}
template <typename Tp>
constexpr void
assert_public_api_struct_properties()
{
assert_public_data_type_properties<Tp>();
static_assert(std::is_class<Tp>::value, "this is not a public API struct");
static_assert(offsetof(Tp, size) == 0, "public API struct should have a size field first");
static_assert(sizeof(std::declval<Tp>().size) == sizeof(uint64_t),
"public API struct size field should be 64 bits");
}
// used to set the "size" field to the offset of the "reserved_padding" field.
// The reserved_padding field is extra unused bytes added to the a struct to
// avoid an ABI break if/when new fields are added. This is only done
// for fields which are regularly passed by value
template <typename Tp, typename Up = Tp>
constexpr auto
compute_runtime_sizeof(int) -> decltype(std::declval<Up>().reserved_padding, size_t{})
{
return offsetof(Tp, reserved_padding);
}
template <typename Tp, typename Up = Tp>
constexpr auto
compute_runtime_sizeof(long)
{
return sizeof(Tp);
}
template <typename Tp>
constexpr auto
compute_runtime_sizeof()
{
return compute_runtime_sizeof<Tp>(0);
}
template <typename Tp, typename... Args>
decltype(auto)
init_public_api_struct(Tp&& val, Args&&... args)
{
assert_public_api_struct_properties<Tp>();
::memset(&val, 0, sizeof(Tp));
if constexpr(sizeof...(Args) == 0)
val.size = compute_runtime_sizeof<Tp>();
else
val = {compute_runtime_sizeof<Tp>(), std::forward<Args>(args)...};
return std::forward<Tp>(val);
}
template <typename Tp, typename... Args>
Tp&
init_public_api_struct(Tp& val, Args&&... args)
{
assert_public_api_struct_properties<Tp>();
::memset(&val, 0, sizeof(Tp));
if constexpr(sizeof...(Args) == 0)
val.size = compute_runtime_sizeof<Tp>();
else
val = {compute_runtime_sizeof<Tp>(), std::forward<Args>(args)...};
return val;
}
/**
* A simple wrapper that will call a function when the
* wrapper is being destroyed. This is primarily useful
* for static variables where we want to run some destruction
* operations when the program exits.
*/
template <typename Tp>
class static_cleanup_wrapper
{
public:
using data_type = Tp;
using functor_type = std::function<void(Tp&)>;
static_cleanup_wrapper(data_type&& data, functor_type&& destroy_func)
: m_data(std::move(data))
, m_destroy_func(std::move(destroy_func))
{}
static_cleanup_wrapper(functor_type&& destroy_func)
: m_destroy_func(std::move(destroy_func))
{}
~static_cleanup_wrapper() { m_destroy_func(m_data); }
void destroy() { m_destroy_func(m_data); }
data_type& get() { return m_data; }
const data_type& get() const { return m_data; }
private:
data_type m_data = {};
functor_type m_destroy_func = {};
};
template <typename Tp = long, typename RatioT = std::ratio<1, 1000>>
void
yield(std::chrono::duration<Tp, RatioT> duration = std::chrono::milliseconds{10})
{
std::this_thread::yield();
std::this_thread::sleep_for(duration);
}
template <typename PredicateT, typename Tp = long, typename RatioT = std::ratio<1, 1000>>
bool
yield(PredicateT&& predicate,
std::chrono::duration<Tp, RatioT> max_yield_time,
std::chrono::duration<Tp, RatioT> query_interval = std::chrono::milliseconds{10})
{
auto now = []() { return std::chrono::steady_clock::now(); };
auto start = now();
auto result = false;
while(!(result = predicate()))
{
yield(query_interval);
if((now() - start) > max_yield_time)
{
break;
}
}
// return the result of the last predicate query
return result;
}
class assert_single_threaded
{
public:
assert_single_threaded(std::atomic<bool>& lock)
: m_is_initialized(lock)
{
bool expected = false;
if(!m_is_initialized.compare_exchange_strong(expected, true))
{
ROCP_FATAL << "This code must be run in a single thread!!!";
}
}
~assert_single_threaded() { m_is_initialized.store(false, std::memory_order_release); }
private:
std::atomic<bool>& m_is_initialized;
};
} // namespace common
} // namespace rocprofiler
extern "C" {
void
rocprofiler_debugger_block();
void
rocprofiler_debugger_continue();
}
@@ -0,0 +1,151 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/uuid_v7.hpp"
#include "lib/common/defines.hpp"
#include "lib/common/logging.hpp"
#include "lib/common/mpl.hpp"
#include "lib/common/sha256.hpp"
#include <fmt/format.h>
#include <unistd.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <random>
#include <sstream>
#include <string>
#include <vector>
namespace rocprofiler
{
namespace common
{
uint64_t
get_process_start_ticks_since_boot(pid_t pid)
{
auto line = std::string{};
// Read the stat file
if(auto stat_file = std::ifstream{fmt::format("/proc/{}/stat", pid)}; !stat_file.is_open())
{
ROCP_CI_LOG(WARNING) << fmt::format("failed to open /proc/{}/stat for process start time",
pid);
return 0;
}
else
{
// Read entire line
std::getline(stat_file, line);
}
// Locate the end of the comm field (")")
size_t rparen = line.rfind(')');
if(rparen == std::string::npos)
{
ROCP_CI_LOG(WARNING) << fmt::format("Malformed stat file for pid {}", pid);
return 0;
}
// Tokenize fields after ") "
auto iss = std::istringstream{line.substr(rparen + 2)};
auto token = std::string{};
// Skip fields 3 through 21
for(int i = 0; i < 20; ++i)
{
if(!(iss >> token))
{
ROCP_CI_LOG(WARNING) << fmt::format("Unexpected end of /proc/{}/stat", pid);
return 0;
}
}
// Field 22: starttime in clock ticks since boot
uint64_t start_ticks = 0;
if(!(iss >> start_ticks))
{
ROCP_CI_LOG(WARNING) << fmt::format(
"Unexpected end of /proc/{}/stat. Failed to read start ticks", pid);
return 0;
}
return start_ticks;
}
uint64_t
compute_system_seed(std::string_view machine_id, pid_t pid, pid_t ppid, uint64_t pstart_ticks)
{
// If no machine_id provided, read from /etc/machine-id
ROCP_CI_LOG_IF(WARNING, machine_id.empty())
<< fmt::format("compute_system_seed provided empty machine id");
// Hash for seed value
return std::hash<std::string>{}(
sha256{fmt::format("{}|{}|{}|{}", machine_id, pid, ppid, pstart_ticks)}.hexdigest());
}
std::string
generate_uuid_v7(uint64_t timestamp_ns, uint64_t seed, std::string_view delim)
{
constexpr auto nanosec_per_millisec = std::nano::den / std::milli::den;
auto timestamp_ms = timestamp_ns / nanosec_per_millisec;
auto uuid = std::array<uint8_t, 16>{};
// First 6 bytes = timestamp
for(int i = 0; i < 6; ++i)
{
uuid[i] = static_cast<uint8_t>((timestamp_ms >> (40 - 8 * i)) & 0xFF);
}
// Set version to 7 with ordering based on timestamp.
uuid[6] = static_cast<uint8_t>((timestamp_ms >> 8) & 0x0F);
uuid[6] |= 0x70;
uuid[7] = static_cast<uint8_t>(timestamp_ms & 0xFF);
// Seeded RNG
auto rand64 = std::mt19937_64{seed}();
for(int i = 0; i < 8; ++i)
{
uuid[8 + i] = static_cast<uint8_t>((rand64 >> (56 - 8 * i)) & 0xFF);
}
// Set variant to RFC 4122
uuid[8] = (uuid[8] & 0x3F) | 0x80;
// Format as UUID string
auto oss = std::ostringstream{};
oss << std::hex << std::setfill('0');
for(int i = 0; i < 16; ++i)
{
oss << std::setw(2) << static_cast<int>(uuid[i]);
if(i == 3 || i == 5 || i == 7 || i == 9) oss << delim;
}
return oss.str();
}
} // namespace common
} // namespace rocprofiler
@@ -0,0 +1,49 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc.
//
// 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 "lib/common/defines.hpp"
#include "lib/common/mpl.hpp"
#include <array>
#include <cstdint>
#include <random>
#include <string>
namespace rocprofiler
{
namespace common
{
uint64_t
get_process_start_ticks_since_boot(pid_t pid);
// use this function to create a deterministic random number seed for the system and process
uint64_t
compute_system_seed(std::string_view machine_id, pid_t pid, pid_t ppid, uint64_t pstart_ticks);
std::string
generate_uuid_v7(uint64_t timestamp_ns,
uint64_t seed = std::random_device{}(),
std::string_view delim = "-");
} // namespace common
} // namespace rocprofiler