extra/mcp/eval_guide.md
This document covers the bindings-layer detail that the analysis
guidance (tracy://prompt) does not.
ctx is a TracyServerBindings.Worker — the same object Tracy Assist's
C++ tools query through Worker::Get*. The pybind methods are the canonical
data surface. Common entry points:
get_all_zone_stats() (every callsite, large), get_root_zone_stats()
(top-level zones only, useful for "where is the program spending time"),
get_zone_stats(srcloc_id), get_child_zone_stats(srcloc_id) (subtract for
self-time), get_zone_durations(name), get_zone_count(),
get_all_zone_source_locations()get_all_gpu_zone_stats(), get_gpu_zone_durations(...),
get_gpu_contexts()get_frame_count(), get_frame_times(), get_frame_times_named(name),
get_frame_boundaries(), get_zones_in_frame(...)get_threads(), get_thread_name(tid), get_thread_context_switches(tid)get_messages(), get_plots(),
get_locks(), get_memory_events(), get_callstack_frames(...)get_sections() — timed code sections from
TracySectionEnter/TracySectionLeave instrumentation. Returns a list of
{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).get_capture_name(), get_capture_program(),
get_first_time(), get_last_time(), get_resolution(), get_host_info()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.
get_first_time() / get_last_time() bound the capture timeline.ZoneStats fields: count, total, min, max, avg, sum_sq. total
is the inclusive aggregate; use get_child_zone_stats(srcloc_id) to subtract
child time when you need self-time.get_all_zone_stats() returns dict[str, ZoneStats] keyed by an opaque label
of the form 'name (addr)[arch] <srcloc_id>'. The trailing <id> is the
source-location ID — the int accepted by get_zone_stats(int),
get_zone_durations_by_id, and friends. Parse it with a regex if you need
to join across calls.get_all_zone_source_locations() are the join key
between zone-name lookups and per-callsite queries.Small Python snippets for the queries you'll reach for most often:
# top 10 hottest zones by total time
top = sorted(ctx.get_all_zone_stats().items(),
key=lambda kv: kv[1].total, reverse=True)[:10]
for k, v in top:
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
# primary frame set timing
times = ctx.get_frame_times() # ns per frame
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
# stats for a named zone — find the srcloc id, then drill in
import re
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))
stats = ctx.get_zone_stats(sid)
For long-running queries pass async_mode=True to eval; it returns
{task_id, status: "running"}. Poll with the task tool
(action="poll", task_id=...).
tracy_mcp.py runs as a long-lived singleton process shared across every
MCP client (all VS Code windows), and every live_connect/load_capture
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.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.live_connect caps a live session at TRACY_MCP_LIVE_MEMORY_LIMIT_MB
(default 8192, override per-call or set 0 to disable) — a long-lived
session on a busy target otherwise grows unbounded and can OOM-kill the
whole server process. Hitting the cap disconnects that instance cleanly;
data collected so far stays queryable and save_trace-able.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.TRACY_MCP_MAX_INSTANCES (default 4); it drops a disconnected live
instance that's sat idle for TRACY_MCP_DISCONNECTED_TTL_S (default
1800s / 30 min) so a session is still there for analysis right after the
target disconnects, but doesn't linger forever if forgotten; and it drops
a file-loaded capture that's sat idle for TRACY_MCP_FILE_IDLE_TTL_S
(default 1800s) — safe to let expire since it's already durably on disk,
just load_capture it again.