From 44eabc5814fee44e1d43c8b3c10e1a6665f7b4fc Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 9 Sep 2026 10:18:59 -0700 Subject: [PATCH 1/6] GPU Zone Statistics --- profiler/src/profiler/TracyView.hpp | 1 + profiler/src/profiler/TracyView_FindZone.cpp | 295 +++++++++++++----- .../src/profiler/TracyView_Statistics.cpp | 2 +- profiler/src/profiler/TracyView_ZoneInfo.cpp | 13 + server/TracyWorker.cpp | 49 +++ server/TracyWorker.hpp | 7 + 6 files changed, 280 insertions(+), 87 deletions(-) diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index 8f71664a..b839a0af 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -440,6 +440,7 @@ private: void FindZones(); void FindZonesCompare(); + bool IsGpuSourceLocation( int16_t srcloc ) const; std::vector GetMemoryPages() const; diff --git a/profiler/src/profiler/TracyView_FindZone.cpp b/profiler/src/profiler/TracyView_FindZone.cpp index ef7ecc7a..518a15a7 100644 --- a/profiler/src/profiler/TracyView_FindZone.cpp +++ b/profiler/src/profiler/TracyView_FindZone.cpp @@ -23,10 +23,14 @@ void View::FindZones() m_findZone.match = m_worker.GetMatchingSourceLocation( m_findZone.pattern, m_findZone.ignoreCase ); if( m_findZone.match.empty() ) return; + const auto& gpuMap = m_worker.GetGpuSourceLocationZones(); auto it = m_findZone.match.begin(); while( it != m_findZone.match.end() ) { - if( m_worker.GetZonesForSourceLocation( *it ).zones.empty() ) + const bool hasCpu = !m_worker.GetZonesForSourceLocation( *it ).zones.empty(); + const auto gpu_it = gpuMap.find( *it ); + const bool hasGpu = gpu_it != gpuMap.end() && !gpu_it->second.zones.empty(); + if( !hasCpu && !hasGpu ) { it = m_findZone.match.erase( it ); } @@ -37,6 +41,14 @@ void View::FindZones() } } +bool View::IsGpuSourceLocation( int16_t srcloc ) const +{ + if( !m_worker.GetZonesForSourceLocation( srcloc ).zones.empty() ) return false; + const auto& gpuMap = m_worker.GetGpuSourceLocationZones(); + const auto it = gpuMap.find( srcloc ); + return it != gpuMap.end() && !it->second.zones.empty(); +} + uint64_t View::GetSelectionTarget( const Worker::ZoneThreadData& ev, FindZone::GroupBy groupBy ) const { switch( groupBy ) @@ -372,7 +384,17 @@ void View::DrawFindZone() for( auto& v : m_findZone.match ) { auto& srcloc = m_worker.GetSourceLocation( v ); - auto& zones = m_worker.GetZonesForSourceLocation( v ).zones; + const auto isGpuLoc = IsGpuSourceLocation( v ); + size_t zoneCnt; + if( isGpuLoc ) + { + const auto& gpuMap = m_worker.GetGpuSourceLocationZones(); + zoneCnt = gpuMap.find( v )->second.zones.size(); + } + else + { + zoneCnt = m_worker.GetZonesForSourceLocation( v ).zones.size(); + } SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); ImGui::SameLine(); ImGui::PushID( idx ); @@ -390,7 +412,7 @@ void View::DrawFindZone() ImGui::SameLine(); } const auto fileName = m_worker.GetString( srcloc.file ); - ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s", RealToString( zones.size() ), LocationToString( fileName, srcloc.line ) ); + ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s%s", RealToString( zoneCnt ), isGpuLoc ? "[GPU] " : "", LocationToString( fileName, srcloc.line ) ); if( ImGui::IsItemHovered() ) { DrawSourceTooltip( fileName, srcloc.line, srcloc.line ); @@ -423,9 +445,27 @@ void View::DrawFindZone() ImGui::Separator(); - auto& zoneData = m_worker.GetZonesForSourceLocation( m_findZone.match[m_findZone.selMatch] ); - auto& zones = zoneData.zones; - zones.ensure_sorted(); + const auto matchSrcloc = m_findZone.match[m_findZone.selMatch]; + const bool isGpu = IsGpuSourceLocation( matchSrcloc ); + + size_t zsz; + int64_t rawTotal, selfTotal; + if( !isGpu ) + { + auto& zoneData = m_worker.GetZonesForSourceLocation( matchSrcloc ); + zoneData.zones.ensure_sorted(); + zsz = zoneData.zones.size(); + rawTotal = zoneData.total; + selfTotal = zoneData.selfTotal; + } + else + { + auto& zoneData = m_worker.GetGpuZonesForSourceLocation( matchSrcloc ); + zoneData.zones.ensure_sorted(); + zsz = zoneData.zones.size(); + rawTotal = zoneData.total; + selfTotal = zoneData.selfTotal; + } if( ImGui::TreeNodeEx( "Histogram", ImGuiTreeNodeFlags_DefaultOpen ) ) { const auto ty = ImGui::GetTextLineHeight(); @@ -434,109 +474,180 @@ void View::DrawFindZone() int64_t tmax = m_findZone.tmax; int64_t total = m_findZone.total; double sumSq = m_findZone.sumSq; - const auto zsz = zones.size(); if( m_findZone.sortedNum != zsz ) { auto& vec = m_findZone.sorted; const auto vszorig = vec.size(); vec.reserve( zsz ); size_t i; - if( m_findZone.runningTime ) + if( !isGpu ) { - if( m_findZone.range.active ) + auto& zoneData = m_worker.GetZonesForSourceLocation( matchSrcloc ); + auto& zones = zoneData.zones; + if( m_findZone.runningTime ) { - for( i=m_findZone.sortedNum; i rangeMax || zone.Start() < rangeMin ) continue; - const auto ctx = m_worker.GetContextSwitchData( m_worker.DecompressThread( zones[i].Thread() ) ); - if( !ctx ) break; - int64_t t; - if( !GetZoneRunningTime( ctx, zone, t ) ) break; - vec.push_back_no_space_check( t ); - total += t; - sumSq += double( t ) * t; - if( t < tmin ) tmin = t; - else if( t > tmax ) tmax = t; + for( i=m_findZone.sortedNum; i rangeMax || zone.Start() < rangeMin ) continue; + const auto ctx = m_worker.GetContextSwitchData( m_worker.DecompressThread( zones[i].Thread() ) ); + if( !ctx ) break; + int64_t t; + if( !GetZoneRunningTime( ctx, zone, t ) ) break; + vec.push_back_no_space_check( t ); + total += t; + sumSq += double( t ) * t; + if( t < tmin ) tmin = t; + else if( t > tmax ) tmax = t; + } + } + else + { + for( i=m_findZone.sortedNum; i tmax ) tmax = t; + } + } + } + else if( m_findZone.selfTime ) + { + tmin = zoneData.selfMin; + tmax = zoneData.selfMax; + if( m_findZone.range.active ) + { + for( i=m_findZone.sortedNum; i rangeMax || start < rangeMin ) continue; + const auto t = end - start - GetZoneChildTimeFast( zone ); + vec.push_back_no_space_check( t ); + total += t; + sumSq += double( t ) * t; + } + } + else + { + for( i=m_findZone.sortedNum; i tmax ) tmax = t; + for( i=m_findZone.sortedNum; i rangeMax || start < rangeMin ) continue; + const auto t = end - start; + vec.push_back_no_space_check( t ); + total += t; + sumSq += double( t ) * t; + } } - } - } - else if( m_findZone.selfTime ) - { - tmin = zoneData.selfMin; - tmax = zoneData.selfMax; - if( m_findZone.range.active ) - { - for( i=m_findZone.sortedNum; i rangeMax || start < rangeMin ) continue; - const auto t = end - start - GetZoneChildTimeFast( zone ); - vec.push_back_no_space_check( t ); - total += t; - sumSq += double( t ) * t; - } - } - else - { - for( i=m_findZone.sortedNum; i rangeMax || start < rangeMin ) continue; - const auto t = end - start; - vec.push_back_no_space_check( t ); - total += t; - sumSq += double( t ) * t; + for( i=m_findZone.sortedNum; i rangeMax || start < rangeMin ) continue; + const auto t = end - start - GetZoneChildTime( zone ); + vec.push_back_no_space_check( t ); + total += t; + sumSq += double( t ) * t; + } + } + else + { + for( i=m_findZone.sortedNum; i rangeMax || start < rangeMin ) continue; + const auto t = end - start; + vec.push_back_no_space_check( t ); + total += t; + sumSq += double( t ) * t; + } + } + else + { + for( i=m_findZone.sortedNum; isecond : empty; } +Worker::GpuSourceLocationZones& Worker::GetGpuZonesForSourceLocation( int16_t srcloc ) +{ + assert( AreGpuSourceLocationZonesReady() ); + static GpuSourceLocationZones empty; + auto it = m_data.gpuSourceLocationZones.find( srcloc ); + return it != m_data.gpuSourceLocationZones.end() ? it->second : empty; +} + +const Worker::GpuSourceLocationZones& Worker::GetGpuZonesForSourceLocation( int16_t srcloc ) const +{ + assert( AreGpuSourceLocationZonesReady() ); + static const GpuSourceLocationZones empty; + auto it = m_data.gpuSourceLocationZones.find( srcloc ); + return it != m_data.gpuSourceLocationZones.end() ? it->second : empty; +} + const SymbolStats* Worker::GetSymbolStats( uint64_t symAddr ) const { assert( AreCallstackSamplesReady() ); @@ -3603,6 +3619,31 @@ Worker::GpuSourceLocationZones* Worker::GetGpuSourceLocationZonesReal( uint16_t m_data.gpuZonesLast.second = &it->second; return &it->second; } + +int64_t Worker::GetGpuChildTime( const GpuEvent& zone ) +{ + int64_t time = 0; + if( zone.Child() >= 0 ) + { + auto& children = GetGpuChildren( zone.Child() ); + if( children.is_magic() ) + { + auto& vec = *(Vector*)&children; + for( auto& v : vec ) + { + time += std::max( int64_t( 0 ), v.GpuEnd() - v.GpuStart() ); + } + } + else + { + for( auto& v : children ) + { + time += std::max( int64_t( 0 ), v->GpuEnd() - v->GpuStart() ); + } + } + } + return time; +} #else uint64_t* Worker::GetSourceLocationZonesCntReal( uint16_t srcloc ) { @@ -6200,6 +6241,10 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) if( slz->max < timeSpan ) slz->max = timeSpan; slz->total += timeSpan; slz->sumSq += double( timeSpan ) * timeSpan; + const auto selfSpan = timeSpan - GetGpuChildTime( *zone ); + if( slz->selfMin > selfSpan ) slz->selfMin = selfSpan; + if( slz->selfMax < selfSpan ) slz->selfMax = selfSpan; + slz->selfTotal += selfSpan; } #else CountZoneStatistics( zone ); @@ -8118,6 +8163,10 @@ void Worker::ReconstructZoneStatistics( GpuEvent& zone, uint16_t thread ) if( slz.max < timeSpan ) slz.max = timeSpan; slz.total += timeSpan; slz.sumSq += double( timeSpan ) * timeSpan; + const auto selfSpan = timeSpan - GetGpuChildTime( zone ); + if( slz.selfMin > selfSpan ) slz.selfMin = selfSpan; + if( slz.selfMax < selfSpan ) slz.selfMax = selfSpan; + slz.selfTotal += selfSpan; } } #else diff --git a/server/TracyWorker.hpp b/server/TracyWorker.hpp index 1ad677e9..0547ed3b 100644 --- a/server/TracyWorker.hpp +++ b/server/TracyWorker.hpp @@ -224,6 +224,9 @@ private: int64_t max = std::numeric_limits::min(); int64_t total = 0; double sumSq = 0; + int64_t selfMin = std::numeric_limits::max(); + int64_t selfMax = std::numeric_limits::min(); + int64_t selfTotal = 0; }; struct CallstackFrameIdHash @@ -645,6 +648,8 @@ public: #ifndef TRACY_NO_STATISTICS SourceLocationZones& GetZonesForSourceLocation( int16_t srcloc ); const SourceLocationZones& GetZonesForSourceLocation( int16_t srcloc ) const; + GpuSourceLocationZones& GetGpuZonesForSourceLocation( int16_t srcloc ); + const GpuSourceLocationZones& GetGpuZonesForSourceLocation( int16_t srcloc ) const; const unordered_flat_map& GetSourceLocationZones() const { return m_data.sourceLocationZones; } const unordered_flat_map& GetGpuSourceLocationZones() const { return m_data.gpuSourceLocationZones; } bool AreSourceLocationZonesReady() const { return m_data.sourceLocationZonesReady; } @@ -933,6 +938,8 @@ private: return GetGpuSourceLocationZonesReal( srcloc ); } GpuSourceLocationZones* GetGpuSourceLocationZonesReal( uint16_t srcloc ); + + int64_t GetGpuChildTime( const GpuEvent& zone ); #else uint64_t* GetSourceLocationZonesCnt( uint16_t srcloc ) { From a71d7c2ee21f22bde25e81e3ffb453acec9e75ba Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 9 Sep 2026 15:06:30 -0700 Subject: [PATCH 2/6] fix load-time data race on the GPU map --- profiler/src/profiler/TracyView_FindZone.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/profiler/src/profiler/TracyView_FindZone.cpp b/profiler/src/profiler/TracyView_FindZone.cpp index 518a15a7..00d10536 100644 --- a/profiler/src/profiler/TracyView_FindZone.cpp +++ b/profiler/src/profiler/TracyView_FindZone.cpp @@ -23,13 +23,18 @@ void View::FindZones() m_findZone.match = m_worker.GetMatchingSourceLocation( m_findZone.pattern, m_findZone.ignoreCase ); if( m_findZone.match.empty() ) return; + const bool gpuReady = m_worker.AreGpuSourceLocationZonesReady(); const auto& gpuMap = m_worker.GetGpuSourceLocationZones(); auto it = m_findZone.match.begin(); while( it != m_findZone.match.end() ) { const bool hasCpu = !m_worker.GetZonesForSourceLocation( *it ).zones.empty(); - const auto gpu_it = gpuMap.find( *it ); - const bool hasGpu = gpu_it != gpuMap.end() && !gpu_it->second.zones.empty(); + bool hasGpu = false; + if( gpuReady ) + { + const auto gpu_it = gpuMap.find( *it ); + hasGpu = gpu_it != gpuMap.end() && !gpu_it->second.zones.empty(); + } if( !hasCpu && !hasGpu ) { it = m_findZone.match.erase( it ); @@ -44,6 +49,7 @@ void View::FindZones() bool View::IsGpuSourceLocation( int16_t srcloc ) const { if( !m_worker.GetZonesForSourceLocation( srcloc ).zones.empty() ) return false; + if( !m_worker.AreGpuSourceLocationZonesReady() ) return false; const auto& gpuMap = m_worker.GetGpuSourceLocationZones(); const auto it = gpuMap.find( srcloc ); return it != gpuMap.end() && !it->second.zones.empty(); From 661f4de6361286af1ede8b3df1b51efd15b24b74 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 9 Sep 2026 15:09:04 -0700 Subject: [PATCH 3/6] addressing code duplication --- profiler/src/profiler/TracyView_Utility.cpp | 24 +---------- server/TracyWorker.cpp | 47 +++++++++++---------- server/TracyWorker.hpp | 3 +- 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/profiler/src/profiler/TracyView_Utility.cpp b/profiler/src/profiler/TracyView_Utility.cpp index 114640a7..f1d799f7 100644 --- a/profiler/src/profiler/TracyView_Utility.cpp +++ b/profiler/src/profiler/TracyView_Utility.cpp @@ -577,29 +577,7 @@ int64_t View::GetZoneChildTime( const ZoneEvent& zone ) int64_t View::GetZoneChildTime( const GpuEvent& zone ) { - int64_t time = 0; - if( zone.Child() >= 0 ) - { - auto& children = m_worker.GetGpuChildren( zone.Child() ); - if( children.is_magic() ) - { - auto& vec = *(Vector*)&children; - for( auto& v : vec ) - { - const auto childSpan = std::max( int64_t( 0 ), v.GpuEnd() - v.GpuStart() ); - time += childSpan; - } - } - else - { - for( auto& v : children ) - { - const auto childSpan = std::max( int64_t( 0 ), v->GpuEnd() - v->GpuStart() ); - time += childSpan; - } - } - } - return time; + return m_worker.GetGpuChildTime( zone ); } int64_t View::GetZoneChildTimeFast( const ZoneEvent& zone ) diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 79d717f5..1c84799a 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -3598,29 +3598,7 @@ ThreadData* Worker::GetCurrentThreadData() return td; } -#ifndef TRACY_NO_STATISTICS -Worker::SourceLocationZones* Worker::GetSourceLocationZonesReal( uint16_t srcloc ) -{ - auto it = m_data.sourceLocationZones.find( srcloc ); - assert( it != m_data.sourceLocationZones.end() ); - m_data.srclocZonesLast.first = srcloc; - m_data.srclocZonesLast.second = &it->second; - return &it->second; -} - -Worker::GpuSourceLocationZones* Worker::GetGpuSourceLocationZonesReal( uint16_t srcloc ) -{ - auto it = m_data.gpuSourceLocationZones.find( srcloc ); - if( it == m_data.gpuSourceLocationZones.end() ) - { - it = m_data.gpuSourceLocationZones.emplace( srcloc, GpuSourceLocationZones() ).first; - } - m_data.gpuZonesLast.first = srcloc; - m_data.gpuZonesLast.second = &it->second; - return &it->second; -} - -int64_t Worker::GetGpuChildTime( const GpuEvent& zone ) +int64_t Worker::GetGpuChildTime( const GpuEvent& zone ) const { int64_t time = 0; if( zone.Child() >= 0 ) @@ -3644,6 +3622,29 @@ int64_t Worker::GetGpuChildTime( const GpuEvent& zone ) } return time; } + +#ifndef TRACY_NO_STATISTICS +Worker::SourceLocationZones* Worker::GetSourceLocationZonesReal( uint16_t srcloc ) +{ + auto it = m_data.sourceLocationZones.find( srcloc ); + assert( it != m_data.sourceLocationZones.end() ); + m_data.srclocZonesLast.first = srcloc; + m_data.srclocZonesLast.second = &it->second; + return &it->second; +} + +Worker::GpuSourceLocationZones* Worker::GetGpuSourceLocationZonesReal( uint16_t srcloc ) +{ + auto it = m_data.gpuSourceLocationZones.find( srcloc ); + if( it == m_data.gpuSourceLocationZones.end() ) + { + it = m_data.gpuSourceLocationZones.emplace( srcloc, GpuSourceLocationZones() ).first; + } + m_data.gpuZonesLast.first = srcloc; + m_data.gpuZonesLast.second = &it->second; + return &it->second; +} + #else uint64_t* Worker::GetSourceLocationZonesCntReal( uint16_t srcloc ) { diff --git a/server/TracyWorker.hpp b/server/TracyWorker.hpp index 0547ed3b..80295613 100644 --- a/server/TracyWorker.hpp +++ b/server/TracyWorker.hpp @@ -633,6 +633,7 @@ public: tracy_force_inline const Vector>& GetZoneChildren( int32_t idx ) const { return m_data.zoneChildren[idx]; } tracy_force_inline const Vector>& GetGpuChildren( int32_t idx ) const { return m_data.gpuChildren[idx]; } + int64_t GetGpuChildTime( const GpuEvent& zone ) const; #ifndef TRACY_NO_STATISTICS tracy_force_inline const Vector& GetGhostChildren( int32_t idx ) const { return m_data.ghostChildren[idx]; } tracy_force_inline const GhostKey& GetGhostFrame( const Int24& frame ) const { return m_data.ghostFrames[frame.Val()]; } @@ -938,8 +939,6 @@ private: return GetGpuSourceLocationZonesReal( srcloc ); } GpuSourceLocationZones* GetGpuSourceLocationZonesReal( uint16_t srcloc ); - - int64_t GetGpuChildTime( const GpuEvent& zone ); #else uint64_t* GetSourceLocationZonesCnt( uint16_t srcloc ) { From 9535a39fba89b92f9c72e72e254be9054ad1f32c Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 9 Sep 2026 15:12:00 -0700 Subject: [PATCH 4/6] refactoring --- profiler/src/profiler/TracyView.hpp | 1 + profiler/src/profiler/TracyView_FindZone.cpp | 1259 +++++++++--------- 2 files changed, 637 insertions(+), 623 deletions(-) diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index b839a0af..3daf4f56 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -331,6 +331,7 @@ private: void DrawMessages(); void DrawMessageLine( const MessageData& msg, bool hasCallstack, int& idx ); void DrawFindZone(); + void DrawFindZoneCpuDetails( int16_t matchSrcloc ); void AccumulationModeComboBox(); void DrawStatistics(); void DrawSamplesStatistics(Vector& data, int64_t timeRange, uint64_t totalSamples, AccumulationMode accumulationMode); diff --git a/profiler/src/profiler/TracyView_FindZone.cpp b/profiler/src/profiler/TracyView_FindZone.cpp index 00d10536..497bd571 100644 --- a/profiler/src/profiler/TracyView_FindZone.cpp +++ b/profiler/src/profiler/TracyView_FindZone.cpp @@ -453,6 +453,11 @@ void View::DrawFindZone() const auto matchSrcloc = m_findZone.match[m_findZone.selMatch]; const bool isGpu = IsGpuSourceLocation( matchSrcloc ); + if( isGpu && m_findZone.runningTime ) + { + m_findZone.runningTime = false; + m_findZone.scheduleResetMatch = true; + } size_t zsz; int64_t rawTotal, selfTotal; @@ -1676,633 +1681,641 @@ void View::DrawFindZone() } else { - auto& zoneData = m_worker.GetZonesForSourceLocation( matchSrcloc ); - auto& zones = zoneData.zones; - ImGui::Separator(); - SmallCheckbox( "Show zone time in frames", &m_findZone.showZoneInFrames ); - ImGui::Separator(); - - ImGui::AlignTextToFramePadding(); - TextDisabledUnformatted( "Filter user text:" ); - ImGui::SameLine(); - bool filterChanged = m_userTextFilter.Draw( ICON_FA_FILTER "###resultFilter", 200 ); - - ImGui::SameLine(); - if( ImGui::Button( ICON_FA_DELETE_LEFT " Clear###userText" ) ) - { - m_userTextFilter.Clear(); - filterChanged = true; - } - ImGui::Separator(); - if( filterChanged ) - { - m_filteredZones.clear(); - m_findZone.ResetGroups(); - } - - ImGui::TextUnformatted( "Found zones:" ); - ImGui::SameLine(); - DrawHelpMarker( "Left click to highlight entry." ); - if( m_findZone.selGroup != m_findZone.Unselected ) - { - ImGui::SameLine(); - if( ImGui::SmallButton( ICON_FA_DELETE_LEFT " Clear" ) ) - { - m_findZone.selGroup = m_findZone.Unselected; - m_findZone.ResetSelection(); - } - } - - bool groupChanged = false; - ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); - ImGui::TextUnformatted( "Group by:" ); - ImGui::SameLine(); - groupChanged |= ImGui::RadioButton( "Thread", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::Thread ); - ImGui::SameLine(); - groupChanged |= ImGui::RadioButton( "User text", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::UserText ); - ImGui::SameLine(); - groupChanged |= ImGui::RadioButton( "Zone name", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::ZoneName ); - ImGui::SameLine(); - groupChanged |= ImGui::RadioButton( "Call stacks", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::Callstack ); - ImGui::SameLine(); - groupChanged |= ImGui::RadioButton( "Parent", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::Parent ); - ImGui::SameLine(); - groupChanged |= ImGui::RadioButton( "No grouping", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::NoGrouping ); - if( groupChanged ) - { - m_findZone.selGroup = m_findZone.Unselected; - m_findZone.ResetGroups(); - } - m_findZoneConstraint.MarkMinWidth(); - - ImGui::TextUnformatted( "Sort by:" ); - ImGui::SameLine(); - ImGui::RadioButton( "Order", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Order ); - ImGui::SameLine(); - ImGui::RadioButton( "Count", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Count ); - ImGui::SameLine(); - ImGui::RadioButton( "Time", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Time ); - ImGui::SameLine(); - ImGui::RadioButton( "MTPC", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Mtpc ); - ImGui::PopStyleVar(); - ImGui::SameLine(); - DrawHelpMarker( "Mean time per call" ); - - const auto hmin = std::min( m_findZone.highlight.start, m_findZone.highlight.end ); - const auto hmax = std::max( m_findZone.highlight.start, m_findZone.highlight.end ); - const auto groupBy = m_findZone.groupBy; - const auto highlightActive = m_findZone.highlight.active; - const auto limitRange = m_findZone.range.active; - FindZone::Group* group = nullptr; - constexpr uint64_t invalidGid = std::numeric_limits::max() - 1; - uint64_t lastGid = invalidGid; - auto zptr = zones.data() + m_findZone.processed; - const auto zend = zones.data() + zones.size(); - while( zptr < zend ) - { - auto& ev = *zptr; - const auto end = ev.Zone()->End(); - const auto start = ev.Zone()->Start(); - if( limitRange && ( start < rangeMin || end > rangeMax ) ) - { - zptr++; - continue; - } - - if( m_userTextFilter.IsActive() ) - { - bool keep = false; - if ( m_worker.HasZoneExtra( *ev.Zone() ) && m_worker.GetZoneExtra( *ev.Zone() ).text.Active() ) - { - auto text = m_worker.GetString( m_worker.GetZoneExtra( *ev.Zone() ).text ); - if( m_userTextFilter.PassFilter( text ) ) - { - keep = true; - } - } - if( !keep ) - { - m_filteredZones.insert( &ev ); - zptr++; - continue; - } - } - - auto timespan = end - start; - assert( timespan != 0 ); - if( m_findZone.selfTime ) - { - timespan -= GetZoneChildTimeFast( *ev.Zone() ); - } - else if( m_findZone.runningTime ) - { - const auto ctx = m_worker.GetContextSwitchData( m_worker.DecompressThread( ev.Thread() ) ); - if( !ctx ) break; - int64_t t; - if( !GetZoneRunningTime( ctx, *ev.Zone(), t ) ) break; - timespan = t; - } - - if( highlightActive ) - { - if( timespan < hmin || timespan > hmax ) - { - zptr++; - continue; - } - } - - zptr++; - uint64_t gid = 0; - switch( groupBy ) - { - case FindZone::GroupBy::Thread: - gid = ev.Thread(); - break; - case FindZone::GroupBy::UserText: - { - const auto& zone = *ev.Zone(); - if( !m_worker.HasZoneExtra( zone ) ) - { - gid = std::numeric_limits::max(); - } - else - { - const auto& extra = m_worker.GetZoneExtra( zone ); - gid = extra.text.Active() ? extra.text.Idx() : std::numeric_limits::max(); - } - break; - } - case FindZone::GroupBy::ZoneName: - { - const auto& zone = *ev.Zone(); - if( !m_worker.HasZoneExtra( zone ) ) - { - gid = std::numeric_limits::max(); - } - else - { - const auto& extra = m_worker.GetZoneExtra( zone ); - gid = extra.name.Active() ? extra.name.Idx() : std::numeric_limits::max(); - } - break; - } - case FindZone::GroupBy::Callstack: - gid = m_worker.GetZoneExtra( *ev.Zone() ).callstack.Val(); - break; - case FindZone::GroupBy::Parent: - { - const auto parent = GetZoneParent( *ev.Zone(), m_worker.DecompressThread( ev.Thread() ) ); - if( parent ) gid = uint64_t( uint16_t( parent->SrcLoc() ) ); - break; - } - case FindZone::GroupBy::NoGrouping: - break; - default: - assert( false ); - break; - } - if( lastGid != gid ) - { - lastGid = gid; - auto it = m_findZone.groups.find( gid ); - if( it == m_findZone.groups.end() ) - { - it = m_findZone.groups.emplace( gid, FindZone::Group { m_findZone.groupId++ } ).first; - it->second.zones.reserve( 1024 ); - if( m_findZone.samples.enabled ) - it->second.zonesTids.reserve( 1024 ); - } - group = &it->second; - } - group->time += timespan; - group->zones.push_back_non_empty( ev.Zone() ); - if( m_findZone.samples.enabled ) - group->zonesTids.push_back_non_empty( ev.Thread() ); - } - m_findZone.processed = zptr - zones.data(); - - const bool groupsUpdated = lastGid != invalidGid; - if( m_findZone.samples.enabled && groupsUpdated ) - { - m_findZone.samples.scheduleUpdate = true; - } - - - Vector groups; - groups.reserve_and_use( m_findZone.groups.size() ); - int idx = 0; - for( auto it = m_findZone.groups.begin(); it != m_findZone.groups.end(); ++it ) - { - groups[idx++] = it; - } - - switch( m_findZone.sortBy ) - { - case FindZone::SortBy::Order: - pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.id < rhs->second.id; } ); - break; - case FindZone::SortBy::Count: - pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.zones.size() > rhs->second.zones.size(); } ); - break; - case FindZone::SortBy::Time: - pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.time > rhs->second.time; } ); - break; - case FindZone::SortBy::Mtpc: - pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return double( lhs->second.time ) / lhs->second.zones.size() > double( rhs->second.time ) / rhs->second.zones.size(); } ); - break; - default: - assert( false ); - break; - } - - int16_t changeZone = 0; - - if( groupBy == FindZone::GroupBy::Callstack ) - { - const auto gsz = (int)groups.size(); - if( gsz > 0 ) - { - if( m_findZone.selCs > gsz ) m_findZone.selCs = gsz; - const auto group = groups[m_findZone.selCs]; - - const bool selHilite = m_findZone.selGroup == group->first; - if( selHilite ) SetButtonHighlightColor(); - if( ImGui::SmallButton( " " ICON_FA_CHECK " " ) ) - { - m_findZone.selGroup = group->first; - m_findZone.ResetSelection(); - } - if( selHilite ) ImGui::PopStyleColor( 3 ); - ImGui::SameLine(); - if( ImGui::SmallButton( " " ICON_FA_CARET_LEFT " " ) ) - { - m_findZone.selCs = std::max( m_findZone.selCs - 1, 0 ); - } - ImGui::SameLine(); - ImGui::Text( "%s / %s", RealToString( m_findZone.selCs + 1 ), RealToString( gsz ) ); - if( ImGui::IsItemClicked() ) ImGui::OpenPopup( "FindZoneCallstackPopup" ); - ImGui::SameLine(); - if( ImGui::SmallButton( " " ICON_FA_CARET_RIGHT " " ) ) - { - m_findZone.selCs = std::min( m_findZone.selCs + 1, gsz - 1 ); - } - if( ImGui::BeginPopup( "FindZoneCallstackPopup" ) ) - { - int sel = m_findZone.selCs + 1; - ImGui::SetNextItemWidth( 120 * scale ); - const bool clicked = ImGui::InputInt( "##findZoneCallstack", &sel, 1, 100, ImGuiInputTextFlags_EnterReturnsTrue ); - if( clicked ) m_findZone.selCs = std::min( std::max( sel, 1 ), int( gsz ) ) - 1; - ImGui::EndPopup(); - } - - ImGui::SameLine(); - TextFocused( "Count:", RealToString( group->second.zones.size() ) ); - ImGui::SameLine(); - TextFocused( "Time:", TimeToString( group->second.time ) ); - ImGui::SameLine(); - char buf[64]; - PrintStringPercent( buf, group->second.time * 100.f / zoneData.total ); - TextDisabledUnformatted( buf ); - - if( group->first != 0 ) - { - ImGui::SameLine(); - int idx = 0; - SmallCallstackButton( " " ICON_FA_ALIGN_JUSTIFY " ", group->first, idx, 0, false ); - - int fidx = 0; - ImGui::Spacing(); - ImGui::Indent(); - auto& csdata = m_worker.GetCallstack( group->first ); - for( auto& entry : csdata ) - { - auto frameData = m_worker.GetCallstackFrame( entry ); - if( !frameData ) - { - ImGui::TextDisabled( "%i.", fidx++ ); - ImGui::SameLine(); - ImGui::Text( "%p", (void*)m_worker.GetCanonicalPointer( entry ) ); - } - else - { - const auto fsz = frameData->size; - for( uint8_t f=0; fdata[f]; - auto txt = m_worker.GetString( frame.name ); - - if( fidx == 0 && f != fsz-1 ) - { - auto test = s_tracyStackFrames; - bool match = false; - do - { - if( strcmp( txt, *test ) == 0 ) - { - match = true; - break; - } - } - while( *++test ); - if( match ) continue; - } - if( f == fsz-1 ) - { - ImGui::TextDisabled( "%i.", fidx++ ); - } - else - { - TextDisabledUnformatted( ICON_FA_CARET_RIGHT ); - } - ImGui::SameLine(); - if( m_vd.shortenName == ShortenName::Never ) - { - ImGui::TextUnformatted( txt ); - } - else - { - const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, txt ); - ImGui::TextUnformatted( normalized ); - TooltipNormalizedName( txt, normalized ); - } - } - } - } - ImGui::Unindent(); - } - else - { - ImGui::Text( "No call stack" ); - } - - ImGui::Spacing(); - if( ImGui::TreeNodeEx( "Zone list" ) ) - { - DrawZoneList( group->second.id, group->second.zones ); - } - } - } - else - { - TextFocused( "Number of groups:", RealToString( groups.size() ) ); - for( auto& v : groups ) - { - bool isFiber = false; - const char* hdrString; - switch( groupBy ) - { - case FindZone::GroupBy::Thread: - { - const auto tid = m_worker.DecompressThread( v->first ); - const auto threadColor = GetThreadColor( tid, 0 ); - SmallColorBox( threadColor ); - ImGui::SameLine(); - hdrString = m_worker.GetThreadName( tid ); - isFiber = m_worker.IsThreadFiber( tid ); - break; - } - case FindZone::GroupBy::UserText: - hdrString = v->first == std::numeric_limits::max() ? "No user text" : m_worker.GetString( StringIdx( v->first ) ); - break; - case FindZone::GroupBy::ZoneName: - if( v->first == std::numeric_limits::max() ) - { - auto& srcloc = m_worker.GetSourceLocation( m_findZone.match[m_findZone.selMatch] ); - hdrString = m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ); - } - else - { - hdrString = m_worker.GetString( StringIdx( v->first ) ); - } - break; - case FindZone::GroupBy::Callstack: - if( v->first == 0 ) - { - hdrString = "No callstack"; - } - else - { - auto& callstack = m_worker.GetCallstack( v->first ); - auto& frameData = *m_worker.GetCallstackFrame( *callstack.begin() ); - hdrString = m_worker.GetString( frameData.data[frameData.size-1].name ); - } - break; - case FindZone::GroupBy::Parent: - if( v->first == 0 ) - { - hdrString = ""; - SmallColorBox( 0 ); - } - else - { - auto& srcloc = m_worker.GetSourceLocation( int16_t( v->first ) ); - hdrString = m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ); - SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); - } - ImGui::SameLine(); - break; - case FindZone::GroupBy::NoGrouping: - hdrString = "Zone list"; - break; - default: - hdrString = nullptr; - assert( false ); - break; - } - ImGui::PushID( v->first ); - const bool expand = ImGui::TreeNodeEx( hdrString, ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ( v->first == m_findZone.selGroup ? ImGuiTreeNodeFlags_Selected : 0 ) ); - if( ImGui::IsItemClicked() ) - { - m_findZone.selGroup = v->first; - m_findZone.ResetSelection(); - } - if( m_findZone.groupBy == FindZone::GroupBy::Parent && ImGui::IsItemClicked( 2 ) ) - { - changeZone = int16_t( v->first ); - } - ImGui::PopID(); - if( isFiber ) - { - ImGui::SameLine(); - TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" ); - } - ImGui::SameLine(); - ImGui::TextColored( ImVec4( 0.5f, 0.5f, 0.5f, 1.0f ), "(%s) %s", RealToString( v->second.zones.size() ), TimeToString( v->second.time ) ); - if( expand ) - { - DrawZoneList( v->second.id, v->second.zones ); - } - } - } - - if( m_findZone.samples.enabled && m_findZone.samples.scheduleUpdate && !m_findZone.scheduleResetMatch && m_worker.AreSymbolSamplesReady() ) - { - m_findZone.samples.scheduleUpdate = false; - - const auto& symMap = m_worker.GetSymbolMap(); - m_findZone.samples.counts.clear(); - m_findZone.samples.counts.reserve( symMap.size() ); - - struct GroupRange { - const FindZone::Group* group; - Vector>::const_iterator begin; - Vector>::const_iterator end; - }; - Vector selectedGroups; - selectedGroups.reserve( m_findZone.groups.size() ); - for( auto it = m_findZone.groups.begin(); it != m_findZone.groups.end(); ++it ) - { - assert( it->second.zones.size() == it->second.zonesTids.size() ); - if( ( m_findZone.selGroup == m_findZone.Unselected || it->first == m_findZone.selGroup ) - && !it->second.zones.empty() ) - { - selectedGroups.push_back_no_space_check( GroupRange{&it->second} ); - } - } - - for( auto& v : symMap ) - { - bool pass = ( m_statShowKernel || ( v.first >> 63 ) == 0 ); - if( !pass && v.second.size.Val() == 0 ) - { - const auto parentAddr = m_worker.GetSymbolForAddress( v.first ); - if( parentAddr != 0 ) - { - auto pit = symMap.find( parentAddr ); - if( pit != symMap.end() ) - { - pass = ( m_statShowKernel || ( parentAddr >> 63 ) == 0 ); - } - } - } - if( !pass ) continue; - - auto samples = m_worker.GetSamplesForSymbol( v.first ); - if( !samples ) continue; - - auto samplesBegin = samples->begin(); - auto samplesEnd = samples->end(); - if( m_findZone.range.active ) - { - const auto rangeMin = m_findZone.range.min; - const auto rangeMax = m_findZone.range.max; - samplesBegin = std::lower_bound( samplesBegin, samplesEnd, rangeMin, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } ); - samplesEnd = std::lower_bound( samplesBegin, samplesEnd, rangeMax, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } ); - } - if( samplesBegin == samplesEnd ) continue; - - bool empty = true; - const auto firstTime = samplesBegin->time.Val(); - const auto lastTime = samplesEnd == samples->end() ? m_worker.GetLastTime() : samplesEnd->time.Val(); - for( auto& g: selectedGroups ) - { - const auto& zones = g.group->zones; - auto begin = std::lower_bound( zones.begin(), zones.end(), firstTime, [] ( const auto& l, const auto& r ) { return l->Start() < r; } ); - auto end = std::upper_bound( begin, zones.end(), lastTime, [] ( const auto& l, const auto& r ) { return l <= r->Start(); } ); - g.begin = begin; - g.end = end; - empty = empty && (begin == end); - } - if (empty) continue; - - uint32_t count = 0; - for( auto it = samplesBegin; it != samplesEnd; ++it ) - { - const auto time = it->time.Val(); - bool pass = false; - for( auto& g: selectedGroups ) - { - while( g.begin != g.end && time > (*g.begin)->End() ) ++g.begin; - if( g.begin == g.end ) continue; - if( time < (*g.begin)->Start() ) continue; - - const auto& tids = g.group->zonesTids; - const auto firstZone = g.group->zones.begin(); - for (auto z = g.begin; z != g.end && (*z)->Start() <= time; ++z) - { - auto zoneIndex = z - firstZone; - if( (*z)->End() > time && it->thread == tids[zoneIndex] ) - { - pass = true; - break; - } - } - } - if( pass ) count ++; - } - if( count > 0 ) m_findZone.samples.counts.push_back_no_space_check( SymList { v.first, 0, count } ); - } - } - - ImGui::Separator(); - const bool hasSamples = m_worker.AreCallstackSamplesReady() && m_worker.GetCallstackSampleCount() > 0; - if( hasSamples && ImGui::TreeNodeEx( ICON_FA_EYE_DROPPER " Samples", ImGuiTreeNodeFlags_None ) ) - { - { - ImGui::Checkbox( ICON_FA_STOPWATCH " Show time", &m_statSampleTime ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - ImGui::Checkbox( ICON_FA_EYE_SLASH " Hide unknown", &m_statHideUnknown ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - ImGui::Checkbox( ICON_FA_SITEMAP " Inlines", &m_statSeparateInlines ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - ImGui::Checkbox( ICON_FA_AT " Address", &m_statShowAddress ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - if( ImGui::Checkbox( ICON_FA_HAT_WIZARD " Kernel", &m_statShowKernel )) - { - m_findZone.samples.scheduleUpdate = true; - } - m_findZoneConstraint.MarkMinWidth(); - } - - if( !m_findZone.samples.enabled ) - { - m_findZone.samples.enabled = true; - m_findZone.samples.scheduleUpdate = true; - m_findZone.scheduleResetMatch = true; - } - - Vector data; - data.reserve( m_findZone.samples.counts.size() ); - uint64_t totalSamples = 0; - for( auto it: m_findZone.samples.counts ) - { - data.push_back_no_space_check( it ); - totalSamples += it.excl; - } - int64_t timeRange = ( m_findZone.selGroup != m_findZone.Unselected ) ? m_findZone.selTotal : m_findZone.total; - DrawSamplesStatistics( data, timeRange, totalSamples, AccumulationMode::SelfOnly ); - - ImGui::TreePop(); - } - else - { - if( m_findZone.samples.enabled ) - { - m_findZone.samples.enabled = false; - m_findZone.samples.scheduleUpdate = false; - m_findZone.samples.counts = Vector(); - for( auto& it: m_findZone.groups ) it.second.zonesTids.clear(); - } - } - - if( changeZone != 0 ) - { - auto& srcloc = m_worker.GetSourceLocation( changeZone ); - m_findZone.ShowZone( changeZone, m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) ); - } + DrawFindZoneCpuDetails( matchSrcloc ); } } ImGui::EndChild(); ImGui::End(); } +void View::DrawFindZoneCpuDetails( int16_t matchSrcloc ) +{ + const auto scale = GetScale(); + const auto rangeMin = m_findZone.range.min; + const auto rangeMax = m_findZone.range.max; + auto& zoneData = m_worker.GetZonesForSourceLocation( matchSrcloc ); + auto& zones = zoneData.zones; + ImGui::Separator(); + SmallCheckbox( "Show zone time in frames", &m_findZone.showZoneInFrames ); + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); + TextDisabledUnformatted( "Filter user text:" ); + ImGui::SameLine(); + bool filterChanged = m_userTextFilter.Draw( ICON_FA_FILTER "###resultFilter", 200 ); + + ImGui::SameLine(); + if( ImGui::Button( ICON_FA_DELETE_LEFT " Clear###userText" ) ) + { + m_userTextFilter.Clear(); + filterChanged = true; + } + ImGui::Separator(); + if( filterChanged ) + { + m_filteredZones.clear(); + m_findZone.ResetGroups(); + } + + ImGui::TextUnformatted( "Found zones:" ); + ImGui::SameLine(); + DrawHelpMarker( "Left click to highlight entry." ); + if( m_findZone.selGroup != m_findZone.Unselected ) + { + ImGui::SameLine(); + if( ImGui::SmallButton( ICON_FA_DELETE_LEFT " Clear" ) ) + { + m_findZone.selGroup = m_findZone.Unselected; + m_findZone.ResetSelection(); + } + } + + bool groupChanged = false; + ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); + ImGui::TextUnformatted( "Group by:" ); + ImGui::SameLine(); + groupChanged |= ImGui::RadioButton( "Thread", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::Thread ); + ImGui::SameLine(); + groupChanged |= ImGui::RadioButton( "User text", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::UserText ); + ImGui::SameLine(); + groupChanged |= ImGui::RadioButton( "Zone name", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::ZoneName ); + ImGui::SameLine(); + groupChanged |= ImGui::RadioButton( "Call stacks", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::Callstack ); + ImGui::SameLine(); + groupChanged |= ImGui::RadioButton( "Parent", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::Parent ); + ImGui::SameLine(); + groupChanged |= ImGui::RadioButton( "No grouping", (int*)( &m_findZone.groupBy ), (int)FindZone::GroupBy::NoGrouping ); + if( groupChanged ) + { + m_findZone.selGroup = m_findZone.Unselected; + m_findZone.ResetGroups(); + } + m_findZoneConstraint.MarkMinWidth(); + + ImGui::TextUnformatted( "Sort by:" ); + ImGui::SameLine(); + ImGui::RadioButton( "Order", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Order ); + ImGui::SameLine(); + ImGui::RadioButton( "Count", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Count ); + ImGui::SameLine(); + ImGui::RadioButton( "Time", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Time ); + ImGui::SameLine(); + ImGui::RadioButton( "MTPC", (int*)( &m_findZone.sortBy ), (int)FindZone::SortBy::Mtpc ); + ImGui::PopStyleVar(); + ImGui::SameLine(); + DrawHelpMarker( "Mean time per call" ); + + const auto hmin = std::min( m_findZone.highlight.start, m_findZone.highlight.end ); + const auto hmax = std::max( m_findZone.highlight.start, m_findZone.highlight.end ); + const auto groupBy = m_findZone.groupBy; + const auto highlightActive = m_findZone.highlight.active; + const auto limitRange = m_findZone.range.active; + FindZone::Group* group = nullptr; + constexpr uint64_t invalidGid = std::numeric_limits::max() - 1; + uint64_t lastGid = invalidGid; + auto zptr = zones.data() + m_findZone.processed; + const auto zend = zones.data() + zones.size(); + while( zptr < zend ) + { + auto& ev = *zptr; + const auto end = ev.Zone()->End(); + const auto start = ev.Zone()->Start(); + if( limitRange && ( start < rangeMin || end > rangeMax ) ) + { + zptr++; + continue; + } + + if( m_userTextFilter.IsActive() ) + { + bool keep = false; + if ( m_worker.HasZoneExtra( *ev.Zone() ) && m_worker.GetZoneExtra( *ev.Zone() ).text.Active() ) + { + auto text = m_worker.GetString( m_worker.GetZoneExtra( *ev.Zone() ).text ); + if( m_userTextFilter.PassFilter( text ) ) + { + keep = true; + } + } + if( !keep ) + { + m_filteredZones.insert( &ev ); + zptr++; + continue; + } + } + + auto timespan = end - start; + assert( timespan != 0 ); + if( m_findZone.selfTime ) + { + timespan -= GetZoneChildTimeFast( *ev.Zone() ); + } + else if( m_findZone.runningTime ) + { + const auto ctx = m_worker.GetContextSwitchData( m_worker.DecompressThread( ev.Thread() ) ); + if( !ctx ) break; + int64_t t; + if( !GetZoneRunningTime( ctx, *ev.Zone(), t ) ) break; + timespan = t; + } + + if( highlightActive ) + { + if( timespan < hmin || timespan > hmax ) + { + zptr++; + continue; + } + } + + zptr++; + uint64_t gid = 0; + switch( groupBy ) + { + case FindZone::GroupBy::Thread: + gid = ev.Thread(); + break; + case FindZone::GroupBy::UserText: + { + const auto& zone = *ev.Zone(); + if( !m_worker.HasZoneExtra( zone ) ) + { + gid = std::numeric_limits::max(); + } + else + { + const auto& extra = m_worker.GetZoneExtra( zone ); + gid = extra.text.Active() ? extra.text.Idx() : std::numeric_limits::max(); + } + break; + } + case FindZone::GroupBy::ZoneName: + { + const auto& zone = *ev.Zone(); + if( !m_worker.HasZoneExtra( zone ) ) + { + gid = std::numeric_limits::max(); + } + else + { + const auto& extra = m_worker.GetZoneExtra( zone ); + gid = extra.name.Active() ? extra.name.Idx() : std::numeric_limits::max(); + } + break; + } + case FindZone::GroupBy::Callstack: + gid = m_worker.GetZoneExtra( *ev.Zone() ).callstack.Val(); + break; + case FindZone::GroupBy::Parent: + { + const auto parent = GetZoneParent( *ev.Zone(), m_worker.DecompressThread( ev.Thread() ) ); + if( parent ) gid = uint64_t( uint16_t( parent->SrcLoc() ) ); + break; + } + case FindZone::GroupBy::NoGrouping: + break; + default: + assert( false ); + break; + } + if( lastGid != gid ) + { + lastGid = gid; + auto it = m_findZone.groups.find( gid ); + if( it == m_findZone.groups.end() ) + { + it = m_findZone.groups.emplace( gid, FindZone::Group { m_findZone.groupId++ } ).first; + it->second.zones.reserve( 1024 ); + if( m_findZone.samples.enabled ) + it->second.zonesTids.reserve( 1024 ); + } + group = &it->second; + } + group->time += timespan; + group->zones.push_back_non_empty( ev.Zone() ); + if( m_findZone.samples.enabled ) + group->zonesTids.push_back_non_empty( ev.Thread() ); + } + m_findZone.processed = zptr - zones.data(); + + const bool groupsUpdated = lastGid != invalidGid; + if( m_findZone.samples.enabled && groupsUpdated ) + { + m_findZone.samples.scheduleUpdate = true; + } + + + Vector groups; + groups.reserve_and_use( m_findZone.groups.size() ); + int idx = 0; + for( auto it = m_findZone.groups.begin(); it != m_findZone.groups.end(); ++it ) + { + groups[idx++] = it; + } + + switch( m_findZone.sortBy ) + { + case FindZone::SortBy::Order: + pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.id < rhs->second.id; } ); + break; + case FindZone::SortBy::Count: + pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.zones.size() > rhs->second.zones.size(); } ); + break; + case FindZone::SortBy::Time: + pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.time > rhs->second.time; } ); + break; + case FindZone::SortBy::Mtpc: + pdqsort_branchless( groups.begin(), groups.end(), []( const auto& lhs, const auto& rhs ) { return double( lhs->second.time ) / lhs->second.zones.size() > double( rhs->second.time ) / rhs->second.zones.size(); } ); + break; + default: + assert( false ); + break; + } + + int16_t changeZone = 0; + + if( groupBy == FindZone::GroupBy::Callstack ) + { + const auto gsz = (int)groups.size(); + if( gsz > 0 ) + { + if( m_findZone.selCs > gsz ) m_findZone.selCs = gsz; + const auto group = groups[m_findZone.selCs]; + + const bool selHilite = m_findZone.selGroup == group->first; + if( selHilite ) SetButtonHighlightColor(); + if( ImGui::SmallButton( " " ICON_FA_CHECK " " ) ) + { + m_findZone.selGroup = group->first; + m_findZone.ResetSelection(); + } + if( selHilite ) ImGui::PopStyleColor( 3 ); + ImGui::SameLine(); + if( ImGui::SmallButton( " " ICON_FA_CARET_LEFT " " ) ) + { + m_findZone.selCs = std::max( m_findZone.selCs - 1, 0 ); + } + ImGui::SameLine(); + ImGui::Text( "%s / %s", RealToString( m_findZone.selCs + 1 ), RealToString( gsz ) ); + if( ImGui::IsItemClicked() ) ImGui::OpenPopup( "FindZoneCallstackPopup" ); + ImGui::SameLine(); + if( ImGui::SmallButton( " " ICON_FA_CARET_RIGHT " " ) ) + { + m_findZone.selCs = std::min( m_findZone.selCs + 1, gsz - 1 ); + } + if( ImGui::BeginPopup( "FindZoneCallstackPopup" ) ) + { + int sel = m_findZone.selCs + 1; + ImGui::SetNextItemWidth( 120 * scale ); + const bool clicked = ImGui::InputInt( "##findZoneCallstack", &sel, 1, 100, ImGuiInputTextFlags_EnterReturnsTrue ); + if( clicked ) m_findZone.selCs = std::min( std::max( sel, 1 ), int( gsz ) ) - 1; + ImGui::EndPopup(); + } + + ImGui::SameLine(); + TextFocused( "Count:", RealToString( group->second.zones.size() ) ); + ImGui::SameLine(); + TextFocused( "Time:", TimeToString( group->second.time ) ); + ImGui::SameLine(); + char buf[64]; + PrintStringPercent( buf, group->second.time * 100.f / zoneData.total ); + TextDisabledUnformatted( buf ); + + if( group->first != 0 ) + { + ImGui::SameLine(); + int idx = 0; + SmallCallstackButton( " " ICON_FA_ALIGN_JUSTIFY " ", group->first, idx, 0, false ); + + int fidx = 0; + ImGui::Spacing(); + ImGui::Indent(); + auto& csdata = m_worker.GetCallstack( group->first ); + for( auto& entry : csdata ) + { + auto frameData = m_worker.GetCallstackFrame( entry ); + if( !frameData ) + { + ImGui::TextDisabled( "%i.", fidx++ ); + ImGui::SameLine(); + ImGui::Text( "%p", (void*)m_worker.GetCanonicalPointer( entry ) ); + } + else + { + const auto fsz = frameData->size; + for( uint8_t f=0; fdata[f]; + auto txt = m_worker.GetString( frame.name ); + + if( fidx == 0 && f != fsz-1 ) + { + auto test = s_tracyStackFrames; + bool match = false; + do + { + if( strcmp( txt, *test ) == 0 ) + { + match = true; + break; + } + } + while( *++test ); + if( match ) continue; + } + if( f == fsz-1 ) + { + ImGui::TextDisabled( "%i.", fidx++ ); + } + else + { + TextDisabledUnformatted( ICON_FA_CARET_RIGHT ); + } + ImGui::SameLine(); + if( m_vd.shortenName == ShortenName::Never ) + { + ImGui::TextUnformatted( txt ); + } + else + { + const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, txt ); + ImGui::TextUnformatted( normalized ); + TooltipNormalizedName( txt, normalized ); + } + } + } + } + ImGui::Unindent(); + } + else + { + ImGui::Text( "No call stack" ); + } + + ImGui::Spacing(); + if( ImGui::TreeNodeEx( "Zone list" ) ) + { + DrawZoneList( group->second.id, group->second.zones ); + } + } + } + else + { + TextFocused( "Number of groups:", RealToString( groups.size() ) ); + for( auto& v : groups ) + { + bool isFiber = false; + const char* hdrString; + switch( groupBy ) + { + case FindZone::GroupBy::Thread: + { + const auto tid = m_worker.DecompressThread( v->first ); + const auto threadColor = GetThreadColor( tid, 0 ); + SmallColorBox( threadColor ); + ImGui::SameLine(); + hdrString = m_worker.GetThreadName( tid ); + isFiber = m_worker.IsThreadFiber( tid ); + break; + } + case FindZone::GroupBy::UserText: + hdrString = v->first == std::numeric_limits::max() ? "No user text" : m_worker.GetString( StringIdx( v->first ) ); + break; + case FindZone::GroupBy::ZoneName: + if( v->first == std::numeric_limits::max() ) + { + auto& srcloc = m_worker.GetSourceLocation( m_findZone.match[m_findZone.selMatch] ); + hdrString = m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ); + } + else + { + hdrString = m_worker.GetString( StringIdx( v->first ) ); + } + break; + case FindZone::GroupBy::Callstack: + if( v->first == 0 ) + { + hdrString = "No callstack"; + } + else + { + auto& callstack = m_worker.GetCallstack( v->first ); + auto& frameData = *m_worker.GetCallstackFrame( *callstack.begin() ); + hdrString = m_worker.GetString( frameData.data[frameData.size-1].name ); + } + break; + case FindZone::GroupBy::Parent: + if( v->first == 0 ) + { + hdrString = ""; + SmallColorBox( 0 ); + } + else + { + auto& srcloc = m_worker.GetSourceLocation( int16_t( v->first ) ); + hdrString = m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ); + SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); + } + ImGui::SameLine(); + break; + case FindZone::GroupBy::NoGrouping: + hdrString = "Zone list"; + break; + default: + hdrString = nullptr; + assert( false ); + break; + } + ImGui::PushID( v->first ); + const bool expand = ImGui::TreeNodeEx( hdrString, ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ( v->first == m_findZone.selGroup ? ImGuiTreeNodeFlags_Selected : 0 ) ); + if( ImGui::IsItemClicked() ) + { + m_findZone.selGroup = v->first; + m_findZone.ResetSelection(); + } + if( m_findZone.groupBy == FindZone::GroupBy::Parent && ImGui::IsItemClicked( 2 ) ) + { + changeZone = int16_t( v->first ); + } + ImGui::PopID(); + if( isFiber ) + { + ImGui::SameLine(); + TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" ); + } + ImGui::SameLine(); + ImGui::TextColored( ImVec4( 0.5f, 0.5f, 0.5f, 1.0f ), "(%s) %s", RealToString( v->second.zones.size() ), TimeToString( v->second.time ) ); + if( expand ) + { + DrawZoneList( v->second.id, v->second.zones ); + } + } + } + + if( m_findZone.samples.enabled && m_findZone.samples.scheduleUpdate && !m_findZone.scheduleResetMatch && m_worker.AreSymbolSamplesReady() ) + { + m_findZone.samples.scheduleUpdate = false; + + const auto& symMap = m_worker.GetSymbolMap(); + m_findZone.samples.counts.clear(); + m_findZone.samples.counts.reserve( symMap.size() ); + + struct GroupRange { + const FindZone::Group* group; + Vector>::const_iterator begin; + Vector>::const_iterator end; + }; + Vector selectedGroups; + selectedGroups.reserve( m_findZone.groups.size() ); + for( auto it = m_findZone.groups.begin(); it != m_findZone.groups.end(); ++it ) + { + assert( it->second.zones.size() == it->second.zonesTids.size() ); + if( ( m_findZone.selGroup == m_findZone.Unselected || it->first == m_findZone.selGroup ) + && !it->second.zones.empty() ) + { + selectedGroups.push_back_no_space_check( GroupRange{&it->second} ); + } + } + + for( auto& v : symMap ) + { + bool pass = ( m_statShowKernel || ( v.first >> 63 ) == 0 ); + if( !pass && v.second.size.Val() == 0 ) + { + const auto parentAddr = m_worker.GetSymbolForAddress( v.first ); + if( parentAddr != 0 ) + { + auto pit = symMap.find( parentAddr ); + if( pit != symMap.end() ) + { + pass = ( m_statShowKernel || ( parentAddr >> 63 ) == 0 ); + } + } + } + if( !pass ) continue; + + auto samples = m_worker.GetSamplesForSymbol( v.first ); + if( !samples ) continue; + + auto samplesBegin = samples->begin(); + auto samplesEnd = samples->end(); + if( m_findZone.range.active ) + { + const auto rangeMin = m_findZone.range.min; + const auto rangeMax = m_findZone.range.max; + samplesBegin = std::lower_bound( samplesBegin, samplesEnd, rangeMin, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } ); + samplesEnd = std::lower_bound( samplesBegin, samplesEnd, rangeMax, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } ); + } + if( samplesBegin == samplesEnd ) continue; + + bool empty = true; + const auto firstTime = samplesBegin->time.Val(); + const auto lastTime = samplesEnd == samples->end() ? m_worker.GetLastTime() : samplesEnd->time.Val(); + for( auto& g: selectedGroups ) + { + const auto& zones = g.group->zones; + auto begin = std::lower_bound( zones.begin(), zones.end(), firstTime, [] ( const auto& l, const auto& r ) { return l->Start() < r; } ); + auto end = std::upper_bound( begin, zones.end(), lastTime, [] ( const auto& l, const auto& r ) { return l <= r->Start(); } ); + g.begin = begin; + g.end = end; + empty = empty && (begin == end); + } + if (empty) continue; + + uint32_t count = 0; + for( auto it = samplesBegin; it != samplesEnd; ++it ) + { + const auto time = it->time.Val(); + bool pass = false; + for( auto& g: selectedGroups ) + { + while( g.begin != g.end && time > (*g.begin)->End() ) ++g.begin; + if( g.begin == g.end ) continue; + if( time < (*g.begin)->Start() ) continue; + + const auto& tids = g.group->zonesTids; + const auto firstZone = g.group->zones.begin(); + for (auto z = g.begin; z != g.end && (*z)->Start() <= time; ++z) + { + auto zoneIndex = z - firstZone; + if( (*z)->End() > time && it->thread == tids[zoneIndex] ) + { + pass = true; + break; + } + } + } + if( pass ) count ++; + } + if( count > 0 ) m_findZone.samples.counts.push_back_no_space_check( SymList { v.first, 0, count } ); + } + } + + ImGui::Separator(); + const bool hasSamples = m_worker.AreCallstackSamplesReady() && m_worker.GetCallstackSampleCount() > 0; + if( hasSamples && ImGui::TreeNodeEx( ICON_FA_EYE_DROPPER " Samples", ImGuiTreeNodeFlags_None ) ) + { + { + ImGui::Checkbox( ICON_FA_STOPWATCH " Show time", &m_statSampleTime ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + ImGui::Checkbox( ICON_FA_EYE_SLASH " Hide unknown", &m_statHideUnknown ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + ImGui::Checkbox( ICON_FA_SITEMAP " Inlines", &m_statSeparateInlines ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + ImGui::Checkbox( ICON_FA_AT " Address", &m_statShowAddress ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + if( ImGui::Checkbox( ICON_FA_HAT_WIZARD " Kernel", &m_statShowKernel )) + { + m_findZone.samples.scheduleUpdate = true; + } + m_findZoneConstraint.MarkMinWidth(); + } + + if( !m_findZone.samples.enabled ) + { + m_findZone.samples.enabled = true; + m_findZone.samples.scheduleUpdate = true; + m_findZone.scheduleResetMatch = true; + } + + Vector data; + data.reserve( m_findZone.samples.counts.size() ); + uint64_t totalSamples = 0; + for( auto it: m_findZone.samples.counts ) + { + data.push_back_no_space_check( it ); + totalSamples += it.excl; + } + int64_t timeRange = ( m_findZone.selGroup != m_findZone.Unselected ) ? m_findZone.selTotal : m_findZone.total; + DrawSamplesStatistics( data, timeRange, totalSamples, AccumulationMode::SelfOnly ); + + ImGui::TreePop(); + } + else + { + if( m_findZone.samples.enabled ) + { + m_findZone.samples.enabled = false; + m_findZone.samples.scheduleUpdate = false; + m_findZone.samples.counts = Vector(); + for( auto& it: m_findZone.groups ) it.second.zonesTids.clear(); + } + } + + if( changeZone != 0 ) + { + auto& srcloc = m_worker.GetSourceLocation( changeZone ); + m_findZone.ShowZone( changeZone, m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) ); + } +} + } From e75106a16f6e1ebc1cc71eeba2ed9b19e92c2566 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 9 Sep 2026 15:17:46 -0700 Subject: [PATCH 5/6] removing [GPU] tag from source location listing (it does not work as I expected) --- profiler/src/profiler/TracyView_FindZone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiler/src/profiler/TracyView_FindZone.cpp b/profiler/src/profiler/TracyView_FindZone.cpp index 497bd571..16e02b0e 100644 --- a/profiler/src/profiler/TracyView_FindZone.cpp +++ b/profiler/src/profiler/TracyView_FindZone.cpp @@ -418,7 +418,7 @@ void View::DrawFindZone() ImGui::SameLine(); } const auto fileName = m_worker.GetString( srcloc.file ); - ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s%s", RealToString( zoneCnt ), isGpuLoc ? "[GPU] " : "", LocationToString( fileName, srcloc.line ) ); + ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s", RealToString( zoneCnt ), LocationToString( fileName, srcloc.line ) ); if( ImGui::IsItemHovered() ) { DrawSourceTooltip( fileName, srcloc.line, srcloc.line ); From 190809c990be254c171f93a37fa33cdfcccfeb87 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Thu, 10 Sep 2026 09:59:46 -0700 Subject: [PATCH 6/6] update docs --- NEWS | 5 +++++ manual/tracy.tex | 2 ++ server/TracyWorker.cpp | 1 + 3 files changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 85b33f65..206f04a3 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ Note: There is no guarantee that version mismatched client and server will be able to talk with each other. Network protocol breakages won't be listed here. +vx.xx.x (2026-xx-xx) +-------------------- + +- Find zone window now works with GPU zones (histogram, statistics, self time). + v0.14.1 (2026-08-22) -------------------- diff --git a/manual/tracy.tex b/manual/tracy.tex index ef683ef7..8f9de006 100644 --- a/manual/tracy.tex +++ b/manual/tracy.tex @@ -4474,6 +4474,8 @@ Tracy gives you the ability to display an execution time histogram of all occurr You start by entering a search query, which will be matched against known zone names (see section~\ref{markingzones} for information on the grouping of zone names). If the search found some results, you will be presented with a list of zones in the \emph{matched source locations} drop-down. The selected zone's graph is displayed on the \emph{histogram} drop-down, and also the matching zones are highlighted on the timeline view. +GPU zones are supported as well, using their GPU execution time. Zone grouping, the found-zones list, and call stack samples are available only for CPU zones. + Clicking the \RMB{}~right mouse button on the source file location will open the source file view window (if applicable, see section~\ref{sourceview}). If symbol data is available Tracy will try to match the instrumented zone name to a captured symbol. If this succeeds and there are no duplicate matches, the source file view will be accompanied by the disassembly of the code. Since this matching is not exact, in rare cases you may get the wrong data here. To just display the source code, press and hold the \keys{\ctrl} key while clicking the \RMB{}~right mouse button. An example histogram is presented in figure~\ref{findzonehistogram}. Here you can see that the majority of zone calls (by count) are clustered in the 300~\si{\nano\second} group, closely followed by the 10~\si{\micro\second} cluster. There are some outliers at the 1~and~10~\si{\milli\second} marks, which can be ignored on most occasions, as these are single occurrences. diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 1c84799a..de56a8e4 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -3600,6 +3600,7 @@ ThreadData* Worker::GetCurrentThreadData() int64_t Worker::GetGpuChildTime( const GpuEvent& zone ) const { + // Unresolved children (GpuEnd == -1, e.g. aborted trace) clamp to 0, overstating parent self-time. int64_t time = 0; if( zone.Child() >= 0 ) {