[rocprofiler-systems] Add cached demangler and replace old demangle (#2135)

* Add cached demangler and replace old

* Add unit tests

* Applied suggestions from code review

* Applied suggestions from code review
This commit is contained in:
Milan Radosavljevic
2025-12-16 08:32:18 +01:00
committed by GitHub
parent 21afa807a9
commit 666e76deac
27 changed files with 450 additions and 84 deletions
@@ -48,8 +48,6 @@ set(core_sources
${CMAKE_CURRENT_LIST_DIR}/state.cpp
${CMAKE_CURRENT_LIST_DIR}/timemory.cpp
${CMAKE_CURRENT_LIST_DIR}/utility.cpp
${CMAKE_CURRENT_LIST_DIR}/agent_manager.cpp
${CMAKE_CURRENT_LIST_DIR}/node_info.cpp
)
set(core_headers
@@ -66,6 +64,7 @@ set(core_headers
${CMAKE_CURRENT_LIST_DIR}/constraint.hpp
${CMAKE_CURRENT_LIST_DIR}/cpu.hpp
${CMAKE_CURRENT_LIST_DIR}/debug.hpp
${CMAKE_CURRENT_LIST_DIR}/demangler.hpp
${CMAKE_CURRENT_LIST_DIR}/dynamic_library.hpp
${CMAKE_CURRENT_LIST_DIR}/exception.hpp
${CMAKE_CURRENT_LIST_DIR}/gpu.hpp
@@ -81,9 +80,6 @@ set(core_headers
${CMAKE_CURRENT_LIST_DIR}/state.hpp
${CMAKE_CURRENT_LIST_DIR}/timemory.hpp
${CMAKE_CURRENT_LIST_DIR}/utility.hpp
${CMAKE_CURRENT_LIST_DIR}/agent.hpp
${CMAKE_CURRENT_LIST_DIR}/agent_manager.hpp
${CMAKE_CURRENT_LIST_DIR}/node_info.hpp
)
add_library(rocprofiler-systems-core-library STATIC)
@@ -141,3 +137,7 @@ set_target_properties(
rocprofiler-systems-core-library
PROPERTIES OUTPUT_NAME ${BINARY_NAME_PREFIX}-core
)
if(ROCPROFSYS_BUILD_TESTING)
add_subdirectory(tests)
endif()
@@ -115,12 +115,10 @@ namespace policy = ::tim::policy; // NOLINT
namespace trait = ::tim::trait; // NOLINT
namespace cereal = ::tim::cereal; // NOLINT
using ::tim::auto_lock_t; // NOLINT
using ::tim::demangle; // NOLINT
using ::tim::get_env; // NOLINT
using ::tim::set_env; // NOLINT
using ::tim::try_demangle; // NOLINT
using ::tim::type_mutex; // NOLINT
using ::tim::auto_lock_t; // NOLINT
using ::tim::get_env; // NOLINT
using ::tim::set_env; // NOLINT
using ::tim::type_mutex; // NOLINT
struct construct_on_thread
{
@@ -0,0 +1,144 @@
// 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 <cstdlib>
#include <cxxabi.h>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>
namespace rocprofsys
{
namespace utility
{
struct cxa_demangle_wrapper_impl
{
static char* demangle(const char* _mangled_name, char* _output_buffer,
size_t* _length, int* _status)
{
return abi::__cxa_demangle(_mangled_name, _output_buffer, _length, _status);
}
};
template <typename DemanglerTp = cxa_demangle_wrapper_impl>
struct demangler
{
template <typename Tp>
std::string demangle()
{
return demangle(typeid(Tp).name());
}
std::string demangle(std::string_view _mangled_name)
{
if(_mangled_name.empty()) return {};
const auto result = try_get_from_cache(_mangled_name);
if(result._found)
{
return result._cache_it->second;
}
// Possible improvement: Limit the cache size to avoid memory bloat
return demangle_and_cache(_mangled_name);
}
private:
std::shared_mutex m_mutex;
std::map<std::string, std::string, std::less<>> m_cache;
using cache_iterator = typename decltype(m_cache)::iterator;
struct cache_result
{
bool _found;
cache_iterator _cache_it;
};
static std::string demangle_impl(const char* _mangled_name)
{
int _status = 0;
std::unique_ptr<char, decltype(&std::free)> _demangled(
DemanglerTp::demangle(_mangled_name, nullptr, nullptr, &_status), &std::free);
if(_status != 0 || !_demangled) return std::string{ _mangled_name };
return std::string{ _demangled.get() };
}
cache_result try_get_from_cache(std::string_view _mangled_name)
{
std::shared_lock<std::shared_mutex> _read_lock{ m_mutex };
auto _it = m_cache.find(_mangled_name);
if(_it != m_cache.end())
{
return { true, _it };
}
return { false, m_cache.end() };
}
std::string demangle_and_cache(std::string_view _mangled_name)
{
std::unique_lock<std::shared_mutex> _write_lock{ m_mutex };
auto _it = m_cache.find(_mangled_name);
if(_it != m_cache.end())
{
return _it->second;
}
auto _result = demangle_impl(_mangled_name.data());
m_cache.emplace(_mangled_name, _result);
return _result;
}
};
inline demangler<>&
get_demangler()
{
static demangler g_demangler;
return g_demangler;
}
template <typename Tp>
inline std::string
demangle()
{
return get_demangler().demangle<Tp>();
}
inline std::string
demangle(std::string_view name)
{
return get_demangler().demangle(name);
}
} // namespace utility
} // namespace rocprofsys
@@ -0,0 +1,30 @@
# 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.
add_library(demangler-tests OBJECT demangler_test.cpp)
target_link_libraries(
demangler-tests
PRIVATE rocprofiler-systems-googletest-library rocprofiler-systems-core-library
)
target_include_directories(demangler-tests PRIVATE ${PROJECT_SOURCE_DIR}/source/lib)
@@ -0,0 +1,155 @@
// 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 "gtest/gtest.h"
#include "core/demangler.hpp"
#include <cstring>
#include <string>
struct mock_demangler_impl
{
static inline int call_count = 0;
static char* demangle(const char* _mangled_name, char*, size_t*, int* _status)
{
++call_count;
if(std::string(_mangled_name).find("FAIL") != std::string::npos)
{
*_status = -1;
return nullptr;
}
std::string result = "demangled_" + std::string(_mangled_name);
char* output = static_cast<char*>(std::malloc(result.size() + 1));
memcpy(output, result.c_str(), result.size() + 1);
*_status = 0;
return output;
}
};
class demangler_test : public ::testing::Test
{
protected:
void SetUp() override { mock_demangler_impl::call_count = 0; }
};
TEST_F(demangler_test, successful_demangle)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string result = demangler.demangle("mangled");
EXPECT_EQ(result, "demangled_mangled");
EXPECT_EQ(mock_demangler_impl::call_count, 1);
}
TEST_F(demangler_test, failed_demangle_returns_original)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string result = demangler.demangle("mangled_FAIL");
EXPECT_EQ(result, "mangled_FAIL");
EXPECT_EQ(mock_demangler_impl::call_count, 1);
}
TEST_F(demangler_test, empty_string_returns_empty)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string result = demangler.demangle("");
EXPECT_TRUE(result.empty());
EXPECT_EQ(mock_demangler_impl::call_count, 0);
}
TEST_F(demangler_test, caching_prevents_redundant_calls)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string first = demangler.demangle("mangled");
std::string second = demangler.demangle("mangled");
std::string third = demangler.demangle("mangled");
EXPECT_EQ(first, "demangled_mangled");
EXPECT_EQ(first, second);
EXPECT_EQ(second, third);
EXPECT_EQ(mock_demangler_impl::call_count, 1);
}
TEST_F(demangler_test, different_inputs_cached_separately)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string result1 = demangler.demangle("mangled1");
std::string result2 = demangler.demangle("mangled2");
std::string result1_again = demangler.demangle("mangled1");
EXPECT_EQ(result1, "demangled_mangled1");
EXPECT_EQ(result2, "demangled_mangled2");
EXPECT_EQ(result1, result1_again);
EXPECT_EQ(mock_demangler_impl::call_count, 2);
}
TEST_F(demangler_test, string_view_interface)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string str = "mangled";
std::string_view sv{ str };
std::string result = demangler.demangle(sv);
EXPECT_EQ(result, "demangled_mangled");
EXPECT_EQ(mock_demangler_impl::call_count, 1);
}
TEST_F(demangler_test, template_demangle_interface)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string result = demangler.demangle<int>();
EXPECT_NE(result.find("demangled_"), std::string::npos);
EXPECT_FALSE(result.empty());
}
TEST_F(demangler_test, cache_handles_failed_demangle)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler;
std::string first = demangler.demangle("FAIL");
std::string second = demangler.demangle("FAIL");
EXPECT_EQ(first, "FAIL");
EXPECT_EQ(first, second);
EXPECT_EQ(mock_demangler_impl::call_count, 1);
}
TEST_F(demangler_test, multiple_instances_independent_caches)
{
rocprofsys::utility::demangler<mock_demangler_impl> demangler1;
rocprofsys::utility::demangler<mock_demangler_impl> demangler2;
demangler1.demangle("mangled");
demangler2.demangle("mangled");
EXPECT_EQ(mock_demangler_impl::call_count, 2);
}
@@ -25,6 +25,7 @@
#include "core/agent_manager.hpp"
#include "core/categories.hpp"
#include "core/common_types.hpp"
#include "core/demangler.hpp"
#include "core/gpu_metrics.hpp"
#include "core/utility.hpp"
#include "library/tracing.hpp"
@@ -474,7 +475,7 @@ perfetto_processor_t::handle([[maybe_unused]] const kernel_dispatch_sample& _kds
throw std::runtime_error("Kernel symbol is missing for kernel dispatch");
}
auto kernel_name = tim::demangle(kernel_symbol->kernel_name);
auto kernel_name = rocprofsys::utility::demangle(kernel_symbol->kernel_name);
const auto _track =
tracing::get_perfetto_track(category::rocm_kernel_dispatch{}, _track_desc,
@@ -25,6 +25,7 @@
#include "core/common_types.hpp"
#include "core/config.hpp"
#include "core/debug.hpp"
#include "core/demangler.hpp"
#include "core/gpu_metrics.hpp"
#include "core/node_info.hpp"
#include "core/rocpd/data_processor.hpp"
@@ -39,7 +40,6 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <timemory/utility/demangle.hpp>
#if ROCPROFSYS_USE_ROCM > 0
# include "library/rocprofiler-sdk/fwd.hpp"
@@ -91,7 +91,7 @@ rocpd_processor_t::handle([[maybe_unused]] const kernel_dispatch_sample& _kds)
}
auto region_name_primary_key = m_data_processor->insert_string(
tim::demangle(kernel_symbol->kernel_name).c_str());
rocprofsys::utility::demangle(kernel_symbol->kernel_name).c_str());
auto stack_id = _kds.correlation_id_internal;
auto parent_stack_id = _kds.correlation_id_ancestor;
@@ -698,7 +698,7 @@ rocpd_processor_t::post_process_metadata()
auto _kernel_symbols_list = m_metadata->get_kernel_symbol_list();
for(const auto& kernel_symbol : _kernel_symbols_list)
{
auto kernel_name = tim::demangle(kernel_symbol.kernel_name);
auto kernel_name = rocprofsys::utility::demangle(kernel_symbol.kernel_name);
m_data_processor->insert_kernel_symbol(
kernel_symbol.kernel_id, n_info.id, process_info.pid,
kernel_symbol.code_object_id, kernel_symbol.kernel_name, kernel_name.c_str(),