From 87b8791c8f051757a1a388a07dac7f69aacfd245 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Sat, 12 Sep 2026 14:11:27 +0000 Subject: [PATCH 1/5] device_memory_report: lazily initialize CounterTrack instances Static CounterTrack instances constructed at static initialization time lack process context (parent_uuid), which prevents Perfetto tracks from scoping under the process and being associated with the target application track group. Initializing tracks on first use inside GetCounterTrack ensures they properly bind to ProcessTrack::Current(). --- .../device_memory_report_perfetto.cpp | 63 ++++--------------- 1 file changed, 11 insertions(+), 52 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index 01b4dc258e..5484b369fa 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -32,63 +32,22 @@ void InitializeDeviceMemoryReportPerfetto() { }); } -namespace { -static const std::unordered_map kStaticTracks = { - // App usage tracks - {"vulkan.mem.app.usage.staging_transfer", perfetto::CounterTrack("vulkan.mem.app.usage.staging_transfer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.ray_tracing", perfetto::CounterTrack("vulkan.mem.app.usage.ray_tracing").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.indirect_gpu_driven", perfetto::CounterTrack("vulkan.mem.app.usage.indirect_gpu_driven").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.compute_storage", perfetto::CounterTrack("vulkan.mem.app.usage.compute_storage").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.uniform_constants", perfetto::CounterTrack("vulkan.mem.app.usage.uniform_constants").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.geometry_mesh", perfetto::CounterTrack("vulkan.mem.app.usage.geometry_mesh").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.transform_feedback", perfetto::CounterTrack("vulkan.mem.app.usage.transform_feedback").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.general_buffer", perfetto::CounterTrack("vulkan.mem.app.usage.general_buffer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.transient_memoryless", perfetto::CounterTrack("vulkan.mem.app.usage.transient_memoryless").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.storage_compute_image", perfetto::CounterTrack("vulkan.mem.app.usage.storage_compute_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.depth_stencil_target", perfetto::CounterTrack("vulkan.mem.app.usage.depth_stencil_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.color_render_target", perfetto::CounterTrack("vulkan.mem.app.usage.color_render_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.static_texture", perfetto::CounterTrack("vulkan.mem.app.usage.static_texture").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.general_image", perfetto::CounterTrack("vulkan.mem.app.usage.general_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.app.usage.unbound_memory", perfetto::CounterTrack("vulkan.mem.app.usage.unbound_memory").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, +perfetto::CounterTrack GetCounterTrack(std::string_view name) { + InitializeDeviceMemoryReportPerfetto(); - // Driver usage tracks - {"vulkan.mem.driver.usage.staging_transfer", perfetto::CounterTrack("vulkan.mem.driver.usage.staging_transfer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.ray_tracing", perfetto::CounterTrack("vulkan.mem.driver.usage.ray_tracing").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.indirect_gpu_driven", perfetto::CounterTrack("vulkan.mem.driver.usage.indirect_gpu_driven").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.compute_storage", perfetto::CounterTrack("vulkan.mem.driver.usage.compute_storage").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.uniform_constants", perfetto::CounterTrack("vulkan.mem.driver.usage.uniform_constants").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.geometry_mesh", perfetto::CounterTrack("vulkan.mem.driver.usage.geometry_mesh").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.transform_feedback", perfetto::CounterTrack("vulkan.mem.driver.usage.transform_feedback").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.general_buffer", perfetto::CounterTrack("vulkan.mem.driver.usage.general_buffer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.transient_memoryless", perfetto::CounterTrack("vulkan.mem.driver.usage.transient_memoryless").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.storage_compute_image", perfetto::CounterTrack("vulkan.mem.driver.usage.storage_compute_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.depth_stencil_target", perfetto::CounterTrack("vulkan.mem.driver.usage.depth_stencil_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.color_render_target", perfetto::CounterTrack("vulkan.mem.driver.usage.color_render_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.static_texture", perfetto::CounterTrack("vulkan.mem.driver.usage.static_texture").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.general_image", perfetto::CounterTrack("vulkan.mem.driver.usage.general_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, - {"vulkan.mem.driver.usage.unbound_memory", perfetto::CounterTrack("vulkan.mem.driver.usage.unbound_memory").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, -}; -} // namespace + static std::mutex tracks_mutex; + static std::unordered_map tracks; + static std::unordered_set track_names; + std::lock_guard lock(tracks_mutex); -perfetto::CounterTrack GetCounterTrack(std::string_view name) { - auto it = kStaticTracks.find(name); - if (it != kStaticTracks.end()) { + auto it = tracks.find(name); + if (it != tracks.end()) { return it->second; } - static std::mutex dynamic_mutex; - static std::unordered_map dynamic_tracks; - static std::unordered_set dynamic_names; - std::lock_guard lock(dynamic_mutex); - auto dyn_it = dynamic_tracks.find(std::string(name)); - if (dyn_it != dynamic_tracks.end()) { - return dyn_it->second; - } - auto name_it = dynamic_names.insert(std::string(name)).first; + + auto name_it = track_names.insert(std::string(name)).first; perfetto::CounterTrack track = perfetto::CounterTrack(perfetto::DynamicString(name_it->c_str())) .set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES); - dynamic_tracks.emplace( - std::piecewise_construct, - std::forward_as_tuple(*name_it), - std::forward_as_tuple(track)); + tracks.emplace(*name_it, track); return track; } From fcfa96163584342a04c42635daf5acf770755181 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 14 Sep 2026 10:47:51 +0000 Subject: [PATCH 2/5] device_memory_report: test that counter tracks are process-scoped Add regression tests covering the lazy CounterTrack initialization fix. Tracks constructed at static initialization time were created before perfetto::Tracing::Initialize() assigned Track::process_uuid, so they ended up with parent_uuid == 0 and were emitted as global tracks instead of being scoped under the process. --- layersvt/test/test_devicememoryreport.cpp | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 6fc6c639b9..229d820911 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -422,6 +422,54 @@ TEST_F(DeviceMemoryReportTests, StaticCounterTrackLookup) { EXPECT_EQ(dynamic_track_again.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); } +TEST_F(DeviceMemoryReportTests, CounterTracksAreScopedToProcessTrack) { + TEST_DESCRIPTION( + "Test that counter tracks are parented to the process track, so Perfetto scopes them under the traced " + "application instead of emitting them as global tracks"); + + InitializeDeviceMemoryReportPerfetto(); + + // Perfetto only assigns a process uuid once tracing has been initialized. Tracks created before that (for + // example during static initialization) are permanently parented to the null/global track. + const uint64_t process_uuid = perfetto::ProcessTrack::Current().uuid; + ASSERT_NE(process_uuid, 0u) << "Perfetto tracing was not initialized"; + + const char* kTrackNames[] = { + "vulkan.mem.app.usage.color_render_target", + "vulkan.mem.app.usage.unbound_memory", + "vulkan.mem.driver.usage.static_texture", + "vulkan.mem.driver.usage.unbound_memory", + // A name that has never had a pre-registered track. + "vulkan.mem.app.usage.brand_new_category", + }; + + for (const char* name : kTrackNames) { + perfetto::CounterTrack track = GetCounterTrack(name); + + EXPECT_NE(track.uuid, 0u) << "track " << name; + EXPECT_EQ(track.parent_uuid, process_uuid) << "track " << name << " is not scoped to the process track"; + + // The uuid must match what Perfetto derives for a process-scoped track of the same name. + EXPECT_EQ(track.uuid, perfetto::CounterTrack(perfetto::DynamicString(name)).uuid) << "track " << name; + + const perfetto::protos::gen::TrackDescriptor descriptor = track.Serialize(); + EXPECT_EQ(descriptor.uuid(), track.uuid) << "track " << name; + EXPECT_EQ(descriptor.parent_uuid(), process_uuid) << "track " << name; + EXPECT_EQ(descriptor.counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES) + << "track " << name; + } +} + +TEST_F(DeviceMemoryReportTests, GetCounterTrackInitializesTracing) { + TEST_DESCRIPTION("Test that GetCounterTrack initializes Perfetto itself, so tracks always have process context"); + + // No explicit InitializeDeviceMemoryReportPerfetto() call here: GetCounterTrack must do it. + perfetto::CounterTrack track = GetCounterTrack("vulkan.mem.app.usage.general_image"); + + EXPECT_NE(perfetto::ProcessTrack::Current().uuid, 0u); + EXPECT_EQ(track.parent_uuid, perfetto::ProcessTrack::Current().uuid); +} + TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { TEST_DESCRIPTION("Test that the layer proactively queries memory requirements when creating images and buffers"); From 7410aaff7202e0e259d36f7fde1281cadcf8368b Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 10:35:17 +0000 Subject: [PATCH 3/5] device_memory_report: use threadsafe death test for cold tracing init Run GetCounterTrackInitializesTracing in a fresh child process via EXPECT_EXIT with threadsafe death test mode so it tests cold Perfetto initialization from uninitialized static state. --- layersvt/test/test_devicememoryreport.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 229d820911..3613f8519d 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -463,11 +463,23 @@ TEST_F(DeviceMemoryReportTests, CounterTracksAreScopedToProcessTrack) { TEST_F(DeviceMemoryReportTests, GetCounterTrackInitializesTracing) { TEST_DESCRIPTION("Test that GetCounterTrack initializes Perfetto itself, so tracks always have process context"); - // No explicit InitializeDeviceMemoryReportPerfetto() call here: GetCounterTrack must do it. - perfetto::CounterTrack track = GetCounterTrack("vulkan.mem.app.usage.general_image"); - - EXPECT_NE(perfetto::ProcessTrack::Current().uuid, 0u); - EXPECT_EQ(track.parent_uuid, perfetto::ProcessTrack::Current().uuid); + // Force fork+execve so the child starts with an uninitialized process_uuid rather than + // inheriting static state already initialized by earlier tests in this binary. + GTEST_FLAG_SET(death_test_style, "threadsafe"); + + EXPECT_EXIT( + { + if (perfetto::ProcessTrack::Current().uuid != 0u) { + exit(1); + } + perfetto::CounterTrack track = GetCounterTrack("vulkan.mem.app.usage.general_image"); + const uint64_t process_uuid = perfetto::ProcessTrack::Current().uuid; + if (process_uuid == 0u || track.parent_uuid != process_uuid) { + exit(2); + } + exit(0); + }, + ::testing::ExitedWithCode(0), ""); } TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { From 6cb52a5eaef1b78356fa3fcaafe12fbc624ff195 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 10:55:08 +0000 Subject: [PATCH 4/5] device_memory_report: clean up GetCounterTrack initialization and teardown - Declare track_names before tracks to preserve LIFO static destruction order. - Defer InitializeDeviceMemoryReportPerfetto() until cache miss to avoid std::call_once on fast path. - Emplace string keys directly from string_view and pass const std::string& to DynamicString. --- .../device_memory_report_perfetto.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index 5484b369fa..eb381d07fb 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -33,11 +33,10 @@ void InitializeDeviceMemoryReportPerfetto() { } perfetto::CounterTrack GetCounterTrack(std::string_view name) { - InitializeDeviceMemoryReportPerfetto(); - static std::mutex tracks_mutex; - static std::unordered_map tracks; + // Declare track_names first so LIFO static destruction destroys tracks before the strings it views. static std::unordered_set track_names; + static std::unordered_map tracks; std::lock_guard lock(tracks_mutex); auto it = tracks.find(name); @@ -45,8 +44,12 @@ perfetto::CounterTrack GetCounterTrack(std::string_view name) { return it->second; } - auto name_it = track_names.insert(std::string(name)).first; - perfetto::CounterTrack track = perfetto::CounterTrack(perfetto::DynamicString(name_it->c_str())) + // CounterTrack captures ProcessTrack::Current().uuid at construction time; ensure tracing is + // initialized before constructing the first track so it isn't parented to the null/global track. + InitializeDeviceMemoryReportPerfetto(); + + auto name_it = track_names.emplace(name).first; + perfetto::CounterTrack track = perfetto::CounterTrack(perfetto::DynamicString(*name_it)) .set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES); tracks.emplace(*name_it, track); return track; From e5502250364ac2e0a34c9cce51043f9072f10036 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 16 Sep 2026 10:59:22 +0000 Subject: [PATCH 5/5] device_memory_report: remove obsolete comment in counter track scoping test Now that static pre-registered tracks have been removed in favor of lazy creation, remove the obsolete comment about pre-registered tracks. --- layersvt/test/test_devicememoryreport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 3613f8519d..e57b61e682 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -439,7 +439,6 @@ TEST_F(DeviceMemoryReportTests, CounterTracksAreScopedToProcessTrack) { "vulkan.mem.app.usage.unbound_memory", "vulkan.mem.driver.usage.static_texture", "vulkan.mem.driver.usage.unbound_memory", - // A name that has never had a pre-registered track. "vulkan.mem.app.usage.brand_new_category", };