diff --git a/manual/tracy.tex b/manual/tracy.tex index f711d289..41d30266 100644 --- a/manual/tracy.tex +++ b/manual/tracy.tex @@ -3658,6 +3658,7 @@ To define a time range, drag the \LMB{}~left mouse button over the timeline view \begin{itemize} \item \emph{\faSearch{}~Limit find zone time range} -- this will limit find zone results. See chapter~\ref{findzone} for more details. \item \emph{\faSortAmountUp{}~Limit statistics time range} -- selecting this option will limit statistics results. See chapter~\ref{statistics} for more details. +\item \emph{\faFire{}~Limit flame graph time range} -- limits flame graph results. Refer to chapter~\ref{flamegraph}. \item \emph{\faHourglassHalf{}~Limit wait stacks time range} -- limits wait stacks results. Refer to chapter~\ref{waitstackswindow}. \item \emph{\faMemory{}~Limit memory time range} -- limits memory results. Read more about this in chapter~\ref{memorywindow}. \item \emph{\faStickyNote{}~Add annotation} -- use to annotate regions of interest, as described in chapter~\ref{annotatingtrace}. @@ -4109,6 +4110,8 @@ Similar to the statistics window (section~\ref{statistics}), the flame graph can In the sampling mode you can exclude \emph{external frames} from the graph, which typically would be internal implementation details of starting threads, handling smart pointers, and other such things that are quick to execute and not really interesting. This leaves only the frames from your code. One exception is \emph{external tails}, or calls that your code makes that do not eventually land in your application down the call chain. Think of functions that write to a file or send data on the network. These can be time-consuming, and you may want to see them. There is a separate option to disable these. +The flame graph can be restricted to a specific time extent using the \emph{Limit range} option (chapter~\ref{timeranges}). You can access more options through the \emph{\faRuler{}~Limits} button, which will open the time range limits window, described in section~\ref{timerangelimits}. + \subsection{Memory window} \label{memorywindow} @@ -4520,7 +4523,7 @@ A new view-sized annotation can be added in this window by pressing the \emph{\f \subsection{Time range limits} \label{timerangelimits} -This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), memory (section~\ref{memorywindow}) and wait stacks (section~\ref{waitstackswindow}) results. Each limit can be enabled or disabled and adjusted through the following options: +This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), memory (section~\ref{memorywindow}) and wait stacks (section~\ref{waitstackswindow}) results. Each limit can be enabled or disabled and adjusted through the following options: \begin{itemize} \item \emph{Limit to view} -- Set the time range limit to current view. diff --git a/profiler/src/profiler/TracyView.cpp b/profiler/src/profiler/TracyView.cpp index b2d6b67f..ca063185 100644 --- a/profiler/src/profiler/TracyView.cpp +++ b/profiler/src/profiler/TracyView.cpp @@ -1173,6 +1173,12 @@ bool View::DrawImpl() m_statRange.min = s; m_statRange.max = e; } + if( ImGui::Selectable( ICON_FA_FIRE_FLAME_CURVED " Limit flame time range" ) ) + { + m_flameRange.active = true; + m_flameRange.min = s; + m_flameRange.max = e; + } if( ImGui::Selectable( ICON_FA_HOURGLASS_HALF " Limit wait stacks range" ) ) { m_waitStackRange.active = true; diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index e402a91e..95cc31ac 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -167,6 +167,7 @@ public: bool m_showRanges = false; Range m_statRange; + Range m_flameRange; Range m_waitStackRange; private: @@ -377,6 +378,7 @@ private: int64_t GetZoneSelfTime( const ZoneEvent& zone ); int64_t GetZoneSelfTime( const GpuEvent& zone ); bool GetZoneRunningTime( const ContextSwitch* ctx, const ZoneEvent& ev, int64_t& time, uint64_t& cnt ); + bool GetZoneRunningTime( const ContextSwitch* ctx, const ZoneEvent& ev, const RangeSlim& range, int64_t& time, uint64_t& cnt ); const char* GetThreadContextData( uint64_t thread, bool& local, bool& untracked, const char*& program ); tracy_force_inline void CalcZoneTimeData( unordered_flat_map& data, int64_t& ztime, const ZoneEvent& zone ); @@ -926,6 +928,7 @@ private: { uint64_t count = 0; uint64_t lastTime = 0; + RangeSlim range = {false, 0, 0}; void Reset() { diff --git a/profiler/src/profiler/TracyView_FlameGraph.cpp b/profiler/src/profiler/TracyView_FlameGraph.cpp index edad5b52..af9b9c06 100644 --- a/profiler/src/profiler/TracyView_FlameGraph.cpp +++ b/profiler/src/profiler/TracyView_FlameGraph.cpp @@ -27,7 +27,17 @@ void View::BuildFlameGraph( const Worker& worker, std::vector& d { if( !v.IsEndValid() ) break; const auto srcloc = v.SrcLoc(); - const auto duration = v.End() - v.Start(); + + auto start = v.Start(); + auto end = v.End(); + + if ( m_flameGraphInvariant.range.active ) + { + start = std::clamp(start, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max); + end = std::clamp(end, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max); + } + + const auto duration = end - start; if( srcloc == last ) { cache->time += duration; @@ -70,7 +80,17 @@ void View::BuildFlameGraph( const Worker& worker, std::vector& d { if( !v->IsEndValid() ) break; const auto srcloc = v->SrcLoc(); - const auto duration = v->End() - v->Start(); + + auto start = v->Start(); + auto end = v->End(); + + if ( m_flameGraphInvariant.range.active ) + { + start = std::clamp(start, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max); + end = std::clamp(end, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max); + } + + const auto duration = end - start; if( srcloc == last ) { cache->time += duration; @@ -124,7 +144,15 @@ void View::BuildFlameGraph( const Worker& worker, std::vector& d const auto srcloc = v.SrcLoc(); int64_t duration; uint64_t cnt; - if( !GetZoneRunningTime( ctx, v, duration, cnt ) ) break; + if ( m_flameRange.active ) + { + if( !GetZoneRunningTime( ctx, v, m_flameGraphInvariant.range, duration, cnt ) ) continue; + } + else + { + if( !GetZoneRunningTime( ctx, v, duration, cnt ) ) break; + } + if( srcloc == last ) { cache->time += duration; @@ -169,7 +197,15 @@ void View::BuildFlameGraph( const Worker& worker, std::vector& d const auto srcloc = v->SrcLoc(); int64_t duration; uint64_t cnt; - if( !GetZoneRunningTime( ctx, *v, duration, cnt ) ) break; + if ( m_flameRange.active ) + { + if( !GetZoneRunningTime( ctx, *v, m_flameGraphInvariant.range, duration, cnt ) ) continue; + } + else + { + if( !GetZoneRunningTime( ctx, *v, duration, cnt ) ) break; + } + if( srcloc == last ) { cache->time += duration; @@ -221,6 +257,15 @@ void View::BuildFlameGraph( const Worker& worker, std::vector& d for( auto& v : samples ) { + if ( m_flameGraphInvariant.range.active ) + { + if ( v.time.Val() < m_flameGraphInvariant.range.min || + v.time.Val() > m_flameGraphInvariant.range.max ) + { + continue; + } + } + cache.clear(); const auto cs = v.callstack.Val(); @@ -730,6 +775,24 @@ void View::DrawFlameGraph() if( m_flameExternal ) ImGui::EndDisabled(); } + if( ImGui::Checkbox( "Limit range", &m_flameRange.active ) ) + { + if( m_flameRange.active && m_flameRange.min == 0 && m_flameRange.max == 0 ) + { + m_flameRange.min = m_vd.zvStart; + m_flameRange.max = m_vd.zvEnd; + } + + m_flameGraphInvariant.Reset(); + } + if( m_flameRange.active ) + { + ImGui::SameLine(); + TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION ); + ImGui::SameLine(); + ToggleButton( ICON_FA_RULER " Limits", m_showRanges ); + } + auto& td = m_worker.GetThreadData(); auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" ); ImGui::SameLine(); @@ -791,8 +854,11 @@ void View::DrawFlameGraph() ImGui::PopStyleVar(); if( m_flameMode == 0 && ( m_flameGraphInvariant.count != m_worker.GetZoneCount() || m_flameGraphInvariant.lastTime != m_worker.GetLastTime() ) || - m_flameMode == 1 && ( m_flameGraphInvariant.count != m_worker.GetCallstackSampleCount() ) ) + m_flameMode == 1 && ( m_flameGraphInvariant.count != m_worker.GetCallstackSampleCount() ) || + m_flameGraphInvariant.range != m_flameRange ) { + m_flameGraphInvariant.range = m_flameRange; + size_t sz = 0; for( auto& thread : td ) if( FlameGraphThread( thread->id ) ) sz++; diff --git a/profiler/src/profiler/TracyView_Ranges.cpp b/profiler/src/profiler/TracyView_Ranges.cpp index e366824c..3fadcce4 100644 --- a/profiler/src/profiler/TracyView_Ranges.cpp +++ b/profiler/src/profiler/TracyView_Ranges.cpp @@ -14,9 +14,11 @@ void View::DrawRanges() ImGui::Separator(); DrawRangeEntry( m_statRange, ICON_FA_ARROW_UP_WIDE_SHORT " Statistics", 0x448888EE, "RangeStatisticsCopyFrom", 1 ); ImGui::Separator(); - DrawRangeEntry( m_waitStackRange, ICON_FA_HOURGLASS_HALF " Wait stacks", 0x44EEB588, "RangeWaitStackCopyFrom", 2 ); + DrawRangeEntry( m_flameRange, ICON_FA_FIRE_FLAME_CURVED " Flame", 0x4488B5EE, "RangeFlameCopyFrom", 2 ); ImGui::Separator(); - DrawRangeEntry( m_memInfo.range, ICON_FA_MEMORY " Memory", 0x4488EEE3, "RangeMemoryCopyFrom", 3 ); + DrawRangeEntry( m_waitStackRange, ICON_FA_HOURGLASS_HALF " Wait stacks", 0x44EEB588, "RangeWaitStackCopyFrom", 3 ); + ImGui::Separator(); + DrawRangeEntry( m_memInfo.range, ICON_FA_MEMORY " Memory", 0x4488EEE3, "RangeMemoryCopyFrom", 4 ); ImGui::End(); } @@ -79,9 +81,14 @@ void View::DrawRangeEntry( Range& range, const char* label, uint32_t color, cons if( id != 2 ) { ImGui::SameLine(); - if( SmallButtonDisablable( ICON_FA_HOURGLASS_HALF " Copy from wait stacks", m_waitStackRange.min == 0 && m_waitStackRange.max == 0 ) ) range = m_waitStackRange; + if( SmallButtonDisablable( ICON_FA_FIRE_FLAME_CURVED " Copy from flame", m_flameRange.min == 0 && m_flameRange.max == 0 ) ) range = m_flameRange; } if( id != 3 ) + { + ImGui::SameLine(); + if( SmallButtonDisablable( ICON_FA_HOURGLASS_HALF " Copy from wait stacks", m_waitStackRange.min == 0 && m_waitStackRange.max == 0 ) ) range = m_waitStackRange; + } + if( id != 4 ) { ImGui::SameLine(); if( SmallButtonDisablable( ICON_FA_MEMORY " Copy from memory", m_memInfo.range.min == 0 && m_memInfo.range.max == 0 ) ) range = m_memInfo.range; diff --git a/profiler/src/profiler/TracyView_Timeline.cpp b/profiler/src/profiler/TracyView_Timeline.cpp index 5200fd2c..a8c8b9e9 100644 --- a/profiler/src/profiler/TracyView_Timeline.cpp +++ b/profiler/src/profiler/TracyView_Timeline.cpp @@ -254,6 +254,7 @@ void View::DrawTimeline() m_zoneHover2.Decay( nullptr ); m_findZone.range.StartFrame(); m_statRange.StartFrame(); + m_flameRange.StartFrame(); m_waitStackRange.StartFrame(); m_memInfo.range.StartFrame(); m_yDelta = 0; @@ -283,6 +284,7 @@ void View::DrawTimeline() { HandleRange( m_findZone.range, timespan, ImGui::GetCursorScreenPos(), w ); HandleRange( m_statRange, timespan, ImGui::GetCursorScreenPos(), w ); + HandleRange( m_flameRange, timespan, ImGui::GetCursorScreenPos(), w ); HandleRange( m_waitStackRange, timespan, ImGui::GetCursorScreenPos(), w ); HandleRange( m_memInfo.range, timespan, ImGui::GetCursorScreenPos(), w ); for( auto& v : m_annotations ) @@ -495,6 +497,15 @@ void View::DrawTimeline() DrawLine( draw, ImVec2( dpos.x + px1, linepos.y + 0.5f ), ImVec2( dpos.x + px1, linepos.y + lineh + 0.5f ), m_statRange.hiMax ? 0x998888EE : 0x338888EE, m_statRange.hiMax ? 2 : 1 ); } + if( m_flameRange.active && ( m_showFlameGraph || m_showRanges ) ) + { + const auto px0 = ( m_flameRange.min - m_vd.zvStart ) * pxns; + const auto px1 = std::max( px0 + std::max( 1.0, pxns * 0.5 ), ( m_flameRange.max - m_vd.zvStart ) * pxns ); + DrawStripedRect( draw, wpos, px0, linepos.y, px1, linepos.y + lineh, 10 * scale, 0x2288B5EE, true, false ); + DrawLine( draw, ImVec2( dpos.x + px0, linepos.y + 0.5f ), ImVec2( dpos.x + px0, linepos.y + lineh + 0.5f ), m_flameRange.hiMin ? 0x9988B5EE : 0x3388B5EE, m_flameRange.hiMin ? 2 : 1 ); + DrawLine( draw, ImVec2( dpos.x + px1, linepos.y + 0.5f ), ImVec2( dpos.x + px1, linepos.y + lineh + 0.5f ), m_flameRange.hiMax ? 0x9988B5EE : 0x3388B5EE, m_flameRange.hiMax ? 2 : 1 ); + } + if( m_waitStackRange.active && ( m_showWaitStacks || m_showRanges ) ) { const auto px0 = ( m_waitStackRange.min - m_vd.zvStart ) * pxns; diff --git a/profiler/src/profiler/TracyView_Utility.cpp b/profiler/src/profiler/TracyView_Utility.cpp index 93428ff7..cc08264a 100644 --- a/profiler/src/profiler/TracyView_Utility.cpp +++ b/profiler/src/profiler/TracyView_Utility.cpp @@ -737,6 +737,35 @@ bool View::GetZoneRunningTime( const ContextSwitch* ctx, const ZoneEvent& ev, in return true; } +bool View::GetZoneRunningTime( const ContextSwitch* ctx, const ZoneEvent& ev, const RangeSlim& range, int64_t& time, uint64_t& cnt ) +{ + const auto start = std::max( ev.Start(), range.min ); + auto it = std::lower_bound( ctx->v.begin(), ctx->v.end(), start, [] ( const auto& l, const auto& r ) { return (uint64_t)l.End() < (uint64_t)r; } ); + if( it == ctx->v.end() ) return false; + const auto end = std::min( m_worker.GetZoneEnd( ev ), range.max ); + const auto eit = std::upper_bound( it, ctx->v.end(), end, [] ( const auto& l, const auto& r ) { return l < r.Start(); } ); + if( eit == ctx->v.end() ) return false; + cnt = std::distance( it, eit ); + if( cnt == 0 ) return false; + if( cnt == 1 ) + { + time = end - start; + } + else + { + int64_t running = it->End() - start; + ++it; + for( uint64_t i=0; iEnd() - it->Start(); + ++it; + } + running += end - it->Start(); + time = running; + } + return true; +} + const char* View::SourceSubstitution( const char* srcFile ) const { if( !m_sourceRegexValid || m_sourceSubstitutions.empty() ) return srcFile;