From 5b451c355759a6a007a5a3bae63336741a38ed41 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Sat, 2 Jul 2022 15:00:52 +0200 Subject: [PATCH] Extract samples UI from View. --- profiler/build/win32/Tracy.vcxproj | 1 + profiler/build/win32/Tracy.vcxproj.filters | 3 + server/TracyView.cpp | 858 -------------------- server/TracyView_Samples.cpp | 869 +++++++++++++++++++++ 4 files changed, 873 insertions(+), 858 deletions(-) create mode 100644 server/TracyView_Samples.cpp diff --git a/profiler/build/win32/Tracy.vcxproj b/profiler/build/win32/Tracy.vcxproj index 4560db14..d6f8165b 100644 --- a/profiler/build/win32/Tracy.vcxproj +++ b/profiler/build/win32/Tracy.vcxproj @@ -151,6 +151,7 @@ + diff --git a/profiler/build/win32/Tracy.vcxproj.filters b/profiler/build/win32/Tracy.vcxproj.filters index fca9b148..880774c8 100644 --- a/profiler/build/win32/Tracy.vcxproj.filters +++ b/profiler/build/win32/Tracy.vcxproj.filters @@ -300,6 +300,9 @@ server + + server + diff --git a/server/TracyView.cpp b/server/TracyView.cpp index e8263818..6d9afbf2 100644 --- a/server/TracyView.cpp +++ b/server/TracyView.cpp @@ -5393,490 +5393,6 @@ void View::DrawHistogramMinMaxLabel( ImDrawList* draw, int64_t tmin, int64_t tma draw->AddText( wpos + ImVec2( round( (w-1-rsz) * 0.5 ), ty15 ), 0x66FFFFFF, range ); } -void View::DrawSamplesStatistics( Vector& data, int64_t timeRange, AccumulationMode accumulationMode ) -{ - static unordered_flat_map inlineMap; - assert( inlineMap.empty() ); - if( !m_statSeparateInlines ) - { - static unordered_flat_map 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 - { - ImGui::TextUnformatted( name ); - } - } - else - { - ImGui::PushID( idx++ ); - if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); - const auto clicked = ImGui::Selectable( name, m_sampleParents.withInlines && m_sampleParents.symAddr == v.symAddr, ImGuiSelectableFlags_SpanAllColumns ); - if( isKernel ) ImGui::PopStyleColor(); - if( clicked ) ShowSampleParents( v.symAddr, !m_statSeparateInlines ); - ImGui::PopID(); - } - if( parentName ) - { - ImGui::SameLine(); - ImGui::TextDisabled( "(%s)", parentName ); - } - 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 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 ) - { - ImGui::TextUnformatted( sn ); - } - else - { - ImGui::PushID( idx++ ); - if( ImGui::Selectable( sn, !m_sampleParents.withInlines && m_sampleParents.symAddr == iv.symAddr, ImGuiSelectableFlags_SpanAllColumns ) ) - { - 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::DrawTextEditor() { const auto scale = GetScale(); @@ -6192,380 +5708,6 @@ void View::DrawAnnotationList() ImGui::End(); } -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() ); - - ImGui::PushFont( m_bigFont ); - TextFocused( "Symbol:", m_worker.GetString( symbol->name ) ); - 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( 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 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; fdata[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 - { - ImGui::TextUnformatted( txt ); - } - 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; - } -} - void View::DrawRanges() { ImGui::Begin( "Time range limits", &m_showRanges, ImGuiWindowFlags_AlwaysAutoResize ); diff --git a/server/TracyView_Samples.cpp b/server/TracyView_Samples.cpp new file mode 100644 index 00000000..4c96000a --- /dev/null +++ b/server/TracyView_Samples.cpp @@ -0,0 +1,869 @@ +#include + +#include "TracyFilesystem.hpp" +#include "TracyPrint.hpp" +#include "TracySourceView.hpp" +#include "TracyView.hpp" + +namespace tracy +{ + +void View::DrawSamplesStatistics( Vector& data, int64_t timeRange, AccumulationMode accumulationMode ) +{ + static unordered_flat_map inlineMap; + assert( inlineMap.empty() ); + if( !m_statSeparateInlines ) + { + static unordered_flat_map 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 + { + ImGui::TextUnformatted( name ); + } + } + else + { + ImGui::PushID( idx++ ); + if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); + const auto clicked = ImGui::Selectable( name, m_sampleParents.withInlines && m_sampleParents.symAddr == v.symAddr, ImGuiSelectableFlags_SpanAllColumns ); + if( isKernel ) ImGui::PopStyleColor(); + if( clicked ) ShowSampleParents( v.symAddr, !m_statSeparateInlines ); + ImGui::PopID(); + } + if( parentName ) + { + ImGui::SameLine(); + ImGui::TextDisabled( "(%s)", parentName ); + } + 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 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 ) + { + ImGui::TextUnformatted( sn ); + } + else + { + ImGui::PushID( idx++ ); + if( ImGui::Selectable( sn, !m_sampleParents.withInlines && m_sampleParents.symAddr == iv.symAddr, ImGuiSelectableFlags_SpanAllColumns ) ) + { + 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() ); + + ImGui::PushFont( m_bigFont ); + TextFocused( "Symbol:", m_worker.GetString( symbol->name ) ); + 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( 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 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; fdata[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 + { + ImGui::TextUnformatted( txt ); + } + 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; + } +} + +}