From 35f55c781b390cc646b7e024416b8173e4c5ddf1 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Sat, 2 Jul 2022 13:48:54 +0200 Subject: [PATCH] Extract compare UI from View. --- profiler/build/win32/Tracy.vcxproj | 1 + profiler/build/win32/Tracy.vcxproj.filters | 3 + server/TracyView.cpp | 1067 ------------------- server/TracyView_Compare.cpp | 1079 ++++++++++++++++++++ 4 files changed, 1083 insertions(+), 1067 deletions(-) create mode 100644 server/TracyView_Compare.cpp diff --git a/profiler/build/win32/Tracy.vcxproj b/profiler/build/win32/Tracy.vcxproj index 812b79f6..db861fcf 100644 --- a/profiler/build/win32/Tracy.vcxproj +++ b/profiler/build/win32/Tracy.vcxproj @@ -137,6 +137,7 @@ + diff --git a/profiler/build/win32/Tracy.vcxproj.filters b/profiler/build/win32/Tracy.vcxproj.filters index 2362874c..8a74c1cc 100644 --- a/profiler/build/win32/Tracy.vcxproj.filters +++ b/profiler/build/win32/Tracy.vcxproj.filters @@ -264,6 +264,9 @@ server + + server + diff --git a/server/TracyView.cpp b/server/TracyView.cpp index 4766277e..7bcd6859 100644 --- a/server/TracyView.cpp +++ b/server/TracyView.cpp @@ -7054,1034 +7054,6 @@ void View::DrawZoneList( int id, const Vector>& zones ) ImGui::TreePop(); } -bool View::FindMatchingZone( int prev0, int prev1, int flags ) -{ - int idx = 0; - bool found = false; - auto& srcloc0 = m_worker.GetSourceLocation( m_compare.match[0][m_compare.selMatch[0]] ); - auto& srcloc1 = m_compare.second->GetSourceLocation( m_compare.match[1][m_compare.selMatch[1]] ); - auto string0 = m_worker.GetString( srcloc0.name.active ? srcloc0.name : srcloc0.function ); - auto string1 = m_compare.second->GetString( srcloc1.name.active ? srcloc1.name : srcloc1.function ); - auto file0 = m_worker.GetString( srcloc0.file ); - auto file1 = m_compare.second->GetString( srcloc1.file ); - bool wrongFile = false; - bool wrongLine = false; - if( flags & FindMatchingZoneFlagSourceFile ) - { - wrongFile = strcmp( file0, file1 ) != 0; - } - if( flags & FindMatchingZoneFlagLineNum ) - { - wrongLine = srcloc0.line != srcloc1.line; - } - - if( strcmp( string0, string1 ) != 0 || wrongFile || wrongLine ) - { - if( prev0 != m_compare.selMatch[0] ) - { - for( auto& v : m_compare.match[1] ) - { - auto& srcloc = m_compare.second->GetSourceLocation( v ); - auto string = m_compare.second->GetString( srcloc.name.active ? srcloc.name : srcloc.function ); - auto file = m_compare.second->GetString( srcloc.file ); - bool sameFile = true; - bool sameLine = true; - if( flags & FindMatchingZoneFlagSourceFile ) - { - sameFile = strcmp( file0, file ) == 0; - } - if( flags & FindMatchingZoneFlagLineNum ) - { - sameLine = srcloc0.line == srcloc.line; - } - if( strcmp( string0, string ) == 0 && sameFile && sameLine ) - { - m_compare.selMatch[1] = idx; - found = true; - break; - } - idx++; - } - } - else - { - assert( prev1 != m_compare.selMatch[1] ); - for( auto& v : m_compare.match[0] ) - { - auto& srcloc = m_worker.GetSourceLocation( v ); - auto string = m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ); - auto file = m_worker.GetString( srcloc.file ); - bool sameFile = true; - bool sameLine = true; - if( flags & FindMatchingZoneFlagSourceFile ) - { - sameFile = strcmp( file1, file ) == 0; - } - if( flags & FindMatchingZoneFlagLineNum ) - { - sameLine = srcloc1.line == srcloc.line; - } - if( strcmp( string1, string ) == 0 && sameFile && sameLine ) - { - m_compare.selMatch[0] = idx; - found = true; - break; - } - idx++; - } - - } - } - return found; -} - -void View::DrawCompare() -{ - const auto scale = GetScale(); - ImGui::SetNextWindowSize( ImVec2( 590 * scale, 800 * scale ), ImGuiCond_FirstUseEver ); - ImGui::Begin( "Compare traces", &m_compare.show, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); - if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; } -#ifdef TRACY_NO_STATISTICS - ImGui::TextWrapped( "Collection of statistical data is disabled in this build." ); - ImGui::TextWrapped( "Rebuild without the TRACY_NO_STATISTICS macro to enable trace comparison." ); -#elif defined TRACY_NO_FILESELECTOR - ImGui::TextWrapped( "File selector is disabled in this build." ); - ImGui::TextWrapped( "Rebuild without the TRACY_NO_FILESELECTOR macro to enable trace comparison." ); -#else - if( !m_compare.second ) - { - ImGui::TextWrapped( "Please load a second trace to compare results." ); - if( ImGui::Button( ICON_FA_FOLDER_OPEN " Open second trace" ) && !m_compare.loadThread.joinable() ) - { - nfdu8filteritem_t filter = { "Tracy Profiler trace file", "tracy" }; - nfdu8char_t* fn; - auto res = NFD_OpenDialogU8( &fn, &filter, 1, nullptr ); - if( res == NFD_OKAY ) - { - try - { - auto f = std::shared_ptr( tracy::FileRead::Open( fn ) ); - if( f ) - { - m_compare.loadThread = std::thread( [this, f] { - try - { - m_compare.second = std::make_unique( *f, EventType::None ); - m_compare.userData = std::make_unique( m_compare.second->GetCaptureProgram().c_str(), m_compare.second->GetCaptureTime() ); - } - catch( const tracy::UnsupportedVersion& e ) - { - m_compare.badVer.state = BadVersionState::UnsupportedVersion; - m_compare.badVer.version = e.version; - } - } ); - } - } - catch( const tracy::NotTracyDump& ) - { - m_compare.badVer.state = BadVersionState::BadFile; - } - catch( const tracy::FileReadError& ) - { - m_compare.badVer.state = BadVersionState::ReadError; - } - NFD_FreePathU8( fn ); - } - } - tracy::BadVersion( m_compare.badVer, m_bigFont ); - ImGui::End(); - return; - } - - if( m_compare.loadThread.joinable() ) m_compare.loadThread.join(); - - if( !m_worker.AreSourceLocationZonesReady() || !m_compare.second->AreSourceLocationZonesReady() ) - { - ImGui::TextWrapped( "Please wait, computing data..." ); - DrawWaitingDots( s_time ); - ImGui::End(); - return; - } - - TextColoredUnformatted( ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ), ICON_FA_LEMON ); - ImGui::SameLine(); - TextDisabledUnformatted( "This trace:" ); - ImGui::SameLine(); - const auto& desc0 = m_userData.GetDescription(); - if( desc0.empty() ) - { - ImGui::TextUnformatted( m_worker.GetCaptureName().c_str() ); - } - else - { - ImGui::TextUnformatted( desc0.c_str() ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%s)", m_worker.GetCaptureName().c_str() ); - } - - TextColoredUnformatted( ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ), ICON_FA_GEM ); - ImGui::SameLine(); - TextDisabledUnformatted( "External trace:" ); - ImGui::SameLine(); - const auto& desc1 = m_compare.userData->GetDescription(); - if( desc1.empty() ) - { - ImGui::TextUnformatted( m_compare.second->GetCaptureName().c_str() ); - } - else - { - ImGui::TextUnformatted( desc1.c_str() ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%s)", m_compare.second->GetCaptureName().c_str() ); - } - - if( ImGui::Button( ICON_FA_TRASH_ALT " Unload" ) ) - { - m_compare.Reset(); - m_compare.second.reset(); - m_compare.userData.reset(); - ImGui::End(); - return; - } - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - ImGui::Text( "Compare mode: " ); - ImGui::SameLine(); - const auto oldMode = m_compare.compareMode; - ImGui::RadioButton( "Zones", &m_compare.compareMode, 0 ); - ImGui::SameLine(); - ImGui::RadioButton( "Frames", &m_compare.compareMode, 1 ); - if( oldMode != m_compare.compareMode ) - { - m_compare.Reset(); - } - - bool findClicked = false; - - if( m_compare.compareMode == 0 ) - { - ImGui::PushItemWidth( -0.01f ); - findClicked |= ImGui::InputTextWithHint( "###compare", "Enter zone name to search for", m_compare.pattern, 1024, ImGuiInputTextFlags_EnterReturnsTrue ); - ImGui::PopItemWidth(); - - findClicked |= ImGui::Button( ICON_FA_SEARCH " Find" ); - ImGui::SameLine(); - - if( ImGui::Button( ICON_FA_BAN " Clear" ) ) - { - m_compare.Reset(); - } - ImGui::SameLine(); - ImGui::Checkbox( "Ignore case", &m_compare.ignoreCase ); - - if( findClicked ) - { - m_compare.Reset(); - FindZonesCompare(); - } - - if( m_compare.match[0].empty() && m_compare.match[1].empty() ) - { - ImGui::End(); - return; - } - - ImGui::Separator(); - ImGui::BeginChild( "##compare" ); - - if( ImGui::TreeNodeEx( "Matched source locations", ImGuiTreeNodeFlags_DefaultOpen ) ) - { - ImGui::SameLine(); - SmallCheckbox( "Link selection", &m_compare.link ); - - ImGui::Separator(); - ImGui::Columns( 2 ); - TextColoredUnformatted( ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ), ICON_FA_LEMON ); - ImGui::SameLine(); - ImGui::TextUnformatted( "This trace" ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%zu)", m_compare.match[0].size() ); - ImGui::NextColumn(); - TextColoredUnformatted( ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ), ICON_FA_GEM ); - ImGui::SameLine(); - ImGui::TextUnformatted( "External trace" ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%zu)", m_compare.match[1].size() ); - ImGui::Separator(); - ImGui::NextColumn(); - - const auto prev0 = m_compare.selMatch[0]; - int idx = 0; - for( auto& v : m_compare.match[0] ) - { - auto& srcloc = m_worker.GetSourceLocation( v ); - auto& zones = m_worker.GetZonesForSourceLocation( v ).zones; - SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); - ImGui::SameLine(); - ImGui::PushID( idx ); - ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); - ImGui::RadioButton( m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ), &m_compare.selMatch[0], idx++ ); - ImGui::PopStyleVar(); - ImGui::SameLine(); - ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s", RealToString( zones.size() ), LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); - ImGui::PopID(); - } - ImGui::NextColumn(); - - const auto prev1 = m_compare.selMatch[1]; - idx = 0; - for( auto& v : m_compare.match[1] ) - { - auto& srcloc = m_compare.second->GetSourceLocation( v ); - auto& zones = m_compare.second->GetZonesForSourceLocation( v ).zones; - ImGui::PushID( -1 - idx ); - ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); - ImGui::RadioButton( m_compare.second->GetString( srcloc.name.active ? srcloc.name : srcloc.function ), &m_compare.selMatch[1], idx++ ); - ImGui::PopStyleVar(); - ImGui::SameLine(); - ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s", RealToString( zones.size() ), LocationToString( m_compare.second->GetString( srcloc.file ), srcloc.line ) ); - ImGui::PopID(); - } - ImGui::NextColumn(); - ImGui::EndColumns(); - ImGui::TreePop(); - - if( prev0 != m_compare.selMatch[0] || prev1 != m_compare.selMatch[1] ) - { - m_compare.ResetSelection(); - - if( m_compare.link ) - { - if( !FindMatchingZone( prev0, prev1, FindMatchingZoneFlagSourceFile | FindMatchingZoneFlagLineNum ) ) - { - if( !FindMatchingZone( prev0, prev1, FindMatchingZoneFlagSourceFile ) ) - { - FindMatchingZone( prev0, prev1, FindMatchingZoneFlagDefault ); - } - } - } - } - } - - if( m_compare.match[0].empty() || m_compare.match[1].empty() ) - { - ImGui::Separator(); - ImGui::TextWrapped( "Both traces must have matches." ); - ImGui::End(); - return; - } - } - else - { - assert( m_compare.compareMode == 1 ); - - ImGui::Separator(); - ImGui::BeginChild( "##compare" ); - if( ImGui::TreeNodeEx( "Frame sets", ImGuiTreeNodeFlags_DefaultOpen ) ) - { - const auto& f0 = m_worker.GetFrames(); - const auto& f1 = m_compare.second->GetFrames(); - - ImGui::SameLine(); - SmallCheckbox( "Link selection", &m_compare.link ); - - ImGui::Separator(); - ImGui::Columns( 2 ); - TextColoredUnformatted( ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ), ICON_FA_LEMON ); - ImGui::SameLine(); - ImGui::TextUnformatted( "This trace" ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%zu)", f0.size() ); - ImGui::NextColumn(); - TextColoredUnformatted( ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ), ICON_FA_GEM ); - ImGui::SameLine(); - ImGui::TextUnformatted( "External trace" ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%zu)", f1.size() ); - ImGui::Separator(); - ImGui::NextColumn(); - - const auto prev0 = m_compare.selMatch[0]; - int idx = 0; - for( auto& v : f0 ) - { - const auto name = m_worker.GetString( v->name ); - ImGui::PushID( -1 - idx ); - ImGui::RadioButton( name, &m_compare.selMatch[0], idx++ ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%s)", RealToString( v->frames.size() ) ); - ImGui::PopID(); - } - ImGui::NextColumn(); - - const auto prev1 = m_compare.selMatch[1]; - idx = 0; - for( auto& v : f1 ) - { - const auto name = m_compare.second->GetString( v->name ); - ImGui::PushID( idx ); - ImGui::RadioButton( name, &m_compare.selMatch[1], idx++ ); - ImGui::SameLine(); - ImGui::TextDisabled( "(%s)", RealToString( v->frames.size() ) ); - ImGui::PopID(); - } - ImGui::NextColumn(); - ImGui::EndColumns(); - ImGui::TreePop(); - - if( prev0 != m_compare.selMatch[0] || prev1 != m_compare.selMatch[1] ) - { - m_compare.ResetSelection(); - - if( m_compare.link ) - { - auto string0 = m_worker.GetString( f0[m_compare.selMatch[0]]->name ); - auto string1 = m_compare.second->GetString( f1[m_compare.selMatch[1]]->name ); - - if( strcmp( string0, string1 ) != 0 ) - { - idx = 0; - if( prev0 != m_compare.selMatch[0] ) - { - for( auto& v : f1 ) - { - auto string = m_compare.second->GetString( v->name ); - if( strcmp( string0, string ) == 0 ) - { - m_compare.selMatch[1] = idx; - break; - } - idx++; - } - } - else - { - assert( prev1 != m_compare.selMatch[1] ); - for( auto& v : f0 ) - { - auto string = m_worker.GetString( v->name ); - if( strcmp( string1, string ) == 0 ) - { - m_compare.selMatch[0] = idx; - break; - } - idx++; - } - } - } - } - } - } - } - - ImGui::Separator(); - if( ImGui::TreeNodeEx( "Histogram", ImGuiTreeNodeFlags_DefaultOpen ) ) - { - const auto ty = ImGui::GetTextLineHeight(); - - int64_t tmin, tmax; - size_t size0, size1; - int64_t total0, total1; - double sumSq0, sumSq1; - - if( m_compare.compareMode == 0 ) - { - auto& zoneData0 = m_worker.GetZonesForSourceLocation( m_compare.match[0][m_compare.selMatch[0]] ); - auto& zoneData1 = m_compare.second->GetZonesForSourceLocation( m_compare.match[1][m_compare.selMatch[1]] ); - auto& zones0 = zoneData0.zones; - auto& zones1 = zoneData1.zones; - zones0.ensure_sorted(); - zones1.ensure_sorted(); - - tmin = std::min( zoneData0.min, zoneData1.min ); - tmax = std::max( zoneData0.max, zoneData1.max ); - - size0 = zones0.size(); - size1 = zones1.size(); - total0 = zoneData0.total; - total1 = zoneData1.total; - sumSq0 = zoneData0.sumSq; - sumSq1 = zoneData1.sumSq; - - const size_t zsz[2] = { size0, size1 }; - for( int k=0; k<2; k++ ) - { - if( m_compare.sortedNum[k] != zsz[k] ) - { - auto& zones = k == 0 ? zones0 : zones1; - auto& vec = m_compare.sorted[k]; - vec.reserve( zsz[k] ); - int64_t total = m_compare.total[k]; - size_t i; - for( i=m_compare.sortedNum[k]; iGetFrames()[m_compare.selMatch[1]]; - - tmin = std::min( f0->min, f1->min ); - tmax = std::max( f0->max, f1->max ); - - size0 = f0->frames.size(); - size1 = f1->frames.size(); - total0 = f0->total; - total1 = f1->total; - sumSq0 = f0->sumSq; - sumSq1 = f1->sumSq; - - const size_t zsz[2] = { size0, size1 }; - for( int k=0; k<2; k++ ) - { - if( m_compare.sortedNum[k] != zsz[k] ) - { - auto& frameSet = k == 0 ? f0 : f1; - auto worker = k == 0 ? &m_worker : m_compare.second.get(); - auto& vec = m_compare.sorted[k]; - vec.reserve( zsz[k] ); - int64_t total = m_compare.total[k]; - size_t i; - for( i=m_compare.sortedNum[k]; iGetFrameEnd( *frameSet, i ) == worker->GetLastTime() ) break; - const auto t = worker->GetFrameTime( *frameSet, i ); - vec.emplace_back( t ); - total += t; - } - auto mid = vec.begin() + m_compare.sortedNum[k]; - pdqsort_branchless( mid, vec.end() ); - std::inplace_merge( vec.begin(), mid, vec.end() ); - - m_compare.average[k] = float( total ) / i; - m_compare.median[k] = vec[i/2]; - m_compare.total[k] = total; - m_compare.sortedNum[k] = i; - } - } - } - - if( tmin != std::numeric_limits::max() ) - { - TextDisabledUnformatted( "Minimum values in bin:" ); - ImGui::SameLine(); - ImGui::SetNextItemWidth( ImGui::CalcTextSize( "123456890123456" ).x ); - ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 1, 1 ) ); - ImGui::InputInt( "##minBinVal", &m_compare.minBinVal ); - if( m_compare.minBinVal < 1 ) m_compare.minBinVal = 1; - ImGui::SameLine(); - if( ImGui::Button( "Reset" ) ) m_compare.minBinVal = 1; - ImGui::PopStyleVar(); - - SmallCheckbox( "Log values", &m_compare.logVal ); - ImGui::SameLine(); - SmallCheckbox( "Log time", &m_compare.logTime ); - ImGui::SameLine(); - SmallCheckbox( "Cumulate time", &m_compare.cumulateTime ); - ImGui::SameLine(); - DrawHelpMarker( "Show total time taken by calls in each bin instead of call counts." ); - ImGui::SameLine(); - SmallCheckbox( "Normalize values", &m_compare.normalize ); - ImGui::SameLine(); - DrawHelpMarker( "Normalization will fudge reported data values!" ); - - const auto cumulateTime = m_compare.cumulateTime; - - if( tmax - tmin > 0 ) - { - const auto w = ImGui::GetContentRegionAvail().x; - - const auto numBins = int64_t( w - 4 ); - if( numBins > 1 ) - { - if( numBins > m_compare.numBins ) - { - m_compare.numBins = numBins; - m_compare.bins = std::make_unique( numBins ); - m_compare.binTime = std::make_unique( numBins ); - } - - const auto& bins = m_compare.bins; - const auto& binTime = m_compare.binTime; - - memset( bins.get(), 0, sizeof( CompVal ) * numBins ); - memset( binTime.get(), 0, sizeof( CompVal ) * numBins ); - - double adj0 = 1; - double adj1 = 1; - if( m_compare.normalize ) - { - if( size0 > size1 ) - { - adj1 = double( size0 ) / size1; - } - else - { - adj0 = double( size1 ) / size0; - } - } - - const auto& sorted = m_compare.sorted; - auto sBegin0 = sorted[0].begin(); - auto sBegin1 = sorted[1].begin(); - auto sEnd0 = sorted[0].end(); - auto sEnd1 = sorted[1].end(); - - if( m_compare.minBinVal > 1 ) - { - if( m_compare.logTime ) - { - const auto tMinLog = log10( tmin ); - const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; - int64_t i; - for( i=0; i= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; - sBegin0 = nit0; - sBegin1 = nit1; - } - for( int64_t j=numBins-1; j>i; j-- ) - { - const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( j-1 ) * zmax ) ); - auto nit0 = std::lower_bound( sBegin0, sEnd0, nextBinVal ); - auto nit1 = std::lower_bound( sBegin1, sEnd1, nextBinVal ); - const auto distance0 = std::distance( nit0, sEnd0 ); - const auto distance1 = std::distance( nit1, sEnd1 ); - if( distance0 >= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; - sEnd0 = nit0; - sEnd1 = nit1; - } - } - else - { - const auto zmax = tmax - tmin; - int64_t i; - for( i=0; i= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; - sBegin0 = nit0; - sBegin1 = nit1; - } - for( int64_t j=numBins-1; j>i; j-- ) - { - const auto nextBinVal = tmin + ( j-1 ) * zmax / numBins; - auto nit0 = std::lower_bound( sBegin0, sEnd0, nextBinVal ); - auto nit1 = std::lower_bound( sBegin1, sEnd1, nextBinVal ); - const auto distance0 = std::distance( nit0, sEnd0 ); - const auto distance1 = std::distance( nit1, sEnd1 ); - if( distance0 >= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; - sEnd0 = nit0; - sEnd1 = nit1; - } - } - - tmin = std::min( *sBegin0, *sBegin1 ); - tmax = std::max( *(sEnd0-1), *(sEnd1-1) ); - } - - auto zit0 = sBegin0; - auto zit1 = sBegin1; - if( m_compare.logTime ) - { - const auto tMinLog = log10( tmin ); - const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; - for( int64_t i=0; i 1 ) - { - const auto sz = sorted[0].size(); - const auto avg = m_compare.average[0]; - const auto ss = sumSq0 - 2. * total0 * avg + avg * avg * sz; - const auto sd = sqrt( ss / ( sz - 1 ) ); - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - TextColoredUnformatted( ImVec4( 0xDD/511.f, 0xDD/511.f, 0x22/511.f, 1.f ), ICON_FA_LEMON ); - ImGui::SameLine(); - TextFocused( "\xcf\x83 (this):", TimeToString( sd ) ); - TooltipIfHovered( "Standard deviation" ); - } - - - TextColoredUnformatted( ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ), ICON_FA_GEM ); - ImGui::SameLine(); - TextFocused( "Mean time (ext.):", TimeToString( m_compare.average[1] ) ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - TextColoredUnformatted( ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ), ICON_FA_GEM ); - ImGui::SameLine(); - TextFocused( "Median time (ext.):", TimeToString( m_compare.median[1] ) ); - if( sorted[1].size() > 1 ) - { - const auto sz = sorted[1].size(); - const auto avg = m_compare.average[1]; - const auto ss = sumSq1 - 2. * total1 * avg + avg * avg * sz; - const auto sd = sqrt( ss / ( sz - 1 ) ); - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - TextColoredUnformatted( ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ), ICON_FA_GEM ); - ImGui::SameLine(); - TextFocused( "\xcf\x83 (ext.):", TimeToString( sd ) ); - TooltipIfHovered( "Standard deviation" ); - } - - ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0xDD/511.f, 0xDD/511.f, 0x22/511.f, 1.f ) ); - ImGui::PushStyleColor( ImGuiCol_Button, ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ) ); - ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ) ); - ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ) ); - ImGui::Button( ICON_FA_LEMON ); - ImGui::PopStyleColor( 4 ); - ImGui::SameLine(); - ImGui::TextUnformatted( "This trace" ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ) ); - ImGui::PushStyleColor( ImGuiCol_Button, ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ) ); - ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ) ); - ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ) ); - ImGui::Button( ICON_FA_GEM ); - ImGui::PopStyleColor( 4 ); - ImGui::SameLine(); - ImGui::TextUnformatted( "External trace" ); - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - ImGui::ColorButton( "c3", ImVec4( 0x44/255.f, 0xBB/255.f, 0xBB/255.f, 1.f ), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop ); - ImGui::SameLine(); - ImGui::TextUnformatted( "Overlap" ); - - const auto Height = 200 * scale; - const auto wpos = ImGui::GetCursorScreenPos(); - const auto dpos = wpos + ImVec2( 0.5f, 0.5f ); - - ImGui::InvisibleButton( "##histogram", ImVec2( w, Height + round( ty * 2.5 ) ) ); - const bool hover = ImGui::IsItemHovered(); - - auto draw = ImGui::GetWindowDrawList(); - draw->AddRectFilled( wpos, wpos + ImVec2( w, Height ), 0x22FFFFFF ); - draw->AddRect( wpos, wpos + ImVec2( w, Height ), 0x88FFFFFF ); - - if( m_compare.logVal ) - { - const auto hAdj = double( Height - 4 ) / log10( maxVal + 1 ); - for( int i=0; i 0 || val1 > 0 ) - { - const auto val = std::min( val0, val1 ); - if( val > 0 ) - { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), 0xFFBBBB44 ); - } - if( val1 == val ) - { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), dpos + ImVec2( 2+i, Height-3 - log10( val0 + 1 ) * hAdj ), 0xFF22DDDD ); - } - else - { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), dpos + ImVec2( 2+i, Height-3 - log10( val1 + 1 ) * hAdj ), 0xFF2222DD ); - } - } - } - } - else - { - const auto hAdj = double( Height - 4 ) / maxVal; - for( int i=0; i 0 || val1 > 0 ) - { - const auto val = std::min( val0, val1 ); - if( val > 0 ) - { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - val * hAdj ), 0xFFBBBB44 ); - } - if( val1 == val ) - { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - val * hAdj ), dpos + ImVec2( 2+i, Height-3 - val0 * hAdj ), 0xFF22DDDD ); - } - else - { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - val * hAdj ), dpos + ImVec2( 2+i, Height-3 - val1 * hAdj ), 0xFF2222DD ); - } - } - } - } - - const auto xoff = 2; - const auto yoff = Height + 1; - - DrawHistogramMinMaxLabel( draw, tmin, tmax, wpos + ImVec2( 0, yoff ), w, ty ); - - const auto ty05 = round( ty * 0.5f ); - const auto ty025 = round( ty * 0.25f ); - if( m_compare.logTime ) - { - const auto ltmin = log10( tmin ); - const auto ltmax = log10( tmax ); - const auto start = int( floor( ltmin ) ); - const auto end = int( ceil( ltmax ) ); - - const auto range = ltmax - ltmin; - const auto step = w / range; - auto offset = start - ltmin; - int tw = 0; - int tx = 0; - - auto tt = int64_t( pow( 10, start ) ); - - static const double logticks[] = { log10( 2 ), log10( 3 ), log10( 4 ), log10( 5 ), log10( 6 ), log10( 7 ), log10( 8 ), log10( 9 ) }; - - for( int i=start; i<=end; i++ ) - { - const auto x = ( i - start + offset ) * step; - - if( x >= 0 ) - { - DrawLine( draw, dpos + ImVec2( x, yoff ), dpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF ); - if( tw == 0 || x > tx + tw + ty * 1.1 ) - { - tx = x; - auto txt = TimeToString( tt ); - draw->AddText( wpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF, txt ); - tw = ImGui::CalcTextSize( txt ).x; - } - } - - for( int j=0; j<8; j++ ) - { - const auto xoff = x + logticks[j] * step; - if( xoff >= 0 ) - { - DrawLine( draw, dpos + ImVec2( xoff, yoff ), dpos + ImVec2( xoff, yoff + ty025 ), 0x66FFFFFF ); - } - } - - tt *= 10; - } - } - else - { - const auto pxns = numBins / double( tmax - tmin ); - const auto nspx = 1.0 / pxns; - const auto scale = std::max( 0.0f, round( log10( nspx ) + 2 ) ); - const auto step = pow( 10, scale ); - - const auto dx = step * pxns; - double x = 0; - int tw = 0; - int tx = 0; - - const auto sstep = step / 10.0; - const auto sdx = dx / 10.0; - - static const double linelen[] = { 0.5, 0.25, 0.25, 0.25, 0.25, 0.375, 0.25, 0.25, 0.25, 0.25 }; - - int64_t tt = int64_t( ceil( tmin / sstep ) * sstep ); - const auto diff = tmin / sstep - int64_t( tmin / sstep ); - const auto xo = ( diff == 0 ? 0 : ( ( 1 - diff ) * sstep * pxns ) ) + xoff; - int iter = int( ceil( ( tmin - int64_t( tmin / step ) * step ) / sstep ) ); - - while( x < numBins ) - { - DrawLine( draw, dpos + ImVec2( xo + x, yoff ), dpos + ImVec2( xo + x, yoff + round( ty * linelen[iter] ) ), 0x66FFFFFF ); - if( iter == 0 && ( tw == 0 || x > tx + tw + ty * 1.1 ) ) - { - tx = x; - auto txt = TimeToString( tt ); - draw->AddText( wpos + ImVec2( xo + x, yoff + ty05 ), 0x66FFFFFF, txt ); - tw = ImGui::CalcTextSize( txt ).x; - } - - iter = ( iter + 1 ) % 10; - x += sdx; - tt += sstep; - } - } - - if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( 2, 2 ), wpos + ImVec2( w-2, Height + round( ty * 1.5 ) ) ) ) - { - const auto ltmin = log10( tmin ); - const auto ltmax = log10( tmax ); - - auto& io = ImGui::GetIO(); - DrawLine( draw, ImVec2( io.MousePos.x + 0.5f, dpos.y ), ImVec2( io.MousePos.x + 0.5f, dpos.y+Height-2 ), 0x33FFFFFF ); - - const auto bin = int64_t( io.MousePos.x - wpos.x - 2 ); - int64_t t0, t1; - if( m_compare.logTime ) - { - t0 = int64_t( pow( 10, ltmin + double( bin ) / numBins * ( ltmax - ltmin ) ) ); - t1 = int64_t( pow( 10, ltmin + double( bin+1 ) / numBins * ( ltmax - ltmin ) ) ); - } - else - { - t0 = int64_t( tmin + double( bin ) / numBins * ( tmax - tmin ) ); - t1 = int64_t( tmin + double( bin+1 ) / numBins * ( tmax - tmin ) ); - } - - int64_t tBefore[2] = { 0, 0 }; - for( int i=0; iGetMatchingSourceLocation( m_compare.pattern, m_compare.ignoreCase ); - if( !m_compare.match[1].empty() ) - { - auto it = m_compare.match[1].begin(); - while( it != m_compare.match[1].end() ) - { - if( m_compare.second->GetZonesForSourceLocation( *it ).zones.empty() ) - { - it = m_compare.match[1].erase( it ); - } - else - { - ++it; - } - } - } -} -#endif - void View::SmallCallstackButton( const char* name, uint32_t callstack, int& idx, bool tooltip ) { bool hilite = m_callstackInfoWindow == callstack; diff --git a/server/TracyView_Compare.cpp b/server/TracyView_Compare.cpp new file mode 100644 index 00000000..3aa566b1 --- /dev/null +++ b/server/TracyView_Compare.cpp @@ -0,0 +1,1079 @@ +#include "../nfd/nfd.h" + +#include "TracyFileRead.hpp" +#include "TracyPrint.hpp" +#include "TracyView.hpp" + +namespace tracy +{ + +extern double s_time; + +#ifndef TRACY_NO_STATISTICS + void View::FindZonesCompare() + { + m_compare.match[0] = m_worker.GetMatchingSourceLocation( m_compare.pattern, m_compare.ignoreCase ); + if( !m_compare.match[0].empty() ) + { + auto it = m_compare.match[0].begin(); + while( it != m_compare.match[0].end() ) + { + if( m_worker.GetZonesForSourceLocation( *it ).zones.empty() ) + { + it = m_compare.match[0].erase( it ); + } + else + { + ++it; + } + } + } + + m_compare.match[1] = m_compare.second->GetMatchingSourceLocation( m_compare.pattern, m_compare.ignoreCase ); + if( !m_compare.match[1].empty() ) + { + auto it = m_compare.match[1].begin(); + while( it != m_compare.match[1].end() ) + { + if( m_compare.second->GetZonesForSourceLocation( *it ).zones.empty() ) + { + it = m_compare.match[1].erase( it ); + } + else + { + ++it; + } + } + } + } +#endif + +bool View::FindMatchingZone( int prev0, int prev1, int flags ) +{ + int idx = 0; + bool found = false; + auto& srcloc0 = m_worker.GetSourceLocation( m_compare.match[0][m_compare.selMatch[0]] ); + auto& srcloc1 = m_compare.second->GetSourceLocation( m_compare.match[1][m_compare.selMatch[1]] ); + auto string0 = m_worker.GetString( srcloc0.name.active ? srcloc0.name : srcloc0.function ); + auto string1 = m_compare.second->GetString( srcloc1.name.active ? srcloc1.name : srcloc1.function ); + auto file0 = m_worker.GetString( srcloc0.file ); + auto file1 = m_compare.second->GetString( srcloc1.file ); + bool wrongFile = false; + bool wrongLine = false; + if( flags & FindMatchingZoneFlagSourceFile ) + { + wrongFile = strcmp( file0, file1 ) != 0; + } + if( flags & FindMatchingZoneFlagLineNum ) + { + wrongLine = srcloc0.line != srcloc1.line; + } + + if( strcmp( string0, string1 ) != 0 || wrongFile || wrongLine ) + { + if( prev0 != m_compare.selMatch[0] ) + { + for( auto& v : m_compare.match[1] ) + { + auto& srcloc = m_compare.second->GetSourceLocation( v ); + auto string = m_compare.second->GetString( srcloc.name.active ? srcloc.name : srcloc.function ); + auto file = m_compare.second->GetString( srcloc.file ); + bool sameFile = true; + bool sameLine = true; + if( flags & FindMatchingZoneFlagSourceFile ) + { + sameFile = strcmp( file0, file ) == 0; + } + if( flags & FindMatchingZoneFlagLineNum ) + { + sameLine = srcloc0.line == srcloc.line; + } + if( strcmp( string0, string ) == 0 && sameFile && sameLine ) + { + m_compare.selMatch[1] = idx; + found = true; + break; + } + idx++; + } + } + else + { + assert( prev1 != m_compare.selMatch[1] ); + for( auto& v : m_compare.match[0] ) + { + auto& srcloc = m_worker.GetSourceLocation( v ); + auto string = m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ); + auto file = m_worker.GetString( srcloc.file ); + bool sameFile = true; + bool sameLine = true; + if( flags & FindMatchingZoneFlagSourceFile ) + { + sameFile = strcmp( file1, file ) == 0; + } + if( flags & FindMatchingZoneFlagLineNum ) + { + sameLine = srcloc1.line == srcloc.line; + } + if( strcmp( string1, string ) == 0 && sameFile && sameLine ) + { + m_compare.selMatch[0] = idx; + found = true; + break; + } + idx++; + } + + } + } + return found; +} + +void View::DrawCompare() +{ + const auto scale = GetScale(); + ImGui::SetNextWindowSize( ImVec2( 590 * scale, 800 * scale ), ImGuiCond_FirstUseEver ); + ImGui::Begin( "Compare traces", &m_compare.show, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); + if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; } +#ifdef TRACY_NO_STATISTICS + ImGui::TextWrapped( "Collection of statistical data is disabled in this build." ); + ImGui::TextWrapped( "Rebuild without the TRACY_NO_STATISTICS macro to enable trace comparison." ); +#elif defined TRACY_NO_FILESELECTOR + ImGui::TextWrapped( "File selector is disabled in this build." ); + ImGui::TextWrapped( "Rebuild without the TRACY_NO_FILESELECTOR macro to enable trace comparison." ); +#else + if( !m_compare.second ) + { + ImGui::TextWrapped( "Please load a second trace to compare results." ); + if( ImGui::Button( ICON_FA_FOLDER_OPEN " Open second trace" ) && !m_compare.loadThread.joinable() ) + { + nfdu8filteritem_t filter = { "Tracy Profiler trace file", "tracy" }; + nfdu8char_t* fn; + auto res = NFD_OpenDialogU8( &fn, &filter, 1, nullptr ); + if( res == NFD_OKAY ) + { + try + { + auto f = std::shared_ptr( tracy::FileRead::Open( fn ) ); + if( f ) + { + m_compare.loadThread = std::thread( [this, f] { + try + { + m_compare.second = std::make_unique( *f, EventType::None ); + m_compare.userData = std::make_unique( m_compare.second->GetCaptureProgram().c_str(), m_compare.second->GetCaptureTime() ); + } + catch( const tracy::UnsupportedVersion& e ) + { + m_compare.badVer.state = BadVersionState::UnsupportedVersion; + m_compare.badVer.version = e.version; + } + } ); + } + } + catch( const tracy::NotTracyDump& ) + { + m_compare.badVer.state = BadVersionState::BadFile; + } + catch( const tracy::FileReadError& ) + { + m_compare.badVer.state = BadVersionState::ReadError; + } + NFD_FreePathU8( fn ); + } + } + tracy::BadVersion( m_compare.badVer, m_bigFont ); + ImGui::End(); + return; + } + + if( m_compare.loadThread.joinable() ) m_compare.loadThread.join(); + + if( !m_worker.AreSourceLocationZonesReady() || !m_compare.second->AreSourceLocationZonesReady() ) + { + ImGui::TextWrapped( "Please wait, computing data..." ); + DrawWaitingDots( s_time ); + ImGui::End(); + return; + } + + TextColoredUnformatted( ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ), ICON_FA_LEMON ); + ImGui::SameLine(); + TextDisabledUnformatted( "This trace:" ); + ImGui::SameLine(); + const auto& desc0 = m_userData.GetDescription(); + if( desc0.empty() ) + { + ImGui::TextUnformatted( m_worker.GetCaptureName().c_str() ); + } + else + { + ImGui::TextUnformatted( desc0.c_str() ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%s)", m_worker.GetCaptureName().c_str() ); + } + + TextColoredUnformatted( ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ), ICON_FA_GEM ); + ImGui::SameLine(); + TextDisabledUnformatted( "External trace:" ); + ImGui::SameLine(); + const auto& desc1 = m_compare.userData->GetDescription(); + if( desc1.empty() ) + { + ImGui::TextUnformatted( m_compare.second->GetCaptureName().c_str() ); + } + else + { + ImGui::TextUnformatted( desc1.c_str() ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%s)", m_compare.second->GetCaptureName().c_str() ); + } + + if( ImGui::Button( ICON_FA_TRASH_ALT " Unload" ) ) + { + m_compare.Reset(); + m_compare.second.reset(); + m_compare.userData.reset(); + ImGui::End(); + return; + } + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + ImGui::Text( "Compare mode: " ); + ImGui::SameLine(); + const auto oldMode = m_compare.compareMode; + ImGui::RadioButton( "Zones", &m_compare.compareMode, 0 ); + ImGui::SameLine(); + ImGui::RadioButton( "Frames", &m_compare.compareMode, 1 ); + if( oldMode != m_compare.compareMode ) + { + m_compare.Reset(); + } + + bool findClicked = false; + + if( m_compare.compareMode == 0 ) + { + ImGui::PushItemWidth( -0.01f ); + findClicked |= ImGui::InputTextWithHint( "###compare", "Enter zone name to search for", m_compare.pattern, 1024, ImGuiInputTextFlags_EnterReturnsTrue ); + ImGui::PopItemWidth(); + + findClicked |= ImGui::Button( ICON_FA_SEARCH " Find" ); + ImGui::SameLine(); + + if( ImGui::Button( ICON_FA_BAN " Clear" ) ) + { + m_compare.Reset(); + } + ImGui::SameLine(); + ImGui::Checkbox( "Ignore case", &m_compare.ignoreCase ); + + if( findClicked ) + { + m_compare.Reset(); + FindZonesCompare(); + } + + if( m_compare.match[0].empty() && m_compare.match[1].empty() ) + { + ImGui::End(); + return; + } + + ImGui::Separator(); + ImGui::BeginChild( "##compare" ); + + if( ImGui::TreeNodeEx( "Matched source locations", ImGuiTreeNodeFlags_DefaultOpen ) ) + { + ImGui::SameLine(); + SmallCheckbox( "Link selection", &m_compare.link ); + + ImGui::Separator(); + ImGui::Columns( 2 ); + TextColoredUnformatted( ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ), ICON_FA_LEMON ); + ImGui::SameLine(); + ImGui::TextUnformatted( "This trace" ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%zu)", m_compare.match[0].size() ); + ImGui::NextColumn(); + TextColoredUnformatted( ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ), ICON_FA_GEM ); + ImGui::SameLine(); + ImGui::TextUnformatted( "External trace" ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%zu)", m_compare.match[1].size() ); + ImGui::Separator(); + ImGui::NextColumn(); + + const auto prev0 = m_compare.selMatch[0]; + int idx = 0; + for( auto& v : m_compare.match[0] ) + { + auto& srcloc = m_worker.GetSourceLocation( v ); + auto& zones = m_worker.GetZonesForSourceLocation( v ).zones; + SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); + ImGui::SameLine(); + ImGui::PushID( idx ); + ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); + ImGui::RadioButton( m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ), &m_compare.selMatch[0], idx++ ); + ImGui::PopStyleVar(); + ImGui::SameLine(); + ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s", RealToString( zones.size() ), LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); + ImGui::PopID(); + } + ImGui::NextColumn(); + + const auto prev1 = m_compare.selMatch[1]; + idx = 0; + for( auto& v : m_compare.match[1] ) + { + auto& srcloc = m_compare.second->GetSourceLocation( v ); + auto& zones = m_compare.second->GetZonesForSourceLocation( v ).zones; + ImGui::PushID( -1 - idx ); + ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); + ImGui::RadioButton( m_compare.second->GetString( srcloc.name.active ? srcloc.name : srcloc.function ), &m_compare.selMatch[1], idx++ ); + ImGui::PopStyleVar(); + ImGui::SameLine(); + ImGui::TextColored( ImVec4( 0.5, 0.5, 0.5, 1 ), "(%s) %s", RealToString( zones.size() ), LocationToString( m_compare.second->GetString( srcloc.file ), srcloc.line ) ); + ImGui::PopID(); + } + ImGui::NextColumn(); + ImGui::EndColumns(); + ImGui::TreePop(); + + if( prev0 != m_compare.selMatch[0] || prev1 != m_compare.selMatch[1] ) + { + m_compare.ResetSelection(); + + if( m_compare.link ) + { + if( !FindMatchingZone( prev0, prev1, FindMatchingZoneFlagSourceFile | FindMatchingZoneFlagLineNum ) ) + { + if( !FindMatchingZone( prev0, prev1, FindMatchingZoneFlagSourceFile ) ) + { + FindMatchingZone( prev0, prev1, FindMatchingZoneFlagDefault ); + } + } + } + } + } + + if( m_compare.match[0].empty() || m_compare.match[1].empty() ) + { + ImGui::Separator(); + ImGui::TextWrapped( "Both traces must have matches." ); + ImGui::End(); + return; + } + } + else + { + assert( m_compare.compareMode == 1 ); + + ImGui::Separator(); + ImGui::BeginChild( "##compare" ); + if( ImGui::TreeNodeEx( "Frame sets", ImGuiTreeNodeFlags_DefaultOpen ) ) + { + const auto& f0 = m_worker.GetFrames(); + const auto& f1 = m_compare.second->GetFrames(); + + ImGui::SameLine(); + SmallCheckbox( "Link selection", &m_compare.link ); + + ImGui::Separator(); + ImGui::Columns( 2 ); + TextColoredUnformatted( ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ), ICON_FA_LEMON ); + ImGui::SameLine(); + ImGui::TextUnformatted( "This trace" ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%zu)", f0.size() ); + ImGui::NextColumn(); + TextColoredUnformatted( ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ), ICON_FA_GEM ); + ImGui::SameLine(); + ImGui::TextUnformatted( "External trace" ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%zu)", f1.size() ); + ImGui::Separator(); + ImGui::NextColumn(); + + const auto prev0 = m_compare.selMatch[0]; + int idx = 0; + for( auto& v : f0 ) + { + const auto name = m_worker.GetString( v->name ); + ImGui::PushID( -1 - idx ); + ImGui::RadioButton( name, &m_compare.selMatch[0], idx++ ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%s)", RealToString( v->frames.size() ) ); + ImGui::PopID(); + } + ImGui::NextColumn(); + + const auto prev1 = m_compare.selMatch[1]; + idx = 0; + for( auto& v : f1 ) + { + const auto name = m_compare.second->GetString( v->name ); + ImGui::PushID( idx ); + ImGui::RadioButton( name, &m_compare.selMatch[1], idx++ ); + ImGui::SameLine(); + ImGui::TextDisabled( "(%s)", RealToString( v->frames.size() ) ); + ImGui::PopID(); + } + ImGui::NextColumn(); + ImGui::EndColumns(); + ImGui::TreePop(); + + if( prev0 != m_compare.selMatch[0] || prev1 != m_compare.selMatch[1] ) + { + m_compare.ResetSelection(); + + if( m_compare.link ) + { + auto string0 = m_worker.GetString( f0[m_compare.selMatch[0]]->name ); + auto string1 = m_compare.second->GetString( f1[m_compare.selMatch[1]]->name ); + + if( strcmp( string0, string1 ) != 0 ) + { + idx = 0; + if( prev0 != m_compare.selMatch[0] ) + { + for( auto& v : f1 ) + { + auto string = m_compare.second->GetString( v->name ); + if( strcmp( string0, string ) == 0 ) + { + m_compare.selMatch[1] = idx; + break; + } + idx++; + } + } + else + { + assert( prev1 != m_compare.selMatch[1] ); + for( auto& v : f0 ) + { + auto string = m_worker.GetString( v->name ); + if( strcmp( string1, string ) == 0 ) + { + m_compare.selMatch[0] = idx; + break; + } + idx++; + } + } + } + } + } + } + } + + ImGui::Separator(); + if( ImGui::TreeNodeEx( "Histogram", ImGuiTreeNodeFlags_DefaultOpen ) ) + { + const auto ty = ImGui::GetTextLineHeight(); + + int64_t tmin, tmax; + size_t size0, size1; + int64_t total0, total1; + double sumSq0, sumSq1; + + if( m_compare.compareMode == 0 ) + { + auto& zoneData0 = m_worker.GetZonesForSourceLocation( m_compare.match[0][m_compare.selMatch[0]] ); + auto& zoneData1 = m_compare.second->GetZonesForSourceLocation( m_compare.match[1][m_compare.selMatch[1]] ); + auto& zones0 = zoneData0.zones; + auto& zones1 = zoneData1.zones; + zones0.ensure_sorted(); + zones1.ensure_sorted(); + + tmin = std::min( zoneData0.min, zoneData1.min ); + tmax = std::max( zoneData0.max, zoneData1.max ); + + size0 = zones0.size(); + size1 = zones1.size(); + total0 = zoneData0.total; + total1 = zoneData1.total; + sumSq0 = zoneData0.sumSq; + sumSq1 = zoneData1.sumSq; + + const size_t zsz[2] = { size0, size1 }; + for( int k=0; k<2; k++ ) + { + if( m_compare.sortedNum[k] != zsz[k] ) + { + auto& zones = k == 0 ? zones0 : zones1; + auto& vec = m_compare.sorted[k]; + vec.reserve( zsz[k] ); + int64_t total = m_compare.total[k]; + size_t i; + for( i=m_compare.sortedNum[k]; iGetFrames()[m_compare.selMatch[1]]; + + tmin = std::min( f0->min, f1->min ); + tmax = std::max( f0->max, f1->max ); + + size0 = f0->frames.size(); + size1 = f1->frames.size(); + total0 = f0->total; + total1 = f1->total; + sumSq0 = f0->sumSq; + sumSq1 = f1->sumSq; + + const size_t zsz[2] = { size0, size1 }; + for( int k=0; k<2; k++ ) + { + if( m_compare.sortedNum[k] != zsz[k] ) + { + auto& frameSet = k == 0 ? f0 : f1; + auto worker = k == 0 ? &m_worker : m_compare.second.get(); + auto& vec = m_compare.sorted[k]; + vec.reserve( zsz[k] ); + int64_t total = m_compare.total[k]; + size_t i; + for( i=m_compare.sortedNum[k]; iGetFrameEnd( *frameSet, i ) == worker->GetLastTime() ) break; + const auto t = worker->GetFrameTime( *frameSet, i ); + vec.emplace_back( t ); + total += t; + } + auto mid = vec.begin() + m_compare.sortedNum[k]; + pdqsort_branchless( mid, vec.end() ); + std::inplace_merge( vec.begin(), mid, vec.end() ); + + m_compare.average[k] = float( total ) / i; + m_compare.median[k] = vec[i/2]; + m_compare.total[k] = total; + m_compare.sortedNum[k] = i; + } + } + } + + if( tmin != std::numeric_limits::max() ) + { + TextDisabledUnformatted( "Minimum values in bin:" ); + ImGui::SameLine(); + ImGui::SetNextItemWidth( ImGui::CalcTextSize( "123456890123456" ).x ); + ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 1, 1 ) ); + ImGui::InputInt( "##minBinVal", &m_compare.minBinVal ); + if( m_compare.minBinVal < 1 ) m_compare.minBinVal = 1; + ImGui::SameLine(); + if( ImGui::Button( "Reset" ) ) m_compare.minBinVal = 1; + ImGui::PopStyleVar(); + + SmallCheckbox( "Log values", &m_compare.logVal ); + ImGui::SameLine(); + SmallCheckbox( "Log time", &m_compare.logTime ); + ImGui::SameLine(); + SmallCheckbox( "Cumulate time", &m_compare.cumulateTime ); + ImGui::SameLine(); + DrawHelpMarker( "Show total time taken by calls in each bin instead of call counts." ); + ImGui::SameLine(); + SmallCheckbox( "Normalize values", &m_compare.normalize ); + ImGui::SameLine(); + DrawHelpMarker( "Normalization will fudge reported data values!" ); + + const auto cumulateTime = m_compare.cumulateTime; + + if( tmax - tmin > 0 ) + { + const auto w = ImGui::GetContentRegionAvail().x; + + const auto numBins = int64_t( w - 4 ); + if( numBins > 1 ) + { + if( numBins > m_compare.numBins ) + { + m_compare.numBins = numBins; + m_compare.bins = std::make_unique( numBins ); + m_compare.binTime = std::make_unique( numBins ); + } + + const auto& bins = m_compare.bins; + const auto& binTime = m_compare.binTime; + + memset( bins.get(), 0, sizeof( CompVal ) * numBins ); + memset( binTime.get(), 0, sizeof( CompVal ) * numBins ); + + double adj0 = 1; + double adj1 = 1; + if( m_compare.normalize ) + { + if( size0 > size1 ) + { + adj1 = double( size0 ) / size1; + } + else + { + adj0 = double( size1 ) / size0; + } + } + + const auto& sorted = m_compare.sorted; + auto sBegin0 = sorted[0].begin(); + auto sBegin1 = sorted[1].begin(); + auto sEnd0 = sorted[0].end(); + auto sEnd1 = sorted[1].end(); + + if( m_compare.minBinVal > 1 ) + { + if( m_compare.logTime ) + { + const auto tMinLog = log10( tmin ); + const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + int64_t i; + for( i=0; i= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; + sBegin0 = nit0; + sBegin1 = nit1; + } + for( int64_t j=numBins-1; j>i; j-- ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( j-1 ) * zmax ) ); + auto nit0 = std::lower_bound( sBegin0, sEnd0, nextBinVal ); + auto nit1 = std::lower_bound( sBegin1, sEnd1, nextBinVal ); + const auto distance0 = std::distance( nit0, sEnd0 ); + const auto distance1 = std::distance( nit1, sEnd1 ); + if( distance0 >= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; + sEnd0 = nit0; + sEnd1 = nit1; + } + } + else + { + const auto zmax = tmax - tmin; + int64_t i; + for( i=0; i= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; + sBegin0 = nit0; + sBegin1 = nit1; + } + for( int64_t j=numBins-1; j>i; j-- ) + { + const auto nextBinVal = tmin + ( j-1 ) * zmax / numBins; + auto nit0 = std::lower_bound( sBegin0, sEnd0, nextBinVal ); + auto nit1 = std::lower_bound( sBegin1, sEnd1, nextBinVal ); + const auto distance0 = std::distance( nit0, sEnd0 ); + const auto distance1 = std::distance( nit1, sEnd1 ); + if( distance0 >= m_compare.minBinVal || distance1 >= m_compare.minBinVal ) break; + sEnd0 = nit0; + sEnd1 = nit1; + } + } + + tmin = std::min( *sBegin0, *sBegin1 ); + tmax = std::max( *(sEnd0-1), *(sEnd1-1) ); + } + + auto zit0 = sBegin0; + auto zit1 = sBegin1; + if( m_compare.logTime ) + { + const auto tMinLog = log10( tmin ); + const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + for( int64_t i=0; i 1 ) + { + const auto sz = sorted[0].size(); + const auto avg = m_compare.average[0]; + const auto ss = sumSq0 - 2. * total0 * avg + avg * avg * sz; + const auto sd = sqrt( ss / ( sz - 1 ) ); + + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + TextColoredUnformatted( ImVec4( 0xDD/511.f, 0xDD/511.f, 0x22/511.f, 1.f ), ICON_FA_LEMON ); + ImGui::SameLine(); + TextFocused( "\xcf\x83 (this):", TimeToString( sd ) ); + TooltipIfHovered( "Standard deviation" ); + } + + + TextColoredUnformatted( ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ), ICON_FA_GEM ); + ImGui::SameLine(); + TextFocused( "Mean time (ext.):", TimeToString( m_compare.average[1] ) ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + TextColoredUnformatted( ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ), ICON_FA_GEM ); + ImGui::SameLine(); + TextFocused( "Median time (ext.):", TimeToString( m_compare.median[1] ) ); + if( sorted[1].size() > 1 ) + { + const auto sz = sorted[1].size(); + const auto avg = m_compare.average[1]; + const auto ss = sumSq1 - 2. * total1 * avg + avg * avg * sz; + const auto sd = sqrt( ss / ( sz - 1 ) ); + + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + TextColoredUnformatted( ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ), ICON_FA_GEM ); + ImGui::SameLine(); + TextFocused( "\xcf\x83 (ext.):", TimeToString( sd ) ); + TooltipIfHovered( "Standard deviation" ); + } + + ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0xDD/511.f, 0xDD/511.f, 0x22/511.f, 1.f ) ); + ImGui::PushStyleColor( ImGuiCol_Button, ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ) ); + ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ) ); + ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImVec4( 0xDD/255.f, 0xDD/255.f, 0x22/255.f, 1.f ) ); + ImGui::Button( ICON_FA_LEMON ); + ImGui::PopStyleColor( 4 ); + ImGui::SameLine(); + ImGui::TextUnformatted( "This trace" ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + + ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0xDD/511.f, 0x22/511.f, 0x22/511.f, 1.f ) ); + ImGui::PushStyleColor( ImGuiCol_Button, ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ) ); + ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ) ); + ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImVec4( 0xDD/255.f, 0x22/255.f, 0x22/255.f, 1.f ) ); + ImGui::Button( ICON_FA_GEM ); + ImGui::PopStyleColor( 4 ); + ImGui::SameLine(); + ImGui::TextUnformatted( "External trace" ); + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + + ImGui::ColorButton( "c3", ImVec4( 0x44/255.f, 0xBB/255.f, 0xBB/255.f, 1.f ), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop ); + ImGui::SameLine(); + ImGui::TextUnformatted( "Overlap" ); + + const auto Height = 200 * scale; + const auto wpos = ImGui::GetCursorScreenPos(); + const auto dpos = wpos + ImVec2( 0.5f, 0.5f ); + + ImGui::InvisibleButton( "##histogram", ImVec2( w, Height + round( ty * 2.5 ) ) ); + const bool hover = ImGui::IsItemHovered(); + + auto draw = ImGui::GetWindowDrawList(); + draw->AddRectFilled( wpos, wpos + ImVec2( w, Height ), 0x22FFFFFF ); + draw->AddRect( wpos, wpos + ImVec2( w, Height ), 0x88FFFFFF ); + + if( m_compare.logVal ) + { + const auto hAdj = double( Height - 4 ) / log10( maxVal + 1 ); + for( int i=0; i 0 || val1 > 0 ) + { + const auto val = std::min( val0, val1 ); + if( val > 0 ) + { + DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), 0xFFBBBB44 ); + } + if( val1 == val ) + { + DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), dpos + ImVec2( 2+i, Height-3 - log10( val0 + 1 ) * hAdj ), 0xFF22DDDD ); + } + else + { + DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), dpos + ImVec2( 2+i, Height-3 - log10( val1 + 1 ) * hAdj ), 0xFF2222DD ); + } + } + } + } + else + { + const auto hAdj = double( Height - 4 ) / maxVal; + for( int i=0; i 0 || val1 > 0 ) + { + const auto val = std::min( val0, val1 ); + if( val > 0 ) + { + DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - val * hAdj ), 0xFFBBBB44 ); + } + if( val1 == val ) + { + DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - val * hAdj ), dpos + ImVec2( 2+i, Height-3 - val0 * hAdj ), 0xFF22DDDD ); + } + else + { + DrawLine( draw, dpos + ImVec2( 2+i, Height-3 - val * hAdj ), dpos + ImVec2( 2+i, Height-3 - val1 * hAdj ), 0xFF2222DD ); + } + } + } + } + + const auto xoff = 2; + const auto yoff = Height + 1; + + DrawHistogramMinMaxLabel( draw, tmin, tmax, wpos + ImVec2( 0, yoff ), w, ty ); + + const auto ty05 = round( ty * 0.5f ); + const auto ty025 = round( ty * 0.25f ); + if( m_compare.logTime ) + { + const auto ltmin = log10( tmin ); + const auto ltmax = log10( tmax ); + const auto start = int( floor( ltmin ) ); + const auto end = int( ceil( ltmax ) ); + + const auto range = ltmax - ltmin; + const auto step = w / range; + auto offset = start - ltmin; + int tw = 0; + int tx = 0; + + auto tt = int64_t( pow( 10, start ) ); + + static const double logticks[] = { log10( 2 ), log10( 3 ), log10( 4 ), log10( 5 ), log10( 6 ), log10( 7 ), log10( 8 ), log10( 9 ) }; + + for( int i=start; i<=end; i++ ) + { + const auto x = ( i - start + offset ) * step; + + if( x >= 0 ) + { + DrawLine( draw, dpos + ImVec2( x, yoff ), dpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF ); + if( tw == 0 || x > tx + tw + ty * 1.1 ) + { + tx = x; + auto txt = TimeToString( tt ); + draw->AddText( wpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF, txt ); + tw = ImGui::CalcTextSize( txt ).x; + } + } + + for( int j=0; j<8; j++ ) + { + const auto xoff = x + logticks[j] * step; + if( xoff >= 0 ) + { + DrawLine( draw, dpos + ImVec2( xoff, yoff ), dpos + ImVec2( xoff, yoff + ty025 ), 0x66FFFFFF ); + } + } + + tt *= 10; + } + } + else + { + const auto pxns = numBins / double( tmax - tmin ); + const auto nspx = 1.0 / pxns; + const auto scale = std::max( 0.0f, round( log10( nspx ) + 2 ) ); + const auto step = pow( 10, scale ); + + const auto dx = step * pxns; + double x = 0; + int tw = 0; + int tx = 0; + + const auto sstep = step / 10.0; + const auto sdx = dx / 10.0; + + static const double linelen[] = { 0.5, 0.25, 0.25, 0.25, 0.25, 0.375, 0.25, 0.25, 0.25, 0.25 }; + + int64_t tt = int64_t( ceil( tmin / sstep ) * sstep ); + const auto diff = tmin / sstep - int64_t( tmin / sstep ); + const auto xo = ( diff == 0 ? 0 : ( ( 1 - diff ) * sstep * pxns ) ) + xoff; + int iter = int( ceil( ( tmin - int64_t( tmin / step ) * step ) / sstep ) ); + + while( x < numBins ) + { + DrawLine( draw, dpos + ImVec2( xo + x, yoff ), dpos + ImVec2( xo + x, yoff + round( ty * linelen[iter] ) ), 0x66FFFFFF ); + if( iter == 0 && ( tw == 0 || x > tx + tw + ty * 1.1 ) ) + { + tx = x; + auto txt = TimeToString( tt ); + draw->AddText( wpos + ImVec2( xo + x, yoff + ty05 ), 0x66FFFFFF, txt ); + tw = ImGui::CalcTextSize( txt ).x; + } + + iter = ( iter + 1 ) % 10; + x += sdx; + tt += sstep; + } + } + + if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( 2, 2 ), wpos + ImVec2( w-2, Height + round( ty * 1.5 ) ) ) ) + { + const auto ltmin = log10( tmin ); + const auto ltmax = log10( tmax ); + + auto& io = ImGui::GetIO(); + DrawLine( draw, ImVec2( io.MousePos.x + 0.5f, dpos.y ), ImVec2( io.MousePos.x + 0.5f, dpos.y+Height-2 ), 0x33FFFFFF ); + + const auto bin = int64_t( io.MousePos.x - wpos.x - 2 ); + int64_t t0, t1; + if( m_compare.logTime ) + { + t0 = int64_t( pow( 10, ltmin + double( bin ) / numBins * ( ltmax - ltmin ) ) ); + t1 = int64_t( pow( 10, ltmin + double( bin+1 ) / numBins * ( ltmax - ltmin ) ) ); + } + else + { + t0 = int64_t( tmin + double( bin ) / numBins * ( tmax - tmin ) ); + t1 = int64_t( tmin + double( bin+1 ) / numBins * ( tmax - tmin ) ); + } + + int64_t tBefore[2] = { 0, 0 }; + for( int i=0; i