From fddef714a07800f400b28bc9450af32cb2bd5d88 Mon Sep 17 00:00:00 2001 From: Milan Radosavljevic Date: Wed, 3 Dec 2025 15:25:33 +0100 Subject: [PATCH] [rocprofiler-systems] Add trace_cache unit tests (#2086) Improve test coverage and reliability of the trace_cache module by adding comprehensive unit tests for all major components. --- .../lib/core/trace_cache/CMakeLists.txt | 4 + .../lib/core/trace_cache/buffer_storage.cpp | 13 +- .../lib/core/trace_cache/buffer_storage.hpp | 14 +- .../lib/core/trace_cache/cache_manager.cpp | 10 +- .../lib/core/trace_cache/cache_manager.hpp | 4 + .../source/lib/core/trace_cache/cacheable.hpp | 1 - .../lib/core/trace_cache/sample_type.hpp | 3 +- .../lib/core/trace_cache/storage_parser.hpp | 24 +- .../lib/core/trace_cache/tests/CMakeLists.txt | 43 ++ .../core/trace_cache/tests/mocked_types.hpp | 167 +++++ .../trace_cache/tests/test_buffer_storage.cpp | 461 ++++++++++++++ .../tests/test_cache_integration.cpp | 556 +++++++++++++++++ .../core/trace_cache/tests/test_cacheable.cpp | 316 ++++++++++ .../trace_cache/tests/test_flush_worker.cpp | 206 +++++++ .../trace_cache/tests/test_sample_type.cpp | 568 ++++++++++++++++++ .../trace_cache/tests/test_storage_parser.cpp | 466 ++++++++++++++ .../trace_cache/tests/test_type_registry.cpp | 126 ++++ .../source/lib/rocprof-sys/library.cpp | 6 +- .../source/tests/CMakeLists.txt | 7 +- 19 files changed, 2953 insertions(+), 42 deletions(-) create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_flush_worker.cpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt index 57842f9a2f..e73da2e8e9 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt @@ -48,3 +48,7 @@ target_include_directories( rocprofiler-systems-core-library PUBLIC ${CMAKE_CURRENT_LIST_DIR} ) + +if(ROCPROFSYS_BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp index 74cc0e47f1..c5f20156d0 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp @@ -21,14 +21,10 @@ // SOFTWARE. #include "buffer_storage.hpp" -#include "PTL/Task.hh" -#include "PTL/TaskGroup.hh" -#include "PTL/ThreadPool.hh" -#include "debug.hpp" -#include "library/runtime.hpp" -#include + #include #include +#include #include #include #include @@ -71,8 +67,6 @@ flush_worker_t::start(const pid_t& current_pid) m_worker_synchronization->origin_pid = current_pid; m_worker_synchronization->is_running = true; - ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false); - m_flushing_thread = std::make_unique([&]() { std::mutex _shutdown_condition_mutex; while(m_worker_synchronization->is_running) @@ -99,7 +93,6 @@ flush_worker_t::stop(const pid_t& current_pid) if(flushing_thread_exist && worker_is_running) { - ROCPROFSYS_DEBUG("Buffer storage shutting down..\n"); m_worker_synchronization->is_running = false; m_worker_synchronization->is_running_condition.notify_all(); @@ -107,8 +100,6 @@ flush_worker_t::stop(const pid_t& current_pid) current_pid == m_worker_synchronization->origin_pid; if(!thread_is_created_in_this_process) { - ROCPROFSYS_DEBUG( - "Buffer storage is not created in same process as shutting down..\n"); return; } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp index 78dcebc656..d282adc982 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp @@ -25,16 +25,19 @@ #include "core/trace_cache/cacheable.hpp" #include "common/defines.h" -#include "core/debug.hpp" +#include #include #include #include #include #include +#include +#include #include #include #include +#include #include #include @@ -134,7 +137,6 @@ public: { throw std::runtime_error( "Worker is null - unable to shutdown buffered storage."); - return; } if(!is_running()) @@ -213,11 +215,9 @@ private: } if(ofs.fail()) { - ROCPROFSYS_WARNING(1, "Error flushing buffered storage to file for pid: %d", - m_worker_synchronization->origin_pid); - ROCPROFSYS_CI_THROW(true, - "Error flushing buffered storage to file for pid: %d", - m_worker_synchronization->origin_pid); + throw std::runtime_error( + std::string("Error flushing buffered storage to file for pid: ") + + std::to_string(m_worker_synchronization->origin_pid) + "\n"); } } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp index b9b5ec6701..8d3ba87c40 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include namespace rocprofsys @@ -444,7 +445,14 @@ process_buffered_storage( storage_parser_t _parser(_storage_filename); _processor_coordinator->prepare_for_processing(); - _parser.load(_processor_coordinator); + try + { + _parser.load(_processor_coordinator); + + } catch(const std::runtime_error& exp) + { + ROCPROFSYS_WARNING(1, "Error parsing buffered storage: %s\n", exp.what()); + } _processor_coordinator->finalize_processing(); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.hpp index 7091272943..47e8cd2a5a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.hpp @@ -26,7 +26,11 @@ #include "core/trace_cache/metadata_registry.hpp" #include "core/trace_cache/sample_type.hpp" #include "core/trace_cache/storage_parser.hpp" + +#include "library/runtime.hpp" + #include +#include namespace rocprofsys { diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 0fef2bfdd3..710e64f573 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -22,7 +22,6 @@ #pragma once #include "core/trace_cache/cache_type_traits.hpp" -#include "library/runtime.hpp" #include #include diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp index 6e1114cbdb..9bd20bfc8c 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp @@ -22,6 +22,7 @@ #pragma once #include "core/trace_cache/cacheable.hpp" + #include #include #include @@ -407,7 +408,7 @@ inline void serialize(uint8_t* buffer, const in_time_sample& item) { utility::store_value( - buffer, std::string_view(item.track_name), + buffer, item.category_enum_id, std::string_view(item.track_name), static_cast(item.timestamp_ns), std::string_view(item.event_metadata), static_cast(item.stack_id), static_cast(item.parent_stack_id), static_cast(item.correlation_id), std::string_view(item.call_stack), diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp index 20e446be7f..5431390972 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp @@ -23,7 +23,7 @@ #pragma once #include "common/defines.h" -#include "core/debug.hpp" + #include "core/trace_cache/cacheable.hpp" #include "core/trace_cache/type_registry.hpp" @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include #include #include @@ -67,9 +67,6 @@ public: throw std::runtime_error("TypeProcessing is nullptr"); } - ROCPROFSYS_DEBUG("Consuming buffered storage with filename: %s\n", - m_filename.c_str()); - std::ifstream ifs(m_filename, std::ios::binary); if(!ifs.good()) { @@ -94,10 +91,9 @@ public: { if(!ifs.good()) { - ROCPROFSYS_WARNING(0, - "Stream not in good state, stopping parse. File: %s\n", - m_filename.c_str()); - break; + throw std::runtime_error( + std::string("Stream not in good state, stopping parse. File: ") + + m_filename + "\n"); } ifs.read(reinterpret_cast(&header), sizeof(header)); @@ -118,11 +114,10 @@ public: if(ifs.fail()) { - ROCPROFSYS_WARNING(1, - "Bad read while consuming buffered storage. Filename: " - "%s Bytes read: %d\n", - m_filename.c_str(), static_cast(ifs.tellg())); - continue; + throw std::runtime_error( + std::string("Bad read while consuming buffered storage. Filename: ") + + m_filename + " Bytes read: " + + std::to_string(static_cast(ifs.tellg())) + "\n"); } if(header.type == TypeIdentifierEnum::fragmented_space) @@ -144,7 +139,6 @@ public: } else { - ROCPROFSYS_DEBUG("Unsupported type detected. Skipping current sample.\n"); continue; } } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt new file mode 100644 index 0000000000..c575eb7d5f --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# 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. + +set(trace_cache_tests_sources + test_type_registry.cpp + test_cacheable.cpp + test_buffer_storage.cpp + test_storage_parser.cpp + test_flush_worker.cpp + test_cache_integration.cpp + test_sample_type.cpp +) + +add_library(trace-cache-tests OBJECT ${trace_cache_tests_sources}) + +target_link_libraries( + trace-cache-tests + PRIVATE rocprofiler-systems-googletest-library rocprofiler-systems-core-library +) + +target_include_directories( + trace-cache-tests + PRIVATE ${PROJECT_SOURCE_DIR}/source/lib ${CMAKE_BINARY_DIR}/source/lib/ +) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp new file mode 100644 index 0000000000..eaea4f4b7c --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp @@ -0,0 +1,167 @@ +// 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 "core/trace_cache/cacheable.hpp" + +#include +#include +#include + +enum class test_type_identifier_t : uint32_t +{ + sample_type_1 = 1, + sample_type_2 = 2, + sample_type_3 = 3, + fragmented_space = 0xFFFF +}; +struct test_sample_1 : public rocprofsys::trace_cache::cacheable_t +{ + static constexpr test_type_identifier_t type_identifier = + test_type_identifier_t::sample_type_1; + + test_sample_1() = default; + test_sample_1(int v, std::string_view s) + : value(v) + , text(s) + {} + + int value = 0; + std::string_view text; + + bool operator==(const test_sample_1& other) const + { + return value == other.value && text == other.text; + } +}; + +struct test_sample_2 : public rocprofsys::trace_cache::cacheable_t +{ + static constexpr test_type_identifier_t type_identifier = + test_type_identifier_t::sample_type_2; + + test_sample_2() = default; + test_sample_2(double d, uint32_t id) + : data(d) + , sample_id(id) + {} + + double data = 0.0; + uint32_t sample_id = 0; + + bool operator==(const test_sample_2& other) const + { + if(sample_id != other.sample_id) return false; + + if(std::isnan(data) && std::isnan(other.data)) return true; + + if(std::isinf(data) && std::isinf(other.data)) + return std::signbit(data) == std::signbit(other.data); + + return std::abs(data - other.data) < 1e-9; + } +}; + +struct test_sample_3 : public rocprofsys::trace_cache::cacheable_t +{ + static constexpr test_type_identifier_t type_identifier = + test_type_identifier_t::sample_type_3; + + test_sample_3() = default; + test_sample_3(std::vector p) + : payload(std::move(p)) + {} + + std::vector payload; + + bool operator==(const test_sample_3& other) const { return payload == other.payload; } +}; + +template <> +inline void +rocprofsys::trace_cache::serialize(uint8_t* buffer, const test_sample_1& item) +{ + rocprofsys::trace_cache::utility::store_value(buffer, item.value, item.text); +} + +template <> +inline test_sample_1 +rocprofsys::trace_cache::deserialize(uint8_t*& buffer) +{ + test_sample_1 result; + rocprofsys::trace_cache::utility::parse_value(buffer, result.value, result.text); + return result; +} + +template <> +inline size_t +rocprofsys::trace_cache::get_size(const test_sample_1& item) +{ + return rocprofsys::trace_cache::utility::get_size(item.value, item.text); +} + +template <> +inline void +rocprofsys::trace_cache::serialize(uint8_t* buffer, const test_sample_2& item) +{ + rocprofsys::trace_cache::utility::store_value(buffer, item.data, item.sample_id); +} + +template <> +inline test_sample_2 +rocprofsys::trace_cache::deserialize(uint8_t*& buffer) +{ + test_sample_2 result; + rocprofsys::trace_cache::utility::parse_value(buffer, result.data, result.sample_id); + return result; +} + +template <> +inline size_t +rocprofsys::trace_cache::get_size(const test_sample_2& item) +{ + return rocprofsys::trace_cache::utility::get_size(item.data, item.sample_id); +} + +template <> +inline void +rocprofsys::trace_cache::serialize(uint8_t* buffer, const test_sample_3& item) +{ + rocprofsys::trace_cache::utility::store_value(buffer, item.payload); +} + +template <> +inline test_sample_3 +rocprofsys::trace_cache::deserialize(uint8_t*& buffer) +{ + test_sample_3 result; + rocprofsys::trace_cache::utility::parse_value(buffer, result.payload); + return result; +} + +template <> +inline size_t +rocprofsys::trace_cache::get_size(const test_sample_3& item) +{ + return rocprofsys::trace_cache::utility::get_size(item.payload); +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp new file mode 100644 index 0000000000..b398fb52b0 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp @@ -0,0 +1,461 @@ +// 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 "core/trace_cache/buffer_storage.hpp" +#include "mocked_types.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +template +void +verify_buffer_contains(const T& sample, const uint8_t* buffer, size_t& buffer_pos) +{ + auto type_id = *reinterpret_cast(buffer + buffer_pos); + EXPECT_EQ(type_id, T::type_identifier); + buffer_pos += sizeof(test_type_identifier_t); + + auto size = *reinterpret_cast(buffer + buffer_pos); + EXPECT_EQ(size, rocprofsys::trace_cache::get_size(sample)); + buffer_pos += sizeof(size_t); + + uint8_t* deserialize_ptr = const_cast(buffer + buffer_pos); + auto deserialized = rocprofsys::trace_cache::deserialize(deserialize_ptr); + EXPECT_EQ(deserialized, sample); + buffer_pos += size; +} + +struct mock_worker_t +{ + explicit mock_worker_t(rocprofsys::trace_cache::worker_function_t worker_function, + rocprofsys::trace_cache::worker_synchronization_ptr_t sync, + std::string filepath) + + : m_worker_function(std::move(worker_function)) + , m_sync(std::move(sync)) + , m_filepath(std::move(filepath)) + {} + + MOCK_METHOD(void, start, (const pid_t&) ); + MOCK_METHOD(void, stop, (const pid_t&) ); + + void execute_flush(bool force = false) + { + m_worker_function(m_output_string_stream, force); + } + + std::ostringstream m_output_string_stream; + + rocprofsys::trace_cache::worker_function_t m_worker_function; + rocprofsys::trace_cache::worker_synchronization_ptr_t m_sync; + std::string m_filepath; +}; + +std::shared_ptr g_mock_worker; + +struct mock_worker_factory_t +{ + using worker_t = mock_worker_t; + + mock_worker_factory_t() = delete; + mock_worker_factory_t(mock_worker_factory_t&) = delete; + mock_worker_factory_t& operator=(mock_worker_factory_t&) = delete; + mock_worker_factory_t(mock_worker_factory_t&&) = delete; + mock_worker_factory_t& operator=(mock_worker_factory_t&&) = delete; + + static std::shared_ptr get_worker( + rocprofsys::trace_cache::worker_function_t worker_function, + const rocprofsys::trace_cache::worker_synchronization_ptr_t& + worker_synchronization_ptr, + std::string filepath) + { + g_mock_worker = std::make_shared( + worker_function, worker_synchronization_ptr, std::move(filepath)); + return g_mock_worker; + } +}; + +} // namespace + +class buffer_storage_test : public ::testing::Test +{ +protected: + void SetUp() override + { + test_file_path = "test_cache_" + std::to_string(test_counter++) + ".bin"; + std::remove(test_file_path.c_str()); // Be sure that file is created by test case + } + + void TearDown() override + { + std::remove(test_file_path.c_str()); + g_mock_worker.reset(); + } + + std::string test_file_path; + static std::atomic test_counter; + + void SetUpStartStopOnCall() + { + ON_CALL(*g_mock_worker, start).WillByDefault([] { + g_mock_worker->m_sync->is_running = true; + }); + + ON_CALL(*g_mock_worker, stop).WillByDefault([] { + g_mock_worker->m_sync->is_running = false; + }); + } +}; + +std::atomic buffer_storage_test::test_counter{ 0 }; + +TEST_F(buffer_storage_test, multiple_start) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + EXPECT_CALL(*g_mock_worker, start).Times(1).WillOnce([] { + g_mock_worker->m_sync->is_running = true; + }); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + EXPECT_NO_THROW(storage.start()); + EXPECT_NO_THROW(storage.start()); + EXPECT_EQ(test_file_path, g_mock_worker->m_filepath); +} + +TEST_F(buffer_storage_test, start_stop) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + + EXPECT_CALL(*g_mock_worker, start).Times(1).WillOnce([] { + g_mock_worker->m_sync->is_running = true; + }); + EXPECT_CALL(*g_mock_worker, stop).Times(1).WillOnce([] { + g_mock_worker->m_sync->is_running = false; + }); + + storage.start(); + storage.shutdown(); +} + +TEST_F(buffer_storage_test, try_store_event_sample_throw) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + + EXPECT_CALL(*g_mock_worker, start).Times(0); + EXPECT_CALL(*g_mock_worker, stop).Times(0); + + test_sample_1 sample{ 10, "test string" }; + EXPECT_THROW(storage.store(sample), std::runtime_error); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + EXPECT_NO_THROW(storage.shutdown()); +} + +TEST_F(buffer_storage_test, store_after_shutdown) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + storage.start(); + test_sample_1 before_shutdown(1, "before"); + EXPECT_NO_THROW(storage.store(before_shutdown)); + + EXPECT_NO_THROW(storage.shutdown()); + + test_sample_1 after_shutdown(2, "after"); + EXPECT_THROW(storage.store(after_shutdown), std::runtime_error); +} + +TEST_F(buffer_storage_test, store_event_samples) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + EXPECT_NO_THROW(storage.start()); + test_sample_1 sample{ 10, "test string" }; + EXPECT_NO_THROW(storage.store(sample)); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + EXPECT_NO_THROW(storage.shutdown()); +} + +TEST_F(buffer_storage_test, immediately_flush) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + EXPECT_NO_THROW(storage.start()); + EXPECT_NO_THROW(g_mock_worker->execute_flush(true)); + EXPECT_NO_THROW(storage.shutdown()); +} + +TEST_F(buffer_storage_test, flush_below_threshold) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + EXPECT_NO_THROW(storage.start()); + + test_sample_1 sample{ 10, "test string" }; + EXPECT_NO_THROW(storage.store(sample)); + + EXPECT_NO_THROW(g_mock_worker->execute_flush()); + EXPECT_EQ(g_mock_worker->m_output_string_stream.str().size(), 0); + EXPECT_NO_THROW(storage.shutdown()); +} + +TEST_F(buffer_storage_test, MixedSampleTypes) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + storage.start(); + test_sample_1 sample1(42, "event_data"); + test_sample_2 sample2(3.14159, 1001); + test_sample_3 sample3({ 0xAA, 0xBB, 0xCC, 0xDD }); + + // Empty samples + test_sample_3 sample4; + test_sample_1 sample5; + + EXPECT_NO_THROW(storage.store(sample1)); + EXPECT_NO_THROW(storage.store(sample2)); + EXPECT_NO_THROW(storage.store(sample3)); + EXPECT_NO_THROW(storage.store(sample1)); + EXPECT_NO_THROW(storage.store(sample4)); + EXPECT_NO_THROW(storage.store(sample5)); + + g_mock_worker->execute_flush(true); + + EXPECT_NO_THROW(storage.shutdown()); + + std::string buffer_data = g_mock_worker->m_output_string_stream.str(); + ASSERT_FALSE(buffer_data.empty()); + + const uint8_t* buffer = reinterpret_cast(buffer_data.data()); + size_t buffer_pos = 0; + + verify_buffer_contains(sample1, buffer, buffer_pos); + verify_buffer_contains(sample2, buffer, buffer_pos); + verify_buffer_contains(sample3, buffer, buffer_pos); + verify_buffer_contains(sample1, buffer, buffer_pos); + verify_buffer_contains(sample4, buffer, buffer_pos); + verify_buffer_contains(sample5, buffer, buffer_pos); + + EXPECT_EQ(buffer_pos, buffer_data.size()); +} + +TEST_F(buffer_storage_test, large_payload_handling) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + storage.start(); + std::vector large_payload(5000, 0xFF); + test_sample_3 large_sample(large_payload); + + EXPECT_NO_THROW(storage.store(large_sample)); + + for(int i = 0; i < 10; ++i) + { + std::vector payload(1000 + i * 100, static_cast(i)); + test_sample_3 sample(payload); + EXPECT_NO_THROW(storage.store(sample)); + } + + for(int i = 0; i < 5; ++i) + { + std::string large_string(1000 + i * 200, 'A' + (i % 26)); + test_sample_1 large_text_sample(i * 1000, large_string); + EXPECT_NO_THROW(storage.store(large_text_sample)); + } + + EXPECT_NO_THROW(storage.shutdown()); +} + +TEST_F(buffer_storage_test, concurrent_mixed_type_store) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + storage.start(); + + const int num_threads = 4; + const int items_per_thread = 10; + std::vector threads; + + for(int t = 0; t < num_threads; ++t) + { + threads.emplace_back([&, t]() { + for(int i = 0; i < items_per_thread; ++i) + { + switch(t % 3) + { + case 0: + EXPECT_NO_THROW( + storage.store(test_sample_1(t * 100 + i, "data"))); + break; + case 1: + EXPECT_NO_THROW(storage.store(test_sample_2(t * 2.5 + i, t + i))); + break; + case 2: + EXPECT_NO_THROW( + storage.store(test_sample_3(std::vector(10, t)))); + break; + } + } + }); + } + + for(auto& thread : threads) + { + thread.join(); + } + + g_mock_worker->execute_flush(true); + EXPECT_NO_THROW(storage.shutdown()); + + std::string buffer_data = g_mock_worker->m_output_string_stream.str(); + EXPECT_FALSE(buffer_data.empty()); + + size_t actual_samples = 0; + size_t buffer_pos = 0; + + while(buffer_pos < buffer_data.size()) + { + auto type_id = *reinterpret_cast( + buffer_data.data() + buffer_pos); + buffer_pos += sizeof(test_type_identifier_t); + + auto size = *reinterpret_cast(buffer_data.data() + buffer_pos); + buffer_pos += sizeof(size_t) + size; + + if(type_id != test_type_identifier_t::fragmented_space) + { + actual_samples++; + } + } + + EXPECT_EQ(actual_samples, num_threads * items_per_thread); +} + +TEST_F(buffer_storage_test, repeated_fragmentation) +{ + rocprofsys::trace_cache::buffer_storage + storage(test_file_path); + SetUpStartStopOnCall(); + EXPECT_CALL(*g_mock_worker, start).Times(1); + EXPECT_CALL(*g_mock_worker, stop).Times(1); + + storage.start(); + + const size_t fragment_trigger_size = rocprofsys::trace_cache::buffer_size / 5; + std::vector fragment_payload(fragment_trigger_size, 0xDD); + const int cycle_count = 3; + const int iter_count = 2; + + for(int cycle = 0; cycle < cycle_count; ++cycle) + { + for(int i = 0; i < iter_count; ++i) + { + test_sample_3 sample(fragment_payload); + EXPECT_NO_THROW(storage.store(sample)); + } + + test_sample_1 small_sample(cycle, "cycle_" + std::to_string(cycle)); + EXPECT_NO_THROW(storage.store(small_sample)); + + g_mock_worker->execute_flush(true); + } + + EXPECT_NO_THROW(storage.shutdown()); + + std::string buffer_data = g_mock_worker->m_output_string_stream.str(); + const uint8_t* buffer = reinterpret_cast(buffer_data.data()); + size_t buffer_pos = 0; + size_t fragmented_space_count = 0; + size_t sample1_count = 0; + size_t sample3_count = 0; + + while(buffer_pos < buffer_data.size()) + { + auto type_id = + *reinterpret_cast(buffer + buffer_pos); + buffer_pos += sizeof(test_type_identifier_t); + + auto size = *reinterpret_cast(buffer + buffer_pos); + buffer_pos += sizeof(size_t) + size; + + switch(type_id) + { + case test_type_identifier_t::sample_type_1: sample1_count++; break; + case test_type_identifier_t::sample_type_3: sample3_count++; break; + case test_type_identifier_t::fragmented_space: + fragmented_space_count++; + break; + case test_type_identifier_t::sample_type_2: + FAIL() << "Unexpected sample type"; + break; + } + } + + EXPECT_EQ(sample1_count, cycle_count); + EXPECT_EQ(sample3_count, cycle_count * iter_count); + EXPECT_GT(fragmented_space_count, 0); +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp new file mode 100644 index 0000000000..6b616a6d70 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp @@ -0,0 +1,556 @@ +// 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 "mocked_types.hpp" + +#include "core/trace_cache/buffer_storage.hpp" +#include "core/trace_cache/storage_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +struct sample_1_hash +{ + size_t operator()(const test_sample_1& s) const + { + size_t h = std::hash{}(s.value); + + for(size_t i = 0; i < s.text.size(); ++i) + { + h ^= std::hash{}(s.text[i]) + 0x9e3779b9 + (h << 6) + (h >> 2) + i; + } + + return h; + } +}; + +struct sample_2_hash +{ + size_t operator()(const test_sample_2& s) const + { + size_t h1 = std::hash{}(s.data); + size_t h2 = std::hash{}(s.sample_id); + return h1 ^ (h2 << 1); + } +}; + +struct sample_3_hash +{ + size_t operator()(const test_sample_3& s) const + { + size_t h = 0; + for(auto byte : s.payload) + { + h ^= std::hash{}(byte) + 0x9e3779b9 + (h << 6) + (h >> 2); + } + return h; + } +}; + +} // namespace + +class integration_sample_processor_t +{ +public: + integration_sample_processor_t() = default; + + void set_expected_samples_1(const std::vector& samples) + { + std::lock_guard lock(m_data_mutex); + m_expected_samples_1.clear(); + for(const auto& s : samples) + { + m_expected_samples_1[s]++; + } + } + + void set_expected_samples_2(const std::vector& samples) + { + std::lock_guard lock(m_data_mutex); + m_expected_samples_2.clear(); + for(const auto& s : samples) + { + m_expected_samples_2[s]++; + } + } + + void set_expected_samples_3(const std::vector& samples) + { + std::lock_guard lock(m_data_mutex); + m_expected_samples_3.clear(); + for(const auto& s : samples) + { + m_expected_samples_3[s]++; + } + } + + void execute_sample_processing(test_type_identifier_t type_identifier, + const rocprofsys::trace_cache::cacheable_t& value) + { + switch(type_identifier) + { + case test_type_identifier_t::sample_type_1: + { + const auto& sample = static_cast(value); + std::lock_guard lock(m_data_mutex); + m_sample_1_count++; + check_sample_1(sample); + break; + } + case test_type_identifier_t::sample_type_2: + { + const auto& sample = static_cast(value); + std::lock_guard lock(m_data_mutex); + m_sample_2_count++; + check_sample_2(sample); + break; + } + case test_type_identifier_t::sample_type_3: + { + const auto& sample = static_cast(value); + std::lock_guard lock(m_data_mutex); + m_sample_3_count++; + check_sample_3(sample); + break; + } + default: break; + } + } + + int get_sample_1_count() const { return m_sample_1_count.load(); } + int get_sample_2_count() const { return m_sample_2_count.load(); } + int get_sample_3_count() const { return m_sample_3_count.load(); } + bool all_expected_samples_found() const + { + std::lock_guard lock(m_data_mutex); + return m_expected_samples_1.empty() && m_expected_samples_2.empty() && + m_expected_samples_3.empty(); + } + +private: + void check_sample_1(const test_sample_1& sample) + { + auto it = m_expected_samples_1.find(sample); + EXPECT_NE(it, m_expected_samples_1.end()); + if(it != m_expected_samples_1.end()) + { + it->second--; + if(it->second == 0) + { + m_expected_samples_1.erase(it); + } + } + } + + void check_sample_2(const test_sample_2& sample) + { + auto it = m_expected_samples_2.find(sample); + EXPECT_NE(it, m_expected_samples_2.end()); + if(it != m_expected_samples_2.end()) + { + it->second--; + if(it->second == 0) + { + m_expected_samples_2.erase(it); + } + } + } + + void check_sample_3(const test_sample_3& sample) + { + auto it = m_expected_samples_3.find(sample); + EXPECT_NE(it, m_expected_samples_3.end()); + if(it != m_expected_samples_3.end()) + { + it->second--; + if(it->second == 0) + { + m_expected_samples_3.erase(it); + } + } + } + + std::atomic m_sample_1_count{ 0 }; + std::atomic m_sample_2_count{ 0 }; + std::atomic m_sample_3_count{ 0 }; + std::unordered_map m_expected_samples_1; + std::unordered_map m_expected_samples_2; + std::unordered_map m_expected_samples_3; + mutable std::mutex m_data_mutex; +}; + +class trace_cache_module_integration_test : public ::testing::Test +{ +protected: + void SetUp() override + { + test_file_path = + "integration_test_cache_" + std::to_string(test_counter++) + ".bin"; + std::remove(test_file_path.c_str()); + } + + void TearDown() override { std::remove(test_file_path.c_str()); } + + std::string test_file_path; + static std::atomic test_counter; +}; + +std::atomic trace_cache_module_integration_test::test_counter{ 0 }; + +TEST_F(trace_cache_module_integration_test, buffer_fragmentation_handling) +{ + std::vector large_texts; + large_texts.reserve(100); + std::vector large_samples; + large_samples.reserve(100); + std::vector small_samples; + small_samples.reserve(100); + + for(int i = 0; i < 100; ++i) + { + large_texts.push_back(std::string(1000, 'A' + (i % 26))); + large_samples.push_back({ i, large_texts[i] }); + + std::vector small_payload(10, static_cast(i)); + small_samples.emplace_back(small_payload); + } + + std::vector expected_1; + std::vector expected_3; + expected_1.reserve(100); + expected_3.reserve(50); + + { + rocprofsys::trace_cache::buffer_storage< + rocprofsys::trace_cache::flush_worker_factory_t, test_type_identifier_t> + storage(test_file_path); + storage.start(); + + for(size_t i = 0; i < large_samples.size(); ++i) + { + storage.store(large_samples[i]); + expected_1.push_back(large_samples[i]); + if(i % 2 == 0 && i < small_samples.size()) + { + storage.store(small_samples[i]); + expected_3.push_back(small_samples[i]); + } + } + + storage.shutdown(); + } + + auto processor = std::make_shared(); + processor->set_expected_samples_1(expected_1); + processor->set_expected_samples_3(expected_3); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + parser.load(processor); + + EXPECT_EQ(processor->get_sample_1_count(), 100); + EXPECT_EQ(processor->get_sample_3_count(), 50); + EXPECT_EQ(processor->get_sample_1_count() + processor->get_sample_3_count(), 150); +} + +TEST_F(trace_cache_module_integration_test, content_validation_edge_cases) +{ + std::vector strings; + strings.reserve(4); + strings.emplace_back("max_value"); + strings.emplace_back("min_value"); + strings.emplace_back(""); + strings.emplace_back("Special\n\t\r\0chars"); + + test_sample_1 max_int(std::numeric_limits::max(), strings[0]); + test_sample_1 min_int(std::numeric_limits::min(), strings[1]); + test_sample_1 zero_int(0, strings[2]); + test_sample_1 special_chars(123, strings[3]); + + test_sample_2 max_double(std::numeric_limits::max(), + std::numeric_limits::max()); + test_sample_2 min_double(std::numeric_limits::lowest(), 0); + test_sample_2 infinity(std::numeric_limits::infinity(), 42); + test_sample_2 neg_infinity(-std::numeric_limits::infinity(), 43); + + std::vector max_vector(10000, 0xFF); + test_sample_3 large_payload(max_vector); + test_sample_3 empty_payload; + std::vector single_zero = { 0x00 }; + test_sample_3 zero_payload(single_zero); + + std::vector expected_1; + std::vector expected_2; + std::vector expected_3; + expected_1.reserve(4); + expected_2.reserve(4); + expected_3.reserve(3); + + { + rocprofsys::trace_cache::buffer_storage< + rocprofsys::trace_cache::flush_worker_factory_t, test_type_identifier_t> + storage(test_file_path); + storage.start(); + + storage.store(max_int); + expected_1.push_back(max_int); + storage.store(min_int); + expected_1.push_back(min_int); + storage.store(zero_int); + expected_1.push_back(zero_int); + storage.store(special_chars); + expected_1.push_back(special_chars); + + storage.store(max_double); + expected_2.push_back(max_double); + storage.store(min_double); + expected_2.push_back(min_double); + storage.store(infinity); + expected_2.push_back(infinity); + storage.store(neg_infinity); + expected_2.push_back(neg_infinity); + + storage.store(large_payload); + expected_3.push_back(large_payload); + storage.store(empty_payload); + expected_3.push_back(empty_payload); + storage.store(zero_payload); + expected_3.push_back(zero_payload); + + storage.shutdown(); + } + + auto processor = std::make_shared(); + processor->set_expected_samples_1(expected_1); + processor->set_expected_samples_2(expected_2); + processor->set_expected_samples_3(expected_3); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + parser.load(processor); + + EXPECT_EQ(processor->get_sample_1_count(), 4); + EXPECT_EQ(processor->get_sample_2_count(), 4); + EXPECT_EQ(processor->get_sample_3_count(), 3); +} + +TEST_F(trace_cache_module_integration_test, stress_test_multiple_fragmentations) +{ + const int iterations = 10; + const int samples_per_iteration = 10000; + + std::mt19937 rng(42); + std::uniform_int_distribution value_dist(1, 1000); + std::uniform_int_distribution size_dist(1, 500); + + std::vector texts; + texts.reserve(iterations * samples_per_iteration); + std::vector expected_1; + expected_1.reserve(iterations * samples_per_iteration); + + { + rocprofsys::trace_cache::buffer_storage< + rocprofsys::trace_cache::flush_worker_factory_t, test_type_identifier_t> + storage(test_file_path); + storage.start(); + + for(int iter = 0; iter < iterations; ++iter) + { + for(int i = 0; i < samples_per_iteration; ++i) + { + int value = value_dist(rng); + size_t text_size = size_dist(rng); + texts.push_back(std::string(text_size, 'X')); + + test_sample_1 sample(value, texts.back()); + expected_1.push_back(sample); + storage.store(sample); + } + } + + storage.shutdown(); + } + + auto processor = std::make_shared(); + processor->set_expected_samples_1(expected_1); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + parser.load(processor); + + EXPECT_EQ(processor->get_sample_1_count(), iterations * samples_per_iteration); +} + +TEST_F(trace_cache_module_integration_test, performance_write_test) +{ + const int sample_count = 50000; + const size_t payload_size = 1024 * 2; + + std::vector payloads; + payloads.reserve(sample_count); + std::vector samples; + samples.reserve(sample_count); + for(int i = 0; i < sample_count; ++i) + { + payloads.push_back(std::string(payload_size, static_cast(i % 255))); + samples.push_back({ i, payloads[i] }); + } + + auto start_time = std::chrono::high_resolution_clock::now(); + + { + rocprofsys::trace_cache::buffer_storage< + rocprofsys::trace_cache::flush_worker_factory_t, test_type_identifier_t> + storage(test_file_path); + storage.start(); + + for(const auto& sample : samples) + { + storage.store(sample); + } + + storage.shutdown(); + } + + using unit = std::chrono::microseconds; + + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration_in_microseconds = + std::chrono::duration_cast(end_time - start_time); + auto period = static_cast(unit::period().den); + + double avg_write_time = + static_cast(duration_in_microseconds.count()) / sample_count; + double throughput = + (sample_count * payload_size) / (duration_in_microseconds.count() / period); + + EXPECT_LT(avg_write_time, 50.0); + EXPECT_GT(throughput, 10 * 1024.0); + + auto processor = std::make_shared(); + processor->set_expected_samples_1(samples); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + parser.load(processor); + + EXPECT_EQ(processor->get_sample_1_count(), sample_count); +} + +TEST_F(trace_cache_module_integration_test, concurrent_write_read_validation) +{ + const int thread_count = 4; + const int samples_per_thread = 250; + const int total_samples = thread_count * samples_per_thread; + std::vector writers; + std::vector thread_counters(thread_count, 0); + + std::vector> thread_strings(thread_count); + for(int t = 0; t < thread_count; ++t) + { + thread_strings[t].reserve(samples_per_thread); + for(int i = 0; i < samples_per_thread; ++i) + { + thread_strings[t].push_back("thread_" + std::to_string(t) + "_sample_" + + std::to_string(i)); + } + } + + std::vector expected_1; + expected_1.reserve(total_samples); + + for(int t = 0; t < thread_count; ++t) + { + for(int i = 0; i < samples_per_thread; ++i) + { + expected_1.emplace_back(t, thread_strings[t][i]); + } + } + + { + rocprofsys::trace_cache::buffer_storage< + rocprofsys::trace_cache::flush_worker_factory_t, test_type_identifier_t> + storage(test_file_path); + storage.start(); + + for(int t = 0; t < thread_count; ++t) + { + writers.emplace_back([&, thread_id = t]() { + for(int i = 0; i < samples_per_thread; ++i) + { + test_sample_1 sample(thread_id, thread_strings[thread_id][i]); + + storage.store(sample); + thread_counters[thread_id]++; + + if(i % 10 == 0) + { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } + }); + } + + for(auto& writer : writers) + { + writer.join(); + } + + storage.shutdown(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + int total_written = 0; + for(int counter : thread_counters) + { + EXPECT_EQ(counter, samples_per_thread); + total_written += counter; + } + EXPECT_EQ(total_written, total_samples); + + auto processor = std::make_shared(); + processor->set_expected_samples_1(expected_1); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + parser.load(processor); + + EXPECT_EQ(processor->get_sample_1_count(), total_samples); + EXPECT_TRUE(processor->all_expected_samples_found()); +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp new file mode 100644 index 0000000000..cfe1267e59 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp @@ -0,0 +1,316 @@ +// 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 "core/trace_cache/cacheable.hpp" + +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; + +class cacheable_test : public ::testing::Test +{ +protected: + void SetUp() override + { + buffer.fill(0); + position = 0; + } + + std::array buffer; + size_t position; +}; + +TEST_F(cacheable_test, store_value_int) +{ + int value = 42; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + EXPECT_EQ(position, sizeof(int)); + int stored_value = *reinterpret_cast(buffer.data()); + EXPECT_EQ(stored_value, 42); +} + +TEST_F(cacheable_test, store_value_double) +{ + double value = 3.14159; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + EXPECT_EQ(position, sizeof(double)); + double stored_value = *reinterpret_cast(buffer.data()); + EXPECT_DOUBLE_EQ(stored_value, 3.14159); +} + +TEST_F(cacheable_test, store_value_unsigned_long) +{ + unsigned long value = 123456789UL; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + EXPECT_EQ(position, sizeof(unsigned long)); + unsigned long stored_value = *reinterpret_cast(buffer.data()); + EXPECT_EQ(stored_value, 123456789UL); +} + +TEST_F(cacheable_test, store_value_unsigned_char) +{ + unsigned char value = 255; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + EXPECT_EQ(position, sizeof(unsigned char)); + unsigned char stored_value = *reinterpret_cast(buffer.data()); + EXPECT_EQ(stored_value, 255); +} + +TEST_F(cacheable_test, store_value_string_literal) +{ + auto value = "Hello World"sv; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + size_t expected_size = value.size() + sizeof(size_t); + EXPECT_EQ(position, expected_size); + + std::string stored_value( + reinterpret_cast(buffer.data() + sizeof(size_t))); + EXPECT_EQ(stored_value, "Hello World"); +} + +TEST_F(cacheable_test, store_value_empty_string) +{ + auto value = ""sv; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + EXPECT_EQ(position, sizeof(size_t)); + EXPECT_EQ(buffer[0], '\0'); +} + +TEST_F(cacheable_test, store_value_byte_array) +{ + std::vector value = { 1, 2, 3, 4, 5 }; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + size_t expected_size = value.size() + sizeof(size_t); + EXPECT_EQ(position, expected_size); + + size_t stored_size = *reinterpret_cast(buffer.data()); + EXPECT_EQ(stored_size, 5); + + uint8_t* data_start = buffer.data() + sizeof(size_t); + for(size_t i = 0; i < value.size(); ++i) + { + EXPECT_EQ(data_start[i], value[i]); + } +} + +TEST_F(cacheable_test, store_value_empty_byte_array) +{ + std::vector value; + rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position); + + EXPECT_EQ(position, sizeof(size_t)); + size_t stored_size = *reinterpret_cast(buffer.data()); + EXPECT_EQ(stored_size, 0); +} + +TEST_F(cacheable_test, store_multiple_values) +{ + int int_val = 100; + double double_val = 2.718; + auto str_val = "test"sv; + + rocprofsys::trace_cache::utility::store_value(int_val, buffer.data(), position); + rocprofsys::trace_cache::utility::store_value(double_val, buffer.data(), position); + rocprofsys::trace_cache::utility::store_value(str_val, buffer.data(), position); + + size_t expected_total = + sizeof(int) + sizeof(double) + str_val.size() + sizeof(size_t); + EXPECT_EQ(position, expected_total); +} + +TEST_F(cacheable_test, parse_value_int) +{ + int original_value = 987; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + int parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_EQ(parsed_value, 987); + EXPECT_EQ(data_pos, buffer.data() + sizeof(int)); +} + +TEST_F(cacheable_test, parse_value_double) +{ + double original_value = 1.618033988; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + double parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_DOUBLE_EQ(parsed_value, 1.618033988); + EXPECT_EQ(data_pos, buffer.data() + sizeof(double)); +} + +TEST_F(cacheable_test, parse_value_unsigned_long) +{ + unsigned long original_value = 0xDEADBEEF; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + unsigned long parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_EQ(parsed_value, 0xDEADBEEF); + EXPECT_EQ(data_pos, buffer.data() + sizeof(unsigned long)); +} + +TEST_F(cacheable_test, parse_value_string) +{ + auto original_value = "Parse this string"sv; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + std::string_view parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_EQ(parsed_value, "Parse this string"); + EXPECT_EQ(data_pos, buffer.data() + original_value.size() + sizeof(size_t)); +} + +TEST_F(cacheable_test, parse_value_empty_string) +{ + auto original_value = ""sv; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + std::string_view parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_EQ(parsed_value, ""); + EXPECT_EQ(data_pos, buffer.data() + sizeof(size_t)); +} + +TEST_F(cacheable_test, parse_value_byte_array) +{ + std::vector original_value = { 10, 20, 30, 40, 50 }; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + std::vector parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_EQ(parsed_value.size(), 5); + EXPECT_EQ(parsed_value, original_value); + EXPECT_EQ(data_pos, buffer.data() + sizeof(size_t) + original_value.size()); +} + +TEST_F(cacheable_test, parse_value_empty_byte_array) +{ + std::vector original_value; + rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(), + position); + + uint8_t* data_pos = buffer.data(); + std::vector parsed_value; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_value); + + EXPECT_EQ(parsed_value.size(), 0); + EXPECT_TRUE(parsed_value.empty()); + EXPECT_EQ(data_pos, buffer.data() + sizeof(size_t)); +} +TEST_F(cacheable_test, parse_multiple_values) +{ + int int_val = 42; + double double_val = 3.14; + auto str_val = "multi"sv; + unsigned char uchar_val = 128; + + rocprofsys::trace_cache::utility::store_value(int_val, buffer.data(), position); + rocprofsys::trace_cache::utility::store_value(double_val, buffer.data(), position); + rocprofsys::trace_cache::utility::store_value(str_val, buffer.data(), position); + rocprofsys::trace_cache::utility::store_value(uchar_val, buffer.data(), position); + + uint8_t* data_pos = buffer.data(); + + int parsed_int; + double parsed_double; + std::string_view parsed_string; + unsigned char parsed_uchar; + + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_int, parsed_double, + parsed_string, parsed_uchar); + + EXPECT_EQ(parsed_int, 42); + EXPECT_DOUBLE_EQ(parsed_double, 3.14); + EXPECT_EQ(parsed_string, "multi"); + EXPECT_EQ(parsed_uchar, 128); +} + +TEST_F(cacheable_test, get_size_helper_int) +{ + int value = 42; + size_t size = rocprofsys::trace_cache::utility::get_size(value); + EXPECT_EQ(size, sizeof(int)); +} + +TEST_F(cacheable_test, get_size_helper_double) +{ + double value = 3.14; + size_t size = rocprofsys::trace_cache::utility::get_size(value); + EXPECT_EQ(size, sizeof(double)); +} + +TEST_F(cacheable_test, get_size_helper_string_literal) +{ + auto value = "test string"sv; + size_t size = rocprofsys::trace_cache::utility::get_size(value); + EXPECT_EQ(size, value.size() + sizeof(size_t)); +} + +TEST_F(cacheable_test, get_size_helper_byte_array) +{ + std::vector value = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + size_t size = rocprofsys::trace_cache::utility::get_size(value); + EXPECT_EQ(size, value.size() + sizeof(size_t)); +} + +TEST_F(cacheable_test, get_buffered_storage_filename) +{ + int ppid = 1234; + int pid = 5678; + + std::string filename = + rocprofsys::trace_cache::utility::get_buffered_storage_filename(ppid, pid); + std::string expected = "/tmp/buffered_storage_1234_5678.bin"; + + EXPECT_EQ(filename, expected); +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_flush_worker.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_flush_worker.cpp new file mode 100644 index 0000000000..f15321ac87 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_flush_worker.cpp @@ -0,0 +1,206 @@ +// 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 "core/trace_cache/buffer_storage.hpp" + +#include +#include +#include +#include +#include + +class flush_worker_test : public ::testing::Test +{ +protected: + void SetUp() override + { + test_file_path = + "flush_test_" + std::to_string(test_counter.fetch_add(1)) + ".bin"; + std::remove(test_file_path.c_str()); + worker_sync = + std::make_shared(); + } + + void TearDown() override { std::remove(test_file_path.c_str()); } + + rocprofsys::trace_cache::worker_synchronization_ptr_t worker_sync; + std::string test_file_path; + static std::atomic test_counter; +}; + +std::atomic flush_worker_test::test_counter{ 0 }; + +TEST_F(flush_worker_test, start_worker_in_correct_state) +{ + bool worker_called = false; + auto worker_function = [&](rocprofsys::trace_cache::ofs_t&, bool) { + worker_called = true; + }; + + rocprofsys::trace_cache::flush_worker_t worker(worker_function, worker_sync, + test_file_path); + pid_t current_pid = getpid(); + + worker.start(current_pid); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + EXPECT_TRUE(worker_sync->is_running); + EXPECT_EQ(worker_sync->origin_pid, current_pid); + + worker.stop(current_pid); +} + +TEST_F(flush_worker_test, stop_worker_complete) +{ + std::atomic worker_called{ false }; + auto worker_function = [&](rocprofsys::trace_cache::ofs_t&, bool) { + worker_called = true; + }; + + rocprofsys::trace_cache::flush_worker_t worker(worker_function, worker_sync, + test_file_path); + pid_t current_pid = getpid(); + + worker.start(current_pid); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + worker.stop(current_pid); + + EXPECT_TRUE(worker_sync->exit_finished); + EXPECT_FALSE(worker_sync->is_running); + EXPECT_TRUE(worker_called); +} + +TEST_F(flush_worker_test, worker_function_called_on_stop) +{ + std::atomic call_count{ 0 }; + std::atomic force_flag{ false }; + auto worker_function = [&](rocprofsys::trace_cache::ofs_t&, bool force) { + call_count++; + force_flag = force; + }; + + rocprofsys::trace_cache::flush_worker_t worker(worker_function, worker_sync, + test_file_path); + pid_t current_pid = getpid(); + + worker.start(current_pid); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + worker.stop(current_pid); + + EXPECT_GE(call_count.load(), 1); + EXPECT_TRUE(force_flag); +} + +TEST_F(flush_worker_test, multiple_stop_calls_are_safe) +{ + auto worker_function = [](rocprofsys::trace_cache::ofs_t&, bool) {}; + + rocprofsys::trace_cache::flush_worker_t worker(worker_function, worker_sync, + test_file_path); + pid_t current_pid = getpid(); + + worker.start(current_pid); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + worker.stop(current_pid); + worker.stop(current_pid); + worker.stop(current_pid); + + EXPECT_TRUE(worker_sync->exit_finished); + EXPECT_FALSE(worker_sync->is_running); +} + +TEST_F(flush_worker_test, worker_factory_creates_valid_object) +{ + auto worker_function = [](rocprofsys::trace_cache::ofs_t&, bool) {}; + + auto worker = rocprofsys::trace_cache::flush_worker_factory_t::get_worker( + worker_function, worker_sync, test_file_path); + + EXPECT_NE(worker, nullptr); + EXPECT_EQ(typeid(*worker), typeid(rocprofsys::trace_cache::flush_worker_t)); +} + +TEST_F(flush_worker_test, worker_handles_invalid_path) +{ + auto worker_function = [](rocprofsys::trace_cache::ofs_t&, bool) {}; + std::string invalid_path = "/invalid/path/file.bin"; + + rocprofsys::trace_cache::flush_worker_t worker(worker_function, worker_sync, + invalid_path); + pid_t current_pid = getpid(); + + EXPECT_THROW(worker.start(current_pid), std::runtime_error); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + worker.stop(current_pid); + + EXPECT_FALSE(worker_sync->exit_finished); + EXPECT_FALSE(worker_sync->is_running); +} + +TEST_F(flush_worker_test, different_pid_start_stop) +{ + std::atomic worker_called{ false }; + auto worker_function = [&](rocprofsys::trace_cache::ofs_t&, bool) { + worker_called = true; + }; + + rocprofsys::trace_cache::flush_worker_t worker(worker_function, worker_sync, + test_file_path); + pid_t parent_pid = getpid(); + + worker.start(parent_pid); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + EXPECT_TRUE(worker_sync->is_running); + EXPECT_EQ(worker_sync->origin_pid, parent_pid); + + pid_t child_pid = fork(); + if(child_pid == 0) + { + pid_t current_child_pid = getpid(); + worker.stop(current_child_pid); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + bool still_running = worker_sync->is_running; + bool exit_finished = worker_sync->exit_finished; + + exit(still_running ? 1 : (exit_finished ? 2 : 0)); + } + else + { + int status; + waitpid(child_pid, &status, 0); + int child_exit_code = WEXITSTATUS(status); + + EXPECT_EQ(child_exit_code, 0); + EXPECT_FALSE(worker_sync->exit_finished); + EXPECT_TRUE(worker_sync->is_running); + + worker.stop(parent_pid); + EXPECT_TRUE(worker_sync->exit_finished); + EXPECT_FALSE(worker_sync->is_running); + EXPECT_TRUE(worker_called); + } +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp new file mode 100644 index 0000000000..d73019e74e --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp @@ -0,0 +1,568 @@ +// 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 "core/trace_cache/sample_type.hpp" + +#include +#include +#include +#include + +using namespace rocprofsys::trace_cache; + +class sample_type_test : public ::testing::Test +{ +protected: + void SetUp() override { buffer.fill(0); } + + std::array buffer; +}; + +TEST_F(sample_type_test, kernel_dispatch_sample_serialize_deserialize) +{ + kernel_dispatch_sample original(1000, 2000, 42, 100, 200, 300, 400, 500, 600, 1024, + 2048, 64, 32, 16, 256, 128, 64, 0xABCD); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.start_timestamp, original.start_timestamp); + EXPECT_EQ(deserialized.end_timestamp, original.end_timestamp); + EXPECT_EQ(deserialized.thread_id, original.thread_id); + EXPECT_EQ(deserialized.agent_id_handle, original.agent_id_handle); + EXPECT_EQ(deserialized.kernel_id, original.kernel_id); + EXPECT_EQ(deserialized.dispatch_id, original.dispatch_id); + EXPECT_EQ(deserialized.queue_id_handle, original.queue_id_handle); + EXPECT_EQ(deserialized.correlation_id_internal, original.correlation_id_internal); + EXPECT_EQ(deserialized.correlation_id_ancestor, original.correlation_id_ancestor); + EXPECT_EQ(deserialized.private_segment_size, original.private_segment_size); + EXPECT_EQ(deserialized.group_segment_size, original.group_segment_size); + EXPECT_EQ(deserialized.workgroup_size_x, original.workgroup_size_x); + EXPECT_EQ(deserialized.workgroup_size_y, original.workgroup_size_y); + EXPECT_EQ(deserialized.workgroup_size_z, original.workgroup_size_z); + EXPECT_EQ(deserialized.grid_size_x, original.grid_size_x); + EXPECT_EQ(deserialized.grid_size_y, original.grid_size_y); + EXPECT_EQ(deserialized.grid_size_z, original.grid_size_z); + EXPECT_EQ(deserialized.stream_handle, original.stream_handle); +} + +TEST_F(sample_type_test, kernel_dispatch_sample_get_size) +{ + kernel_dispatch_sample sample(1000, 2000, 42, 100, 200, 300, 400, 500, 600, 1024, + 2048, 64, 32, 16, 256, 128, 64, 0xABCD); + + size_t expected_size = sizeof(uint64_t) * 9 + sizeof(uint32_t) * 8 + sizeof(uint64_t); + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, kernel_dispatch_sample_type_identifier) +{ + EXPECT_EQ(kernel_dispatch_sample::type_identifier, + type_identifier_t::kernel_dispatch); +} + +TEST_F(sample_type_test, memory_copy_sample_serialize_deserialize) +{ + memory_copy_sample original(5000, 6000, 123, 200, 201, 1, 2, 4096, 700, 800, 0x1000, + 0x2000, 0xDEAD); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.start_timestamp, original.start_timestamp); + EXPECT_EQ(deserialized.end_timestamp, original.end_timestamp); + EXPECT_EQ(deserialized.thread_id, original.thread_id); + EXPECT_EQ(deserialized.dst_agent_id_handle, original.dst_agent_id_handle); + EXPECT_EQ(deserialized.src_agent_id_handle, original.src_agent_id_handle); + EXPECT_EQ(deserialized.kind, original.kind); + EXPECT_EQ(deserialized.operation, original.operation); + EXPECT_EQ(deserialized.bytes, original.bytes); + EXPECT_EQ(deserialized.correlation_id_internal, original.correlation_id_internal); + EXPECT_EQ(deserialized.correlation_id_ancestor, original.correlation_id_ancestor); + EXPECT_EQ(deserialized.dst_address_value, original.dst_address_value); + EXPECT_EQ(deserialized.src_address_value, original.src_address_value); + EXPECT_EQ(deserialized.stream_handle, original.stream_handle); +} + +TEST_F(sample_type_test, memory_copy_sample_get_size) +{ + memory_copy_sample sample(5000, 6000, 123, 200, 201, 1, 2, 4096, 700, 800, 0x1000, + 0x2000, 0xDEAD); + + size_t expected_size = sizeof(uint64_t) * 11 + sizeof(int32_t) * 2; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, memory_copy_sample_type_identifier) +{ + EXPECT_EQ(memory_copy_sample::type_identifier, type_identifier_t::memory_copy); +} + +TEST_F(sample_type_test, memory_allocate_sample_serialize_deserialize) +{ + memory_allocate_sample original(7000, 8000, 456, 300, 3, 4, 8192, 900, 1000, 0x3000, + 0xBEEF); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.start_timestamp, original.start_timestamp); + EXPECT_EQ(deserialized.end_timestamp, original.end_timestamp); + EXPECT_EQ(deserialized.thread_id, original.thread_id); + EXPECT_EQ(deserialized.agent_id_handle, original.agent_id_handle); + EXPECT_EQ(deserialized.kind, original.kind); + EXPECT_EQ(deserialized.operation, original.operation); + EXPECT_EQ(deserialized.allocation_size, original.allocation_size); + EXPECT_EQ(deserialized.correlation_id_internal, original.correlation_id_internal); + EXPECT_EQ(deserialized.correlation_id_ancestor, original.correlation_id_ancestor); + EXPECT_EQ(deserialized.address_value, original.address_value); + EXPECT_EQ(deserialized.stream_handle, original.stream_handle); +} + +TEST_F(sample_type_test, memory_allocate_sample_get_size) +{ + memory_allocate_sample sample(7000, 8000, 456, 300, 3, 4, 8192, 900, 1000, 0x3000, + 0xBEEF); + + size_t expected_size = sizeof(uint64_t) * 9 + sizeof(int32_t) * 2; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, memory_allocate_sample_type_identifier) +{ + EXPECT_EQ(memory_allocate_sample::type_identifier, type_identifier_t::memory_alloc); +} + +TEST_F(sample_type_test, region_sample_serialize_deserialize) +{ + region_sample original(789, "test_function", 1100, 1200, 10000, 20000, + "frame1\nframe2", "arg1=1, arg2=hello", "hip"); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.thread_id, original.thread_id); + EXPECT_EQ(deserialized.name, original.name); + EXPECT_EQ(deserialized.correlation_id_internal, original.correlation_id_internal); + EXPECT_EQ(deserialized.correlation_id_ancestor, original.correlation_id_ancestor); + EXPECT_EQ(deserialized.start_timestamp, original.start_timestamp); + EXPECT_EQ(deserialized.end_timestamp, original.end_timestamp); + EXPECT_EQ(deserialized.call_stack, original.call_stack); + EXPECT_EQ(deserialized.args_str, original.args_str); + EXPECT_EQ(deserialized.category, original.category); +} + +TEST_F(sample_type_test, region_sample_get_size) +{ + region_sample sample(789, "test_function", 1100, 1200, 10000, 20000, "frame1\nframe2", + "arg1=1, arg2=hello", "hip"); + + size_t expected_size = sizeof(uint64_t) * 5 + sizeof(size_t) * 4 + 13 + 13 + 18 + 3; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, region_sample_type_identifier) +{ + EXPECT_EQ(region_sample::type_identifier, type_identifier_t::region); +} + +TEST_F(sample_type_test, region_sample_empty_strings) +{ + region_sample original(123, "", 0, 0, 0, 0, "", "", ""); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.thread_id, original.thread_id); + EXPECT_EQ(deserialized.name, ""); + EXPECT_EQ(deserialized.call_stack, ""); + EXPECT_EQ(deserialized.args_str, ""); + EXPECT_EQ(deserialized.category, ""); +} + +TEST_F(sample_type_test, in_time_sample_serialize_deserialize) +{ + in_time_sample original(42, "GPU:0", 50000, "kernel_launch", 100, 99, 1500, + "main\nfoo\nbar", "file.cpp:42"); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.category_enum_id, original.category_enum_id); + EXPECT_EQ(deserialized.track_name, original.track_name); + EXPECT_EQ(deserialized.timestamp_ns, original.timestamp_ns); + EXPECT_EQ(deserialized.event_metadata, original.event_metadata); + EXPECT_EQ(deserialized.stack_id, original.stack_id); + EXPECT_EQ(deserialized.parent_stack_id, original.parent_stack_id); + EXPECT_EQ(deserialized.correlation_id, original.correlation_id); + EXPECT_EQ(deserialized.call_stack, original.call_stack); + EXPECT_EQ(deserialized.line_info, original.line_info); +} + +TEST_F(sample_type_test, in_time_sample_get_size) +{ + in_time_sample sample(42, "GPU:0", 50000, "kernel_launch", 100, 99, 1500, + "main\nfoo\nbar", "file.cpp:42"); + + size_t expected_size = sizeof(size_t) + sizeof(size_t) + 5 + sizeof(uint64_t) + + sizeof(size_t) + 13 + sizeof(uint64_t) * 3 + sizeof(size_t) + + 12 + sizeof(size_t) + 11; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, in_time_sample_type_identifier) +{ + EXPECT_EQ(in_time_sample::type_identifier, type_identifier_t::in_time_sample); +} + +TEST_F(sample_type_test, pmc_event_with_sample_serialize_deserialize) +{ + pmc_event_with_sample original(42, "CPU:0", 60000, "counter_sample", 200, 199, 1600, + "entry\nexit", "counter.cpp:100", 5, 1, + "PERF_COUNT_HW_CPU_CYCLES", 12345.67); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.category_enum_id, original.category_enum_id); + EXPECT_EQ(deserialized.track_name, original.track_name); + EXPECT_EQ(deserialized.timestamp_ns, original.timestamp_ns); + EXPECT_EQ(deserialized.event_metadata, original.event_metadata); + EXPECT_EQ(deserialized.stack_id, original.stack_id); + EXPECT_EQ(deserialized.parent_stack_id, original.parent_stack_id); + EXPECT_EQ(deserialized.correlation_id, original.correlation_id); + EXPECT_EQ(deserialized.call_stack, original.call_stack); + EXPECT_EQ(deserialized.line_info, original.line_info); + EXPECT_EQ(deserialized.device_id, original.device_id); + EXPECT_EQ(deserialized.device_type, original.device_type); + EXPECT_EQ(deserialized.pmc_info_name, original.pmc_info_name); + EXPECT_DOUBLE_EQ(deserialized.value, original.value); +} + +TEST_F(sample_type_test, pmc_event_with_sample_get_size) +{ + pmc_event_with_sample sample(42, "CPU:0", 60000, "counter_sample", 200, 199, 1600, + "entry\nexit", "counter.cpp:100", 5, 1, + "PERF_COUNT_HW_CPU_CYCLES", 12345.67); + + size_t expected_size = sizeof(size_t) * 6 + 5 + 14 + 10 + 15 + 24 + + sizeof(uint64_t) * 4 + sizeof(uint32_t) + sizeof(uint8_t) + + sizeof(double); + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, pmc_event_with_sample_type_identifier) +{ + EXPECT_EQ(pmc_event_with_sample::type_identifier, + type_identifier_t::pmc_event_with_sample); +} + +TEST_F(sample_type_test, amd_smi_sample_serialize_deserialize) +{ + std::vector gpu_activity_data = { 10, 20, 30, 40, 50 }; + amd_smi_sample original(0xFF, 2, 70000, 80, 60, 40, 250, 75, 1024 * 1024 * 512, + gpu_activity_data); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.settings, original.settings); + EXPECT_EQ(deserialized.device_id, original.device_id); + EXPECT_EQ(deserialized.timestamp, original.timestamp); + EXPECT_EQ(deserialized.gfx_activity, original.gfx_activity); + EXPECT_EQ(deserialized.umc_activity, original.umc_activity); + EXPECT_EQ(deserialized.mm_activity, original.mm_activity); + EXPECT_EQ(deserialized.power, original.power); + EXPECT_EQ(deserialized.temperature, original.temperature); + EXPECT_EQ(deserialized.mem_usage, original.mem_usage); + EXPECT_EQ(deserialized.gpu_activity.size(), original.gpu_activity.size()); + EXPECT_EQ(deserialized.gpu_activity, original.gpu_activity); +} + +TEST_F(sample_type_test, amd_smi_sample_get_size) +{ + std::vector gpu_activity_data = { 10, 20, 30, 40, 50 }; + amd_smi_sample sample(0xFF, 2, 70000, 80, 60, 40, 250, 75, 1024 * 1024 * 512, + gpu_activity_data); + + size_t expected_size = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint64_t) + + sizeof(uint32_t) * 4 + sizeof(int64_t) + sizeof(uint64_t) + + sizeof(size_t) + 5; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, amd_smi_sample_type_identifier) +{ + EXPECT_EQ(amd_smi_sample::type_identifier, type_identifier_t::amd_smi_sample); +} + +TEST_F(sample_type_test, amd_smi_sample_empty_gpu_activity) +{ + std::vector empty_activity; + amd_smi_sample original(0, 0, 0, 0, 0, 0, 0, 0, 0, empty_activity); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_TRUE(deserialized.gpu_activity.empty()); +} + +TEST_F(sample_type_test, cpu_freq_sample_serialize_deserialize) +{ + std::vector freqs_data = { 100, 150, 200, 180, 190, 195, 185, 170 }; + cpu_freq_sample original(80000, 1024, 2048, 4096, 500, 100, 1000000, 500000, + freqs_data); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.timestamp, original.timestamp); + EXPECT_EQ(deserialized.page_rss, original.page_rss); + EXPECT_EQ(deserialized.virt_mem_usage, original.virt_mem_usage); + EXPECT_EQ(deserialized.peak_rss, original.peak_rss); + EXPECT_EQ(deserialized.context_switch_count, original.context_switch_count); + EXPECT_EQ(deserialized.page_faults, original.page_faults); + EXPECT_EQ(deserialized.user_mode_time, original.user_mode_time); + EXPECT_EQ(deserialized.kernel_mode_time, original.kernel_mode_time); + EXPECT_EQ(deserialized.freqs.size(), original.freqs.size()); + EXPECT_EQ(deserialized.freqs, original.freqs); +} + +TEST_F(sample_type_test, cpu_freq_sample_get_size) +{ + std::vector freqs_data = { 100, 150, 200, 180, 190, 195, 185, 170 }; + cpu_freq_sample sample(80000, 1024, 2048, 4096, 500, 100, 1000000, 500000, + freqs_data); + + size_t expected_size = sizeof(uint64_t) + sizeof(int64_t) * 7 + sizeof(size_t) + 8; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, cpu_freq_sample_type_identifier) +{ + EXPECT_EQ(cpu_freq_sample::type_identifier, type_identifier_t::cpu_freq_sample); +} + +TEST_F(sample_type_test, cpu_freq_sample_empty_freqs) +{ + std::vector empty_freqs; + cpu_freq_sample original(0, 0, 0, 0, 0, 0, 0, 0, empty_freqs); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_TRUE(deserialized.freqs.empty()); +} + +TEST_F(sample_type_test, backtrace_region_sample_serialize_deserialize) +{ + backtrace_region_sample original(1, 999, "Thread:999", "my_function", 90000, 95000, + "rocm", "main\nworker\nfunc", "worker.cpp:256", + "{\"extra\":\"data\"}"); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.type, original.type); + EXPECT_EQ(deserialized.thread_id, original.thread_id); + EXPECT_EQ(deserialized.track_name, original.track_name); + EXPECT_EQ(deserialized.name, original.name); + EXPECT_EQ(deserialized.start_timestamp, original.start_timestamp); + EXPECT_EQ(deserialized.end_timestamp, original.end_timestamp); + EXPECT_EQ(deserialized.category, original.category); + EXPECT_EQ(deserialized.call_stack, original.call_stack); + EXPECT_EQ(deserialized.line_info, original.line_info); + EXPECT_EQ(deserialized.extdata, original.extdata); +} + +TEST_F(sample_type_test, backtrace_region_sample_get_size) +{ + backtrace_region_sample sample(1, 999, "Thread:999", "my_function", 90000, 95000, + "rocm", "main\nworker\nfunc", "worker.cpp:256", + "{\"extra\":\"data\"}"); + + size_t expected_size = sizeof(uint32_t) + sizeof(uint64_t) * 3 + sizeof(size_t) * 6 + + 10 + 11 + 4 + 16 + 14 + 16; + + EXPECT_EQ(get_size(sample), expected_size); +} + +TEST_F(sample_type_test, backtrace_region_sample_type_identifier) +{ + EXPECT_EQ(backtrace_region_sample::type_identifier, + type_identifier_t::backtrace_region_sample); +} + +TEST_F(sample_type_test, backtrace_region_sample_empty_strings) +{ + backtrace_region_sample original(0, 0, "", "", 0, 0, "", "", "", ""); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.track_name, ""); + EXPECT_EQ(deserialized.name, ""); + EXPECT_EQ(deserialized.category, ""); + EXPECT_EQ(deserialized.call_stack, ""); + EXPECT_EQ(deserialized.line_info, ""); + EXPECT_EQ(deserialized.extdata, ""); +} + +TEST_F(sample_type_test, type_identifier_enum_values) +{ + EXPECT_EQ(static_cast(type_identifier_t::in_time_sample), 0x0000); + EXPECT_EQ(static_cast(type_identifier_t::pmc_event_with_sample), 0x0001); + EXPECT_EQ(static_cast(type_identifier_t::region), 0x0002); + EXPECT_EQ(static_cast(type_identifier_t::kernel_dispatch), 0x0003); + EXPECT_EQ(static_cast(type_identifier_t::memory_copy), 0x0004); + EXPECT_EQ(static_cast(type_identifier_t::memory_alloc), 0x0005); + EXPECT_EQ(static_cast(type_identifier_t::amd_smi_sample), 0x0006); + EXPECT_EQ(static_cast(type_identifier_t::cpu_freq_sample), 0x0007); + EXPECT_EQ(static_cast(type_identifier_t::backtrace_region_sample), 0x0008); + EXPECT_EQ(static_cast(type_identifier_t::fragmented_space), 0xFFFF); +} + +TEST_F(sample_type_test, kernel_dispatch_sample_default_constructor) +{ + kernel_dispatch_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::kernel_dispatch); +} + +TEST_F(sample_type_test, memory_copy_sample_default_constructor) +{ + memory_copy_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::memory_copy); +} + +TEST_F(sample_type_test, memory_allocate_sample_default_constructor) +{ + memory_allocate_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::memory_alloc); +} + +TEST_F(sample_type_test, region_sample_default_constructor) +{ + region_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::region); +} + +TEST_F(sample_type_test, in_time_sample_default_constructor) +{ + in_time_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::in_time_sample); +} + +TEST_F(sample_type_test, pmc_event_with_sample_default_constructor) +{ + pmc_event_with_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::pmc_event_with_sample); +} + +TEST_F(sample_type_test, amd_smi_sample_default_constructor) +{ + amd_smi_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::amd_smi_sample); +} + +TEST_F(sample_type_test, cpu_freq_sample_default_constructor) +{ + cpu_freq_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::cpu_freq_sample); +} + +TEST_F(sample_type_test, backtrace_region_sample_default_constructor) +{ + backtrace_region_sample sample; + EXPECT_EQ(sample.type_identifier, type_identifier_t::backtrace_region_sample); +} + +TEST_F(sample_type_test, kernel_dispatch_sample_large_values) +{ + kernel_dispatch_sample original( + UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX, + UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, + UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, SIZE_MAX); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.start_timestamp, UINT64_MAX); + EXPECT_EQ(deserialized.end_timestamp, UINT64_MAX); + EXPECT_EQ(deserialized.thread_id, UINT64_MAX); + EXPECT_EQ(deserialized.private_segment_size, UINT32_MAX); + EXPECT_EQ(deserialized.grid_size_z, UINT32_MAX); +} + +TEST_F(sample_type_test, amd_smi_sample_large_gpu_activity) +{ + std::vector large_activity(256); + for(size_t i = 0; i < large_activity.size(); ++i) + { + large_activity[i] = static_cast(i); + } + + amd_smi_sample original(0xFF, 0, 0, 0, 0, 0, 0, 0, 0, large_activity); + + serialize(buffer.data(), original); + + uint8_t* buffer_ptr = buffer.data(); + auto deserialized = deserialize(buffer_ptr); + + EXPECT_EQ(deserialized.gpu_activity.size(), 256); + EXPECT_EQ(deserialized.gpu_activity, large_activity); +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp new file mode 100644 index 0000000000..9eadd1d979 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp @@ -0,0 +1,466 @@ +// 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 "core/trace_cache/storage_parser.hpp" +#include "mocked_types.hpp" + +#include +#include +#include +#include +#include +#include +#include + +class sample_processor_t +{ +public: + sample_processor_t() = default; + + void set_expected_samples_1(const std::vector& samples) + { + std::lock_guard lock(m_data_mutex); + m_expected_samples_1 = samples; + } + + void set_expected_samples_2(const std::vector& samples) + { + std::lock_guard lock(m_data_mutex); + m_expected_samples_2 = samples; + } + + void set_expected_samples_3(const std::vector& samples) + { + std::lock_guard lock(m_data_mutex); + m_expected_samples_3 = samples; + } + + void execute_sample_processing(test_type_identifier_t type_identifier, + const rocprofsys::trace_cache::cacheable_t& value) + { + switch(type_identifier) + { + case test_type_identifier_t::sample_type_1: + { + const auto& sample = static_cast(value); + std::lock_guard lock(m_data_mutex); + size_t idx = m_sample_1_count++; + check_sample_1(idx, sample); + break; + } + case test_type_identifier_t::sample_type_2: + { + const auto& sample = static_cast(value); + std::lock_guard lock(m_data_mutex); + size_t idx = m_sample_2_count++; + check_sample_2(idx, sample); + break; + } + case test_type_identifier_t::sample_type_3: + { + const auto& sample = static_cast(value); + std::lock_guard lock(m_data_mutex); + size_t idx = m_sample_3_count++; + check_sample_3(idx, sample); + break; + } + default: m_unknown_count++; break; + } + } + + int get_sample_1_count() const { return m_sample_1_count.load(); } + int get_sample_2_count() const { return m_sample_2_count.load(); } + int get_sample_3_count() const { return m_sample_3_count.load(); } + int get_unknown_count() const { return m_unknown_count.load(); } + +private: + void check_sample_1(size_t index, const test_sample_1& sample) + { + if(index < m_expected_samples_1.size()) + { + EXPECT_EQ(m_expected_samples_1[index], sample); + } + } + + void check_sample_2(size_t index, const test_sample_2& sample) + { + if(index < m_expected_samples_2.size()) + { + EXPECT_EQ(m_expected_samples_2[index], sample); + } + } + + void check_sample_3(size_t index, const test_sample_3& sample) + { + if(index < m_expected_samples_3.size()) + { + EXPECT_EQ(m_expected_samples_3[index], sample); + } + } + + std::atomic m_sample_1_count{ 0 }; + std::atomic m_sample_2_count{ 0 }; + std::atomic m_sample_3_count{ 0 }; + std::atomic m_unknown_count{ 0 }; + std::vector m_expected_samples_1; + std::vector m_expected_samples_2; + std::vector m_expected_samples_3; + std::mutex m_data_mutex; +}; + +struct __attribute__((packed)) sample_header +{ + test_type_identifier_t type; + size_t sample_size; +}; + +class storage_parser_test : public ::testing::Test +{ +protected: + void SetUp() override + { + test_file_path = "test_storage_parser_" + std::to_string(test_counter++) + ".bin"; + cleanup_test_file(); + } + + void TearDown() override { cleanup_test_file(); } + + void cleanup_test_file() { std::remove(test_file_path.c_str()); } + + template + void write_vector(std::ofstream& ofs, const std::vector& vec, + test_type_identifier_t identifier) + { + for(const auto& sample : vec) + { + sample_header header; + header.type = identifier; + header.sample_size = rocprofsys::trace_cache::get_size(sample); + + ofs.write(reinterpret_cast(&header), sizeof(header)); + + std::vector buffer; + buffer.reserve(header.sample_size); + buffer.assign(header.sample_size, 0xFF); + + rocprofsys::trace_cache::serialize(buffer.data(), sample); + + ofs.write(reinterpret_cast(buffer.data()), header.sample_size); + } + } + + void create_test_file_with_samples(const std::vector& samples_1, + const std::vector& samples_2, + const std::vector& samples_3) + { + std::ofstream ofs(test_file_path, std::ios::binary); + ASSERT_TRUE(ofs.is_open()); + + write_vector(ofs, samples_1, test_type_identifier_t::sample_type_1); + write_vector(ofs, samples_2, test_type_identifier_t::sample_type_2); + write_vector(ofs, samples_3, test_type_identifier_t::sample_type_3); + + ofs.close(); + } + + std::string test_file_path; + static std::atomic test_counter; +}; + +std::atomic storage_parser_test::test_counter{ 0 }; + +TEST_F(storage_parser_test, load_empty_file) +{ + std::ofstream ofs(test_file_path, std::ios::binary); + ofs.close(); + + auto processor = std::make_shared(); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_1_count(), 0); + EXPECT_EQ(processor->get_sample_2_count(), 0); + EXPECT_EQ(processor->get_sample_3_count(), 0); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, load_single_sample_type_1) +{ + std::vector samples_1 = { test_sample_1(42, "test_string"), + test_sample_1(100, "another_test") }; + + create_test_file_with_samples(samples_1, {}, {}); + + auto processor = std::make_shared(); + processor->set_expected_samples_1(samples_1); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_1_count(), 2); + EXPECT_EQ(processor->get_sample_2_count(), 0); + EXPECT_EQ(processor->get_sample_3_count(), 0); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, load_multiple_sample_types) +{ + std::vector samples_1 = { test_sample_1(123, "mixed_test") }; + std::vector samples_2 = { test_sample_2(3.14159, 555), + test_sample_2(2.71828, 777) }; + std::vector samples_3 = { test_sample_3({ 0x01, 0x02, 0x03 }) }; + + create_test_file_with_samples(samples_1, samples_2, samples_3); + + auto processor = std::make_shared(); + processor->set_expected_samples_1(samples_1); + processor->set_expected_samples_2(samples_2); + processor->set_expected_samples_3(samples_3); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_1_count(), 1); + EXPECT_EQ(processor->get_sample_2_count(), 2); + EXPECT_EQ(processor->get_sample_3_count(), 1); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, load_unsupported_sample_type) +{ + std::vector samples_1 = { test_sample_1(123, "mixed_test") }; + std::vector samples_2 = { test_sample_2(3.14159, 555), + test_sample_2(2.71828, 777) }; + std::vector samples_3 = { test_sample_3({ 0x01, 0x02, 0x03 }) }; + + create_test_file_with_samples(samples_1, samples_2, samples_3); + + auto processor = std::make_shared(); + processor->set_expected_samples_1(samples_1); + processor->set_expected_samples_2(samples_2); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_1_count(), 1); + EXPECT_EQ(processor->get_sample_2_count(), 2); + EXPECT_EQ(processor->get_sample_3_count(), 0); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, load_file_with_zero_sized_samples) +{ + test_sample_1 valid_sample(42, "valid"); + { + std::ofstream ofs(test_file_path, std::ios::binary); + + sample_header zero_header; + zero_header.type = test_type_identifier_t::sample_type_1; + zero_header.sample_size = 0; + + ofs.write(reinterpret_cast(&zero_header), sizeof(zero_header)); + ofs.write(reinterpret_cast(&zero_header), sizeof(zero_header)); + + sample_header valid_header; + valid_header.type = test_type_identifier_t::sample_type_1; + valid_header.sample_size = rocprofsys::trace_cache::get_size(valid_sample); + + ofs.write(reinterpret_cast(&valid_header), sizeof(valid_header)); + + std::vector buffer(valid_header.sample_size); + rocprofsys::trace_cache::serialize(buffer.data(), valid_sample); + ofs.write(reinterpret_cast(buffer.data()), valid_header.sample_size); + + ofs.close(); + } + + auto processor = std::make_shared(); + processor->set_expected_samples_1({ valid_sample }); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + EXPECT_EQ(processor->get_sample_1_count(), 1); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, load_nonexisting_file) +{ + auto processor = std::make_shared(); + + rocprofsys::trace_cache::storage_parser + parser("non_existent_file.bin"); + + EXPECT_THROW(parser.load(processor), std::runtime_error); +} + +TEST_F(storage_parser_test, load_large_sample_data) +{ + std::vector large_payload(10000); + std::iota(large_payload.begin(), large_payload.end(), 0); + + std::vector samples_3 = { test_sample_3(large_payload) }; + + create_test_file_with_samples({}, {}, samples_3); + + auto processor = std::make_shared(); + processor->set_expected_samples_3(samples_3); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_3_count(), 1); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, load_many_small_samples) +{ + constexpr auto num_of_elements = 15; + + std::vector strings; + strings.reserve(num_of_elements); + std::vector many_samples; + many_samples.reserve(num_of_elements); + + for(int i = 0; i < num_of_elements; ++i) + { + auto x = "sample_" + std::to_string(i); + strings.push_back(x); + many_samples.push_back({ 0, strings[i] }); + } + + create_test_file_with_samples(many_samples, {}, {}); + + auto processor = std::make_shared(); + processor->set_expected_samples_1(many_samples); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_1_count(), num_of_elements); + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, write_less_than_expected) +{ + std::ofstream ofs(test_file_path, std::ios::binary); + + sample_header header; + header.type = test_type_identifier_t::sample_type_1; + header.sample_size = 100; + + ofs.write(reinterpret_cast(&header), sizeof(header)); + + std::vector partial_data(50, 0xAA); + ofs.write(reinterpret_cast(partial_data.data()), partial_data.size()); + + ofs.close(); + + auto processor = std::make_shared(); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_THROW(parser.load(processor), std::runtime_error); + + EXPECT_EQ(processor->get_sample_1_count(), 0); + EXPECT_EQ(processor->get_sample_2_count(), 0); + EXPECT_EQ(processor->get_sample_3_count(), 0); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} + +TEST_F(storage_parser_test, read_fragmented_space) +{ + std::vector samples_1 = { test_sample_1(123, + "fragmented-space test") }; + std::vector samples_2 = { test_sample_2(3.14159, 555), + test_sample_2(2.71828, 777) }; + std::vector samples_3 = { test_sample_3({ 0x01, 0x02, 0x03 }) }; + { + std::ofstream ofs(test_file_path, std::ios::binary); + + write_vector(ofs, samples_1, test_type_identifier_t::sample_type_1); + + sample_header header; + header.sample_size = 100; + header.type = test_type_identifier_t::fragmented_space; + std::vector fragmented_space; + fragmented_space.reserve(header.sample_size); + fragmented_space.assign(header.sample_size, 0); + + ofs.write(reinterpret_cast(&header), sizeof(header)); + ofs.write(reinterpret_cast(fragmented_space.data()), + header.sample_size); + + write_vector(ofs, samples_2, test_type_identifier_t::sample_type_2); + write_vector(ofs, samples_3, test_type_identifier_t::sample_type_3); + + ofs.close(); + } + + auto processor = std::make_shared(); + processor->set_expected_samples_1(samples_1); + processor->set_expected_samples_2(samples_2); + processor->set_expected_samples_3(samples_3); + + rocprofsys::trace_cache::storage_parser + parser(test_file_path); + + EXPECT_NO_THROW(parser.load(processor)); + + EXPECT_EQ(processor->get_sample_1_count(), 1); + EXPECT_EQ(processor->get_sample_2_count(), 2); + EXPECT_EQ(processor->get_sample_3_count(), 1); + + EXPECT_EQ(std::remove(test_file_path.c_str()), 0); +} diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp new file mode 100644 index 0000000000..878c868ee1 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp @@ -0,0 +1,126 @@ +// 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 "core/trace_cache/type_registry.hpp" +#include "mocked_types.hpp" + +#include + +class type_registry_test : public ::testing::Test +{ +protected: + rocprofsys::trace_cache::type_registry + type_registry; +}; + +TEST_F(type_registry_test, test_get_type_sample_1) +{ + test_sample_1 test_value{ 42, "hello" }; + size_t buffer_size = rocprofsys::trace_cache::get_size(test_value); + std::vector buffer(buffer_size); + rocprofsys::trace_cache::serialize(buffer.data(), test_value); + + auto* buffer_data = buffer.data(); + auto result = + type_registry.get_type(test_type_identifier_t::sample_type_1, buffer_data); + + ASSERT_TRUE(result.has_value()); + ASSERT_TRUE(std::holds_alternative(result.value())); + + auto sample_1 = std::get(result.value()); + EXPECT_EQ(sample_1.value, 42); + EXPECT_EQ(sample_1.text, "hello"); +} + +TEST_F(type_registry_test, test_get_type_sample_2) +{ + test_sample_2 test_value{ 3.14, 123 }; + size_t buffer_size = rocprofsys::trace_cache::get_size(test_value); + std::vector buffer(buffer_size); + rocprofsys::trace_cache::serialize(buffer.data(), test_value); + + auto* buffer_data = buffer.data(); + auto result = + type_registry.get_type(test_type_identifier_t::sample_type_2, buffer_data); + + ASSERT_TRUE(result.has_value()); + ASSERT_TRUE(std::holds_alternative(result.value())); + + auto sample_2 = std::get(result.value()); + EXPECT_DOUBLE_EQ(sample_2.data, 3.14); + EXPECT_EQ(sample_2.sample_id, 123); +} + +TEST_F(type_registry_test, test_get_type_unknown_id) +{ + uint8_t dummy_data = 0; + uint8_t* data = &dummy_data; + + auto result = type_registry.get_type(test_type_identifier_t::fragmented_space, data); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(type_registry_test, test_variant_type_definition) +{ + using expected_variant = std::variant; + using actual_variant = + rocprofsys::trace_cache::type_registry::variant_t; + + EXPECT_TRUE((std::is_same_v) ); +} + +TEST_F(type_registry_test, test_multiple_calls_same_type) +{ + test_sample_1 test_value1{ 100, "first" }; + test_sample_1 test_value2{ 200, "second" }; + + size_t buffer_size1 = rocprofsys::trace_cache::get_size(test_value1); + size_t buffer_size2 = rocprofsys::trace_cache::get_size(test_value2); + + std::vector buffer1(buffer_size1); + std::vector buffer2(buffer_size2); + + rocprofsys::trace_cache::serialize(buffer1.data(), test_value1); + rocprofsys::trace_cache::serialize(buffer2.data(), test_value2); + + auto* buffer1_data = buffer1.data(); + auto* buffer2_data = buffer2.data(); + + auto result1 = + type_registry.get_type(test_type_identifier_t::sample_type_1, buffer1_data); + auto result2 = + type_registry.get_type(test_type_identifier_t::sample_type_1, buffer2_data); + + ASSERT_TRUE(result1.has_value()); + ASSERT_TRUE(result2.has_value()); + + auto sample_1_1 = std::get(result1.value()); + auto sample_1_2 = std::get(result2.value()); + + EXPECT_EQ(sample_1_1.value, 100); + EXPECT_EQ(sample_1_1.text, "first"); + EXPECT_EQ(sample_1_2.value, 200); + EXPECT_EQ(sample_1_2.text, "second"); +} diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library.cpp index b209218f52..74e5056223 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library.cpp @@ -578,7 +578,11 @@ rocprofsys_init_tooling_hidden(void) ROCPROFSYS_DEBUG_F("State: %s -> State::Active\n", std::to_string(get_state()).c_str()); - trace_cache::get_buffer_storage().start(getpid()); + { + ROCPROFSYS_SCOPED_SAMPLING_ON_CHILD_THREADS(false); + trace_cache::get_buffer_storage().start(getpid()); + } + set_state(State::Active); // set to active as very last operation } }; diff --git a/projects/rocprofiler-systems/source/tests/CMakeLists.txt b/projects/rocprofiler-systems/source/tests/CMakeLists.txt index fd894593a9..1d42292b03 100644 --- a/projects/rocprofiler-systems/source/tests/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/tests/CMakeLists.txt @@ -20,12 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -add_executable(rocprof-sys-unit-tests dummy.cpp) +add_executable(rocprof-sys-unit-tests dummy.cpp $) target_link_libraries( rocprof-sys-unit-tests - PRIVATE rocprofiler-systems-googletest-library + PRIVATE rocprofiler-systems-googletest-library rocprofiler-systems-core-library ) - -include(GoogleTest) -gtest_discover_tests(rocprof-sys-unit-tests)