mirror of
https://github.com/wolfpld/tracy.git
synced 2026-10-06 00:45:21 +00:00
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:
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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] } );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user