diff --git a/profiler/src/profiler/TracyLockHelpers.hpp b/profiler/src/profiler/TracyLockHelpers.hpp deleted file mode 100644 index 2aa5911a..00000000 --- a/profiler/src/profiler/TracyLockHelpers.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef __TRACYLOCKHELPERS_HPP__ -#define __TRACYLOCKHELPERS_HPP__ - -#include - -#include "../public/common/TracyForceInline.hpp" - -namespace tracy -{ - -static tracy_force_inline uint64_t GetThreadBit( uint8_t thread ) -{ - return uint64_t( 1 ) << thread; -} - -static tracy_force_inline bool IsThreadWaiting( uint64_t bitlist, uint64_t threadBit ) -{ - return ( bitlist & threadBit ) != 0; -} - -static tracy_force_inline bool AreOtherWaiting( uint64_t bitlist, uint64_t threadBit ) -{ - return ( bitlist & ~threadBit ) != 0; -} - -} - -#endif diff --git a/profiler/src/profiler/TracyTimelineDraw.hpp b/profiler/src/profiler/TracyTimelineDraw.hpp index 24fc660f..dd827bdf 100644 --- a/profiler/src/profiler/TracyTimelineDraw.hpp +++ b/profiler/src/profiler/TracyTimelineDraw.hpp @@ -4,6 +4,7 @@ #include #include "TracyEvent.hpp" +#include "TracyLocks.hpp" #include "TracyShortPtr.hpp" namespace tracy @@ -81,30 +82,19 @@ struct CpuCtxDraw -struct LockState -{ - enum Type : uint8_t - { - Nothing = 1 << 0, - HasLock = 1 << 1, // green - HasBlockingLock = 1 << 2, // yellow - WaitLock = 1 << 3 // red - }; -}; - struct LockDrawItem { - Int48 t1; - LockState::Type state; - uint32_t condensed; - short_ptr ptr, next; + int64_t t1; + uint8_t state; + uint32_t num; + const LockSegment* seg; }; struct LockDraw { uint32_t id; bool forceDraw; - uint8_t thread; + uint16_t thread; std::vector data; }; @@ -113,7 +103,7 @@ struct LockHighlight int64_t id; int64_t begin; int64_t end; - uint8_t thread; + uint16_t thread; bool blocked; }; diff --git a/profiler/src/profiler/TracyTimelineItemThread.cpp b/profiler/src/profiler/TracyTimelineItemThread.cpp index 3eeedc9f..930425d9 100644 --- a/profiler/src/profiler/TracyTimelineItemThread.cpp +++ b/profiler/src/profiler/TracyTimelineItemThread.cpp @@ -3,7 +3,6 @@ #include "TracyColor.hpp" #include "TracyImGui.hpp" -#include "TracyLockHelpers.hpp" #include "TracyMouse.hpp" #include "TracyPrint.hpp" #include "TracyTimelineContext.hpp" @@ -17,7 +16,6 @@ namespace tracy constexpr float MinVisSize = 3; constexpr float MinCtxSize = 4; -// Sum Wait→Obtain (and WaitShared→ObtainShared) for one thread across all locks. static int64_t GetThreadLockWaitTime( const Worker& worker, uint64_t tid, size_t& lockCnt, uint64_t& waitCount ) { int64_t waitTotal = 0; @@ -30,28 +28,9 @@ static int64_t GetThreadLockWaitTime( const Worker& worker, uint64_t tid, size_t auto it = lockmap.threadMap.find( tid ); if( it == lockmap.threadMap.end() ) continue; lockCnt++; - const uint8_t thread = it->second; - bool pending = false; - int64_t waitStart = 0; - for( const auto& evPtr : lockmap.timeline ) - { - const auto* ev = evPtr.ptr.get(); - if( !ev || ev->thread != thread ) continue; - if( ev->type == LockEvent::Type::Wait || ev->type == LockEvent::Type::WaitShared ) - { - pending = true; - waitStart = ev->Time(); - } - else if( ev->type == LockEvent::Type::Obtain || ev->type == LockEvent::Type::ObtainShared ) - { - if( pending ) - { - waitTotal += ev->Time() - waitStart; - waitCount++; - pending = false; - } - } - } + const auto& ti = lockmap.threads[it->second]; + waitTotal += ti.waitTotal; + waitCount += ti.waitCount; } return waitTotal; } @@ -134,10 +113,8 @@ int64_t TimelineItemThread::RangeBegin() const 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(); + const auto& ti = lockmap.threads[it->second]; + if( ti.firstTime < first ) first = ti.firstTime; } return first; } @@ -173,10 +150,8 @@ int64_t TimelineItemThread::RangeEnd() const 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(); + const auto& ti = lockmap.threads[it->second]; + if( ti.lastTime > last ) last = ti.lastTime; } return last; } @@ -755,367 +730,63 @@ void TimelineItemThread::PreprocessMessages( const TimelineContext& ctx, const V } } -static Vector::const_iterator GetNextLockEvent( const Vector::const_iterator& it, const Vector::const_iterator& end, LockState::Type& nextState, uint64_t threadBit ) -{ - auto next = it; - next++; - switch( nextState ) - { - case LockState::Nothing: - while( next < end ) - { - if( next->lockCount != 0 ) - { - if( GetThreadBit( next->lockingThread ) == threadBit ) - { - nextState = AreOtherWaiting( next->waitList, threadBit ) ? LockState::HasBlockingLock : LockState::HasLock; - break; - } - else if( IsThreadWaiting( next->waitList, threadBit ) ) - { - nextState = LockState::WaitLock; - break; - } - } - next++; - } - break; - case LockState::HasLock: - while( next < end ) - { - if( next->lockCount == 0 ) - { - nextState = LockState::Nothing; - break; - } - if( next->waitList != 0 ) - { - if( AreOtherWaiting( next->waitList, threadBit ) ) - { - nextState = LockState::HasBlockingLock; - } - break; - } - if( next->waitList != it->waitList || next->lockCount != it->lockCount ) - { - break; - } - next++; - } - break; - case LockState::HasBlockingLock: - while( next < end ) - { - if( next->lockCount == 0 ) - { - nextState = LockState::Nothing; - break; - } - if( next->waitList != it->waitList || next->lockCount != it->lockCount ) - { - break; - } - next++; - } - break; - case LockState::WaitLock: - while( next < end ) - { - if( GetThreadBit( next->lockingThread ) == threadBit ) - { - nextState = AreOtherWaiting( next->waitList, threadBit ) ? LockState::HasBlockingLock : LockState::HasLock; - break; - } - if( next->lockingThread != it->lockingThread ) - { - break; - } - if( next->lockCount == 0 ) - { - break; - } - next++; - } - break; - default: - assert( false ); - break; - } - - return next; -} - -static LockState::Type CombineLockState( LockState::Type state, LockState::Type next ) -{ - return std::max( state, next ); -} - -static Vector::const_iterator GetNextLockEventShared( const Vector::const_iterator& it, const Vector::const_iterator& end, LockState::Type& nextState, uint64_t threadBit ) -{ - const auto itptr = (const LockEventShared*)(const LockEvent*)it->ptr; - auto next = it; - next++; - - switch( nextState ) - { - case LockState::Nothing: - while( next < end ) - { - const auto ptr = (const LockEventShared*)(const LockEvent*)next->ptr; - if( next->lockCount != 0 ) - { - const auto wait = next->waitList | ptr->waitShared; - if( GetThreadBit( next->lockingThread ) == threadBit ) - { - nextState = AreOtherWaiting( wait, threadBit ) ? LockState::HasBlockingLock : LockState::HasLock; - break; - } - else if( IsThreadWaiting( wait, threadBit ) ) - { - nextState = LockState::WaitLock; - break; - } - } - else if( IsThreadWaiting( ptr->sharedList, threadBit ) ) - { - nextState = ( next->waitList != 0 ) ? LockState::HasBlockingLock : LockState::HasLock; - break; - } - else if( ptr->sharedList != 0 && IsThreadWaiting( next->waitList, threadBit ) ) - { - nextState = LockState::WaitLock; - break; - } - next++; - } - break; - case LockState::HasLock: - while( next < end ) - { - const auto ptr = (const LockEventShared*)(const LockEvent*)next->ptr; - if( next->lockCount == 0 && !IsThreadWaiting( ptr->sharedList, threadBit ) ) - { - nextState = LockState::Nothing; - break; - } - if( next->waitList != 0 ) - { - if( AreOtherWaiting( next->waitList, threadBit ) ) - { - nextState = LockState::HasBlockingLock; - } - break; - } - else if( !IsThreadWaiting( ptr->sharedList, threadBit ) && ptr->waitShared != 0 ) - { - nextState = LockState::HasBlockingLock; - break; - } - if( next->waitList != it->waitList || ptr->waitShared != itptr->waitShared || next->lockCount != it->lockCount || ptr->sharedList != itptr->sharedList ) - { - break; - } - next++; - } - break; - case LockState::HasBlockingLock: - while( next < end ) - { - const auto ptr = (const LockEventShared*)(const LockEvent*)next->ptr; - if( next->lockCount == 0 && !IsThreadWaiting( ptr->sharedList, threadBit ) ) - { - nextState = LockState::Nothing; - break; - } - if( next->waitList != it->waitList || ptr->waitShared != itptr->waitShared || next->lockCount != it->lockCount || ptr->sharedList != itptr->sharedList ) - { - break; - } - next++; - } - break; - case LockState::WaitLock: - while( next < end ) - { - const auto ptr = (const LockEventShared*)(const LockEvent*)next->ptr; - if( GetThreadBit( next->lockingThread ) == threadBit ) - { - const auto wait = next->waitList | ptr->waitShared; - nextState = AreOtherWaiting( wait, threadBit ) ? LockState::HasBlockingLock : LockState::HasLock; - break; - } - if( IsThreadWaiting( ptr->sharedList, threadBit ) ) - { - nextState = ( next->waitList != 0 ) ? LockState::HasBlockingLock : LockState::HasLock; - break; - } - if( next->lockingThread != it->lockingThread ) - { - break; - } - if( next->lockCount == 0 && !IsThreadWaiting( ptr->waitShared, threadBit ) ) - { - break; - } - next++; - } - break; - default: - assert( false ); - break; - } - - return next; -} void TimelineItemThread::PreprocessLocks( const TimelineContext& ctx, const unordered_flat_map& locks, uint32_t tid, TaskDispatch& td, bool visible ) { const auto vStart = ctx.vStart; const auto vEnd = ctx.vEnd; - const auto nspx = ctx.nspx; const auto& vd = m_view.GetViewData(); const auto lockInfoWindow = m_view.GetLockInfoWindow(); - const auto MinVisNs = int64_t( round( ctx.scale * MinVisSize * nspx ) ); + const auto MinVisNs = int64_t( round( ctx.scale * MinVisSize * ctx.nspx ) ); for( auto& v : locks ) { const auto& lockmap = *v.second; if( !lockmap.valid ) continue; if( !m_view.Vis( &lockmap ) ) continue; - if( vd.onlyContendedLocks && lockInfoWindow != v.first && ( lockmap.threadList.size() == 1 || !lockmap.isContended ) ) continue; + if( vd.onlyContendedLocks && lockInfoWindow != v.first && ( lockmap.threads.size() == 1 || !lockmap.isContended ) ) continue; auto it = lockmap.threadMap.find( tid ); if( it == lockmap.threadMap.end() ) continue; + const auto slot = it->second; + const auto& ti = lockmap.threads[slot]; assert( !lockmap.timeline.empty() ); - const auto& range = lockmap.range[it->second]; - if( range.start > vEnd || range.end < vStart ) + const auto drawnEnd = ti.segments.empty() ? ti.lastTime : + ( ti.segments.back().nextEv == LockEvent::NoEvent ? m_worker.GetLastTime() : lockmap.timeline[ ti.segments.back().nextEv ].Time() ); + if( ti.firstTime > vEnd || drawnEnd < vStart ) { if( lockInfoWindow == v.first ) { - m_lockDraw.emplace_back( std::make_unique( LockDraw { v.first, true, it->second } ) ); + m_lockDraw.emplace_back( std::make_unique( LockDraw { v.first, true, slot } ) ); } continue; } - auto drawData = std::make_unique( LockDraw { v.first, false, it->second } ); + auto drawData = std::make_unique( LockDraw { v.first, false, slot } ); auto drawPtr = drawData.get(); m_lockDraw.emplace_back( std::move( drawData ) ); - td.Queue( [this, it, &lockmap, &ctx, &range, &vd, visible, drawPtr, MinVisNs] { + td.Queue( [this, &lockmap, &ti, &ctx, &vd, visible, drawPtr, MinVisNs] { const auto vStart = ctx.vStart; const auto vEnd = ctx.vEnd; + const auto lastTime = m_worker.GetLastTime(); + const uint8_t mask = vd.onlyContendedLocks ? ( LockEventState::Nothing | LockEventState::HasLock ) : ( uint8_t )LockEventState::Nothing; - auto GetNextLockFunc = lockmap.type == LockType::Lockable ? GetNextLockEvent : GetNextLockEventShared; - const auto thread = it->second; - const auto threadBit = GetThreadBit( thread ); - const auto& tl = lockmap.timeline; - - auto vbegin = std::lower_bound( tl.begin(), tl.end(), std::max( range.start, vStart ), [] ( const auto& l, const auto& r ) { return l.ptr->Time() < r; } ); - const auto vend = std::lower_bound( vbegin, tl.end(), std::min( range.end, vEnd ), [] ( const auto& l, const auto& r ) { return l.ptr->Time() < r; } ); - - if( vbegin > tl.begin() ) vbegin--; - - LockState::Type state = LockState::Nothing; - if( lockmap.type == LockType::Lockable ) - { - if( vbegin->lockCount != 0 ) - { - if( vbegin->lockingThread == thread ) - { - state = AreOtherWaiting( vbegin->waitList, threadBit ) ? LockState::HasBlockingLock : LockState::HasLock; - } - else if( IsThreadWaiting( vbegin->waitList, threadBit ) ) - { - state = LockState::WaitLock; - } - } - } - else - { - auto ptr = (const LockEventShared*)(const LockEvent*)vbegin->ptr; - if( vbegin->lockCount != 0 ) - { - if( vbegin->lockingThread == thread ) - { - state = ( AreOtherWaiting( vbegin->waitList, threadBit ) || AreOtherWaiting( ptr->waitShared, threadBit ) ) ? LockState::HasBlockingLock : LockState::HasLock; - } - else if( IsThreadWaiting( vbegin->waitList, threadBit ) || IsThreadWaiting( ptr->waitShared, threadBit ) ) - { - state = LockState::WaitLock; - } - } - else if( IsThreadWaiting( ptr->sharedList, threadBit ) ) - { - state = vbegin->waitList != 0 ? LockState::HasBlockingLock : LockState::HasLock; - } - else if( ptr->sharedList != 0 && IsThreadWaiting( vbegin->waitList, threadBit ) ) - { - state = LockState::WaitLock; - } - } - - const uint8_t mask = vd.onlyContendedLocks ? ( LockState::Nothing | LockState::HasLock ) : LockState::Nothing; if( !visible ) { - while( vbegin < vend && ( state & mask ) != 0 ) - { - vbegin = GetNextLockFunc( vbegin, vend, state, threadBit ); - } - drawPtr->forceDraw = vbegin < vend; + drawPtr->forceDraw = HasLockDrawItems( lockmap, ti, vStart, vEnd, mask, lastTime ); return; } auto& dst = drawPtr->data; - for(;;) - { - while( vbegin < vend && ( state & mask ) != 0 ) - { - vbegin = GetNextLockFunc( vbegin, vend, state, threadBit ); - } - if( vbegin >= vend ) break; - assert( ( state & mask ) == 0 ); - - LockState::Type drawState = state; - auto next = GetNextLockFunc( vbegin, vend, state, threadBit ); - - const auto tStart = vbegin->ptr->Time(); - int64_t t0 = tStart; - int64_t t1 = next == tl.end() ? m_worker.GetLastTime() : next->ptr->Time(); - uint32_t condensed = 0; - - for(;;) - { - if( next >= vend || t1 - t0 > MinVisNs ) break; - auto n = next; - auto ns = state; - while( n < vend && ( ns & mask ) != 0 ) - { - n = GetNextLockFunc( n, vend, ns, threadBit ); - } - if( n >= vend ) break; - if( n == next ) - { - n = GetNextLockFunc( n, vend, ns, threadBit ); - } - drawState = CombineLockState( drawState, state ); - condensed++; - const auto t2 = n == tl.end() ? m_worker.GetLastTime() : n->ptr->Time(); - if( t2 - t1 > MinVisNs ) break; - if( drawState != ns && t2 - tStart > MinVisNs && ( ns & mask ) == 0 ) break; - t0 = t1; - t1 = t2; - next = n; - state = ns; - } - - dst.emplace_back( LockDrawItem { t1, drawState, condensed, vbegin, next } ); - - vbegin = next; - } + const auto& segs = ti.segments; + ForEachLockDrawItem( lockmap, ti, vStart, vEnd, MinVisNs, mask, lastTime, [&dst, &segs] ( uint32_t first, uint32_t num, int64_t t1, uint8_t state ) { + dst.emplace_back( LockDrawItem { t1, state, num, &segs[first] } ); + } ); } ); } } diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index 22382bb7..d1b60fc0 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -326,7 +326,7 @@ private: int DrawGpuZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, uint64_t thread, float yMin, float yMax, int64_t begin, int drift ); template int SkipGpuZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, uint64_t thread, float yMin, float yMax, int64_t begin, int drift ); - void DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLocation& srcloc, bool hover, ImDrawList* draw, const ImVec2& wpos, float w, float ty, float offset, uint8_t tid ); + void DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLocation& srcloc, bool hover, ImDrawList* draw, const ImVec2& wpos, float w, float ty, float offset, uint16_t tid ); int DrawLocks( const TimelineContext& ctx, const std::vector>& lockDraw, uint64_t tid, int _offset, LockHighlight& highlight ); void DrawPlotPoint( const ImVec2& wpos, float x, float y, int offset, uint32_t color, bool hover, bool hasPrev, const PlotItem& item, double prev, PlotType type, PlotValueFormatting format, float PlotHeight, uint64_t name ); void DrawPlotPoint( const ImVec2& wpos, float x, float y, int offset, uint32_t color, bool hover, double val, PlotValueFormatting format, float PlotHeight ); diff --git a/profiler/src/profiler/TracyView_Locks.cpp b/profiler/src/profiler/TracyView_Locks.cpp index 09186ebd..fb06a2b6 100644 --- a/profiler/src/profiler/TracyView_Locks.cpp +++ b/profiler/src/profiler/TracyView_Locks.cpp @@ -3,7 +3,6 @@ #include "TracyColor.hpp" #include "TracyFilesystem.hpp" #include "TracyImGui.hpp" -#include "TracyLockHelpers.hpp" #include "TracyMouse.hpp" #include "TracyPrint.hpp" #include "TracyTimelineContext.hpp" @@ -16,7 +15,37 @@ namespace tracy constexpr float MinVisSize = 3; -void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLocation& srcloc, bool hover, ImDrawList* draw, const ImVec2& wpos, float w, float ty, float offset, uint8_t tid ) +template +static void ForEachRoleAt( const LockMap& lockmap, uint32_t at, uint8_t flag, F&& fn ) +{ + for( size_t slot=0; slot < lockmap.threads.size(); slot++ ) + { + const auto& segs = lockmap.threads[slot].segments; + auto it = std::upper_bound( segs.begin(), segs.end(), at, [] ( uint32_t v, const LockSegment& s ) { return v < s.evStart; } ); + if( it == segs.begin() ) continue; + --it; + if( ( it->nextEv == LockEvent::NoEvent || it->nextEv > at ) && ( it->flags & flag ) != 0 ) fn( ( uint16_t )slot ); + } +} + + +static size_t CountRoleAt( const LockMap& lockmap, uint32_t at, uint8_t flag ) +{ + size_t cnt = 0; + ForEachRoleAt( lockmap, at, flag, [ &cnt ] ( uint16_t ) { cnt++; } ); + return cnt; +} + +// A shared hold ends only at the owning thread's ReleaseShared; segments split by +// other events within a hold keep their SharedHolding flag and must stay merged. +static bool SharedHoldEnded( const LockMap& lockmap, uint16_t slot, const LockSegment& seg ) +{ + if( seg.nextEv == LockEvent::NoEvent ) return false; + const auto& ev = lockmap.timeline[ seg.nextEv ]; + return ev.thread == slot && ( LockEvent::Type )ev.type == LockEvent::Type::ReleaseShared; +} + +void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLocation& srcloc, bool hover, ImDrawList* draw, const ImVec2& wpos, float w, float ty, float offset, uint16_t tid ) { char buf[1024]; if( lockmap.customName.Active() ) @@ -36,8 +65,8 @@ void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLoca if( ImGui::IsMouseHoveringRect( wpos + ImVec2( 0, offset ), wpos + ImVec2( ty + ImGui::CalcTextSize( buf ).x, offset + ty + 1 ) ) ) { - const auto& range = lockmap.range[tid]; - const auto activity = range.end - range.start; + const auto& tinfo = lockmap.threads[tid]; + const auto activity = tinfo.lastTime - tinfo.firstTime; const auto traceLen = m_worker.GetLastTime(); int64_t timeAnnounce = lockmap.timeAnnounce; @@ -46,11 +75,11 @@ void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLoca { if( timeAnnounce <= 0 ) { - timeAnnounce = lockmap.timeline.front().ptr->Time(); + timeAnnounce = lockmap.timeline.front().Time(); } if( timeTerminate <= 0 ) { - timeTerminate = lockmap.timeline.back().ptr->Time(); + timeTerminate = lockmap.timeline.back().Time(); } } const auto lockLen = timeTerminate - timeAnnounce; @@ -70,8 +99,8 @@ void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLoca } ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); ImGui::Separator(); - TextFocused( ICON_FA_SHUFFLE " Appeared at", TimeToString( range.start ) ); - TextFocused( ICON_FA_SHUFFLE " Last event at", TimeToString( range.end ) ); + TextFocused( ICON_FA_SHUFFLE " Appeared at", TimeToString( tinfo.firstTime ) ); + TextFocused( ICON_FA_SHUFFLE " Last event at", TimeToString( tinfo.lastTime ) ); TextFocused( ICON_FA_SHUFFLE " Activity time:", TimeToString( activity ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%.2f%% of lock lifetime)", activity / double( lockLen ) * 100 ); @@ -84,11 +113,11 @@ void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLoca ImGui::Separator(); TextDisabledUnformatted( "Thread list:" ); ImGui::Indent( ty ); - for( const auto& t : lockmap.threadList ) + for( const auto& ti : lockmap.threads ) { - SmallColorBox( GetThreadColor( t, 0 ) ); + SmallColorBox( GetThreadColor( ti.thread, 0 ) ); ImGui::SameLine(); - ImGui::TextUnformatted( m_worker.GetThreadName( t ) ); + ImGui::TextUnformatted( m_worker.GetThreadName( ti.thread ) ); } ImGui::Unindent( ty ); ImGui::Separator(); @@ -101,7 +130,7 @@ void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLoca } if( IsMouseClicked( ImGuiMouseButton_Middle ) ) { - ZoomToRange( range.start, range.end ); + ZoomToRange( tinfo.firstTime, tinfo.lastTime ); } } } @@ -146,325 +175,301 @@ int View::DrawLocks( const TimelineContext& ctx, const std::vectorTime(); - const auto t1 = v.t1.Val(); - const auto px0 = ( t0 - vStart ) * pxns; - // The usual method of collapsing single small zones into zig-zags would be very bad here. Lock wait zones should - // be easily visible without having to zoom in first. This sets a minimum width for any lock zone. - const auto px1 = std::max( ( t1 - vStart ) * pxns, px0 + MinVisPx ); - - bool itemHovered = hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + ostep ) ); - if( itemHovered ) + for( auto& v : lock.data ) { - if( IsMouseClicked( ImGuiMouseButton_Left ) ) + const int rank = v.state == LockEventState::WaitLock ? 2 : v.state == LockEventState::HasBlockingLock ? 1 : 0; + if( rank != pass ) continue; + const auto t0 = lockmap.timeline[ v.seg->evStart ].Time(); + const auto t1 = v.t1; + const auto px0 = ( t0 - vStart ) * pxns; + // The usual method of collapsing single small zones into zig-zags would be very bad here. Lock wait zones should + // be easily visible without having to zoom in first. This sets a minimum width for any lock zone. + const auto px1 = std::max( ( t1 - vStart ) * pxns, px0 + MinVisPx ); + + bool itemHovered = hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + ostep ) ); + if( itemHovered ) { - m_lockInfoWindow = lock.id; - } - if( IsMouseClicked( ImGuiMouseButton_Middle ) ) - { - ZoomToRange( t0, t1 ); + if( IsMouseClicked( ImGuiMouseButton_Left ) ) + { + m_lockInfoWindow = lock.id; + } + if( IsMouseClicked( ImGuiMouseButton_Middle ) ) + { + ZoomToRange( t0, t1 ); + } + + if( v.num > 1 ) + { + ImGui::BeginTooltip(); + TextFocused( "Multiple lock events:", RealToString( v.num ) ); + ImGui::EndTooltip(); + } + else + { + const auto cursorTime = ( int64_t )( vStart + ( ImGui::GetMousePos().x - wpos.x ) / pxns ); + const auto zoneBegin = lockmap.timeline.begin() + v.seg->evStart; + const auto zoneEnd = v.seg->nextEv == LockEvent::NoEvent ? lockmap.timeline.end() : lockmap.timeline.begin() + v.seg->nextEv; + auto evIt = std::upper_bound( zoneBegin, zoneEnd, cursorTime, + [] ( int64_t t, const LockEvent& e ) { return t < e.Time(); } ); + const uint32_t cursorIdx = evIt != zoneBegin ? ( uint32_t )( evIt - lockmap.timeline.begin() - 1 ) : v.seg->evStart; + highlight.blocked = v.state == LockEventState::HasBlockingLock; + const auto hinfo = HolderAt( lockmap, cursorIdx ); + if( !highlight.blocked ) + { + highlight.id = lock.id; + highlight.begin = t0; + highlight.end = t1; + highlight.thread = lock.thread; + highlight.blocked = false; + } + else if( hinfo.count > 0 ) + { + const auto h = hinfo.holder; + const auto& log = lockmap.holderChanges; + auto cit = std::upper_bound( log.begin(), log.end(), cursorIdx, [] ( uint32_t val, const LockHolderChange& e ) { return val < e.idx; } ); + assert( cit != log.begin() ); + auto b = std::prev( cit ); + while( b != log.begin() ) + { + auto prev = std::prev( b ); + if( prev->holder != h || prev->count == 0 ) break; + b = prev; + } + highlight.begin = lockmap.timeline[b->idx].Time(); + + auto e2 = cit; + while( e2 != log.end() && e2->holder == h && e2->count != 0 ) e2++; + if( e2 != log.end() ) + { + highlight.id = lock.id; + highlight.end = lockmap.timeline[e2->idx].Time(); + highlight.thread = lock.thread; + } + } + else if( v.seg->flags & LockEventFlags::SharedHolding ) + { + const auto& segs = lockmap.threads[lock.thread].segments; + auto sit = segs.begin() + ( v.seg - segs.data() ); + while( sit != segs.begin() && ( ( sit - 1 )->flags & LockEventFlags::SharedHolding ) != 0 && !SharedHoldEnded( lockmap, lock.thread, *( sit - 1 ) ) ) --sit; + auto e1 = sit; + do { ++e1; } while( e1 != segs.end() && ( e1->flags & LockEventFlags::SharedHolding ) != 0 && !SharedHoldEnded( lockmap, lock.thread, *( e1 - 1 ) ) ); + const auto last = e1 - 1; + if( last->nextEv != LockEvent::NoEvent ) + { + highlight.id = lock.id; + highlight.begin = lockmap.timeline[sit->evStart].Time(); + highlight.end = lockmap.timeline[last->nextEv].Time(); + highlight.thread = lock.thread; + } + } + + ImGui::BeginTooltip(); + if( lockmap.customName.Active() ) + { + ImGui::Text( "Lock #%" PRIu32 ": %s", lock.id, m_worker.GetString( lockmap.customName ) ); + } + else + { + ImGui::Text( "Lock #%" PRIu32 ": %s", lock.id, m_worker.GetString( srcloc.function ) ); + } + ImGui::Separator(); + ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); + TextFocused( "Time:", TimeToString( t1 - t0 ) ); + ImGui::Separator(); + + int16_t markloc = 0; + { + const auto& marks = lockmap.threads[lock.thread].marks; + auto mit = std::upper_bound( marks.begin(), marks.end(), cursorIdx ); + if( mit != marks.begin() ) + { + markloc = lockmap.timeline[*( mit - 1 )].SrcLoc(); + } + } + if( markloc != 0 ) + { + const auto& marklocdata = m_worker.GetSourceLocation( markloc ); + ImGui::TextUnformatted( "Lock event location:" ); + ImGui::TextUnformatted( m_worker.GetString( marklocdata.function ) ); + ImGui::TextUnformatted( LocationToString( m_worker.GetString( marklocdata.file ), marklocdata.line ) ); + ImGui::Separator(); + } + + if( lockmap.type == LockType::Lockable ) + { + switch( ( LockEventState::Type )v.state ) + { + case LockEventState::HasLock: + if( hinfo.count == 1 ) + { + ImGui::Text( "Thread \"%s\" has lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); + } + else + { + ImGui::Text( "Thread \"%s\" has %i locks. No other threads are waiting.", m_worker.GetThreadName( tid ), (int)hinfo.count ); + } + if( ( v.seg->flags & ( LockEventFlags::LockHolding | LockEventFlags::Waiting ) ) == ( LockEventFlags::LockHolding | LockEventFlags::Waiting ) ) + { + ImGui::TextUnformatted( "Recursive lock acquire in thread." ); + } + break; + case LockEventState::HasBlockingLock: + { + const auto nBlocked = CountRoleAt( lockmap, cursorIdx, LockEventFlags::Waiting ); + if( hinfo.count == 1 ) + { + ImGui::Text( "Thread \"%s\" has lock. Blocked threads (%zu):", m_worker.GetThreadName( tid ), nBlocked ); + } + else + { + ImGui::Text( "Thread \"%s\" has %i locks. Blocked threads (%zu):", m_worker.GetThreadName( tid ), (int)hinfo.count, nBlocked ); + } + ImGui::Indent( ty ); + ForEachRoleAt( lockmap, cursorIdx, LockEventFlags::Waiting, [this, &lockmap] ( uint16_t slot ) { + ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[slot].thread ) ); + } ); + ImGui::Unindent( ty ); + break; + } + case LockEventState::WaitLock: + { + if( hinfo.count > 0 ) + { + ImGui::Text( "Thread \"%s\" is blocked by other thread:", m_worker.GetThreadName( tid ) ); + } + else + { + ImGui::Text( "Thread \"%s\" waits to obtain lock after release by thread:", m_worker.GetThreadName( tid ) ); + } + ImGui::Indent( ty ); + ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[hinfo.holder].thread ) ); + ImGui::Unindent( ty ); + break; + } + default: + assert( false ); + break; + } + } + else + { + const auto idx = cursorIdx; + switch( ( LockEventState::Type )v.state ) + { + case LockEventState::HasLock: + { + const auto nShared = CountRoleAt( lockmap, idx, LockEventFlags::SharedHolding ); + if( nShared == 0 ) + { + if( hinfo.count == 1 ) + ImGui::Text( "Thread \"%s\" has lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); + else + ImGui::Text( "Thread \"%s\" has %i locks. No other threads are waiting.", m_worker.GetThreadName( tid ), ( int )hinfo.count ); + } + else if( nShared == 1 ) + { + ImGui::Text( "Thread \"%s\" has a sole shared lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); + } + else + { + ImGui::Text( "Thread \"%s\" has shared lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); + ImGui::Text( "Threads sharing the lock (%zu):", nShared - 1 ); + ImGui::Indent( ty ); + ForEachRoleAt( lockmap, idx, LockEventFlags::SharedHolding, [this, &lockmap, slot = lock.thread] ( uint16_t s ) { + if( s != slot ) ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[s].thread ) ); + } ); + ImGui::Unindent( ty ); + } + break; + } + case LockEventState::HasBlockingLock: + { + const auto nShared = CountRoleAt( lockmap, idx, LockEventFlags::SharedHolding ); + const auto nBlocked = CountRoleAt( lockmap, idx, LockEventFlags::Waiting ) + CountRoleAt( lockmap, idx, LockEventFlags::SharedWaiting ); + if( nShared == 0 ) + { + if( hinfo.count == 1 ) + ImGui::Text( "Thread \"%s\" has lock. Blocked threads (%zu):", m_worker.GetThreadName( tid ), nBlocked ); + else + ImGui::Text( "Thread \"%s\" has %i locks. Blocked threads (%zu):", m_worker.GetThreadName( tid ), ( int )hinfo.count, nBlocked ); + } + else if( nShared == 1 ) + { + ImGui::Text( "Thread \"%s\" has a sole shared lock. Blocked threads (%zu):", m_worker.GetThreadName( tid ), nBlocked ); + } + else + { + ImGui::Text( "Thread \"%s\" has shared lock.", m_worker.GetThreadName( tid ) ); + ImGui::Text( "Threads sharing the lock (%zu):", nShared - 1 ); + ImGui::Indent( ty ); + ForEachRoleAt( lockmap, idx, LockEventFlags::SharedHolding, [this, &lockmap, slot = lock.thread] ( uint16_t s ) { + if( s != slot ) ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[s].thread ) ); + } ); + ImGui::Unindent( ty ); + ImGui::Text( "Blocked threads (%zu):", nBlocked ); + } + + ImGui::Indent( ty ); + ForEachRoleAt( lockmap, idx, LockEventFlags::Waiting, [this, &lockmap] ( uint16_t s ) { + ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[s].thread ) ); + } ); + ForEachRoleAt( lockmap, idx, LockEventFlags::SharedWaiting, [this, &lockmap] ( uint16_t s ) { + ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[s].thread ) ); + } ); + ImGui::Unindent( ty ); + break; + } + case LockEventState::WaitLock: + { + const auto nShared = CountRoleAt( lockmap, idx, LockEventFlags::SharedHolding ); + if( hinfo.count != 0 || nShared != 0 ) + { + ImGui::Text( "Thread \"%s\" is blocked by other threads (%zu):", m_worker.GetThreadName( tid ), ( size_t )( hinfo.count != 0 ? 1 : 0 ) + nShared ); + } + else + { + ImGui::Text( "Thread \"%s\" waits to obtain lock after release by thread:", m_worker.GetThreadName( tid ) ); + } + ImGui::Indent( ty ); + if( hinfo.count != 0 ) + { + ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[hinfo.holder].thread ) ); + } + ForEachRoleAt( lockmap, idx, LockEventFlags::SharedHolding, [this, &lockmap] ( uint16_t s ) { + ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threads[s].thread ) ); + } ); + ImGui::Unindent( ty ); + break; + } + default: + assert( false ); + break; + } + } + ImGui::EndTooltip(); + } } - if( v.condensed > 1 ) + const auto cfilled = v.state == LockEventState::HasLock ? 0xFF228A22 : ( v.state == LockEventState::HasBlockingLock ? 0xFF228A8A : 0xFF2222BD ); + draw->AddRectFilled( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( px1, double( w + 10 ) ), offset + ty ), cfilled ); + if( m_lockHighlight.thread != lock.thread && ( v.state == LockEventState::HasBlockingLock ) != m_lockHighlight.blocked && v.seg->nextEv != LockEvent::NoEvent && m_lockHighlight.id == int64_t( lock.id ) && m_lockHighlight.begin <= t1 && m_lockHighlight.end >= t0 ) { - ImGui::BeginTooltip(); - TextFocused( "Multiple lock events:", RealToString( v.condensed ) ); - ImGui::EndTooltip(); + const auto t = uint8_t( ( sin( std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count() * 0.01 ) * 0.5 + 0.5 ) * 255 ); + draw->AddRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( px1, double( w + 10 ) ), offset + ty ), 0x00FFFFFF | ( t << 24 ), 0.f, 2.f ); + m_wasActive.store( true, std::memory_order_release ); + } + else if( v.num == 1 ) + { + const auto coutline = v.state == LockEventState::HasLock ? 0xFF3BA33B : ( v.state == LockEventState::HasBlockingLock ? 0xFF3BA3A3 : 0xFF3B3BD6 ); + draw->AddRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( px1, double( w + 10 ) ), offset + ty ), coutline ); } else { - highlight.blocked = v.state == LockState::HasBlockingLock; - if( !highlight.blocked ) - { - highlight.id = lock.id; - highlight.begin = t0; - highlight.end = t1; - highlight.thread = lock.thread; - highlight.blocked = false; - } - else - { - const auto& tl = lockmap.timeline; - auto b = v.ptr.get(); - while( b != tl.begin() ) - { - if( b->lockingThread != v.ptr->lockingThread ) - { - break; - } - b--; - } - b++; - highlight.begin = b->ptr->Time(); - - auto e = v.next.get(); - while( e != tl.end() ) - { - if( e->lockingThread != v.next->lockingThread ) - { - highlight.id = lock.id; - highlight.end = e->ptr->Time(); - highlight.thread = lock.thread; - break; - } - e++; - } - } - - ImGui::BeginTooltip(); - if( lockmap.customName.Active() ) - { - ImGui::Text( "Lock #%" PRIu32 ": %s", lock.id, m_worker.GetString( lockmap.customName ) ); - } - else - { - ImGui::Text( "Lock #%" PRIu32 ": %s", lock.id, m_worker.GetString( srcloc.function ) ); - } - ImGui::Separator(); - ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); - TextFocused( "Time:", TimeToString( t1 - t0 ) ); - ImGui::Separator(); - - const auto threadBit = GetThreadBit( lock.thread ); - int16_t markloc = 0; - auto it = v.ptr.get(); - for(;;) - { - if( it->ptr->thread == lock.thread ) - { - if( ( it->lockingThread == lock.thread || IsThreadWaiting( it->waitList, threadBit ) ) && it->ptr->SrcLoc() != 0 ) - { - markloc = it->ptr->SrcLoc(); - break; - } - } - if( it == lockmap.timeline.begin() ) break; - --it; - } - if( markloc != 0 ) - { - const auto& marklocdata = m_worker.GetSourceLocation( markloc ); - ImGui::TextUnformatted( "Lock event location:" ); - ImGui::TextUnformatted( m_worker.GetString( marklocdata.function ) ); - ImGui::TextUnformatted( LocationToString( m_worker.GetString( marklocdata.file ), marklocdata.line ) ); - ImGui::Separator(); - } - - if( lockmap.type == LockType::Lockable ) - { - switch( v.state ) - { - case LockState::HasLock: - if( v.ptr->lockCount == 1 ) - { - ImGui::Text( "Thread \"%s\" has lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); - } - else - { - ImGui::Text( "Thread \"%s\" has %i locks. No other threads are waiting.", m_worker.GetThreadName( tid ), v.ptr->lockCount ); - } - if( v.ptr->waitList != 0 ) - { - assert( !AreOtherWaiting( v.next->waitList, threadBit ) ); - ImGui::TextUnformatted( "Recursive lock acquire in thread." ); - } - break; - case LockState::HasBlockingLock: - { - if( v.ptr->lockCount == 1 ) - { - ImGui::Text( "Thread \"%s\" has lock. Blocked threads (%" PRIu64 "):", m_worker.GetThreadName( tid ), TracyCountBits( v.ptr->waitList ) ); - } - else - { - ImGui::Text( "Thread \"%s\" has %i locks. Blocked threads (%" PRIu64 "):", m_worker.GetThreadName( tid ), v.ptr->lockCount, TracyCountBits( v.ptr->waitList ) ); - } - auto waitList = v.ptr->waitList; - int t = 0; - ImGui::Indent( ty ); - while( waitList != 0 ) - { - if( waitList & 0x1 ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[t] ) ); - } - waitList >>= 1; - t++; - } - ImGui::Unindent( ty ); - break; - } - case LockState::WaitLock: - { - if( v.ptr->lockCount > 0 ) - { - ImGui::Text( "Thread \"%s\" is blocked by other thread:", m_worker.GetThreadName( tid ) ); - } - else - { - ImGui::Text( "Thread \"%s\" waits to obtain lock after release by thread:", m_worker.GetThreadName( tid ) ); - } - ImGui::Indent( ty ); - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[v.ptr->lockingThread] ) ); - ImGui::Unindent( ty ); - break; - } - default: - assert( false ); - break; - } - } - else - { - const auto ptr = (const LockEventShared*)(const LockEvent*)v.ptr->ptr; - switch( v.state ) - { - case LockState::HasLock: - assert( v.ptr->waitList == 0 ); - if( ptr->sharedList == 0 ) - { - assert( v.ptr->lockCount == 1 ); - ImGui::Text( "Thread \"%s\" has lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); - } - else if( TracyCountBits( ptr->sharedList ) == 1 ) - { - ImGui::Text( "Thread \"%s\" has a sole shared lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); - } - else - { - ImGui::Text( "Thread \"%s\" has shared lock. No other threads are waiting.", m_worker.GetThreadName( tid ) ); - ImGui::Text( "Threads sharing the lock (%" PRIu64 "):", TracyCountBits( ptr->sharedList ) - 1 ); - auto sharedList = ptr->sharedList; - int t = 0; - ImGui::Indent( ty ); - while( sharedList != 0 ) - { - if( sharedList & 0x1 && t != lock.thread ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[t] ) ); - } - sharedList >>= 1; - t++; - } - ImGui::Unindent( ty ); - } - break; - case LockState::HasBlockingLock: - { - if( ptr->sharedList == 0 ) - { - assert( v.ptr->lockCount == 1 ); - ImGui::Text( "Thread \"%s\" has lock. Blocked threads (%" PRIu64 "):", m_worker.GetThreadName( tid ), TracyCountBits( v.ptr->waitList ) + TracyCountBits( ptr->waitShared ) ); - } - else if( TracyCountBits( ptr->sharedList ) == 1 ) - { - ImGui::Text( "Thread \"%s\" has a sole shared lock. Blocked threads (%" PRIu64 "):", m_worker.GetThreadName( tid ), TracyCountBits( v.ptr->waitList ) + TracyCountBits( ptr->waitShared ) ); - } - else - { - ImGui::Text( "Thread \"%s\" has shared lock.", m_worker.GetThreadName( tid ) ); - ImGui::Text( "Threads sharing the lock (%" PRIu64 "):", TracyCountBits( ptr->sharedList ) - 1 ); - auto sharedList = ptr->sharedList; - int t = 0; - ImGui::Indent( ty ); - while( sharedList != 0 ) - { - if( sharedList & 0x1 && t != lock.thread ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[t] ) ); - } - sharedList >>= 1; - t++; - } - ImGui::Unindent( ty ); - ImGui::Text( "Blocked threads (%" PRIu64 "):", TracyCountBits( v.ptr->waitList ) + TracyCountBits( ptr->waitShared ) ); - } - - auto waitList = v.ptr->waitList; - int t = 0; - ImGui::Indent( ty ); - while( waitList != 0 ) - { - if( waitList & 0x1 ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[t] ) ); - } - waitList >>= 1; - t++; - } - auto waitShared = ptr->waitShared; - t = 0; - while( waitShared != 0 ) - { - if( waitShared & 0x1 ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[t] ) ); - } - waitShared >>= 1; - t++; - } - ImGui::Unindent( ty ); - break; - } - case LockState::WaitLock: - { - assert( v.ptr->lockCount == 0 || v.ptr->lockCount == 1 ); - if( v.ptr->lockCount != 0 || ptr->sharedList != 0 ) - { - ImGui::Text( "Thread \"%s\" is blocked by other threads (%" PRIu64 "):", m_worker.GetThreadName( tid ), v.ptr->lockCount + TracyCountBits( ptr->sharedList ) ); - } - else - { - ImGui::Text( "Thread \"%s\" waits to obtain lock after release by thread:", m_worker.GetThreadName( tid ) ); - } - ImGui::Indent( ty ); - if( v.ptr->lockCount != 0 ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[v.ptr->lockingThread] ) ); - } - auto sharedList = ptr->sharedList; - int t = 0; - while( sharedList != 0 ) - { - if( sharedList & 0x1 ) - { - ImGui::Text( "\"%s\"", m_worker.GetThreadName( lockmap.threadList[t] ) ); - } - sharedList >>= 1; - t++; - } - ImGui::Unindent( ty ); - break; - } - default: - assert( false ); - break; - } - } - ImGui::EndTooltip(); + DrawZigZag( draw, wpos + ImVec2( 0, offset + ty05 ), px0, px1, ty025, DarkenColor( cfilled ) ); } } - - const auto cfilled = v.state == LockState::HasLock ? 0xFF228A22 : ( v.state == LockState::HasBlockingLock ? 0xFF228A8A : 0xFF2222BD ); - draw->AddRectFilled( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( px1, double( w + 10 ) ), offset + ty ), cfilled ); - if( m_lockHighlight.thread != lock.thread && ( v.state == LockState::HasBlockingLock ) != m_lockHighlight.blocked && v.next != lockmap.timeline.end() && m_lockHighlight.id == int64_t( lock.id ) && m_lockHighlight.begin <= v.ptr->ptr->Time() && m_lockHighlight.end >= v.next->ptr->Time() ) - { - const auto t = uint8_t( ( sin( std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count() * 0.01 ) * 0.5 + 0.5 ) * 255 ); - draw->AddRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( px1, double( w + 10 ) ), offset + ty ), 0x00FFFFFF | ( t << 24 ), 0.f, 2.f ); - m_wasActive.store( true, std::memory_order_release ); - } - else if( v.condensed == 0 ) - { - const auto coutline = v.state == LockState::HasLock ? 0xFF3BA33B : ( v.state == LockState::HasBlockingLock ? 0xFF3BA3A3 : 0xFF3B3BD6 ); - draw->AddRect( wpos + ImVec2( std::max( px0, -10.0 ), offset ), wpos + ImVec2( std::min( px1, double( w + 10 ) ), offset + ty ), coutline ); - } - else if( v.condensed > 1 ) - { - DrawZigZag( draw, wpos + ImVec2( 0, offset + ty05 ), px0, px1, ty025, DarkenColor( cfilled ) ); - } } if( m_lockInfoWindow == lock.id ) @@ -503,59 +508,11 @@ void View::DrawLockInfoWindow() { if( timeAnnounce <= 0 ) { - timeAnnounce = lock.timeline.front().ptr->Time(); + timeAnnounce = lock.timeline.front().Time(); } if( timeTerminate <= 0 ) { - timeTerminate = lock.timeline.back().ptr->Time(); - } - } - - bool waitState = false; - bool holdState = false; - int64_t waitStartTime = 0; - int64_t holdStartTime = 0; - int64_t waitTotalTime = 0; - int64_t holdTotalTime = 0; - uint32_t maxWaitingThreads = 0; - for( auto& v : lock.timeline ) - { - if( holdState ) - { - if( v.lockCount == 0 ) - { - holdTotalTime += v.ptr->Time() - holdStartTime; - holdState = false; - } - } - else - { - if( v.lockCount != 0 ) - { - holdStartTime = v.ptr->Time(); - holdState = true; - } - } - if( waitState ) - { - if( v.waitList == 0 ) - { - waitTotalTime += v.ptr->Time() - waitStartTime; - waitState = false; - } - else - { - maxWaitingThreads = std::max( maxWaitingThreads, TracyCountBits( v.waitList ) ); - } - } - else - { - if( v.waitList != 0 ) - { - waitStartTime = v.ptr->Time(); - waitState = true; - maxWaitingThreads = std::max( maxWaitingThreads, TracyCountBits( v.waitList ) ); - } + timeTerminate = lock.timeline.back().Time(); } } @@ -629,27 +586,27 @@ void View::DrawLockInfoWindow() ImGui::TextDisabled( "(%.2f%% of trace time)", lifetime / double( traceLen ) * 100 ); ImGui::Separator(); - TextFocused( "Lock hold time:", TimeToString( holdTotalTime ) ); + TextFocused( "Lock hold time:", TimeToString( lock.holdTotal ) ); ImGui::SameLine(); - ImGui::TextDisabled( "(%.2f%% of lock lifetime)", holdTotalTime / float( lifetime ) * 100.f ); - TextFocused( "Lock wait time:", TimeToString( waitTotalTime ) ); + ImGui::TextDisabled( "(%.2f%% of lock lifetime)", lock.holdTotal / float( lifetime ) * 100.f ); + TextFocused( "Lock wait time:", TimeToString( lock.waitTotalAgg ) ); ImGui::SameLine(); - ImGui::TextDisabled( "(%.2f%% of lock lifetime)", waitTotalTime / float( lifetime ) * 100.f ); - TextFocused( "Max waiting threads:", RealToString( maxWaitingThreads ) ); + ImGui::TextDisabled( "(%.2f%% of lock lifetime)", lock.waitTotalAgg / float( lifetime ) * 100.f ); + TextFocused( "Max waiting threads:", RealToString( lock.maxWaiting ) ); ImGui::Separator(); const auto threadList = ImGui::TreeNode( "Thread list" ); ImGui::SameLine(); - ImGui::TextDisabled( "(%zu)", lock.threadList.size() ); + ImGui::TextDisabled( "(%zu)", lock.threads.size() ); if( threadList ) { - for( const auto& t : lock.threadList ) + for( const auto& ti : lock.threads ) { - SmallColorBox( GetThreadColor( t, 0 ) ); + SmallColorBox( GetThreadColor( ti.thread, 0 ) ); ImGui::SameLine(); - ImGui::TextUnformatted( m_worker.GetThreadName( t ) ); + ImGui::TextUnformatted( m_worker.GetThreadName( ti.thread ) ); ImGui::SameLine(); - ImGui::TextDisabled( "(%s)", RealToString( t ) ); + ImGui::TextDisabled( "(%s)", RealToString( ti.thread ) ); } ImGui::TreePop(); } diff --git a/profiler/src/profiler/TracyView_Options.cpp b/profiler/src/profiler/TracyView_Options.cpp index e65b5fa1..671b3466 100644 --- a/profiler/src/profiler/TracyView_Options.cpp +++ b/profiler/src/profiler/TracyView_Options.cpp @@ -349,7 +349,7 @@ void View::DrawOptions() if( l.second->valid && !l.second->timeline.empty() ) { lockCnt++; - if( l.second->threadList.size() == 1 ) + if( l.second->threads.size() == 1 ) { singleCnt++; } @@ -409,7 +409,7 @@ void View::DrawOptions() const bool multiExpand = ImGui::TreeNodeEx( "Contended locks present in multiple threads", ImGuiTreeNodeFlags_DefaultOpen ); ImGui::SameLine(); size_t visibleMultiCntCont = 0; - for( const auto& l : m_worker.GetLockMap() ) if( l.second->threadList.size() != 1 && l.second->isContended && Vis( l.second ) ) visibleMultiCntCont++; + for( const auto& l : m_worker.GetLockMap() ) if( l.second->threads.size() != 1 && l.second->isContended && Vis( l.second ) ) visibleMultiCntCont++; if( visibleMultiCntCont == multiCntCont ) { ImGui::TextDisabled( "(%zu)", multiCntCont ); @@ -425,7 +425,7 @@ void View::DrawOptions() { for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->threadList.size() != 1 && l.second->isContended ) Vis( l.second ) = true; + if( l.second->threads.size() != 1 && l.second->isContended ) Vis( l.second ) = true; } } ImGui::SameLine(); @@ -433,13 +433,13 @@ void View::DrawOptions() { for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->threadList.size() != 1 && l.second->isContended ) Vis( l.second ) = false; + if( l.second->threads.size() != 1 && l.second->isContended ) Vis( l.second ) = false; } } for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->valid && !l.second->timeline.empty() && l.second->threadList.size() != 1 && l.second->isContended ) + if( l.second->valid && !l.second->timeline.empty() && l.second->threads.size() != 1 && l.second->isContended ) { auto& sl = m_worker.GetSourceLocation( l.second->srcloc ); auto fileName = m_worker.GetString( sl.file ); @@ -496,7 +496,7 @@ void View::DrawOptions() const bool multiUncontExpand = ImGui::TreeNodeEx( "Uncontended locks present in multiple threads", 0 ); ImGui::SameLine(); uint64_t visibleMultiCntUncont = 0; - for( const auto& l : m_worker.GetLockMap() ) if( l.second->threadList.size() != 1 && !l.second->isContended && Vis( l.second ) ) visibleMultiCntUncont++; + for( const auto& l : m_worker.GetLockMap() ) if( l.second->threads.size() != 1 && !l.second->isContended && Vis( l.second ) ) visibleMultiCntUncont++; if( visibleMultiCntUncont == multiCntUncont ) { ImGui::TextDisabled( "(%zu)", multiCntUncont ); @@ -512,7 +512,7 @@ void View::DrawOptions() { for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->threadList.size() != 1 && !l.second->isContended ) Vis( l.second ) = true; + if( l.second->threads.size() != 1 && !l.second->isContended ) Vis( l.second ) = true; } } ImGui::SameLine(); @@ -520,13 +520,13 @@ void View::DrawOptions() { for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->threadList.size() != 1 && !l.second->isContended ) Vis( l.second ) = false; + if( l.second->threads.size() != 1 && !l.second->isContended ) Vis( l.second ) = false; } } for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->valid && !l.second->timeline.empty() && l.second->threadList.size() != 1 && !l.second->isContended ) + if( l.second->valid && !l.second->timeline.empty() && l.second->threads.size() != 1 && !l.second->isContended ) { auto& sl = m_worker.GetSourceLocation( l.second->srcloc ); auto fileName = m_worker.GetString( sl.file ); @@ -583,7 +583,7 @@ void View::DrawOptions() const auto singleExpand = ImGui::TreeNodeEx( "Locks present in a single thread", 0 ); ImGui::SameLine(); uint64_t visibleSingleCnt = 0; - for( const auto& l : m_worker.GetLockMap() ) if( l.second->threadList.size() == 1 && Vis( l.second ) ) visibleSingleCnt++; + for( const auto& l : m_worker.GetLockMap() ) if( l.second->threads.size() == 1 && Vis( l.second ) ) visibleSingleCnt++; if( visibleSingleCnt == singleCnt ) { ImGui::TextDisabled( "(%zu)", singleCnt ); @@ -599,7 +599,7 @@ void View::DrawOptions() { for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->threadList.size() == 1 ) Vis( l.second ) = true; + if( l.second->threads.size() == 1 ) Vis( l.second ) = true; } } ImGui::SameLine(); @@ -607,13 +607,13 @@ void View::DrawOptions() { for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->threadList.size() == 1 ) Vis( l.second ) = false; + if( l.second->threads.size() == 1 ) Vis( l.second ) = false; } } for( const auto& l : m_worker.GetLockMap() ) { - if( l.second->valid && !l.second->timeline.empty() && l.second->threadList.size() == 1 ) + if( l.second->valid && !l.second->timeline.empty() && l.second->threads.size() == 1 ) { auto& sl = m_worker.GetSourceLocation( l.second->srcloc ); auto fileName = m_worker.GetString( sl.file ); diff --git a/server/TracyLocks.hpp b/server/TracyLocks.hpp index 971f0e05..45cae912 100644 --- a/server/TracyLocks.hpp +++ b/server/TracyLocks.hpp @@ -164,6 +164,86 @@ tracy_force_inline LockHolderInfo HolderAt( const LockMap& map, uint32_t at ) return { it->holder, it->count }; } +tracy_force_inline uint32_t FirstLockSegmentAtOrAfter( const Vector& v, uint32_t pos ) +{ + auto it = std::lower_bound( v.begin(), v.end(), pos ); + return it != v.end() ? *it : ~uint32_t( 0 ); +} + +// segments overlapping [vStart, vEnd); an open segment ends at lastTime +struct LockSegmentRange +{ + uint32_t begin, end; +}; +inline LockSegmentRange GetLockSegmentRange( const LockMap& map, const LockThreadInfo& ti, int64_t vStart, int64_t vEnd, int64_t lastTime ) +{ + const auto& segs = ti.segments; + const auto startsBefore = [&map] ( const LockSegment& s, int64_t t ) { return map.timeline[s.evStart].Time() < t; }; + auto end = std::lower_bound( segs.begin(), segs.end(), vEnd, startsBefore ); + auto begin = std::lower_bound( segs.begin(), end, vStart, startsBefore ); + if( begin != segs.begin() ) + { + const auto& prev = *( begin - 1 ); + const auto prevEnd = prev.nextEv == LockEvent::NoEvent ? lastTime : map.timeline[prev.nextEv].Time(); + if( prevEnd >= vStart ) begin--; + } + return { uint32_t( begin - segs.begin() ), uint32_t( end - segs.begin() ) }; +} + +inline bool HasLockDrawItems( const LockMap& map, const LockThreadInfo& ti, int64_t vStart, int64_t vEnd, uint8_t mask, int64_t lastTime ) +{ + const auto range = GetLockSegmentRange( map, ti, vStart, vEnd, lastTime ); + if( range.begin == range.end ) return false; + if( ( mask & LockEventState::HasLock ) == 0 ) return true; + return std::min( FirstLockSegmentAtOrAfter( ti.yellowSegs, range.begin ), FirstLockSegmentAtOrAfter( ti.redSegs, range.begin ) ) < range.end; +} + +// emit( first, num, t1, state ) for each drawn item: num segments from first, ending at t1, worst state. +// Sub-pixel segments fold like zones do (next end within MinVisNs of the previous end), and a +// different severity starts a new item once the fold is visible. +template +inline void ForEachLockDrawItem( const LockMap& map, const LockThreadInfo& ti, int64_t vStart, int64_t vEnd, int64_t MinVisNs, uint8_t mask, int64_t lastTime, F&& emit ) +{ + const auto& segs = ti.segments; + const auto GetT0 = [&map] ( const LockSegment& s ) { return map.timeline[s.evStart].Time(); }; + const auto GetT1 = [&map, lastTime] ( const LockSegment& s ) { return s.nextEv == LockEvent::NoEvent ? lastTime : map.timeline[s.nextEv].Time(); }; + + const auto range = GetLockSegmentRange( map, ti, vStart, vEnd, lastTime ); + uint32_t i = range.begin; + while( i < range.end ) + { + if( ( segs[i].state & mask ) != 0 ) + { + i = std::min( FirstLockSegmentAtOrAfter( ti.yellowSegs, i ), FirstLockSegmentAtOrAfter( ti.redSegs, i ) ); + if( i >= range.end ) break; + } + const int64_t t0 = GetT0( segs[i] ); + int64_t t1 = GetT1( segs[i] ); + uint8_t state = segs[i].state; + uint32_t next = i + 1; + uint32_t last = next; + if( t1 - t0 < MinVisNs ) + { + while( next < range.end ) + { + const auto& ns = segs[next]; + const int64_t nt1 = GetT1( ns ); + if( nt1 - t1 >= MinVisNs ) break; + if( ( ns.state & mask ) == 0 ) + { + if( ns.state != state && t1 - t0 >= MinVisNs ) break; + state = std::max( state, ns.state ); + t1 = nt1; + last = next + 1; + } + next++; + } + } + emit( i, last - i, t1, state ); + i = next; + } +} + } #endif