Merge pull request #1431 from alandtse/fix/mcp-live-worker-lock-race

fix(mcp): lock Worker reads vs receive thread
This commit is contained in:
Bartosz Taudul
2026-07-20 12:04:36 +02:00
committed by GitHub
3 changed files with 145 additions and 98 deletions
+14 -4
View File
@@ -23,9 +23,13 @@ data surface. Common entry points:
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
- Sections: `get_sections()` — timed code sections from
`TracySectionEnter`/`TracySectionLeave` instrumentation. Returns a list of
`{start, end, text}` dicts (start/end in ns).
`{category, start, end, text}` dicts (start/end in ns); `category` is the
section's named group (multiple unrelated section sets can coexist in one
capture).
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
- Readiness: `is_background_done()` — see "Instance lifecycle" below before
querying zone/symbol stats right after `load_capture`.
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
@@ -81,12 +85,18 @@ instance holds its *entire* trace — zones, messages, callstacks, memory
events — resident in memory for as long as that process runs. Nothing
frees it just because your conversation ends.
- `load_capture` returns before zone/symbol statistics finish building — a
background thread populates them after the file itself is read. Check
`is_background_done()` (or `background_done` in `list_instances`) before
relying on `get_all_zone_stats()` and similar stats-based queries; they
can return empty or partial results until it's true. For small captures
this finishes before you'd notice; for multi-GB ones it can take seconds.
- Call `unload_capture(instance_id)` as soon as you're done analyzing a
capture. Don't rely on automatic eviction as your primary cleanup path —
treat it as a backstop for forgotten sessions, not a substitute.
- `list_instances` reports `idle_seconds` and `connected` per instance; use
it to spot stale ones before starting a new session, especially if you're
about to load several captures for comparison.
- `list_instances` reports `idle_seconds`, `connected`, and `background_done`
per instance; use it to spot stale ones before starting a new session,
especially if you're about to load several captures for comparison.
- The server does provide two backstops, both configurable via environment
variables at startup: it evicts the least-recently-used *evictable*
instance (disconnected live sessions, or any loaded file capture — never
+12
View File
@@ -385,6 +385,11 @@ async def list_instances() -> list[dict]:
kicks in at the `TRACY_MCP_MAX_INSTANCES` cap (LRU, connected live
instances are never evicted) or after a disconnected live instance sits
idle past `TRACY_MCP_DISCONNECTED_TTL_S`.
`background_done: false` means a just-loaded capture is still building
zone/symbol statistics on a background thread — `get_all_zone_stats`
and similar stats-based `eval` queries can return empty or partial
results until this flips to true.
"""
now = time.time()
return [
@@ -394,6 +399,7 @@ async def list_instances() -> list[dict]:
"mtime": inst.mtime,
"live": inst.path is None,
"connected": inst.worker.is_connected() if inst.path is None and inst.worker else None,
"background_done": inst.worker.is_background_done() if inst.worker else None,
"idle_seconds": now - inst.last_used,
}
for name, inst in instances.items()
@@ -523,6 +529,12 @@ async def load_capture(path: str, alias: str | None = None) -> str:
If you don't already have a path, call `list_captures` first — it lists
.tracy files in the TRACY_CAPTURES_DIR environment directory.
Loading returns before zone/symbol statistics finish building — a
background thread populates them after the file itself is read. Check
`background_done` in `list_instances` (or poll it) before relying on
`get_all_zone_stats` and similar stats-based `eval` queries; they can
return empty or partial results until it's true.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
+119 -94
View File
@@ -25,6 +25,20 @@ using namespace pybind11::literals;
namespace tracy
{
// locked() wraps a capture-less Worker-reading lambda with
// Worker::ObtainLockForMainThread() before it touches w — the same
// cooperative lock the GUI already takes once per frame, so reads
// can't race the receive thread mutating live data underneath them.
template <typename Ret, typename... Args>
auto locked( Ret (*f)( const Worker&, Args... ) )
{
return [f]( Worker& w, Args... args ) -> Ret
{
auto lock = w.ObtainLockForMainThread();
return f( w, args... );
};
}
PYBIND11_MODULE( TracyServerBindings, m )
{
m.doc() = "Tracy Server (Analysis) Bindings";
@@ -192,56 +206,63 @@ PYBIND11_MODULE( TracyServerBindings, m )
.def( py::init<const char*, uint16_t, int64_t>(), "addr"_a, "port"_a, "memoryLimit"_a = -1 )
// --- Capture metadata ---
.def( "get_capture_name", &Worker::GetCaptureName )
.def( "get_capture_program", &Worker::GetCaptureProgram )
.def( "get_capture_time", &Worker::GetCaptureTime )
.def( "get_host_info", &Worker::GetHostInfo )
.def( "get_pid", &Worker::GetPid )
.def( "get_resolution", &Worker::GetResolution )
.def( "get_first_time", &Worker::GetFirstTime )
.def( "get_last_time", &Worker::GetLastTime )
.def( "get_cpu_manufacturer", &Worker::GetCpuManufacturer )
.def( "get_capture_name", locked( +[]( const Worker& w ) { return w.GetCaptureName(); } ) )
.def( "get_capture_program", locked( +[]( const Worker& w ) { return w.GetCaptureProgram(); } ) )
.def( "get_capture_time", locked( +[]( const Worker& w ) { return w.GetCaptureTime(); } ) )
.def( "get_host_info", locked( +[]( const Worker& w ) { return w.GetHostInfo(); } ) )
.def( "get_pid", locked( +[]( const Worker& w ) { return w.GetPid(); } ) )
.def( "get_resolution", locked( +[]( const Worker& w ) { return w.GetResolution(); } ) )
.def( "get_first_time", locked( +[]( const Worker& w ) { return w.GetFirstTime(); } ) )
.def( "get_last_time", locked( +[]( const Worker& w ) { return w.GetLastTime(); } ) )
.def( "get_cpu_manufacturer", locked( +[]( const Worker& w ) { return w.GetCpuManufacturer(); } ) )
// --- Counts ---
.def( "get_zone_count", &Worker::GetZoneCount )
.def( "get_gpu_zone_count", &Worker::GetGpuZoneCount )
.def( "get_lock_count", &Worker::GetLockCount )
.def( "get_plot_count", &Worker::GetPlotCount )
.def( "get_context_switch_count", &Worker::GetContextSwitchCount )
.def( "get_src_loc_count", &Worker::GetSrcLocCount )
.def( "get_callstack_sample_count", &Worker::GetCallstackSampleCount )
.def( "get_message_count", []( const Worker& w ) {
.def( "get_zone_count", locked( +[]( const Worker& w ) { return w.GetZoneCount(); } ) )
.def( "get_gpu_zone_count", locked( +[]( const Worker& w ) { return w.GetGpuZoneCount(); } ) )
.def( "get_lock_count", locked( +[]( const Worker& w ) { return w.GetLockCount(); } ) )
.def( "get_plot_count", locked( +[]( const Worker& w ) { return w.GetPlotCount(); } ) )
.def( "get_context_switch_count", locked( +[]( const Worker& w ) { return w.GetContextSwitchCount(); } ) )
.def( "get_src_loc_count", locked( +[]( const Worker& w ) { return w.GetSrcLocCount(); } ) )
.def( "get_callstack_sample_count", locked( +[]( const Worker& w ) { return w.GetCallstackSampleCount(); } ) )
.def( "get_message_count", locked( +[]( const Worker& w ) {
return w.GetMessages().size();
} )
} ) )
// --- Sections ---
.def( "get_sections", []( const Worker& w ) {
.def( "get_sections", locked( +[]( const Worker& w ) {
// GetSections() is keyed by category (multiple named section sets);
// flatten across categories, tagging each item with its category name.
py::list result;
for( auto& s : w.GetSections() )
for( const auto& kv : w.GetSections() )
{
py::dict d;
d["start"] = s.start.Val();
d["end"] = s.end.Val();
d["text"] = std::string( w.GetString( s.text ) );
result.append( d );
const char* category = w.GetSectionCategoryDescription( kv.first );
for( const auto& s : kv.second )
{
py::dict d;
d["category"] = std::string( category );
d["start"] = s.start.Val();
d["end"] = s.end.Val();
d["text"] = std::string( w.GetString( s.text ) );
result.append( d );
}
}
return result;
} )
} ) )
// --- Source locations / zones ---
.def( "get_src_loc", []( const Worker& w, int16_t id ) {
.def( "get_src_loc", locked( +[]( const Worker& w, int16_t id ) {
return w.GetSourceLocation( id );
} ).def( "get_zone_name", []( const Worker& w, int16_t id ) {
} ) ).def( "get_zone_name", locked( +[]( const Worker& w, int16_t id ) {
return w.GetZoneName( w.GetSourceLocation( id ) );
} )
} ) )
#ifndef TRACY_NO_STATISTICS
.def( "get_zone_stats", []( const Worker& w, int16_t id ) {
.def( "get_zone_stats", locked( +[]( const Worker& w, int16_t id ) {
const auto& stats = w.GetZonesForSourceLocation( id );
const size_t cnt = stats.zones.size();
return ZoneStats{ stats.min, stats.max, stats.total, stats.sumSq, cnt, cnt ? (double)stats.total / cnt : 0.0 };
} )
} ) )
#endif
.def( "get_all_zone_stats", []( const Worker& w ) {
.def( "get_all_zone_stats", locked( +[]( const Worker& w ) {
py::dict result;
#ifndef TRACY_NO_STATISTICS
for( const auto& kv : w.GetSourceLocationZones() )
@@ -255,7 +276,7 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
#endif
return result;
} ).def( "get_root_zone_stats", []( const Worker& w ) {
} ) ).def( "get_root_zone_stats", locked( +[]( const Worker& w ) {
// Aggregate stats for top-level (root) zones only — no nesting, safe to sum
// File-loaded data uses is_magic() — zones stored inline, not as short_ptr
struct Acc
@@ -298,10 +319,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result[name] = ZoneStats{ s.min, s.max, s.total, s.sumSq, s.count, avg };
}
return result;
} )
} ) )
// --- Per-occurrence zone data (for temporal correlation / distribution) ---
.def( "get_zone_durations", []( const Worker& w, const std::string& name, size_t maxSamples ) {
.def( "get_zone_durations", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
// Accumulates across ALL srclocs with this name (same name can appear at multiple srclocs)
std::vector<int64_t> result;
#ifndef TRACY_NO_STATISTICS
@@ -318,8 +339,8 @@ PYBIND11_MODULE( TracyServerBindings, m )
done_durations:;
#endif
return result;
}, "name"_a, "max_samples"_a = 100000 )
.def( "get_zone_occurrences", []( const Worker& w, const std::string& name, size_t maxSamples ) {
} ), "name"_a, "max_samples"_a = 100000 )
.def( "get_zone_occurrences", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
// Returns list of (start_ns, duration_ns) — accumulates across all srclocs with this name
std::vector<std::pair<int64_t, int64_t>> result;
#ifndef TRACY_NO_STATISTICS
@@ -336,8 +357,8 @@ PYBIND11_MODULE( TracyServerBindings, m )
done_occurrences:;
#endif
return result;
}, "name"_a, "max_samples"_a = 100000 )
.def( "get_zone_annotations", []( const Worker& w, const std::string& name, size_t maxSamples ) {
} ), "name"_a, "max_samples"_a = 100000 )
.def( "get_zone_annotations", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
// Returns text annotations attached to individual zone occurrences
std::vector<std::string> result;
#ifndef TRACY_NO_STATISTICS
@@ -358,8 +379,8 @@ PYBIND11_MODULE( TracyServerBindings, m )
done_annotations:;
#endif
return result;
}, "name"_a, "max_samples"_a = 10000 )
.def( "get_gpu_zone_durations", []( const Worker& w, const std::string& name, size_t maxSamples ) {
} ), "name"_a, "max_samples"_a = 10000 )
.def( "get_gpu_zone_durations", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
std::vector<int64_t> result;
for( const auto& kv : w.GetGpuSourceLocationZones() )
{
@@ -373,8 +394,8 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
done_gpu_dur:;
return result;
}, "name"_a, "max_samples"_a = 100000 )
.def( "get_gpu_zone_occurrences", []( const Worker& w, const std::string& name, size_t maxSamples ) {
} ), "name"_a, "max_samples"_a = 100000 )
.def( "get_gpu_zone_occurrences", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
std::vector<std::pair<int64_t, int64_t>> result;
for( const auto& kv : w.GetGpuSourceLocationZones() )
{
@@ -388,10 +409,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
done_gpu_occ:;
return result;
}, "name"_a, "max_samples"_a = 100000 )
} ), "name"_a, "max_samples"_a = 100000 )
// --- Callstack resolution ---
.def( "get_callstack_frames", []( const Worker& w, uint32_t callstackIdx ) {
.def( "get_callstack_frames", locked( +[]( const Worker& w, uint32_t callstackIdx ) {
py::list result;
const auto& cs = w.GetCallstack( callstackIdx );
for( size_t i = 0; i < cs.size(); ++i )
@@ -410,10 +431,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
}
return result;
}, "callstack_idx"_a )
} ), "callstack_idx"_a )
// --- Context switches per thread ---
.def( "get_thread_context_switches", []( const Worker& w, uint64_t tid, size_t maxSamples ) {
.def( "get_thread_context_switches", locked( +[]( const Worker& w, uint64_t tid, size_t maxSamples ) {
py::list result;
const auto* cs = const_cast<Worker&>( w ).GetContextSwitchData( tid );
if( !cs ) return result;
@@ -429,10 +450,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result.append( d );
}
return result;
}, "tid"_a, "max_samples"_a = 50000 )
} ), "tid"_a, "max_samples"_a = 50000 )
// --- CPU thread running time / migrations ---
.def( "get_cpu_thread_data", []( const Worker& w ) {
.def( "get_cpu_thread_data", locked( +[]( const Worker& w ) {
py::dict result;
for( const auto& kv : w.GetCpuThreadData() )
{
@@ -443,10 +464,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result[py::int_( kv.first )] = d;
}
return result;
} )
} ) )
// --- Zone occurrences with thread attribution ---
.def( "get_zone_occurrences_with_thread", []( const Worker& w, const std::string& name, size_t maxSamples ) {
.def( "get_zone_occurrences_with_thread", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
// Returns list of (start_ns, duration_ns, thread_id) — thread_id is the OS thread ID
std::vector<std::tuple<int64_t, int64_t, uint64_t>> result;
#ifndef TRACY_NO_STATISTICS
@@ -467,10 +488,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
done_occ_thread:;
#endif
return result;
}, "name"_a, "max_samples"_a = 100000 )
} ), "name"_a, "max_samples"_a = 100000 )
// --- Child zone stats: aggregate direct children of all occurrences of a parent zone ---
.def( "get_child_zone_stats", []( const Worker& w, const std::string& name, size_t maxParents ) {
.def( "get_child_zone_stats", locked( +[]( const Worker& w, const std::string& name, size_t maxParents ) {
// Uses SourceLocationZones for O(occurrences) lookup — avoids walking the full zone tree.
// File-loaded data sets is_magic() on child vectors (inline ZoneEvent, not short_ptr).
struct Acc
@@ -529,10 +550,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result[cname] = ZoneStats{ s.min, s.max, s.total, s.sumSq, s.count, avg };
}
return result;
}, "name"_a, "max_parents"_a = 100000 )
} ), "name"_a, "max_parents"_a = 100000 )
// --- Zone source location (file / line / function for LLM code navigation) ---
.def( "get_zone_source_location", []( const Worker& w, const std::string& name ) {
.def( "get_zone_source_location", locked( +[]( const Worker& w, const std::string& name ) {
py::dict result;
#ifndef TRACY_NO_STATISTICS
for( const auto& kv : w.GetSourceLocationZones() )
@@ -548,8 +569,8 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
#endif
return result;
}, "name"_a )
.def( "get_all_zone_source_locations", []( const Worker& w ) {
} ), "name"_a )
.def( "get_all_zone_source_locations", locked( +[]( const Worker& w ) {
// Returns {zone_name: {file, line, function, color}} for every unique zone name.
// Uses first srcloc found per name — sufficient for navigation purposes.
py::dict result;
@@ -568,10 +589,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
#endif
return result;
} )
} ) )
// --- Per-zone callstack samples (call paths leading into a zone) ---
.def( "get_zone_callstacks", []( const Worker& w, const std::string& name, size_t maxSamples ) {
.def( "get_zone_callstacks", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
py::list result;
#ifndef TRACY_NO_STATISTICS
for( const auto& kv : w.GetSourceLocationZones() )
@@ -608,10 +629,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
done_callstacks:;
#endif
return result;
}, "name"_a, "max_samples"_a = 1000 )
} ), "name"_a, "max_samples"_a = 1000 )
// --- Symbol-level sampling stats (inclusive / exclusive counts from call-stack profiling) ---
.def( "get_symbol_stats", []( const Worker& w ) {
.def( "get_symbol_stats", locked( +[]( const Worker& w ) {
py::list result;
for( const auto& kv : w.GetSymbolStats() )
{
@@ -632,10 +653,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result.append( d );
}
return result;
} )
} ) )
// --- Timestamps of all call-stack samples hitting a specific symbol ---
.def( "get_samples_for_symbol", []( const Worker& w, uint64_t symAddr ) {
.def( "get_samples_for_symbol", locked( +[]( const Worker& w, uint64_t symAddr ) {
py::list result;
const auto* samples = w.GetSamplesForSymbol( symAddr );
if( !samples ) return result;
@@ -647,10 +668,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result.append( d );
}
return result;
}, "sym_addr"_a )
} ), "sym_addr"_a )
// --- Hardware performance counter summary per symbol (IPC, cache-miss rate, branch-miss rate) ---
.def( "get_hw_sample_summary", []( const Worker& w ) {
.def( "get_hw_sample_summary", locked( +[]( const Worker& w ) {
py::list result;
for( const auto& kv : w.GetSymbolStats() )
{
@@ -689,10 +710,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result.append( d );
}
return result;
} )
} ) )
// --- Raw memory allocation events (ptr, size, timestamps) for temporal zone correlation ---
.def( "get_memory_events", []( const Worker& w, size_t maxCount, const std::string& poolName ) {
.def( "get_memory_events", locked( +[]( const Worker& w, size_t maxCount, const std::string& poolName ) {
py::list result;
for( const auto& kv : w.GetMemNameMap() )
{
@@ -717,10 +738,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
if( !poolName.empty() ) break;
}
return result;
}, "max_count"_a = 100000, "pool_name"_a = "" )
} ), "max_count"_a = 100000, "pool_name"_a = "" )
// --- Per-lock wait/contention stats (total and average wait time) ---
.def( "get_lock_wait_stats", []( const Worker& w ) {
.def( "get_lock_wait_stats", locked( +[]( const Worker& w ) {
py::list result;
for( const auto& kv : w.GetLockMap() )
{
@@ -763,10 +784,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result.append( d );
}
return result;
} )
} ) )
// --- GPU zone stats ---
.def( "get_all_gpu_zone_stats", []( const Worker& w ) {
.def( "get_all_gpu_zone_stats", locked( +[]( const Worker& w ) {
py::dict result;
for( const auto& kv : w.GetGpuSourceLocationZones() )
{
@@ -778,7 +799,7 @@ PYBIND11_MODULE( TracyServerBindings, m )
result[name] = GpuZoneStats{ s.min, s.max, s.total, s.sumSq, cnt, (double)s.total / cnt };
}
return result;
} ).def( "get_gpu_child_zone_stats", []( const Worker& w, const std::string& name, size_t maxParents ) {
} ) ).def( "get_gpu_child_zone_stats", locked( +[]( const Worker& w, const std::string& name, size_t maxParents ) {
// GPU equivalent of get_child_zone_stats — returns per-child-name GPU duration stats
// for all occurrences of the named parent GPU zone.
struct Acc
@@ -828,13 +849,13 @@ PYBIND11_MODULE( TracyServerBindings, m )
result[cname] = GpuZoneStats{ s.min, s.max, s.total, s.sumSq, s.count, (double)s.total / s.count };
}
return result;
}, "name"_a, "max_parents"_a = 100000 )
} ), "name"_a, "max_parents"_a = 100000 )
// --- Frame sets ---
.def( "get_frame_count", []( const Worker& w ) {
.def( "get_frame_count", locked( +[]( const Worker& w ) -> size_t {
auto frames = w.GetFramesBase();
return frames ? w.GetFrameCount( *frames ) : 0;
} ).def( "get_all_frame_stats", []( const Worker& w ) {
} ) ).def( "get_all_frame_stats", locked( +[]( const Worker& w ) {
std::vector<FrameStats> result;
for( const auto* fd : w.GetFrames() )
{
@@ -846,7 +867,7 @@ PYBIND11_MODULE( TracyServerBindings, m )
cnt, cnt ? (double)fd->total / cnt : 0.0 } );
}
return result;
} ).def( "get_frame_boundaries", []( const Worker& w ) {
} ) ).def( "get_frame_boundaries", locked( +[]( const Worker& w ) {
auto* fd = w.GetFramesBase();
if( !fd ) return std::vector<std::pair<int64_t, int64_t>>{};
const size_t cnt = w.GetFrameCount( *fd );
@@ -855,7 +876,7 @@ PYBIND11_MODULE( TracyServerBindings, m )
for( size_t i = 0; i < cnt; ++i )
result.emplace_back( w.GetFrameBegin( *fd, i ), w.GetFrameEnd( *fd, i ) );
return result;
} ).def( "get_frame_times", []( const Worker& w ) {
} ) ).def( "get_frame_times", locked( +[]( const Worker& w ) {
auto* fd = w.GetFramesBase();
if( !fd ) return std::vector<int64_t>{};
const size_t cnt = w.GetFrameCount( *fd );
@@ -864,7 +885,7 @@ PYBIND11_MODULE( TracyServerBindings, m )
for( size_t i = 0; i < cnt; ++i )
times.push_back( w.GetFrameTime( *fd, i ) );
return times;
} ).def( "get_frame_times_named", []( const Worker& w, const std::string& name ) {
} ) ).def( "get_frame_times_named", locked( +[]( const Worker& w, const std::string& name ) {
for( const auto* fd : w.GetFrames() )
{
if( !fd ) continue;
@@ -879,7 +900,7 @@ PYBIND11_MODULE( TracyServerBindings, m )
}
}
return std::vector<int64_t>{};
} ).def( "get_zones_in_frame", []( const Worker& w, size_t frameIdx ) {
} ) ).def( "get_zones_in_frame", locked( +[]( const Worker& w, size_t frameIdx ) {
// Returns {zone_name: {count, total_ns}} for all CPU zones that STARTED within
// the specified frame's time window. Uses sorted thread timelines for early exit.
auto* fd = w.GetFramesBase();
@@ -970,10 +991,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
result[zname] = d;
}
return result;
}, "frame_idx"_a )
} ), "frame_idx"_a )
// --- Messages ---
.def( "get_messages", []( const Worker& w ) {
.def( "get_messages", locked( +[]( const Worker& w ) {
const auto& msgs = w.GetMessages();
std::vector<MessageInfo> result;
result.reserve( msgs.size() );
@@ -987,10 +1008,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
(uint64_t)msg.thread } );
}
return result;
} )
} ) )
// --- Plots ---
.def( "get_plots", []( const Worker& w ) {
.def( "get_plots", locked( +[]( const Worker& w ) {
static const char* plotTypeStr[] = { "User", "Memory", "SysTime", "Power" };
std::vector<PlotSummary> result;
for( const auto* pd : w.GetPlots() )
@@ -1005,10 +1026,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
std::string( typeStr ) } );
}
return result;
} )
} ) )
// --- Memory pools ---
.def( "get_memory_pools", []( const Worker& w ) {
.def( "get_memory_pools", locked( +[]( const Worker& w ) {
std::vector<MemPoolSummary> result;
for( const auto& kv : w.GetMemNameMap() )
{
@@ -1018,10 +1039,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
name, md->high, md->low, md->usage, md->data.size() } );
}
return result;
} )
} ) )
// --- Locks ---
.def( "get_locks", []( const Worker& w ) {
.def( "get_locks", locked( +[]( const Worker& w ) {
std::vector<LockSummary> result;
for( const auto& kv : w.GetLockMap() )
{
@@ -1042,10 +1063,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
lm->threadList } );
}
return result;
} )
} ) )
// --- GPU contexts ---
.def( "get_gpu_contexts", []( const Worker& w ) {
.def( "get_gpu_contexts", locked( +[]( const Worker& w ) {
static const char* gpuTypeStr[] = {
"Invalid", "OpenGL", "Vulkan", "OpenCL", "Direct3D12", "Direct3D11", "Metal", "Custom", "CUDA", "Rocprof", "WebGPU" };
static size_t numTypes = sizeof(gpuTypeStr) / sizeof(gpuTypeStr[0]);
@@ -1060,10 +1081,10 @@ PYBIND11_MODULE( TracyServerBindings, m )
name, ctx->count, std::string( typeStr ), ctx->thread } );
}
return result;
} )
} ) )
// --- Threads ---
.def( "get_threads", []( const Worker& w ) {
.def( "get_threads", locked( +[]( const Worker& w ) {
// Returns list of dicts to avoid raw-pointer pybind11 ownership issues
py::list result;
for( const auto& t : w.GetThreadData() )
@@ -1077,14 +1098,18 @@ PYBIND11_MODULE( TracyServerBindings, m )
result.append( d );
}
return result;
} ).def( "get_thread_name", []( const Worker& w, uint64_t tid ) {
} ) ).def( "get_thread_name", locked( +[]( const Worker& w, uint64_t tid ) {
return w.GetThreadName( tid );
} )
} ) )
// --- Connection control ---
.def( "is_connected", &Worker::IsConnected )
.def( "shutdown", &Worker::Shutdown )
.def( "disconnect", &Worker::Disconnect );
.def( "disconnect", &Worker::Disconnect )
// Plain relaxed atomic like IsConnected() above — no locked()
// needed. True once post-load zone/symbol stats have finished
// building; get_all_zone_stats() et al. can be empty until then.
.def( "is_background_done", &Worker::IsBackgroundDone );
// -------------------------------------------------------------------------
// FileRead