diff --git a/extra/mcp/tracy_mcp.py b/extra/mcp/tracy_mcp.py index 50432363..28a8e787 100644 --- a/extra/mcp/tracy_mcp.py +++ b/extra/mcp/tracy_mcp.py @@ -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." diff --git a/python/bindings/ServerModule.cpp b/python/bindings/ServerModule.cpp index 261e4a13..42c5aae5 100644 --- a/python/bindings/ServerModule.cpp +++ b/python/bindings/ServerModule.cpp @@ -230,14 +230,21 @@ PYBIND11_MODULE( TracyServerBindings, m ) // --- Sections --- .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; } ) ) @@ -1098,7 +1105,11 @@ PYBIND11_MODULE( TracyServerBindings, m ) // --- 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