mirror of
https://github.com/wolfpld/tracy.git
synced 2026-06-08 00:23:47 +00:00
initial attempt to get "sequence point" feature in
This commit is contained in:
21
examples/seq/Makefile
Normal file
21
examples/seq/Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
TRACY_PUBLIC := ../../public
|
||||
CXX := g++
|
||||
|
||||
TRACY_SRCS := $(TRACY_PUBLIC)/TracyClient.cpp
|
||||
INCLUDES := -I$(TRACY_PUBLIC) -I$(TRACY_PUBLIC)/tracy
|
||||
LIBS := -lpthread -ldl
|
||||
|
||||
CXXFLAGS := -std=c++17 -O2 -DTRACY_ENABLE
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: seq
|
||||
|
||||
seq: seq.cpp tracy_client.o
|
||||
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ $< tracy_client.o $(LIBS)
|
||||
|
||||
tracy_client.o: $(TRACY_SRCS)
|
||||
$(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f seq tracy_client.o
|
||||
349
examples/seq/seq.cpp
Normal file
349
examples/seq/seq.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
// Demonstrates Tracy's sequence (async-continuation) feature.
|
||||
//
|
||||
// Submits a mix of four "recipes" — different async pipelines with varying
|
||||
// chain lengths and per-stage work — onto a thread pool. Each pipeline kicks
|
||||
// off on the main thread (via TracySeqCreate + a tiny setup stage) and then
|
||||
// migrates through 2-7 continuations on worker threads. The profiler renders
|
||||
// arrows between suspend/resume points so the cross-thread chain of any one
|
||||
// pipeline is visible by hovering its zones.
|
||||
//
|
||||
// Build:
|
||||
// make
|
||||
//
|
||||
// Run:
|
||||
// ./seq # then connect tracy-profiler
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Tracy.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
explicit ThreadPool( int n )
|
||||
{
|
||||
for( int i = 0; i < n; ++i )
|
||||
{
|
||||
m_workers.emplace_back( [this, i]
|
||||
{
|
||||
char name[16];
|
||||
std::snprintf( name, sizeof( name ), "worker-%d", i );
|
||||
tracy::SetThreadName( name );
|
||||
Run();
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadPool()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk( m_mu );
|
||||
m_stop = true;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
for( auto& w : m_workers ) w.join();
|
||||
}
|
||||
|
||||
void Submit( std::function<void()> task )
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk( m_mu );
|
||||
m_queue.push( std::move( task ) );
|
||||
}
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
private:
|
||||
void Run()
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk( m_mu );
|
||||
m_cv.wait( lk, [this]{ return m_stop || !m_queue.empty(); } );
|
||||
if( m_stop && m_queue.empty() ) return;
|
||||
task = std::move( m_queue.front() );
|
||||
m_queue.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_mu;
|
||||
std::condition_variable m_cv;
|
||||
std::queue<std::function<void()>> m_queue;
|
||||
std::vector<std::thread> m_workers;
|
||||
bool m_stop = false;
|
||||
};
|
||||
|
||||
struct Chain
|
||||
{
|
||||
uint64_t seq;
|
||||
int value;
|
||||
ThreadPool* pool;
|
||||
std::atomic<int>* remaining;
|
||||
};
|
||||
|
||||
// Each stage opens its own ZoneScoped, brackets simulated work with
|
||||
// TracySeqResume/Suspend, then either submits the next stage to the pool or
|
||||
// (for terminal stages) calls TracySeqRetire and bumps the completion counter.
|
||||
|
||||
// ============================================================================
|
||||
// query: 2 stages — short pipeline, tight chain
|
||||
// ============================================================================
|
||||
|
||||
static void query_exec( Chain c )
|
||||
{
|
||||
ZoneScopedN( "query/exec" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 35ms );
|
||||
c.value *= 3;
|
||||
TracySeqSuspend( c.seq );
|
||||
TracySeqRetire( c.seq );
|
||||
c.remaining->fetch_sub( 1, std::memory_order_release );
|
||||
}
|
||||
|
||||
static void query_parse( Chain c )
|
||||
{
|
||||
ZoneScopedN( "query/parse" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 8ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ query_exec( c ); } );
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ingest: 4 stages — IO-heavy
|
||||
// ============================================================================
|
||||
|
||||
static void ingest_store( Chain c )
|
||||
{
|
||||
ZoneScopedN( "ingest/store" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 18ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
TracySeqRetire( c.seq );
|
||||
c.remaining->fetch_sub( 1, std::memory_order_release );
|
||||
}
|
||||
|
||||
static void ingest_validate( Chain c )
|
||||
{
|
||||
ZoneScopedN( "ingest/validate" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 22ms );
|
||||
c.value += 1;
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ ingest_store( c ); } );
|
||||
}
|
||||
|
||||
static void ingest_parse( Chain c )
|
||||
{
|
||||
ZoneScopedN( "ingest/parse" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 28ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ ingest_validate( c ); } );
|
||||
}
|
||||
|
||||
static void ingest_fetch( Chain c )
|
||||
{
|
||||
ZoneScopedN( "ingest/fetch" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 60ms ); // slow IO
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ ingest_parse( c ); } );
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// render: 5 stages — frame-like workload
|
||||
// ============================================================================
|
||||
|
||||
static void render_present( Chain c )
|
||||
{
|
||||
ZoneScopedN( "render/present" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 10ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
TracySeqRetire( c.seq );
|
||||
c.remaining->fetch_sub( 1, std::memory_order_release );
|
||||
}
|
||||
|
||||
static void render_compose( Chain c )
|
||||
{
|
||||
ZoneScopedN( "render/compose" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 35ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ render_present( c ); } );
|
||||
}
|
||||
|
||||
static void render_shadows( Chain c )
|
||||
{
|
||||
ZoneScopedN( "render/shadows" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 45ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ render_compose( c ); } );
|
||||
}
|
||||
|
||||
static void render_geometry( Chain c )
|
||||
{
|
||||
ZoneScopedN( "render/geometry" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 55ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ render_shadows( c ); } );
|
||||
}
|
||||
|
||||
static void render_setup( Chain c )
|
||||
{
|
||||
ZoneScopedN( "render/setup" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 12ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ render_geometry( c ); } );
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// compile: 7 stages — long pipeline
|
||||
// ============================================================================
|
||||
|
||||
static void compile_emit( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/emit" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 12ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
TracySeqRetire( c.seq );
|
||||
c.remaining->fetch_sub( 1, std::memory_order_release );
|
||||
}
|
||||
|
||||
static void compile_codegen( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/codegen" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 40ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ compile_emit( c ); } );
|
||||
}
|
||||
|
||||
static void compile_optimize( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/optimize" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 70ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ compile_codegen( c ); } );
|
||||
}
|
||||
|
||||
static void compile_ir( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/ir" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 25ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ compile_optimize( c ); } );
|
||||
}
|
||||
|
||||
static void compile_typecheck( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/typecheck" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 30ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ compile_ir( c ); } );
|
||||
}
|
||||
|
||||
static void compile_parse( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/parse" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 18ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ compile_typecheck( c ); } );
|
||||
}
|
||||
|
||||
static void compile_lex( Chain c )
|
||||
{
|
||||
ZoneScopedN( "compile/lex" );
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 8ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
c.pool->Submit( [c]{ compile_parse( c ); } );
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Kick off a chain from the main thread. The "kickoff" zone holds the
|
||||
// TracySeqCreate plus a small setup window on main; the first worker stage
|
||||
// fires when its Submit lands on a worker.
|
||||
// ============================================================================
|
||||
|
||||
enum class Recipe { Query, Ingest, Render, Compile };
|
||||
|
||||
static void Kickoff( Recipe r, int value, ThreadPool& pool, std::atomic<int>& remaining )
|
||||
{
|
||||
ZoneScopedN( "kickoff" );
|
||||
Chain c {
|
||||
.seq = TracySeqCreate(),
|
||||
.value = value,
|
||||
.pool = &pool,
|
||||
.remaining = &remaining,
|
||||
};
|
||||
TracySeqResume( c.seq );
|
||||
std::this_thread::sleep_for( 4ms );
|
||||
TracySeqSuspend( c.seq );
|
||||
|
||||
switch( r )
|
||||
{
|
||||
case Recipe::Query: pool.Submit( [c]{ query_parse( c ); } ); break;
|
||||
case Recipe::Ingest: pool.Submit( [c]{ ingest_fetch( c ); } ); break;
|
||||
case Recipe::Render: pool.Submit( [c]{ render_setup( c ); } ); break;
|
||||
case Recipe::Compile: pool.Submit( [c]{ compile_lex( c ); } ); break;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
tracy::SetThreadName( "main" );
|
||||
|
||||
ThreadPool pool( 6 );
|
||||
|
||||
// Mixed schedule: short and long pipelines interleaved so workers stay
|
||||
// saturated and continuations naturally bounce across threads.
|
||||
static constexpr Recipe schedule[] = {
|
||||
Recipe::Compile, Recipe::Query, Recipe::Ingest, Recipe::Render,
|
||||
Recipe::Query, Recipe::Compile, Recipe::Render, Recipe::Ingest,
|
||||
Recipe::Render, Recipe::Query, Recipe::Compile, Recipe::Ingest,
|
||||
Recipe::Query, Recipe::Render, Recipe::Ingest, Recipe::Compile,
|
||||
Recipe::Query, Recipe::Compile, Recipe::Render, Recipe::Query,
|
||||
};
|
||||
constexpr int kChains = sizeof( schedule ) / sizeof( schedule[0] );
|
||||
std::atomic<int> remaining{ kChains };
|
||||
|
||||
for( int i = 0; i < kChains; ++i )
|
||||
{
|
||||
FrameMarkNamed( "submit" );
|
||||
Kickoff( schedule[i], i * 100, pool, remaining );
|
||||
std::this_thread::sleep_for( 6ms );
|
||||
}
|
||||
|
||||
while( remaining.load( std::memory_order_acquire ) > 0 )
|
||||
{
|
||||
std::this_thread::sleep_for( 10ms );
|
||||
}
|
||||
|
||||
std::printf( "%d chains done\n", kChains );
|
||||
return 0;
|
||||
}
|
||||
@@ -281,6 +281,7 @@ private:
|
||||
void DrawTimeline();
|
||||
void DrawSampleList( const TimelineContext& ctx, const std::vector<SamplesDraw>& drawList, const Vector<SampleData>& vec, int offset, uint64_t tid );
|
||||
void DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineDraw>& drawList, int offset, uint64_t tid, int maxDepth, double margin );
|
||||
void DrawSeqArrows( double pxns, const ImVec2& wpos );
|
||||
void DrawThreadCropper( const int depth, const uint64_t tid, const float xPos, const float yPos, const float ostep, const float cropperWidth, const bool hasCtxSwitches );
|
||||
void DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber );
|
||||
int DispatchGpuZoneLevel( const Vector<short_ptr<GpuEvent>>& 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 );
|
||||
@@ -527,6 +528,16 @@ private:
|
||||
uint32_t m_lockInfoWindow = InvalidId;
|
||||
const ZoneEvent* m_zoneHover = nullptr;
|
||||
DecayValue<const ZoneEvent*> m_zoneHover2 = nullptr;
|
||||
|
||||
struct SeqArrowDraw
|
||||
{
|
||||
int64_t fromTime;
|
||||
const ZoneEvent* fromZone;
|
||||
int64_t toTime;
|
||||
const ZoneEvent* toZone;
|
||||
};
|
||||
std::vector<SeqArrowDraw> m_seqArrowDraw;
|
||||
unordered_flat_map<const ZoneEvent*, float> m_seqZoneYPos;
|
||||
int m_frameHover = -1;
|
||||
bool m_messagesScrollBottom;
|
||||
|
||||
|
||||
@@ -259,6 +259,8 @@ void View::DrawTimeline()
|
||||
m_cpuDataThread.Decay( 0 );
|
||||
m_zoneHover = nullptr;
|
||||
m_zoneHover2.Decay( nullptr );
|
||||
m_seqArrowDraw.clear();
|
||||
m_seqZoneYPos.clear();
|
||||
m_findZone.range.StartFrame();
|
||||
m_statRange.StartFrame();
|
||||
m_flameRange.StartFrame();
|
||||
@@ -411,6 +413,7 @@ void View::DrawTimeline()
|
||||
|
||||
const auto vcenter = verticallyCenterTimeline && drawMouseLine && m_viewMode == ViewMode::Paused;
|
||||
m_tc.End( pxns, wpos, hover, vcenter, yMin, yMax );
|
||||
DrawSeqArrows( pxns, wpos );
|
||||
ImGui::EndChild();
|
||||
|
||||
m_lockHighlight = m_nextLockHighlight;
|
||||
|
||||
@@ -354,6 +354,13 @@ void View::DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineD
|
||||
const auto pr1 = ( end - vStart ) * pxns;
|
||||
const auto zsz = std::max( pr1 - pr0, pxns * 0.5 );
|
||||
|
||||
const auto& seqRefMap = m_worker.GetZoneSeqRef();
|
||||
const auto seqIt = seqRefMap.find( &ev );
|
||||
if( seqIt != seqRefMap.end() )
|
||||
{
|
||||
m_seqZoneYPos[&ev] = wpos.y + offset + ty * 0.5f;
|
||||
}
|
||||
|
||||
const auto zoneColor = GetZoneColorData( ev, tid, v.depth, v.inheritedColor );
|
||||
const char* zoneName = m_worker.GetZoneName( ev );
|
||||
|
||||
@@ -409,6 +416,29 @@ void View::DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineD
|
||||
|
||||
m_zoneSrcLocHighlight = ev.SrcLoc();
|
||||
m_zoneHover = &ev;
|
||||
|
||||
if( seqIt != seqRefMap.end() )
|
||||
{
|
||||
const auto& seqMap = m_worker.GetSequences();
|
||||
const auto sit = seqMap.find( seqIt->second.seqId );
|
||||
if( sit != seqMap.end() )
|
||||
{
|
||||
const auto& conts = sit->second->continuations;
|
||||
const auto idx = seqIt->second.continuationIdx;
|
||||
if( idx > 0 )
|
||||
{
|
||||
m_seqArrowDraw.push_back( {
|
||||
conts[idx-1].suspendTime, (const ZoneEvent*)conts[idx-1].zone,
|
||||
conts[idx].resumeTime, (const ZoneEvent*)conts[idx].zone } );
|
||||
}
|
||||
if( idx + 1 < conts.size() )
|
||||
{
|
||||
m_seqArrowDraw.push_back( {
|
||||
conts[idx].suspendTime, (const ZoneEvent*)conts[idx].zone,
|
||||
conts[idx+1].resumeTime, (const ZoneEvent*)conts[idx+1].zone } );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -671,4 +701,46 @@ void View::DrawThreadCropper( const int depth, const uint64_t tid, const float x
|
||||
}
|
||||
}
|
||||
|
||||
void View::DrawSeqArrows( double pxns, const ImVec2& wpos )
|
||||
{
|
||||
if( m_seqArrowDraw.empty() ) return;
|
||||
|
||||
auto draw = ImGui::GetWindowDrawList();
|
||||
const auto vStart = m_vd.zvStart;
|
||||
const auto scale = GetScale();
|
||||
const auto lineThickness = std::max( 1.5f * scale, 1.0f );
|
||||
const auto dotRadius = 2.5f * scale;
|
||||
const auto arrowSize = 5.0f * scale;
|
||||
constexpr ImU32 color = 0xFFFFFFFF;
|
||||
constexpr ImU32 shadow = 0xFF000000;
|
||||
|
||||
for( const auto& a : m_seqArrowDraw )
|
||||
{
|
||||
const auto fromIt = m_seqZoneYPos.find( a.fromZone );
|
||||
const auto toIt = m_seqZoneYPos.find( a.toZone );
|
||||
if( fromIt == m_seqZoneYPos.end() || toIt == m_seqZoneYPos.end() ) continue;
|
||||
|
||||
const ImVec2 p0( wpos.x + ( a.fromTime - vStart ) * pxns, fromIt->second );
|
||||
const ImVec2 p1( wpos.x + ( a.toTime - vStart ) * pxns, toIt->second );
|
||||
|
||||
draw->AddLine( p0 + ImVec2( 1, 1 ), p1 + ImVec2( 1, 1 ), shadow, lineThickness );
|
||||
draw->AddLine( p0, p1, color, lineThickness );
|
||||
draw->AddCircleFilled( p0, dotRadius, color );
|
||||
|
||||
// Arrowhead at p1
|
||||
const auto dx = p1.x - p0.x;
|
||||
const auto dy = p1.y - p0.y;
|
||||
const auto len = std::sqrt( dx * dx + dy * dy );
|
||||
if( len > 1.0f )
|
||||
{
|
||||
const auto nx = dx / len;
|
||||
const auto ny = dy / len;
|
||||
const ImVec2 base( p1.x - nx * arrowSize, p1.y - ny * arrowSize );
|
||||
const ImVec2 left ( base.x - ny * arrowSize * 0.5f, base.y + nx * arrowSize * 0.5f );
|
||||
const ImVec2 right( base.x + ny * arrowSize * 0.5f, base.y - nx * arrowSize * 0.5f );
|
||||
draw->AddTriangleFilled( p1, left, right, color );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2818,6 +2818,17 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token )
|
||||
++item;
|
||||
continue;
|
||||
}
|
||||
case QueueType::SeqCreate:
|
||||
case QueueType::SeqResume:
|
||||
case QueueType::SeqSuspend:
|
||||
case QueueType::SeqRetire:
|
||||
{
|
||||
int64_t t = MemRead<int64_t>( &item->seqEvent.time );
|
||||
int64_t dt = t - refThread;
|
||||
refThread = t;
|
||||
MemWrite( &item->seqEvent.time, dt );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
@@ -3232,6 +3243,18 @@ Profiler::DequeueStatus Profiler::DequeueSerial()
|
||||
MemWrite( &item->fiberLeave.time, dt );
|
||||
break;
|
||||
}
|
||||
case QueueType::SeqCreate:
|
||||
case QueueType::SeqResume:
|
||||
case QueueType::SeqSuspend:
|
||||
case QueueType::SeqRetire:
|
||||
{
|
||||
ThreadCtxCheckSerial( seqEvent );
|
||||
int64_t t = MemRead<int64_t>( &item->seqEvent.time );
|
||||
int64_t dt = t - refThread;
|
||||
refThread = t;
|
||||
MemWrite( &item->seqEvent.time, dt );
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
assert( false );
|
||||
@@ -5177,6 +5200,11 @@ TRACY_API void ___tracy_fiber_enter( const char* fiber ){ tracy::Profiler::Enter
|
||||
TRACY_API void ___tracy_fiber_leave( void ){ tracy::Profiler::LeaveFiber(); }
|
||||
#endif
|
||||
|
||||
TRACY_API uint64_t ___tracy_seq_create( void ){ return tracy::Profiler::CreateSequence(); }
|
||||
TRACY_API void ___tracy_seq_resume( uint64_t id ){ tracy::Profiler::ResumeSequence( id ); }
|
||||
TRACY_API void ___tracy_seq_suspend( uint64_t id ){ tracy::Profiler::SuspendSequence( id ); }
|
||||
TRACY_API void ___tracy_seq_retire( uint64_t id ){ tracy::Profiler::RetireSequence( id ); }
|
||||
|
||||
# if defined TRACY_MANUAL_LIFETIME && defined TRACY_DELAYED_INIT
|
||||
TRACY_API void ___tracy_startup_profiler( void )
|
||||
{
|
||||
|
||||
@@ -792,6 +792,56 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
static tracy_force_inline uint64_t CreateSequence()
|
||||
{
|
||||
const auto id = GetProfiler().m_seqCounter.fetch_add( 1, std::memory_order_relaxed );
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) return id;
|
||||
#endif
|
||||
TracyQueuePrepare( QueueType::SeqCreate );
|
||||
MemWrite( &item->seqEvent.time, GetTime() );
|
||||
MemWrite( &item->seqEvent.id, id );
|
||||
MemWrite( &item->seqEvent.thread, GetThreadHandle() );
|
||||
TracyQueueCommit( seqEvent );
|
||||
return id;
|
||||
}
|
||||
|
||||
static tracy_force_inline void ResumeSequence( uint64_t id )
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) return;
|
||||
#endif
|
||||
TracyQueuePrepare( QueueType::SeqResume );
|
||||
MemWrite( &item->seqEvent.time, GetTime() );
|
||||
MemWrite( &item->seqEvent.id, id );
|
||||
MemWrite( &item->seqEvent.thread, GetThreadHandle() );
|
||||
TracyQueueCommit( seqEvent );
|
||||
}
|
||||
|
||||
static tracy_force_inline void SuspendSequence( uint64_t id )
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) return;
|
||||
#endif
|
||||
TracyQueuePrepare( QueueType::SeqSuspend );
|
||||
MemWrite( &item->seqEvent.time, GetTime() );
|
||||
MemWrite( &item->seqEvent.id, id );
|
||||
MemWrite( &item->seqEvent.thread, GetThreadHandle() );
|
||||
TracyQueueCommit( seqEvent );
|
||||
}
|
||||
|
||||
static tracy_force_inline void RetireSequence( uint64_t id )
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) return;
|
||||
#endif
|
||||
TracyQueuePrepare( QueueType::SeqRetire );
|
||||
MemWrite( &item->seqEvent.time, GetTime() );
|
||||
MemWrite( &item->seqEvent.id, id );
|
||||
MemWrite( &item->seqEvent.thread, GetThreadHandle() );
|
||||
TracyQueueCommit( seqEvent );
|
||||
}
|
||||
|
||||
void SendCallstack( int32_t depth, const char** skipBefore );
|
||||
static void CutCallstack( void* callstack, const char** skipBefore );
|
||||
|
||||
@@ -1087,6 +1137,7 @@ private:
|
||||
std::mutex m_symbolQueueMutex;
|
||||
|
||||
std::atomic<uint64_t> m_frameCount;
|
||||
std::atomic<uint64_t> m_seqCounter{ 1 };
|
||||
std::atomic<bool> m_isConnected;
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
std::atomic<uint64_t> m_connectionId;
|
||||
|
||||
@@ -81,6 +81,10 @@ enum class QueueType : uint8_t
|
||||
SourceCodeMetadata,
|
||||
FiberEnter,
|
||||
FiberLeave,
|
||||
SeqCreate,
|
||||
SeqResume,
|
||||
SeqSuspend,
|
||||
SeqRetire,
|
||||
Terminate,
|
||||
KeepAlive,
|
||||
ThreadContext,
|
||||
@@ -307,6 +311,13 @@ struct QueueFiberLeave
|
||||
uint32_t thread;
|
||||
};
|
||||
|
||||
struct QueueSeqEvent
|
||||
{
|
||||
int64_t time;
|
||||
uint64_t id;
|
||||
uint32_t thread;
|
||||
};
|
||||
|
||||
struct QueueLockTerminate
|
||||
{
|
||||
uint32_t id;
|
||||
@@ -918,6 +929,7 @@ struct QueueItem
|
||||
QueueSourceCodeNotAvailable sourceCodeNotAvailable;
|
||||
QueueFiberEnter fiberEnter;
|
||||
QueueFiberLeave fiberLeave;
|
||||
QueueSeqEvent seqEvent;
|
||||
QueueGpuZoneAnnotation zoneAnnotation;
|
||||
};
|
||||
};
|
||||
@@ -997,6 +1009,10 @@ static constexpr size_t QueueDataSize[] = {
|
||||
sizeof( QueueHeader ), // SourceCodeMetadata - not for wire transfer
|
||||
sizeof( QueueHeader ) + sizeof( QueueFiberEnter ),
|
||||
sizeof( QueueHeader ) + sizeof( QueueFiberLeave ),
|
||||
sizeof( QueueHeader ) + sizeof( QueueSeqEvent ), // SeqCreate
|
||||
sizeof( QueueHeader ) + sizeof( QueueSeqEvent ), // SeqResume
|
||||
sizeof( QueueHeader ) + sizeof( QueueSeqEvent ), // SeqSuspend
|
||||
sizeof( QueueHeader ) + sizeof( QueueSeqEvent ), // SeqRetire
|
||||
// above items must be first
|
||||
sizeof( QueueHeader ), // terminate
|
||||
sizeof( QueueHeader ), // keep alive
|
||||
|
||||
@@ -126,6 +126,11 @@
|
||||
#define TracyFiberEnterHint(x,y)
|
||||
#define TracyFiberLeave
|
||||
|
||||
#define TracySeqCreate() ((uint64_t)0)
|
||||
#define TracySeqResume(id) ((void)(id))
|
||||
#define TracySeqSuspend(id) ((void)(id))
|
||||
#define TracySeqRetire(id) ((void)(id))
|
||||
|
||||
#else
|
||||
|
||||
#include <string.h>
|
||||
@@ -275,6 +280,11 @@
|
||||
# define TracyFiberLeave tracy::Profiler::LeaveFiber()
|
||||
#endif
|
||||
|
||||
#define TracySeqCreate() tracy::Profiler::CreateSequence()
|
||||
#define TracySeqResume( id ) tracy::Profiler::ResumeSequence( id )
|
||||
#define TracySeqSuspend( id ) tracy::Profiler::SuspendSequence( id )
|
||||
#define TracySeqRetire( id ) tracy::Profiler::RetireSequence( id )
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -132,6 +132,11 @@ typedef const void* TracyCLockCtx;
|
||||
# define TracyCFiberLeave
|
||||
#endif
|
||||
|
||||
#define TracyCSeqCreate() ((uint64_t)0)
|
||||
#define TracyCSeqResume(id) ((void)(id))
|
||||
#define TracyCSeqSuspend(id) ((void)(id))
|
||||
#define TracyCSeqRetire(id) ((void)(id))
|
||||
|
||||
#else
|
||||
|
||||
#ifndef TracyConcat
|
||||
@@ -392,6 +397,16 @@ TRACY_API void ___tracy_fiber_leave( void );
|
||||
# define TracyCFiberLeave ___tracy_fiber_leave();
|
||||
#endif
|
||||
|
||||
TRACY_API uint64_t ___tracy_seq_create( void );
|
||||
TRACY_API void ___tracy_seq_resume( uint64_t id );
|
||||
TRACY_API void ___tracy_seq_suspend( uint64_t id );
|
||||
TRACY_API void ___tracy_seq_retire( uint64_t id );
|
||||
|
||||
#define TracyCSeqCreate() ___tracy_seq_create()
|
||||
#define TracyCSeqResume( id ) ___tracy_seq_resume( id )
|
||||
#define TracyCSeqSuspend( id ) ___tracy_seq_suspend( id )
|
||||
#define TracyCSeqRetire( id ) ___tracy_seq_retire( id )
|
||||
|
||||
TRACY_API int64_t ___tracy_get_time();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -875,6 +875,28 @@ struct FlameGraphItem
|
||||
std::vector<FlameGraphItem> children;
|
||||
};
|
||||
|
||||
|
||||
struct SeqRef
|
||||
{
|
||||
uint64_t seqId;
|
||||
uint32_t continuationIdx;
|
||||
};
|
||||
|
||||
struct SeqContinuation
|
||||
{
|
||||
short_ptr<ZoneEvent> zone;
|
||||
uint64_t tid;
|
||||
int64_t resumeTime;
|
||||
int64_t suspendTime; // 0 ⇒ still open
|
||||
};
|
||||
|
||||
struct SequenceData
|
||||
{
|
||||
int64_t createTime;
|
||||
int64_t retireTime; // 0 ⇒ not retired
|
||||
Vector<SeqContinuation> continuations;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4823,6 +4823,18 @@ bool Worker::Process( const QueueItem& ev )
|
||||
case QueueType::FiberLeave:
|
||||
ProcessFiberLeave( ev.fiberLeave );
|
||||
break;
|
||||
case QueueType::SeqCreate:
|
||||
ProcessSeqCreate( ev.seqEvent );
|
||||
break;
|
||||
case QueueType::SeqResume:
|
||||
ProcessSeqResume( ev.seqEvent );
|
||||
break;
|
||||
case QueueType::SeqSuspend:
|
||||
ProcessSeqSuspend( ev.seqEvent );
|
||||
break;
|
||||
case QueueType::SeqRetire:
|
||||
ProcessSeqRetire( ev.seqEvent );
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
@@ -7336,6 +7348,95 @@ void Worker::ProcessFiberLeave( const QueueFiberLeave& ev )
|
||||
td->fiber = nullptr;
|
||||
}
|
||||
|
||||
SequenceData* Worker::GetOrCreateSequence( uint64_t id, int64_t t )
|
||||
{
|
||||
auto sit = m_data.sequences.find( id );
|
||||
if( sit != m_data.sequences.end() ) return sit->second;
|
||||
auto seq = m_slab.AllocInit<SequenceData>();
|
||||
seq->createTime = t;
|
||||
seq->retireTime = 0;
|
||||
return m_data.sequences.emplace( id, seq ).first->second;
|
||||
}
|
||||
|
||||
void Worker::ProcessSeqCreate( const QueueSeqEvent& ev )
|
||||
{
|
||||
const auto t = TscTime( RefTime( m_refTimeThread, ev.time ) );
|
||||
if( m_data.lastTime < t ) m_data.lastTime = t;
|
||||
(void)GetOrCreateSequence( ev.id, t );
|
||||
}
|
||||
|
||||
void Worker::ProcessSeqResume( const QueueSeqEvent& ev )
|
||||
{
|
||||
const auto t = TscTime( RefTime( m_refTimeThread, ev.time ) );
|
||||
if( m_data.lastTime < t ) m_data.lastTime = t;
|
||||
|
||||
auto seq = GetOrCreateSequence( ev.id, t );
|
||||
|
||||
auto td = NoticeThread( ev.thread );
|
||||
if( !td || td->stack.empty() ) return;
|
||||
|
||||
auto zone = td->stack.back();
|
||||
SeqContinuation cont;
|
||||
cont.zone = zone;
|
||||
cont.tid = ev.thread;
|
||||
cont.resumeTime = t;
|
||||
cont.suspendTime = 0;
|
||||
|
||||
// Insert in resumeTime-sorted order. The server processes events from multiple
|
||||
// per-thread queues in interleaved batches, so a later thread's Resume can be
|
||||
// processed before an earlier thread's Resume for the same sequence. Without
|
||||
// sorting, conts[idx] would not reflect the logical chain order and neighbor
|
||||
// lookups (idx-1, idx+1) would jump to wrong continuations.
|
||||
auto& conts = seq->continuations;
|
||||
size_t insertAt = conts.size();
|
||||
while( insertAt > 0 && conts[insertAt - 1].resumeTime > t ) --insertAt;
|
||||
if( insertAt == conts.size() )
|
||||
{
|
||||
conts.push_back( cont );
|
||||
}
|
||||
else
|
||||
{
|
||||
conts.insert( conts.data() + insertAt, cont );
|
||||
}
|
||||
|
||||
// Rewrite zoneSeqRef for every entry from insertAt onwards — their indices shifted.
|
||||
for( size_t i = insertAt; i < conts.size(); ++i )
|
||||
{
|
||||
const auto* z = (const ZoneEvent*)conts[i].zone;
|
||||
m_data.zoneSeqRef[z] = SeqRef{ ev.id, uint32_t( i ) };
|
||||
}
|
||||
}
|
||||
|
||||
void Worker::ProcessSeqSuspend( const QueueSeqEvent& ev )
|
||||
{
|
||||
const auto t = TscTime( RefTime( m_refTimeThread, ev.time ) );
|
||||
if( m_data.lastTime < t ) m_data.lastTime = t;
|
||||
|
||||
auto seq = GetOrCreateSequence( ev.id, t );
|
||||
auto& conts = seq->continuations;
|
||||
|
||||
// Match Suspend to the most recent unsuspended continuation on the same originating thread.
|
||||
// Use ev.thread directly because the server's m_threadCtx may have drifted across the
|
||||
// interleaved processing of multiple per-thread queues by the time this event runs.
|
||||
for( size_t i = conts.size(); i-- > 0; )
|
||||
{
|
||||
if( conts[i].tid == ev.thread && conts[i].suspendTime == 0 )
|
||||
{
|
||||
conts[i].suspendTime = t;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Worker::ProcessSeqRetire( const QueueSeqEvent& ev )
|
||||
{
|
||||
const auto t = TscTime( RefTime( m_refTimeThread, ev.time ) );
|
||||
if( m_data.lastTime < t ) m_data.lastTime = t;
|
||||
|
||||
auto seq = GetOrCreateSequence( ev.id, t );
|
||||
seq->retireTime = t;
|
||||
}
|
||||
|
||||
void Worker::MemAllocChanged( MemData& memdata, int64_t time )
|
||||
{
|
||||
const auto val = (double)memdata.usage;
|
||||
|
||||
@@ -373,6 +373,9 @@ private:
|
||||
|
||||
unordered_flat_map<uint64_t, ContextSwitch*> ctxSwitch;
|
||||
|
||||
unordered_flat_map<uint64_t, SequenceData*> sequences;
|
||||
unordered_flat_map<const ZoneEvent*, SeqRef> zoneSeqRef;
|
||||
|
||||
CpuData cpuData[256];
|
||||
int cpuDataCount = 0;
|
||||
unordered_flat_map<uint64_t, uint64_t> tidToPid;
|
||||
@@ -565,6 +568,8 @@ public:
|
||||
const Vector<PlotData*>& GetPlots() const { return m_data.plots.Data(); }
|
||||
const Vector<ThreadData*>& GetThreadData() const { return m_data.threads; }
|
||||
const ThreadData* GetThreadData( uint64_t tid ) const;
|
||||
const unordered_flat_map<uint64_t, SequenceData*>& GetSequences() const { return m_data.sequences; }
|
||||
const unordered_flat_map<const ZoneEvent*, SeqRef>& GetZoneSeqRef() const { return m_data.zoneSeqRef; }
|
||||
const MemData& GetMemoryNamed( uint64_t name ) const;
|
||||
const unordered_flat_map<uint64_t, MemData*>& GetMemNameMap() const { return m_data.memNameMap; }
|
||||
const Vector<short_ptr<FrameImage>>& GetFrameImages() const { return m_data.frameImage; }
|
||||
@@ -846,6 +851,11 @@ private:
|
||||
tracy_force_inline void ProcessThreadGroupHint( const QueueThreadGroupHint& ev );
|
||||
tracy_force_inline void ProcessFiberEnter( const QueueFiberEnter& ev );
|
||||
tracy_force_inline void ProcessFiberLeave( const QueueFiberLeave& ev );
|
||||
tracy_force_inline void ProcessSeqCreate( const QueueSeqEvent& ev );
|
||||
tracy_force_inline void ProcessSeqResume( const QueueSeqEvent& ev );
|
||||
tracy_force_inline void ProcessSeqSuspend( const QueueSeqEvent& ev );
|
||||
tracy_force_inline void ProcessSeqRetire( const QueueSeqEvent& ev );
|
||||
SequenceData* GetOrCreateSequence( uint64_t id, int64_t t );
|
||||
|
||||
tracy_force_inline ZoneEvent* AllocZoneEvent();
|
||||
tracy_force_inline void ProcessZoneBeginImpl( ZoneEvent* zone, const QueueZoneBegin& ev );
|
||||
|
||||
Reference in New Issue
Block a user