mirror of
https://github.com/wolfpld/tracy.git
synced 2025-03-20 07:40:02 +08:00
487 lines
15 KiB
C++
487 lines
15 KiB
C++
#include "TracyColor.hpp"
|
|
#include "TracyEvent.hpp"
|
|
#include "TracyImGui.hpp"
|
|
#include "TracyPrint.hpp"
|
|
#include "TracyVector.hpp"
|
|
#include "TracyView.hpp"
|
|
#include "tracy_pdqsort.h"
|
|
|
|
namespace tracy
|
|
{
|
|
|
|
struct FlameGraphItem
|
|
{
|
|
int64_t srcloc;
|
|
int64_t time;
|
|
Vector<FlameGraphItem> children;
|
|
};
|
|
|
|
static void BuildFlameGraph( const Worker& worker, Vector<FlameGraphItem>& data, const Vector<short_ptr<ZoneEvent>>& zones )
|
|
{
|
|
FlameGraphItem* it;
|
|
int16_t last = 0;
|
|
|
|
if( zones.is_magic() )
|
|
{
|
|
auto& vec = *(Vector<ZoneEvent>*)&zones;
|
|
for( auto& v : vec )
|
|
{
|
|
if( !v.IsEndValid() ) break;
|
|
const auto srcloc = v.SrcLoc();
|
|
const auto duration = v.End() - v.Start();
|
|
if( srcloc == last )
|
|
{
|
|
it->time += duration;
|
|
if( v.HasChildren() )
|
|
{
|
|
auto& children = worker.GetZoneChildren( v.Child() );
|
|
BuildFlameGraph( worker, it->children, children );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
it = std::find_if( data.begin(), data.end(), [srcloc]( const auto& v ) { return v.srcloc == srcloc; } );
|
|
if( it == data.end() )
|
|
{
|
|
data.push_back( FlameGraphItem { srcloc, duration } );
|
|
if( v.HasChildren() )
|
|
{
|
|
auto& children = worker.GetZoneChildren( v.Child() );
|
|
BuildFlameGraph( worker, data.back().children, children );
|
|
}
|
|
it = &data.back();
|
|
}
|
|
else
|
|
{
|
|
it->time += duration;
|
|
if( v.HasChildren() )
|
|
{
|
|
auto& children = worker.GetZoneChildren( v.Child() );
|
|
BuildFlameGraph( worker, it->children, children );
|
|
}
|
|
}
|
|
last = srcloc;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( auto& v : zones )
|
|
{
|
|
if( !v->IsEndValid() ) break;
|
|
const auto srcloc = v->SrcLoc();
|
|
const auto duration = v->End() - v->Start();
|
|
if( srcloc == last )
|
|
{
|
|
it->time += duration;
|
|
if( v->HasChildren() )
|
|
{
|
|
auto& children = worker.GetZoneChildren( v->Child() );
|
|
BuildFlameGraph( worker, it->children, children );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
it = std::find_if( data.begin(), data.end(), [srcloc]( const auto& v ) { return v.srcloc == srcloc; } );
|
|
if( it == data.end() )
|
|
{
|
|
data.push_back( FlameGraphItem { srcloc, duration } );
|
|
if( v->HasChildren() )
|
|
{
|
|
auto& children = worker.GetZoneChildren( v->Child() );
|
|
BuildFlameGraph( worker, data.back().children, children );
|
|
}
|
|
it = &data.back();
|
|
}
|
|
else
|
|
{
|
|
it->time += duration;
|
|
if( v->HasChildren() )
|
|
{
|
|
auto& children = worker.GetZoneChildren( v->Child() );
|
|
BuildFlameGraph( worker, it->children, children );
|
|
}
|
|
}
|
|
last = srcloc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void BuildFlameGraph( const Worker& worker, Vector<FlameGraphItem>& data, const Vector<SampleData>& samples )
|
|
{
|
|
for( auto& v : samples )
|
|
{
|
|
const auto cs = v.callstack.Val();
|
|
const auto& callstack = worker.GetCallstack( cs );
|
|
|
|
auto vec = &data;
|
|
const auto csz = callstack.size();
|
|
for( size_t i=csz; i>0; i--)
|
|
{
|
|
auto frame = worker.GetCallstackFrame( callstack[i-1] );
|
|
if( frame )
|
|
{
|
|
for( uint8_t j=frame->size; j>0; j-- )
|
|
{
|
|
const auto ip = frame->data[j-1].symAddr;
|
|
const auto symaddr = worker.GetInlineSymbolForAddress( ip );
|
|
if( symaddr != 0 )
|
|
{
|
|
auto it = std::find_if( vec->begin(), vec->end(), [symaddr]( const auto& v ) { return v.srcloc == symaddr; } );
|
|
if( it == vec->end() )
|
|
{
|
|
vec->push_back( FlameGraphItem { (int64_t)symaddr, 1 } );
|
|
vec = &vec->back().children;
|
|
}
|
|
else
|
|
{
|
|
it->time++;
|
|
vec = &it->children;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SortFlameGraph( Vector<FlameGraphItem>& data )
|
|
{
|
|
pdqsort_branchless( data.begin(), data.end(), []( const FlameGraphItem& lhs, const FlameGraphItem& rhs ) { return lhs.time > rhs.time; } );
|
|
for( auto& v : data ) SortFlameGraph( v.children );
|
|
}
|
|
|
|
static void FreeVector( Vector<FlameGraphItem>& data )
|
|
{
|
|
for( auto& v : data )
|
|
{
|
|
FreeVector( v.children );
|
|
v.children.~Vector();
|
|
}
|
|
}
|
|
|
|
struct FlameGraphContext
|
|
{
|
|
ImDrawList* draw;
|
|
ImVec2 wpos;
|
|
ImVec2 dpos;
|
|
float ty;
|
|
float ostep;
|
|
double pxns;
|
|
double nxps;
|
|
};
|
|
|
|
void View::DrawFlameGraphItem( const FlameGraphItem& item, FlameGraphContext& ctx, uint64_t ts, int depth, bool samples )
|
|
{
|
|
const auto x0 = ctx.dpos.x + ts * ctx.pxns;
|
|
const auto x1 = x0 + item.time * ctx.pxns;
|
|
const auto y0 = ctx.dpos.y + depth * ctx.ostep;
|
|
const auto y1 = y0 + ctx.ty;
|
|
|
|
const SourceLocation* srcloc;
|
|
uint32_t color;
|
|
const char* name;
|
|
const char* slName;
|
|
|
|
if( !samples )
|
|
{
|
|
srcloc = &m_worker.GetSourceLocation( item.srcloc );
|
|
color = GetSrcLocColor( *srcloc, depth );
|
|
name = slName = m_worker.GetString( srcloc->name.active ? srcloc->name : srcloc->function );
|
|
}
|
|
else
|
|
{
|
|
auto sym = m_worker.GetSymbolData( (uint64_t)item.srcloc );
|
|
if( sym )
|
|
{
|
|
name = m_worker.GetString( sym->name );
|
|
auto namehash = charutil::hash( name );
|
|
if( namehash == 0 ) namehash++;
|
|
color = GetHsvColor( namehash, depth );
|
|
}
|
|
else
|
|
{
|
|
name = "???";
|
|
color = 0xFF888888;
|
|
}
|
|
}
|
|
|
|
const auto hiColor = HighlightColor( color );
|
|
const auto darkColor = DarkenColor( color );
|
|
|
|
const auto zsz = x1 - x0;
|
|
|
|
auto tsz = ImGui::CalcTextSize( name );
|
|
if( m_vd.shortenName == ShortenName::Always || ( ( m_vd.shortenName == ShortenName::NoSpace || m_vd.shortenName == ShortenName::NoSpaceAndNormalize ) && tsz.x > zsz ) )
|
|
{
|
|
name = ShortenZoneName( m_vd.shortenName, name, tsz, zsz );
|
|
}
|
|
|
|
const bool hover = ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect( ImVec2( x0, y0 ), ImVec2( x1, y1 ) );
|
|
|
|
ctx.draw->AddRectFilled( ImVec2( x0, y0 ), ImVec2( x1, y1 ), color );
|
|
if( hover )
|
|
{
|
|
ctx.draw->AddRect( ImVec2( x0 - 0.5f, y0 - 0.5f ), ImVec2( x1 - 0.5f, y1 - 0.5f ), 0xFFEEEEEE );
|
|
}
|
|
else
|
|
{
|
|
DrawLine( ctx.draw, ImVec2( x0, y1 ), ImVec2( x0, y0 ), ImVec2( x1-1, y0 ), hiColor );
|
|
DrawLine( ctx.draw, ImVec2( x0, y1 ), ImVec2( x1-1, y1), ImVec2( x1-1, y0 ), darkColor );
|
|
}
|
|
|
|
if( tsz.x < zsz )
|
|
{
|
|
const auto x = ( x1 + x0 - tsz.x ) * 0.5;
|
|
DrawTextContrast( ctx.draw, ImVec2( x, y0 ), 0xFFFFFFFF, name );
|
|
}
|
|
else
|
|
{
|
|
ImGui::PushClipRect( ImVec2( x0, y0 ), ImVec2( x1, y1 ), true );
|
|
DrawTextContrast( ctx.draw, ImVec2( x0, y0 ), 0xFFFFFFFF, name );
|
|
ImGui::PopClipRect();
|
|
}
|
|
|
|
if( hover && !samples )
|
|
{
|
|
uint64_t self = item.time;
|
|
for( auto& v : item.children ) self -= v.time;
|
|
|
|
ImGui::BeginTooltip();
|
|
if( srcloc->name.active )
|
|
{
|
|
ImGui::TextUnformatted( m_worker.GetString( srcloc->name ) );
|
|
}
|
|
ImGui::TextUnformatted( m_worker.GetString( srcloc->function ) );
|
|
ImGui::Separator();
|
|
SmallColorBox( GetSrcLocColor( *srcloc, 0 ) );
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc->file ), srcloc->line ) );
|
|
ImGui::Separator();
|
|
TextFocused( "Execution time:", TimeToString( item.time ) );
|
|
if( !item.children.empty() )
|
|
{
|
|
TextFocused( "Self time:", TimeToString( self ) );
|
|
char buf[64];
|
|
PrintStringPercent( buf, 100.f * self / item.time );
|
|
ImGui::SameLine();
|
|
TextDisabledUnformatted( buf );
|
|
}
|
|
ImGui::EndTooltip();
|
|
|
|
if( ImGui::IsMouseClicked( 0 ) )
|
|
{
|
|
m_findZone.ShowZone( item.srcloc, slName );
|
|
}
|
|
}
|
|
|
|
uint64_t cts = ts;
|
|
for( auto& v : item.children )
|
|
{
|
|
DrawFlameGraphItem( v, ctx, cts, depth+1, samples );
|
|
cts += v.time;
|
|
}
|
|
}
|
|
|
|
void View::DrawFlameGraphHeader( uint64_t timespan )
|
|
{
|
|
const auto wpos = ImGui::GetCursorScreenPos();
|
|
const auto dpos = wpos + ImVec2( 0.5f, 0.5f );
|
|
const auto w = ImGui::GetContentRegionAvail().x;// - ImGui::GetStyle().ScrollbarSize;
|
|
auto draw = ImGui::GetWindowDrawList();
|
|
const auto ty = ImGui::GetTextLineHeight();
|
|
const auto ty025 = round( ty * 0.25f );
|
|
const auto ty0375 = round( ty * 0.375f );
|
|
const auto ty05 = round( ty * 0.5f );
|
|
|
|
const auto pxns = w / double( timespan );
|
|
const auto nspx = 1.0 / pxns;
|
|
const auto scale = std::max( 0.0, round( log10( nspx ) + 2 ) );
|
|
const auto step = pow( 10, scale );
|
|
|
|
ImGui::InvisibleButton( "##flameHeader", ImVec2( w, ty * 1.5f ) );
|
|
TooltipIfHovered( TimeToStringExact( ( ImGui::GetIO().MousePos.x - wpos.x ) * nspx ) );
|
|
|
|
const auto dx = step * pxns;
|
|
double x = 0;
|
|
int tw = 0;
|
|
int tx = 0;
|
|
int64_t tt = 0;
|
|
while( x < w )
|
|
{
|
|
DrawLine( draw, dpos + ImVec2( x, 0 ), dpos + ImVec2( x, ty05 ), 0x66FFFFFF );
|
|
if( tw == 0 )
|
|
{
|
|
auto txt = "0";
|
|
draw->AddText( wpos + ImVec2( x, ty05 ), 0x66FFFFFF, txt );
|
|
tw = ImGui::CalcTextSize( txt ).x;
|
|
}
|
|
else if( x > tx + tw + ty * 2 )
|
|
{
|
|
tx = x;
|
|
auto txt = TimeToString( tt );
|
|
draw->AddText( wpos + ImVec2( x, ty05 ), 0x66FFFFFF, txt );
|
|
tw = ImGui::CalcTextSize( txt ).x;
|
|
}
|
|
|
|
if( scale != 0 )
|
|
{
|
|
for( int i=1; i<5; i++ )
|
|
{
|
|
DrawLine( draw, dpos + ImVec2( x + i * dx / 10, 0 ), dpos + ImVec2( x + i * dx / 10, ty025 ), 0x33FFFFFF );
|
|
}
|
|
DrawLine( draw, dpos + ImVec2( x + 5 * dx / 10, 0 ), dpos + ImVec2( x + 5 * dx / 10, ty0375 ), 0x33FFFFFF );
|
|
for( int i=6; i<10; i++ )
|
|
{
|
|
DrawLine( draw, dpos + ImVec2( x + i * dx / 10, 0 ), dpos + ImVec2( x + i * dx / 10, ty025 ), 0x33FFFFFF );
|
|
}
|
|
}
|
|
|
|
x += dx;
|
|
tt += step;
|
|
}
|
|
}
|
|
|
|
void View::DrawFlameGraph()
|
|
{
|
|
const auto scale = GetScale();
|
|
ImGui::SetNextWindowSize( ImVec2( 1400 * scale, 800 * scale ), ImGuiCond_FirstUseEver );
|
|
ImGui::Begin( "Flame graph", &m_showFlameGraph, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse );
|
|
if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
|
|
|
|
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 2, 2 ) );
|
|
ImGui::RadioButton( ICON_FA_SYRINGE " Instrumentation", &m_flameMode, 0 );
|
|
|
|
if( m_worker.AreCallstackSamplesReady() && m_worker.GetCallstackSampleCount() > 0 )
|
|
{
|
|
ImGui::SameLine();
|
|
ImGui::RadioButton( ICON_FA_EYE_DROPPER " Sampling", &m_flameMode, 1 );
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
|
|
ImGui::SameLine();
|
|
|
|
ImGui::Checkbox( ICON_FA_ARROW_UP_WIDE_SHORT " Sort by time", &m_flameSort );
|
|
|
|
auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" );
|
|
ImGui::SameLine();
|
|
size_t visibleThreads = 0;
|
|
size_t tsz = 0;
|
|
for( const auto& t : m_threadOrder )
|
|
{
|
|
if( FlameGraphThread( t->id ) ) visibleThreads++;
|
|
tsz++;
|
|
}
|
|
if( visibleThreads == tsz )
|
|
{
|
|
ImGui::TextDisabled( "(%zu)", tsz );
|
|
}
|
|
else
|
|
{
|
|
ImGui::TextDisabled( "(%zu/%zu)", visibleThreads, tsz );
|
|
}
|
|
if( expand )
|
|
{
|
|
ImGui::SameLine();
|
|
if( ImGui::SmallButton( "Select all" ) )
|
|
{
|
|
for( const auto& t : m_threadOrder )
|
|
{
|
|
FlameGraphThread( t->id ) = true;
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if( ImGui::SmallButton( "Unselect all" ) )
|
|
{
|
|
for( const auto& t : m_threadOrder )
|
|
{
|
|
FlameGraphThread( t->id ) = false;
|
|
}
|
|
}
|
|
|
|
int idx = 0;
|
|
for( const auto& t : m_threadOrder )
|
|
{
|
|
ImGui::PushID( idx++ );
|
|
const auto threadColor = GetThreadColor( t->id, 0 );
|
|
SmallColorBox( threadColor );
|
|
ImGui::SameLine();
|
|
SmallCheckbox( m_worker.GetThreadName( t->id ), &FlameGraphThread( t->id ) );
|
|
ImGui::PopID();
|
|
if( t->isFiber )
|
|
{
|
|
ImGui::SameLine();
|
|
TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" );
|
|
}
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGui::PopStyleVar();
|
|
|
|
Vector<FlameGraphItem> data;
|
|
|
|
if( m_flameMode == 0 )
|
|
{
|
|
for( auto& thread : m_worker.GetThreadData() )
|
|
{
|
|
if( FlameGraphThread( thread->id ) ) BuildFlameGraph( m_worker, data, thread->timeline );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( auto& thread : m_worker.GetThreadData() )
|
|
{
|
|
if( FlameGraphThread( thread->id ) ) BuildFlameGraph( m_worker, data, thread->samples );
|
|
}
|
|
}
|
|
|
|
if( m_flameSort ) SortFlameGraph( data );
|
|
|
|
int64_t zsz = 0;
|
|
for( auto& v : data ) zsz += v.time;
|
|
|
|
ImGui::BeginChild( "##flameGraph" );
|
|
const auto region = ImGui::GetContentRegionAvail();
|
|
|
|
if( data.empty() )
|
|
{
|
|
ImGui::PushFont( m_bigFont );
|
|
ImGui::Dummy( ImVec2( 0, ( region.y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
|
|
TextCentered( ICON_FA_CAT );
|
|
TextCentered( "No data available to display" );
|
|
ImGui::PopFont();
|
|
}
|
|
else
|
|
{
|
|
DrawFlameGraphHeader( m_flameMode == 0 ? zsz : zsz * m_worker.GetSamplingPeriod() );
|
|
|
|
FlameGraphContext ctx;
|
|
ctx.draw = ImGui::GetWindowDrawList();
|
|
ctx.wpos = ImGui::GetCursorScreenPos();
|
|
ctx.dpos = ctx.wpos + ImVec2( 0.5f, 0.5f );
|
|
ctx.ty = ImGui::GetTextLineHeight();
|
|
ctx.ostep = ctx.ty + 1;
|
|
ctx.pxns = region.x / zsz;
|
|
ctx.nxps = 1.0 / ctx.pxns;
|
|
|
|
ImGui::ItemSize( region );
|
|
uint64_t ts = 0;
|
|
for( auto& v : data )
|
|
{
|
|
DrawFlameGraphItem( v, ctx, ts, 0, m_flameMode == 1 );
|
|
ts += v.time;
|
|
}
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
|
|
ImGui::End();
|
|
FreeVector( data );
|
|
}
|
|
|
|
}
|