From 0562cb1a1102c6837a2a979b7f6935ba27ab5a54 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 15:34:46 +0200 Subject: [PATCH 01/25] test(rocprofiler-systems): pin the trace cache record byte format Golden byte tests for every trace cache record type (14 types, plus the empty-optional variants of pmc_event_with_sample and kfd_sample). Each test checks get_size, the exact serialized bytes, that deserialize consumes all of them and that re-serializing the decoded record gives the same bytes. The expected bytes were generated from the current serialization code, so a later refactor of the serialization must keep them unchanged. Unlike the existing round-trip tests, these also catch a field reordered or dropped symmetrically in both serialize and deserialize. --- .../lib/core/trace_cache/tests/CMakeLists.txt | 1 + .../trace_cache/tests/test_record_format.cpp | 656 ++++++++++++++++++ 2 files changed, 657 insertions(+) create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt index 46ffd29acc63..b6e1ceefe214 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt @@ -11,6 +11,7 @@ set(trace_cache_tests_sources test_enabled_formats.cpp test_flush_worker.cpp test_kfd_args.cpp + test_record_format.cpp test_rocpd_pmc_name_contract.cpp test_rocpd_processor.cpp test_rocpd_xcp_output.cpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp new file mode 100644 index 000000000000..6c7f278fb529 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp @@ -0,0 +1,656 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include "backends/amd_smi/gpu_types.hpp" +#include "core/trace_cache/sample_type.hpp" +#include "library/pmc/collectors/cpu/sample.hpp" +#include "library/pmc/collectors/cpu/types.hpp" +#include "library/pmc/collectors/gpu/sample.hpp" +#include "library/pmc/collectors/gpu/types.hpp" +#include "library/pmc/collectors/gpu_perf_counter/sample.hpp" +#include "library/pmc/collectors/gpu_perf_counter/types.hpp" +#include "library/pmc/collectors/hipfile/sample.hpp" +#include "library/pmc/collectors/hipfile/types.hpp" +#include "library/pmc/collectors/nic/sample.hpp" +#include "library/pmc/collectors/nic/types.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace rocprofsys::trace_cache; + +// NOLINTBEGIN(readability-magic-numbers) +// Golden test values and bytes are arbitrary by design. +namespace +{ + +template +void +expect_golden(const Record& record, const std::array& expected) +{ + EXPECT_EQ(get_size(record), N); + + std::array buffer = {}; + serialize(buffer.data(), record); + EXPECT_EQ(buffer, expected); + + std::array writable_copy = expected; + std::uint8_t* cursor = writable_copy.data(); + const Record deserialized = deserialize(cursor); + const std::size_t bytes_read = cursor - writable_copy.data(); + EXPECT_EQ(bytes_read, N); + + std::array reserialized = {}; + serialize(reserialized.data(), deserialized); + EXPECT_EQ(reserialized, expected); +} + +} // namespace + +TEST(record_format, kernel_dispatch_sample) +{ + const kernel_dispatch_sample record{ + 0x1111111111111111UL, 0x2222222222222222UL, 0x3333333333333333UL, + 0x4444444444444444UL, 0x5555555555555555UL, 0x6666666666666666UL, + 0x7777777777777777UL, 0x8888888888888888UL, 0x9999999999999999UL, + 0x11111111U, 0x22222222U, 0x33333333U, + 0x44444444U, 0x55555555U, 0x66666666U, + 0x77777777U, 0x88888888U, 0x9999999AULL + }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, + 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, + 0x55, 0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, + 0x9a, 0x99, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, scratch_memory_sample) +{ + constexpr std::string_view k_name = "scratch_a"; + const scratch_memory_sample record{ 0x1111111111111111UL, 0x2222222222222222UL, + 0x3333333333333333UL, 0x4444444444444444UL, + 0x5555555555555555UL, k_name, + 0x11111111, 0x22222222, + 0x6666666666666666UL, 0x7777777777777777UL, + 0x8888888888888888UL, 0x9999999AULL }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x63, 0x72, 0x61, + 0x74, 0x63, 0x68, 0x5f, 0x61, 0x00, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, + 0x22, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x9a, + 0x99, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, memory_copy_sample) +{ + constexpr std::string_view k_name = "copy_b"; + const memory_copy_sample record{ 0x1111111111111111UL, 0x2222222222222222UL, + 0x3333333333333333UL, 0x4444444444444444UL, + 0x5555555555555555UL, k_name, + 0x6666666666666666UL, 0x7777777777777777UL, + 0x8888888888888888UL, 0x9999999999999999UL, + 0xAAAAAAAAAAAAAAAAUL, 0xBBBBBBBBULL }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x70, 0x79, + 0x5f, 0x62, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0x00, 0x00, 0x00, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, memory_allocate_sample) +{ + constexpr std::string_view k_name = "alloc_c"; + const memory_allocate_sample record{ 0x1111111111111111UL, + 0x2222222222222222UL, + 0x3333333333333333UL, + 0x4444444444444444UL, + k_name, + 0x11111111, + 0x5555555555555555UL, + 0x6666666666666666UL, + 0x7777777777777777UL, + 0x8888888888888888UL, + 0x9999999AULL }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x63, 0x00, + 0x11, 0x11, 0x11, 0x11, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x9a, 0x99, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, region_sample) +{ + constexpr std::string_view k_call_stack = "stack1"; + constexpr std::string_view k_args_str = "arg1"; + constexpr std::string_view k_category = "cat1"; + constexpr std::string_view k_name = "region_d"; + const region_sample record{ 0x1111111111111111UL, + k_name, + 0x2222222222222222UL, + 0x3333333333333333UL, + 0x4444444444444444UL, + 0x5555555555555555UL, + k_call_stack, + k_args_str, + k_category }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x00, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x31, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x61, 0x72, 0x67, 0x31, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x63, 0x61, 0x74, 0x31, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, in_time_sample) +{ + constexpr std::string_view k_call_stack = "stack2"; + constexpr std::string_view k_event_metadata = "meta1"; + constexpr std::string_view k_line_info = "line1"; + constexpr std::string_view k_track_name = "track_e"; + const in_time_sample record{ 0x1111111111111111UL, k_track_name, + 0x2222222222222222UL, k_event_metadata, + 0x3333333333333333UL, 0x4444444444444444UL, + 0x5555555555555555UL, k_call_stack, + k_line_info }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x00, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6d, 0x65, 0x74, 0x61, 0x31, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x32, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x69, 0x6e, 0x65, 0x31, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, pmc_event_with_sample) +{ + constexpr std::string_view k_call_stack = "stack3"; + constexpr std::string_view k_event_metadata = "meta2"; + constexpr std::string_view k_line_info = "line2"; + constexpr std::string_view k_pmc_info_name = "pmc_1"; + constexpr std::string_view k_track_name = "track_f"; + const pmc_event_with_sample record{ 0x1111111111111111UL, + k_track_name, + 0x2222222222222222UL, + k_event_metadata, + 0x3333333333333333UL, + 0x4444444444444444UL, + 0x5555555555555555UL, + k_call_stack, + k_line_info, + 0x11112222U, + 0x33U, + k_pmc_info_name, + 1.5, + static_cast(0x6666666666666666LL) }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x66, 0x00, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6d, 0x65, 0x74, 0x61, 0x32, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x33, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x69, 0x6e, 0x65, 0x32, 0x00, 0x22, 0x22, 0x11, 0x11, 0x33, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x6d, 0x63, 0x5f, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0x01, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, pmc_event_with_sample_empty_optional) +{ + constexpr std::string_view k_call_stack = "stack3"; + constexpr std::string_view k_event_metadata = "meta2"; + constexpr std::string_view k_line_info = "line2"; + constexpr std::string_view k_pmc_info_name = "pmc_1"; + constexpr std::string_view k_track_name = "track_f"; + const pmc_event_with_sample record{ 0x1111111111111111UL, + k_track_name, + 0x2222222222222222UL, + k_event_metadata, + 0x3333333333333333UL, + 0x4444444444444444UL, + 0x5555555555555555UL, + k_call_stack, + k_line_info, + 0x11112222U, + 0x33U, + k_pmc_info_name, + 1.5, + std::nullopt }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x66, 0x00, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6d, 0x65, 0x74, 0x61, 0x32, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x33, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x69, 0x6e, 0x65, 0x32, 0x00, 0x22, 0x22, 0x11, 0x11, 0x33, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x6d, 0x63, 0x5f, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, backtrace_region_sample) +{ + constexpr std::string_view k_call_stack = "stack4"; + constexpr std::string_view k_category = "cat2"; + constexpr std::string_view k_extdata = "ext1"; + constexpr std::string_view k_line_info = "line3"; + constexpr std::string_view k_name = "btrace_1"; + constexpr std::string_view k_track_name = "track_g"; + const backtrace_region_sample record{ 0x11111111U, 0x2222222222222222UL, + k_track_name, k_name, + 0x3333333333333333UL, 0x4444444444444444UL, + k_category, k_call_stack, + k_line_info, k_extdata }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, + 0x67, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x5f, 0x31, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x63, 0x61, 0x74, 0x32, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x34, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x69, 0x6e, 0x65, 0x33, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x78, 0x74, 0x31, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, kfd_sample) +{ + constexpr std::string_view k_args_str = "args1"; + constexpr std::string_view k_category = "cat3"; + constexpr std::string_view k_event_metadata = "meta3"; + constexpr std::string_view k_name = "kfd_ev"; + constexpr std::string_view k_pmc_info_name = "pmc_2"; + constexpr std::string_view k_track_name = "track_h"; + const kfd_sample record{ 0x1111111111111111UL, + k_name, + 0x2222222222222222UL, + 0x3333333333333333UL, + k_args_str, + k_category, + k_track_name, + k_event_metadata, + 0x11112222U, + 0x33U, + k_pmc_info_name, + 2.5, + static_cast(0x7777777777777777LL) }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6b, 0x66, 0x64, 0x5f, 0x65, 0x76, 0x00, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, + 0x72, 0x67, 0x73, 0x31, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x63, 0x61, 0x74, 0x33, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x68, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x65, 0x74, 0x61, 0x33, 0x00, + 0x22, 0x22, 0x11, 0x11, 0x33, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x70, 0x6d, 0x63, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x40, 0x01, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, kfd_sample_empty_optional) +{ + constexpr std::string_view k_args_str = "args1"; + constexpr std::string_view k_category = "cat3"; + constexpr std::string_view k_event_metadata = "meta3"; + constexpr std::string_view k_name = "kfd_ev"; + constexpr std::string_view k_pmc_info_name = "pmc_2"; + constexpr std::string_view k_track_name = "track_h"; + const kfd_sample record{ + 0x1111111111111111UL, k_name, 0x2222222222222222UL, 0x3333333333333333UL, + k_args_str, k_category, k_track_name, k_event_metadata, + 0x11112222U, 0x33U, k_pmc_info_name, 2.5, + std::nullopt + }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6b, 0x66, 0x64, 0x5f, 0x65, 0x76, 0x00, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x72, 0x67, 0x73, 0x31, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x61, 0x74, 0x33, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x63, + 0x6b, 0x5f, 0x68, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, + 0x65, 0x74, 0x61, 0x33, 0x00, 0x22, 0x22, 0x11, 0x11, 0x33, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x6d, 0x63, 0x5f, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x00, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, cpu_pmc_sample) +{ + const auto freqs = std::vector{ 0xaa, 0xbb }; + const auto loads = std::vector{ 0xcc, 0xdd }; + + auto process_data = rocprofsys::pmc::collectors::cpu::process_metrics{}; + process_data.page_rss = 0x3333333333333333UL; + process_data.virt_mem = 0x4444444444444444UL; + process_data.peak_rss = 0x5555555555555555UL; + process_data.context_switches = 0x66666666UL; + process_data.page_faults = 0x77777777UL; + process_data.user_mode_time = static_cast(0x0888888888888888UL); + process_data.kernel_mode_time = static_cast(0x0999999999999999UL); + + auto enabled = rocprofsys::pmc::collectors::cpu::enabled_metrics{}; + enabled.value = 0x0000A5A5U; + const rocprofsys::pmc::collectors::cpu::sample record{ + enabled, 0x11111111U, 0x2222222222222222UL, process_data, freqs, loads + }; + + constexpr std::array k_expected{ + 0xa5, 0xa5, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x08, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x09, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xdd, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, ainic_pmc_sample) +{ + auto metrics = rocprofsys::pmc::collectors::nic::metrics{}; + metrics.rx_rdma_ucast_bytes = 0x3333333333333333UL; + metrics.tx_rdma_ucast_bytes = 0x4444444444444444UL; + metrics.rx_rdma_ucast_pkts = 0x5555555555555555UL; + metrics.tx_rdma_ucast_pkts = 0x6666666666666666UL; + metrics.rx_rdma_cnp_pkts = 0x7777777777777777UL; + metrics.tx_rdma_cnp_pkts = 0x8888888888888888UL; + metrics.tx_rdma_ack_timeout = 0x9999999999999999UL; + metrics.resp_tx_pkt_seq_err = 0xAAAAAAAAAAAAAAAAUL; + metrics.req_rx_pkt_seq_err = 0xBBBBBBBBBBBBBBBBUL; + metrics.req_rx_impl_nak_seq_err = 0xCCCCCCCCCCCCCCCCUL; + + auto enabled = rocprofsys::pmc::collectors::nic::enabled_metrics{}; + enabled.value = 0x00005A5AU; + const rocprofsys::pmc::collectors::nic::sample record{ + enabled, 0x11111111U, std::string("nic_dev_a"), 0x2222222222222222UL, metrics + }; + + constexpr std::array k_expected{ + 0x5a, 0x5a, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6e, 0x69, 0x63, 0x5f, 0x64, 0x65, 0x76, 0x5f, 0x61, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, hipfile_pmc_sample) +{ + auto metrics = rocprofsys::pmc::collectors::hipfile::metrics{}; + metrics.read_bytes = 0x3333333333333333UL; + metrics.write_bytes = 0x4444444444444444UL; + metrics.read_ops = 0x5555555555555555UL; + metrics.write_ops = 0x6666666666666666UL; + metrics.fastpath_reads = 0x7777777777777777UL; + metrics.fastpath_writes = 0x8888888888888888UL; + metrics.fallback_reads = 0x9999999999999999UL; + metrics.fallback_writes = 0xAAAAAAAAAAAAAAAAUL; + metrics.unaligned_reads = 0xBBBBBBBBBBBBBBBBUL; + metrics.unaligned_writes = 0xCCCCCCCCCCCCCCCCUL; + metrics.read_errors = 0xDDDDDDDDDDDDDDDDUL; + metrics.write_errors = 0xEEEEEEEEEEEEEEEEUL; + metrics.read_bandwidth = 1.5; + metrics.write_bandwidth = 2.5; + metrics.query_failed = false; + + auto enabled = rocprofsys::pmc::collectors::hipfile::enabled_metrics{}; + enabled.value = 0x0000C3C3U; + const rocprofsys::pmc::collectors::hipfile::sample record{ enabled, 0x11111111U, + 0x2222222222222222UL, + metrics }; + + constexpr std::array k_expected{ + 0xc3, 0xc3, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xcc, 0xcc, 0xcc, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, gpu_pmc_sample) +{ + auto metrics = rocprofsys::backends::amd_smi::gpu::metrics{}; + metrics.average_socket_power = 0x33333333U; + metrics.current_socket_power = 0x44444444U; + metrics.memory_usage = 0x5555555555555555UL; + metrics.hotspot_temperature = static_cast(0x06666666LL); + metrics.edge_temperature = static_cast(0x07777777LL); + metrics.gfx_activity = 0x88888888U; + metrics.umc_activity = 0x99999999U; + metrics.mm_activity = 0xAAAAAAAAU; + metrics.xcp_stats[0].jpeg_busy[0] = 0x1111U; + metrics.xcp_stats[0].jpeg_busy[1] = 0x2222U; + metrics.xcp_stats[0].vcn_busy[0] = 0x3333U; + metrics.xcp_stats[0].vcn_busy[1] = 0x4444U; + metrics.xcp_stats[1].jpeg_busy[0] = 0x5555U; + metrics.xcp_stats[1].jpeg_busy[1] = 0x6666U; + metrics.xcp_stats[1].vcn_busy[0] = 0x7777U; + metrics.xcp_stats[1].vcn_busy[1] = 0x8888U; + metrics.vcn_activity[0] = 0xBBBBU; + metrics.vcn_activity[1] = 0xCCCCU; + metrics.jpeg_activity[0] = 0xDDDDU; + metrics.jpeg_activity[1] = 0xEEEEU; + metrics.xgmi.link.width = 0xFFFFU; + metrics.xgmi.link.speed = 0x1E1EU; + metrics.xgmi.data_acc.read[0] = 0x2222222222222222UL; + metrics.xgmi.data_acc.read[1] = 0x2A2A2A2A2A2A2A2AUL; + metrics.xgmi.data_acc.write[0] = 0x3333333333333333UL; + metrics.xgmi.data_acc.write[1] = 0x3A3A3A3A3A3A3A3AUL; + metrics.pcie.link.width = 0x4E4EU; + metrics.pcie.link.speed = 0x5E5EU; + metrics.pcie.bandwidth.acc = 0x6E6E6E6E6E6E6E6EUL; + metrics.pcie.bandwidth.inst = 0x7E7E7E7E7E7E7E7EUL; + metrics.sdma_usage = 0x8B8B8B8BU; + metrics.gfx_clock_mhz = 0x9C9C9C9CU; + metrics.mem_clock_mhz = 0xADADADADU; + + auto enabled = rocprofsys::pmc::collectors::gpu::enabled_metrics{}; + enabled.value = 0x00001111U; + const rocprofsys::pmc::collectors::gpu::sample record{ enabled, 0x0C0C0C0CU, + 0x1D1D1D1D1D1D1D1DUL, + metrics }; + + constexpr std::array k_expected{ + 0x11, 0x11, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x77, 0x77, 0x77, 0x07, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x88, 0x88, + 0x99, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x22, 0x22, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x44, + 0x44, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77, 0x88, 0x88, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0xbb, 0xcc, + 0xcc, 0x00, 0x00, 0x00, 0x00, 0xdd, 0xdd, 0xee, 0xee, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x4e, 0x5e, 0x5e, 0x6e, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x8b, 0x8b, 0x8b, 0x8b, 0x9c, 0x9c, 0x9c, 0x9c, 0xad, 0xad, 0xad, 0xad, + }; + + expect_golden(record, k_expected); +} + +TEST(record_format, gpu_perf_counter_sample) +{ + auto entries = + std::vector{}; + entries.push_back(rocprofsys::pmc::collectors::gpu_perf_counter::counter_value{ + .counter_id = + static_cast( + 0x11111111U), + .value = 1.5 }); + entries.push_back(rocprofsys::pmc::collectors::gpu_perf_counter::counter_value{ + .counter_id = + static_cast( + 0x33333333U), + .value = 2.5 }); + + const rocprofsys::pmc::collectors::gpu_perf_counter::sample record{ + 0x0C0C0C0CU, 0x5555555555555555UL, std::move(entries) + }; + + constexpr std::array k_expected{ + 0x0c, 0x0c, 0x0c, 0x0c, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x02, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0x33, 0x33, 0x33, 0x33, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, + }; + + expect_golden(record, k_expected); +} +// NOLINTEND(readability-magic-numbers) From a7295e10df4de13d85aa3d393cd9bfe88529998b Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 15:47:34 +0200 Subject: [PATCH 02/25] feat(rocprofiler-systems): add trace cache archives Add core/trace_cache/archive.hpp: binary_size_archive, binary_write_archive and binary_read_archive, the archive_record concept and record_size / record_write / record_read. A record lists its fields once, in byte order, in a member template serialize(Archive&); the three archives measure, write and read those fields through the existing utility encoding functions, so the byte format is unchanged. A std::span field is read back as a view into the buffer. Records whose read layout differs from the write layout branch on Archive::k_is_loading. Nothing uses the archives yet; the per-record serialize / deserialize / get_size specializations are converted in a follow-up commit. --- .../lib/core/trace_cache/CMakeLists.txt | 1 + .../source/lib/core/trace_cache/archive.hpp | 125 ++++++ .../lib/core/trace_cache/tests/CMakeLists.txt | 1 + .../core/trace_cache/tests/test_archive.cpp | 359 ++++++++++++++++++ 4 files changed, 486 insertions(+) create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt index bd632d886d4e..7d1caccb3ff7 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt @@ -14,6 +14,7 @@ set(trace_cache_sources ) set(trace_cache_headers + ${CMAKE_CURRENT_LIST_DIR}/archive.hpp ${CMAKE_CURRENT_LIST_DIR}/cache_manager.hpp ${CMAKE_CURRENT_LIST_DIR}/data_types.hpp ${CMAKE_CURRENT_LIST_DIR}/discovery.hpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp new file mode 100644 index 000000000000..a9905032c322 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -0,0 +1,125 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core/trace_cache/cacheable.hpp" + +#include +#include +#include +#include +#include + +namespace rocprofsys::trace_cache +{ + +// The cache format writes size_t lengths; records pass size_t fields where the old code +// cast them to std::uint64_t. Both rely on the two being the same type. +static_assert(std::is_same_v, + "trace cache format assumes size_t is std::uint64_t"); + +/** Sums the encoded size of the fields a record passes to it. */ +struct binary_size_archive +{ + static constexpr bool k_is_loading = false; + std::size_t size = 0; + + template + void operator()(const Types&... values) + { + size += (std::size_t{ 0 } + ... + utility::get_size(values)); + } +}; + +/** Writes the fields into a buffer that already has room for record_size() bytes. */ +struct binary_write_archive +{ + static constexpr bool k_is_loading = false; + std::uint8_t* buffer = nullptr; + std::size_t position = 0; + + template + void operator()(const Types&... values) + { + (utility::store_value(values, buffer, position), ...); + } +}; + +/** + * Reads the fields back. string_view and span fields become views into + * the buffer, so the buffer must outlive the record (as for the parse buffer today). + */ +struct binary_read_archive +{ + static constexpr bool k_is_loading = true; + std::uint8_t* cursor = nullptr; + + template + void operator()(Types&... values) + { + (read_one(values), ...); + } + +private: + template + void read_one(Type& value) + { + if constexpr(std::is_same_v>) + { + std::size_t byte_count = 0; + std::memcpy(&byte_count, cursor, sizeof(std::size_t)); + cursor += sizeof(std::size_t); + value = { reinterpret_cast(cursor), byte_count }; + cursor += byte_count; + } + else + { + utility::parse_value(cursor, value); + } + } +}; + +/** A record that lists its fields once, in byte order, in serialize(Archive&). */ +template +concept archive_record = + std::is_default_constructible_v && + requires(T& record, binary_size_archive& sizer, binary_write_archive& writer, + binary_read_archive& reader) { + { T::type_identifier }; + record.serialize(sizer); + record.serialize(writer); + record.serialize(reader); + }; + +// serialize() is one non-const member shared by all archives. The size and write archives +// only read through it, so the const_cast is safe (cereal's output archives do the same). +template +[[nodiscard]] std::size_t +record_size(const T& record) +{ + binary_size_archive archive; + const_cast(record).serialize(archive); + return archive.size; +} + +template +void +record_write(std::uint8_t* buffer, const T& record) +{ + binary_write_archive archive{ .buffer = buffer, .position = 0 }; + const_cast(record).serialize(archive); +} + +template +[[nodiscard]] T +record_read(std::uint8_t*& buffer) +{ + T record{}; + binary_read_archive archive{ .cursor = buffer }; + record.serialize(archive); + buffer = archive.cursor; + return record; +} + +} // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt index b6e1ceefe214..a23e0311202f 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/CMakeLists.txt @@ -3,6 +3,7 @@ set(trace_cache_tests_sources test_agent_info_json.cpp + test_archive.cpp test_args_serialization.cpp test_buffer_storage.cpp test_cache_integration.cpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp new file mode 100644 index 000000000000..108b493e3ee7 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -0,0 +1,359 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// NOLINTBEGIN(readability-magic-numbers) +// Test values in this file are arbitrary and chosen for coverage, not domain significance + +#include "core/trace_cache/archive.hpp" +#include "core/trace_cache/cacheable.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rocprofsys::trace_cache +{ + +namespace +{ + +enum class test_record_id : std::uint32_t +{ + scalar_test = 1, + string_test = 2, + vector_test = 3, + optional_test = 4, + span_test = 5, + derived_test = 6, + counted_entries_test = 7, +}; + +struct scalar_record +{ + static constexpr auto type_identifier = test_record_id::scalar_test; + + std::uint32_t val_u32 = 0; + std::uint64_t val_u64 = 0; + double val_dbl = 0.0; + + template + void serialize(Archive& archive) + { + archive(val_u32, val_u64, val_dbl); + } +}; + +struct string_record +{ + static constexpr auto type_identifier = test_record_id::string_test; + + std::string_view text; + + template + void serialize(Archive& archive) + { + archive(text); + } +}; + +struct vector_record +{ + static constexpr auto type_identifier = test_record_id::vector_test; + + std::vector data; + + template + void serialize(Archive& archive) + { + archive(data); + } +}; + +struct optional_record +{ + static constexpr auto type_identifier = test_record_id::optional_test; + + std::optional value; + + template + void serialize(Archive& archive) + { + archive(value); + } +}; + +struct span_record +{ + static constexpr auto type_identifier = test_record_id::span_test; + + std::span bytes; + + template + void serialize(Archive& archive) + { + archive(bytes); + } +}; + +struct base_record +{ + static constexpr auto type_identifier = test_record_id::derived_test; + + std::uint32_t base_field = 0; + double base_value = 0.0; + + template + void serialize(Archive& archive) + { + archive(base_field, base_value); + } +}; + +struct derived_record : base_record +{ + static constexpr auto type_identifier = test_record_id::derived_test; + + std::uint64_t extra_field = 0; + + template + void serialize(Archive& archive) + { + base_record::serialize(archive); + archive(extra_field); + } +}; + +struct entry +{ + std::uint64_t id = 0; + double value = 0.0; +}; + +struct counted_entries_record +{ + static constexpr auto type_identifier = test_record_id::counted_entries_test; + + std::uint32_t device = 0; + std::vector entries; + + template + void serialize(Archive& archive) + { + auto count = static_cast(entries.size()); + archive(device, count); + if constexpr(Archive::k_is_loading) + { + entries.resize(count); + } + for(std::size_t idx = 0; idx < entries.size(); ++idx) + { + archive(entries[idx].id, entries[idx].value); + } + } +}; + +} // namespace + +constexpr std::size_t k_buffer_size = 4096; + +TEST(archive, scalars_match_utility_encoding) +{ + std::array buffer{}; + const std::uint32_t val_u32 = 12345U; + const std::uint64_t val_u64 = 98765432101234ULL; + const double val_dbl = 2.71828; + + scalar_record orig; + orig.val_u32 = val_u32; + orig.val_u64 = val_u64; + orig.val_dbl = val_dbl; + + const std::size_t size = record_size(orig); + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const scalar_record read = record_read(read_buffer); + + EXPECT_EQ(read.val_u32, val_u32); + EXPECT_EQ(read.val_u64, val_u64); + EXPECT_DOUBLE_EQ(read.val_dbl, val_dbl); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); + + EXPECT_EQ(size, utility::get_size(val_u32, val_u64, val_dbl)); + + std::array other_buffer{}; + utility::store_value(other_buffer.data(), val_u32, val_u64, val_dbl); + + EXPECT_EQ(std::memcmp(buffer.data(), other_buffer.data(), size), 0); +} + +TEST(archive, string_view_reads_back_as_view_into_buffer) +{ + std::array buffer{}; + const std::string_view orig_text = "hello world"; + + string_record orig; + orig.text = orig_text; + + const std::size_t size = record_size(orig); + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const string_record read = record_read(read_buffer); + + EXPECT_EQ(read.text, orig_text); + EXPECT_EQ(read.text.data(), + reinterpret_cast(buffer.data() + sizeof(std::size_t))); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); +} + +TEST(archive, vector_round_trip) +{ + std::array buffer{}; + const std::vector orig_data = { 10, 20, 30 }; + + vector_record orig; + orig.data = orig_data; + + const std::size_t size = record_size(orig); + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const vector_record read = record_read(read_buffer); + + EXPECT_EQ(read.data, orig_data); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); +} + +TEST(archive, optional_engaged_and_empty) +{ + std::array buffer{}; + optional_record orig_engaged; + orig_engaged.value = 9876543210LL; + + const std::size_t size_engaged = record_size(orig_engaged); + record_write(buffer.data(), orig_engaged); + + std::uint8_t* read_buf = buffer.data(); + const optional_record read_engaged = record_read(read_buf); + + ASSERT_TRUE(read_engaged.value.has_value()); + EXPECT_EQ(read_engaged.value.value(), 9876543210LL); + + buffer.fill(0); + optional_record orig_empty; + orig_empty.value.reset(); + + const std::size_t size_empty = record_size(orig_empty); + record_write(buffer.data(), orig_empty); + + read_buf = buffer.data(); + const optional_record read_empty = record_read(read_buf); + + EXPECT_FALSE(read_empty.value.has_value()); + + EXPECT_EQ(size_engaged, size_empty + sizeof(std::int64_t)); +} + +TEST(archive, byte_span_reads_back_as_view) +{ + std::array buffer{}; + std::array orig_array = { 1.1, 2.2, 3.3 }; + const auto bytes_span = std::as_bytes(std::span(orig_array)); + + span_record orig; + orig.bytes = bytes_span; + + const std::size_t size = record_size(orig); + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const span_record read = record_read(read_buffer); + + EXPECT_EQ(read.bytes.size(), 3 * sizeof(double)); + EXPECT_GE(read.bytes.data(), reinterpret_cast(buffer.data())); + EXPECT_LT(read.bytes.data(), + reinterpret_cast(buffer.data() + buffer.size())); + + for(std::size_t idx = 0; idx < 3; ++idx) + { + double value = 0.0; + std::memcpy(&value, read.bytes.data() + idx * sizeof(double), sizeof(double)); + EXPECT_DOUBLE_EQ(value, orig_array[idx]); + } + + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); +} + +TEST(archive, derived_record_serializes_base_first) +{ + std::array buffer{}; + derived_record orig; + orig.base_field = 555U; + orig.base_value = 3.14; + orig.extra_field = 777ULL; + + const std::size_t base_size = + record_size(base_record{ .base_field = 555U, .base_value = 3.14 }); + const std::size_t total_size = record_size(orig); + + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const derived_record read = record_read(read_buffer); + + EXPECT_EQ(read.base_field, 555U); + EXPECT_DOUBLE_EQ(read.base_value, 3.14); + EXPECT_EQ(read.extra_field, 777ULL); + + EXPECT_EQ(total_size, base_size + sizeof(std::uint64_t)); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(total_size)); +} + +TEST(archive, is_loading_branch_for_counted_entries) +{ + std::array buffer{}; + counted_entries_record orig; + orig.device = 1U; + orig.entries.push_back({ .id = 100, .value = 1.5 }); + orig.entries.push_back({ .id = 200, .value = 2.5 }); + + const std::size_t size = record_size(orig); + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const counted_entries_record read = record_read(read_buffer); + + EXPECT_EQ(read.device, 1U); + EXPECT_EQ(read.entries.size(), 2U); + EXPECT_EQ(read.entries[0].id, 100); + EXPECT_DOUBLE_EQ(read.entries[0].value, 1.5); + EXPECT_EQ(read.entries[1].id, 200); + EXPECT_DOUBLE_EQ(read.entries[1].value, 2.5); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); + + buffer.fill(0); + counted_entries_record orig_empty; + orig_empty.device = 2U; + + const std::size_t size_empty = record_size(orig_empty); + record_write(buffer.data(), orig_empty); + + read_buffer = buffer.data(); + const counted_entries_record read_empty = + record_read(read_buffer); + + EXPECT_EQ(read_empty.device, 2U); + EXPECT_EQ(read_empty.entries.size(), 0U); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size_empty)); +} + +} // namespace rocprofsys::trace_cache + +// NOLINTEND(readability-magic-numbers) From dd50fec2c0744cf3ed545242c80ddb31cd5f496b Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 16:18:07 +0200 Subject: [PATCH 03/25] refactor(rocprofiler-systems): serialize trace cache records through archives Every trace cache record listed its fields three times, in hand-written serialize / deserialize / get_size specializations that had to stay in sync. Each record now lists its fields once, in byte order, in a member template serialize(Archive&) that the archives use to measure, write and read it. - All 14 record types (sample_type.hpp and the cpu, gpu, nic, hipfile and gpu_perf_counter collector samples) and the 5 test mocks are converted; their 42 + 15 specializations are deleted. - pmc_event_with_sample serializes its in_time_sample base first. - nic::sample copies device_name back on read; gpu_perf_counter::sample writes a count and resizes its entries on read (Archive::k_is_loading). - cache_type_traits.hpp loses the primary templates and the serializable / deserializable / has_get_size / cacheable concepts; archive.hpp provides one generic get_size / serialize / deserialize and type_traits::cacheable, so buffer_storage and type_registry are unchanged. The byte format is unchanged: the golden record format tests pass without modification. --- .../source/lib/core/trace_cache/archive.hpp | 31 ++ .../lib/core/trace_cache/buffer_storage.hpp | 1 + .../core/trace_cache/cache_type_traits.hpp | 49 --- .../lib/core/trace_cache/sample_type.hpp | 411 +++--------------- .../core/trace_cache/tests/mocked_types.hpp | 145 ++---- .../trace_cache/tests/test_record_format.cpp | 1 + .../trace_cache/tests/test_sample_type.cpp | 1 + .../lib/core/trace_cache/type_registry.hpp | 1 + .../library/pmc/collectors/cpu/sample.hpp | 47 +- .../pmc/collectors/cpu/tests/test_sample.cpp | 1 + .../library/pmc/collectors/gpu/sample.hpp | 77 +--- .../collectors/gpu_perf_counter/sample.hpp | 59 +-- .../gpu_perf_counter/tests/test_sample.cpp | 1 + .../library/pmc/collectors/hipfile/sample.hpp | 68 +-- .../library/pmc/collectors/nic/sample.hpp | 63 +-- 15 files changed, 211 insertions(+), 745 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index a9905032c322..2723afec29b0 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -3,6 +3,7 @@ #pragma once +#include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/cacheable.hpp" #include @@ -122,4 +123,34 @@ record_read(std::uint8_t*& buffer) return record; } +/** Encoded size of a record. One definition for every record type. */ +template +[[nodiscard]] std::size_t +get_size(const T& record) +{ + return record_size(record); +} + +/** Writes a record into a buffer with room for get_size(record) bytes. */ +template +void +serialize(std::uint8_t* buffer, const T& record) +{ + record_write(buffer, record); +} + +/** Reads a record and advances the buffer past it. */ +template +[[nodiscard]] T +deserialize(std::uint8_t*& buffer) +{ + return record_read(buffer); +} + +namespace type_traits +{ +template +concept cacheable = archive_record && has_type_identifier; +} // namespace type_traits + } // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp index 19f0cb36f03d..1f57f477a5c7 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp @@ -4,6 +4,7 @@ #pragma once #include "core/state.hpp" +#include "core/trace_cache/archive.hpp" #include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/cacheable.hpp" diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp index 67731e28cf13..0e4800ac543e 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp @@ -20,38 +20,6 @@ namespace rocprofsys::trace_cache namespace type_traits { -template -struct always_false : std::false_type -{}; - -} // namespace type_traits - -template -void -serialize(std::uint8_t*, const T&) -{ - static_assert(type_traits::always_false::value, "serialize not specialized"); -} - -template -T -deserialize(std::uint8_t*&) -{ - static_assert(type_traits::always_false::value, "deserialize not specialized"); - return T{}; -} - -template -size_t -get_size(const T&) -{ - static_assert(type_traits::always_false::value, "get_size(T) not specialized"); - return 0; -} - -namespace type_traits -{ - template struct tuple_to_variant; @@ -126,28 +94,11 @@ struct is_enum_class template inline constexpr bool is_enum_class_v = is_enum_class::value; -template -concept serializable = requires(std::uint8_t* dst, const T& v) { serialize(dst, v); }; - -template -concept deserializable = requires(std::uint8_t*& src) { - { deserialize(src) } -> std::same_as; -}; - -template -concept has_get_size = requires(const T& v) { - { get_size(v) } -> std::convertible_to; -}; - template concept has_type_identifier = is_enum_class_v && requires { { T::type_identifier } -> std::convertible_to; }; -template -concept cacheable = serializable && deserializable && has_get_size && - has_type_identifier; - template concept sample_processor = requires(T t, TypeIdentifierEnum e, const CacheableType& c) { { t.execute_sample_processing(e, c) } -> std::same_as; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp index 01ee4b6bf0e8..985552c81017 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp @@ -88,50 +88,17 @@ struct kernel_dispatch_sample : cacheable_t std::uint32_t grid_size_y; std::uint32_t grid_size_z; size_t stream_handle; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const kernel_dispatch_sample& item) -{ - utility::store_value( - buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.kernel_id, item.dispatch_id, item.queue_id_handle, - item.correlation_id_internal, item.correlation_id_ancestor, - item.private_segment_size, item.group_segment_size, item.workgroup_size_x, - item.workgroup_size_y, item.workgroup_size_z, item.grid_size_x, item.grid_size_y, - item.grid_size_z, static_cast(item.stream_handle)); -} - -template <> -inline kernel_dispatch_sample -deserialize(std::uint8_t*& buffer) -{ - kernel_dispatch_sample item; - std::uint64_t stream_handle; - utility::parse_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.kernel_id, item.dispatch_id, - item.queue_id_handle, item.correlation_id_internal, - item.correlation_id_ancestor, item.private_segment_size, - item.group_segment_size, item.workgroup_size_x, - item.workgroup_size_y, item.workgroup_size_z, item.grid_size_x, - item.grid_size_y, item.grid_size_z, stream_handle); - item.stream_handle = stream_handle; - return item; -} - -template <> -inline size_t -get_size(const kernel_dispatch_sample& item) -{ - return utility::get_size( - item.start_timestamp, item.end_timestamp, item.thread_id, item.agent_id_handle, - item.kernel_id, item.dispatch_id, item.queue_id_handle, - item.correlation_id_internal, item.correlation_id_ancestor, - item.private_segment_size, item.group_segment_size, item.workgroup_size_x, - item.workgroup_size_y, item.workgroup_size_z, item.grid_size_x, item.grid_size_y, - item.grid_size_z, static_cast(item.stream_handle)); -} + template + void serialize(Archive& archive) + { + archive(start_timestamp, end_timestamp, thread_id, agent_id_handle, kernel_id, + dispatch_id, queue_id_handle, correlation_id_internal, + correlation_id_ancestor, private_segment_size, group_segment_size, + workgroup_size_x, workgroup_size_y, workgroup_size_z, grid_size_x, + grid_size_y, grid_size_z, stream_handle); + } +}; struct scratch_memory_sample : cacheable_t { @@ -172,44 +139,15 @@ struct scratch_memory_sample : cacheable_t std::uint64_t correlation_id_internal; std::uint64_t correlation_id_ancestor; size_t stream_handle; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const scratch_memory_sample& item) -{ - utility::store_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.queue_id_handle, item.name, - item.operation, item.flags, item.allocation_size, - item.correlation_id_internal, item.correlation_id_ancestor, - static_cast(item.stream_handle)); -} - -template <> -inline scratch_memory_sample -deserialize(std::uint8_t*& buffer) -{ - scratch_memory_sample item; - std::uint64_t stream_handle; - utility::parse_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.queue_id_handle, item.name, - item.operation, item.flags, item.allocation_size, - item.correlation_id_internal, item.correlation_id_ancestor, - stream_handle); - item.stream_handle = stream_handle; - return item; -} - -template <> -inline size_t -get_size(const scratch_memory_sample& item) -{ - return utility::get_size(item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.queue_id_handle, item.name, - item.operation, item.flags, item.allocation_size, - item.correlation_id_internal, item.correlation_id_ancestor, - static_cast(item.stream_handle)); -} + template + void serialize(Archive& archive) + { + archive(start_timestamp, end_timestamp, thread_id, agent_id_handle, + queue_id_handle, name, operation, flags, allocation_size, + correlation_id_internal, correlation_id_ancestor, stream_handle); + } +}; struct memory_copy_sample : cacheable_t { @@ -249,46 +187,16 @@ struct memory_copy_sample : cacheable_t std::uint64_t dst_address_value; std::uint64_t src_address_value; size_t stream_handle; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const memory_copy_sample& item) -{ - utility::store_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.dst_agent_id_handle, item.src_agent_id_handle, item.name, - item.bytes, item.correlation_id_internal, - item.correlation_id_ancestor, item.dst_address_value, - item.src_address_value, - static_cast(item.stream_handle)); -} - -template <> -inline memory_copy_sample -deserialize(std::uint8_t*& buffer) -{ - memory_copy_sample item; - std::uint64_t stream_handle; - utility::parse_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.dst_agent_id_handle, item.src_agent_id_handle, item.name, - item.bytes, item.correlation_id_internal, - item.correlation_id_ancestor, item.dst_address_value, - item.src_address_value, stream_handle); - item.stream_handle = stream_handle; - return item; -} - -template <> -inline size_t -get_size(const memory_copy_sample& item) -{ - return utility::get_size(item.start_timestamp, item.end_timestamp, item.thread_id, - item.dst_agent_id_handle, item.src_agent_id_handle, - item.name, item.bytes, item.correlation_id_internal, - item.correlation_id_ancestor, item.dst_address_value, - item.src_address_value, - static_cast(item.stream_handle)); -} + template + void serialize(Archive& archive) + { + archive(start_timestamp, end_timestamp, thread_id, dst_agent_id_handle, + src_agent_id_handle, name, bytes, correlation_id_internal, + correlation_id_ancestor, dst_address_value, src_address_value, + stream_handle); + } +}; struct memory_allocate_sample : cacheable_t { @@ -326,43 +234,15 @@ struct memory_allocate_sample : cacheable_t std::uint64_t correlation_id_ancestor; std::uint64_t address_value; size_t stream_handle; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const memory_allocate_sample& item) -{ - utility::store_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.name, item.operation, - item.allocation_size, item.correlation_id_internal, - item.correlation_id_ancestor, item.address_value, - static_cast(item.stream_handle)); -} - -template <> -inline memory_allocate_sample -deserialize(std::uint8_t*& buffer) -{ - memory_allocate_sample item; - std::uint64_t stream_handle; - utility::parse_value(buffer, item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.name, item.operation, - item.allocation_size, item.correlation_id_internal, - item.correlation_id_ancestor, item.address_value, stream_handle); - item.stream_handle = stream_handle; - return item; -} - -template <> -inline size_t -get_size(const memory_allocate_sample& item) -{ - return utility::get_size(item.start_timestamp, item.end_timestamp, item.thread_id, - item.agent_id_handle, item.name, item.operation, - item.allocation_size, item.correlation_id_internal, - item.correlation_id_ancestor, item.address_value, - static_cast(item.stream_handle)); -} + template + void serialize(Archive& archive) + { + archive(start_timestamp, end_timestamp, thread_id, agent_id_handle, name, + operation, allocation_size, correlation_id_internal, + correlation_id_ancestor, address_value, stream_handle); + } +}; struct region_sample : cacheable_t { @@ -395,39 +275,14 @@ struct region_sample : cacheable_t std::string_view call_stack; std::string_view args_str; std::string_view category; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const region_sample& item) -{ - utility::store_value(buffer, item.thread_id, item.name, item.correlation_id_internal, - item.correlation_id_ancestor, item.start_timestamp, - item.end_timestamp, item.call_stack, item.args_str, - item.category); -} - -template <> -inline region_sample -deserialize(std::uint8_t*& buffer) -{ - region_sample item; - utility::parse_value(buffer, item.thread_id, item.name, item.correlation_id_internal, - item.correlation_id_ancestor, item.start_timestamp, - item.end_timestamp, item.call_stack, item.args_str, - item.category); - return item; -} - -template <> -inline size_t -get_size(const region_sample& item) -{ - return utility::get_size(item.thread_id, item.name, item.correlation_id_internal, - item.correlation_id_ancestor, item.start_timestamp, - item.end_timestamp, item.call_stack, item.args_str, - item.category); -} + template + void serialize(Archive& archive) + { + archive(thread_id, name, correlation_id_internal, correlation_id_ancestor, + start_timestamp, end_timestamp, call_stack, args_str, category); + } +}; struct in_time_sample : cacheable_t { @@ -460,49 +315,14 @@ struct in_time_sample : cacheable_t size_t correlation_id; std::string_view call_stack; std::string_view line_info; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const in_time_sample& item) -{ - utility::store_value(buffer, item.category_enum_id, item.track_name, - static_cast(item.timestamp_ns), - item.event_metadata, static_cast(item.stack_id), - static_cast(item.parent_stack_id), - static_cast(item.correlation_id), item.call_stack, - item.line_info); -} - -template <> -inline in_time_sample -deserialize(std::uint8_t*& buffer) -{ - in_time_sample item; - size_t category_enum_id; - std::uint64_t timestamp_ns, stack_id, parent_stack_id, correlation_id; - utility::parse_value(buffer, category_enum_id, item.track_name, timestamp_ns, - item.event_metadata, stack_id, parent_stack_id, correlation_id, - item.call_stack, item.line_info); - item.category_enum_id = category_enum_id; - item.timestamp_ns = timestamp_ns; - item.stack_id = stack_id; - item.parent_stack_id = parent_stack_id; - item.correlation_id = correlation_id; - return item; -} - -template <> -inline size_t -get_size(const in_time_sample& item) -{ - return utility::get_size( - item.category_enum_id, item.track_name, - static_cast(item.timestamp_ns), item.event_metadata, - static_cast(item.stack_id), - static_cast(item.parent_stack_id), - static_cast(item.correlation_id), item.call_stack, item.line_info); -} + template + void serialize(Archive& archive) + { + archive(category_enum_id, track_name, timestamp_ns, event_metadata, stack_id, + parent_stack_id, correlation_id, call_stack, line_info); + } +}; struct pmc_event_with_sample : in_time_sample { @@ -533,54 +353,14 @@ struct pmc_event_with_sample : in_time_sample std::string_view pmc_info_name; double value; std::optional system_tid; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const pmc_event_with_sample& item) -{ - utility::store_value(buffer, item.category_enum_id, item.track_name, - static_cast(item.timestamp_ns), - item.event_metadata, static_cast(item.stack_id), - static_cast(item.parent_stack_id), - static_cast(item.correlation_id), item.call_stack, - item.line_info, item.device_id, item.device_type, - item.pmc_info_name, item.value, item.system_tid); -} - -template <> -inline pmc_event_with_sample -deserialize(std::uint8_t*& buffer) -{ - pmc_event_with_sample item; - size_t category_enum_id; - std::uint64_t timestamp_ns, stack_id, parent_stack_id, correlation_id; - utility::parse_value(buffer, category_enum_id, item.track_name, timestamp_ns, - item.event_metadata, stack_id, parent_stack_id, correlation_id, - item.call_stack, item.line_info, item.device_id, - item.device_type, item.pmc_info_name, item.value, - item.system_tid); - item.category_enum_id = category_enum_id; - item.timestamp_ns = timestamp_ns; - item.stack_id = stack_id; - item.parent_stack_id = parent_stack_id; - item.correlation_id = correlation_id; - return item; -} - -template <> -inline size_t -get_size(const pmc_event_with_sample& item) -{ - return utility::get_size( - item.category_enum_id, item.track_name, - static_cast(item.timestamp_ns), item.event_metadata, - static_cast(item.stack_id), - static_cast(item.parent_stack_id), - static_cast(item.correlation_id), item.call_stack, item.line_info, - item.device_id, item.device_type, item.pmc_info_name, item.value, - item.system_tid); -} + template + void serialize(Archive& archive) + { + in_time_sample::serialize(archive); + archive(device_id, device_type, pmc_info_name, value, system_tid); + } +}; struct backtrace_region_sample : cacheable_t { @@ -616,36 +396,14 @@ struct backtrace_region_sample : cacheable_t std::string_view call_stack; std::string_view line_info; std::string_view extdata; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const backtrace_region_sample& item) -{ - utility::store_value(buffer, item.type, item.thread_id, item.track_name, item.name, - item.start_timestamp, item.end_timestamp, item.category, - item.call_stack, item.line_info, item.extdata); -} - -template <> -inline backtrace_region_sample -deserialize(std::uint8_t*& buffer) -{ - backtrace_region_sample item; - utility::parse_value(buffer, item.type, item.thread_id, item.track_name, item.name, - item.start_timestamp, item.end_timestamp, item.category, - item.call_stack, item.line_info, item.extdata); - return item; -} - -template <> -inline size_t -get_size(const backtrace_region_sample& item) -{ - return utility::get_size(item.type, item.thread_id, item.track_name, item.name, - item.start_timestamp, item.end_timestamp, item.category, - item.call_stack, item.line_info, item.extdata); -} + template + void serialize(Archive& archive) + { + archive(type, thread_id, track_name, name, start_timestamp, end_timestamp, + category, call_stack, line_info, extdata); + } +}; struct kfd_sample : cacheable_t { @@ -688,41 +446,14 @@ struct kfd_sample : cacheable_t std::string_view pmc_info_name; double value; std::optional system_tid; -}; -template <> -inline void -serialize(std::uint8_t* buffer, const kfd_sample& item) -{ - utility::store_value(buffer, item.thread_id, item.name, item.start_timestamp, - item.end_timestamp, item.args_str, item.category, - item.track_name, item.event_metadata, item.device_id, - item.device_type, item.pmc_info_name, item.value, - item.system_tid); -} - -template <> -inline kfd_sample -deserialize(std::uint8_t*& buffer) -{ - kfd_sample item; - utility::parse_value(buffer, item.thread_id, item.name, item.start_timestamp, - item.end_timestamp, item.args_str, item.category, - item.track_name, item.event_metadata, item.device_id, - item.device_type, item.pmc_info_name, item.value, - item.system_tid); - return item; -} - -template <> -inline size_t -get_size(const kfd_sample& item) -{ - return utility::get_size(item.thread_id, item.name, item.start_timestamp, - item.end_timestamp, item.args_str, item.category, - item.track_name, item.event_metadata, item.device_id, - item.device_type, item.pmc_info_name, item.value, - item.system_tid); -} + template + void serialize(Archive& archive) + { + archive(thread_id, name, start_timestamp, end_timestamp, args_str, category, + track_name, event_metadata, device_id, device_type, pmc_info_name, value, + system_tid); + } +}; } // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp index 78197dd3c80a..ed19f5ee56e0 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp @@ -37,6 +37,12 @@ struct test_sample_1 : public rocprofsys::trace_cache::cacheable_t { return value == other.value && text == other.text; } + + template + void serialize(Archive& archive) + { + archive(value, text); + } }; struct test_sample_2 : public rocprofsys::trace_cache::cacheable_t @@ -72,6 +78,12 @@ struct test_sample_2 : public rocprofsys::trace_cache::cacheable_t return std::abs(data - other.data) < 1e-9; } + + template + void serialize(Archive& archive) + { + archive(data, sample_id); + } }; struct test_sample_3 : public rocprofsys::trace_cache::cacheable_t @@ -87,6 +99,12 @@ struct test_sample_3 : public rocprofsys::trace_cache::cacheable_t std::vector payload; bool operator==(const test_sample_3& other) const { return payload == other.payload; } + + template + void serialize(Archive& archive) + { + archive(payload); + } }; struct test_sample_4 : public rocprofsys::trace_cache::cacheable_t @@ -102,6 +120,12 @@ struct test_sample_4 : public rocprofsys::trace_cache::cacheable_t std::vector data; bool operator==(const test_sample_4& other) const { return data == other.data; } + + template + void serialize(Archive& archive) + { + archive(data); + } }; struct test_sample_5 : public rocprofsys::trace_cache::cacheable_t @@ -117,119 +141,10 @@ struct test_sample_5 : public rocprofsys::trace_cache::cacheable_t std::optional data; bool operator==(const test_sample_5& other) const { return data == other.data; } -}; - -template <> -inline void -rocprofsys::trace_cache::serialize(std::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(std::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(std::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(std::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(std::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(std::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); -} - -template <> -inline void -rocprofsys::trace_cache::serialize(std::uint8_t* buffer, const test_sample_4& item) -{ - rocprofsys::trace_cache::utility::store_value(buffer, item.data); -} - -template <> -inline test_sample_4 -rocprofsys::trace_cache::deserialize(std::uint8_t*& buffer) -{ - test_sample_4 result; - rocprofsys::trace_cache::utility::parse_value(buffer, result.data); - return result; -} - -template <> -inline size_t -rocprofsys::trace_cache::get_size(const test_sample_4& item) -{ - return rocprofsys::trace_cache::utility::get_size(item.data); -} - -template <> -inline void -rocprofsys::trace_cache::serialize(std::uint8_t* buffer, const test_sample_5& item) -{ - rocprofsys::trace_cache::utility::store_value(buffer, item.data); -} - -template <> -inline test_sample_5 -rocprofsys::trace_cache::deserialize(std::uint8_t*& buffer) -{ - test_sample_5 result; - rocprofsys::trace_cache::utility::parse_value(buffer, result.data); - return result; -} - -template <> -inline size_t -rocprofsys::trace_cache::get_size(const test_sample_5& item) -{ - return rocprofsys::trace_cache::utility::get_size(item.data); -} + template + void serialize(Archive& archive) + { + archive(data); + } +}; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp index 6c7f278fb529..2d1479aac99f 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #include "backends/amd_smi/gpu_types.hpp" +#include "core/trace_cache/archive.hpp" #include "core/trace_cache/sample_type.hpp" #include "library/pmc/collectors/cpu/sample.hpp" #include "library/pmc/collectors/cpu/types.hpp" diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp index 76f88c70fbbc..bbec8b9a775f 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_sample_type.cpp @@ -1,6 +1,7 @@ // Copyright (c) Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT +#include "core/trace_cache/archive.hpp" #include "core/trace_cache/sample_type.hpp" #include "library/pmc/collectors/hipfile/sample.hpp" #include "library/pmc/collectors/hipfile/types.hpp" diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index 61a56a09030f..9925993359d0 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #pragma once +#include "core/trace_cache/archive.hpp" #include "core/trace_cache/cache_type_traits.hpp" #include diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp index 70ed0ec58b54..8b3bf5b10b3d 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp @@ -43,6 +43,15 @@ struct sample : trace_cache::cacheable_t process_metrics process_data{}; std::vector freqs; // serialized cpu_id+freq pairs std::vector loads; // serialized cpu_id+load pairs + + template + void serialize(Archive& archive) + { + archive(enabled_metric.value, device_id, timestamp, process_data.page_rss, + process_data.virt_mem, process_data.peak_rss, + process_data.context_switches, process_data.page_faults, + process_data.user_mode_time, process_data.kernel_mode_time, freqs, loads); + } }; inline std::vector @@ -90,44 +99,6 @@ serialize_loads(const std::vector& cpu_data) namespace rocprofsys::trace_cache { -template <> -inline void -serialize(std::uint8_t* buffer, const pmc::collectors::cpu::sample& item) -{ - utility::store_value(buffer, static_cast(item.enabled_metric.value), - item.device_id, item.timestamp, item.process_data.page_rss, - item.process_data.virt_mem, item.process_data.peak_rss, - item.process_data.context_switches, - item.process_data.page_faults, item.process_data.user_mode_time, - item.process_data.kernel_mode_time, item.freqs, item.loads); -} - -template <> -inline pmc::collectors::cpu::sample -deserialize(std::uint8_t*& buffer) -{ - pmc::collectors::cpu::sample item; - utility::parse_value(buffer, item.enabled_metric.value, item.device_id, - item.timestamp, item.process_data.page_rss, - item.process_data.virt_mem, item.process_data.peak_rss, - item.process_data.context_switches, - item.process_data.page_faults, item.process_data.user_mode_time, - item.process_data.kernel_mode_time, item.freqs, item.loads); - return item; -} - -template <> -inline size_t -get_size(const pmc::collectors::cpu::sample& item) -{ - return utility::get_size( - static_cast(item.enabled_metric.value), item.device_id, - item.timestamp, item.process_data.page_rss, item.process_data.virt_mem, - item.process_data.peak_rss, item.process_data.context_switches, - item.process_data.page_faults, item.process_data.user_mode_time, - item.process_data.kernel_mode_time, item.freqs, item.loads); -} - /// @brief CPU PMC sample type alias using cpu_pmc_sample = pmc::collectors::cpu::sample; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/tests/test_sample.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/tests/test_sample.cpp index f6aa696d35f7..2463046bbcc5 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/tests/test_sample.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/tests/test_sample.cpp @@ -1,6 +1,7 @@ // Copyright (c) Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT +#include "core/trace_cache/archive.hpp" #include "library/pmc/collectors/cpu/sample.hpp" #include diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp index 0750d87403ed..688dbf556a58 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp @@ -35,6 +35,23 @@ struct sample : trace_cache::cacheable_t std::uint32_t device_id = 0; std::uint64_t timestamp = 0; metrics metric_values{}; + + template + void serialize(Archive& archive) + { + archive(enabled_metric.value, device_id, timestamp, + metric_values.average_socket_power, metric_values.current_socket_power, + metric_values.memory_usage, metric_values.hotspot_temperature, + metric_values.edge_temperature, metric_values.gfx_activity, + metric_values.umc_activity, metric_values.mm_activity, + metric_values.xcp_stats, metric_values.vcn_activity, + metric_values.jpeg_activity, metric_values.xgmi.link.width, + metric_values.xgmi.link.speed, metric_values.xgmi.data_acc.read, + metric_values.xgmi.data_acc.write, metric_values.pcie.link.width, + metric_values.pcie.link.speed, metric_values.pcie.bandwidth.acc, + metric_values.pcie.bandwidth.inst, metric_values.sdma_usage, + metric_values.gfx_clock_mhz, metric_values.mem_clock_mhz); + } }; } // namespace rocprofsys::pmc::collectors::gpu @@ -42,66 +59,6 @@ struct sample : trace_cache::cacheable_t namespace rocprofsys::trace_cache { -template <> -inline void -serialize(std::uint8_t* buffer, const pmc::collectors::gpu::sample& item) -{ - utility::store_value( - buffer, static_cast(item.enabled_metric.value), item.device_id, - item.timestamp, item.metric_values.average_socket_power, - item.metric_values.current_socket_power, item.metric_values.memory_usage, - item.metric_values.hotspot_temperature, item.metric_values.edge_temperature, - item.metric_values.gfx_activity, item.metric_values.umc_activity, - item.metric_values.mm_activity, item.metric_values.xcp_stats, - item.metric_values.vcn_activity, item.metric_values.jpeg_activity, - item.metric_values.xgmi.link.width, item.metric_values.xgmi.link.speed, - item.metric_values.xgmi.data_acc.read, item.metric_values.xgmi.data_acc.write, - item.metric_values.pcie.link.width, item.metric_values.pcie.link.speed, - item.metric_values.pcie.bandwidth.acc, item.metric_values.pcie.bandwidth.inst, - item.metric_values.sdma_usage, item.metric_values.gfx_clock_mhz, - item.metric_values.mem_clock_mhz); -} - -template <> -inline pmc::collectors::gpu::sample -deserialize(std::uint8_t*& buffer) -{ - pmc::collectors::gpu::sample item; - utility::parse_value( - buffer, item.enabled_metric.value, item.device_id, item.timestamp, - item.metric_values.average_socket_power, item.metric_values.current_socket_power, - item.metric_values.memory_usage, item.metric_values.hotspot_temperature, - item.metric_values.edge_temperature, item.metric_values.gfx_activity, - item.metric_values.umc_activity, item.metric_values.mm_activity, - item.metric_values.xcp_stats, item.metric_values.vcn_activity, - item.metric_values.jpeg_activity, item.metric_values.xgmi.link.width, - item.metric_values.xgmi.link.speed, item.metric_values.xgmi.data_acc.read, - item.metric_values.xgmi.data_acc.write, item.metric_values.pcie.link.width, - item.metric_values.pcie.link.speed, item.metric_values.pcie.bandwidth.acc, - item.metric_values.pcie.bandwidth.inst, item.metric_values.sdma_usage, - item.metric_values.gfx_clock_mhz, item.metric_values.mem_clock_mhz); - return item; -} - -template <> -inline size_t -get_size(const pmc::collectors::gpu::sample& item) -{ - return utility::get_size( - item.enabled_metric.value, item.device_id, item.timestamp, - item.metric_values.average_socket_power, item.metric_values.current_socket_power, - item.metric_values.memory_usage, item.metric_values.hotspot_temperature, - item.metric_values.edge_temperature, item.metric_values.gfx_activity, - item.metric_values.umc_activity, item.metric_values.mm_activity, - item.metric_values.xcp_stats, item.metric_values.vcn_activity, - item.metric_values.jpeg_activity, item.metric_values.xgmi.link.width, - item.metric_values.xgmi.link.speed, item.metric_values.xgmi.data_acc.read, - item.metric_values.xgmi.data_acc.write, item.metric_values.pcie.link.width, - item.metric_values.pcie.link.speed, item.metric_values.pcie.bandwidth.acc, - item.metric_values.pcie.bandwidth.inst, item.metric_values.sdma_usage, - item.metric_values.gfx_clock_mhz, item.metric_values.mem_clock_mhz); -} - /// @brief GPU PMC sample type alias using gpu_pmc_sample = pmc::collectors::gpu::sample; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp index 25900aff85b4..91f76d99e3a2 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp @@ -38,6 +38,21 @@ struct sample : trace_cache::cacheable_t std::uint32_t device_id = 0; std::uint64_t timestamp = 0; std::vector entries; + + template + void serialize(Archive& archive) + { + auto count = static_cast(entries.size()); + archive(device_id, timestamp, count); + if constexpr(Archive::k_is_loading) + { + entries.resize(count); + } + for(std::size_t idx = 0; idx < entries.size(); ++idx) + { + archive(entries[idx].counter_id, entries[idx].value); + } + } }; } // namespace rocprofsys::pmc::collectors::gpu_perf_counter @@ -48,48 +63,4 @@ namespace rocprofsys::trace_cache /// @brief SDK PMC sample type alias using gpu_perf_counter_sample = pmc::collectors::gpu_perf_counter::sample; -template <> -inline void -serialize(std::uint8_t* buffer, const pmc::collectors::gpu_perf_counter::sample& item) -{ - size_t pos = 0; - const auto num_entries = static_cast(item.entries.size()); - utility::store_value(item.device_id, buffer, pos); - utility::store_value(item.timestamp, buffer, pos); - utility::store_value(num_entries, buffer, pos); - for(const auto& entry : item.entries) - { - utility::store_value(entry.counter_id, buffer, pos); - utility::store_value(entry.value, buffer, pos); - } -} - -template <> -inline pmc::collectors::gpu_perf_counter::sample -deserialize(std::uint8_t*& buffer) -{ - pmc::collectors::gpu_perf_counter::sample item; - std::uint32_t num_entries = 0; - utility::parse_value(buffer, item.device_id, item.timestamp, num_entries); - item.entries.resize(num_entries); - for(auto& entry : item.entries) - { - utility::parse_value(buffer, entry.counter_id, entry.value); - } - return item; -} - -template <> -inline size_t -get_size(const pmc::collectors::gpu_perf_counter::sample& item) -{ - size_t total_size = utility::get_size( - item.device_id, item.timestamp, static_cast(item.entries.size())); - for(const auto& entry : item.entries) - { - total_size += utility::get_size(entry.counter_id, entry.value); - } - return total_size; -} - } // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp index 53c3addcb528..7a61b9da4512 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp @@ -1,6 +1,7 @@ // Copyright (c) Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT +#include "core/trace_cache/archive.hpp" #include "library/pmc/collectors/gpu_perf_counter/sample.hpp" #include diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp index 2000a5abe8e3..57a5f9f55d42 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp @@ -42,6 +42,22 @@ struct sample : trace_cache::cacheable_t std::uint32_t device_id = 0; std::uint64_t timestamp = 0; metrics metric_values{}; + + template + void serialize(Archive& archive) + { + // metrics::query_failed is deliberately not carried: cache_policy::store_sample + // drops a failed query before it reaches the buffer, so every stored sample has + // it false and the default-constructed value round-trips correctly. + archive(enabled_metric.value, device_id, timestamp, metric_values.read_bytes, + metric_values.write_bytes, metric_values.read_ops, + metric_values.write_ops, metric_values.fastpath_reads, + metric_values.fastpath_writes, metric_values.fallback_reads, + metric_values.fallback_writes, metric_values.unaligned_reads, + metric_values.unaligned_writes, metric_values.read_errors, + metric_values.write_errors, metric_values.read_bandwidth, + metric_values.write_bandwidth); + } }; } // namespace rocprofsys::pmc::collectors::hipfile @@ -49,58 +65,6 @@ struct sample : trace_cache::cacheable_t namespace rocprofsys::trace_cache { -// metrics::query_failed is deliberately not carried: cache_policy::store_sample drops a -// failed query before it reaches the buffer, so every stored sample has it false and the -// default-constructed value round-trips correctly. -// Field order after timestamp is metrics member order, excluding query_failed (dropped -// before store). serialize, deserialize, and get_size must list the same fields. -template <> -inline void -serialize(std::uint8_t* buffer, const pmc::collectors::hipfile::sample& item) -{ - utility::store_value( - buffer, static_cast(item.enabled_metric.value), item.device_id, - item.timestamp, item.metric_values.read_bytes, item.metric_values.write_bytes, - item.metric_values.read_ops, item.metric_values.write_ops, - item.metric_values.fastpath_reads, item.metric_values.fastpath_writes, - item.metric_values.fallback_reads, item.metric_values.fallback_writes, - item.metric_values.unaligned_reads, item.metric_values.unaligned_writes, - item.metric_values.read_errors, item.metric_values.write_errors, - item.metric_values.read_bandwidth, item.metric_values.write_bandwidth); -} - -template <> -inline pmc::collectors::hipfile::sample -deserialize(std::uint8_t*& buffer) -{ - pmc::collectors::hipfile::sample item; - utility::parse_value( - buffer, item.enabled_metric.value, item.device_id, item.timestamp, - item.metric_values.read_bytes, item.metric_values.write_bytes, - item.metric_values.read_ops, item.metric_values.write_ops, - item.metric_values.fastpath_reads, item.metric_values.fastpath_writes, - item.metric_values.fallback_reads, item.metric_values.fallback_writes, - item.metric_values.unaligned_reads, item.metric_values.unaligned_writes, - item.metric_values.read_errors, item.metric_values.write_errors, - item.metric_values.read_bandwidth, item.metric_values.write_bandwidth); - return item; -} - -template <> -inline size_t -get_size(const pmc::collectors::hipfile::sample& item) -{ - return utility::get_size( - item.enabled_metric.value, item.device_id, item.timestamp, - item.metric_values.read_bytes, item.metric_values.write_bytes, - item.metric_values.read_ops, item.metric_values.write_ops, - item.metric_values.fastpath_reads, item.metric_values.fastpath_writes, - item.metric_values.fallback_reads, item.metric_values.fallback_writes, - item.metric_values.unaligned_reads, item.metric_values.unaligned_writes, - item.metric_values.read_errors, item.metric_values.write_errors, - item.metric_values.read_bandwidth, item.metric_values.write_bandwidth); -} - /// @brief hipFile PMC sample type alias using hipfile_pmc_sample = pmc::collectors::hipfile::sample; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp index 81e5dfd2465b..0e2a0ddf9999 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp @@ -40,6 +40,22 @@ struct sample : trace_cache::cacheable_t std::string device_name; std::uint64_t timestamp; metrics metric_values; + + template + void serialize(Archive& archive) + { + std::string_view name_view = device_name; + archive(enabled_metric.value, device_id, name_view, timestamp, + metric_values.rx_rdma_ucast_bytes, metric_values.tx_rdma_ucast_bytes, + metric_values.rx_rdma_ucast_pkts, metric_values.tx_rdma_ucast_pkts, + metric_values.rx_rdma_cnp_pkts, metric_values.tx_rdma_cnp_pkts, + metric_values.tx_rdma_ack_timeout, metric_values.resp_tx_pkt_seq_err, + metric_values.req_rx_pkt_seq_err, metric_values.req_rx_impl_nak_seq_err); + if constexpr(Archive::k_is_loading) + { + device_name = std::string(name_view); + } + } }; } // namespace rocprofsys::pmc::collectors::nic @@ -50,51 +66,4 @@ namespace rocprofsys::trace_cache /// @brief AINIC PMC sample type alias using ainic_pmc_sample = pmc::collectors::nic::sample; -template <> -inline void -serialize(std::uint8_t* buffer, const pmc::collectors::nic::sample& item) -{ - utility::store_value( - buffer, static_cast(item.enabled_metric.value), item.device_id, - std::string_view(item.device_name), item.timestamp, - item.metric_values.rx_rdma_ucast_bytes, item.metric_values.tx_rdma_ucast_bytes, - item.metric_values.rx_rdma_ucast_pkts, item.metric_values.tx_rdma_ucast_pkts, - item.metric_values.rx_rdma_cnp_pkts, item.metric_values.tx_rdma_cnp_pkts, - item.metric_values.tx_rdma_ack_timeout, item.metric_values.resp_tx_pkt_seq_err, - item.metric_values.req_rx_pkt_seq_err, - item.metric_values.req_rx_impl_nak_seq_err); -} - -template <> -inline pmc::collectors::nic::sample -deserialize(std::uint8_t*& buffer) -{ - pmc::collectors::nic::sample item; - std::string_view device_name_view; - utility::parse_value( - buffer, item.enabled_metric.value, item.device_id, device_name_view, - item.timestamp, item.metric_values.rx_rdma_ucast_bytes, - item.metric_values.tx_rdma_ucast_bytes, item.metric_values.rx_rdma_ucast_pkts, - item.metric_values.tx_rdma_ucast_pkts, item.metric_values.rx_rdma_cnp_pkts, - item.metric_values.tx_rdma_cnp_pkts, item.metric_values.tx_rdma_ack_timeout, - item.metric_values.resp_tx_pkt_seq_err, item.metric_values.req_rx_pkt_seq_err, - item.metric_values.req_rx_impl_nak_seq_err); - item.device_name = std::string(device_name_view); - return item; -} - -template <> -inline size_t -get_size(const pmc::collectors::nic::sample& item) -{ - return utility::get_size( - item.enabled_metric.value, item.device_id, std::string_view(item.device_name), - item.timestamp, item.metric_values.rx_rdma_ucast_bytes, - item.metric_values.tx_rdma_ucast_bytes, item.metric_values.rx_rdma_ucast_pkts, - item.metric_values.tx_rdma_ucast_pkts, item.metric_values.rx_rdma_cnp_pkts, - item.metric_values.tx_rdma_cnp_pkts, item.metric_values.tx_rdma_ack_timeout, - item.metric_values.resp_tx_pkt_seq_err, item.metric_values.req_rx_pkt_seq_err, - item.metric_values.req_rx_impl_nak_seq_err); -} - } // namespace rocprofsys::trace_cache From cd576bf14e8b8334e649f6346d51228886309f48 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 18:06:19 +0200 Subject: [PATCH 04/25] refactor(rocprofiler-systems): let nested PMC metric types serialize themselves The archives now recurse into any value that has a member serialize(Archive&), so a nested struct lists its own fields once instead of every record spelling out its members. - gpu::metrics, nic::metrics, hipfile::metrics and cpu::process_metrics get a serialize member with their fields in the existing byte order; the enabled_metrics unions serialize their value. hipfile::metrics keeps the comment on why query_failed is not stored. - The cpu, gpu, nic and hipfile records become archive(enabled_metric, device_id, timestamp, metric_values). - Containers keep their existing encoding; the archives do not recurse into their elements. The byte format is unchanged: the golden record format tests pass without modification. --- .../source/lib/backends/amd_smi/gpu_types.hpp | 11 ++++ .../source/lib/core/trace_cache/archive.hpp | 45 +++++++++++++- .../core/trace_cache/tests/test_archive.cpp | 59 +++++++++++++++++++ .../library/pmc/collectors/cpu/sample.hpp | 5 +- .../library/pmc/collectors/cpu/types.hpp | 13 ++++ .../library/pmc/collectors/gpu/sample.hpp | 13 +--- .../library/pmc/collectors/gpu/types.hpp | 6 ++ .../library/pmc/collectors/hipfile/sample.hpp | 12 +--- .../library/pmc/collectors/hipfile/types.hpp | 18 ++++++ .../library/pmc/collectors/nic/sample.hpp | 7 +-- .../library/pmc/collectors/nic/types.hpp | 15 +++++ 11 files changed, 168 insertions(+), 36 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp b/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp index 82b978df61b8..09dd9a126151 100644 --- a/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp +++ b/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp @@ -88,6 +88,17 @@ struct metrics std::uint32_t gfx_clock_mhz = 0; // current_gfxclk (MHz) std::uint32_t mem_clock_mhz = 0; // current_uclk (MHz) + + template + void serialize(Archive& archive) + { + archive(average_socket_power, current_socket_power, memory_usage, + hotspot_temperature, edge_temperature, gfx_activity, umc_activity, + mm_activity, xcp_stats, vcn_activity, jpeg_activity, xgmi.link.width, + xgmi.link.speed, xgmi.data_acc.read, xgmi.data_acc.write, pcie.link.width, + pcie.link.speed, pcie.bandwidth.acc, pcie.bandwidth.inst, sdma_usage, + gfx_clock_mhz, mem_clock_mhz); + } }; template diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 2723afec29b0..42604161e594 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -20,6 +20,11 @@ namespace rocprofsys::trace_cache static_assert(std::is_same_v, "trace cache format assumes size_t is std::uint64_t"); +/** A value (a record or a nested struct) that lists its own fields in serialize(). */ +template +concept archive_serializable = + requires(Type& value, Archive& archive) { value.serialize(archive); }; + /** Sums the encoded size of the fields a record passes to it. */ struct binary_size_archive { @@ -29,7 +34,22 @@ struct binary_size_archive template void operator()(const Types&... values) { - size += (std::size_t{ 0 } + ... + utility::get_size(values)); + (size_one(values), ...); + } + +private: + template + void size_one(const Type& value) + { + if constexpr(archive_serializable) + { + // Recurse into a nested struct; it is only read through serialize(). + const_cast(value).serialize(*this); + } + else + { + size += utility::get_size(value); + } } }; @@ -43,7 +63,22 @@ struct binary_write_archive template void operator()(const Types&... values) { - (utility::store_value(values, buffer, position), ...); + (write_one(values), ...); + } + +private: + template + void write_one(const Type& value) + { + if constexpr(archive_serializable) + { + // Recurse into a nested struct; it is only read through serialize(). + const_cast(value).serialize(*this); + } + else + { + utility::store_value(value, buffer, position); + } } }; @@ -66,7 +101,11 @@ struct binary_read_archive template void read_one(Type& value) { - if constexpr(std::is_same_v>) + if constexpr(archive_serializable) + { + value.serialize(*this); + } + else if constexpr(std::is_same_v>) { std::size_t byte_count = 0; std::memcpy(&byte_count, cursor, sizeof(std::size_t)); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp index 108b493e3ee7..476d92be98fb 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -32,6 +32,7 @@ enum class test_record_id : std::uint32_t span_test = 5, derived_test = 6, counted_entries_test = 7, + nested_test = 8, }; struct scalar_record @@ -158,6 +159,33 @@ struct counted_entries_record } }; +struct inner_values +{ + std::uint32_t alpha = 0; + double beta = 0.0; + + template + void serialize(Archive& archive) + { + archive(alpha, beta); + } +}; + +struct nested_record +{ + static constexpr auto type_identifier = test_record_id::nested_test; + + std::uint64_t head = 0; + inner_values inner; + std::uint16_t tail = 0; + + template + void serialize(Archive& archive) + { + archive(head, inner, tail); + } +}; + } // namespace constexpr std::size_t k_buffer_size = 4096; @@ -354,6 +382,37 @@ TEST(archive, is_loading_branch_for_counted_entries) EXPECT_EQ(read_buffer - buffer.data(), static_cast(size_empty)); } +TEST(archive, nested_struct_serializes_itself) +{ + std::array buffer{}; + nested_record orig; + orig.head = 1111ULL; + orig.inner.alpha = 2222U; + orig.inner.beta = 3.33; + orig.tail = 4444U; + + const std::size_t size = record_size(orig); + record_write(buffer.data(), orig); + + std::uint8_t* read_buffer = buffer.data(); + const nested_record read = record_read(read_buffer); + + EXPECT_EQ(read.head, 1111ULL); + EXPECT_EQ(read.inner.alpha, 2222U); + EXPECT_DOUBLE_EQ(read.inner.beta, 3.33); + EXPECT_EQ(read.tail, 4444U); + EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); + + EXPECT_EQ(size, + utility::get_size(orig.head, orig.inner.alpha, orig.inner.beta, orig.tail)); + + std::array other_buffer{}; + utility::store_value(other_buffer.data(), orig.head, orig.inner.alpha, + orig.inner.beta, orig.tail); + + EXPECT_EQ(std::memcmp(buffer.data(), other_buffer.data(), size), 0); +} + } // namespace rocprofsys::trace_cache // NOLINTEND(readability-magic-numbers) diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp index 8b3bf5b10b3d..049a0d4ee545 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp @@ -47,10 +47,7 @@ struct sample : trace_cache::cacheable_t template void serialize(Archive& archive) { - archive(enabled_metric.value, device_id, timestamp, process_data.page_rss, - process_data.virt_mem, process_data.peak_rss, - process_data.context_switches, process_data.page_faults, - process_data.user_mode_time, process_data.kernel_mode_time, freqs, loads); + archive(enabled_metric, device_id, timestamp, process_data, freqs, loads); } }; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/types.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/types.hpp index 1d50045a3a7c..f0bdc13ea617 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/types.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/types.hpp @@ -42,6 +42,12 @@ union enabled_metrics std::uint32_t kernel_time : 1; } bits; std::uint32_t value = 0; + + template + void serialize(Archive& archive) + { + archive(value); + } }; /// All 9 CPU metrics enabled (bits 0-8). @@ -71,6 +77,13 @@ struct process_metrics std::int64_t page_faults = 0; // count (major + minor) std::int64_t user_mode_time = 0; // microseconds std::int64_t kernel_mode_time = 0; // microseconds + + template + void serialize(Archive& archive) + { + archive(page_rss, virt_mem, peak_rss, context_switches, page_faults, + user_mode_time, kernel_mode_time); + } }; /** diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp index 688dbf556a58..17b96a05c911 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp @@ -39,18 +39,7 @@ struct sample : trace_cache::cacheable_t template void serialize(Archive& archive) { - archive(enabled_metric.value, device_id, timestamp, - metric_values.average_socket_power, metric_values.current_socket_power, - metric_values.memory_usage, metric_values.hotspot_temperature, - metric_values.edge_temperature, metric_values.gfx_activity, - metric_values.umc_activity, metric_values.mm_activity, - metric_values.xcp_stats, metric_values.vcn_activity, - metric_values.jpeg_activity, metric_values.xgmi.link.width, - metric_values.xgmi.link.speed, metric_values.xgmi.data_acc.read, - metric_values.xgmi.data_acc.write, metric_values.pcie.link.width, - metric_values.pcie.link.speed, metric_values.pcie.bandwidth.acc, - metric_values.pcie.bandwidth.inst, metric_values.sdma_usage, - metric_values.gfx_clock_mhz, metric_values.mem_clock_mhz); + archive(enabled_metric, device_id, timestamp, metric_values); } }; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/types.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/types.hpp index 083daaee212e..f9b39c5ed023 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/types.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/types.hpp @@ -73,6 +73,12 @@ union enabled_metrics std::uint32_t mem_clock : 1; // current_uclk (MHz) } bits; std::uint32_t value = 0; + + template + void serialize(Archive& archive) + { + archive(value); + } }; // Socket power: prefer the instantaneous "current" reading, falling back to the diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp index 57a5f9f55d42..01045fe72ed6 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp @@ -46,17 +46,7 @@ struct sample : trace_cache::cacheable_t template void serialize(Archive& archive) { - // metrics::query_failed is deliberately not carried: cache_policy::store_sample - // drops a failed query before it reaches the buffer, so every stored sample has - // it false and the default-constructed value round-trips correctly. - archive(enabled_metric.value, device_id, timestamp, metric_values.read_bytes, - metric_values.write_bytes, metric_values.read_ops, - metric_values.write_ops, metric_values.fastpath_reads, - metric_values.fastpath_writes, metric_values.fallback_reads, - metric_values.fallback_writes, metric_values.unaligned_reads, - metric_values.unaligned_writes, metric_values.read_errors, - metric_values.write_errors, metric_values.read_bandwidth, - metric_values.write_bandwidth); + archive(enabled_metric, device_id, timestamp, metric_values); } }; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/types.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/types.hpp index 5dd6ce5f1c75..6110b3e210bc 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/types.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/types.hpp @@ -53,6 +53,12 @@ union enabled_metrics std::uint32_t write_bandwidth : 1; } bits; std::uint32_t value = 0; + + template + void serialize(Archive& archive) + { + archive(value); + } }; /** @@ -93,6 +99,18 @@ struct metrics /// Default false, which is what a value-initialized `metrics{}` needs: the paused /// collector emits exactly that to drop the counter tracks to zero. bool query_failed = false; + + template + void serialize(Archive& archive) + { + // query_failed is deliberately not carried: cache_policy::store_sample drops + // a failed query before it reaches the buffer, so every stored sample has it + // false and the default-constructed value round-trips correctly. + archive(read_bytes, write_bytes, read_ops, write_ops, fastpath_reads, + fastpath_writes, fallback_reads, fallback_writes, unaligned_reads, + unaligned_writes, read_errors, write_errors, read_bandwidth, + write_bandwidth); + } }; /** diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp index 0e2a0ddf9999..7d4646216961 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp @@ -45,12 +45,7 @@ struct sample : trace_cache::cacheable_t void serialize(Archive& archive) { std::string_view name_view = device_name; - archive(enabled_metric.value, device_id, name_view, timestamp, - metric_values.rx_rdma_ucast_bytes, metric_values.tx_rdma_ucast_bytes, - metric_values.rx_rdma_ucast_pkts, metric_values.tx_rdma_ucast_pkts, - metric_values.rx_rdma_cnp_pkts, metric_values.tx_rdma_cnp_pkts, - metric_values.tx_rdma_ack_timeout, metric_values.resp_tx_pkt_seq_err, - metric_values.req_rx_pkt_seq_err, metric_values.req_rx_impl_nak_seq_err); + archive(enabled_metric, device_id, name_view, timestamp, metric_values); if constexpr(Archive::k_is_loading) { device_name = std::string(name_view); diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/types.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/types.hpp index f6b19856c552..0a67140a4c82 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/types.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/types.hpp @@ -51,6 +51,12 @@ union enabled_metrics std::uint32_t req_rx_impl_nak_seq_err : 1; } bits; std::uint32_t value = 0; + + template + void serialize(Archive& archive) + { + archive(value); + } }; // Bit-mask for all NIC RDMA metrics enabled @@ -74,6 +80,15 @@ struct metrics std::uint64_t resp_tx_pkt_seq_err = 0; std::uint64_t req_rx_pkt_seq_err = 0; std::uint64_t req_rx_impl_nak_seq_err = 0; + + template + void serialize(Archive& archive) + { + archive(rx_rdma_ucast_bytes, tx_rdma_ucast_bytes, rx_rdma_ucast_pkts, + tx_rdma_ucast_pkts, rx_rdma_cnp_pkts, tx_rdma_cnp_pkts, + tx_rdma_ack_timeout, resp_tx_pkt_seq_err, req_rx_pkt_seq_err, + req_rx_impl_nak_seq_err); + } }; } // namespace rocprofsys::pmc::collectors::nic From 475a150d7a9d0af9df4dc823914e6a37b1068112 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 18:09:12 +0200 Subject: [PATCH 05/25] perf(rocprofiler-systems): keep trace cache archive state in registers A benchmark of every record type against develop showed the archives slower than the hand-written code they replaced (serialize median +72%, deserialize +128%; e.g. kernel_dispatch_sample deserialize 1.2 -> 12.5 ns). Two causes, both visible in the generated code: - GCC at -O2 did not inline the variadic archive operator(), so every field was passed by address to an out-of-line call. - The archive's buffer/position/cursor were accessed through a reference inside the record's serialize(). A write through the uint8_t buffer may alias them, so they were reloaded and stored back after every field and adjacent copies could not be merged. Fix, in archive.hpp only: - operator(), the per-value helpers and the generic get_size / serialize / deserialize are ROCPROFSYS_INLINE. - operator() works on local copies of buffer/position/cursor and writes them back once (synced around a nested serialize()). - record_size / record_write / record_read are __attribute__((flatten)), so each record type gets one function with its serialize() inlined, like the old specializations. kernel_dispatch_sample now reads in 23 instructions with merged 16-byte copies (develop: 77) and writes in 38 (develop: 39). Medians against develop: serialize -1.8%, deserialize +0.6%. Byte format unchanged. Also use the deserialize result in a gpu_perf_counter test that ignored it (deserialize is [[nodiscard]] since the archive conversion). --- .../source/lib/core/trace_cache/archive.hpp | 70 +++++++++++++------ .../gpu_perf_counter/tests/test_sample.cpp | 7 +- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 42604161e594..93b91bfc20fc 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -3,6 +3,7 @@ #pragma once +#include "common/defines.h" #include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/cacheable.hpp" @@ -32,14 +33,14 @@ struct binary_size_archive std::size_t size = 0; template - void operator()(const Types&... values) + ROCPROFSYS_INLINE void operator()(const Types&... values) { (size_one(values), ...); } private: template - void size_one(const Type& value) + ROCPROFSYS_INLINE void size_one(const Type& value) { if constexpr(archive_serializable) { @@ -61,23 +62,33 @@ struct binary_write_archive std::size_t position = 0; template - void operator()(const Types&... values) + ROCPROFSYS_INLINE void operator()(const Types&... values) { - (write_one(values), ...); + // Locals, not members: a write through the byte buffer may alias any member, so + // members would be reloaded after every field and adjacent copies not merged. + std::uint8_t* const out = buffer; + std::size_t pos = position; + (write_one(values, out, pos), ...); + position = pos; } private: template - void write_one(const Type& value) + ROCPROFSYS_INLINE void write_one(const Type& value, std::uint8_t* out, + std::size_t& pos) { if constexpr(archive_serializable) { - // Recurse into a nested struct; it is only read through serialize(). + // Recurse into a nested struct; it is only read through serialize(). The + // nested call works on the members, so hand the position over and take it + // back. + position = pos; const_cast(value).serialize(*this); + pos = position; } else { - utility::store_value(value, buffer, position); + utility::store_value(value, out, pos); } } }; @@ -92,30 +103,37 @@ struct binary_read_archive std::uint8_t* cursor = nullptr; template - void operator()(Types&... values) + ROCPROFSYS_INLINE void operator()(Types&... values) { - (read_one(values), ...); + // Local cursor for the same reason as in binary_write_archive. + std::uint8_t* cur = cursor; + (read_one(values, cur), ...); + cursor = cur; } private: template - void read_one(Type& value) + ROCPROFSYS_INLINE void read_one(Type& value, std::uint8_t*& cur) { if constexpr(archive_serializable) { + // The nested call works on the member, so hand the cursor over and take it + // back. + cursor = cur; value.serialize(*this); + cur = cursor; } else if constexpr(std::is_same_v>) { std::size_t byte_count = 0; - std::memcpy(&byte_count, cursor, sizeof(std::size_t)); - cursor += sizeof(std::size_t); - value = { reinterpret_cast(cursor), byte_count }; - cursor += byte_count; + std::memcpy(&byte_count, cur, sizeof(std::size_t)); + cur += sizeof(std::size_t); + value = { reinterpret_cast(cur), byte_count }; + cur += byte_count; } else { - utility::parse_value(cursor, value); + utility::parse_value(cur, value); } } }; @@ -134,9 +152,15 @@ concept archive_record = // serialize() is one non-const member shared by all archives. The size and write archives // only read through it, so the const_cast is safe (cereal's output archives do the same). +// +// flatten inlines everything these functions call, including the record's own +// serialize(): without it GCC keeps that member out of line and copies the fields one by +// one through memory. They are not ROCPROFSYS_INLINE on purpose: flatten is lost once the +// function is itself inlined, so each record type gets one fully inlined function, as the +// hand-written specializations had before. template -[[nodiscard]] std::size_t -record_size(const T& record) +[[nodiscard]] __attribute__((flatten)) std::size_t + record_size(const T& record) { binary_size_archive archive; const_cast(record).serialize(archive); @@ -144,7 +168,7 @@ record_size(const T& record) } template -void +__attribute__((flatten)) void record_write(std::uint8_t* buffer, const T& record) { binary_write_archive archive{ .buffer = buffer, .position = 0 }; @@ -152,7 +176,7 @@ record_write(std::uint8_t* buffer, const T& record) } template -[[nodiscard]] T +[[nodiscard]] __attribute__((flatten)) T record_read(std::uint8_t*& buffer) { T record{}; @@ -164,15 +188,15 @@ record_read(std::uint8_t*& buffer) /** Encoded size of a record. One definition for every record type. */ template -[[nodiscard]] std::size_t -get_size(const T& record) +[[nodiscard]] ROCPROFSYS_INLINE std::size_t + get_size(const T& record) { return record_size(record); } /** Writes a record into a buffer with room for get_size(record) bytes. */ template -void +ROCPROFSYS_INLINE void serialize(std::uint8_t* buffer, const T& record) { record_write(buffer, record); @@ -180,7 +204,7 @@ serialize(std::uint8_t* buffer, const T& record) /** Reads a record and advances the buffer past it. */ template -[[nodiscard]] T +[[nodiscard]] ROCPROFSYS_INLINE T deserialize(std::uint8_t*& buffer) { return record_read(buffer); diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp index 7a61b9da4512..052476a43a3a 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp @@ -112,11 +112,12 @@ TEST_F(SdkPmcSampleTest, GetSizeMatchesSerializedBytes) const size_t computed_size = get_size(original); serialize(buffer.data(), original); - std::uint8_t* ptr = buffer.data(); - deserialize(ptr); - const size_t bytes_consumed = static_cast(ptr - buffer.data()); + std::uint8_t* ptr = buffer.data(); + const sample deserialized = deserialize(ptr); + const size_t bytes_consumed = static_cast(ptr - buffer.data()); EXPECT_EQ(bytes_consumed, computed_size); + EXPECT_EQ(deserialized.entries.size(), original.entries.size()); } TEST_F(SdkPmcSampleTest, DeserializePreservesBufferPointerAdvancement) From c7a43bf33ac3feed52c5a250950ed924ed30d95d Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 21:39:48 +0200 Subject: [PATCH 06/25] perf(rocprofiler-systems): iterate gpu_perf_counter entries with a range-for The index loop in gpu_perf_counter::sample::serialize() re-read the vector's begin/end and recomputed size() after every entry, because a write through the uint8_t output buffer may alias them. A range-for reads them once; the generated write loop is now the same as develop's (64 entries: serialize 22.8 -> 14.3 ns, develop 14.2; deserialize 42.3 -> 32.0 ns, develop 48.0). Byte format unchanged. --- .../library/pmc/collectors/gpu_perf_counter/sample.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp index 91f76d99e3a2..6b8939c6ae4c 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp @@ -48,9 +48,11 @@ struct sample : trace_cache::cacheable_t { entries.resize(count); } - for(std::size_t idx = 0; idx < entries.size(); ++idx) + // Range-for: begin/end are read once. An index loop re-reads them after every + // entry, because a write through the uint8_t buffer may alias the vector. + for(auto& entry : entries) { - archive(entries[idx].counter_id, entries[idx].value); + archive(entry.counter_id, entry.value); } } }; From a7b7600e9b161a8514614cb57ceeedf3341dbd7d Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 22:07:01 +0200 Subject: [PATCH 07/25] perf(rocprofiler-systems): default-initialize the record read from the cache record_read() value-initialized the record (T record{}). Zeroing it first kept the whole record in memory instead of letting the compiler fill the destination directly, and the copy into its final place (the variant built by type_registry) then read 16-byte blocks over the 8-byte stores that had just filled it, stalling store forwarding once per string_view. Default-initialize it instead, as the hand-written deserializers did: every stored field is read right after, and fields that are not stored keep their member initializers. On the type_registry read path (ns per record, develop -> before -> after): memory_allocate 10.5 -> 17.9 -> 8.7, backtrace_region 11.1 -> 19.1 -> 9.6, pmc_event_with 12.1 -> 23.6 -> 13.1, kfd 11.8 -> 34.0 -> 14.2; the other types are unchanged and faster than develop. --- .../source/lib/core/trace_cache/archive.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 93b91bfc20fc..7d0b62ef8c98 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -179,7 +179,11 @@ template [[nodiscard]] __attribute__((flatten)) T record_read(std::uint8_t*& buffer) { - T record{}; + // Default-initialized, not value-initialized: zeroing the whole record first keeps it + // in memory, and the copy into its final place (a variant) then stalls on the 8-byte + // stores. Every stored field is read below; the others keep their member + // initializers. + T record; binary_read_archive archive{ .cursor = buffer }; record.serialize(archive); buffer = archive.cursor; From 0ca50c7e567477ba78b7f5252e9fe6602bd180ca Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 22:59:46 +0200 Subject: [PATCH 08/25] refactor(rocprofiler-systems): use plain inline and [[gnu::flatten]] in archives Replace ROCPROFSYS_INLINE (always_inline) with plain inline on the generic get_size / serialize / deserialize, and drop it from the archive members defined in the class, which are implicitly inline (readability-redundant-inline-specifier). [[gnu::flatten]] on record_size / record_write / record_read already inlines everything they call, so the generated code is unchanged (kernel_dispatch_sample: write 38 instructions, read 23). __attribute__((flatten)) becomes [[gnu::flatten]], which clang-format understands: the function names are no longer pushed 30+ columns to the right. archive.hpp no longer needs common/defines.h. --- .../source/lib/core/trace_cache/archive.hpp | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 7d0b62ef8c98..110ae761ed6b 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -3,7 +3,6 @@ #pragma once -#include "common/defines.h" #include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/cacheable.hpp" @@ -33,14 +32,14 @@ struct binary_size_archive std::size_t size = 0; template - ROCPROFSYS_INLINE void operator()(const Types&... values) + void operator()(const Types&... values) { (size_one(values), ...); } private: template - ROCPROFSYS_INLINE void size_one(const Type& value) + void size_one(const Type& value) { if constexpr(archive_serializable) { @@ -62,7 +61,7 @@ struct binary_write_archive std::size_t position = 0; template - ROCPROFSYS_INLINE void operator()(const Types&... values) + void operator()(const Types&... values) { // Locals, not members: a write through the byte buffer may alias any member, so // members would be reloaded after every field and adjacent copies not merged. @@ -74,8 +73,7 @@ struct binary_write_archive private: template - ROCPROFSYS_INLINE void write_one(const Type& value, std::uint8_t* out, - std::size_t& pos) + void write_one(const Type& value, std::uint8_t* out, std::size_t& pos) { if constexpr(archive_serializable) { @@ -103,7 +101,7 @@ struct binary_read_archive std::uint8_t* cursor = nullptr; template - ROCPROFSYS_INLINE void operator()(Types&... values) + void operator()(Types&... values) { // Local cursor for the same reason as in binary_write_archive. std::uint8_t* cur = cursor; @@ -113,7 +111,7 @@ struct binary_read_archive private: template - ROCPROFSYS_INLINE void read_one(Type& value, std::uint8_t*& cur) + void read_one(Type& value, std::uint8_t*& cur) { if constexpr(archive_serializable) { @@ -155,12 +153,12 @@ concept archive_record = // // flatten inlines everything these functions call, including the record's own // serialize(): without it GCC keeps that member out of line and copies the fields one by -// one through memory. They are not ROCPROFSYS_INLINE on purpose: flatten is lost once the +// one through memory. They are deliberately not inline: flatten is lost once the // function is itself inlined, so each record type gets one fully inlined function, as the // hand-written specializations had before. template -[[nodiscard]] __attribute__((flatten)) std::size_t - record_size(const T& record) +[[nodiscard, gnu::flatten]] std::size_t +record_size(const T& record) { binary_size_archive archive; const_cast(record).serialize(archive); @@ -168,7 +166,7 @@ template } template -__attribute__((flatten)) void +[[gnu::flatten]] void record_write(std::uint8_t* buffer, const T& record) { binary_write_archive archive{ .buffer = buffer, .position = 0 }; @@ -176,7 +174,7 @@ record_write(std::uint8_t* buffer, const T& record) } template -[[nodiscard]] __attribute__((flatten)) T +[[nodiscard, gnu::flatten]] T record_read(std::uint8_t*& buffer) { // Default-initialized, not value-initialized: zeroing the whole record first keeps it @@ -192,15 +190,15 @@ record_read(std::uint8_t*& buffer) /** Encoded size of a record. One definition for every record type. */ template -[[nodiscard]] ROCPROFSYS_INLINE std::size_t - get_size(const T& record) +[[nodiscard]] inline std::size_t +get_size(const T& record) { return record_size(record); } /** Writes a record into a buffer with room for get_size(record) bytes. */ template -ROCPROFSYS_INLINE void +inline void serialize(std::uint8_t* buffer, const T& record) { record_write(buffer, record); @@ -208,7 +206,7 @@ serialize(std::uint8_t* buffer, const T& record) /** Reads a record and advances the buffer past it. */ template -[[nodiscard]] ROCPROFSYS_INLINE T +[[nodiscard]] inline T deserialize(std::uint8_t*& buffer) { return record_read(buffer); From 86c09ca777933b706bae93866256a60632416da8 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 23:18:05 +0200 Subject: [PATCH 09/25] perf(rocprofiler-systems): build records in place in the type registry type_registry returned each record as a variant that get_type() then moved into a std::optional, so every record was written and immediately copied. The copy read 16-byte blocks over the narrower stores that had just filled the record and stalled store forwarding; kfd_sample and pmc_event_with_sample were slower than develop on the read path. The deserializer now returns the std::optional itself and constructs the record inside it: a reader object converts to T by reading it, so the record is initialized directly in its final storage and get_type() returns the optional without a copy. Every record type now reads faster than develop through type_registry. Also rename the touched member to m_deserializers and the iterator to found (readability-identifier-naming / -length). --- .../lib/core/trace_cache/type_registry.hpp | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index 9925993359d0..b5bfe794b3e0 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace rocprofsys::trace_cache { @@ -26,23 +27,36 @@ class type_registry std::optional get_type(TypeIdentifierEnum id, std::uint8_t*& data) { - auto const it = deserializers.find(id); - if(it != deserializers.end()) + auto const found = m_deserializers.find(id); + if(found != m_deserializers.end()) { - return it->second(data); + return found->second(data); } return std::nullopt; } private: - std::map> deserializers; + /// Converts to T by reading it, so T is constructed directly in its final storage. + template + struct reader + { + std::uint8_t*& data; + + explicit operator T() const { return deserialize(data); } + }; + + // The deserializer returns the optional itself and builds the record inside it: a + // returned record would be copied into the variant and again into the optional. + std::map(std::uint8_t*&)>> + m_deserializers; template requires type_traits::cacheable void register_type() { - deserializers[T::type_identifier] = [](std::uint8_t*& data) -> variant_t { - return deserialize(data); + m_deserializers[T::type_identifier] = [](std::uint8_t*& data) { + return std::optional{ std::in_place, std::in_place_type, + reader{ data } }; }; } }; From 652ad2edb460033b498d36b5fd021be2f58ad8f2 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Thu, 22 Oct 2026 23:36:01 +0200 Subject: [PATCH 10/25] style(rocprofiler-systems): trim comments in the trace cache archives Keep only the comments that explain a non-obvious reason (aliasing, string views into the buffer, const_cast, flatten instead of inline, default-initialization, the in-place reader) and drop the ones that restate names or code. --- .../source/lib/core/trace_cache/archive.hpp | 39 +++---------------- .../lib/core/trace_cache/type_registry.hpp | 4 +- .../collectors/gpu_perf_counter/sample.hpp | 2 - 3 files changed, 6 insertions(+), 39 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 110ae761ed6b..10c909390712 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -15,17 +15,13 @@ namespace rocprofsys::trace_cache { -// The cache format writes size_t lengths; records pass size_t fields where the old code -// cast them to std::uint64_t. Both rely on the two being the same type. static_assert(std::is_same_v, "trace cache format assumes size_t is std::uint64_t"); -/** A value (a record or a nested struct) that lists its own fields in serialize(). */ template concept archive_serializable = requires(Type& value, Archive& archive) { value.serialize(archive); }; -/** Sums the encoded size of the fields a record passes to it. */ struct binary_size_archive { static constexpr bool k_is_loading = false; @@ -43,7 +39,6 @@ struct binary_size_archive { if constexpr(archive_serializable) { - // Recurse into a nested struct; it is only read through serialize(). const_cast(value).serialize(*this); } else @@ -53,7 +48,6 @@ struct binary_size_archive } }; -/** Writes the fields into a buffer that already has room for record_size() bytes. */ struct binary_write_archive { static constexpr bool k_is_loading = false; @@ -63,8 +57,7 @@ struct binary_write_archive template void operator()(const Types&... values) { - // Locals, not members: a write through the byte buffer may alias any member, so - // members would be reloaded after every field and adjacent copies not merged. + // Locals: writes through the byte buffer may alias the members. std::uint8_t* const out = buffer; std::size_t pos = position; (write_one(values, out, pos), ...); @@ -77,9 +70,6 @@ struct binary_write_archive { if constexpr(archive_serializable) { - // Recurse into a nested struct; it is only read through serialize(). The - // nested call works on the members, so hand the position over and take it - // back. position = pos; const_cast(value).serialize(*this); pos = position; @@ -91,10 +81,7 @@ struct binary_write_archive } }; -/** - * Reads the fields back. string_view and span fields become views into - * the buffer, so the buffer must outlive the record (as for the parse buffer today). - */ +/** string_view and span fields are read as views into the buffer. */ struct binary_read_archive { static constexpr bool k_is_loading = true; @@ -103,7 +90,6 @@ struct binary_read_archive template void operator()(Types&... values) { - // Local cursor for the same reason as in binary_write_archive. std::uint8_t* cur = cursor; (read_one(values, cur), ...); cursor = cur; @@ -115,8 +101,6 @@ struct binary_read_archive { if constexpr(archive_serializable) { - // The nested call works on the member, so hand the cursor over and take it - // back. cursor = cur; value.serialize(*this); cur = cursor; @@ -136,7 +120,6 @@ struct binary_read_archive } }; -/** A record that lists its fields once, in byte order, in serialize(Archive&). */ template concept archive_record = std::is_default_constructible_v && @@ -148,14 +131,8 @@ concept archive_record = record.serialize(reader); }; -// serialize() is one non-const member shared by all archives. The size and write archives -// only read through it, so the const_cast is safe (cereal's output archives do the same). -// -// flatten inlines everything these functions call, including the record's own -// serialize(): without it GCC keeps that member out of line and copies the fields one by -// one through memory. They are deliberately not inline: flatten is lost once the -// function is itself inlined, so each record type gets one fully inlined function, as the -// hand-written specializations had before. +// const_cast: the size and write archives only read through serialize(). +// flatten, not inline (inlining drops it): one fully inlined function per record type. template [[nodiscard, gnu::flatten]] std::size_t record_size(const T& record) @@ -177,10 +154,7 @@ template [[nodiscard, gnu::flatten]] T record_read(std::uint8_t*& buffer) { - // Default-initialized, not value-initialized: zeroing the whole record first keeps it - // in memory, and the copy into its final place (a variant) then stalls on the 8-byte - // stores. Every stored field is read below; the others keep their member - // initializers. + // Default-initialized like the old deserializers: zeroing it first was slower. T record; binary_read_archive archive{ .cursor = buffer }; record.serialize(archive); @@ -188,7 +162,6 @@ record_read(std::uint8_t*& buffer) return record; } -/** Encoded size of a record. One definition for every record type. */ template [[nodiscard]] inline std::size_t get_size(const T& record) @@ -196,7 +169,6 @@ get_size(const T& record) return record_size(record); } -/** Writes a record into a buffer with room for get_size(record) bytes. */ template inline void serialize(std::uint8_t* buffer, const T& record) @@ -204,7 +176,6 @@ serialize(std::uint8_t* buffer, const T& record) record_write(buffer, record); } -/** Reads a record and advances the buffer past it. */ template [[nodiscard]] inline T deserialize(std::uint8_t*& buffer) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index b5bfe794b3e0..2248ff3f3d0f 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -36,7 +36,7 @@ class type_registry } private: - /// Converts to T by reading it, so T is constructed directly in its final storage. + // Converts to T by reading it, so the record is built inside the returned optional. template struct reader { @@ -45,8 +45,6 @@ class type_registry explicit operator T() const { return deserialize(data); } }; - // The deserializer returns the optional itself and builds the record inside it: a - // returned record would be copied into the variant and again into the optional. std::map(std::uint8_t*&)>> m_deserializers; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp index 6b8939c6ae4c..c7b2b654eb19 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp @@ -48,8 +48,6 @@ struct sample : trace_cache::cacheable_t { entries.resize(count); } - // Range-for: begin/end are read once. An index loop re-reads them after every - // entry, because a write through the uint8_t buffer may alias the vector. for(auto& entry : entries) { archive(entry.counter_id, entry.value); From 13e8fe6204a6c634d3da81b1c7bf3848d7d76627 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 00:59:11 +0200 Subject: [PATCH 11/25] feat(rocprofiler-systems): check trace cache type identifiers at compile time type_registry now fails to compile when two supported record types share a type_identifier, instead of silently overwriting one deserializer with the other at startup. --- .../trace_cache/tests/test_type_registry.cpp | 3 +++ .../lib/core/trace_cache/type_registry.hpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp index 7bccade3abcc..7fc00162c015 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp @@ -7,6 +7,9 @@ #include +static_assert(rocprofsys::trace_cache::detail::are_unique<1, 2, 3>()); +static_assert(!rocprofsys::trace_cache::detail::are_unique<1, 2, 1>()); + class type_registry_test : public ::testing::Test { protected: diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index 2248ff3f3d0f..ac74d3ca1718 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -6,6 +6,8 @@ #include "core/trace_cache/cache_type_traits.hpp" #include +#include +#include #include #include #include @@ -14,11 +16,25 @@ namespace rocprofsys::trace_cache { +namespace detail +{ +template +[[nodiscard]] constexpr bool +are_unique() +{ + std::array identifiers{ Identifiers... }; + std::ranges::sort(identifiers); + return std::ranges::adjacent_find(identifiers) == identifiers.end(); +} +} // namespace detail + template class type_registry { static_assert(type_traits::is_enum_class_v, "TypeIdentifierEnum must be an enum class"); + static_assert(detail::are_unique(), + "two record types share a type_identifier"); public: using variant_t = std::variant; From 990fc13731d8e4c06684bd7445e007c8f63df943 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 00:59:11 +0200 Subject: [PATCH 12/25] refactor(rocprofiler-systems): remove unused tuple_to_variant trait Nothing uses it. type_registry includes itself instead of getting it through cache_type_traits.hpp. --- .../source/lib/core/trace_cache/cache_type_traits.hpp | 11 ----------- .../source/lib/core/trace_cache/type_registry.hpp | 1 + 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp index 0e4800ac543e..4df12253c4df 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp @@ -9,9 +9,7 @@ #include #include #include -#include #include -#include #include namespace rocprofsys::trace_cache @@ -20,15 +18,6 @@ namespace rocprofsys::trace_cache namespace type_traits { -template -struct tuple_to_variant; - -template -struct tuple_to_variant> -{ - using type = std::variant; -}; - template struct is_span : std::false_type {}; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index ac74d3ca1718..1b78e0382034 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace rocprofsys::trace_cache { From bf9c4c5ece25d0e7ddfaeb64154a0a21eabcf2f5 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 10:59:06 +0200 Subject: [PATCH 13/25] refactor(rocprofiler-systems): drop the variadic utility helpers and the record_* layer The multi-value utility::get_size, store_value and parse_value overloads were only used by tests after the records moved to archives; the tests now call the single-value versions. get_size, serialize and deserialize carry [[gnu::flatten]] themselves instead of forwarding to record_size, record_write and record_read. The generated code is unchanged. --- .../source/lib/core/trace_cache/archive.hpp | 27 +----- .../source/lib/core/trace_cache/cacheable.hpp | 23 ------ .../core/trace_cache/tests/test_archive.cpp | 82 ++++++++++--------- .../core/trace_cache/tests/test_cacheable.cpp | 19 +++-- 4 files changed, 61 insertions(+), 90 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 10c909390712..c6b5106912e1 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -135,7 +135,7 @@ concept archive_record = // flatten, not inline (inlining drops it): one fully inlined function per record type. template [[nodiscard, gnu::flatten]] std::size_t -record_size(const T& record) +get_size(const T& record) { binary_size_archive archive; const_cast(record).serialize(archive); @@ -144,7 +144,7 @@ record_size(const T& record) template [[gnu::flatten]] void -record_write(std::uint8_t* buffer, const T& record) +serialize(std::uint8_t* buffer, const T& record) { binary_write_archive archive{ .buffer = buffer, .position = 0 }; const_cast(record).serialize(archive); @@ -152,7 +152,7 @@ record_write(std::uint8_t* buffer, const T& record) template [[nodiscard, gnu::flatten]] T -record_read(std::uint8_t*& buffer) +deserialize(std::uint8_t*& buffer) { // Default-initialized like the old deserializers: zeroing it first was slower. T record; @@ -162,27 +162,6 @@ record_read(std::uint8_t*& buffer) return record; } -template -[[nodiscard]] inline std::size_t -get_size(const T& record) -{ - return record_size(record); -} - -template -inline void -serialize(std::uint8_t* buffer, const T& record) -{ - record_write(buffer, record); -} - -template -[[nodiscard]] inline T -deserialize(std::uint8_t*& buffer) -{ - return record_read(buffer); -} - namespace type_traits { template diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 7d6fdee1251e..96e9d42ad2a0 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -84,13 +84,6 @@ get_size(Type&& val) } } -template -__attribute__((always_inline)) constexpr size_t -get_size(Type&& val, Types&&... vals) -{ - return get_size(std::forward(val)) + get_size(std::forward(vals)...); -} - template requires type_traits::supported_cache_type __attribute__((always_inline)) inline void @@ -137,14 +130,6 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) } } -template -__attribute__((always_inline)) inline void -store_value(std::uint8_t* buffer, const Types&... values) -{ - size_t position = 0; - (store_value(values, buffer, position), ...); -} - template requires type_traits::supported_cache_type __attribute__((always_inline)) inline static void @@ -201,13 +186,5 @@ parse_value(std::uint8_t*& data_pos, Type& arg) } } -template -__attribute__((always_inline)) inline static void -parse_value(std::uint8_t*& data_pos, Type& arg, Types&... args) -{ - parse_value(data_pos, arg); - parse_value(data_pos, args...); -} - } // namespace utility } // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp index 476d92be98fb..2f9ca0d7a1c3 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -202,21 +202,25 @@ TEST(archive, scalars_match_utility_encoding) orig.val_u64 = val_u64; orig.val_dbl = val_dbl; - const std::size_t size = record_size(orig); - record_write(buffer.data(), orig); + const std::size_t size = get_size(orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const scalar_record read = record_read(read_buffer); + const scalar_record read = deserialize(read_buffer); EXPECT_EQ(read.val_u32, val_u32); EXPECT_EQ(read.val_u64, val_u64); EXPECT_DOUBLE_EQ(read.val_dbl, val_dbl); EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); - EXPECT_EQ(size, utility::get_size(val_u32, val_u64, val_dbl)); + EXPECT_EQ(size, utility::get_size(val_u32) + utility::get_size(val_u64) + + utility::get_size(val_dbl)); std::array other_buffer{}; - utility::store_value(other_buffer.data(), val_u32, val_u64, val_dbl); + std::size_t utility_position = 0; + utility::store_value(val_u32, other_buffer.data(), utility_position); + utility::store_value(val_u64, other_buffer.data(), utility_position); + utility::store_value(val_dbl, other_buffer.data(), utility_position); EXPECT_EQ(std::memcmp(buffer.data(), other_buffer.data(), size), 0); } @@ -229,11 +233,11 @@ TEST(archive, string_view_reads_back_as_view_into_buffer) string_record orig; orig.text = orig_text; - const std::size_t size = record_size(orig); - record_write(buffer.data(), orig); + const std::size_t size = get_size(orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const string_record read = record_read(read_buffer); + const string_record read = deserialize(read_buffer); EXPECT_EQ(read.text, orig_text); EXPECT_EQ(read.text.data(), @@ -249,11 +253,11 @@ TEST(archive, vector_round_trip) vector_record orig; orig.data = orig_data; - const std::size_t size = record_size(orig); - record_write(buffer.data(), orig); + const std::size_t size = get_size(orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const vector_record read = record_read(read_buffer); + const vector_record read = deserialize(read_buffer); EXPECT_EQ(read.data, orig_data); EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); @@ -265,11 +269,11 @@ TEST(archive, optional_engaged_and_empty) optional_record orig_engaged; orig_engaged.value = 9876543210LL; - const std::size_t size_engaged = record_size(orig_engaged); - record_write(buffer.data(), orig_engaged); + const std::size_t size_engaged = get_size(orig_engaged); + serialize(buffer.data(), orig_engaged); std::uint8_t* read_buf = buffer.data(); - const optional_record read_engaged = record_read(read_buf); + const optional_record read_engaged = deserialize(read_buf); ASSERT_TRUE(read_engaged.value.has_value()); EXPECT_EQ(read_engaged.value.value(), 9876543210LL); @@ -278,11 +282,11 @@ TEST(archive, optional_engaged_and_empty) optional_record orig_empty; orig_empty.value.reset(); - const std::size_t size_empty = record_size(orig_empty); - record_write(buffer.data(), orig_empty); + const std::size_t size_empty = get_size(orig_empty); + serialize(buffer.data(), orig_empty); read_buf = buffer.data(); - const optional_record read_empty = record_read(read_buf); + const optional_record read_empty = deserialize(read_buf); EXPECT_FALSE(read_empty.value.has_value()); @@ -298,11 +302,11 @@ TEST(archive, byte_span_reads_back_as_view) span_record orig; orig.bytes = bytes_span; - const std::size_t size = record_size(orig); - record_write(buffer.data(), orig); + const std::size_t size = get_size(orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const span_record read = record_read(read_buffer); + const span_record read = deserialize(read_buffer); EXPECT_EQ(read.bytes.size(), 3 * sizeof(double)); EXPECT_GE(read.bytes.data(), reinterpret_cast(buffer.data())); @@ -328,13 +332,13 @@ TEST(archive, derived_record_serializes_base_first) orig.extra_field = 777ULL; const std::size_t base_size = - record_size(base_record{ .base_field = 555U, .base_value = 3.14 }); - const std::size_t total_size = record_size(orig); + get_size(base_record{ .base_field = 555U, .base_value = 3.14 }); + const std::size_t total_size = get_size(orig); - record_write(buffer.data(), orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const derived_record read = record_read(read_buffer); + const derived_record read = deserialize(read_buffer); EXPECT_EQ(read.base_field, 555U); EXPECT_DOUBLE_EQ(read.base_value, 3.14); @@ -352,11 +356,11 @@ TEST(archive, is_loading_branch_for_counted_entries) orig.entries.push_back({ .id = 100, .value = 1.5 }); orig.entries.push_back({ .id = 200, .value = 2.5 }); - const std::size_t size = record_size(orig); - record_write(buffer.data(), orig); + const std::size_t size = get_size(orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const counted_entries_record read = record_read(read_buffer); + const counted_entries_record read = deserialize(read_buffer); EXPECT_EQ(read.device, 1U); EXPECT_EQ(read.entries.size(), 2U); @@ -370,12 +374,12 @@ TEST(archive, is_loading_branch_for_counted_entries) counted_entries_record orig_empty; orig_empty.device = 2U; - const std::size_t size_empty = record_size(orig_empty); - record_write(buffer.data(), orig_empty); + const std::size_t size_empty = get_size(orig_empty); + serialize(buffer.data(), orig_empty); read_buffer = buffer.data(); const counted_entries_record read_empty = - record_read(read_buffer); + deserialize(read_buffer); EXPECT_EQ(read_empty.device, 2U); EXPECT_EQ(read_empty.entries.size(), 0U); @@ -391,11 +395,11 @@ TEST(archive, nested_struct_serializes_itself) orig.inner.beta = 3.33; orig.tail = 4444U; - const std::size_t size = record_size(orig); - record_write(buffer.data(), orig); + const std::size_t size = get_size(orig); + serialize(buffer.data(), orig); std::uint8_t* read_buffer = buffer.data(); - const nested_record read = record_read(read_buffer); + const nested_record read = deserialize(read_buffer); EXPECT_EQ(read.head, 1111ULL); EXPECT_EQ(read.inner.alpha, 2222U); @@ -403,12 +407,16 @@ TEST(archive, nested_struct_serializes_itself) EXPECT_EQ(read.tail, 4444U); EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); - EXPECT_EQ(size, - utility::get_size(orig.head, orig.inner.alpha, orig.inner.beta, orig.tail)); + EXPECT_EQ(size, utility::get_size(orig.head) + utility::get_size(orig.inner.alpha) + + utility::get_size(orig.inner.beta) + + utility::get_size(orig.tail)); std::array other_buffer{}; - utility::store_value(other_buffer.data(), orig.head, orig.inner.alpha, - orig.inner.beta, orig.tail); + std::size_t nested_position = 0; + utility::store_value(orig.head, other_buffer.data(), nested_position); + utility::store_value(orig.inner.alpha, other_buffer.data(), nested_position); + utility::store_value(orig.inner.beta, other_buffer.data(), nested_position); + utility::store_value(orig.tail, other_buffer.data(), nested_position); EXPECT_EQ(std::memcmp(buffer.data(), other_buffer.data(), size), 0); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp index 122cc7fa0a39..59d35a54cf7e 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp @@ -343,8 +343,10 @@ TEST_F(cacheable_test, parse_multiple_values_with_optional) std::string_view parsed_str; std::optional parsed_opt_empty; - rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_int, parsed_opt, - parsed_str, parsed_opt_empty); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_int); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_opt); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_str); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_opt_empty); EXPECT_EQ(parsed_int, 100); ASSERT_TRUE(parsed_opt.has_value()); @@ -495,8 +497,11 @@ TEST_F(cacheable_test, parse_multiple_values_with_optional_vector_and_string_vie std::optional> parsed_nullvec; std::optional parsed_nullstr; - rocprofsys::trace_cache::utility::parse_value( - data_pos, parsed_int, parsed_vec, parsed_str, parsed_nullvec, parsed_nullstr); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_int); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_vec); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_str); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_nullvec); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_nullstr); EXPECT_EQ(parsed_int, 42); ASSERT_TRUE(parsed_vec.has_value()); @@ -555,8 +560,10 @@ TEST_F(cacheable_test, parse_multiple_values) 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); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_int); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_double); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_string); + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed_uchar); EXPECT_EQ(parsed_int, 42); EXPECT_DOUBLE_EQ(parsed_double, 3.14); From 76911d3054c4d003bb57bc56a8ad47168f061b5e Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 10:59:06 +0200 Subject: [PATCH 14/25] test(rocprofiler-systems): pin the per-core CPU entry bytes in the golden test The cpu_pmc_sample golden test used two arbitrary bytes for the frequency and load vectors. It now packs two real cores with serialize_frequencies and serialize_loads, so the expected bytes pin the per-core entry layout (cpu_id followed by the value) and its byte-count prefix. --- .../trace_cache/tests/test_record_format.cpp | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp index 2d1479aac99f..e904b41bc406 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp @@ -16,6 +16,7 @@ #include "library/pmc/collectors/nic/types.hpp" #include +#include #include #include #include @@ -392,8 +393,16 @@ TEST(record_format, kfd_sample_empty_optional) TEST(record_format, cpu_pmc_sample) { - const auto freqs = std::vector{ 0xaa, 0xbb }; - const auto loads = std::vector{ 0xcc, 0xdd }; + const std::vector cores{ + { .cpu_id = 0x1A1A1A1A1A1A1A1AUL, + .frequency = std::bit_cast(0x3C3C3C3CU), + .load = std::bit_cast(0x5D5D5D5D5D5D5D5DUL) }, + { .cpu_id = 0x2B2B2B2B2B2B2B2BUL, + .frequency = std::bit_cast(0x4D4D4D4DU), + .load = std::bit_cast(0x6E6E6E6E6E6E6E6EUL) }, + }; + const auto freqs = rocprofsys::pmc::collectors::cpu::serialize_frequencies(cores); + const auto loads = rocprofsys::pmc::collectors::cpu::serialize_loads(cores); auto process_data = rocprofsys::pmc::collectors::cpu::process_metrics{}; process_data.page_rss = 0x3333333333333333UL; @@ -410,15 +419,19 @@ TEST(record_format, cpu_pmc_sample) enabled, 0x11111111U, 0x2222222222222222UL, process_data, freqs, loads }; - constexpr std::array k_expected{ + constexpr std::array k_expected{ 0xa5, 0xa5, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x08, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x09, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xdd, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x3c, 0x3c, 0x3c, 0x3c, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x4d, 0x4d, 0x4d, 0x4d, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, }; expect_golden(record, k_expected); From ccc1bdef00a19d79b3438ec5dc5349a2b02f9bb0 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 12:46:59 +0200 Subject: [PATCH 15/25] refactor(rocprofiler-systems): name the archive per-value helpers after what they do size_one, write_one and read_one become measure_value, write_value and read_value, matching utility::store_value and parse_value. Private members only; no behavior change. --- .../source/lib/core/trace_cache/archive.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index c6b5106912e1..d53794ffb69a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -30,12 +30,12 @@ struct binary_size_archive template void operator()(const Types&... values) { - (size_one(values), ...); + (measure_value(values), ...); } private: template - void size_one(const Type& value) + void measure_value(const Type& value) { if constexpr(archive_serializable) { @@ -60,13 +60,13 @@ struct binary_write_archive // Locals: writes through the byte buffer may alias the members. std::uint8_t* const out = buffer; std::size_t pos = position; - (write_one(values, out, pos), ...); + (write_value(values, out, pos), ...); position = pos; } private: template - void write_one(const Type& value, std::uint8_t* out, std::size_t& pos) + void write_value(const Type& value, std::uint8_t* out, std::size_t& pos) { if constexpr(archive_serializable) { @@ -91,13 +91,13 @@ struct binary_read_archive void operator()(Types&... values) { std::uint8_t* cur = cursor; - (read_one(values, cur), ...); + (read_value(values, cur), ...); cursor = cur; } private: template - void read_one(Type& value, std::uint8_t*& cur) + void read_value(Type& value, std::uint8_t*& cur) { if constexpr(archive_serializable) { From 075182549cdf5ed11c09e445d506be61da603383 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 12:47:00 +0200 Subject: [PATCH 16/25] refactor(rocprofiler-systems): serialize the record header like the records The [type id][size] header in front of every cached record was written by hand in store() and fragment_memory(), read through a packed struct in storage_parser, and its size was a separate formula. record_header now defines it once with a serialize() member: k_header_size is computed from it at compile time, store() writes header and record with one flattened write_record(), fragment_memory() and the parser use the same archives. A golden test pins the header bytes, which are unchanged; test_storage_parser keeps its own packed writer as an independent check of the old layout. --- .../lib/core/trace_cache/CMakeLists.txt | 1 + .../source/lib/core/trace_cache/archive.hpp | 4 +- .../lib/core/trace_cache/buffer_storage.hpp | 34 +++++------- .../source/lib/core/trace_cache/cacheable.hpp | 5 +- .../lib/core/trace_cache/record_header.hpp | 52 +++++++++++++++++++ .../lib/core/trace_cache/storage_parser.hpp | 26 +++++----- .../trace_cache/tests/test_record_format.cpp | 22 ++++++++ 7 files changed, 105 insertions(+), 39 deletions(-) create mode 100644 projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt index 7d1caccb3ff7..46b874c13c37 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/CMakeLists.txt @@ -15,6 +15,7 @@ set(trace_cache_sources set(trace_cache_headers ${CMAKE_CURRENT_LIST_DIR}/archive.hpp + ${CMAKE_CURRENT_LIST_DIR}/record_header.hpp ${CMAKE_CURRENT_LIST_DIR}/cache_manager.hpp ${CMAKE_CURRENT_LIST_DIR}/data_types.hpp ${CMAKE_CURRENT_LIST_DIR}/discovery.hpp diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index d53794ffb69a..271d91b0ff5f 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -28,14 +28,14 @@ struct binary_size_archive std::size_t size = 0; template - void operator()(const Types&... values) + constexpr void operator()(const Types&... values) { (measure_value(values), ...); } private: template - void measure_value(const Type& value) + constexpr void measure_value(const Type& value) { if constexpr(archive_serializable) { diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp index 1f57f477a5c7..909f09eac5d5 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp @@ -7,6 +7,7 @@ #include "core/trace_cache/archive.hpp" #include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/cacheable.hpp" +#include "core/trace_cache/record_header.hpp" #include "policies/thread_state_policy.hpp" @@ -138,11 +139,8 @@ class buffer_storage "Trying to use buffered storage while it is not running"); } - using TypeIdentifierEnumUderlayingType = - std::underlying_type_t; - - size_t const sample_size = get_size(value); - size_t const bytes_to_reserve = header_size + sample_size; + const record_header header{ .type = Type::type_identifier, + .size = get_size(value) }; // Hold the mutex for the entire reserve-and-write operation so that // the flush worker thread never reads a buffer region whose write is @@ -154,14 +152,8 @@ class buffer_storage ThreadStatePolicy::scoped(ThreadStatePolicy::Internal); std::lock_guard const scope{ m_mutex }; - auto* buf = reserve_memory_space(bytes_to_reserve); - size_t position = 0; - auto const type_identifier_value = - static_cast(Type::type_identifier); - - utility::store_value(type_identifier_value, buf, position); - utility::store_value(sample_size, buf, position); - serialize(buf + position, value); + auto* buf = reserve_memory_space(k_header_size + header.size); + write_record(buf, header, value); } [[nodiscard]] __attribute__((always_inline)) bool is_running() const @@ -220,12 +212,12 @@ class buffer_storage memset(_data + m_head, std::numeric_limits::max(), buffer_size - m_head); - const TypeIdentifierEnum tag = TypeIdentifierEnum::fragmented_space; - std::memcpy(_data + m_head, &tag, sizeof(TypeIdentifierEnum)); - - size_t remaining_bytes = buffer_size - m_head - header_size; - std::memcpy(_data + m_head + sizeof(TypeIdentifierEnum), &remaining_bytes, - sizeof(size_t)); + const record_header header{ + .type = TypeIdentifierEnum::fragmented_space, + .size = buffer_size - m_head - k_header_size + }; + binary_write_archive archive{ .buffer = _data + m_head, .position = 0 }; + archive(header); m_head = 0; } @@ -234,8 +226,8 @@ class buffer_storage [[nodiscard]] __attribute__((always_inline)) std::uint8_t* reserve_memory_space( const size_t& number_of_bytes) { - if(__builtin_expect((m_head + number_of_bytes + header_size) > - buffer_size, + if(__builtin_expect((m_head + number_of_bytes + + k_header_size) > buffer_size, 0)) { fragment_memory(); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 96e9d42ad2a0..a05d0f6210a9 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -32,9 +33,7 @@ constexpr auto CACHE_FILE_FLUSH_TIMEOUT = 10ms; constexpr auto ABSOLUTE = "ABS"; constexpr auto PERCENTAGE = "%"; -template -constexpr size_t header_size = sizeof(TypeIdentifierEnum) + sizeof(size_t); -using buffer_array_t = std::array; +using buffer_array_t = std::array; const auto tmp_directory = std::string{ "/tmp/" }; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp new file mode 100644 index 000000000000..9019c84465f9 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp @@ -0,0 +1,52 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core/trace_cache/archive.hpp" + +#include +#include +#include + +namespace rocprofsys::trace_cache +{ + +/** Frame in front of every record in the cache: the record type and its payload size. */ +template +struct record_header +{ + TypeIdentifierEnum type{}; + std::size_t size = 0; + + template + constexpr void serialize(Archive& archive) + { + auto raw_type = static_cast>(type); + archive(raw_type, size); + if constexpr(Archive::k_is_loading) + { + type = static_cast(raw_type); + } + } +}; + +template +constexpr std::size_t k_header_size = [] { + binary_size_archive archive; + record_header{}.serialize(archive); + return archive.size; +}(); + +// One fully inlined function per record type, like serialize(). +template +[[gnu::flatten]] void +write_record(std::uint8_t* buffer, record_header header, + const T& record) +{ + binary_write_archive archive{ .buffer = buffer, .position = 0 }; + header.serialize(archive); + const_cast(record).serialize(archive); +} + +} // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp index ab5cbbc223dd..4c76efd10d7b 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp @@ -6,12 +6,15 @@ #include "common/defines.h" #include "core/progress/callback.hpp" +#include "core/trace_cache/archive.hpp" #include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/cacheable.hpp" +#include "core/trace_cache/record_header.hpp" #include "core/trace_cache/type_registry.hpp" #include "logger/debug.hpp" +#include #include #include #include @@ -56,13 +59,8 @@ class storage_parser fmt::format("Error opening file for reading: {}", m_filename)); } - struct __attribute__((packed)) sample_header - { - TypeIdentifierEnum type; - size_t sample_size; - }; - - sample_header header; + std::array> header_bytes{}; + record_header header; std::vector sample; sample.reserve(4096); @@ -78,21 +76,23 @@ class storage_parser "Stream not in good state, stopping parse. File: {}", m_filename)); } - ifs.read(reinterpret_cast(&header), sizeof(header)); + ifs.read(reinterpret_cast(header_bytes.data()), header_bytes.size()); + binary_read_archive header_reader{ .cursor = header_bytes.data() }; + header_reader(header); - if(header.sample_size == 0 || ifs.eof()) + if(header.size == 0 || ifs.eof()) { continue; } - if(ROCPROFSYS_UNLIKELY(header.sample_size > last_capacity)) + if(ROCPROFSYS_UNLIKELY(header.size > last_capacity)) { - sample.reserve(header.sample_size); + sample.reserve(header.size); last_capacity = sample.capacity(); } - sample.resize(header.sample_size); - ifs.read(reinterpret_cast(sample.data()), header.sample_size); + sample.resize(header.size); + ifs.read(reinterpret_cast(sample.data()), header.size); if(ifs.fail()) { diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp index e904b41bc406..cf920b322dec 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp @@ -3,6 +3,7 @@ #include "backends/amd_smi/gpu_types.hpp" #include "core/trace_cache/archive.hpp" +#include "core/trace_cache/record_header.hpp" #include "core/trace_cache/sample_type.hpp" #include "library/pmc/collectors/cpu/sample.hpp" #include "library/pmc/collectors/cpu/types.hpp" @@ -56,6 +57,27 @@ expect_golden(const Record& record, const std::array& expected) } // namespace +TEST(record_format, record_header) +{ + const rocprofsys::trace_cache::record_header< + rocprofsys::trace_cache::type_identifier_t> + header{ .type = rocprofsys::trace_cache::type_identifier_t::kernel_dispatch, + .size = 0x1122334455667788UL }; + + constexpr std::array k_expected{ + 0x03, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 + }; + static_assert(rocprofsys::trace_cache::k_header_size< + rocprofsys::trace_cache::type_identifier_t> == k_expected.size()); + + std::array buffer{}; + rocprofsys::trace_cache::binary_write_archive archive{ .buffer = buffer.data(), + .position = 0 }; + archive(header); + + EXPECT_EQ(buffer, k_expected); +} + TEST(record_format, kernel_dispatch_sample) { const kernel_dispatch_sample record{ From 6118506f11427ba3aeadae54b3fb54c3c3c55568 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 13:37:41 +0200 Subject: [PATCH 17/25] perf(rocprofiler-systems): read cached vectors with one memcpy utility::parse_value read a vector element by element with push_back, one byte at a time for the CPU sample's byte vectors. It now resizes the vector once and copies the whole payload. Both the vector write and read static_assert that the element type is trivially copyable, since vectors are cached as raw bytes. --- .../source/lib/core/trace_cache/cacheable.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index a05d0f6210a9..3eb59ff580c5 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -104,6 +104,8 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) else if constexpr(type_traits::is_vector_v || type_traits::is_span_v) { + static_assert(std::is_trivially_copyable_v, + "vectors are cached as raw bytes"); const size_t total_size = get_size(value); const size_t header_size = sizeof(size_t); const size_t data_size = total_size - header_size; @@ -147,19 +149,16 @@ parse_value(std::uint8_t*& data_pos, Type& arg) else if constexpr(type_traits::is_vector_v || type_traits::is_span_v) { - using ContainerType = std::decay_t; - using ItemType = ContainerType::value_type; - const size_t item_size = sizeof(ItemType); - size_t total_size = 0; + using item_type = std::decay_t::value_type; + static_assert(std::is_trivially_copyable_v, + "vectors are cached as raw bytes"); + size_t total_size = 0; std::memcpy(&total_size, data_pos, sizeof(size_t)); data_pos += sizeof(size_t); - const size_t item_count = total_size / item_size; - arg.reserve(item_count); - for(size_t i = 0; i < item_count; ++i) + arg.resize(total_size / sizeof(item_type)); + if(total_size != 0) { - ItemType item; - std::memcpy(&item, data_pos + i * item_size, item_size); - arg.push_back(std::move(item)); + std::memcpy(arg.data(), data_pos, total_size); } data_pos += total_size; } From d9203304d074d5a704f5aca999e975ec70211c15 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 13:37:41 +0200 Subject: [PATCH 18/25] refactor(rocprofiler-systems): cache gpu_perf_counter entries as one vector counter_value is an 8-byte id and an 8-byte double with no padding, so its in-memory layout already equals the cached layout. The sample now writes the entries vector as a whole instead of a uint32_t count followed by a per-entry loop; a static_assert keeps counter_value free of padding. The entries prefix becomes the archive's size_t byte count, so a gpu_perf_counter record grows by 4 bytes; the golden test is updated. --- .../lib/core/trace_cache/tests/test_record_format.cpp | 10 +++++----- .../pmc/collectors/gpu_perf_counter/sample.hpp | 11 +---------- .../collectors/gpu_perf_counter/tests/test_sample.cpp | 10 ++++++---- .../library/pmc/collectors/gpu_perf_counter/types.hpp | 3 +++ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp index cf920b322dec..aeb951b4366a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp @@ -680,11 +680,11 @@ TEST(record_format, gpu_perf_counter_sample) 0x0C0C0C0CU, 0x5555555555555555UL, std::move(entries) }; - constexpr std::array k_expected{ - 0x0c, 0x0c, 0x0c, 0x0c, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, - 0x02, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0x33, 0x33, 0x33, 0x33, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, + constexpr std::array k_expected{ + 0x0c, 0x0c, 0x0c, 0x0c, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0x33, 0x33, 0x33, + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, }; expect_golden(record, k_expected); diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp index c7b2b654eb19..b423ec7f6284 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp @@ -42,16 +42,7 @@ struct sample : trace_cache::cacheable_t template void serialize(Archive& archive) { - auto count = static_cast(entries.size()); - archive(device_id, timestamp, count); - if constexpr(Archive::k_is_loading) - { - entries.resize(count); - } - for(auto& entry : entries) - { - archive(entry.counter_id, entry.value); - } + archive(device_id, timestamp, entries); } }; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp index 052476a43a3a..2966d02efb4e 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/tests/test_sample.cpp @@ -3,10 +3,12 @@ #include "core/trace_cache/archive.hpp" #include "library/pmc/collectors/gpu_perf_counter/sample.hpp" +#include "library/pmc/collectors/gpu_perf_counter/types.hpp" #include #include +#include #include using namespace rocprofsys::pmc::collectors::gpu_perf_counter; @@ -86,9 +88,9 @@ TEST_F(SdkPmcSampleTest, GetSizeEmpty) { const sample test_sample{ 0, 0, {} }; - // device_id(4) + timestamp(8) + num_entries(4) = 16 + // device_id(4) + timestamp(8) + entries byte count(8) = 20 const size_t expected = - sizeof(std::uint32_t) + sizeof(std::uint64_t) + sizeof(std::uint32_t); + sizeof(std::uint32_t) + sizeof(std::uint64_t) + sizeof(std::size_t); EXPECT_EQ(get_size(test_sample), expected); } @@ -96,9 +98,9 @@ TEST_F(SdkPmcSampleTest, GetSizeSingleEntry) { const sample test_sample{ 0, 0, { counter_value{ .counter_id = 10, .value = 1.0 } } }; - // header: 4 + 8 + 4 = 16 + // header: 4 + 8 + 8 = 20 // entry: counter_id(8) + double(8) = 16 - const size_t expected = 16 + sizeof(counter_id_t) + sizeof(double); + const size_t expected = 20 + sizeof(counter_id_t) + sizeof(double); EXPECT_EQ(get_size(test_sample), expected); } diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/types.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/types.hpp index 2f5b7a0ff87f..08bdf18de5ee 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/types.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/types.hpp @@ -33,6 +33,9 @@ struct counter_value double value{ 0.0 }; }; +static_assert(sizeof(counter_value) == sizeof(counter_id_t) + sizeof(double), + "counter_value is cached as raw bytes and must not have padding"); + using metrics = std::vector; struct counter_definition From 14ff24ead675b9cb1ba2b2cd62510f3fe4c092da Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Fri, 23 Oct 2026 13:37:41 +0200 Subject: [PATCH 19/25] refactor(rocprofiler-systems): cache std::string and enums directly, drop k_is_loading Archives now accept std::string (same encoding as std::string_view) and enums (written as their underlying integer, same bytes). The nic sample's device name and record_header's type are written directly, so no record needs a temporary value converted back after reading, and Archive::k_is_loading is removed together with its test. --- .../source/lib/core/trace_cache/archive.hpp | 11 +-- .../core/trace_cache/cache_type_traits.hpp | 8 +- .../source/lib/core/trace_cache/cacheable.hpp | 6 +- .../lib/core/trace_cache/record_header.hpp | 8 +- .../core/trace_cache/tests/test_archive.cpp | 82 ++----------------- .../core/trace_cache/tests/test_cacheable.cpp | 8 +- .../library/pmc/collectors/nic/sample.hpp | 8 +- 7 files changed, 28 insertions(+), 103 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 271d91b0ff5f..071e4a29338a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -24,8 +24,7 @@ concept archive_serializable = struct binary_size_archive { - static constexpr bool k_is_loading = false; - std::size_t size = 0; + std::size_t size = 0; template constexpr void operator()(const Types&... values) @@ -50,9 +49,8 @@ struct binary_size_archive struct binary_write_archive { - static constexpr bool k_is_loading = false; - std::uint8_t* buffer = nullptr; - std::size_t position = 0; + std::uint8_t* buffer = nullptr; + std::size_t position = 0; template void operator()(const Types&... values) @@ -84,8 +82,7 @@ struct binary_write_archive /** string_view and span fields are read as views into the buffer. */ struct binary_read_archive { - static constexpr bool k_is_loading = true; - std::uint8_t* cursor = nullptr; + std::uint8_t* cursor = nullptr; template void operator()(Types&... values) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp index 4df12253c4df..c522ae789042 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -52,8 +53,8 @@ template inline constexpr bool is_array_v = is_array::value; template -static constexpr bool is_string_view_v = - std::is_same_v, std::string_view>; +static constexpr bool is_string_v = std::is_same_v, std::string_view> || + std::is_same_v, std::string>; template struct is_optional : std::false_type @@ -69,7 +70,8 @@ inline constexpr bool is_optional_v = is_optional::value; template inline constexpr bool is_supported_type_v = is_span_v || std::is_integral_v || std::is_floating_point_v || - is_string_view_v || is_vector_v || is_optional_v || is_array_v; + is_string_v || is_vector_v || is_optional_v || is_array_v || + std::is_enum_v; template concept supported_cache_type = is_supported_type_v>; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 3eb59ff580c5..4a8906f664bd 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -57,7 +57,7 @@ get_size(Type&& val) { using DecayedType = std::decay_t; - if constexpr(type_traits::is_string_view_v) + if constexpr(type_traits::is_string_v) { return val.size() + sizeof(char) + sizeof(size_t); } @@ -92,7 +92,7 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) auto* dest = buffer + position; - if constexpr(type_traits::is_string_view_v) + if constexpr(type_traits::is_string_v) { const size_t data_size = value.size(); const size_t entry_size = sizeof(size_t) + data_size + sizeof(char); @@ -138,7 +138,7 @@ parse_value(std::uint8_t*& data_pos, Type& arg) { using DecayedType = std::decay_t; - if constexpr(type_traits::is_string_view_v) + if constexpr(type_traits::is_string_v) { size_t string_size = 0; std::memcpy(&string_size, data_pos, sizeof(size_t)); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp index 9019c84465f9..d1d3dab14b35 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/record_header.hpp @@ -7,7 +7,6 @@ #include #include -#include namespace rocprofsys::trace_cache { @@ -22,12 +21,7 @@ struct record_header template constexpr void serialize(Archive& archive) { - auto raw_type = static_cast>(type); - archive(raw_type, size); - if constexpr(Archive::k_is_loading) - { - type = static_cast(raw_type); - } + archive(type, size); } }; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp index 2f9ca0d7a1c3..11d67c2b27fb 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -25,14 +25,13 @@ namespace enum class test_record_id : std::uint32_t { - scalar_test = 1, - string_test = 2, - vector_test = 3, - optional_test = 4, - span_test = 5, - derived_test = 6, - counted_entries_test = 7, - nested_test = 8, + scalar_test = 1, + string_test = 2, + vector_test = 3, + optional_test = 4, + span_test = 5, + derived_test = 6, + nested_test = 8, }; struct scalar_record @@ -130,35 +129,6 @@ struct derived_record : base_record } }; -struct entry -{ - std::uint64_t id = 0; - double value = 0.0; -}; - -struct counted_entries_record -{ - static constexpr auto type_identifier = test_record_id::counted_entries_test; - - std::uint32_t device = 0; - std::vector entries; - - template - void serialize(Archive& archive) - { - auto count = static_cast(entries.size()); - archive(device, count); - if constexpr(Archive::k_is_loading) - { - entries.resize(count); - } - for(std::size_t idx = 0; idx < entries.size(); ++idx) - { - archive(entries[idx].id, entries[idx].value); - } - } -}; - struct inner_values { std::uint32_t alpha = 0; @@ -348,44 +318,6 @@ TEST(archive, derived_record_serializes_base_first) EXPECT_EQ(read_buffer - buffer.data(), static_cast(total_size)); } -TEST(archive, is_loading_branch_for_counted_entries) -{ - std::array buffer{}; - counted_entries_record orig; - orig.device = 1U; - orig.entries.push_back({ .id = 100, .value = 1.5 }); - orig.entries.push_back({ .id = 200, .value = 2.5 }); - - const std::size_t size = get_size(orig); - serialize(buffer.data(), orig); - - std::uint8_t* read_buffer = buffer.data(); - const counted_entries_record read = deserialize(read_buffer); - - EXPECT_EQ(read.device, 1U); - EXPECT_EQ(read.entries.size(), 2U); - EXPECT_EQ(read.entries[0].id, 100); - EXPECT_DOUBLE_EQ(read.entries[0].value, 1.5); - EXPECT_EQ(read.entries[1].id, 200); - EXPECT_DOUBLE_EQ(read.entries[1].value, 2.5); - EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); - - buffer.fill(0); - counted_entries_record orig_empty; - orig_empty.device = 2U; - - const std::size_t size_empty = get_size(orig_empty); - serialize(buffer.data(), orig_empty); - - read_buffer = buffer.data(); - const counted_entries_record read_empty = - deserialize(read_buffer); - - EXPECT_EQ(read_empty.device, 2U); - EXPECT_EQ(read_empty.entries.size(), 0U); - EXPECT_EQ(read_buffer - buffer.data(), static_cast(size_empty)); -} - TEST(archive, nested_struct_serializes_itself) { std::array buffer{}; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp index 59d35a54cf7e..e352a2cc0b0a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp @@ -919,10 +919,16 @@ TEST(type_traits_test, is_supported_type_v) EXPECT_TRUE( rocprofsys::trace_cache::type_traits::is_supported_type_v>); + EXPECT_TRUE(rocprofsys::trace_cache::type_traits::is_supported_type_v); + + enum class custom_enum : std::uint16_t + { + }; + EXPECT_TRUE(rocprofsys::trace_cache::type_traits::is_supported_type_v); + struct custom_type {}; EXPECT_FALSE(rocprofsys::trace_cache::type_traits::is_supported_type_v); - EXPECT_FALSE(rocprofsys::trace_cache::type_traits::is_supported_type_v); } TEST_F(cacheable_test, get_buffered_storage_filename) diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp index 7d4646216961..07cb112d67bf 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp @@ -8,7 +8,6 @@ #include #include -#include namespace rocprofsys::pmc::collectors::nic { @@ -44,12 +43,7 @@ struct sample : trace_cache::cacheable_t template void serialize(Archive& archive) { - std::string_view name_view = device_name; - archive(enabled_metric, device_id, name_view, timestamp, metric_values); - if constexpr(Archive::k_is_loading) - { - device_name = std::string(name_view); - } + archive(enabled_metric, device_id, device_name, timestamp, metric_values); } }; From 94f42847261a5a76f1f936edb392d545cd095bb4 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Sat, 24 Oct 2026 18:13:41 +0200 Subject: [PATCH 20/25] fix(rocprofiler-systems): read only whole elements of a cached vector parse_value resized the vector to total_size / sizeof(item) elements and then copied total_size bytes. When the byte count is not a multiple of the element size, the copy ran past the end of the allocation (one stray byte after a whole uint32_t is a heap-buffer-overflow under ASan, and a count below one element wrote through a null data pointer). The code before this PR copied only whole elements. Copy item_count * sizeof(item) bytes and still advance the cursor by the whole byte count. store_value also skips the memcpy when there is nothing to copy, so an empty vector or a default string_view no longer passes a null source. The new test fails under ASan without the fix. Without a sanitizer it passes either way, because the overflow lands in malloc slack. --- .../source/lib/core/trace_cache/cacheable.hpp | 18 +++++++++++----- .../core/trace_cache/tests/test_cacheable.cpp | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 4a8906f664bd..97f6f5ed4161 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -97,7 +97,10 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) const size_t data_size = value.size(); const size_t entry_size = sizeof(size_t) + data_size + sizeof(char); std::memcpy(dest, &data_size, sizeof(size_t)); - std::memcpy(dest + sizeof(size_t), value.data(), data_size); + if(data_size != 0) + { + std::memcpy(dest + sizeof(size_t), value.data(), data_size); + } dest[entry_size - sizeof(char)] = '\0'; position += entry_size; } @@ -110,7 +113,10 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) const size_t header_size = sizeof(size_t); const size_t data_size = total_size - header_size; std::memcpy(dest, &data_size, sizeof(size_t)); - std::memcpy(dest + sizeof(size_t), value.data(), data_size); + if(data_size != 0) + { + std::memcpy(dest + sizeof(size_t), value.data(), data_size); + } position += total_size; } else if constexpr(type_traits::is_optional_v) @@ -155,10 +161,12 @@ parse_value(std::uint8_t*& data_pos, Type& arg) size_t total_size = 0; std::memcpy(&total_size, data_pos, sizeof(size_t)); data_pos += sizeof(size_t); - arg.resize(total_size / sizeof(item_type)); - if(total_size != 0) + // Only whole elements are read; a stray tail is skipped, never copied. + const size_t item_count = total_size / sizeof(item_type); + arg.resize(item_count); + if(item_count != 0) { - std::memcpy(arg.data(), data_pos, total_size); + std::memcpy(arg.data(), data_pos, item_count * sizeof(item_type)); } data_pos += total_size; } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp index e352a2cc0b0a..650cdc2b5821 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -541,6 +542,26 @@ TEST_F(cacheable_test, parse_value_empty_byte_array) EXPECT_TRUE(parsed_value.empty()); EXPECT_EQ(data_pos, buffer.data() + sizeof(size_t)); } +TEST_F(cacheable_test, parse_value_vector_skips_a_trailing_partial_element) +{ + // The byte count is one whole uint32_t plus one stray byte. Only whole elements are + // read; the cursor still moves past the whole count. + constexpr std::uint64_t k_byte_count = sizeof(std::uint32_t) + 1; + constexpr std::uint32_t k_whole = 0xDEADBEEF; + + std::memcpy(buffer.data(), &k_byte_count, sizeof(k_byte_count)); + std::memcpy(buffer.data() + sizeof(k_byte_count), &k_whole, sizeof(k_whole)); + buffer[sizeof(k_byte_count) + sizeof(k_whole)] = 0xAA; + + std::uint8_t* data_pos = buffer.data(); + std::vector parsed; + rocprofsys::trace_cache::utility::parse_value(data_pos, parsed); + + ASSERT_EQ(parsed.size(), 1U); + EXPECT_EQ(parsed.front(), k_whole); + EXPECT_EQ(data_pos, buffer.data() + sizeof(k_byte_count) + k_byte_count); +} + TEST_F(cacheable_test, parse_multiple_values) { const int int_val = 42; From 049ee3e9c2bf7ede3877b430e8af6aaf900324d5 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Sat, 24 Oct 2026 18:13:41 +0200 Subject: [PATCH 21/25] refactor(rocprofiler-systems): list the gpu metrics layout in the gpu sample gpu::metrics is a backend type, and it was the only backend type that defined the field order of a trace cache record. CPU, NIC and hipFile define that order in their collectors. Remove serialize() from gpu::metrics and list its fields in gpu::sample::serialize(), so the backend no longer carries the cache byte layout. The fields are archived in the same order as before, so the bytes do not change; the golden test for gpu_pmc_sample passes unchanged. --- .../source/lib/backends/amd_smi/gpu_types.hpp | 11 ----------- .../library/pmc/collectors/gpu/sample.hpp | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp b/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp index 09dd9a126151..82b978df61b8 100644 --- a/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp +++ b/projects/rocprofiler-systems/source/lib/backends/amd_smi/gpu_types.hpp @@ -88,17 +88,6 @@ struct metrics std::uint32_t gfx_clock_mhz = 0; // current_gfxclk (MHz) std::uint32_t mem_clock_mhz = 0; // current_uclk (MHz) - - template - void serialize(Archive& archive) - { - archive(average_socket_power, current_socket_power, memory_usage, - hotspot_temperature, edge_temperature, gfx_activity, umc_activity, - mm_activity, xcp_stats, vcn_activity, jpeg_activity, xgmi.link.width, - xgmi.link.speed, xgmi.data_acc.read, xgmi.data_acc.write, pcie.link.width, - pcie.link.speed, pcie.bandwidth.acc, pcie.bandwidth.inst, sdma_usage, - gfx_clock_mhz, mem_clock_mhz); - } }; template diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp index 17b96a05c911..44b6661039b9 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp @@ -39,7 +39,20 @@ struct sample : trace_cache::cacheable_t template void serialize(Archive& archive) { - archive(enabled_metric, device_id, timestamp, metric_values); + // The byte layout of the cache: gpu::metrics is a backend type and does not + // serialize itself, so its fields are listed here in byte order. + archive(enabled_metric, device_id, timestamp, metric_values.average_socket_power, + metric_values.current_socket_power, metric_values.memory_usage, + metric_values.hotspot_temperature, metric_values.edge_temperature, + metric_values.gfx_activity, metric_values.umc_activity, + metric_values.mm_activity, metric_values.xcp_stats, + metric_values.vcn_activity, metric_values.jpeg_activity, + metric_values.xgmi.link.width, metric_values.xgmi.link.speed, + metric_values.xgmi.data_acc.read, metric_values.xgmi.data_acc.write, + metric_values.pcie.link.width, metric_values.pcie.link.speed, + metric_values.pcie.bandwidth.acc, metric_values.pcie.bandwidth.inst, + metric_values.sdma_usage, metric_values.gfx_clock_mhz, + metric_values.mem_clock_mhz); } }; From 3d1f0aa5546299b44671435b727cd9a06a218df8 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Sat, 24 Oct 2026 18:30:15 +0200 Subject: [PATCH 22/25] fix(rocprofiler-systems): reject record fields the archives would copy wrongly Review follow-ups for the trace cache archives. - A record is serializable only when it declares serialize(Archive&) itself. A record derived from another one without its own serialize used to inherit the base one and silently skip its own fields, and a serialize that takes the archive by value lost the position. archive_serializable now checks the signature and the declaring class. - A field that is copied as raw bytes must be plain data: numbers, scoped enums and arrays of them. A struct has to opt in with explicitly_raw (xcp_metrics and counter_value do, next to the records that use them). std::string, string_view, vectors and structs with pointers or padding are rejected at compile time instead of being copied as bytes. is_enum_class no longer fails to instantiate for a type that is not an enum. - is_string_v becomes the string_like concept. - expect_golden stops with ASSERT_EQ when a record grew, instead of writing past its expected bytes. - The span read branch of the archive is removed: no record in this PR reads a span. It comes back with the record that needs it. - Document the trailing '\0' of stored strings, which consumers rely on. - Compile-time tests for the checks above; the optional access in test_archive.cpp no longer calls value() after the check. --- .../source/lib/core/trace_cache/archive.hpp | 34 ++++---- .../core/trace_cache/cache_type_traits.hpp | 35 ++++++-- .../source/lib/core/trace_cache/cacheable.hpp | 40 ++++++--- .../core/trace_cache/tests/test_archive.cpp | 85 +++++++++---------- .../core/trace_cache/tests/test_cacheable.cpp | 46 +++++++++- .../trace_cache/tests/test_record_format.cpp | 2 +- .../library/pmc/collectors/gpu/sample.hpp | 17 ++++ .../collectors/gpu_perf_counter/sample.hpp | 11 +++ 8 files changed, 187 insertions(+), 83 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp index 071e4a29338a..c4ef0705bd4b 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/archive.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace rocprofsys::trace_cache @@ -18,9 +17,15 @@ namespace rocprofsys::trace_cache static_assert(std::is_same_v, "trace cache format assumes size_t is std::uint64_t"); +/** + * True when Type itself declares `void serialize(Archive&)`. A serialize inherited from a + * base class, or one that takes the archive by value, does not count: either would + * compile and silently skip the fields of the derived record or lose the position. + */ template concept archive_serializable = - requires(Type& value, Archive& archive) { value.serialize(archive); }; + std::is_same_v), + void (Type::*)(Archive&)>; struct binary_size_archive { @@ -79,7 +84,10 @@ struct binary_write_archive } }; -/** string_view and span fields are read as views into the buffer. */ +/** + * string_view fields are read as views into the buffer. Stored strings end with a '\0' + * that consumers rely on (for example rocpd_processor passes `.data()` as a C string). + */ struct binary_read_archive { std::uint8_t* cursor = nullptr; @@ -102,14 +110,6 @@ struct binary_read_archive value.serialize(*this); cur = cursor; } - else if constexpr(std::is_same_v>) - { - std::size_t byte_count = 0; - std::memcpy(&byte_count, cur, sizeof(std::size_t)); - cur += sizeof(std::size_t); - value = { reinterpret_cast(cur), byte_count }; - cur += byte_count; - } else { utility::parse_value(cur, value); @@ -119,14 +119,10 @@ struct binary_read_archive template concept archive_record = - std::is_default_constructible_v && - requires(T& record, binary_size_archive& sizer, binary_write_archive& writer, - binary_read_archive& reader) { - { T::type_identifier }; - record.serialize(sizer); - record.serialize(writer); - record.serialize(reader); - }; + std::is_default_constructible_v && requires { T::type_identifier; } && + archive_serializable && + archive_serializable && + archive_serializable; // const_cast: the size and write archives only read through serialize(). // flatten, not inline (inlining drops it): one fully inlined function per record type. diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp index c522ae789042..5eeac8e3ef98 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp @@ -53,8 +53,8 @@ template inline constexpr bool is_array_v = is_array::value; template -static constexpr bool is_string_v = std::is_same_v, std::string_view> || - std::is_same_v, std::string>; +concept string_like = std::is_same_v, std::string_view> || + std::is_same_v, std::string>; template struct is_optional : std::false_type @@ -70,21 +70,44 @@ inline constexpr bool is_optional_v = is_optional::value; template inline constexpr bool is_supported_type_v = is_span_v || std::is_integral_v || std::is_floating_point_v || - is_string_v || is_vector_v || is_optional_v || is_array_v || + string_like || is_vector_v || is_optional_v || is_array_v || std::is_enum_v; template concept supported_cache_type = is_supported_type_v>; +template > +struct is_enum_class : std::false_type +{}; + template -struct is_enum_class -: std::bool_constant && - !std::is_convertible_v>> +struct is_enum_class +: std::bool_constant>> {}; template inline constexpr bool is_enum_class_v = is_enum_class::value; +/** + * Opt-in for a struct whose bytes may be copied as they are: trivially copyable, no + * padding, no pointers. Specialize it next to the record that uses the struct. + */ +template +struct explicitly_raw : std::false_type +{}; + +/** Types that are cached by copying their bytes: numbers, scoped enums, opted-in structs. + */ +template +struct is_raw_cacheable +: std::bool_constant || is_enum_class_v || + explicitly_raw::value> +{}; + +template +struct is_raw_cacheable> : is_raw_cacheable +{}; + template concept has_type_identifier = is_enum_class_v && requires { { T::type_identifier } -> std::convertible_to; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 97f6f5ed4161..de0127527935 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -57,15 +57,19 @@ get_size(Type&& val) { using DecayedType = std::decay_t; - if constexpr(type_traits::is_string_v) + if constexpr(type_traits::string_like) { return val.size() + sizeof(char) + sizeof(size_t); } else if constexpr(type_traits::is_vector_v || type_traits::is_span_v) { - using ContainerType = std::decay_t; - const size_t item_size = sizeof(typename ContainerType::value_type); + using container_type = std::decay_t; + static_assert( + type_traits::is_raw_cacheable::value, + "vectors and spans are cached as raw bytes: the element must be a " + "number, a scoped enum or a struct marked explicitly_raw"); + const size_t item_size = sizeof(typename container_type::value_type); const size_t item_count = val.size(); const size_t total_size = item_count * item_size; @@ -79,6 +83,10 @@ get_size(Type&& val) } else { + static_assert( + type_traits::is_raw_cacheable::value, + "field type is copied as raw bytes: give it serialize(Archive&) or, " + "if it is plain data, mark it explicitly_raw"); return sizeof(DecayedType); } } @@ -92,7 +100,7 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) auto* dest = buffer + position; - if constexpr(type_traits::is_string_v) + if constexpr(type_traits::string_like) { const size_t data_size = value.size(); const size_t entry_size = sizeof(size_t) + data_size + sizeof(char); @@ -101,14 +109,17 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) { std::memcpy(dest + sizeof(size_t), value.data(), data_size); } - dest[entry_size - sizeof(char)] = '\0'; + dest[entry_size - sizeof(char)] = + '\0'; // part of the format, see binary_read_archive position += entry_size; } else if constexpr(type_traits::is_vector_v || type_traits::is_span_v) { - static_assert(std::is_trivially_copyable_v, - "vectors are cached as raw bytes"); + static_assert( + type_traits::is_raw_cacheable::value, + "vectors and spans are cached as raw bytes: the element must be a " + "number, a scoped enum or a struct marked explicitly_raw"); const size_t total_size = get_size(value); const size_t header_size = sizeof(size_t); const size_t data_size = total_size - header_size; @@ -132,6 +143,10 @@ store_value(const Type& value, std::uint8_t* buffer, size_t& position) } else { + static_assert( + type_traits::is_raw_cacheable::value, + "field type is copied as raw bytes: give it serialize(Archive&) or, " + "if it is plain data, mark it explicitly_raw"); std::memcpy(dest, &value, sizeof(DecayedType)); position += sizeof(DecayedType); } @@ -144,7 +159,7 @@ parse_value(std::uint8_t*& data_pos, Type& arg) { using DecayedType = std::decay_t; - if constexpr(type_traits::is_string_v) + if constexpr(type_traits::string_like) { size_t string_size = 0; std::memcpy(&string_size, data_pos, sizeof(size_t)); @@ -156,8 +171,9 @@ parse_value(std::uint8_t*& data_pos, Type& arg) type_traits::is_span_v) { using item_type = std::decay_t::value_type; - static_assert(std::is_trivially_copyable_v, - "vectors are cached as raw bytes"); + static_assert(type_traits::is_raw_cacheable::value, + "vectors and spans are cached as raw bytes: the element must be a " + "number, a scoped enum or a struct marked explicitly_raw"); size_t total_size = 0; std::memcpy(&total_size, data_pos, sizeof(size_t)); data_pos += sizeof(size_t); @@ -187,6 +203,10 @@ parse_value(std::uint8_t*& data_pos, Type& arg) } else { + static_assert( + type_traits::is_raw_cacheable::value, + "field type is copied as raw bytes: give it serialize(Archive&) or, " + "if it is plain data, mark it explicitly_raw"); std::memcpy(&arg, data_pos, sizeof(DecayedType)); data_pos += sizeof(DecayedType); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp index 11d67c2b27fb..2686668d3122 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -29,11 +28,48 @@ enum class test_record_id : std::uint32_t string_test = 2, vector_test = 3, optional_test = 4, - span_test = 5, derived_test = 6, nested_test = 8, }; +// A record counts as serializable only when it declares its own serialize(Archive&). +struct base_with_serialize +{ + std::uint32_t first = 0; + + template + void serialize(Archive& archive) + { + archive(first); + } +}; + +struct derived_without_serialize : base_with_serialize +{ + std::uint32_t second = 0; +}; + +struct serialize_takes_archive_by_value +{ + std::uint32_t first = 0; + + template + void serialize(Archive archive) + { + archive(first); + } +}; + +static_assert(archive_serializable); +static_assert(archive_serializable); +static_assert(archive_serializable); +static_assert(!archive_serializable, + "an inherited serialize would skip the fields of the derived record"); +static_assert( + !archive_serializable, + "a serialize that takes the archive by value loses the position"); +static_assert(!archive_serializable); + struct scalar_record { static constexpr auto type_identifier = test_record_id::scalar_test; @@ -88,19 +124,6 @@ struct optional_record } }; -struct span_record -{ - static constexpr auto type_identifier = test_record_id::span_test; - - std::span bytes; - - template - void serialize(Archive& archive) - { - archive(bytes); - } -}; - struct base_record { static constexpr auto type_identifier = test_record_id::derived_test; @@ -246,7 +269,7 @@ TEST(archive, optional_engaged_and_empty) const optional_record read_engaged = deserialize(read_buf); ASSERT_TRUE(read_engaged.value.has_value()); - EXPECT_EQ(read_engaged.value.value(), 9876543210LL); + EXPECT_EQ(read_engaged.value, orig_engaged.value); buffer.fill(0); optional_record orig_empty; @@ -263,36 +286,6 @@ TEST(archive, optional_engaged_and_empty) EXPECT_EQ(size_engaged, size_empty + sizeof(std::int64_t)); } -TEST(archive, byte_span_reads_back_as_view) -{ - std::array buffer{}; - std::array orig_array = { 1.1, 2.2, 3.3 }; - const auto bytes_span = std::as_bytes(std::span(orig_array)); - - span_record orig; - orig.bytes = bytes_span; - - const std::size_t size = get_size(orig); - serialize(buffer.data(), orig); - - std::uint8_t* read_buffer = buffer.data(); - const span_record read = deserialize(read_buffer); - - EXPECT_EQ(read.bytes.size(), 3 * sizeof(double)); - EXPECT_GE(read.bytes.data(), reinterpret_cast(buffer.data())); - EXPECT_LT(read.bytes.data(), - reinterpret_cast(buffer.data() + buffer.size())); - - for(std::size_t idx = 0; idx < 3; ++idx) - { - double value = 0.0; - std::memcpy(&value, read.bytes.data() + idx * sizeof(double), sizeof(double)); - EXPECT_DOUBLE_EQ(value, orig_array[idx]); - } - - EXPECT_EQ(read_buffer - buffer.data(), static_cast(size)); -} - TEST(archive, derived_record_serializes_base_first) { std::array buffer{}; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp index 650cdc2b5821..8ea73a850254 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp @@ -9,12 +9,55 @@ #include #include #include +#include #include #include +#include +#include #include using namespace std::string_view_literals; +namespace type_traits = rocprofsys::trace_cache::type_traits; + +// Fields that are copied as raw bytes must be plain data. +enum class scoped_test_enum : std::uint8_t +{ + first +}; +struct plain_test_struct +{ + std::uint64_t id = 0; + double value = 0.0; +}; +struct marked_test_struct +{ + std::uint64_t id = 0; + double value = 0.0; +}; + +template <> +struct rocprofsys::trace_cache::type_traits::explicitly_raw +: std::true_type +{}; + +static_assert(type_traits::is_raw_cacheable::value); +static_assert(type_traits::is_raw_cacheable::value); +static_assert(type_traits::is_raw_cacheable::value); +static_assert(type_traits::is_raw_cacheable>::value); +static_assert(type_traits::is_raw_cacheable::value); +static_assert(type_traits::is_raw_cacheable>::value); +static_assert(!type_traits::is_raw_cacheable::value, + "a struct must opt in: padding or a pointer would be copied silently"); +static_assert(!type_traits::is_raw_cacheable::value, + "an unscoped enum has no fixed underlying type"); +static_assert(!type_traits::is_raw_cacheable::value); +static_assert(!type_traits::is_raw_cacheable::value); +static_assert(!type_traits::is_raw_cacheable>::value); +static_assert(!type_traits::is_raw_cacheable>::value, + "views point into memory that is not cached"); +static_assert(!type_traits::is_raw_cacheable>::value); + class cacheable_test : public ::testing::Test { protected: @@ -548,10 +591,11 @@ TEST_F(cacheable_test, parse_value_vector_skips_a_trailing_partial_element) // read; the cursor still moves past the whole count. constexpr std::uint64_t k_byte_count = sizeof(std::uint32_t) + 1; constexpr std::uint32_t k_whole = 0xDEADBEEF; + constexpr std::uint8_t k_stray_byte = 0xAA; std::memcpy(buffer.data(), &k_byte_count, sizeof(k_byte_count)); std::memcpy(buffer.data() + sizeof(k_byte_count), &k_whole, sizeof(k_whole)); - buffer[sizeof(k_byte_count) + sizeof(k_whole)] = 0xAA; + buffer[sizeof(k_byte_count) + sizeof(k_whole)] = k_stray_byte; std::uint8_t* data_pos = buffer.data(); std::vector parsed; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp index aeb951b4366a..0f9daf94657f 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_record_format.cpp @@ -38,7 +38,7 @@ template void expect_golden(const Record& record, const std::array& expected) { - EXPECT_EQ(get_size(record), N); + ASSERT_EQ(get_size(record), N); std::array buffer = {}; serialize(buffer.data(), record); diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp index 44b6661039b9..c33d35ecc60e 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp @@ -3,10 +3,13 @@ #pragma once +#include "backends/amd_smi/gpu_types.hpp" +#include "core/trace_cache/cache_type_traits.hpp" #include "core/trace_cache/sample_type.hpp" #include "library/pmc/collectors/gpu/types.hpp" #include +#include namespace rocprofsys::pmc::collectors::gpu { @@ -58,6 +61,20 @@ struct sample : trace_cache::cacheable_t } // namespace rocprofsys::pmc::collectors::gpu +namespace rocprofsys::trace_cache::type_traits +{ + +// xcp_stats is cached as raw bytes: it is plain arrays of uint16_t, without padding. +static_assert(std::has_unique_object_representations_v< + backends::amd_smi::gpu::metrics::xcp_metrics>, + "xcp_metrics is cached as raw bytes and must not have padding"); + +template <> +struct explicitly_raw : std::true_type +{}; + +} // namespace rocprofsys::trace_cache::type_traits + namespace rocprofsys::trace_cache { diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp index b423ec7f6284..c19a7aa10297 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -48,6 +49,16 @@ struct sample : trace_cache::cacheable_t } // namespace rocprofsys::pmc::collectors::gpu_perf_counter +namespace rocprofsys::trace_cache::type_traits +{ + +// counter_value has no padding (see its static_assert), so its bytes are the format. +template <> +struct explicitly_raw : std::true_type +{}; + +} // namespace rocprofsys::trace_cache::type_traits + namespace rocprofsys::trace_cache { From 4cfd7b2eb59051359c88c0c4e66c989cfa177404 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Mon, 26 Oct 2026 12:11:58 +0100 Subject: [PATCH 23/25] refactor(rocprofiler-systems): dispatch cached records by type id, drop cacheable_t type_registry::dispatch resolves a record's type id once with a fold over the supported types and hands the deserialized record to a visitor. It replaces the map of std::function, the std::variant, the std::visit cast to cacheable_t& and the second switch plus static_cast in sample_processor_t. - storage_parser::load calls handle_sample on the processor through dispatch; the sample_processor and sample_visitor concepts check every supported type at compile time. - cacheable_t and all its empty bases are removed. - buffer_size and flush_threshold use the data_size literals, and the flush timeout is a std::chrono::milliseconds. Behavior change: a record with a type id that is not supported now throws std::runtime_error out of load() instead of being skipped. memory_allocate_sample is no longer guarded by ROCPROFILER_VERSION on the read path. --- .../core/trace_cache/cache_type_traits.hpp | 12 +- .../source/lib/core/trace_cache/cacheable.hpp | 29 ++- .../lib/core/trace_cache/sample_processor.hpp | 55 ----- .../lib/core/trace_cache/sample_type.hpp | 16 +- .../lib/core/trace_cache/storage_parser.hpp | 21 +- .../core/trace_cache/tests/mocked_types.hpp | 10 +- .../tests/test_cache_integration.cpp | 78 +++----- .../trace_cache/tests/test_storage_parser.cpp | 84 ++++---- .../trace_cache/tests/test_type_registry.cpp | 189 ++++++++---------- .../lib/core/trace_cache/type_registry.hpp | 61 +++--- .../library/pmc/collectors/cpu/sample.hpp | 2 +- .../library/pmc/collectors/gpu/sample.hpp | 2 +- .../collectors/gpu_perf_counter/sample.hpp | 2 +- .../library/pmc/collectors/hipfile/sample.hpp | 2 +- .../library/pmc/collectors/nic/sample.hpp | 3 +- 15 files changed, 219 insertions(+), 347 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp index 5eeac8e3ef98..23d37835bfab 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp @@ -113,10 +113,16 @@ concept has_type_identifier = is_enum_class_v && requires { { T::type_identifier } -> std::convertible_to; }; -template -concept sample_processor = requires(T t, TypeIdentifierEnum e, const CacheableType& c) { - { t.execute_sample_processing(e, c) } -> std::same_as; +template +concept sample_handler = requires(T& t, const SampleType& sample) { + { t.handle_sample(sample) } -> std::same_as; }; +template +concept sample_processor = (sample_handler && ...); + +template +concept sample_visitor = (std::invocable && ...); + } // namespace type_traits } // namespace rocprofsys::trace_cache diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index de0127527935..5c7937cd8e78 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #pragma once +#include "common/units/data_size.hpp" #include "core/trace_cache/cache_type_traits.hpp" #include @@ -20,33 +21,29 @@ using namespace std::chrono_literals; namespace rocprofsys::trace_cache { -struct cacheable_t -{ - cacheable_t() = default; -}; +using namespace common::units::literals; -constexpr size_t MByte = 1024 * 1024; -constexpr size_t buffer_size = 100 * MByte; -constexpr size_t flush_threshold = 80 * MByte; -constexpr auto CACHE_FILE_FLUSH_TIMEOUT = 10ms; +constexpr size_t k_buffer_size = static_cast((100_mib).to_bytes()); +constexpr size_t k_flush_threshold = static_cast((80_mib).to_bytes()); +constexpr auto k_cache_file_flush_timeout = 10ms; -constexpr auto ABSOLUTE = "ABS"; -constexpr auto PERCENTAGE = "%"; +constexpr auto k_absolute = "ABS"; +constexpr auto k_percentage = "%"; -using buffer_array_t = std::array; +using buffer_array_t = std::array; -const auto tmp_directory = std::string{ "/tmp/" }; +const auto k_tmp_directory = std::string{ "/tmp/" }; namespace utility { -const auto get_buffered_storage_filename = [](const int& ppid, const int& pid) { - return std::string{ tmp_directory + "buffered_storage_" + std::to_string(ppid) + "_" + - std::to_string(pid) + ".bin" }; +const auto k_get_buffered_storage_filename = [](const int& ppid, const int& pid) { + return std::string{ k_tmp_directory + "buffered_storage_" + std::to_string(ppid) + + "_" + std::to_string(pid) + ".bin" }; }; const auto get_metadata_filepath = [](const int& ppid, const int& pid) { - return std::string{ tmp_directory + "metadata_" + std::to_string(ppid) + "_" + + return std::string{ k_tmp_directory + "metadata_" + std::to_string(ppid) + "_" + std::to_string(pid) + ".json" }; }; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_processor.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_processor.hpp index d0522699191b..6c8fa0d661a6 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_processor.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_processor.hpp @@ -7,8 +7,6 @@ #include "core/trace_cache/sample_processor_interface.hpp" #include "core/trace_cache/sample_type.hpp" -#include - #include #include #include @@ -55,59 +53,6 @@ struct sample_processor_t return m_processors.empty(); } - ROCPROFSYS_INLINE void execute_sample_processing( - type_identifier_t type_identifier, const trace_cache::cacheable_t& sample) const - { - switch(type_identifier) - { - case type_identifier_t::region: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::kernel_dispatch: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::scratch_memory: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::memory_copy: - handle_sample(static_cast(sample)); - break; -#if ROCPROFILER_VERSION >= 600 - case type_identifier_t::memory_alloc: - handle_sample(static_cast(sample)); - break; -#endif - case type_identifier_t::in_time_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::pmc_event_with_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::gpu_pmc_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::ainic_pmc_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::cpu_pmc_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::hipfile_pmc_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::gpu_perf_counter_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::backtrace_region_sample: - handle_sample(static_cast(sample)); - break; - case type_identifier_t::kfd_sample: - handle_sample(static_cast(sample)); - break; - default: throw std::runtime_error("Unsupported sample type"); - } - } - private: std::vector> m_processors; }; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp index 985552c81017..c7b1e5ef7a9e 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/sample_type.hpp @@ -34,7 +34,7 @@ enum class type_identifier_t : std::uint32_t fragmented_space = 0xFFFF }; -struct kernel_dispatch_sample : cacheable_t +struct kernel_dispatch_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::kernel_dispatch; @@ -100,7 +100,7 @@ struct kernel_dispatch_sample : cacheable_t } }; -struct scratch_memory_sample : cacheable_t +struct scratch_memory_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::scratch_memory; @@ -149,7 +149,7 @@ struct scratch_memory_sample : cacheable_t } }; -struct memory_copy_sample : cacheable_t +struct memory_copy_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::memory_copy; @@ -198,7 +198,7 @@ struct memory_copy_sample : cacheable_t } }; -struct memory_allocate_sample : cacheable_t +struct memory_allocate_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::memory_alloc; @@ -244,7 +244,7 @@ struct memory_allocate_sample : cacheable_t } }; -struct region_sample : cacheable_t +struct region_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::region; @@ -284,7 +284,7 @@ struct region_sample : cacheable_t } }; -struct in_time_sample : cacheable_t +struct in_time_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::in_time_sample; @@ -362,7 +362,7 @@ struct pmc_event_with_sample : in_time_sample } }; -struct backtrace_region_sample : cacheable_t +struct backtrace_region_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::backtrace_region_sample; @@ -405,7 +405,7 @@ struct backtrace_region_sample : cacheable_t } }; -struct kfd_sample : cacheable_t +struct kfd_sample { static constexpr type_identifier_t type_identifier = type_identifier_t::kfd_sample; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp index 4c76efd10d7b..a4fab0041df4 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/storage_parser.hpp @@ -40,8 +40,7 @@ class storage_parser {} template - requires type_traits::sample_processor + requires type_traits::sample_processor void load(std::shared_ptr _type_processing, progress::progress_callback _progress_cb = {}) { @@ -124,21 +123,9 @@ class storage_parser auto* data = sample.data(); - auto sample_value = m_registry.get_type(header.type, data); - if(sample_value.has_value()) - { - _type_processing->execute_sample_processing( - header.type, std::visit( - [](auto& arg) -> cacheable_t& { - return static_cast(arg); - }, - sample_value.value())); - } - else - { - LOG_TRACE("Unknown sample type encountered, skipping"); - continue; - } + m_registry.dispatch(header.type, data, [&](const auto& record) { + _type_processing->handle_sample(record); + }); } ifs.close(); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp index ed19f5ee56e0..eb57c2f2aaac 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/mocked_types.hpp @@ -19,7 +19,7 @@ enum class test_type_identifier_t : std::uint32_t sample_type_5 = 5, fragmented_space = 0xFFFF }; -struct test_sample_1 : public rocprofsys::trace_cache::cacheable_t +struct test_sample_1 { static constexpr test_type_identifier_t type_identifier = test_type_identifier_t::sample_type_1; @@ -45,7 +45,7 @@ struct test_sample_1 : public rocprofsys::trace_cache::cacheable_t } }; -struct test_sample_2 : public rocprofsys::trace_cache::cacheable_t +struct test_sample_2 { static constexpr test_type_identifier_t type_identifier = test_type_identifier_t::sample_type_2; @@ -86,7 +86,7 @@ struct test_sample_2 : public rocprofsys::trace_cache::cacheable_t } }; -struct test_sample_3 : public rocprofsys::trace_cache::cacheable_t +struct test_sample_3 { static constexpr test_type_identifier_t type_identifier = test_type_identifier_t::sample_type_3; @@ -107,7 +107,7 @@ struct test_sample_3 : public rocprofsys::trace_cache::cacheable_t } }; -struct test_sample_4 : public rocprofsys::trace_cache::cacheable_t +struct test_sample_4 { static constexpr test_type_identifier_t type_identifier = test_type_identifier_t::sample_type_4; @@ -128,7 +128,7 @@ struct test_sample_4 : public rocprofsys::trace_cache::cacheable_t } }; -struct test_sample_5 : public rocprofsys::trace_cache::cacheable_t +struct test_sample_5 { static constexpr test_type_identifier_t type_identifier = test_type_identifier_t::sample_type_5; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp index 2352b1f9c5b1..5dadbb7280d3 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cache_integration.cpp @@ -140,53 +140,39 @@ class integration_sample_processor_t } } - void execute_sample_processing(test_type_identifier_t type_identifier, - const rocprofsys::trace_cache::cacheable_t& value) + void handle_sample(const test_sample_1& sample) { - switch(type_identifier) - { - case test_type_identifier_t::sample_type_1: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - m_sample_1_count++; - check_sample_1(sample); - break; - } - case test_type_identifier_t::sample_type_2: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - m_sample_2_count++; - check_sample_2(sample); - break; - } - case test_type_identifier_t::sample_type_3: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - m_sample_3_count++; - check_sample_3(sample); - break; - } - case test_type_identifier_t::sample_type_4: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - m_sample_4_count++; - check_sample_4(sample); - break; - } - case test_type_identifier_t::sample_type_5: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - m_sample_5_count++; - check_sample_5(sample); - break; - } - default: break; - } + const std::lock_guard lock(m_data_mutex); + m_sample_1_count++; + check_sample_1(sample); + } + + void handle_sample(const test_sample_2& sample) + { + const std::lock_guard lock(m_data_mutex); + m_sample_2_count++; + check_sample_2(sample); + } + + void handle_sample(const test_sample_3& sample) + { + const std::lock_guard lock(m_data_mutex); + m_sample_3_count++; + check_sample_3(sample); + } + + void handle_sample(const test_sample_4& sample) + { + const std::lock_guard lock(m_data_mutex); + m_sample_4_count++; + check_sample_4(sample); + } + + void handle_sample(const test_sample_5& sample) + { + const std::lock_guard lock(m_data_mutex); + m_sample_5_count++; + check_sample_5(sample); } int get_sample_1_count() const { return m_sample_1_count.load(); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp index e3a503f0ca65..0eb4860d9675 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp @@ -48,53 +48,39 @@ class sample_processor_t m_expected_samples_5 = samples; } - void execute_sample_processing(test_type_identifier_t type_identifier, - const rocprofsys::trace_cache::cacheable_t& value) + void handle_sample(const test_sample_1& sample) { - switch(type_identifier) - { - case test_type_identifier_t::sample_type_1: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - const size_t idx = m_sample_1_count++; - check_sample_1(idx, sample); - break; - } - case test_type_identifier_t::sample_type_2: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - const size_t idx = m_sample_2_count++; - check_sample_2(idx, sample); - break; - } - case test_type_identifier_t::sample_type_3: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - const size_t idx = m_sample_3_count++; - check_sample_3(idx, sample); - break; - } - case test_type_identifier_t::sample_type_4: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - const size_t idx = m_sample_4_count++; - check_sample_4(idx, sample); - break; - } - case test_type_identifier_t::sample_type_5: - { - const auto& sample = static_cast(value); - const std::lock_guard lock(m_data_mutex); - const size_t idx = m_sample_5_count++; - check_sample_5(idx, sample); - break; - } - default: m_unknown_count++; break; - } + const std::lock_guard lock(m_data_mutex); + const size_t idx = m_sample_1_count++; + check_sample_1(idx, sample); + } + + void handle_sample(const test_sample_2& sample) + { + const std::lock_guard lock(m_data_mutex); + const size_t idx = m_sample_2_count++; + check_sample_2(idx, sample); + } + + void handle_sample(const test_sample_3& sample) + { + const std::lock_guard lock(m_data_mutex); + const size_t idx = m_sample_3_count++; + check_sample_3(idx, sample); + } + + void handle_sample(const test_sample_4& sample) + { + const std::lock_guard lock(m_data_mutex); + const size_t idx = m_sample_4_count++; + check_sample_4(idx, sample); + } + + void handle_sample(const test_sample_5& sample) + { + const std::lock_guard lock(m_data_mutex); + const size_t idx = m_sample_5_count++; + check_sample_5(idx, sample); } int get_sample_1_count() const { return m_sample_1_count.load(); } @@ -102,7 +88,6 @@ class sample_processor_t int get_sample_3_count() const { return m_sample_3_count.load(); } int get_sample_4_count() const { return m_sample_4_count.load(); } int get_sample_5_count() const { return m_sample_5_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) @@ -150,7 +135,6 @@ class sample_processor_t std::atomic m_sample_3_count{ 0 }; std::atomic m_sample_4_count{ 0 }; std::atomic m_sample_5_count{ 0 }; - std::atomic m_unknown_count{ 0 }; std::vector m_expected_samples_1; std::vector m_expected_samples_2; std::vector m_expected_samples_3; @@ -290,7 +274,7 @@ TEST_F(storage_parser_test, load_multiple_sample_types) EXPECT_EQ(std::remove(test_file_path.c_str()), 0); } -TEST_F(storage_parser_test, load_unsupported_sample_type) +TEST_F(storage_parser_test, load_unsupported_sample_type_throws) { const std::vector samples_1 = { test_sample_1(123, "mixed_test") }; const std::vector samples_2 = { test_sample_2(3.14159, 555), @@ -307,7 +291,7 @@ TEST_F(storage_parser_test, load_unsupported_sample_type) test_sample_2> parser(test_file_path); - EXPECT_NO_THROW(parser.load(processor)); + EXPECT_THROW(parser.load(processor), std::runtime_error); EXPECT_EQ(processor->get_sample_1_count(), 1); EXPECT_EQ(processor->get_sample_2_count(), 2); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp index 7fc00162c015..e77773a3a4a5 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp @@ -6,10 +6,40 @@ #include #include +#include +#include +#include +#include static_assert(rocprofsys::trace_cache::detail::are_unique<1, 2, 3>()); static_assert(!rocprofsys::trace_cache::detail::are_unique<1, 2, 1>()); +namespace +{ +// Serializes `value`, dispatches it under `type_id` and calls `check` with the record +// the visitor received, while the serialized buffer is still alive (string_view members +// point into it). Returns whether the visitor saw a `Sample`. +template +bool +dispatch_round_trip(const Registry& registry, test_type_identifier_t type_id, + const Sample& value, Check&& check) +{ + std::vector buffer(rocprofsys::trace_cache::get_size(value)); + rocprofsys::trace_cache::serialize(buffer.data(), value); + + auto* data = buffer.data(); + bool received = false; + registry.dispatch(type_id, data, [&](const auto& sample) { + if constexpr(std::is_same_v>) + { + received = true; + check(sample); + } + }); + return received; +} +} // namespace + class type_registry_test : public ::testing::Test { protected: @@ -18,97 +48,66 @@ class type_registry_test : public ::testing::Test type_registry; }; -TEST_F(type_registry_test, test_get_type_sample_1) +TEST_F(type_registry_test, test_dispatch_sample_1) { - const test_sample_1 test_value{ 42, "hello" }; - const size_t buffer_size = rocprofsys::trace_cache::get_size(test_value); - std::vector buffer(buffer_size); - rocprofsys::trace_cache::serialize(buffer.data(), test_value); - - auto* buffer_data = buffer.data(); - auto result = - type_registry.get_type(test_type_identifier_t::sample_type_1, buffer_data); - - ASSERT_TRUE(result.has_value()); - ASSERT_TRUE(std::holds_alternative(result.value())); + EXPECT_TRUE(dispatch_round_trip(type_registry, test_type_identifier_t::sample_type_1, + test_sample_1{ 42, "hello" }, + [](const test_sample_1& sample) { + EXPECT_EQ(sample.value, 42); + EXPECT_EQ(sample.text, "hello"); + })); +} - auto const sample_1 = std::get(result.value()); - EXPECT_EQ(sample_1.value, 42); - EXPECT_EQ(sample_1.text, "hello"); +TEST_F(type_registry_test, test_dispatch_sample_2) +{ + EXPECT_TRUE(dispatch_round_trip(type_registry, test_type_identifier_t::sample_type_2, + test_sample_2{ 3.14, 123 }, + [](const test_sample_2& sample) { + EXPECT_DOUBLE_EQ(sample.data, 3.14); + EXPECT_EQ(sample.sample_id, 123); + })); } -TEST_F(type_registry_test, test_get_type_sample_2) +TEST_F(type_registry_test, test_dispatch_calls_visitor_once) { const test_sample_2 test_value{ 3.14, 123 }; - const size_t buffer_size = rocprofsys::trace_cache::get_size(test_value); - std::vector buffer(buffer_size); + std::vector buffer(rocprofsys::trace_cache::get_size(test_value)); rocprofsys::trace_cache::serialize(buffer.data(), test_value); - auto* buffer_data = buffer.data(); - auto result = - type_registry.get_type(test_type_identifier_t::sample_type_2, buffer_data); - - ASSERT_TRUE(result.has_value()); - ASSERT_TRUE(std::holds_alternative(result.value())); + auto* data = buffer.data(); + int calls = 0; + type_registry.dispatch(test_type_identifier_t::sample_type_2, data, + [&](const auto&) { ++calls; }); - auto const sample_2 = std::get(result.value()); - EXPECT_DOUBLE_EQ(sample_2.data, 3.14); - EXPECT_EQ(sample_2.sample_id, 123); + EXPECT_EQ(calls, 1); } -TEST_F(type_registry_test, test_get_type_unknown_id) +TEST_F(type_registry_test, test_dispatch_unknown_id_throws_without_calling_visitor) { std::uint8_t dummy_data = 0; std::uint8_t* data = &dummy_data; + int calls = 0; - auto const result = - type_registry.get_type(test_type_identifier_t::fragmented_space, data); - - EXPECT_FALSE(result.has_value()); + EXPECT_THROW(type_registry.dispatch(test_type_identifier_t::fragmented_space, data, + [&](const auto&) { ++calls; }), + std::runtime_error); + EXPECT_EQ(calls, 0); } -TEST_F(type_registry_test, test_variant_type_definition) +TEST_F(type_registry_test, test_dispatch_multiple_calls_same_type) { - using expected_variant = std::variant; - using actual_variant = - rocprofsys::trace_cache::type_registry::variant_t; - - EXPECT_TRUE((std::is_same_v) ); -} - -TEST_F(type_registry_test, test_multiple_calls_same_type) -{ - const test_sample_1 test_value1{ 100, "first" }; - const test_sample_1 test_value2{ 200, "second" }; - - const size_t buffer_size1 = rocprofsys::trace_cache::get_size(test_value1); - const size_t buffer_size2 = rocprofsys::trace_cache::get_size(test_value2); - - std::vector buffer1(buffer_size1); - std::vector buffer2(buffer_size2); - - rocprofsys::trace_cache::serialize(buffer1.data(), test_value1); - rocprofsys::trace_cache::serialize(buffer2.data(), test_value2); - - auto* buffer1_data = buffer1.data(); - auto* buffer2_data = buffer2.data(); - - auto result1 = - type_registry.get_type(test_type_identifier_t::sample_type_1, buffer1_data); - auto result2 = - type_registry.get_type(test_type_identifier_t::sample_type_1, buffer2_data); - - ASSERT_TRUE(result1.has_value()); - ASSERT_TRUE(result2.has_value()); - - auto const sample_1_1 = std::get(result1.value()); - auto const sample_1_2 = std::get(result2.value()); - - EXPECT_EQ(sample_1_1.value, 100); - EXPECT_EQ(sample_1_1.text, "first"); - EXPECT_EQ(sample_1_2.value, 200); - EXPECT_EQ(sample_1_2.text, "second"); + EXPECT_TRUE(dispatch_round_trip(type_registry, test_type_identifier_t::sample_type_1, + test_sample_1{ 100, "first" }, + [](const test_sample_1& sample) { + EXPECT_EQ(sample.value, 100); + EXPECT_EQ(sample.text, "first"); + })); + EXPECT_TRUE(dispatch_round_trip(type_registry, test_type_identifier_t::sample_type_1, + test_sample_1{ 200, "second" }, + [](const test_sample_1& sample) { + EXPECT_EQ(sample.value, 200); + EXPECT_EQ(sample.text, "second"); + })); } class type_registry_optional_test : public ::testing::Test @@ -119,40 +118,20 @@ class type_registry_optional_test : public ::testing::Test type_registry; }; -TEST_F(type_registry_optional_test, test_get_type_sample_5_with_value) +TEST_F(type_registry_optional_test, test_dispatch_sample_5_with_value) { - const test_sample_5 test_value{ std::optional{ 42 } }; - const size_t buffer_size = rocprofsys::trace_cache::get_size(test_value); - std::vector buffer(buffer_size); - rocprofsys::trace_cache::serialize(buffer.data(), test_value); - - auto* buffer_data = buffer.data(); - auto result = - type_registry.get_type(test_type_identifier_t::sample_type_5, buffer_data); - - ASSERT_TRUE(result.has_value()); - ASSERT_TRUE(std::holds_alternative(result.value())); - - auto sample_5 = std::get(result.value()); - ASSERT_TRUE(sample_5.data.has_value()); - EXPECT_EQ(sample_5.data.value(), 42); + EXPECT_TRUE(dispatch_round_trip(type_registry, test_type_identifier_t::sample_type_5, + test_sample_5{ std::optional{ 42 } }, + [](const test_sample_5& sample) { + ASSERT_TRUE(sample.data.has_value()); + EXPECT_EQ(sample.data.value(), 42); + })); } -TEST_F(type_registry_optional_test, test_get_type_sample_5_nullopt) +TEST_F(type_registry_optional_test, test_dispatch_sample_5_nullopt) { - const test_sample_5 test_value{ std::nullopt }; - const size_t buffer_size = rocprofsys::trace_cache::get_size(test_value); - std::vector buffer(buffer_size); - rocprofsys::trace_cache::serialize(buffer.data(), test_value); - - auto* buffer_data = buffer.data(); - auto result = - type_registry.get_type(test_type_identifier_t::sample_type_5, buffer_data); - - ASSERT_TRUE(result.has_value()); - ASSERT_TRUE(std::holds_alternative(result.value())); - - auto const sample_5 = std::get(result.value()); - EXPECT_FALSE(sample_5.data.has_value()); - EXPECT_EQ(sample_5.data, std::nullopt); + EXPECT_TRUE(dispatch_round_trip( + type_registry, test_type_identifier_t::sample_type_5, + test_sample_5{ std::nullopt }, + [](const test_sample_5& sample) { EXPECT_EQ(sample.data, std::nullopt); })); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index 1b78e0382034..cea72adc132c 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -8,11 +8,10 @@ #include #include -#include -#include -#include +#include +#include +#include #include -#include namespace rocprofsys::trace_cache { @@ -38,41 +37,31 @@ class type_registry "two record types share a type_identifier"); public: - using variant_t = std::variant; - - type_registry() { (register_type(), ...); } - - std::optional get_type(TypeIdentifierEnum id, std::uint8_t*& data) + /** + * Deserializes the record whose type_identifier matches `type_id` and passes it to + * `visitor`. + * @throws std::runtime_error when no supported type has that id. + */ + template + requires type_traits::sample_visitor + void dispatch(TypeIdentifierEnum type_id, std::uint8_t*& data, + Visitor&& visitor) const { - auto const found = m_deserializers.find(id); - if(found != m_deserializers.end()) + const auto try_type = [&](std::type_identity) { + if(type_id != T::type_identifier) + { + return false; + } + visitor(deserialize(data)); + return true; + }; + if(!(try_type(std::type_identity{}) || ...)) { - return found->second(data); + throw std::runtime_error( + "No record type registered for type id " + + std::to_string( + static_cast>(type_id))); } - return std::nullopt; - } - -private: - // Converts to T by reading it, so the record is built inside the returned optional. - template - struct reader - { - std::uint8_t*& data; - - explicit operator T() const { return deserialize(data); } - }; - - std::map(std::uint8_t*&)>> - m_deserializers; - - template - requires type_traits::cacheable - void register_type() - { - m_deserializers[T::type_identifier] = [](std::uint8_t*& data) { - return std::optional{ std::in_place, std::in_place_type, - reader{ data } }; - }; } }; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp index 049a0d4ee545..71bb18027aac 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/sample.hpp @@ -19,7 +19,7 @@ namespace rocprofsys::pmc::collectors::cpu * Stores enabled metrics, timestamp, process-level data, and serialized * per-CPU frequency/load data as byte vectors. */ -struct sample : trace_cache::cacheable_t +struct sample { static constexpr trace_cache::type_identifier_t type_identifier{ trace_cache::type_identifier_t::cpu_pmc_sample diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp index c33d35ecc60e..d957cf83ab97 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp @@ -19,7 +19,7 @@ namespace rocprofsys::pmc::collectors::gpu * * This struct represents a sample of GPU performance metrics collected by the PMC. */ -struct sample : trace_cache::cacheable_t +struct sample { static constexpr trace_cache::type_identifier_t type_identifier{ trace_cache::type_identifier_t::gpu_pmc_sample diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp index c19a7aa10297..3a36668341fe 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/sample.hpp @@ -23,7 +23,7 @@ namespace rocprofsys::pmc::collectors::gpu_perf_counter * rocprofiler_sample_device_counting_service(). Each entry carries its * own qualified counter name including dimension positions. */ -struct sample : trace_cache::cacheable_t +struct sample { static constexpr trace_cache::type_identifier_t type_identifier{ trace_cache::type_identifier_t::gpu_perf_counter_sample diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp index 01045fe72ed6..f91da2ca6133 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/sample.hpp @@ -23,7 +23,7 @@ namespace rocprofsys::pmc::collectors::hipfile * the sampling thread that all the other collectors share. Here the names are resolved * once during post-processing, from METRIC_TABLE, so the sampling path stores a POD. */ -struct sample : trace_cache::cacheable_t +struct sample { static constexpr trace_cache::type_identifier_t type_identifier{ trace_cache::type_identifier_t::hipfile_pmc_sample diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp index 07cb112d67bf..2ff2c9ee95d3 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/sample.hpp @@ -16,9 +16,8 @@ namespace rocprofsys::pmc::collectors::nic * @brief NIC RDMA sample type for trace cache. * * This struct represents a sample of NIC RDMA metrics collected by the PMC. - * It inherits from cacheable_t to support serialization to the trace cache. */ -struct sample : trace_cache::cacheable_t +struct sample { static constexpr trace_cache::type_identifier_t type_identifier{ trace_cache::type_identifier_t::ainic_pmc_sample From 727e916d409e9b4b2bfc46beafbb0dea7d0e021e Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Wed, 28 Oct 2026 16:07:51 +0100 Subject: [PATCH 24/25] fix(rocprofiler-systems): use the renamed trace cache constants at every use site cacheable.hpp renamed buffer_size, flush_threshold, CACHE_FILE_FLUSH_TIMEOUT, tmp_directory, ABSOLUTE and PERCENTAGE to their k_ names, but buffer_storage, discovery, the cache policies and a few components still used the old names, so the library did not compile. - buffer_storage.{hpp,cpp}, cache_manager.cpp, discovery.cpp, test_buffer_storage.cpp: use k_buffer_size, k_flush_threshold, k_cache_file_flush_timeout and k_tmp_directory - cacheable.hpp: keep get_buffered_storage_filename without the k_ prefix, as all callers use it (it is a function object, not a constant) - components and the cpu, gpu, gpu_perf_counter, hipfile and nic cache policies, rocprofiler-sdk.cpp, rocprofiler-sdk/counters.cpp: trace_cache::k_absolute and trace_cache::k_percentage - test_archive.cpp: rename the test's own k_buffer_size, which now clashes with the constant from cacheable.hpp --- .../lib/core/trace_cache/buffer_storage.cpp | 2 +- .../lib/core/trace_cache/buffer_storage.hpp | 12 ++-- .../lib/core/trace_cache/cache_manager.cpp | 2 +- .../source/lib/core/trace_cache/cacheable.hpp | 2 +- .../source/lib/core/trace_cache/discovery.cpp | 4 +- .../core/trace_cache/tests/test_archive.cpp | 38 ++++++------ .../trace_cache/tests/test_buffer_storage.cpp | 2 +- .../library/components/backtrace_metrics.cpp | 2 +- .../library/components/comm_data.cpp | 8 +-- .../pmc/collectors/cpu/cache_policy.hpp | 40 ++++++------- .../pmc/collectors/gpu/cache_policy.hpp | 58 +++++++++---------- .../gpu_perf_counter/cache_policy.hpp | 12 ++-- .../pmc/collectors/hipfile/cache_policy.hpp | 2 +- .../pmc/collectors/nic/cache_policy.hpp | 2 +- .../rocprof-sys/library/rocprofiler-sdk.cpp | 2 +- .../library/rocprofiler-sdk/counters.cpp | 2 +- 16 files changed, 95 insertions(+), 95 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp index 24589d27a78a..69ec01826ba4 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.cpp @@ -73,7 +73,7 @@ flush_worker_t::start(const pid_t& current_pid) // heap-backed condition-variable state. Polling at the existing // 10 ms flush interval bounds shutdown latency without that // callback lifetime race. - wait_condition.wait_for(wait_lock, CACHE_FILE_FLUSH_TIMEOUT, + wait_condition.wait_for(wait_lock, k_cache_file_flush_timeout, [&]() { return stoken.stop_requested(); }); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp index 909f09eac5d5..e007f2a059ed 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/buffer_storage.hpp @@ -180,8 +180,8 @@ class buffer_storage } auto const used_space = - _head > _tail ? (_head - _tail) : (buffer_size - _tail + _head); - if(!force && used_space < flush_threshold) + _head > _tail ? (_head - _tail) : (k_buffer_size - _tail + _head); + if(!force && used_space < k_flush_threshold) { return; } @@ -195,7 +195,7 @@ class buffer_storage else { ofs.write(reinterpret_cast(m_buffer->data() + _tail), - buffer_size - _tail); + k_buffer_size - _tail); ofs.write(reinterpret_cast(m_buffer->data()), _head); } if(ofs.fail()) @@ -210,11 +210,11 @@ class buffer_storage { auto* _data = m_buffer->data(); memset(_data + m_head, std::numeric_limits::max(), - buffer_size - m_head); + k_buffer_size - m_head); const record_header header{ .type = TypeIdentifierEnum::fragmented_space, - .size = buffer_size - m_head - k_header_size + .size = k_buffer_size - m_head - k_header_size }; binary_write_archive archive{ .buffer = _data + m_head, .position = 0 }; archive(header); @@ -227,7 +227,7 @@ class buffer_storage const size_t& number_of_bytes) { if(__builtin_expect((m_head + number_of_bytes + - k_header_size) > buffer_size, + k_header_size) > k_buffer_size, 0)) { fragment_memory(); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp index e197b0ee384d..0d101741dc76 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp @@ -48,7 +48,7 @@ cache_manager::post_process_bulk(progress::tracker& _tracker) LOG_DEBUG("Root process ID: {}", root_pid); const auto temp_directory_content = - discovery::list_dir_files(trace_cache::tmp_directory); + discovery::list_dir_files(trace_cache::k_tmp_directory); LOG_TRACE("Found {} files in temp directory", temp_directory_content.size()); const auto cache_files = diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 5c7937cd8e78..3bb9e3eecdb8 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -37,7 +37,7 @@ const auto k_tmp_directory = std::string{ "/tmp/" }; namespace utility { -const auto k_get_buffered_storage_filename = [](const int& ppid, const int& pid) { +const auto get_buffered_storage_filename = [](const int& ppid, const int& pid) { return std::string{ k_tmp_directory + "buffered_storage_" + std::to_string(ppid) + "_" + std::to_string(pid) + ".bin" }; }; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp index 2a5f27f9d550..33a51416321a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp @@ -93,7 +93,7 @@ find_cache_files(const pid_t& root_pid, const data::directory_files_t& dir_conte const int pid = std::stoi(match[2]); if(parent_pid == root_pid) { - cache_map[pid].buff_storage = trace_cache::tmp_directory + filename; + cache_map[pid].buff_storage = trace_cache::k_tmp_directory + filename; } } else if(std::regex_match(filename, match, meta_regex)) @@ -102,7 +102,7 @@ find_cache_files(const pid_t& root_pid, const data::directory_files_t& dir_conte const int pid = std::stoi(match[2]); if(parent_pid == root_pid) { - cache_map[pid].metadata = trace_cache::tmp_directory + filename; + cache_map[pid].metadata = trace_cache::k_tmp_directory + filename; } } } catch(const std::exception& e) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp index 2686668d3122..b1565b55bb65 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -181,14 +181,14 @@ struct nested_record } // namespace -constexpr std::size_t k_buffer_size = 4096; +constexpr std::size_t k_test_buffer_size = 4096; TEST(archive, scalars_match_utility_encoding) { - std::array buffer{}; - const std::uint32_t val_u32 = 12345U; - const std::uint64_t val_u64 = 98765432101234ULL; - const double val_dbl = 2.71828; + std::array buffer{}; + const std::uint32_t val_u32 = 12345U; + const std::uint64_t val_u64 = 98765432101234ULL; + const double val_dbl = 2.71828; scalar_record orig; orig.val_u32 = val_u32; @@ -209,8 +209,8 @@ TEST(archive, scalars_match_utility_encoding) EXPECT_EQ(size, utility::get_size(val_u32) + utility::get_size(val_u64) + utility::get_size(val_dbl)); - std::array other_buffer{}; - std::size_t utility_position = 0; + std::array other_buffer{}; + std::size_t utility_position = 0; utility::store_value(val_u32, other_buffer.data(), utility_position); utility::store_value(val_u64, other_buffer.data(), utility_position); utility::store_value(val_dbl, other_buffer.data(), utility_position); @@ -220,8 +220,8 @@ TEST(archive, scalars_match_utility_encoding) TEST(archive, string_view_reads_back_as_view_into_buffer) { - std::array buffer{}; - const std::string_view orig_text = "hello world"; + std::array buffer{}; + const std::string_view orig_text = "hello world"; string_record orig; orig.text = orig_text; @@ -240,8 +240,8 @@ TEST(archive, string_view_reads_back_as_view_into_buffer) TEST(archive, vector_round_trip) { - std::array buffer{}; - const std::vector orig_data = { 10, 20, 30 }; + std::array buffer{}; + const std::vector orig_data = { 10, 20, 30 }; vector_record orig; orig.data = orig_data; @@ -258,8 +258,8 @@ TEST(archive, vector_round_trip) TEST(archive, optional_engaged_and_empty) { - std::array buffer{}; - optional_record orig_engaged; + std::array buffer{}; + optional_record orig_engaged; orig_engaged.value = 9876543210LL; const std::size_t size_engaged = get_size(orig_engaged); @@ -288,8 +288,8 @@ TEST(archive, optional_engaged_and_empty) TEST(archive, derived_record_serializes_base_first) { - std::array buffer{}; - derived_record orig; + std::array buffer{}; + derived_record orig; orig.base_field = 555U; orig.base_value = 3.14; orig.extra_field = 777ULL; @@ -313,8 +313,8 @@ TEST(archive, derived_record_serializes_base_first) TEST(archive, nested_struct_serializes_itself) { - std::array buffer{}; - nested_record orig; + std::array buffer{}; + nested_record orig; orig.head = 1111ULL; orig.inner.alpha = 2222U; orig.inner.beta = 3.33; @@ -336,8 +336,8 @@ TEST(archive, nested_struct_serializes_itself) utility::get_size(orig.inner.beta) + utility::get_size(orig.tail)); - std::array other_buffer{}; - std::size_t nested_position = 0; + std::array other_buffer{}; + std::size_t nested_position = 0; utility::store_value(orig.head, other_buffer.data(), nested_position); utility::store_value(orig.inner.alpha, other_buffer.data(), nested_position); utility::store_value(orig.inner.beta, other_buffer.data(), nested_position); diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp index d5c3df44515c..37dd5db6826a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp @@ -479,7 +479,7 @@ TEST_F(buffer_storage_test, repeated_fragmentation) storage.start(); - const size_t fragment_trigger_size = rocprofsys::trace_cache::buffer_size / 5; + const size_t fragment_trigger_size = rocprofsys::trace_cache::k_buffer_size / 5; const std::vector fragment_payload(fragment_trigger_size, 0xDD); const int cycle_count = 3; const int iter_count = 2; diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/backtrace_metrics.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/backtrace_metrics.cpp index 3129e65929a5..9b14439f0bb6 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/backtrace_metrics.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/backtrace_metrics.cpp @@ -238,7 +238,7 @@ metadata_initialize_backtrace_metrics_pmc(size_t dev_id, const char* _units, trace_cache::get_metadata_registry().add_pmc_info( { agent_type::cpu, dev_id, TARGET_ARCH, EVENT_CODE, INSTANCE_ID, _track_name, trait::name::value, trait::name::description, - LONG_DESCRIPTION, COMPONENT, _units, trace_cache::ABSOLUTE, BLOCK, + LONG_DESCRIPTION, COMPONENT, _units, trace_cache::k_absolute, BLOCK, EXPRESSION, 0, 0, "{}" }); }); } diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp index 94e7c62f9a7c..896c67d8f714 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp @@ -77,7 +77,7 @@ metadata_initialize_comm_data_pmc() .long_description = k_long_description, .component = k_component, .units = k_msg, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = k_block, .expression = k_expression, .is_constant = 0, @@ -95,7 +95,7 @@ metadata_initialize_comm_data_pmc() .long_description = k_long_description, .component = k_component, .units = k_msg, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = k_block, .expression = k_expression, .is_constant = 0, @@ -114,7 +114,7 @@ metadata_initialize_comm_data_pmc() .long_description = k_long_description, .component = k_component, .units = k_msg, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = k_block, .expression = k_expression, .is_constant = 0, @@ -132,7 +132,7 @@ metadata_initialize_comm_data_pmc() .long_description = k_long_description, .component = k_component, .units = k_msg, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = k_block, .expression = k_expression, .is_constant = 0, diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/cache_policy.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/cache_policy.hpp index b8f9d3802645..d3d94fa4451e 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/cache_policy.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/cpu/cache_policy.hpp @@ -84,12 +84,12 @@ struct cache_policy .long_description = long_description, .component = component, .units = "MHz", - .value_type = rocprofsys::trace_cache::ABSOLUTE, - .block = block, - .expression = expression, - .is_constant = is_constant, - .is_derived = is_derived, - .extdata = extdata }); + .value_type = rocprofsys::trace_cache::k_absolute, + .block = block, + .expression = expression, + .is_constant = is_constant, + .is_derived = is_derived, + .extdata = extdata }); registry.add_track({ .track_name = freq_name, .thread_id = std::nullopt, .extdata = extdata }); @@ -106,13 +106,13 @@ struct cache_policy .description = "CPU Core Load Percentage", .long_description = long_description, .component = component, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, - .block = block, - .expression = expression, - .is_constant = is_constant, - .is_derived = is_derived, - .extdata = extdata }); + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, + .block = block, + .expression = expression, + .is_constant = is_constant, + .is_derived = is_derived, + .extdata = extdata }); registry.add_track({ .track_name = load_name, .thread_id = std::nullopt, .extdata = extdata }); @@ -151,29 +151,29 @@ struct cache_policy add_process_pmc(name::value, "Page RSS", "Process Physical Memory (RSS)", "MB", - rocprofsys::trace_cache::ABSOLUTE); + rocprofsys::trace_cache::k_absolute); add_process_pmc(name::value, "Virt Mem", "Process Virtual Memory", "MB", - rocprofsys::trace_cache::ABSOLUTE); + rocprofsys::trace_cache::k_absolute); add_process_pmc(name::value, "Peak RSS", "Process Peak Memory (HWM)", "MB", - rocprofsys::trace_cache::ABSOLUTE); + rocprofsys::trace_cache::k_absolute); add_process_pmc(name::value, "Ctx Switches", - "Context Switches", "count", rocprofsys::trace_cache::ABSOLUTE); + "Context Switches", "count", rocprofsys::trace_cache::k_absolute); add_process_pmc(name::value, "Page Faults", - "Page Faults", "count", rocprofsys::trace_cache::ABSOLUTE); + "Page Faults", "count", rocprofsys::trace_cache::k_absolute); add_process_pmc(name::value, "User Time", "Process CPU Time in User Mode", "sec", - rocprofsys::trace_cache::ABSOLUTE); + rocprofsys::trace_cache::k_absolute); add_process_pmc(name::value, "Kernel Time", "Process CPU Time in Kernel Mode", "sec", - rocprofsys::trace_cache::ABSOLUTE); + rocprofsys::trace_cache::k_absolute); } /** diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp index 6e46d64b29af..bbc4f0d55a55 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp @@ -214,8 +214,8 @@ struct cache_policy .description = trait::name::description, .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -233,8 +233,8 @@ struct cache_policy .description = trait::name::description, .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -252,8 +252,8 @@ struct cache_policy .description = trait::name::description, .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -272,7 +272,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = CELSIUS_DEGREES, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -291,7 +291,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "W", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -310,7 +310,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "MB", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -333,8 +333,8 @@ struct cache_policy .description = "VCN (Video Decode) Engine Activity", .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -359,8 +359,8 @@ struct cache_policy .description = "JPEG (Image Decode) Engine Activity", .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -387,8 +387,8 @@ struct cache_policy .description = "VCN (Video Decode) Engine Activity", .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -415,8 +415,8 @@ struct cache_policy .description = "JPEG (Image Decode) Engine Activity", .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -436,8 +436,8 @@ struct cache_policy .description = trait::name::description, .long_description = LONG_DESCRIPTION, .component = COMPONENT, - .units = trace_cache::PERCENTAGE, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .units = trace_cache::k_percentage, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -456,7 +456,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "MHz", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -475,7 +475,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "MHz", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -494,7 +494,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "lanes", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -513,7 +513,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "Mbps", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -538,7 +538,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "KB", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -560,7 +560,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "KB", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -580,7 +580,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "lanes", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -599,7 +599,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "MT/s", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -619,7 +619,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "bytes", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, @@ -639,7 +639,7 @@ struct cache_policy .long_description = LONG_DESCRIPTION, .component = COMPONENT, .units = "bytes/s", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = BLOCK, .expression = EXPRESSION, .is_constant = 0, diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/cache_policy.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/cache_policy.hpp index 87e2ec915006..ad293e6d3f8d 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/cache_policy.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu_perf_counter/cache_policy.hpp @@ -67,12 +67,12 @@ struct cache_policy .long_description = long_description, .component = component, .units = "count", - .value_type = rocprofsys::trace_cache::ABSOLUTE, - .block = meta.block, - .expression = meta.expression, - .is_constant = meta.is_constant ? 1U : 0U, - .is_derived = meta.is_derived ? 1U : 0U, - .extdata = "{}" }); + .value_type = rocprofsys::trace_cache::k_absolute, + .block = meta.block, + .expression = meta.expression, + .is_constant = meta.is_constant ? 1U : 0U, + .is_derived = meta.is_derived ? 1U : 0U, + .extdata = "{}" }); name_entries.push_back({ .counter_id = meta.counter_id, .pmc_info_name = std::move(qname), diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/cache_policy.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/cache_policy.hpp index b98b810bf371..57bc7356bb33 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/cache_policy.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/hipfile/cache_policy.hpp @@ -67,7 +67,7 @@ struct cache_policy { agent_type::gpu, gpu_id, TARGET_ARCH, EVENT_CODE, INSTANCE_ID, pmc_name(metric.suffix), metric.suffix, trait::name::description, LONG_DESCRIPTION, - COMPONENT, metric.unit, rocprofsys::trace_cache::ABSOLUTE, BLOCK, + COMPONENT, metric.unit, rocprofsys::trace_cache::k_absolute, BLOCK, EXPRESSION, 0, 0, "{}" }); } } diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/cache_policy.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/cache_policy.hpp index 64556d7f6cf9..38062a58b119 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/cache_policy.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/nic/cache_policy.hpp @@ -115,7 +115,7 @@ struct cache_policy .long_description = k_long_description, .component = k_component, .units = units, - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = k_block, .expression = k_expression, .is_constant = 0, diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk.cpp index 5e7578f4e751..33fc6b56ac4e 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk.cpp @@ -446,7 +446,7 @@ struct external_dependencies // Single source of truth is core/trace_cache/cacheable.hpp's ABSOLUTE constant; // kfd_events.hpp never includes that header, so the value is surfaced here. - static constexpr std::string_view k_pmc_value_type_absolute = trace_cache::ABSOLUTE; + static constexpr std::string_view k_pmc_value_type_absolute = trace_cache::k_absolute; // Single source of truth for these strings is core/categories.hpp's // trait::name; kfd_events.hpp itself never includes diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp index 47d882d129fa..d84fc7ae90fa 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp @@ -56,7 +56,7 @@ metadata_initialize_counters_pmc(size_t dev_id, const std::string& name, .long_description = long_description, .component = component, .units = "Unit Count", - .value_type = rocprofsys::trace_cache::ABSOLUTE, + .value_type = rocprofsys::trace_cache::k_absolute, .block = block, .expression = expression, .is_constant = 0, From 48fc38f8e773f0b932066af393ed41462ede9202 Mon Sep 17 00:00:00 2001 From: Aleksandar Djordjevic Date: Tue, 3 Nov 2026 14:48:55 +0100 Subject: [PATCH 25/25] fix(rocprofiler-systems): resolve clang-tidy and fixedwidth-types CI findings Add direct includes, rename concept-handler param, make k_tmp_directory a constexpr string_view, split a test helper. --- .../lib/core/trace_cache/cache_manager.cpp | 3 ++- .../core/trace_cache/cache_type_traits.hpp | 4 ++-- .../source/lib/core/trace_cache/cacheable.hpp | 11 ++++++----- .../source/lib/core/trace_cache/discovery.cpp | 6 ++++-- .../core/trace_cache/tests/test_archive.cpp | 4 ++-- .../trace_cache/tests/test_buffer_storage.cpp | 1 + .../core/trace_cache/tests/test_cacheable.cpp | 4 ++-- .../trace_cache/tests/test_storage_parser.cpp | 1 + .../trace_cache/tests/test_type_registry.cpp | 19 ++++++++++++++----- .../lib/core/trace_cache/type_registry.hpp | 1 - .../library/components/comm_data.cpp | 1 + .../pmc/collectors/gpu/cache_policy.hpp | 1 + .../library/pmc/collectors/gpu/sample.hpp | 2 +- .../library/rocprofiler-sdk/counters.cpp | 1 + 14 files changed, 38 insertions(+), 21 deletions(-) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp index 0d101741dc76..556b2fb04778 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_manager.cpp @@ -6,6 +6,7 @@ #include "core/config.hpp" #include "core/perfetto/cached_perfetto_session.hpp" #include "core/timemory.hpp" +#include "core/trace_cache/cacheable.hpp" #include "core/trace_cache/data_types.hpp" #include "core/trace_cache/discovery.hpp" #include "core/trace_cache/post_processor.hpp" @@ -48,7 +49,7 @@ cache_manager::post_process_bulk(progress::tracker& _tracker) LOG_DEBUG("Root process ID: {}", root_pid); const auto temp_directory_content = - discovery::list_dir_files(trace_cache::k_tmp_directory); + discovery::list_dir_files(std::string{ trace_cache::k_tmp_directory }); LOG_TRACE("Found {} files in temp directory", temp_directory_content.size()); const auto cache_files = diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp index 23d37835bfab..b0de50bfbcb2 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cache_type_traits.hpp @@ -114,8 +114,8 @@ concept has_type_identifier = is_enum_class_v && requires { }; template -concept sample_handler = requires(T& t, const SampleType& sample) { - { t.handle_sample(sample) } -> std::same_as; +concept sample_handler = requires(T& handler, const SampleType& sample) { + { handler.handle_sample(sample) } -> std::same_as; }; template diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp index 3bb9e3eecdb8..d8af4659cc7e 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/cacheable.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -32,19 +33,19 @@ constexpr auto k_percentage = "%"; using buffer_array_t = std::array; -const auto k_tmp_directory = std::string{ "/tmp/" }; +constexpr std::string_view k_tmp_directory = "/tmp/"; namespace utility { const auto get_buffered_storage_filename = [](const int& ppid, const int& pid) { - return std::string{ k_tmp_directory + "buffered_storage_" + std::to_string(ppid) + - "_" + std::to_string(pid) + ".bin" }; + return std::string{ k_tmp_directory }.append( + "buffered_storage_" + std::to_string(ppid) + "_" + std::to_string(pid) + ".bin"); }; const auto get_metadata_filepath = [](const int& ppid, const int& pid) { - return std::string{ k_tmp_directory + "metadata_" + std::to_string(ppid) + "_" + - std::to_string(pid) + ".json" }; + return std::string{ k_tmp_directory }.append("metadata_" + std::to_string(ppid) + + "_" + std::to_string(pid) + ".json"); }; template diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp index 33a51416321a..b21112afaa2b 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/discovery.cpp @@ -93,7 +93,8 @@ find_cache_files(const pid_t& root_pid, const data::directory_files_t& dir_conte const int pid = std::stoi(match[2]); if(parent_pid == root_pid) { - cache_map[pid].buff_storage = trace_cache::k_tmp_directory + filename; + cache_map[pid].buff_storage = + std::string{ trace_cache::k_tmp_directory } + filename; } } else if(std::regex_match(filename, match, meta_regex)) @@ -102,7 +103,8 @@ find_cache_files(const pid_t& root_pid, const data::directory_files_t& dir_conte const int pid = std::stoi(match[2]); if(parent_pid == root_pid) { - cache_map[pid].metadata = trace_cache::k_tmp_directory + filename; + cache_map[pid].metadata = + std::string{ trace_cache::k_tmp_directory } + filename; } } } catch(const std::exception& e) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp index b1565b55bb65..720d504f7330 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_archive.cpp @@ -1,7 +1,7 @@ // Copyright (c) Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT -// NOLINTBEGIN(readability-magic-numbers) +// NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) // Test values in this file are arbitrary and chosen for coverage, not domain significance #include "core/trace_cache/archive.hpp" @@ -348,4 +348,4 @@ TEST(archive, nested_struct_serializes_itself) } // namespace rocprofsys::trace_cache -// NOLINTEND(readability-magic-numbers) +// NOLINTEND(readability-magic-numbers, readability-identifier-naming) diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp index 37dd5db6826a..06d94cc5fb2a 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_buffer_storage.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #include "core/trace_cache/buffer_storage.hpp" +#include "core/trace_cache/cacheable.hpp" #include "mocked_types.hpp" #include diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp index 8ea73a850254..519e9d99f293 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_cacheable.cpp @@ -587,8 +587,8 @@ TEST_F(cacheable_test, parse_value_empty_byte_array) } TEST_F(cacheable_test, parse_value_vector_skips_a_trailing_partial_element) { - // The byte count is one whole uint32_t plus one stray byte. Only whole elements are - // read; the cursor still moves past the whole count. + // The byte count is one whole std::uint32_t plus one stray byte. Only whole elements + // are read; the cursor still moves past the whole count. constexpr std::uint64_t k_byte_count = sizeof(std::uint32_t) + 1; constexpr std::uint32_t k_whole = 0xDEADBEEF; constexpr std::uint8_t k_stray_byte = 0xAA; diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp index 0eb4860d9675..bdcc9a274280 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_storage_parser.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include class sample_processor_t diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp index e77773a3a4a5..4fb08708e598 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/tests/test_type_registry.cpp @@ -1,6 +1,7 @@ // Copyright (c) Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT +#include "core/trace_cache/archive.hpp" #include "core/trace_cache/type_registry.hpp" #include "mocked_types.hpp" #include @@ -38,6 +39,16 @@ dispatch_round_trip(const Registry& registry, test_type_identifier_t type_id, }); return received; } + +template +void +dispatch_counting_calls(const Registry& registry, test_type_identifier_t type_id, + int& calls) +{ + std::uint8_t dummy_data = 0; + std::uint8_t* data = &dummy_data; + registry.dispatch(type_id, data, [&](const auto&) { ++calls; }); +} } // namespace class type_registry_test : public ::testing::Test @@ -84,12 +95,10 @@ TEST_F(type_registry_test, test_dispatch_calls_visitor_once) TEST_F(type_registry_test, test_dispatch_unknown_id_throws_without_calling_visitor) { - std::uint8_t dummy_data = 0; - std::uint8_t* data = &dummy_data; - int calls = 0; + int calls = 0; - EXPECT_THROW(type_registry.dispatch(test_type_identifier_t::fragmented_space, data, - [&](const auto&) { ++calls; }), + EXPECT_THROW(dispatch_counting_calls(type_registry, + test_type_identifier_t::fragmented_space, calls), std::runtime_error); EXPECT_EQ(calls, 0); } diff --git a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp index cea72adc132c..eee04eeeb6fe 100644 --- a/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp +++ b/projects/rocprofiler-systems/source/lib/core/trace_cache/type_registry.hpp @@ -11,7 +11,6 @@ #include #include #include -#include namespace rocprofsys::trace_cache { diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp index bf4aa800e0e6..bbe8b229fb9a 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/components/comm_data.cpp @@ -7,6 +7,7 @@ #include "core/config.hpp" #include "core/node_info.hpp" #include "core/trace_cache/cache_manager.hpp" +#include "core/trace_cache/cacheable.hpp" #include "core/trace_cache/sample_type.hpp" #include "library/tracing.hpp" #include diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp index e5966ff71d10..25ac4a6f24ce 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/cache_policy.hpp @@ -5,6 +5,7 @@ #include "core/config.hpp" #include "core/trace_cache/cache_manager.hpp" +#include "core/trace_cache/cacheable.hpp" #include "core/trace_cache/metadata_registry.hpp" #include "library/pmc/collectors/gpu/sample.hpp" #include "library/pmc/collectors/gpu/types.hpp" diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp index d957cf83ab97..9746c3f50a51 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/pmc/collectors/gpu/sample.hpp @@ -64,7 +64,7 @@ struct sample namespace rocprofsys::trace_cache::type_traits { -// xcp_stats is cached as raw bytes: it is plain arrays of uint16_t, without padding. +// xcp_stats is cached as raw bytes: it is plain arrays of std::uint16_t, without padding. static_assert(std::has_unique_object_representations_v< backends::amd_smi::gpu::metrics::xcp_metrics>, "xcp_metrics is cached as raw bytes and must not have padding"); diff --git a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp index ef6669940139..97183ae54d3a 100644 --- a/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp +++ b/projects/rocprofiler-systems/source/lib/rocprof-sys/library/rocprofiler-sdk/counters.cpp @@ -5,6 +5,7 @@ #include "core/agent_manager.hpp" #include "core/demangler.hpp" #include "core/trace_cache/cache_manager.hpp" +#include "core/trace_cache/cacheable.hpp" #include "core/trace_cache/metadata_registry.hpp" #include "library/rocprofiler-sdk/fwd.hpp" #include