2
0
mirror of https://github.com/wolfpld/tracy synced 2026-01-19 04:52:09 +00:00

Add limit range for flame graph.

Allow restricting the flame graph to a specific time range.
Similar to the existing range limits for other tools, such as for
statistics.
This commit is contained in:
rmarker
2025-06-15 17:41:27 +09:30
parent b69813f077
commit e1b325741e
7 changed files with 134 additions and 9 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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<int16_t, ZoneTimeData>& 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()
{

View File

@@ -27,7 +27,17 @@ void View::BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& 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<FlameGraphItem>& 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<FlameGraphItem>& 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<FlameGraphItem>& 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<FlameGraphItem>& 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++;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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; i<cnt-2; i++ )
{
running += it->End() - 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;