#include #include #include "TracyImGui.hpp" #include "TracyMouse.hpp" #include "TracyPrint.hpp" #include "TracyTimelineContext.hpp" #include "TracyTimelineItemThread.hpp" #include "TracyView.hpp" #include "TracyWorker.hpp" namespace tracy { constexpr float MinVisSize = 3; TimelineItemThread::TimelineItemThread( View& view, Worker& worker, const ThreadData* thread ) : TimelineItem( view, worker, thread, true ) , m_thread( thread ) , m_ghost( false ) { auto name = worker.GetThreadName( thread->id ); if( strncmp( name, "Tracy ", 6 ) == 0 ) { m_showFull = false; } } bool TimelineItemThread::IsEmpty() const { auto& crash = m_worker.GetCrashEvent(); return crash.thread != m_thread->id && m_thread->timeline.empty() && m_thread->messages.empty() && m_thread->ghostZones.empty(); } uint32_t TimelineItemThread::HeaderColor() const { auto& crash = m_worker.GetCrashEvent(); if( crash.thread == m_thread->id ) return 0xFF2222FF; if( m_thread->isFiber ) return 0xFF88FF88; return 0xFFFFFFFF; } uint32_t TimelineItemThread::HeaderColorInactive() const { auto& crash = m_worker.GetCrashEvent(); if( crash.thread == m_thread->id ) return 0xFF111188; if( m_thread->isFiber ) return 0xFF448844; return 0xFF888888; } uint32_t TimelineItemThread::HeaderLineColor() const { return 0x33FFFFFF; } const char* TimelineItemThread::HeaderLabel() const { return m_worker.GetThreadName( m_thread->id ); } int64_t TimelineItemThread::RangeBegin() const { int64_t first = std::numeric_limits::max(); const auto ctx = m_worker.GetContextSwitchData( m_thread->id ); if( ctx && !ctx->v.empty() ) { first = ctx->v.begin()->Start(); } if( !m_thread->timeline.empty() ) { if( m_thread->timeline.is_magic() ) { auto& tl = *((Vector*)&m_thread->timeline); first = std::min( first, tl.front().Start() ); } else { first = std::min( first, m_thread->timeline.front()->Start() ); } } if( !m_thread->messages.empty() ) { first = std::min( first, m_thread->messages.front()->time ); } for( const auto& lock : m_worker.GetLockMap() ) { const auto& lockmap = *lock.second; if( !lockmap.valid ) continue; auto it = lockmap.threadMap.find( m_thread->id ); if( it == lockmap.threadMap.end() ) continue; const auto thread = it->second; auto lptr = lockmap.timeline.data(); while( lptr->ptr->thread != thread ) lptr++; if( lptr->ptr->Time() < first ) first = lptr->ptr->Time(); } return first; } int64_t TimelineItemThread::RangeEnd() const { int64_t last = -1; const auto ctx = m_worker.GetContextSwitchData( m_thread->id ); if( ctx && !ctx->v.empty() ) { const auto& back = ctx->v.back(); last = back.IsEndValid() ? back.End() : back.Start(); } if( !m_thread->timeline.empty() ) { if( m_thread->timeline.is_magic() ) { auto& tl = *((Vector*)&m_thread->timeline); last = std::max( last, m_worker.GetZoneEnd( tl.back() ) ); } else { last = std::max( last, m_worker.GetZoneEnd( *m_thread->timeline.back() ) ); } } if( !m_thread->messages.empty() ) { last = std::max( last, m_thread->messages.back()->time ); } for( const auto& lock : m_worker.GetLockMap() ) { const auto& lockmap = *lock.second; if( !lockmap.valid ) continue; auto it = lockmap.threadMap.find( m_thread->id ); if( it == lockmap.threadMap.end() ) continue; const auto thread = it->second; auto eptr = lockmap.timeline.data() + lockmap.timeline.size() - 1; while( eptr->ptr->thread != thread ) eptr--; if( eptr->ptr->Time() > last ) last = eptr->ptr->Time(); } return last; } void TimelineItemThread::HeaderTooltip( const char* label ) const { m_view.HighlightThread( m_thread->id ); ImGui::BeginTooltip(); SmallColorBox( GetThreadColor( m_thread->id, 0, m_view.GetViewData().dynamicColors ) ); ImGui::SameLine(); ImGui::TextUnformatted( m_worker.GetThreadName( m_thread->id ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( m_thread->id ) ); auto& crash = m_worker.GetCrashEvent(); if( crash.thread == m_thread->id ) { ImGui::SameLine(); TextColoredUnformatted( ImVec4( 1.f, 0.2f, 0.2f, 1.f ), ICON_FA_SKULL " Crashed" ); } if( m_thread->isFiber ) { ImGui::SameLine(); TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" ); } const auto ctx = m_worker.GetContextSwitchData( m_thread->id ); const auto first = RangeBegin(); const auto last = RangeEnd(); ImGui::Separator(); size_t lockCnt = 0; for( const auto& lock : m_worker.GetLockMap() ) { const auto& lockmap = *lock.second; if( !lockmap.valid ) continue; auto it = lockmap.threadMap.find( m_thread->id ); if( it == lockmap.threadMap.end() ) continue; lockCnt++; } if( last >= 0 ) { const auto lifetime = last - first; const auto traceLen = m_worker.GetLastTime() - m_worker.GetFirstTime(); TextFocused( "Appeared at", TimeToString( first ) ); TextFocused( "Last event at", TimeToString( last ) ); TextFocused( "Lifetime:", TimeToString( lifetime ) ); ImGui::SameLine(); char buf[64]; PrintStringPercent( buf, lifetime / double( traceLen ) * 100 ); TextDisabledUnformatted( buf ); if( ctx ) { TextFocused( "Time in running state:", TimeToString( ctx->runningTime ) ); ImGui::SameLine(); PrintStringPercent( buf, ctx->runningTime / double( lifetime ) * 100 ); TextDisabledUnformatted( buf ); } } ImGui::Separator(); if( !m_thread->timeline.empty() ) { TextFocused( "Zone count:", RealToString( m_thread->count ) ); TextFocused( "Top-level zones:", RealToString( m_thread->timeline.size() ) ); } if( !m_thread->messages.empty() ) { TextFocused( "Messages:", RealToString( m_thread->messages.size() ) ); } if( lockCnt != 0 ) { TextFocused( "Locks:", RealToString( lockCnt ) ); } if( ctx ) { TextFocused( "Running state regions:", RealToString( ctx->v.size() ) ); } if( !m_thread->samples.empty() ) { TextFocused( "Call stack samples:", RealToString( m_thread->samples.size() ) ); if( m_thread->kernelSampleCnt != 0 ) { TextFocused( "Kernel samples:", RealToString( m_thread->kernelSampleCnt ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%.2f%%)", 100.f * m_thread->kernelSampleCnt / m_thread->samples.size() ); } } ImGui::EndTooltip(); } void TimelineItemThread::HeaderExtraContents( const TimelineContext& ctx, int offset, float labelWidth ) { m_view.DrawThreadMessages( ctx, *m_thread, offset ); #ifndef TRACY_NO_STATISTICS const bool hasGhostZones = m_worker.AreGhostZonesReady() && !m_thread->ghostZones.empty(); if( hasGhostZones && !m_thread->timeline.empty() ) { auto draw = ImGui::GetWindowDrawList(); const auto ty = ImGui::GetTextLineHeight(); const auto color = m_ghost ? 0xFFAA9999 : 0x88AA7777; draw->AddText( ctx.wpos + ImVec2( 1.5f * ty + labelWidth, offset ), color, ICON_FA_GHOST ); float ghostSz = ImGui::CalcTextSize( ICON_FA_GHOST ).x; if( ctx.hover && ImGui::IsMouseHoveringRect( ctx.wpos + ImVec2( 1.5f * ty + labelWidth, offset ), ctx.wpos + ImVec2( 1.5f * ty + labelWidth + ghostSz, offset + ty ) ) ) { if( IsMouseClicked( 0 ) ) { m_ghost = !m_ghost; } } } #endif } bool TimelineItemThread::DrawContents( const TimelineContext& ctx, int& offset ) { const auto res = m_view.DrawThread( ctx, *m_thread, offset, m_ghost ); if( !res ) { auto& crash = m_worker.GetCrashEvent(); return crash.thread == m_thread->id; } return true; } void TimelineItemThread::DrawOverlay( const ImVec2& ul, const ImVec2& dr ) { m_view.DrawThreadOverlays( *m_thread, ul, dr ); } void TimelineItemThread::DrawFinished() { m_draw.clear(); } void TimelineItemThread::Preprocess( const TimelineContext& ctx ) { assert( m_draw.empty() ); #ifndef TRACY_NO_STATISTICS if( m_worker.AreGhostZonesReady() && ( m_ghost || ( m_view.GetViewData().ghostZones && m_thread->timeline.empty() ) ) ) { m_depth = PreprocessGhostLevel( ctx, m_thread->ghostZones, 0 ); } else #endif { m_depth = PreprocessZoneLevel( ctx, m_thread->timeline, 0 ); } } #ifndef TRACY_NO_STATISTICS int TimelineItemThread::PreprocessGhostLevel( const TimelineContext& ctx, const Vector& vec, int depth ) { const auto pxns = ctx.pxns; const auto nspx = ctx.nspx; const auto vStart = ctx.vStart; const auto vEnd = ctx.vEnd; auto it = std::lower_bound( vec.begin(), vec.end(), std::max( 0, vStart - 2 * MinVisSize * nspx ), [] ( const auto& l, const auto& r ) { return l.end.Val() < r; } ); if( it == vec.end() ) return depth; const auto zitend = std::lower_bound( it, vec.end(), vEnd, [] ( const auto& l, const auto& r ) { return l.start.Val() < r; } ); if( it == zitend ) return depth; if( (zitend-1)->end.Val() < vStart ) return depth; int maxdepth = depth + 1; while( it < zitend ) { auto& ev = *it; const auto end = ev.end.Val(); const auto zsz = std::max( ( end - ev.start.Val() ) * pxns, pxns * 0.5 ); if( zsz < MinVisSize ) { const auto MinVisNs = MinVisSize * nspx; auto px1ns = ev.end.Val() - vStart; auto rend = end; auto nextTime = end + MinVisNs; for(;;) { const auto prevIt = it; it = std::lower_bound( it, zitend, nextTime, [] ( const auto& l, const auto& r ) { return l.end.Val() < r; } ); if( it == prevIt ) ++it; if( it == zitend ) break; const auto nend = it->end.Val(); const auto nsnext = nend - vStart; if( nsnext - px1ns >= MinVisNs * 2 ) break; px1ns = nsnext; rend = nend; nextTime = nend + nspx; } m_draw.emplace_back( TimelineDraw { TimelineDrawType::GhostFolded, uint16_t( depth ), (void**)&ev, rend } ); } else { if( ev.child >= 0 ) { const auto d = PreprocessGhostLevel( ctx, m_worker.GetGhostChildren( ev.child ), depth + 1 ); if( d > maxdepth ) maxdepth = d; } m_draw.emplace_back( TimelineDraw { TimelineDrawType::Ghost, uint16_t( depth ), (void**)&ev } ); ++it; } } return maxdepth; } #endif int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const Vector>& vec, int depth ) { if( vec.is_magic() ) { return PreprocessZoneLevel>( ctx, *(Vector*)( &vec ), depth ); } else { return PreprocessZoneLevel>( ctx, vec, depth ); } } template int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const V& vec, int depth ) { const auto delay = m_worker.GetDelay(); const auto resolution = m_worker.GetResolution(); const auto vStart = ctx.vStart; const auto vEnd = ctx.vEnd; const auto nspx = ctx.nspx; const auto pxns = ctx.pxns; // cast to uint64_t, so that unended zones (end = -1) are still drawn auto it = std::lower_bound( vec.begin(), vec.end(), std::max( 0, vStart - std::max( delay, 2 * MinVisSize * nspx ) ), [] ( const auto& l, const auto& r ) { Adapter a; return (uint64_t)a(l).End() < (uint64_t)r; } ); if( it == vec.end() ) return depth; const auto zitend = std::lower_bound( it, vec.end(), vEnd + resolution, [] ( const auto& l, const auto& r ) { Adapter a; return a(l).Start() < r; } ); if( it == zitend ) return depth; Adapter a; if( !a(*it).IsEndValid() && m_worker.GetZoneEnd( a(*it) ) < vStart ) return depth; if( m_worker.GetZoneEnd( a(*(zitend-1)) ) < vStart ) return depth; int maxdepth = depth + 1; while( it < zitend ) { auto& ev = a(*it); const auto end = m_worker.GetZoneEnd( ev ); const auto zsz = std::max( ( end - ev.Start() ) * pxns, pxns * 0.5 ); if( zsz < MinVisSize ) { const auto MinVisNs = MinVisSize * nspx; int num = 0; auto px1ns = end - vStart; auto rend = end; auto nextTime = end + MinVisNs; for(;;) { const auto prevIt = it; it = std::lower_bound( it, zitend, nextTime, [] ( const auto& l, const auto& r ) { Adapter a; return (uint64_t)a(l).End() < (uint64_t)r; } ); if( it == prevIt ) ++it; num += std::distance( prevIt, it ); if( it == zitend ) break; const auto nend = m_worker.GetZoneEnd( a(*it) ); const auto nsnext = nend - vStart; if( nsnext - px1ns >= MinVisNs * 2 ) break; px1ns = nsnext; rend = nend; nextTime = nend + nspx; } m_draw.emplace_back( TimelineDraw { TimelineDrawType::Folded, uint16_t( depth ), (void**)&ev, rend, num } ); } else { if( ev.HasChildren() ) { const auto d = PreprocessZoneLevel( ctx, m_worker.GetZoneChildren( ev.Child() ), depth + 1 ); if( d > maxdepth ) maxdepth = d; } m_draw.emplace_back( TimelineDraw { TimelineDrawType::Zone, uint16_t( depth ), (void**)&ev } ); ++it; } } return maxdepth; } }