[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.
This commit is contained in:
Milan Radosavljevic
2025-12-03 15:25:33 +01:00
zatwierdzone przez GitHub
rodzic 09a9f9e31d
commit fddef714a0
19 zmienionych plików z 2953 dodań i 42 usunięć
@@ -48,3 +48,7 @@ target_include_directories(
rocprofiler-systems-core-library
PUBLIC ${CMAKE_CURRENT_LIST_DIR}
)
if(ROCPROFSYS_BUILD_TESTING)
add_subdirectory(tests)
endif()
@@ -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 <chrono>
#include <memory>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <thread>
#include <unistd.h>
@@ -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::thread>([&]() {
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;
}
@@ -25,16 +25,19 @@
#include "core/trace_cache/cacheable.hpp"
#include "common/defines.h"
#include "core/debug.hpp"
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <unistd.h>
@@ -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");
}
}
@@ -37,6 +37,7 @@
#include <cstring>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <vector>
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();
}
@@ -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 <memory>
#include <unistd.h>
namespace rocprofsys
{
@@ -22,7 +22,6 @@
#pragma once
#include "core/trace_cache/cache_type_traits.hpp"
#include "library/runtime.hpp"
#include <algorithm>
#include <cassert>
@@ -22,6 +22,7 @@
#pragma once
#include "core/trace_cache/cacheable.hpp"
#include <cstdint>
#include <stdint.h>
#include <string>
@@ -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<uint64_t>(item.timestamp_ns), std::string_view(item.event_metadata),
static_cast<uint64_t>(item.stack_id), static_cast<uint64_t>(item.parent_stack_id),
static_cast<uint64_t>(item.correlation_id), std::string_view(item.call_stack),
@@ -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 <cstdint>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
@@ -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<char*>(&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<int>(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<int>(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;
}
}
@@ -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/
)
@@ -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 <cmath>
#include <cstddef>
#include <vector>
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<uint8_t> p)
: payload(std::move(p))
{}
std::vector<uint8_t> 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);
}
@@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <atomic>
#include <chrono>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <thread>
#include <vector>
namespace
{
template <typename T>
void
verify_buffer_contains(const T& sample, const uint8_t* buffer, size_t& buffer_pos)
{
auto type_id = *reinterpret_cast<const test_type_identifier_t*>(buffer + buffer_pos);
EXPECT_EQ(type_id, T::type_identifier);
buffer_pos += sizeof(test_type_identifier_t);
auto size = *reinterpret_cast<const size_t*>(buffer + buffer_pos);
EXPECT_EQ(size, rocprofsys::trace_cache::get_size(sample));
buffer_pos += sizeof(size_t);
uint8_t* deserialize_ptr = const_cast<uint8_t*>(buffer + buffer_pos);
auto deserialized = rocprofsys::trace_cache::deserialize<T>(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<mock_worker_t> 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<worker_t> 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_t>(
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<int> 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<int> buffer_storage_test::test_counter{ 0 };
TEST_F(buffer_storage_test, multiple_start)
{
rocprofsys::trace_cache::buffer_storage<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<mock_worker_factory_t, test_type_identifier_t>
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<const uint8_t*>(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<mock_worker_factory_t, test_type_identifier_t>
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<uint8_t> 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<uint8_t> payload(1000 + i * 100, static_cast<uint8_t>(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<mock_worker_factory_t, test_type_identifier_t>
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<std::thread> 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<uint8_t>(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<const test_type_identifier_t*>(
buffer_data.data() + buffer_pos);
buffer_pos += sizeof(test_type_identifier_t);
auto size = *reinterpret_cast<const size_t*>(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<mock_worker_factory_t, test_type_identifier_t>
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<uint8_t> 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<const uint8_t*>(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<const test_type_identifier_t*>(buffer + buffer_pos);
buffer_pos += sizeof(test_type_identifier_t);
auto size = *reinterpret_cast<const size_t*>(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);
}
@@ -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 <atomic>
#include <chrono>
#include <gtest/gtest.h>
#include <memory>
#include <random>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <vector>
namespace
{
struct sample_1_hash
{
size_t operator()(const test_sample_1& s) const
{
size_t h = std::hash<int>{}(s.value);
for(size_t i = 0; i < s.text.size(); ++i)
{
h ^= std::hash<char>{}(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<double>{}(s.data);
size_t h2 = std::hash<uint32_t>{}(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<uint8_t>{}(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<test_sample_1>& samples)
{
std::lock_guard<std::mutex> 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<test_sample_2>& samples)
{
std::lock_guard<std::mutex> 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<test_sample_3>& samples)
{
std::lock_guard<std::mutex> 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<const test_sample_1&>(value);
std::lock_guard<std::mutex> 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<const test_sample_2&>(value);
std::lock_guard<std::mutex> 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<const test_sample_3&>(value);
std::lock_guard<std::mutex> 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<std::mutex> 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<int> m_sample_1_count{ 0 };
std::atomic<int> m_sample_2_count{ 0 };
std::atomic<int> m_sample_3_count{ 0 };
std::unordered_map<test_sample_1, int, sample_1_hash> m_expected_samples_1;
std::unordered_map<test_sample_2, int, sample_2_hash> m_expected_samples_2;
std::unordered_map<test_sample_3, int, sample_3_hash> 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<int> test_counter;
};
std::atomic<int> trace_cache_module_integration_test::test_counter{ 0 };
TEST_F(trace_cache_module_integration_test, buffer_fragmentation_handling)
{
std::vector<std::string> large_texts;
large_texts.reserve(100);
std::vector<test_sample_1> large_samples;
large_samples.reserve(100);
std::vector<test_sample_3> 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<uint8_t> small_payload(10, static_cast<uint8_t>(i));
small_samples.emplace_back(small_payload);
}
std::vector<test_sample_1> expected_1;
std::vector<test_sample_3> 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<integration_sample_processor_t>();
processor->set_expected_samples_1(expected_1);
processor->set_expected_samples_3(expected_3);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<std::string> 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<int>::max(), strings[0]);
test_sample_1 min_int(std::numeric_limits<int>::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<double>::max(),
std::numeric_limits<uint32_t>::max());
test_sample_2 min_double(std::numeric_limits<double>::lowest(), 0);
test_sample_2 infinity(std::numeric_limits<double>::infinity(), 42);
test_sample_2 neg_infinity(-std::numeric_limits<double>::infinity(), 43);
std::vector<uint8_t> max_vector(10000, 0xFF);
test_sample_3 large_payload(max_vector);
test_sample_3 empty_payload;
std::vector<uint8_t> single_zero = { 0x00 };
test_sample_3 zero_payload(single_zero);
std::vector<test_sample_1> expected_1;
std::vector<test_sample_2> expected_2;
std::vector<test_sample_3> 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<integration_sample_processor_t>();
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<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<int> value_dist(1, 1000);
std::uniform_int_distribution<size_t> size_dist(1, 500);
std::vector<std::string> texts;
texts.reserve(iterations * samples_per_iteration);
std::vector<test_sample_1> 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<integration_sample_processor_t>();
processor->set_expected_samples_1(expected_1);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<std::string> payloads;
payloads.reserve(sample_count);
std::vector<test_sample_1> samples;
samples.reserve(sample_count);
for(int i = 0; i < sample_count; ++i)
{
payloads.push_back(std::string(payload_size, static_cast<char>(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<unit>(end_time - start_time);
auto period = static_cast<double>(unit::period().den);
double avg_write_time =
static_cast<double>(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<integration_sample_processor_t>();
processor->set_expected_samples_1(samples);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<std::thread> writers;
std::vector<int> thread_counters(thread_count, 0);
std::vector<std::vector<std::string>> 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<test_sample_1> 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<integration_sample_processor_t>();
processor->set_expected_samples_1(expected_1);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
parser(test_file_path);
parser.load(processor);
EXPECT_EQ(processor->get_sample_1_count(), total_samples);
EXPECT_TRUE(processor->all_expected_samples_found());
}
@@ -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 <array>
#include <cstddef>
#include <gtest/gtest.h>
#include <string>
#include <vector>
using namespace std::string_view_literals;
class cacheable_test : public ::testing::Test
{
protected:
void SetUp() override
{
buffer.fill(0);
position = 0;
}
std::array<uint8_t, 1024> 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<int*>(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<double*>(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<unsigned long*>(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<unsigned char*>(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<const char*>(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<uint8_t> 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<size_t*>(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<uint8_t> value;
rocprofsys::trace_cache::utility::store_value(value, buffer.data(), position);
EXPECT_EQ(position, sizeof(size_t));
size_t stored_size = *reinterpret_cast<size_t*>(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<uint8_t> 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<uint8_t> 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<uint8_t> original_value;
rocprofsys::trace_cache::utility::store_value(original_value, buffer.data(),
position);
uint8_t* data_pos = buffer.data();
std::vector<uint8_t> 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<uint8_t> 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);
}
@@ -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 <atomic>
#include <chrono>
#include <gtest/gtest.h>
#include <stdexcept>
#include <thread>
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<rocprofsys::trace_cache::worker_synchronization_t>();
}
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<int> test_counter;
};
std::atomic<int> 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<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));
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<int> call_count{ 0 };
std::atomic<bool> 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<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 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);
}
}
@@ -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 <array>
#include <cstdint>
#include <gtest/gtest.h>
#include <vector>
using namespace rocprofsys::trace_cache;
class sample_type_test : public ::testing::Test
{
protected:
void SetUp() override { buffer.fill(0); }
std::array<uint8_t, 4096> 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<kernel_dispatch_sample>(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<memory_copy_sample>(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<memory_allocate_sample>(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<region_sample>(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<region_sample>(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<in_time_sample>(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<pmc_event_with_sample>(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<uint8_t> 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<amd_smi_sample>(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<uint8_t> 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<uint8_t> 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<amd_smi_sample>(buffer_ptr);
EXPECT_TRUE(deserialized.gpu_activity.empty());
}
TEST_F(sample_type_test, cpu_freq_sample_serialize_deserialize)
{
std::vector<uint8_t> 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<cpu_freq_sample>(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<uint8_t> 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<uint8_t> 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<cpu_freq_sample>(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<backtrace_region_sample>(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<backtrace_region_sample>(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<uint32_t>(type_identifier_t::in_time_sample), 0x0000);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::pmc_event_with_sample), 0x0001);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::region), 0x0002);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::kernel_dispatch), 0x0003);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::memory_copy), 0x0004);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::memory_alloc), 0x0005);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::amd_smi_sample), 0x0006);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::cpu_freq_sample), 0x0007);
EXPECT_EQ(static_cast<uint32_t>(type_identifier_t::backtrace_region_sample), 0x0008);
EXPECT_EQ(static_cast<uint32_t>(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<kernel_dispatch_sample>(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<uint8_t> large_activity(256);
for(size_t i = 0; i < large_activity.size(); ++i)
{
large_activity[i] = static_cast<uint8_t>(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<amd_smi_sample>(buffer_ptr);
EXPECT_EQ(deserialized.gpu_activity.size(), 256);
EXPECT_EQ(deserialized.gpu_activity, large_activity);
}
@@ -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 <atomic>
#include <cstddef>
#include <fstream>
#include <gtest/gtest.h>
#include <memory>
#include <numeric>
#include <vector>
class sample_processor_t
{
public:
sample_processor_t() = default;
void set_expected_samples_1(const std::vector<test_sample_1>& samples)
{
std::lock_guard<std::mutex> lock(m_data_mutex);
m_expected_samples_1 = samples;
}
void set_expected_samples_2(const std::vector<test_sample_2>& samples)
{
std::lock_guard<std::mutex> lock(m_data_mutex);
m_expected_samples_2 = samples;
}
void set_expected_samples_3(const std::vector<test_sample_3>& samples)
{
std::lock_guard<std::mutex> 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<const test_sample_1&>(value);
std::lock_guard<std::mutex> 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<const test_sample_2&>(value);
std::lock_guard<std::mutex> 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<const test_sample_3&>(value);
std::lock_guard<std::mutex> 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<int> m_sample_1_count{ 0 };
std::atomic<int> m_sample_2_count{ 0 };
std::atomic<int> m_sample_3_count{ 0 };
std::atomic<int> m_unknown_count{ 0 };
std::vector<test_sample_1> m_expected_samples_1;
std::vector<test_sample_2> m_expected_samples_2;
std::vector<test_sample_3> 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 <typename T>
void write_vector(std::ofstream& ofs, const std::vector<T>& 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<const char*>(&header), sizeof(header));
std::vector<uint8_t> buffer;
buffer.reserve(header.sample_size);
buffer.assign(header.sample_size, 0xFF);
rocprofsys::trace_cache::serialize(buffer.data(), sample);
ofs.write(reinterpret_cast<const char*>(buffer.data()), header.sample_size);
}
}
void create_test_file_with_samples(const std::vector<test_sample_1>& samples_1,
const std::vector<test_sample_2>& samples_2,
const std::vector<test_sample_3>& 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<int> test_counter;
};
std::atomic<int> 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<sample_processor_t>();
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<test_sample_1> 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<sample_processor_t>();
processor->set_expected_samples_1(samples_1);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<test_sample_1> samples_1 = { test_sample_1(123, "mixed_test") };
std::vector<test_sample_2> samples_2 = { test_sample_2(3.14159, 555),
test_sample_2(2.71828, 777) };
std::vector<test_sample_3> samples_3 = { test_sample_3({ 0x01, 0x02, 0x03 }) };
create_test_file_with_samples(samples_1, samples_2, samples_3);
auto processor = std::make_shared<sample_processor_t>();
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<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<test_sample_1> samples_1 = { test_sample_1(123, "mixed_test") };
std::vector<test_sample_2> samples_2 = { test_sample_2(3.14159, 555),
test_sample_2(2.71828, 777) };
std::vector<test_sample_3> samples_3 = { test_sample_3({ 0x01, 0x02, 0x03 }) };
create_test_file_with_samples(samples_1, samples_2, samples_3);
auto processor = std::make_shared<sample_processor_t>();
processor->set_expected_samples_1(samples_1);
processor->set_expected_samples_2(samples_2);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2>
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<const char*>(&zero_header), sizeof(zero_header));
ofs.write(reinterpret_cast<const char*>(&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<const char*>(&valid_header), sizeof(valid_header));
std::vector<uint8_t> buffer(valid_header.sample_size);
rocprofsys::trace_cache::serialize(buffer.data(), valid_sample);
ofs.write(reinterpret_cast<const char*>(buffer.data()), valid_header.sample_size);
ofs.close();
}
auto processor = std::make_shared<sample_processor_t>();
processor->set_expected_samples_1({ valid_sample });
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<sample_processor_t>();
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
parser("non_existent_file.bin");
EXPECT_THROW(parser.load(processor), std::runtime_error);
}
TEST_F(storage_parser_test, load_large_sample_data)
{
std::vector<uint8_t> large_payload(10000);
std::iota(large_payload.begin(), large_payload.end(), 0);
std::vector<test_sample_3> samples_3 = { test_sample_3(large_payload) };
create_test_file_with_samples({}, {}, samples_3);
auto processor = std::make_shared<sample_processor_t>();
processor->set_expected_samples_3(samples_3);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<std::string> strings;
strings.reserve(num_of_elements);
std::vector<test_sample_1> 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<sample_processor_t>();
processor->set_expected_samples_1(many_samples);
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<const char*>(&header), sizeof(header));
std::vector<uint8_t> partial_data(50, 0xAA);
ofs.write(reinterpret_cast<const char*>(partial_data.data()), partial_data.size());
ofs.close();
auto processor = std::make_shared<sample_processor_t>();
rocprofsys::trace_cache::storage_parser<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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<test_sample_1> samples_1 = { test_sample_1(123,
"fragmented-space test") };
std::vector<test_sample_2> samples_2 = { test_sample_2(3.14159, 555),
test_sample_2(2.71828, 777) };
std::vector<test_sample_3> 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<uint8_t> fragmented_space;
fragmented_space.reserve(header.sample_size);
fragmented_space.assign(header.sample_size, 0);
ofs.write(reinterpret_cast<const char*>(&header), sizeof(header));
ofs.write(reinterpret_cast<const char*>(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<sample_processor_t>();
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<test_type_identifier_t, test_sample_1,
test_sample_2, test_sample_3>
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);
}
@@ -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 <gtest/gtest.h>
class type_registry_test : public ::testing::Test
{
protected:
rocprofsys::trace_cache::type_registry<test_type_identifier_t, test_sample_1,
test_sample_2>
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<uint8_t> 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<test_sample_1>(result.value()));
auto sample_1 = std::get<test_sample_1>(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<uint8_t> 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<test_sample_2>(result.value()));
auto sample_2 = std::get<test_sample_2>(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<test_sample_1, test_sample_2>;
using actual_variant =
rocprofsys::trace_cache::type_registry<test_type_identifier_t, test_sample_1,
test_sample_2>::variant_t;
EXPECT_TRUE((std::is_same_v<expected_variant, actual_variant>) );
}
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<uint8_t> buffer1(buffer_size1);
std::vector<uint8_t> 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<test_sample_1>(result1.value());
auto sample_1_2 = std::get<test_sample_1>(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");
}
@@ -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
} };
@@ -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_OBJECTS:trace-cache-tests>)
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)