Draw lock timelines from engine segments.

The differences against the previous renderer are deliberate: the
drawn state is canonical from event 0, HasBlockingLock clears when the
blockers drain, same-state neighbors merge, a waiting thread shows as
holding only on its true acquire, a shared holder waiting for exclusive
draws as blocking, the Blocked and Sharing lists contain only genuinely
blocked roles, and the tooltip holder count follows the cursor position
in the holder-change log.
This commit is contained in:
Bartosz Taudul
2026-09-15 00:45:49 +02:00
parent b81fe9db72
commit aa54183ea0
7 changed files with 462 additions and 792 deletions

View File

@@ -1,28 +0,0 @@
#ifndef __TRACYLOCKHELPERS_HPP__
#define __TRACYLOCKHELPERS_HPP__
#include <stdint.h>
#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

View File

@@ -4,6 +4,7 @@
#include <stdint.h>
#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<LockEventPtr> 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<LockDrawItem> data;
};
@@ -113,7 +103,7 @@ struct LockHighlight
int64_t id;
int64_t begin;
int64_t end;
uint8_t thread;
uint16_t thread;
bool blocked;
};

View File

@@ -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<LockEventPtr>::const_iterator GetNextLockEvent( const Vector<LockEventPtr>::const_iterator& it, const Vector<LockEventPtr>::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<LockEventPtr>::const_iterator GetNextLockEventShared( const Vector<LockEventPtr>::const_iterator& it, const Vector<LockEventPtr>::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<uint32_t, LockMap*>& 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>( LockDraw { v.first, true, it->second } ) );
m_lockDraw.emplace_back( std::make_unique<LockDraw>( LockDraw { v.first, true, slot } ) );
}
continue;
}
auto drawData = std::make_unique<LockDraw>( LockDraw { v.first, false, it->second } );
auto drawData = std::make_unique<LockDraw>( 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] } );
} );
} );
}
}

View File

@@ -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<typename Adapter, typename V>
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<std::unique_ptr<LockDraw>>& 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 );

View File

@@ -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<typename F>
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::vector<std::unique_p
continue;
}
for( auto& v : lock.data )
for( int pass = 0; pass < 3; pass++ )
{
const auto& le = *v.ptr;
const auto t0 = le.ptr->Time();
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::milliseconds>( 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::milliseconds>( 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<uint32_t>( maxWaitingThreads, TracyCountBits( v.waitList ) );
}
}
else
{
if( v.waitList != 0 )
{
waitStartTime = v.ptr->Time();
waitState = true;
maxWaitingThreads = std::max<uint32_t>( 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();
}

View File

@@ -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 );

View File

@@ -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<uint32_t>& 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<typename F>
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