diff --git a/.gitmodules b/.gitmodules index 8235ed93c..21ef85d66 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/jrmadsen/Caliper.git [submodule "external/gotcha"] path = external/gotcha - url = https://github.com/jrmadsen/GOTCHA.git + url = https://github.com/ROCm/GOTCHA.git [submodule "external/llvm-ompt"] path = external/llvm-ompt url = https://github.com/NERSC/LLVM-openmp.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc7fb000..0f4936900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # timemory +## Version 4.0.1rc0 + +> Date: Tue Dec 3, 2025 + +### Bug Fixes + +- Fixed hard-coded thread limit in `operation::set_storage` + - Changed from `max_threads = 4096` to use `TIMEMORY_MAX_THREADS` + - Prevents segfaults when Linux thread IDs exceed 4096 + ## Version 3.2.4 > Date: Mon Jul 19 17:22:28 2021 -0500 diff --git a/VERSION b/VERSION index b19ee8389..c8621cb54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.0.0rc0 +4.0.1rc0 diff --git a/cmake/Modules/ConfigBinutils.cmake b/cmake/Modules/ConfigBinutils.cmake index 837bf6b3b..614884ad3 100644 --- a/cmake/Modules/ConfigBinutils.cmake +++ b/cmake/Modules/ConfigBinutils.cmake @@ -74,17 +74,15 @@ include(ExternalProject) externalproject_add( binutils-external PREFIX ${PROJECT_BINARY_DIR}/external/binutils - URL - ${TIMEMORY_BINUTILS_DOWNLOAD_URL} - http://ftpmirror.gnu.org/gnu/binutils/binutils-2.40.tar.gz - http://mirrors.kernel.org/sourceware/binutils/releases/binutils-2.40.tar.gz + URL ${TIMEMORY_BINUTILS_DOWNLOAD_URL} + http://ftpmirror.gnu.org/gnu/binutils/binutils-2.42.tar.gz + http://mirrors.kernel.org/sourceware/binutils/releases/binutils-2.42.tar.gz BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${CMAKE_C_COMPILER} CFLAGS=-fPIC\ -O3 CXX=${CMAKE_CXX_COMPILER} CXXFLAGS=-fPIC\ -O3 /configure --prefix=${TPL_STAGING_PREFIX} ${_binutils_CONFIG_FLAGS} - BUILD_COMMAND ${MAKE_COMMAND} all-libiberty all-bfd all-opcodes all-libelf - all-libsframe + BUILD_COMMAND ${MAKE_COMMAND} all-libiberty all-bfd all-opcodes all-libsframe INSTALL_COMMAND "" BUILD_BYPRODUCTS "${_TIMEMORY_BINUTILS_BUILD_BYPRODUCTS}") @@ -94,7 +92,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} ARGS -E make_directory ${TPL_STAGING_PREFIX}/lib COMMAND install ARGS -C - ${PROJECT_BINARY_DIR}/external/binutils/src/binutils-external/bfd/libbfd.a + ${PROJECT_BINARY_DIR}/external/binutils/src/binutils-external/bfd/.libs/libbfd.a ${PROJECT_BINARY_DIR}/external/binutils/src/binutils-external/opcodes/libopcodes.a ${PROJECT_BINARY_DIR}/external/binutils/src/binutils-external/libiberty/libiberty.a ${PROJECT_BINARY_DIR}/external/binutils/src/binutils-external/libsframe/.libs/libsframe.a diff --git a/cmake/Modules/ConfigLibunwind.cmake b/cmake/Modules/ConfigLibunwind.cmake index 079610c1b..797cf24d6 100644 --- a/cmake/Modules/ConfigLibunwind.cmake +++ b/cmake/Modules/ConfigLibunwind.cmake @@ -149,7 +149,8 @@ if(TIMEMORY_INSTALL_HEADERS) endforeach() endif() -file(GLOB libunwind_libs "${PROJECT_BINARY_DIR}/external/libunwind/install/${CMAKE_INSTALL_LIBDIR}/*") +file(GLOB libunwind_libs + "${PROJECT_BINARY_DIR}/external/libunwind/install/${CMAKE_INSTALL_LIBDIR}/*") foreach(_LIB ${libunwind_libs}) if(IS_DIRECTORY ${_LIB}) @@ -158,6 +159,19 @@ foreach(_LIB ${libunwind_libs}) if("${_LIB}" MATCHES "\\.so($|\\.)") execute_process(COMMAND ${CMAKE_STRIP} ${_LIB}) + find_program(CHRPATH_EXECUTABLE chrpath) + find_program(PATCHELF_EXECUTABLE patchelf) + + if(CHRPATH_EXECUTABLE) + execute_process(COMMAND ${CHRPATH_EXECUTABLE} -r "$ORIGIN" ${_LIB}) + elseif(PATCHELF_EXECUTABLE) + execute_process(COMMAND ${PATCHELF_EXECUTABLE} --set-rpath "$ORIGIN" ${_LIB}) + else() + message( + AUTHOR_WARNING + "[timemory] Neither chrpath nor patchelf found. Skipping rpath modification for libunwind." + ) + endif() endif() install( @@ -171,12 +185,15 @@ install( DESTINATION ${CMAKE_INSTALL_LIBDIR}/timemory/libunwind/pkgconfig OPTIONAL) +# Add include directories with BEFORE to ensure they come first in include search path +# This ensures GNU libunwind headers are found before LLVM libunwind headers target_include_directories( - timemory-libunwind SYSTEM + timemory-libunwind BEFORE INTERFACE $ $) target_link_directories( - timemory-libunwind INTERFACE + timemory-libunwind + INTERFACE $ $) target_link_libraries( diff --git a/external/gotcha b/external/gotcha index 5aff8260e..6ef1232a0 160000 --- a/external/gotcha +++ b/external/gotcha @@ -1 +1 @@ -Subproject commit 5aff8260e0756d7c182812ef6b92453374265e28 +Subproject commit 6ef1232a0acc99f3340c2ca4c5d23890e9b8a459 diff --git a/source/timemory/backends/papi.cpp b/source/timemory/backends/papi.cpp index 853d5366d..e748cc8e5 100644 --- a/source/timemory/backends/papi.cpp +++ b/source/timemory/backends/papi.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -277,8 +278,8 @@ get_event_code(string_view_cref_t event_code_str) int event_code = -1; int retval = PAPI_event_name_to_code(event_code_str.data(), &event_code); working() = check(retval, TIMEMORY_JOIN(' ', "Warning!! Failure converting", - event_code_str, "to enum value") - .c_str()); + event_code_str, "to enum value") + .c_str()); return (retval == PAPI_OK) ? event_code : PAPI_NOT_INITED; #else consume_parameters(event_code_str); @@ -401,7 +402,7 @@ add_events(int event_set, string_t* events, int number) TIMEMORY_BACKENDS_INLINE hwcounter_info_t -available_events_info() +available_events_info(const std::unordered_set& excluded_components) { hwcounter_info_t evts{}; @@ -528,6 +529,10 @@ available_events_info() continue; # endif + // Skip excluded components + if(excluded_components.count(component->name) > 0) + continue; + // show this component has not found any events yet // int num_cmp_events = 0; diff --git a/source/timemory/backends/papi.hpp b/source/timemory/backends/papi.hpp index 35b8471be..d9f02fabf 100644 --- a/source/timemory/backends/papi.hpp +++ b/source/timemory/backends/papi.hpp @@ -254,7 +254,7 @@ check(int retval, string_view_cref_t mesg, bool quiet = false) #if defined(TIMEMORY_USE_PAPI) auto* error_str = PAPI_strerror(retval); auto&& _msg = TIMEMORY_JOIN(' ', "[timemory][papi]", mesg, ":: PAPI_error", - retval, ":", error_str); + retval, ":", error_str); if(settings::papi_fail_on_error()) { TIMEMORY_EXCEPTION(_msg); @@ -1023,7 +1023,7 @@ overflow(int evt_set, string_view_cref_t evt_name, int threshold, int flags, //--------------------------------------------------------------------------------------// hwcounter_info_t -available_events_info(); +available_events_info(const std::unordered_set& excluded_components = {}); //--------------------------------------------------------------------------------------// diff --git a/source/timemory/components/ompt/context.hpp b/source/timemory/components/ompt/context.hpp index 80d5756cb..8170c2ac2 100644 --- a/source/timemory/components/ompt/context.hpp +++ b/source/timemory/components/ompt/context.hpp @@ -41,9 +41,20 @@ namespace openmp { struct labeled_argument { + template + std::string get_type_name(const Type& arg) + { + const char* name = typeid(arg).name(); + int status; + std::unique_ptr demangledName( + abi::__cxa_demangle(name, nullptr, nullptr, &status), std::free); + return status == 0 ? demangledName.get() : name; + } + template labeled_argument(std::string_view _lbl, Tp&& _val) : label{ _lbl } + , type{ get_type_name(_val) } , value{ timemory::join::join("", std::forward(_val)) } {} @@ -55,6 +66,7 @@ struct labeled_argument } std::string_view label = {}; + std::string type = {}; std::string value = {}; }; diff --git a/source/timemory/operations/types.hpp b/source/timemory/operations/types.hpp index 40d6ad052..9dfe7a14c 100644 --- a/source/timemory/operations/types.hpp +++ b/source/timemory/operations/types.hpp @@ -39,8 +39,10 @@ #include "timemory/storage/types.hpp" #include "timemory/variadic/types.hpp" +#include #include #include +#include #include #include @@ -798,6 +800,109 @@ struct get_depth } }; // +// +/// \struct tim::operation::stable_storage +/// \tparam StorageType The storage pointer type (e.g., storage*) +/// \tparam ChunkSize Size of each chunk (default: max_threads) +/// +/// \brief A pointer-stable storage container using vector of unique_ptr to arrays. +/// Unlike std::vector, existing element pointers remain valid during growth. +template +class stable_storage +{ +public: + using value_type = StorageType; + using chunk_type = std::array; + using chunk_ptr = std::unique_ptr; + + stable_storage() = default; + + explicit stable_storage(size_t initial_size, StorageType default_val = StorageType{}) + { + reserve(initial_size); + for(size_t i = 0; i < initial_size; ++i) + (*this)[i] = default_val; + m_size = initial_size; + } + + StorageType& operator[](size_t idx) + { + ensure_capacity(idx); + return (*m_chunks[idx / ChunkSize])[idx % ChunkSize]; + } + + StorageType& at(size_t idx) + { + ensure_capacity(idx); + return (*m_chunks[idx / ChunkSize])[idx % ChunkSize]; + } + + size_t size() const { return m_size; } + size_t capacity() const { return m_chunks.size() * ChunkSize; } + + void reserve(size_t new_cap) + { + while(capacity() < new_cap) + add_chunk(); + } + + void ensure_capacity(size_t idx) + { + if(idx >= capacity()) + { + std::lock_guard lock(m_mutex); + while(idx >= capacity()) + add_chunk(); + } + if(idx >= m_size) + m_size = idx + 1; + } + +private: + void add_chunk() + { + auto chunk = std::make_unique(); + chunk->fill(StorageType{}); + m_chunks.push_back(std::move(chunk)); + } + + std::vector m_chunks{}; + size_t m_size{0}; + std::mutex m_mutex{}; +}; + +/// \struct tim::operation::dynamic_storage_base +/// \tparam StorageType The storage pointer type (e.g., storage*) +/// \tparam max_threads Initial capacity for the storage array +/// +/// \brief A reusable base class for thread-safe dynamic storage management. +/// Uses stable_storage which maintains pointer stability during growth. +template +struct dynamic_storage_base +{ + using storage_array_t = stable_storage; + + /// @brief Returns the current capacity (thread-safe read-only access) + static size_t get_capacity() + { + return get_storage().capacity(); + } + +protected: + static storage_array_t& get_storage() + { + static storage_array_t instance(max_threads, nullptr); + return instance; + } + + static void ensure_capacity(storage_array_t& _v, size_t _idx) + { + _v.ensure_capacity(_idx); + } +}; +// +//--------------------------------------------------------------------------------------// + //--------------------------------------------------------------------------------------// // template @@ -810,29 +915,30 @@ struct get_storage; /// the data storage structure for a component which should be updated for /// aggregation/logging. template -struct set_storage +struct set_storage : public dynamic_storage_base*, TIMEMORY_MAX_THREADS> { - friend struct get_storage; - static constexpr size_t max_threads = 4096; + static constexpr size_t max_threads = TIMEMORY_MAX_THREADS; using type = T; - using storage_array_t = std::array*, max_threads>; + using base_type = dynamic_storage_base*, max_threads>; + using storage_array_t = typename base_type::storage_array_t; TIMEMORY_DEFAULT_OBJECT(set_storage) TIMEMORY_INLINE auto operator()(storage* _storage, size_t _idx) const { + base_type::ensure_capacity(get(), _idx); get().at(_idx) = static_cast*>(_storage); } TIMEMORY_INLINE auto operator()(type& _obj, size_t _idx) const { + base_type::ensure_capacity(get(), _idx); get().at(_idx) = static_cast*>(_obj.get_storage()); } -private: static storage_array_t& get() { - static storage_array_t _v = { nullptr }; + static storage_array_t _v(max_threads, nullptr); return _v; } }; @@ -865,6 +971,9 @@ struct get_storage TIMEMORY_INLINE auto operator()(size_t _idx) const { + // Thread-safe read using atomic capacity + if(_idx >= operation::set_storage::get_capacity()) + return static_cast*>(nullptr); return operation::set_storage::get().at(_idx); } diff --git a/source/timemory/tpls/cereal/cereal/types/tuple.hpp b/source/timemory/tpls/cereal/cereal/types/tuple.hpp index 3f02ba23d..ec1255068 100644 --- a/source/timemory/tpls/cereal/cereal/types/tuple.hpp +++ b/source/timemory/tpls/cereal/cereal/types/tuple.hpp @@ -101,7 +101,7 @@ struct serialize template inline static void apply(Archive& ar, std::tuple& tuple) { - serialize::template apply(ar, tuple); + serialize::template apply<>(ar, tuple); ar(TIMEMORY_CEREAL_NVP_(tuple_element_name::c_str(), std::get(tuple))); } @@ -123,7 +123,7 @@ template inline void TIMEMORY_CEREAL_SERIALIZE_FUNCTION_NAME(Archive& ar, std::tuple& tuple) { - tuple_detail::serialize>::value>::template apply( + tuple_detail::serialize>::value>::template apply<>( ar, tuple); } } // namespace cereal diff --git a/source/timemory/utility/argparse.cpp b/source/timemory/utility/argparse.cpp index 28b051d84..7f48c8991 100644 --- a/source/timemory/utility/argparse.cpp +++ b/source/timemory/utility/argparse.cpp @@ -289,7 +289,7 @@ argument_parser::enable_help() TIMEMORY_UTILITY_INLINE // clang-format on argument_parser::argument& -argument_parser::enable_help(const std::string& _extra, const std::string& _epilogue, +argument_parser::enable_help(const std::string _extra, const std::string _epilogue, int _exit_code) { m_help_enabled = true; @@ -414,7 +414,7 @@ argument_parser::enable_version( TIMEMORY_UTILITY_INLINE // clang-format on void -argument_parser::print_help(const std::string& _extra, const std::string& _epilogue) +argument_parser::print_help(const std::string _extra, const std::string _epilogue) { end_group(); @@ -985,9 +985,7 @@ argument_parser::parse(const std::vector& _args, int verbose_level) // execute the argument-specific actions for(auto& itr : m_arg_map) { - if(exists(itr.first)) - itr.second->execute_actions(*this); - else if(itr.second->m_default) + if(exists(itr.first) || itr.second->m_default) itr.second->execute_actions(*this); } diff --git a/source/timemory/utility/argparse.hpp b/source/timemory/utility/argparse.hpp index 0e090900c..d0bad3922 100644 --- a/source/timemory/utility/argparse.hpp +++ b/source/timemory/utility/argparse.hpp @@ -718,7 +718,6 @@ struct argument_parser bool is_separator() const; friend struct argument_parser; - bool m_is_default = false; int m_position = Position::IgnoreArgument; int m_count = Count::ANY; int m_min_count = Count::ANY; @@ -875,7 +874,7 @@ struct argument_parser // //----------------------------------------------------------------------------------// // - void print_help(const std::string& _extra = {}, const std::string& _epilogue = {}); + void print_help(const std::string _extra = {}, const std::string _epilogue = {}); // //----------------------------------------------------------------------------------// // @@ -982,7 +981,7 @@ struct argument_parser /// \fn argument& enable_help() /// \brief Add a help command - argument& enable_help(const std::string& _extra, const std::string& _epilogue = {}, + argument& enable_help(const std::string _extra, const std::string _epilogue = {}, int _exit_code = EXIT_SUCCESS); /// \fn argument& enable_version( diff --git a/source/timemory/utility/filepath.cpp b/source/timemory/utility/filepath.cpp index d685f6e87..ea3c29bd4 100644 --- a/source/timemory/utility/filepath.cpp +++ b/source/timemory/utility/filepath.cpp @@ -42,6 +42,7 @@ # include #endif +#include #include #include #include @@ -49,7 +50,6 @@ #include #include #include -#include namespace tim { @@ -136,7 +136,7 @@ makedir(std::string _dir, int umask) if(!direxists(_base)) { auto _err = _make_dir(_base); - if(_err != 0) + if(_err != 0 && _err != EEXIST) // EEXIST is OK - race condition with other processes { TIMEMORY_PRINTF_WARNING(stderr, "mkdir(\"%s\", %i) failed: %s\n", _base.c_str(), umask, strerror(_err)); diff --git a/source/timemory/utility/procfs/cpuinfo.hpp b/source/timemory/utility/procfs/cpuinfo.hpp index 162129134..f8ae0d333 100644 --- a/source/timemory/utility/procfs/cpuinfo.hpp +++ b/source/timemory/utility/procfs/cpuinfo.hpp @@ -45,10 +45,9 @@ struct freq auto operator()(size_t _idx) const; explicit operator bool() const; - static auto get(size_t _idx); - static size_t size() { return get_offsets().size(); } - static std::vector& get_offsets(); - static auto& get_ifstream() + static double get(size_t _idx); + static size_t size() { return std::thread::hardware_concurrency(); } + static auto& get_ifstream() { static thread_local auto _v = []() { auto _ifs = @@ -63,72 +62,47 @@ struct freq inline freq::operator bool() const { return (get_ifstream() != nullptr); } -inline auto +inline double freq::get(size_t _idx) { - auto& _ifs = get_ifstream(); - auto& _offsets = get_offsets(); - double _freq = 0.0; - _ifs->seekg(_offsets.at(_idx), _ifs->beg); - (*_ifs) >> _freq; - return _freq; -} + std::ifstream ifs("/proc/cpuinfo"); + if(!ifs) return 0.0; -inline auto -freq::operator()(size_t _idx) const -{ - return freq::get(_idx % size()); -} + std::string line; + size_t current_cpu = 0; -inline std::vector& -freq::get_offsets() -{ - static auto _v = []() { - auto _ncpu = std::thread::hardware_concurrency(); - std::vector _cpu_mhz_pos{}; - std::ifstream _ifs{ "/proc/cpuinfo" }; - if(_ifs) + while(std::getline(ifs, line)) + { + if(line.find("processor") == 0) { - for(size_t i = 0; i < _ncpu; ++i) + size_t idx; + if(sscanf(line.c_str(), "processor : %zu", &idx) == 1) { - short _n = 0; - std::string _st{}; - while(_ifs && _ifs.good()) - { - std::string _s{}; - _ifs >> _s; - if(!_ifs.good() || !_ifs) - break; - - if(_s == "cpu" || _s == "MHz" || _s == ":") - { - ++_n; - _st += _s + " "; - } - else - { - _n = 0; - _st = {}; - } - - if(_n == 3) - { - size_t _pos = _ifs.tellg(); - _cpu_mhz_pos.emplace_back(_pos + 1); - _ifs >> _s; - if(!_ifs.good() || !_ifs) - break; - break; - } - } + current_cpu = idx; } } + if(current_cpu == _idx && line.find("cpu MHz") == 0) + { + double freq = 0.0; + size_t pos = line.find(':'); + if(pos != std::string::npos) + { + std::string value = line.substr(pos + 1); + freq = std::stod(value); + return freq; + } + } + } - _ifs.close(); - return _cpu_mhz_pos; - }(); - return _v; + return 0.0; } + +inline auto +freq::operator()(size_t _idx) const +{ + return freq::get(_idx % size()); +} + } // namespace cpuinfo } // namespace procfs -} // namespace tim +} // namespace tim \ No newline at end of file