tracy/server/TracyView_Samples.cpp
Bartosz Taudul f428a5b52b
Fix popup of collapsed items near timeline start.
There are various changes involved into making this work:

1. Zone size (zsz) is no longer clamped to the timeline viewport area.
   This clamping has to be removed to prevent otherwise uncollapsed zones
   from apparently becoming small near the viewport borders. Such a small
   zone would then be collapsed, resulting in unwanted popping.
   Interesingly, only the CPU zones were clamped before. GPU zones were
   not.
2. Iteration over visible zones has to start before the visible timeline
   viewport area. Without this some zones that would be otherwise
   included in the collapsed area (started by a previous zone) may be
   fully visible. This causes child zones to be drawn and produces
   unwanted popping. (At this point threshold for continuing collapsed
   area is greater than threshold for starting it.)
3. Since the iteration now starts before timeline visible area, it may so
   happen that everything found will be in a small slice of timeline that
   is outside the screen. To fix this, the end time of last found item is
   checked against the viewport start time.
   It is always valid to access *(zitend-1), as it is in each case done
   after null set check (it == zitend).

Similar but simpler fix was also applied to per-thread call stack samples.
2023-03-09 00:38:23 +01:00

1019 lines
46 KiB
C++

#include <inttypes.h>
#include <math.h>
#include "TracyFilesystem.hpp"
#include "TracyImGui.hpp"
#include "TracyMouse.hpp"
#include "TracyPrint.hpp"
#include "TracySourceView.hpp"
#include "TracyView.hpp"
namespace tracy
{
void View::DrawSamples( const Vector<SampleData>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset )
{
const auto MinVis = 6 * GetScale();
auto it = std::lower_bound( vec.begin(), vec.end(), m_vd.zvStart - 2 * MinVis * nspx, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
if( it == vec.end() ) return;
const auto itend = std::lower_bound( it, vec.end(), m_vd.zvEnd, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
if( it == itend ) return;
const auto ty0375 = offset + round( ImGui::GetTextLineHeight() * 0.375f );
const auto ty02 = round( ImGui::GetTextLineHeight() * 0.2f );
const auto ty01 = round( ImGui::GetTextLineHeight() * 0.1f );
const auto y0 = ty0375 - ty02 - 3;
const auto y1 = ty0375 + ty02 - 1;
auto draw = ImGui::GetWindowDrawList();
bool tooltipDisplayed = false;
while( it < itend )
{
bool visible = true;
const auto px0 = ( it->time.Val() - m_vd.zvStart ) * pxns;
double px1;
auto next = it+1;
int num;
if( next != itend )
{
auto px1ns = next->time.Val() - m_vd.zvStart;
px1 = px1ns * pxns;
if( px1 - px0 < MinVis )
{
const auto MinVisNs = MinVis * nspx;
visible = false;
auto nextTime = px0 + MinVisNs;
for(;;)
{
const auto prev = next;
next = std::lower_bound( next, itend, nextTime, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
if( prev == next ) ++next;
if( next == itend ) break;
const auto nsnext = next->time.Val() - m_vd.zvStart;
if( nsnext - px1ns >= MinVisNs ) break;
px1ns = nsnext;
nextTime = next->time.Val() + nspx;
}
num = next - it;
px1 = px1ns * pxns;
}
}
if( visible )
{
draw->AddCircleFilled( wpos + ImVec2( px0, ty0375 ), ty02, 0xFFDD8888 );
if( !tooltipDisplayed && hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( px0 - ty02 - 2, y0 ), wpos + ImVec2( px0 + ty02 + 1, y1 ) ) )
{
tooltipDisplayed = true;
CallstackTooltip( it->callstack.Val() );
if( IsMouseClicked( 0 ) )
{
m_callstackInfoWindow = it->callstack.Val();
}
}
}
else
{
DrawZigZag( draw, wpos + ImVec2( 0, ty0375 ), px0, std::max( px1, px0+MinVis ), ty01, 0xFF997777 );
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( px0, y0 ), wpos + ImVec2( std::max( px1, px0+MinVis ), y1 ) ) )
{
ImGui::BeginTooltip();
ImGui::TextUnformatted( "Multiple call stack samples" );
TextFocused( "Number of samples:", RealToString( num ) );
ImGui::EndTooltip();
if( IsMouseClicked( 2 ) )
{
const auto prev = next-1;
ZoomToRange( it->time.Val(), prev->time.Val() + 1 );
}
}
}
it = next;
}
}
void View::DrawSamplesStatistics( Vector<SymList>& data, int64_t timeRange, AccumulationMode accumulationMode )
{
static unordered_flat_map<uint64_t, SymList> inlineMap;
assert( inlineMap.empty() );
if( !m_statSeparateInlines )
{
static unordered_flat_map<uint64_t, SymList> baseMap;
assert( baseMap.empty() );
for( auto& v : data )
{
auto sym = m_worker.GetSymbolData( v.symAddr );
const auto symAddr = ( sym && sym->isInline ) ? m_worker.GetSymbolForAddress( v.symAddr ) : v.symAddr;
auto it = baseMap.find( symAddr );
if( it == baseMap.end() )
{
baseMap.emplace( symAddr, SymList { symAddr, v.incl, v.excl, 0 } );
}
else
{
assert( symAddr == it->second.symAddr );
it->second.incl += v.incl;
it->second.excl += v.excl;
it->second.count++;
}
}
for( auto& v : data ) inlineMap.emplace( v.symAddr, SymList { v.symAddr, v.incl, v.excl, v.count } );
data.clear();
for( auto& v : baseMap )
{
data.push_back_no_space_check( v.second );
}
baseMap.clear();
}
if( data.empty() )
{
ImGui::TextUnformatted( "No entries to be displayed." );
}
else
{
const auto& symMap = m_worker.GetSymbolMap();
if( accumulationMode == AccumulationMode::SelfOnly )
{
pdqsort_branchless( data.begin(), data.end(), []( const auto& l, const auto& r ) { return l.excl != r.excl ? l.excl > r.excl : l.symAddr < r.symAddr; } );
}
else
{
pdqsort_branchless( data.begin(), data.end(), []( const auto& l, const auto& r ) { return l.incl != r.incl ? l.incl > r.incl : l.symAddr < r.symAddr; } );
}
ImGui::BeginChild( "##statisticsSampling" );
if( ImGui::BeginTable( "##statisticsSampling", 5, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY ) )
{
ImGui::TableSetupScrollFreeze( 0, 1 );
ImGui::TableSetupColumn( "Name", ImGuiTableColumnFlags_NoHide );
ImGui::TableSetupColumn( "Location", ImGuiTableColumnFlags_NoSort );
ImGui::TableSetupColumn( "Image" );
ImGui::TableSetupColumn( m_statSampleTime ? "Time" : "Count", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize );
ImGui::TableSetupColumn( "Code size", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize );
ImGui::TableHeadersRow();
double revSampleCount100;
if( m_statRange.active && m_worker.GetSamplingPeriod() != 0 )
{
const auto st = m_statRange.max - m_statRange.min;
const auto cnt = st / m_worker.GetSamplingPeriod();
revSampleCount100 = 100. / cnt;
}
else
{
revSampleCount100 = 100. / m_worker.GetCallstackSampleCount();
}
const bool showAll = m_showAllSymbols;
const auto period = m_worker.GetSamplingPeriod();
int idx = 0;
for( auto& v : data )
{
const auto cnt = accumulationMode == AccumulationMode::SelfOnly ? v.excl : v.incl;
if( cnt > 0 || showAll )
{
const char* name = "[unknown]";
const char* file = "[unknown]";
const char* imageName = "[unknown]";
uint32_t line = 0;
bool isInline = false;
uint32_t symlen = 0;
auto codeAddr = v.symAddr;
auto sit = symMap.find( v.symAddr );
if( sit != symMap.end() )
{
name = m_worker.GetString( sit->second.name );
imageName = m_worker.GetString( sit->second.imageName );
isInline = sit->second.isInline;
switch( m_statSampleLocation )
{
case 0:
file = m_worker.GetString( sit->second.file );
line = sit->second.line;
break;
case 1:
file = m_worker.GetString( sit->second.callFile );
line = sit->second.callLine;
break;
case 2:
if( sit->second.isInline )
{
file = m_worker.GetString( sit->second.callFile );
line = sit->second.callLine;
}
else
{
file = m_worker.GetString( sit->second.file );
line = sit->second.line;
}
break;
default:
assert( false );
break;
}
if( m_statHideUnknown && file[0] == '[' ) continue;
symlen = sit->second.size.Val();
}
else if( m_statHideUnknown )
{
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
const bool isKernel = v.symAddr >> 63 != 0;
const char* parentName = nullptr;
if( symlen == 0 && !isKernel )
{
const auto parentAddr = m_worker.GetSymbolForAddress( v.symAddr );
if( parentAddr != 0 )
{
auto pit = symMap.find( parentAddr );
if( pit != symMap.end() )
{
codeAddr = parentAddr;
symlen = pit->second.size.Val();
parentName = m_worker.GetString( pit->second.name );
}
}
}
bool expand = false;
if( !m_statSeparateInlines )
{
if( v.count > 0 && v.symAddr != 0 )
{
ImGui::PushID( v.symAddr );
expand = ImGui::TreeNodeEx( "", v.count == 0 ? ImGuiTreeNodeFlags_Leaf : 0 );
ImGui::PopID();
ImGui::SameLine();
}
}
else if( isInline )
{
TextDisabledUnformatted( ICON_FA_CARET_RIGHT );
ImGui::SameLine();
}
uint32_t excl;
if( m_statSeparateInlines )
{
excl = v.excl;
}
else
{
auto it = inlineMap.find( v.symAddr );
excl = it != inlineMap.end() ? it->second.excl : 0;
}
bool hasNoSamples = v.symAddr == 0 || excl == 0;
if( !m_statSeparateInlines && hasNoSamples && v.symAddr != 0 && v.count > 0 )
{
auto inSym = m_worker.GetInlineSymbolList( v.symAddr, symlen );
if( inSym )
{
const auto symEnd = v.symAddr + symlen;
while( *inSym < symEnd )
{
auto sit = inlineMap.find( *inSym );
if( sit != inlineMap.end() )
{
if( sit->second.excl != 0 )
{
hasNoSamples = false;
break;
}
}
inSym++;
}
}
}
if( hasNoSamples )
{
if( isKernel )
{
TextColoredUnformatted( 0xFF8888FF, name );
}
else if( m_shortenName == ShortenName::Never )
{
ImGui::TextUnformatted( name );
}
else
{
const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, name );
ImGui::TextUnformatted( normalized );
TooltipNormalizedName( name, normalized );
}
}
else
{
ImGui::PushID( idx++ );
bool clicked;
if( isKernel )
{
ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF );
clicked = ImGui::Selectable( name, m_sampleParents.withInlines && m_sampleParents.symAddr == v.symAddr, ImGuiSelectableFlags_SpanAllColumns );
ImGui::PopStyleColor();
}
else if( m_shortenName == ShortenName::Never )
{
clicked = ImGui::Selectable( name, m_sampleParents.withInlines && m_sampleParents.symAddr == v.symAddr, ImGuiSelectableFlags_SpanAllColumns );
}
else
{
const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, name );
clicked = ImGui::Selectable( "", m_sampleParents.withInlines && m_sampleParents.symAddr == v.symAddr, ImGuiSelectableFlags_SpanAllColumns );
ImGui::SameLine( 0, 0 );
ImGui::TextUnformatted( normalized );
TooltipNormalizedName( name, normalized );
}
if( clicked ) ShowSampleParents( v.symAddr, !m_statSeparateInlines );
ImGui::PopID();
}
if( parentName )
{
ImGui::SameLine();
if( m_shortenName == ShortenName::Never )
{
ImGui::TextDisabled( "(%s)", parentName );
}
else
{
const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, parentName );
ImGui::TextDisabled( "(%s)", normalized );
TooltipNormalizedName( parentName, normalized );
}
}
if( !m_statSeparateInlines && v.count > 0 && v.symAddr != 0 )
{
ImGui::SameLine();
ImGui::TextDisabled( "(+%s)", RealToString( v.count ) );
}
ImGui::TableNextColumn();
float indentVal = 0.f;
if( m_statBuzzAnim.Match( v.symAddr ) )
{
const auto time = m_statBuzzAnim.Time();
indentVal = sin( time * 60.f ) * 10.f * time;
ImGui::Indent( indentVal );
}
if( m_statShowAddress )
{
ImGui::TextDisabled( "0x%" PRIx64, v.symAddr );
}
else
{
TextDisabledUnformatted( LocationToString( file, line ) );
}
if( ImGui::IsItemHovered() )
{
DrawSourceTooltip( file, line );
if( ImGui::IsItemClicked( 1 ) )
{
if( SourceFileValid( file, m_worker.GetCaptureTime(), *this, m_worker ) )
{
ViewSymbol( file, line, codeAddr, v.symAddr );
if( !m_statSeparateInlines ) m_sourceView->CalcInlineStats( false );
}
else if( symlen != 0 )
{
uint32_t len;
if( m_worker.GetSymbolCode( codeAddr, len ) )
{
ViewSymbol( nullptr, 0, codeAddr, v.symAddr );
if( !m_statSeparateInlines ) m_sourceView->CalcInlineStats( false );
}
else
{
m_statBuzzAnim.Enable( v.symAddr, 0.5f );
}
}
else
{
m_statBuzzAnim.Enable( v.symAddr, 0.5f );
}
}
}
if( indentVal != 0.f )
{
ImGui::Unindent( indentVal );
}
ImGui::TableNextColumn();
TextDisabledUnformatted( imageName );
ImGui::TableNextColumn();
if( cnt > 0 )
{
char buf[64];
if( m_statSampleTime )
{
const auto t = cnt * period;
ImGui::TextUnformatted( TimeToString( t ) );
PrintStringPercent( buf, 100. * t / timeRange );
}
else
{
ImGui::TextUnformatted( RealToString( cnt ) );
PrintStringPercent( buf, cnt * revSampleCount100 );
}
ImGui::SameLine();
TextDisabledUnformatted( buf );
}
ImGui::TableNextColumn();
if( symlen != 0 )
{
if( m_worker.HasSymbolCode( codeAddr ) )
{
TextDisabledUnformatted( ICON_FA_DATABASE );
ImGui::SameLine();
}
if( isInline )
{
TextDisabledUnformatted( "<" );
ImGui::SameLine();
}
TextDisabledUnformatted( MemSizeToString( symlen ) );
}
if( !m_statSeparateInlines && expand )
{
assert( v.count > 0 );
assert( symlen != 0 );
auto inSym = m_worker.GetInlineSymbolList( v.symAddr, symlen );
assert( inSym != nullptr );
const auto symEnd = v.symAddr + symlen;
Vector<SymList> inSymList;
while( *inSym < symEnd )
{
auto sit = inlineMap.find( *inSym );
if( sit != inlineMap.end() )
{
inSymList.push_back( SymList { *inSym, sit->second.incl, sit->second.excl } );
}
else
{
inSymList.push_back( SymList { *inSym, 0, 0 } );
}
inSym++;
}
auto statIt = inlineMap.find( v.symAddr );
if( statIt != inlineMap.end() )
{
inSymList.push_back( SymList { v.symAddr, statIt->second.incl, statIt->second.excl } );
}
if( accumulationMode == AccumulationMode::SelfOnly )
{
pdqsort_branchless( inSymList.begin(), inSymList.end(), []( const auto& l, const auto& r ) { return l.excl != r.excl ? l.excl > r.excl : l.symAddr < r.symAddr; } );
}
else
{
pdqsort_branchless( inSymList.begin(), inSymList.end(), []( const auto& l, const auto& r ) { return l.incl != l.incl ? l.incl > r.incl : l.symAddr < r.symAddr; } );
}
ImGui::Indent();
for( auto& iv : inSymList )
{
const auto cnt = accumulationMode == AccumulationMode::SelfOnly ? iv.excl : iv.incl;
if( cnt > 0 || showAll )
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
auto sit = symMap.find( iv.symAddr );
assert( sit != symMap.end() );
name = m_worker.GetString( sit->second.name );
switch( m_statSampleLocation )
{
case 0:
file = m_worker.GetString( sit->second.file );
line = sit->second.line;
break;
case 1:
file = m_worker.GetString( sit->second.callFile );
line = sit->second.callLine;
break;
case 2:
if( sit->second.isInline )
{
file = m_worker.GetString( sit->second.callFile );
line = sit->second.callLine;
}
else
{
file = m_worker.GetString( sit->second.file );
line = sit->second.line;
}
break;
default:
assert( false );
break;
}
const auto sn = iv.symAddr == v.symAddr ? "[ - self - ]" : name;
if( iv.excl == 0 )
{
if( m_shortenName == ShortenName::Never )
{
ImGui::TextUnformatted( sn );
}
else
{
const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, sn );
ImGui::TextUnformatted( normalized );
TooltipNormalizedName( sn, normalized );
}
}
else
{
ImGui::PushID( idx++ );
bool clicked;
if( m_shortenName == ShortenName::Never )
{
clicked = ImGui::Selectable( sn, !m_sampleParents.withInlines && m_sampleParents.symAddr == iv.symAddr, ImGuiSelectableFlags_SpanAllColumns );
}
else
{
const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, sn );
clicked = ImGui::Selectable( "", !m_sampleParents.withInlines && m_sampleParents.symAddr == iv.symAddr, ImGuiSelectableFlags_SpanAllColumns );
ImGui::SameLine( 0, 0 );
ImGui::TextUnformatted( normalized );
TooltipNormalizedName( sn, normalized );
}
if( clicked ) ShowSampleParents( iv.symAddr, false );
ImGui::PopID();
}
ImGui::TableNextColumn();
float indentVal = 0.f;
if( m_statBuzzAnim.Match( iv.symAddr ) )
{
const auto time = m_statBuzzAnim.Time();
indentVal = sin( time * 60.f ) * 10.f * time;
ImGui::Indent( indentVal );
}
if( m_statShowAddress )
{
ImGui::TextDisabled( "0x%" PRIx64, iv.symAddr );
}
else
{
TextDisabledUnformatted( LocationToString( file, line ) );
}
if( ImGui::IsItemHovered() )
{
DrawSourceTooltip( file, line );
if( ImGui::IsItemClicked( 1 ) )
{
if( SourceFileValid( file, m_worker.GetCaptureTime(), *this, m_worker ) )
{
ViewSymbol( file, line, codeAddr, iv.symAddr );
if( !m_statSeparateInlines ) m_sourceView->CalcInlineStats( true );
}
else if( symlen != 0 )
{
uint32_t len;
if( m_worker.GetSymbolCode( codeAddr, len ) )
{
ViewSymbol( nullptr, 0, codeAddr, iv.symAddr );
if( !m_statSeparateInlines ) m_sourceView->CalcInlineStats( true );
}
else
{
m_statBuzzAnim.Enable( iv.symAddr, 0.5f );
}
}
else
{
m_statBuzzAnim.Enable( iv.symAddr, 0.5f );
}
}
}
if( indentVal != 0.f )
{
ImGui::Unindent( indentVal );
}
ImGui::TableNextColumn();
ImGui::TableNextColumn();
if( cnt > 0 )
{
char buf[64];
if( m_statSampleTime )
{
const auto t = cnt * period;
ImGui::TextUnformatted( TimeToString( t ) );
PrintStringPercent( buf, 100. * t / timeRange );
}
else
{
ImGui::TextUnformatted( RealToString( cnt ) );
PrintStringPercent( buf, cnt * revSampleCount100 );
}
ImGui::SameLine();
TextDisabledUnformatted( buf );
}
}
}
ImGui::Unindent();
ImGui::TreePop();
}
}
}
ImGui::EndTable();
}
ImGui::EndChild();
inlineMap.clear();
}
}
void View::DrawSampleParents()
{
bool show = true;
const auto scale = GetScale();
ImGui::SetNextWindowSize( ImVec2( 1400 * scale, 500 * scale ), ImGuiCond_FirstUseEver );
ImGui::Begin( "Sample entry call stacks", &show, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse );
if( !ImGui::GetCurrentWindowRead()->SkipItems )
{
auto ss = m_worker.GetSymbolStats( m_sampleParents.symAddr );
auto excl = ss->excl;
auto stats = ss->parents;
const auto symbol = m_worker.GetSymbolData( m_sampleParents.symAddr );
if( !symbol->isInline && m_sampleParents.withInlines )
{
const auto symlen = symbol->size.Val();
auto inSym = m_worker.GetInlineSymbolList( m_sampleParents.symAddr, symlen );
if( inSym )
{
const auto symEnd = m_sampleParents.symAddr + symlen;
while( *inSym < symEnd )
{
auto istat = m_worker.GetSymbolStats( *inSym++ );
if( !istat ) continue;
excl += istat->excl;
for( auto& v : istat->baseParents )
{
auto it = stats.find( v.first );
if( it == stats.end() )
{
stats.emplace( v.first, v.second );
}
else
{
it->second += v.second;
}
}
}
}
}
assert( !stats.empty() );
const auto symName = m_worker.GetString( symbol->name );
const char* normalized = m_shortenName != ShortenName::Never ? ShortenZoneName( ShortenName::OnlyNormalize, symName ) : nullptr;
ImGui::PushFont( m_bigFont );
TextFocused( "Function:", normalized ? normalized : symName );
if( normalized )
{
ImGui::PopFont();
TooltipNormalizedName( symName, normalized );
ImGui::PushFont( m_bigFont );
}
if( symbol->isInline )
{
ImGui::SameLine();
TextDisabledUnformatted( "(inline)" );
}
else if( !m_sampleParents.withInlines )
{
ImGui::SameLine();
TextDisabledUnformatted( "(without inlines)" );
}
ImGui::PopFont();
TextDisabledUnformatted( "Location:" );
ImGui::SameLine();
const auto callFile = m_worker.GetString( symbol->callFile );
ImGui::TextUnformatted( LocationToString( callFile, symbol->callLine ) );
if( ImGui::IsItemClicked( 1 ) )
{
ViewDispatch( callFile, symbol->callLine, m_sampleParents.symAddr );
}
TextDisabledUnformatted( "Entry point:" );
ImGui::SameLine();
const auto file = m_worker.GetString( symbol->file );
ImGui::TextUnformatted( LocationToString( file, symbol->line ) );
if( ImGui::IsItemClicked( 1 ) )
{
ViewDispatch( file, symbol->line, m_sampleParents.symAddr );
}
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
TextDisabledUnformatted( m_worker.GetString( symbol->imageName ) );
ImGui::Separator();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 2, 2 ) );
if( ImGui::RadioButton( ICON_FA_TABLE " List", m_sampleParents.mode == 0 ) ) m_sampleParents.mode = 0;
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
if( ImGui::RadioButton( ICON_FA_TREE " Bottom-up tree", m_sampleParents.mode == 1 ) ) m_sampleParents.mode = 1;
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
if( ImGui::RadioButton( ICON_FA_TREE " Top-down tree", m_sampleParents.mode == 2 ) ) m_sampleParents.mode = 2;
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
ImGui::Checkbox( ICON_FA_STOPWATCH " Show time", &m_statSampleTime );
ImGui::PopStyleVar();
ImGui::Separator();
ImGui::BeginChild( "##sampleParents" );
switch( m_sampleParents.mode )
{
case 0:
{
TextDisabledUnformatted( "Entry call stack:" );
ImGui::SameLine();
if( ImGui::SmallButton( " " ICON_FA_CARET_LEFT " " ) )
{
m_sampleParents.sel = std::max( m_sampleParents.sel - 1, 0 );
}
ImGui::SameLine();
ImGui::Text( "%s / %s", RealToString( m_sampleParents.sel + 1 ), RealToString( stats.size() ) );
if( ImGui::IsItemClicked() ) ImGui::OpenPopup( "EntryCallStackPopup" );
ImGui::SameLine();
if( ImGui::SmallButton( " " ICON_FA_CARET_RIGHT " " ) )
{
m_sampleParents.sel = std::min<int>( m_sampleParents.sel + 1, stats.size() - 1 );
}
if( ImGui::BeginPopup( "EntryCallStackPopup" ) )
{
int sel = m_sampleParents.sel + 1;
ImGui::SetNextItemWidth( 120 * scale );
const bool clicked = ImGui::InputInt( "##entryCallStack", &sel, 1, 100, ImGuiInputTextFlags_EnterReturnsTrue );
if( clicked ) m_sampleParents.sel = std::min( std::max( sel, 1 ), int( stats.size() ) ) - 1;
ImGui::EndPopup();
}
Vector<decltype(stats.begin())> data;
data.reserve( stats.size() );
for( auto it = stats.begin(); it != stats.end(); ++it ) data.push_back( it );
pdqsort_branchless( data.begin(), data.end(), []( const auto& l, const auto& r ) { return l->second > r->second; } );
ImGui::SameLine();
ImGui::TextUnformatted( m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * data[m_sampleParents.sel]->second ) : RealToString( data[m_sampleParents.sel]->second ) );
ImGui::SameLine();
char buf[64];
PrintStringPercent( buf, 100. * data[m_sampleParents.sel]->second / excl );
TextDisabledUnformatted( buf );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
ImGui::TextUnformatted( ICON_FA_AT " Frame location:" );
ImGui::SameLine();
ImGui::RadioButton( "Source code", &m_showCallstackFrameAddress, 0 );
ImGui::SameLine();
ImGui::RadioButton( "Entry point", &m_showCallstackFrameAddress, 3 );
ImGui::SameLine();
ImGui::RadioButton( "Return address", &m_showCallstackFrameAddress, 1 );
ImGui::SameLine();
ImGui::RadioButton( "Symbol address", &m_showCallstackFrameAddress, 2 );
ImGui::PopStyleVar();
auto& cs = m_worker.GetParentCallstack( data[m_sampleParents.sel]->first );
ImGui::Separator();
if( ImGui::BeginTable( "##callstack", 4, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY ) )
{
ImGui::TableSetupScrollFreeze( 0, 1 );
ImGui::TableSetupColumn( "Frame", ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize );
ImGui::TableSetupColumn( "Function" );
ImGui::TableSetupColumn( "Location" );
ImGui::TableSetupColumn( "Image" );
ImGui::TableHeadersRow();
int fidx = 0;
int bidx = 0;
for( auto& entry : cs )
{
auto frameData = entry.custom ? m_worker.GetParentCallstackFrame( entry ) : m_worker.GetCallstackFrame( entry );
assert( frameData );
const auto fsz = frameData->size;
for( uint8_t f=0; f<fsz; f++ )
{
const auto& frame = frameData->data[f];
auto txt = m_worker.GetString( frame.name );
bidx++;
ImGui::TableNextRow();
ImGui::TableNextColumn();
if( f == fsz-1 )
{
ImGui::Text( "%i", fidx++ );
}
else
{
ImGui::PushFont( m_smallFont );
TextDisabledUnformatted( "inline" );
ImGui::PopFont();
}
ImGui::TableNextColumn();
{
ImGui::PushTextWrapPos( 0.0f );
if( txt[0] == '[' )
{
TextDisabledUnformatted( txt );
}
else if( m_worker.GetCanonicalPointer( entry ) >> 63 != 0 )
{
TextColoredUnformatted( 0xFF8888FF, txt );
}
else if( m_shortenName == ShortenName::Never )
{
ImGui::TextUnformatted( txt );
}
else
{
const auto normalized = ShortenZoneName( ShortenName::OnlyNormalize, txt );
ImGui::TextUnformatted( normalized );
TooltipNormalizedName( txt, normalized );
}
ImGui::PopTextWrapPos();
}
if( ImGui::IsItemClicked() )
{
ImGui::SetClipboardText( txt );
}
ImGui::TableNextColumn();
ImGui::PushTextWrapPos( 0.0f );
float indentVal = 0.f;
if( m_sampleParentBuzzAnim.Match( bidx ) )
{
const auto time = m_sampleParentBuzzAnim.Time();
indentVal = sin( time * 60.f ) * 10.f * time;
ImGui::Indent( indentVal );
}
txt = m_worker.GetString( frame.file );
switch( m_showCallstackFrameAddress )
{
case 0:
TextDisabledUnformatted( LocationToString( txt, frame.line ) );
if( ImGui::IsItemClicked() )
{
ImGui::SetClipboardText( txt );
}
break;
case 1:
if( entry.custom == 0 )
{
const auto addr = m_worker.GetCanonicalPointer( entry );
ImGui::TextDisabled( "0x%" PRIx64, addr );
if( ImGui::IsItemClicked() )
{
char tmp[32];
sprintf( tmp, "0x%" PRIx64, addr );
ImGui::SetClipboardText( tmp );
}
}
else
{
TextDisabledUnformatted( "unavailable" );
}
break;
case 2:
ImGui::TextDisabled( "0x%" PRIx64, frame.symAddr );
if( ImGui::IsItemClicked() )
{
char tmp[32];
sprintf( tmp, "0x%" PRIx64, frame.symAddr );
ImGui::SetClipboardText( tmp );
}
break;
case 3:
{
const auto sym = m_worker.GetSymbolData( frame.symAddr );
if( sym )
{
const auto symtxt = m_worker.GetString( sym->file );
TextDisabledUnformatted( LocationToString( symtxt, sym->line ) );
if( ImGui::IsItemClicked() )
{
ImGui::SetClipboardText( symtxt );
}
}
else
{
TextDisabledUnformatted( "[unknown]" );
}
break;
}
default:
assert( false );
break;
}
if( ImGui::IsItemHovered() )
{
if( m_showCallstackFrameAddress == 3 )
{
const auto sym = m_worker.GetSymbolData( frame.symAddr );
if( sym )
{
const auto symtxt = m_worker.GetString( sym->file );
DrawSourceTooltip( symtxt, sym->line );
}
}
else
{
DrawSourceTooltip( txt, frame.line );
}
if( ImGui::IsItemClicked( 1 ) )
{
if( m_showCallstackFrameAddress == 3 )
{
const auto sym = m_worker.GetSymbolData( frame.symAddr );
if( sym )
{
const auto symtxt = m_worker.GetString( sym->file );
if( !ViewDispatch( symtxt, sym->line, frame.symAddr ) )
{
m_sampleParentBuzzAnim.Enable( bidx, 0.5f );
}
}
else
{
m_sampleParentBuzzAnim.Enable( bidx, 0.5f );
}
}
else
{
if( !ViewDispatch( txt, frame.line, frame.symAddr ) )
{
m_sampleParentBuzzAnim.Enable( bidx, 0.5f );
}
}
}
}
if( indentVal != 0.f )
{
ImGui::Unindent( indentVal );
}
ImGui::PopTextWrapPos();
ImGui::TableNextColumn();
if( frameData->imageName.Active() )
{
TextDisabledUnformatted( m_worker.GetString( frameData->imageName ) );
}
}
}
ImGui::EndTable();
}
break;
}
case 1:
{
SmallCheckbox( "Group by function name", &m_sampleParents.groupBottomUp );
auto tree = GetParentsCallstackFrameTreeBottomUp( stats, m_sampleParents.groupBottomUp );
if( !tree.empty() )
{
int idx = 0;
DrawParentsFrameTreeLevel( tree, idx );
}
else
{
TextDisabledUnformatted( "No call stacks to show" );
}
break;
}
case 2:
{
SmallCheckbox( "Group by function name", &m_sampleParents.groupTopDown );
auto tree = GetParentsCallstackFrameTreeTopDown( stats, m_sampleParents.groupTopDown );
if( !tree.empty() )
{
int idx = 0;
DrawParentsFrameTreeLevel( tree, idx );
}
else
{
TextDisabledUnformatted( "No call stacks to show" );
}
break;
}
default:
assert( false );
break;
}
ImGui::EndChild();
}
ImGui::End();
if( !show )
{
m_sampleParents.symAddr = 0;
}
}
}