`pip install mcp` now installs 2.x by default (v1 is
maintenance-only going forward), and nothing in this repo pinned a
version, so a fresh install already broke: v2 removed
mcp.server.fastmcp entirely, no compat shim.
- Import/class: mcp.server.fastmcp.FastMCP ->
mcp.server.mcpserver.MCPServer.
- Transport options (host, port, sse_path, streamable_http_path)
moved off the constructor and the settings object onto run();
mutating mcp_server.settings.port now raises ValueError.
- _http_ping and the startup message read _SSE_PATH/
_STREAMABLE_HTTP_PATH constants instead of settings.sse_path/
settings.streamable_http_path -- v2 no longer exposes them to
read back. Values match the SDK's own defaults in both versions
("/sse", "/mcp"), so served paths are unchanged.
- Adds extra/mcp/requirements.txt (mcp>=2.0.0,<3) -- there was no
dependency manifest at all before this.
Decorator API is unchanged; every tool/resource here was already
async def, so the "sync handlers now run on worker threads" change
doesn't apply.
Verified in an isolated venv (mcp 2.0.0): module imports cleanly
with all 10 tools and 2 resources registered, the server starts
and serves streamable-http on the expected path, and _http_ping
correctly reports a running instance as alive.
The CMake option only controlled whether NFD is built and linked, but
the file selector code is guarded by the TRACY_NO_FILESELECTOR macro,
which the build never defined - configuring with -DNO_FILESELECTOR=ON
failed to compile (nfd.h not found in TracyFileselector.cpp,
BackendWayland.cpp and BackendGlfw.cpp). Define the macro for the
profiler target when the option is set.
When the native dialog failed, the popup said "File selector cannot
be displayed. Check nfd library implementation for details." but never
showed any details: NFD_GetError() was never read, so the actual
failure reason (e.g. the xdg-desktop-portal lacking a FileChooser
implementation on the session) was invisible and the user was stuck
without a way to open or save traces.
Capture NFD_GetError() into a static string when
NFD_OpenDialogU8_With()/NFD_SaveDialogU8_With() return NFD_ERROR and
display it in the popup. The copy is required: the returned pointer
references NFD-internal storage (the D-Bus error message or a static
buffer) that is overwritten by the next NFD call or cleared on
shutdown. In TRACY_NO_FILESELECTOR builds no NFD exists, so the
previous fallback line is kept there.
The error is shown as a single line (TextUnformatted) rather than
wrapped: in an AlwaysAutoResize window the wrap width is derived from
the previous frame's window size, which feeds back into the auto-size
and oscillates, converging to a narrow multi-line column for a ~110
character D-Bus message.
When a child group was expanded, the member rows were not visibly
indented under the group name: each row starts at the tree indent
(column 0 is IndentEnable by default, and TableBeginRow() latches the
open group's TreePush indent), so a single Indent() put the label two
IndentSpacing from the cell origin. The group header, however, leads
with the source location color box plus item spacing before its tree
arrow, so its name starts four IndentSpacing further right than the
member labels - the child list read as a continuation of the header
row instead of a subtree.
Add a second Indent() (and the balancing Unindent()) so the member
labels land clearly to the right of the group name. The full-row
Selectable behavior is unchanged. GPU zones are unaffected: their
rows carry no color box, so the member labels were already one level
right of the group name.
The imgui 1.92.9b ini writer omits 'Column N' lines for columns without
saved data (e.g. the unsized, unsorted 'Zone' column of the child zones
table, which only persists the default Time sort). On restore, the
missing entry keeps Index == -1, the sequential fallback in
TableLoadSettingsForColumns() assigns it to the remaining column, and
TableFixDisplayOrder() sorts DisplayOrder -1 to the end - flipping the
column order (Time | Zone in the zone info child list) for the session.
Guard the non-reorderable load path against phantom entries.
Upstream: https://github.com/ocornut/imgui/issues/9519
The group name-sort key was always the source location name, but
single-member groups display the zone-level name, which prefers the
runtime ZoneName() string. Such groups sorted by a name the user could
not see. The key now mirrors the display: zone name for single-member
groups, source location name for the rest. GPU zones are unaffected
(their names always resolve from the source location).
The child zone list was always sorted by time. It is now a sortable
table with Zone and Time columns, like the statistics view and the
time distribution table in the same window: Time is the default sort
(descending, as before), Zone sorts by name with time as the
tiebreak, and groups plus their members follow the same selection.
The sort choice persists across zones and the group toggle. Group
and child names are looked up once per draw only when name-sorting.
Otherwise on unmap you can get the validation layer error:
"ID3D12Resource3::ID3D12Resource::Unmap: pWrittenRange does not point to an empty D3D12_RANGE and
the heap type is D3D12_HEAP_TYPE_READBACK. Readback resources can be written by the CPU but there's
not much utility. The rationale is that readback heaps are stuck in COPY_DEST state such that the
GPU can never use what the CPU is writing. The range [0, 524288) should be empty (Begin >= End)."
Check also https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12resource-unmap
"This indicates the region the CPU might have modified" -> tracy essentially declares the entire range as written, but it's not even a cpu->gpu write buffer -> bug.
TracyConfig.cmake now sets TRACY_VERSION_STRING and a
TracyConfigVersion.cmake is generated and installed, so
find_package(Tracy X.Y CONFIG) works against installed trees.
Compatibility mode is SameMinorVersion: for a 0.x project the minor
version is the compatibility unit, and consumers who need to pin the
exact version can use find_package(... EXACT). SamePatchVersion is
not an option - it requires CMake 4.4 while the project minimum is
3.13.
SysTraceStart previously reported success even when no perf events
could be opened, leaving a worker thread running over no buffers
and an empty CPU section without explanation; it now fails in that
case. Also log when /proc/kallsyms cannot be read, which otherwise
silently results in ??? kernel stack frames.
perf_event_attr.use_clockid only exists since Linux 4.1 and
sample_max_stack since Linux 4.8; older kernels (e.g. the 3.18
kernels of 32-bit Android devices) reject the attributes with
EINVAL/E2BIG, which silently disabled all of system tracing.
Classify the running kernel from uname(2) and only send the fields
its perf_event_open ABI supports.
The symbol worker thread, which answers source code queries, only exists
when call stack support is compiled in. Handle the query directly in the
main thread so it is always answered and the server can terminate.
The mrc to c13/c0/3 (thread id register) is undefined on ARMv5 (e.g.
ARM926EJ-S) and SIGILL'd during init. __builtin_thread_pointer lowers
to the same mrc on targets with a thread pointer register and to
__aeabi_read_tp on soft-TLS targets.
Delayed init was introduced 2019-02-19 in ef5e30056 and was lazy init at
that time, matching the name.
This changed 2020-05-19 in 4eb78f5c8, when auto-init was added, making
delayed init not delayed anymore.
While the option is still needed to make manual lifetime work, and it has
to be enabled on apple, there's no reason anymore to expose it to users.
The macro fast paths only reference GetProfiler from -O0 code, direct
calls, and ON_DEMAND builds: the single GetProfiler() call on the macro
path sits behind the constexpr-dead callstack guard and is folded away
at -O1+, so Release builds carried no config fingerprint and mismatched
clients linked silently.
GetToken is the hot-path binding of every queueing macro and is
referenced at all optimization levels; mangle it the same way so
mismatches fail at link time in Release builds too.
ImGui OpenGL loader uses dlopen / dlsym / dlclose to load the OpenGL
implementation.
Linking with libdl is not needed with glibc >= 2.34 (released in 2021):
NEWS for version 2.34
=====================
Major new features:
* In order to support smoother in-place-upgrades and to simplify
the implementation of the runtime all functionality formerly
implemented in the libraries libpthread, libdl, libutil, libanl has
been integrated into libc. New applications do not need to link with
-lpthread, -ldl, -lutil, -lanl anymore.
(...)
Older glibc versions and also possibly other libc implementations still
need the libdl link.
The gpu backends each used a bare 255 as the sentinel for a
not-yet-initialized context id. Define InvalidGpuContextId (-1) in
TracyQueue.hpp and replace the scattered 255s and their asserts
with it.
Id exhaustion is handled by an error message and an assert in the
new NextGpuContextId() getter. Non-assert builds continue execution,
at which point they are no longer valid. Handling this code path
is out of scope here. Various attempts at handling the exhaustion
problem have been otherwise purged from the API implementations.
Alloc and free events stored the OS thread, so the memory view's
Zone alloc/Zone free columns resolved the zone on the OS thread's
timeline instead of the currently-running fiber. Attribute them to
the active fiber the same way zone and message events are
(follow ThreadData::fiber).
The decoder recovered offset-encoded 16-bit string lengths into a
uint16_t, so sz += ProtocolOffset8Bit truncated lengths in [65536,
65791] back into [0, 255] in release builds, desynchronizing the
stream. Read the wire value into uint16_t sz16 and recover into a
uint32_t sz, relying on automatic promotion for the addition.
Align the client asserts with the encoder's actual capacity
(ProtocolOffset8Bit + uint16 max).
Define CMAKE_PARALLEL once per environment (--parallel 2 on GitHub CI,
unlimited locally) and use it across every cmake build, so all builds
are OOM-protected, not just the profiler GUI.
Set contents: read permissions on every workflow and add per-workflow
concurrency groups keyed on the git ref to deduplicate concurrent runs.
Release workflow keeps cancel-in-progress: false so a release build is
never canceled; job-level contents: write on attach-to-release is
preserved. Concurrency groups use hardcoded prefixes so reusable
workflows called from release.yml do not inherit the caller workflow
name and collide.
Ship the version header alongside the rest of public/common so installed
users can query the Tracy version at compile time; it is the single
source of truth CMake already parses via cmake/version.cmake.
TracyFormat.h is transitively included by installed public headers
(TracyC.h, TracyProfiler.hpp, TracyScoped.hpp) but was missing from
the common_includes install list in both build systems, breaking
installed users. Add it to the list in CMakeLists.txt and meson.build.
Build the CLI tools in a dedicated ubuntu:24.04 workflow and pack them
with the AppImage into a single linux-<version>.zip, matching the
Windows/macOS release shape.
ImGui's CalcWordWrapPosition() only cuts a word mid-word when it fits on
no line, i.e. when it is wider than the full line width. PrintTextWrapped
passes the leftover width for a glued continuation segment's first line,
so a continuation word wider than the leftover (but fitting on the next
full-width line) was being cut mid-word. Detect that case and move the
whole word to the next line instead.
CUDACtx's constructor writes GpuNewContext directly through
QueueSerialFinish(), unlike every other GPU backend (Vulkan, OpenGL,
D3D11/12, Metal, WebGPU, Rocprof), which all defer it via
GetProfiler().DeferItem() so it survives on-demand's per-connection
queue clear.
A profiler connecting any time after the CUDA context is created (in
practice: any time after process start) never receives GpuNewContext.
The GpuContextName message that Name() sends right after (already
correctly deferred) then crashes the server's
Worker::ProcessGpuContextName with an unregistered context id
(assert(ctx) fails; undefined behavior in release builds).
Same fix already applied to the Rocprof backend in #1336. Fixes#1171.
Includes a repro test under tests/cuda/repro/on_demand/, mirroring the
structure #1336 added for Rocprof: a minimal CUDA program that creates
an on-demand context (repro.cu/CMakeLists.txt), and a check_gpu_zones
tool that loads the resulting .tracy file and verifies the GPU context
was named and populated with zones. Verified locally: unpatched
tracy-capture crashes on the first connection attempt; patched, three
consecutive connect/disconnect cycles all succeed and check_gpu_zones
reports a named context with recorded zones.
The guide claimed "two backstops" and only described the LRU cap
and the disconnected-live TTL, omitting the new file-idle TTL
entirely -- a cold agent reading it would have no idea idle
file-loaded captures now expire too, or what env var controls it.
Automatic eviction only ever covered disconnected live instances
(_evict_disconnected_idle explicitly skipped anything with a
path), so a file-loaded capture -- potentially many GB -- stayed
resident forever until the TRACY_MCP_MAX_INSTANCES cap forced an
LRU eviction to make room. list_instances already documented
"unload_capture instead of waiting for automatic eviction" as the
alternative, but no automatic path actually existed for this case.
Generalizes the sweep into _evict_idle: file-loaded instances now
get their own idle-since-last-use TTL (TRACY_MCP_FILE_IDLE_TTL_S,
default 1800s matching the disconnected-live TTL). Safe to evict
on a timer since they're already durably on disk -- load_capture
brings them back. Connected live instances are untouched, same as
before.
Addresses the "server just vanishes" pattern from
alandtse/tracy#2, where Unable to connect gives no way to tell a
segfault apart from a hang and required manually killing an
unresponsive process before restart.
- faulthandler.enable() at startup writes a thread-state
traceback to tracy_mcp.crash.log on a genuine fatal crash
(works on Windows via SetUnhandledExceptionFilter).
- The periodic sweep loop now also logs a heartbeat (uptime,
instance/task counts, evictions) so a hung event loop is
distinguishable from a dead process by the last timestamp on
disk.
- _is_our_server_running() now backs its os.kill(pid, 0) check
with an HTTP self-ping. A deadlocked-but-alive process passed
the old PID-only check, silently blocking restart; a
non-responsive server is now reported by PID and a fresh
instance starts on a new port instead.
live_connect wrapped Worker(addr, port) with no memoryLimit, so
the binding default (-1, unlimited) applied. A long-lived live
session on a busy target grows unbounded -- every zone/message/
memory event stays resident until disconnect -- and can OOM-kill
the whole server process rather than just that one instance.
Worker already has a graceful cap: TracyWorker.cpp's receive loop
checks memoryLimit and calls QueryTerminate() + a clean disconnect
once exceeded, instead of continuing to grow. Just wasn't wired up
from Python.
Adds memory_limit_mb to live_connect (defaults to
TRACY_MCP_LIVE_MEMORY_LIMIT_MB, 8192 if unset; 0 disables),
converts to bytes for the Worker constructor, and reports the
active limit in the connect response.
There is a legitimate, if rare, way to get nonReentrantCount == 0 with
total != 0 — an outer zone that never terminated before the capture ended,
whose reentrant inner zone did terminate. The outer never contributes to
slz (no end event), but it did increment the stack count, so the inner one
is correctly recorded as a reentry.
The child calls display normalized hotness colors by the sum of two
independently tracked maxima, which can occur on different addresses.
The denominator could therefore exceed any real combined cost, so
nothing reached full heat and the color scale changed meaning when the
child calls display was toggled. Track the maximum of the per-address
sums instead.
Rows in the sampling statistics were only clickable when the symbol had
exclusive samples. In the "with children" accumulation mode this made
symbols which never directly executed, such as dispatchers or wrappers
with all cost in their callees, visible but completely inert, even
though the entry stacks window can now display how such symbols were
reached. Base the interactivity on the count shown in the current
accumulation mode. The popup menu items already disable themselves when
their data is not available.
The inline function expansion follows the same rule, so expanding such
a symbol now lists its inline functions with their inclusive counts
instead of showing an empty tree.
The find zone samples list shares the drawing code with the sampling
statistics window, which computed the percentage denominator from its
own state: the whole-trace sample count, or the statistics range
filter, if one was active. The find zone counts are scoped to the
matched zones, so the percentages mixed two meanings in one table: the
time column was relative to the zone selection while the count column
was relative to the whole trace, and changing the range filter in the
statistics window silently rescaled it.
Pass the denominator from the caller. Find zone sums its zone-scoped
counts, so both columns are now relative to the selection, and the
statistics window computes the same denominator as before.
The symbol disassembly tool scoped its cost data to the statistics
range filter. This is invisible UI state which the model cannot see, so
range-limited numbers were indistinguishable from whole-trace figures
and could silently change between tool calls. The other sampling tools
always report whole-trace data; do the same here.
The heuristic reconstruction picks the call stack from the samples
within the zone's time span. Context switch samples are always parked
at the scheduler, so for zones which spent their time blocked they
dominated the root selection and the reconstructed stack showed the
scheduler path instead of the zone's real call stack. A zone covering
only context switch samples now reconstructs nothing instead.
Context switch samples are excluded from the sampling statistics, but
the flame graph built from thread samples included them. Threads which
spend time blocked accumulated large scheduler towers which none of the
other sampling views show, and the flame graph totals did not
correspond to the statistics for the same trace. Filter the samples the
same way the trace load jobs do.
The per-symbol exclusive counts reported by the tool come from the
sampling statistics, which exclude context switch samples, but the
total time reference was computed from the full sample count. This
inflated the total and made every symbol look proportionally cheaper
to the model. Use the same denominator as the statistics window.
The job which builds the symbol samples and child samples maps called
CompressThread, which updates the lookup cache and can insert into the
compression map, while the timeline processing job concurrently reads
the map, deliberately using the raw lookup to avoid this exact hazard.
All threads are already present in the compression data loaded from the
trace, so use the raw lookup as well.
When displaying the hottest inline function's name in place of the base
symbol name, the symbol map lookup was dereferenced without checking
for a missing entry, crashing in release builds. Keep the base symbol
name when the inline symbol has no symbol data.
Several code paths read the per-symbol sample lists, symbol statistics
or child sample data without checking the readiness flags. These
structures are populated by background jobs during trace load, and the
flags are the only synchronization mechanism, so reading early races
the jobs and trips the readiness asserts in debug builds. The find zone
samples list checked a different flag than the data it reads requires,
and two symbol view conditions called the accessor before the readiness
check made elsewhere in the same function. The trace information window
performed no check at all, while it is typically open during loading.
The denominator includes samples with unresolved call stacks and
samples belonging to filtered-out rows, so describing it as the number
of samples attributable to the displayed symbols was overstating what
the code does.
The button was gated on the entry stack maps being non-empty, but the
sample entry stacks window also needs the symbol data to display
anything. Without it, clicking the button silently closed the window
through its null guard. Match the gate used by the statistics window
menu items.
Since the inclusive count of an aggregated symbol entry is taken from
the base symbol's own statistics, a row whose base symbol could not be
resolved can display an inclusive count of zero while its inline
functions have nonzero counts. With the relative inline display active,
the per-inline percentages divided by the base count, printing infinity
in such cases. The time percentage variant divided by the same count
scaled by the sampling period, which cancels out, so both variants can
share the guarded reciprocal.
SortedVector considered an appended element equal to the last one to
break the ordering, marking the vector as unsorted. A non-decreasing
sequence is sorted, so only a strictly smaller element has to trigger
the marker. Equal keys are common: child sample vectors receive
identical timestamps whenever a recursive call stack contains the same
call site twice, which flipped the vectors to unsorted on virtually
every recursive workload and caused the lazy sort in GetChildSamples to
run over and over again while holding the data lock.
The lazy sort machinery handles duplicate keys correctly, as both the
prefix and tail merge windows are computed with lower bounds.
The denominator subtracts context switch sample counts from total
sample counts, both for the whole trace and within the range filter.
The subtraction could underflow: on traces with inconsistent sample
ordering the binary searches over the unsorted sample vectors can
return too few samples, and a sample which arrives with a duplicated
timestamp is merged into the existing entry while its context switch
classification is still recorded, so the context switch samples are not
a strict subset of the samples. An underflow made every percentage in
the table display as zero.
Clamp the subtraction, per thread in range mode, so that one thread
with bad data does not affect the counts of the others.
Context switch samples were appended in arrival order. Samples which
were postponed due to missing context switch data are replayed after
newer samples were already classified, so the vector could become
unordered. Everything that reads it assumes time order: the wait stacks
range filter, the sampling statistics percentage denominator, and the
context switch sample filters in the trace load jobs. In the load jobs
an unordered vector could silently disable the filtering for the rest
of a thread, reintroducing the context switch samples into the symbol
and child sample maps.
Use a SortedVector and restore the ordering at the points where it can
break: after the postponed sample replay and when saving a trace. The
save file version is bumped, so that the sort order check on load is
only performed for traces saved by previous versions.
The tool only reported "was executing" entry stacks, while the sample
entry stacks window now defaults to showing the stacks through which a
symbol was reached. The model was told there is no data for symbols
which never executed directly, even when the window right next to it
displayed their entry stacks.
Add an optional "mode" parameter with the "reached" (non-reentrant),
"reached_recursive" and "executing" values. The default matches the UI
default. Inline symbols are now accepted, as the reached maps cover
them directly, and the executing mode can use their exact statistics.
The reported mode is included in the tool output, and the chat view
shows it in the tool call label.
The tool description keeps the mode summaries short. The detailed
guidance on choosing a mode is in the optimization skill, so it only
occupies context when the skill is loaded.
LLM tool calls execute on the LLM worker thread, while the render
thread holds the worker data lock for the duration of each frame and
the network thread mutates worker data under the same lock during live
capture. The tools accessed worker data with no synchronization at all.
Some of the accessors also lazily modify worker state, such as the
postponed symbol list sorting or the lazy sorting of child sample
vectors, so this raced even in a fully loaded trace.
Take the data lock in the five tools which access worker state. No LLM
locks are held while tools execute, so no lock ordering cycle with the
render thread is possible. Tools which perform network requests do not
touch worker data and remain lockless, as holding the lock across a
network transfer would stall the profiler.
The background jobs which compute sampling statistics during trace load
do not hold the data lock. The readiness flags are the synchronization
mechanism there, so gate the sampling tools on them. This also matches
the readiness asserts in the worker accessors these tools call.
With an active range filter, the percentage denominator was a
theoretical capacity estimate: the number of samples a single thread
would produce if it ran continuously through the range at the nominal
sampling rate. Actual sample volume scales with total CPU occupancy
across all threads, so the estimate was off in either direction: on
multi-core workloads percentages were inflated several times over and
could exceed 100%, while on mostly idle workloads they were deflated.
Toggling the range filter also silently changed what the percentages
meant, as the whole-trace mode divides by the collected sample count.
Count the samples actually present in the range instead, and exclude
context switch samples from both denominators, as they cannot be
attributed to any displayed symbol. Percentages now mean the same thing
with and without a range filter: the share of attributable samples.
Time percentages are intentionally unchanged. They are normalized by
wall clock time in both modes, where exceeding 100% legitimately means
more than one core was busy.
During live capture, child samples were appended to their per-address
vectors in arrival order. Samples postponed due to missing context
switch data are replayed after newer samples were already processed, so
the vectors could become unordered. All range-limited queries binary
search these vectors by time and would silently return wrong results.
Inserting in sorted order at collection time would require a mid-vector
insertion for every stack frame of every replayed sample, so instead
the vectors are now SortedVector and are sorted lazily when accessed,
following what the inline symbol list already does. This also restores
proper query results for traces with inconsistent sample order.
Context switch samples are excluded from sampling statistics, as they
are not produced by the statistical profiling timer and their stacks
are always parked at the scheduler. During live capture this exclusion
is structural, as such samples never enter the statistics processing
path. On trace load, however, only the job which computes symbol
statistics and the instruction pointer map filtered them out. The job
which builds the per-symbol sample lists and the child sample map did
not, so on a loaded trace these two structures included tens of
thousands of context switch samples that a live session would not
count.
This made range-limited statistics counts exceed the whole-trace
counts, inflated child sample costs in the symbol view, and caused the
same trace to show different numbers live and after a save and reload.
On the test trace, __schedule reported 56 exclusive samples but 32747
entries in its sample list.
Apply the same context switch sample filter when building the symbol
samples and child samples maps.
When inline functions are aggregated into their base symbols in the
sampling statistics, the inclusive counts were summed up, the same as
the exclusive counts. This is wrong. Exclusive counts partition the
samples, as each sample is attributed to exactly one symbol. Inclusive
counts overlap: an inline function can only ever be on the stack within
a frame group whose base is its parent symbol, so every sample counted
for an inline function is also counted for the base symbol. Summing
therefore counted the same sample multiple times, inflating the counts,
the sort order and the displayed percentages, in the worst observed
case by a factor of 7.
The base symbol's own inclusive count is exactly the correct value for
the aggregated entry, so use it directly.
The statistics window can display rows for which the sample entry
stacks window cannot show anything: symbols with no symbol data,
symbols that only have range-mode sample counts without callstack
statistics, or any row while the statistics are still being computed
in the background. Grey out the menu item in such cases, following
what the "View symbol" item already does.
The button was displayed unconditionally, allowing the sample entry
stacks window to be opened for symbols without any sample data. Gate it
on the availability of callstack sample statistics and a non-empty
reached map, following what the call stack window does. Checking for
statistics readiness also prevents asserting when the button is used
while a trace is still being loaded in the background.
The window can be opened for a symbol which has no symbol stats or no
symbol data. For example, the statistics window can produce clickable
rows for symbols that are not present in the symbol map. Close the
window instead of dereferencing null pointers.
The instruction tooltip advertised the number of "was executing" entry
call stacks, but middle-clicking opens the sample entry stacks window,
which now defaults to the "was reached" mode. Count the non-reentrant
reached stacks instead, so the tooltip matches what the window shows.
Gating on the reached map also makes entry stacks accessible for
symbols which only ever appear deeper in the call stack. This can
happen when inspecting instructions whose cost consists purely of
child samples.
SymbolStats now has two additional entry stack maps, which record the
call stacks below every occurrence of a symbol in a sample, not only
when the symbol was at the top of the stack:
- wasReached counts each occurrence separately. If a symbol re-enters
itself through recursion, every re-entry adds an entry stack, which
will itself contain the symbol somewhere below.
- wasReachedNonReentrant counts only the outermost occurrence, so each
sample contributes exactly once and recursion is ignored. The sum of
counts in this map equals the symbol's inclusive sample count.
To achieve this, the call stack is walked bottom-up, which makes the
first encountered occurrence of a symbol the outermost one. Symbols at
inline positions get the remaining inline frames of their frame group
as a synthetic frame, mirroring what was already done for the top of
the stack.
The wasExecuting and wasExecutingBase maps are now filled by the same
walk, as its topmost-frame special case. The keys they receive are
identical to what the previous code produced.
Note that there is no "base" variant of the reached maps. A base symbol
is present as the last frame of every frame group that contains its
inline functions, so its wasReached map already covers the whole symbol
at base granularity. Merging in the inline symbols' maps, as done for
wasExecutingBase, would only multi-count the same samples.
A sample with a single-frame call stack already produces a zero-length parent
stack, and GetParentsCallstackFrameTree{BottomUp,TopDown} call cs.back() /
cs.front() on it, resulting in UB.
patchelf the stripped binary to /../lib, collapsing the RUNPATH
to the single intended entry. The wayland .pc files baked an absolute
build-host path and a trailing empty component into RUNPATH; the former
leaks the build environment and could shadow bundled libs on the target,
the latter can cause cwd library searches on older glibc.
Add patchelf to the appimage workflow's apt install list.
This update introduces a new function, GetThreadLockWaitTime, which computes the total wait time for locks held by a specific thread. The function aggregates wait times across all locks and updates the tooltip to display lock wait time and count, enhancing the profiling capabilities of the timeline item.
The timeout option stops the connection if it has now finished after the
specified time, even if data transfer is underway. Switch to using a low
speed timeout instead, which stops the connection only if no data is being
transferred.
A typical case where this was hit was a slow-responding LLM generating
a large amount of text.
The high timeout value is still needed to account for long prefill times.
Two staleness gaps a cold agent would hit: get_sections()'s
documented {start, end, text} shape didn't match the actual
return value once category was added, and is_background_done()
-- introduced to close the stats-read race exercised while
testing the lock fix -- wasn't mentioned anywhere, so a cold
agent had no way to discover it short of dir(ctx).
Smoke-testing the lock fix against a real 143M-zone capture
surfaced a second concurrent-mutation source: file loads spawn a
background thread that finishes populating zone/symbol statistics
after Worker construction returns, so get_all_zone_stats() et al.
could silently come back empty or partial with no way to tell
"not built yet" from "genuinely empty".
Adds is_background_done (a relaxed atomic, same as is_connected,
so no locked() wrapper needed) and surfaces it as background_done
in list_instances, with load_capture's docstring pointing callers
at it.
Every Worker-reading Python binding except save_worker() read
m_data's zones/plots/threads/etc. without taking
Worker::ObtainLockForMainThread(), racing the live receive thread
that mutates the same structures. This matches
alandtse/tracy#2: crashes only ever hit live instances, never
file-loaded ones (no concurrent writer there), and cluster right
after a big save_trace when the receive thread is catching up on
backlog.
Routes every Worker accessor through a new locked() wrapper using
the same cooperative lock TracyView.cpp already takes once per
frame, so reads can't race live writes without introducing new
stalls on the profiled client.
D3D11_QUERY_DATA_TIMESTAMP_DISJOINT can report Frequency == 0 even
when Disjoint == FALSE. Neither the initial CPU/GPU calibration
loop nor the per-frame Collect() path guarded against this, so
`timestamp * (1000000000 / disjoint.Frequency)` divides by zero
and crashes the profiled application with
EXCEPTION_INT_DIVIDE_BY_ZERO inside client instrumentation code.
Treats a zero frequency the same as an existing guard already does
for a disjoint result: skip/retry in calibration, and in Collect()
advance the checkpoint and drop the batch via TracyD3D11Panic,
matching the existing disjoint-timestamp handling immediately
above it.
Dereference of end() iterator in section category options list
(profiler/src/profiler/TracyView_Options.cpp:152-153)
The new category list iterates categories = m_worker.GetSectionDescriptions()
(the description map) and then looks up each category in sections = m_worker.
GetSections():
for( const auto& v : categories )
{
...
auto it = sections.find( v.first );
ImGui::TextDisabled( "(%s)", RealToString( it->second.size() ) );
...
}
sectionsDescription is a superset of sections's keys: ProcessSectionSetup
(server/TracyWorker.cpp:7484-7496) inserts into sectionsDescription only,
while ProcessSectionEnter (server/TracyWorker.cpp:7416-7454) inserts into
both. A category set up via TracySectionSetup that has no TracySectionEnter
events yet (or a saved trace containing such a category) will be in
sectionsDescription but not in sections. find returns end() and
it->second.size() is undefined behavior (likely crash).
The current "parents" mode of operation is "was this symbol executing".
The new name for the maps reflects that.
There should also be "was this symbol reached" (both recursive and non-
reentrant) modes.
Added a "Sort" button to the Flame Graph view, allowing users to sort threads based on group hints, thread names, and IDs. Updated the child window to always show a vertical scrollbar for better usability.
Currently, the profiler will build against the git NFD, but link it as a
shared library. Since libnfd is not installed when building Tracy as a
distro package, it will end up actually depending on and running against
an older distro-provided NFD, hitting an unresolved function error due
to the newly added NFD function in git.
Other deps fetched with CPM are linked statically, avoiding this
problem.
gcc breaks on this:
error: function ‘static uint32_t tracy::Profiler::SectionEnter(const char*, ...)’ can never be inlined because it uses variable argument lists
tracy_mcp.py runs as a long-lived singleton shared across every MCP
client, and the `instances` dict never evicted entries — each
live_connect/load_capture materializes a full trace (zones, messages,
callstacks, memory events) with no size cap, so private bytes grow
without bound across recording sessions unless unload_capture is
called manually every time.
Add two backstops, both env-configurable: an LRU cap
(TRACY_MCP_MAX_INSTANCES, default 4) enforced on live_connect/
load_capture that never evicts a still-connected live instance, and a
periodic sweep that drops a disconnected live instance once it has
sat idle past TRACY_MCP_DISCONNECTED_TTL_S (default 1800s), keeping a
grace window for post-session analysis. list_instances now reports
connected/idle_seconds so callers can spot stale sessions, and
unload_capture/eviction now explicitly call Worker.shutdown() instead
of relying on GC. Document the lifecycle expectations (call
unload_capture proactively; treat eviction as a backstop) in
eval_guide.md.
Practically, this fixes horizontal panning being "quantized" to 100 µs
steps (one period). I guess it also simplifies the code a bit and makes
"time" actually mean time.
Based on patch by GitHub user ofats.
* elf.c (elf_zstd_decompress_frame): New static function,
broken out of elf_zstd_decompress.
(elf_zstd_decompress): Call elf_zstd_decompress_frame in a loop.
* zstdtest.c (test_large): Compress the file in chunks.
Without this using the popup is quite unintuitive. Setting the range
apparently does not have an effect – because ranges are only shown if the
ranges window is open (or the windows appropriate for each of the ranges).
eval_guide.md referenced a tracy://catalog resource that was never
registered (only tracy://prompt and tracy://eval-guide exist), so an
agent following the guide would try to read a nonexistent resource.
Remove the references; the worked snippets the catalog described are
already inlined under "Common query patterns".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EvfzHvUsDBSAwEzTfLTtA
Bind Worker::GetSections() as get_sections() so the TracySectionEnter /
TracySectionLeave instrumentation added to the client is reachable from
the MCP eval tool's ctx object. Returns a list of {start, end, text}
dicts with nanosecond timestamps, matching the existing list-of-dict
accessor convention. Documented in eval_guide.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EvfzHvUsDBSAwEzTfLTtA
Tracy requires some textures to have repeat wrapping mode set. The ImGui
implementation of samplers doesn't make it easy to achieve. Disalbe use
of samplers and rely on texture flags, as done originally.
Bug (High Severity): Wrong queue type in MemDiscardCallstack
In the callstack path of MemDiscardCallstack, the wrong queue type is
sent:
SendMemDiscard( QueueType::MemDiscard, thread, name );
Every other callstack variant correctly uses its callstack queue type
(MemAllocCallstack, MemFreeCallstack, etc.), but this one uses the
non-callstack type. The SendMemDiscard assertion at line 1026 confirms
MemDiscardCallstack is a valid value.
Impact: The callstack captured by SendCallstackSerial() will be orphaned.
The server processes the event via the non-callstack handler, leaving the
callstack serial data unconsumed, which desynchronizes the serial queue
and corrupts all subsequent events.
A zone emitted from a shared object initializer runs before the
executable's constructors, so its timestamp precedes s_initTime, which
the server uses as the trace epoch (baseTime). Such a zone converts to
negative trace time and its end no longer satisfies IsEndValid(), which
excludes it from statistics reconstruction and makes it render as
never-ending.
Record the current time when a producer token is created before
s_initTime is constructed and use it as the init time, ensuring no event
timestamp precedes the trace epoch.
ELF init_priority only orders constructors within a single module. All of
a shared object's initializers run before any of the executable's, so an
instrumented dependency .so emitting a zone from its static initializer
creates the main thread producer token against the zero-initialized
s_queue. The queue constructor then resets the producer list, orphaning
that producer: every zone emitted on the main thread from that point on
is enqueued into blocks no consumer ever iterates and silently lost,
while sampling (worker thread producer) keeps working.
Re-link such a producer right after the queue is constructed. In the
common case, where nothing was emitted during shared object init, this
merely constructs the main thread token eagerly.
* disabling LTO when building the profiler on macos via the github workflow
* diagnosing where in the linking stage it's getting stuck
* fowrward declarations
* compilation time report
* trying clang build analyzer...
* reducing number of parallel workers
* limiting parallel workers on windows/linux as well
* re-enabling LTO on macos
* reverting forward declaration header include (emscripten is failing with them).
* reverting act changes
* removing comments
Conform to the new repo layout (master moved repros under tests/, e.g.
tests/cuda/repro/graph). Relocate examples/RocprofOnDemandRepro to
tests/rocprof/repro/on_demand and replace the hand-written Makefile with
a CMake build mirroring the CUDA repro: builds the HIP reproducer, wires
it as a ctest target, and optionally builds the check_gpu_ctx_name
verification helper against the Tracy server library.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fixed batches of 1024 addresses could overflow the platform's command-line limit (`La ligne de commande est trop longue.` from cmd.exe on Windows, whose limit is ~8191 characters). Build each batch by appending addresses until a length budget is reached instead. A single conservative budget of 8000 stays under the smallest limit on every platform, and keeps batches in the same ballpark as before (several hundred addresses per invocation).
The new `-R` option of tracy-update sets every callstack frame back to `[unresolved]` / `[unknown]`. Since failed lookups leave frames untouched and the image-relative offset in `symAddr` survives patching, this makes it possible to chain several resolution passes over the same capture, each with different `-p` path substitutions (e.g. one pass per symbol directory).
The addr2line backend of tracy-update now builds on every platform, including Windows, and can be pointed at any addr2line-compatible executable:
- `-a`: path to a custom symbol resolution tool (e.g. `llvm-addr2line` or a cross-compilation toolchain's `addr2line`). Works on all platforms and takes precedence over the platform default (DbgHelp on Windows, the `addr2line` found in `PATH` elsewhere). Path-like values are validated up front so a wrong path fails with an actionable message instead of a cryptic, localized shell error.
- `-A`: extra arguments passed verbatim to the tool, e.g. `--relative-address` so `llvm-addr2line`/`llvm-symbolizer` accept the image-relative offsets Tracy records for images with a non-zero preferred base (PE, Mach-O).
- `-v`: verbose output while patching symbols.
OLDNAMES.lib may not be linked if you use /NODEFAULTLIB.
Note: This uses `_MSC_VER` as a gate and not _WIN32 as MinGW apparently uses `fileno` and not `_fileno`.
When zoomed in very far the panning resolution can be so small that it
is less than one unit. In order to continue panning, we store partial
pans so that they can accumulate across frames.
Now that the ruler just shows the delta time across the view it doesn't
indicate where the view is currently looking.
The new position bar fills this role to allow orientating oneself.
It could be challenging to examine fine details within the flamegraph.
The flamegraph has been enhanced so that it allows zooming with the
mouse wheel, and then panning around with the right mouse button.
This provides a familiar experience to the timeline view.
When typing in e.g. "127.0.0.1" the first character "1" as a valid address
that does not immediately fail the connection attempt. The result was that
any further interaction with the UI (including completing the input) was
blocked by the "please wait" screen during connection attempt.
Extension point so private/unsupported platforms can plug in their own implementations of the kernel/libc primitives Tracy depends on, without patching the `#if`/`#elif` chains.
Projects supply a platform header via `-DTRACY_PLATFORM_HEADER="\"my_platform.h\""` at build time. Tracy includes it in any TU that needs the hooks. The header toggles per-category `TRACY_HAS_CUSTOM_*` macros and declares matching `tracy::Platform*` functions.
Available hooks:
- `TRACY_HAS_CUSTOM_THREAD_ID` → `PlatformGetThreadId`
- `TRACY_HAS_CUSTOM_USER_INFO` → `PlatformGetHostname`, `PlatformGetUserLogin`, `PlatformGetUserFullName`
- `TRACY_HAS_CUSTOM_SAFE_COPY` → `PlatformSafeMemcpy`
- `TRACY_HAS_CUSTOM_ALLOCATOR` → `PlatformMalloc`, `PlatformFree`, `PlatformRealloc`, `PlatformAllocatorInit`, `PlatformAllocatorThreadInit`, `PlatformAllocatorFinalize`, `PlatformAllocatorThreadFinalize`
Each hook is wired as the first arm of its respective `#if`/`#elif` chain, so existing supported platforms are unaffected.
Template files in `examples/CustomPlatform/` and a new subsection in `manual/tracy.tex` document the mechanism.
Move `tracy_set_option` and `tracy_set_option_value` from `CMakeLists.txt` into `cmake/options.cmake`. Add `tracy_set_option_value_as_string` for options whose value is embedded as a C string literal. All three accept an optional trailing target argument; when provided, the option is also propagated as a PUBLIC compile definition on that target.
Existing `set_option`/`set_option_value` are unchanged but will be replaced later by the `tracy_*` versions.
The function is about to dispatch between rpmalloc and a pluggable allocator hook, so the rpmalloc-specific name no longer fits. Pure rename plus a small consequence: the SymbolWorker call site no longer needs the TRACY_USE_RPMALLOC guard, since the no-op static-inline fallback in TracyAlloc.hpp makes InitAllocator() safe to call unconditionally.
If as.ipMaxAsm.local is 0 and m_childCalls is false, GetHotnessColor(count, 0)
performs float(2 * count) / 0. The old code explicitly guarded against this
with the as.ipMaxAsm.local != 0 check.
The "outdated" concept is strictly for chain of assistant replies with
nothing in between, i.e.:
"I will check this..." <- outdated
<tool call> <- not displayed
"Now I will do that..." <- outdated
<tool call> <- not displayed
"Let me consider..." <- outdated
<reasoning> <- not displayed
"Now I have the answer..."
The first three messages are at this point considered outdated, as the
model provided a more recent message.
Note that in chain such as below there are NO outdated messages:
"How can I help..."
<user input>
"Ah, I see..."
<user input>
"You may try to..."
Similarly, if the tool calls or reasoning sections are explicitly enabled
in the chat UI, the messages are also not considered outdated.
Replace `#ifdef BSD` (which requires including `<sys/param.h>` first) with explicit checks for `__FreeBSD__`, `__NetBSD__`, `__OpenBSD__` and `__DragonFly__`, matching how these BSDs are already enumerated elsewhere in the codebase (OS name strings, thread id helpers, etc.).
This also avoids leaking the `sys/param.h` requirement through public headers (`TracySysTime.hpp`, `TracyCallstack.h`), where consumers would otherwise need it to correctly see `TRACY_HAS_SYSTIME` / `TRACY_HAS_CALLSTACK`.
`libbacktrace/config.h` is left as-is — it's third-party and only included from .c files where the `BSD` macro can still be picked up locally.
Note: for `setsockopt( m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&val, sizeof( val ) );` I added `__APPLE__` too since this was the only place where it was not checked explicitely.
This can happen notably when the user does not call ZoneEnd.
I used 256 arbitrarily as it seemed higher values would just make the UI freeze anyway due to perf reasons.
I added a warning in the notification area so that users can locate it.
Many of the zones would have a negative running time due to a missing `cs->IsEndValid()` check.
This could end reporting context switches before the zone start, due to `cs->End()` returning -1.
This happened when systrace dropped event, or when using Fibers and `TracyFiberEnter` is called on the new thread once the fiber has been scheduled. (The manual actually does not really hint this is wrong, we should probably fix the manual or the server code.)
In both cases, we assume runtime to be 0 for that context switch. Since we have no actual information. Both options (counting full runtime or no runtime) are wrong, and most of the code handling `!cs->IsEndValid()` uses `Start` instead so that's what I did. This is still a net improvement over displaying negative values. If we want to change this handling, we'd need to review the other places that do `it->IsEndValid() ? it->End() : it->Start()` as well.
It also seems two different concepts were being mixed:
1. Do we have any context switch data at all ? (`it != ctx->v.end()` ie `count != 0`)
2. Do we have complete data for the last context switch (`eit != ctx->v.end()`)
This led to some places of the code not displaying or counting running time at all, notably when hovering a zone.
I think most of the time we wanted 1, as it reports correctly and assumes the last context switch is still running, which is a fair assumption if we didn't see one putting the thread to sleep.
I also fixed a case where we were overcounting runtime when range start was during a sleep.
- save_worker binding: wraps Worker::Write under
Worker::ObtainLockForMainThread() so live instances yield their
receive thread cooperatively for the save's duration — the same
pattern View::Save uses in the GUI.
- save_trace MCP tool: defaults to async_mode=True for multi-GB
traces; reuses the existing Task/executor machinery so callers
poll via the task tool. Path resolution mirrors load_capture.
- manual/tracy.tex: add save_trace bullet to the MCP tool list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cache is shared between image names and source file names, because the
underlying StringIdx storage makes indices unique. Both name sets should
be completely separate, but if you have conflicts here, you have much
more pressing problems to solve.
In terms of how much BuildFrameGraph execution time was spent in
IsFrameExternal:
1. no cache: 67%
2. global + shared_mutex: 84%
3. global + mutex: 80%
4. local: 41% (this commit)
For consistency, always provide the OS-level sampling data, not the hardware
samples.
Always disable inline propagation, which is intended as a local area help
for a human. It does not make sense in context of an llm.
Two bugs fixed in the process:
1. X86_REG_BPL is now properly set (previously there were duplicate
X86_REG_BP entries).
2. maxLine is now properly calculated, instead of being set to the
last line value.
The end address is now readily available in lower_bound search, instead
of needing to be calculated constantly in the lambda.
The size can be equivalently calculated from the end address, but this
only happens once, after the symbol is found.
Frames whose symbol data is shipped inline with the callstack payload
(sel=1, e.g. Lua-side stack entries) were being passed to
GetCanonicalPointer() in the AddCallstackAllocPayload() query loop,
tripping its sel==0 assertion. They have no native pointer to query
and were already registered in callstackFrameMap earlier in the same
function, so just skip them.
Regression from c704f909, which hoisted the per-call-site dedup into
QueryCallstackFrame(). Three of the four updated call sites were
equivalent before and after, because the old guard and the new one
keyed on the same value. The fourth, this one, was not: the old guard
tested the frame as-is and matched the entry inserted a few lines above,
short-circuiting before GetCanonicalPointer() ran. The new guard keys on
PackPointer(addr), so GetCanonicalPointer() must run first to compute
addr, and the assert fires.
The profiler will typically want to send bursts of queries (e.g. 3 queries
to retrieve source location strings, or multiple queries to get all the call
stack frames, etc.).
Each of these queries will be sent immediately, if available space in the
network buffer permits. Each of these sends is a separate syscall.
Remove this and instead batch all queries with the already existing network
buffer overflow handling functionality.
After investigating (downloading and installing) all publicly available SDKs at https://learn.microsoft.com/en-us/windows/apps/windows-sdk/downloads-archive I concluded the `TRACEHANDLE` deprecation started in `10.0.26100`.
This defines `PROCESSTRACE_HANDLE` and `CONTROLTRACE_ID` as done by the SDK when using older versions. Using `WDK_NTDDI_VERSION` (and not `NTDDI_VERSION` which may change based on `_WIN32_WINNT` or user input seems to be the most reliable way to do it. While it says "WDK" it's been part of the SDK in `shared\sdkddkver.h`. Note it doesn't work for MinGW because it updates half of its sdk files for some reason.
Tested with both 10.0.26100 and 10.0.22621.0 which is the last one I found without the new types.
Also changes CONTROLTRACE_ID to ULONG64 on mingw which is correct (type used by `TRACEHANDLE` too in mingw fe2763863a/mingw-w64-headers/include/evntrace.h (L60) )
* Add MCP server for AI-assisted trace analysis.
Introduce an optional Model Context Protocol (MCP) server that lets AI
assistants analyze Tracy captures and live sessions through Tracy's own
server engine. The server runs as a Python sidecar and talks to the
existing C++ analysis code through new pybind11 bindings.
- python/bindings/ServerModule.cpp: TracyServerBindings module exposing
Worker, file I/O, zones, GPU zones, frame data, plots, messages, locks,
source locations, and summary statistics (zone/GPU child stats, frame
timing, etc.).
- python/CMakeLists.txt: builds and installs TracyServerBindings alongside
TracyClientBindings.
- extra/mcp/tracy_mcp.py: FastMCP SSE singleton with dynamic port
discovery, PID-file based singleton detection, session-isolated worker
instances, synchronous and background eval, task polling, and a
shutdown tool to release the .pyd lock during development.
- extra/mcp/start_mcp.sh, .gitignore: launcher with local override hook;
ignores generated port/pid files.
- manual/tracy.md: documents building, running, and integrating the
server with an AI assistant.
* Improve Tracy MCP cold-start guidance.
Cold-start usability testing showed an LLM agent burned ~7 exploratory
calls discovering the ctx object model, time-unit conventions, and join
keys before producing useful analysis. Surface that information up front
through MCP resources and entry-point tool guidance.
- extra/mcp/eval_guide.md: new bindings-layer reference covering the
Worker object graph (zone / GPU zone / frame / thread / message /
plot / lock / memory entry points), nanosecond time units, ZoneStats
field semantics including self-time via get_child_zone_stats, the
opaque 'name (addr)[arch] <srcloc_id>' key format, and worked
examples translating common queries into ctx Python.
- extra/mcp/tracy_mcp.py: expose system.prompt.md and eval_guide.md as
MCP resources (tracy://prompt and tracy://eval-guide) so external
agents and Tracy Assist share the same guidance source. Resource
content is re-read per request — edits propagate without a server
restart.
- Point load_capture and live_connect return values plus the eval tool
description at the resources, so the agent reads them before its
first eval rather than introspecting blind.
- Expand load_capture docstring: name the path parameter explicitly,
show Windows path syntax, and direct agents to list_captures plus
TRACY_CAPTURES_DIR for capture discovery.
- Probe is_connected() briefly after Worker construction in
live_connect and surface an actionable error on silent handshake
failures (typically a Tracy client/server version mismatch or
TRACY_ON_DEMAND) instead of returning misleading success.
Reduces a fresh agent's cold-start overhead from 7 exploratory calls
to 4, where the remaining 4 are unavoidable harness/schema-fetch
overhead, not API-design friction.
* Detect Tracy protocol mismatches via UDP broadcast pre-flight.
Tracy clients announce themselves on UDP port 8086 every ~3 seconds with
a BroadcastMessage carrying the protocol version, listen port, and
program name (public/common/TracyProtocol.hpp). The Tracy GUI reads this
and refuses to attempt a TCP connection on protocol mismatch, surfacing
a precise error. live_connect previously had no equivalent check, so a
mismatch produced an opaque 2-second handshake timeout with no
diagnostic about what was wrong.
- Add a broadcast parser handling versions 0-3, with variable-length
programName (Tracy sends only the actual name + null terminator on
the wire, not the full 64-byte buffer).
- Add a non-blocking UDP listener that binds 8086 with SO_REUSEADDR
and waits up to 3.5s — enough to guarantee catching at least one
beat at the 3s broadcast cadence.
- Read our bindings' ProtocolVersion at startup by parsing
TracyProtocol.hpp, so the comparison stays in sync with the build
without new C++ wiring.
- live_connect runs the broadcast pre-flight before constructing
Worker. On a matched listen_port with a differing protocol_version,
it returns a single-line error naming the program, both versions,
and the remediation, without ever opening a TCP connection. If no
matching broadcast arrives, it falls through to the existing
handshake probe, which now reports any other broadcasts seen as a
hint (helpful when the target uses a non-default port).
* Add MCP Server section to LaTeX manual.
The markdown manual is auto-generated from the LaTeX source; add the
corresponding \subsection{MCP Server} so the two stay in sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Remove hand-written MCP section from tracy.md.
tracy.md is generated from tracy.tex via latex2md.sh. The MCP section
was previously written by hand directly in the markdown; now that the
LaTeX source has been updated, the markdown section should be
regenerated by running latex2md.sh rather than maintained manually.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous solution (75c173) didn't account for the fact that the text
to print may start with a space, in which case the text width calculation
results in 0. In effect, first word length was never greater than the
space left for printing, and the problem was still there.
Fix by walking through all initial spaces in firstWord.
If fwLen > left, then a line break is needed. In this case ignore initial
spaces in text.
TracyDebug fires from SysPower's ctor while it scans intel-rapl, which
runs as a Profiler member initializer -- before s_instance is set in
the Profiler ctor body. Under TRACY_MANUAL_LIFETIME without
TRACY_ON_DEMAND, the TracyInternalMessage path guarded this with
assert(ProfilerAvailable()), which aborted tracy-monitor whenever it
was run as root (only then is intel-rapl readable, so the log is
actually reached).
Soften the assert to an early-out, matching the TRACY_ON_DEMAND branch.
A TracyDebug issued before the profiler is up now silently skips
instead of aborting.
FindExternalImageRefresh already re-parsed /proc/<pid>/maps on miss,
but only one of three external decode paths used it. Switch the
DecodeCallstackPtrFastExternal and DecodeSymbolAddressExternal paths
over so symbol-name and file/line lookups stay fresh after the target
dlopens a library.
Rate-limit the re-parse to once per wall-clock second so samples
landing on permanently unresolvable regions (JIT, vDSO, stacks) do
not trigger a full parse each time.
perf_event_open(pid>0, cpu>=0) binds to a single task, so the previous
setup only sampled the target's main thread. In monitor mode, enumerate
/proc/<pid>/task/ and open one per-task event per existing thread with
cpu=-1; inherit=1 then covers every descendant. Self-profiling behavior
is preserved byte-for-byte: the iter list becomes (currentPid, i) for
each CPU, exactly what the old code did inline.
- Loop startup waitpid on EINTR; kill and reap the child on fatal error
or when interrupted, instead of leaking a ptrace-stopped process.
- Treat PTRACE_DETACH failure as fatal -- otherwise the child is stuck
stopped forever.
- Zero-initialize procName so the memcpy into ___tracy_magic_process_name
does not copy uninitialized stack past the NUL.
- Forward SIGINT to the child from the signal handler when in forked
mode, so Ctrl-C during a blocking waitpid unblocks cleanly.
- Preflight perf_event_open on the target before StartupProfiler so
permission failures surface with actionable guidance instead of
silently producing no samples.
- Also handle SIGHUP and SIGQUIT.
We redirect GetProfiler() (most likely used by any project consuming tracy, since it's used by `tracy::ScopedZone`) to its implementation which now has a different function name based on the macros that can impact ABI (and enabled/disabled).
That way, when linking with mismatched defines you'd get an error such as
> main.obj : error LNK2019: unresolved external symbol "int __cdecl GetProfiler_CFG_E0_OD0_DI0_ML0_F0_DHT0_TF0(void)" (?GetProfiler_CFG_E0_OD0_DI0_ML0_F0_DHT0_TF0@@YAHXZ) referenced in function "int __cdecl GetProfiler(void)" (?GetProfiler@@YAHXZ)
Or
>[build] /usr/bin/ld: CMakeFiles/app.dir/main.cpp.o: in function `GetProfiler()':
[build] /..../TracyProfiler.hpp:143: undefined reference to `GetProfiler_CFG_E1_OD0_DI0_ML0_F0_DHT0_TF0()'
Reason for going with acronym+0/1 instead of just acronym when enabled is for us to be able to tell users easily which define is wrong by just looking at the error if needed.
The only thing we don't really detect is user not having TRACY_ENABLE but tracy having been built with it. This is because macros become noops in that case, with no reference to `GetProfiler`.
There may be a way to do it by introducing a local variable into each TU, but I don't really like that idea.
We could also add pragma detect mismatch for a more user-friendly error on windows (https://learn.microsoft.com/en-us/cpp/preprocessor/detect-mismatch?view=msvc-170).
This is to be consistent with what libtracefs does: 6fad6a14ba/src/tracefs-utils.c (L104)
In theory one may mount tracefs with another name, though unlikely.
This forces to (re)use frequency values as input, which may be changed by the platform code later on. This way we have a single "source of truth" for sample freq.
Also removed the Win32 `GetSamplingInterval` which was a wrapper above `GetSamplingPeriod` but its value would be divided again anyway.
On 32-bit arm, phdr.p_vaddr is 32-bit, which causes a compilation
error because std::min expects both arguments to be of the same
type. Adding the static cast handles this case explicitly.
Without this, a late-connecting client receives the deferred
GpuNewContext but not the GpuContextName, so the GPU context appears
unnamed in the profiler.
Add check_gpu_ctx_name tool to verify context names in captured traces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Minimal HIP program that demonstrates the assertion failure in
tracy-capture when connecting to a TRACY_ON_DEMAND + TRACY_ROCPROF
application. See examples/RocprofOnDemandRepro/README.md for details.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues prevented the rocprofiler GPU backend from working with
TRACY_ON_DEMAND:
1. GpuNewContext not deferred: When a Tracy client connects late (on-demand
mode), it never receives the GPU context creation message because the
GpuNewContext queue item was not buffered via DeferItem. This caused an
assertion failure (ctx == nullptr) in the capture/profiler when
processing GPU zone events. Add the same DeferItem pattern used by the
CUDA backend.
2. Kernel symbols dropped before init: The data->init guard at the top of
tool_callback_tracing_callback() blocked kernel symbol registrations
(CODE_OBJECT_DEVICE_KERNEL_SYMBOL_REGISTER) which happen at HIP init
time, before any Tracy client connects. Move the init guard after the
code_object block so symbols are always recorded, while dispatch and
memory-copy events are still gated on initialization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The comment still described consuming/not-consuming cudaCallSiteInfo
entries, but memory CBIDs are no longer tracked so no entry exists.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the mutex-guarded empty check in OnBufferCompleted with an
std::atomic<bool> dirty flag. The mutex is now only acquired when
there is actual retirement work to do. Also update stale comment
on cudaGraphCurrentLaunch that said "let them leak".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
operator[] on ConcurrentHashMap returns a reference after releasing the
read lock — the subsequent assignment happens with no lock held. This is
a latent data race if the map is ever accessed from multiple threads.
insert_or_assign performs the lookup and assignment atomically under a
single write lock, which is the correct pattern for a ConcurrentHashMap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add `using GraphID = uint32_t` typedef and use it throughout for
graphId-typed variables (PersistentState, matchGraphActivityToAPICall,
getGraphIdFromRecord, retirement set, buffer loop).
- Move matchError from matchGraphActivityToAPICall to caller sites
(KERNEL, MEMCPY, MEMSET handlers). Keeping the error at the caller
provides more debugging context about which activity kind failed.
Remove the now-unnecessary `kind` parameter from the function.
- Replace insert_or_assign with operator[] assignment in
matchGraphActivityToAPICall. Access to graphLaunchCache is
single-threaded (CUPTI worker), so the simpler syntax is sufficient.
Remove the insert_or_assign method from ConcurrentHashMap entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cudaMalloc/cudaFree (and driver equivalents) were tracked in
cbidRuntimeTrackers/cbidDriverTrackers, creating a cudaCallSiteInfo
entry on each API call. But the MEMORY2 handler never calls
matchActivityToAPICall (and never calls EmitGpuZone) — it only needs
the address, size, and timestamp from the activity record itself. Since
no activity handler consumes these entries, they leaked indefinitely.
Remove the 6 memory API CBIDs from both tracker maps so no entry is
created. This eliminates the leak with no change in visible behavior:
the MEMORY2 handler already operates independently of cudaCallSiteInfo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without retirement, the cache grows by one entry per unique exec handle
ever launched and never shrinks. While bounded by the number of distinct
execs in the application, long-running programs creating and destroying
many exec handles accumulate stale entries indefinitely.
Retirement mechanism:
- At cudaGraphExecDestroy (ENTER, while handle is still valid): call
cuptiGetGraphExecId to translate exec handle → graphId and add to a
pending-retirement set. Works for both runtime (cudaGraphExecDestroy)
and driver (cuGraphExecDestroy) APIs. No new subscription needed —
the existing cuptiEnableDomain already routes all API callbacks here.
- Deferral in OnBufferCompleted: erasure is not done immediately because
cudaGraphExecDestroy does not wait for GPU completion. CUPTI may still
have undelivered activity records for the last launch in its internal
buffers. We defer the erase until a full buffer arrives that contains
no records bearing the retired graphId, indicating all in-flight
records have been delivered.
- getGraphIdFromRecord: new helper that extracts the graphId field from
CONCURRENT_KERNEL / MEMCPY / MEMSET activity records (the three kinds
that carry a graphId) for use in the per-buffer tracking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In the JSON exception catch handler, m_jobsLock.lock() is called directly
on the mutex instead of through the jobsLock unique_lock. When the function
returns, jobsLock's destructor runs but it doesn't own the lock (it was
unlocked earlier at line 1134), and m_jobsLock is never released. This
causes a permanent deadlock the next time anything tries to acquire
m_jobsLock.
Currently including the Tracy.hpp header from a set of installed Tracy
headers will result in the following error:
In file included from <...>/tracy/include/tracy/tracy/Tracy.hpp:133:
In file included from <...>/tracy/include/tracy/tracy/../client/TracyLock.hpp:9:
In file included from <...>/tracy/include/tracy/tracy/../client/TracyProfiler.hpp:18:
<...>/tracy/include/tracy/tracy/../client/../common/TracyQueue.hpp:6:10: fatal error: 'TracyTaggedUserlandAddress.hpp' file not found
6 | #include "TracyTaggedUserlandAddress.hpp"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Apparently introduced in f981330, which included the
TracyTaggedUserlandAddress.hpp header in TracyQueue.hpp without adding
it to the list of installed common header. Fixed by making the necessary
CMake change to install the header.
Ran into this issue while integrating Tracy as a dependency within
Blender[^1], where we use the latest main instead of stable for WoA
support, and use the install target to harvest the static lib and
headers for our libraries.
[^1]: https://projects.blender.org/blender/blender/pulls/156661
The documentation states that Tracy is disabled by default, but the
build system defaults were ON/true. Change CMake and Meson defaults to
OFF/false. Projects that need profiling enabled must now opt in
explicitly. Add explicit TRACY_ENABLE=ON / tracy_enable=true to CI
steps and the test project to preserve existing behavior.
Tests whether CUPTI recycles graphId values after cudaGraphExecDestroy,
which would be the only scenario where the graphLaunchCache in TracyCUDA
could serve stale entries for a non-matching exec handle.
Result (H100, CUDA 12, CUPTI): graphId is a monotonically increasing
counter that is never recycled. 22 create/instantiate/launch/destroy
cycles produced unique IDs ranging from 2 to 65 (incrementing by 3 per
cycle — one unit per node created during graph construction).
This confirms that the stale-cache concern raised in code review is not
a real risk in practice: two distinct exec handles always have distinct
graphIds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests two questions:
1. Does relaunching the same cudaGraphExec produce a new correlationId
each time, or is it reused?
2. Do two different cudaGraphExec handles from the same cudaGraph share
a graphId?
Results on H100, CUDA 13.1:
- Each launch of the same exec handle gets a strictly unique, monotonically
increasing correlationId. CPU callback corrId == GPU activity corrId.
This is formally documented in cupti_activity.h:
"Each graph launch is assigned a unique correlation ID that is
identical to the correlation ID in the driver API activity record
that launched the graph."
- graphId identifies the exec handle (instantiation), not the graph
definition. Two cudaGraphInstantiate calls on the same graph produce
different graphIds.
These findings confirm that the cudaGraphCurrentLaunch cache in
matchGraphActivityToAPICall is always refreshed by the first activity
of each new launch before the graphId fallback path is ever used.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calling matchActivityToAPICall in the MEMORY2 handler was consuming
the cudaCallSiteInfo entry for the graph launch correlationId. If a
graph mixes alloc nodes with kernel/memcpy nodes, all activities share
the same correlationId — consuming it here would cause
matchGraphActivityToAPICall to fail for the kernel/memcpy records that
follow, silently dropping their GPU zones.
Since apiCall is never used by the MEMORY2 handler (only address, size,
and timestamp from the activity record are needed), remove the call
entirely and leave the entry for the kernel/memcpy to consume and cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CUpti_ActivityMemory3 has no graphId field, so graph-launched alloc
nodes and pre-profiling allocations can't be correlated to an API call.
The handler only needs address, size, and timestamp from the activity
record — apiCall is never used. Remove the early return so memory
tracking works in all cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CUpti_ActivityMemory3 has no graphId field, so matchGraphActivityToAPICall
cannot be applied. Graph-launched cudaGraphAddMemAllocNode emits multiple
MEMORY2 records sharing the launch correlationId; only the first is
tracked, subsequent ones fire a spurious matchError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix wrong comments on graph launch tracker entries: they claimed
correlation works "via CUPTI_ACTIVITY_KIND_GRAPH_TRACE", but that
approach was rejected (GRAPH_TRACE suppresses per-kernel records).
The actual mechanism is the shared correlationId across all nodes
in one graph launch.
- Fix ConcurrentHashMap::fetch() missing its read lock — a pre-existing
data race now exercised by the new graph correlation hot path.
- Cache PersistentState::Get().cudaGraphCurrentLaunch in a local ref
inside matchGraphActivityToAPICall instead of calling Get() twice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The cudaGraphCurrentLaunch cache update was acquiring the write lock
twice (once for erase, once for emplace). Wrapping
std::unordered_map::insert_or_assign under a single write lock lets
the caller do it in one operation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NVCC 13.1 defaults to a PTX version incompatible with the installed
driver (580.105.08), causing kernels to silently fail with "provided
PTX was compiled with an unsupported toolchain". Use -arch=native so
NVCC auto-detects the target GPU (H100, sm_90) at build time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The kernel, memcpy, and memset cases all had identical logic for
handling graph-launched activities. Extract it into a single helper
next to matchActivityToAPICall.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CUPTI discovery: all kernels launched by one cuGraphLaunch share the
same correlationId as the launch call itself. GRAPH_TRACE was the
wrong approach — enabling it suppresses per-kernel CONCURRENT_KERNEL
records entirely, replacing them with graph-level summaries.
New approach:
- Drop CUPTI_ACTIVITY_KIND_GRAPH_TRACE (it conflicts with CONCURRENT_KERNEL)
- Drop two-pass buffer processing (no longer needed)
- On the first kernel/memcpy/memset from a graph launch, matchActivityToAPICall
succeeds (consuming the cuGraphLaunch entry) and the result is cached in
cudaGraphCurrentLaunch[graphId]
- Subsequent operations from the same launch find the cached entry via graphId
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The repro uses cudaGraphLaunch (runtime API) not cuGraphLaunch (driver
API). Add cudaGraphLaunch_v10000 and its _ptsz variant to
cbidRuntimeTrackers so that graphs launched via the runtime API also
get their CPU call site captured for GPU zone correlation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the synthetic APICallInfo hack with proper correlation via
CUPTI_ACTIVITY_KIND_GRAPH_TRACE. When cuGraphLaunch fires an API
callback, its correlationId is stored in cudaCallSiteInfo. The
GRAPH_TRACE activity record carries the same correlationId plus the
graphId, which lets us build a graphId→APICallInfo map. Kernel/memcpy/
memset activities then look up this map via their graphId field.
Key changes:
- Add cuGraphLaunch/cuGraphLaunch_ptsz to cbidDriverTrackers so the
API callback machinery captures the CPU call site
- Enable CUPTI_ACTIVITY_KIND_GRAPH_TRACE and handle it in
DoProcessDeviceEvent to populate cudaGraphCurrentLaunch[graphId]
- Add cudaGraphCurrentLaunch map to PersistentState
- Two-pass buffer processing in OnBufferCompleted so GRAPH_TRACE
records (which complete last on GPU) are processed before the
kernel/memcpy/memset records that depend on them
- Replace graphId=0 fallback in kernel/memcpy/memset with proper
cudaGraphCurrentLaunch lookup; fall through to matchError if
the graphId is not found
- Update repro to include TracyCUDA headers and properly test
GPU zone correlation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This is often a source of missing symbols or incomprehension as to why they are not getting resolved. Having a debug log will help debugging such cases.
- Add external_file field to backtrace_state struct
- Add backtrace_create_state_for_file() function that marks
state for external ELF files not loaded in current process
- In backtrace_initialize, use external_file flag to:
- Pass exe=0 to elf_add for ET_DYN files (allows DWARF loading)
- Skip dl_iterate_phdr enumeration (avoids noise from current process)
This enables symbol resolution from arbitrary ELF files on disk,
with caller responsible for address translation.
Minimal reproducer showing that CUDA Graph-launched kernels produce
0 GPU zones in Tracy. The repro creates a simple graph (2 kernels +
1 memcpy), launches it 10 times, and expects ~30 GPU zones. Without
the fallback patch, all activity records are dropped by matchError().
Tested on NVIDIA H100, CUDA 13.1.
When kernels are launched via CUDA Graphs (cuGraphLaunch), CUPTI delivers
CONCURRENT_KERNEL, MEMCPY, and MEMSET activity records but no
corresponding API callback fires for the individual operations. This
means matchActivityToAPICall() always fails, and every GPU activity
record is silently dropped by matchError().
Fix this by falling back to a synthetic APICallInfo using the GPU
timestamps from the activity record when no API correlation exists.
This produces correct GPU zones with kernel names and timing — just
without the CPU-to-GPU launch correlation arrow.
Tested on NVIDIA H100 with CUDA 13.1: before this fix, 0 GPU zones
appeared for CUDA Graph workloads; after, all kernel and memcpy zones
are visible in the Tracy timeline.
Use '<pid>_<ip>_<port>' string as client ID instead of IP+port hash.
This allows:
- Same program restarting (new PID) to be recognized as new client
- Multiple instances of same program (different PIDs) to capture separately
- Add subsection about tracy-capture-daemon in the capturing section
- Move merge tool documentation to follow capture daemon section
- Both tools are now documented as part of the capture workflow
A discovery-and-capture daemon that listens for UDP broadcasts from
Tracy clients, automatically connects to discovered clients, and
captures each to a separate file.
Features:
- Continuous discovery until Ctrl+C
- Per-client capture threads
- Terminal display with per-client stats
- Output files named: <program>_<ip>_<port>.tracy
- Collision handling with _1, _2 suffix
- Graceful shutdown on signal
Based on the multicapture design by Grégoire Roussel, but simplified
to output separate files instead of merging (use tracy-merge for that).
Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
Extract common output functions from capture.cpp into a proper library:
- InitTerminalDetection()/IsTerminal() - terminal detection
- AnsiPrintf() - printf with ANSI escape codes
- WaitForConnection() - blocks until connected, returns error code
- PrintCaptureProgress() - prints throughput/memory/time stats
- PrintWorkerFailure() - prints failure details with callstack
Functions are declared in CaptureOutput.hpp and implemented in
CaptureOutput.cpp. Both tracy-capture and future tools can share
this code.
Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
- Always export plots (remove -p/--export-plots option)
- Add plot name disambiguation: prefix with process name
- Include PID in name when same process/plot appears in multiple traces
Merges multiple .tracy files into a single combined trace using the
Import API. Each trace's threads are remapped using compound TIDs
encoding (pid << 32) | tid to prevent collisions.
Thread names are prefixed with process name. If the same process/thread
name combination appears in multiple traces, PID is included to
disambiguate (e.g., myapp[12345]/MainThread).
Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
Move broadcast message parsing logic from profiler/src/main.cpp into
server/TracyBroadcast.cpp/hpp. This reduces code duplication and enables
reuse by other tools (e.g., multi-capture).
ParseBroadcastMessage() handles all broadcast protocol versions (0-3) and
returns std::optional<BroadcastMessage>. ClientUniqueID() generates a unique
identifier from IP address and port.
Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
Add option to configure the maximum tool reply size in the advanced
settings of the LLM assistant. The limit can be enabled/disabled via
a checkbox, with the value stored in bytes. The context-based limit
is now displayed alongside the configured limit for transparency.
External symbols checkbox now appears before kernel symbols, and kernel
is disabled when external is not selected since kernel symbols are a
subset of external symbols.
Introduce Windows ARM64(native) support across ToyPathTracer,
profiler, and server code paths when building with MSVC(_M_ARM64).
Key changes:
- MathSimd.h/Maths.h:
- Fix NEON movemask constants for MSVC/ARM64 by loading from a uint32_t[]
via vld1q_u32() and using vdupq_n_u32() for highbit.
- enkiTS/TaskScheduler.cpp:
- Provide Pause() implementation on _M_ARM64 using __yield().
- profiler/winmain.cpp:
- AVX feature checks to x86/x64 only and skip on ARM64.
- server/TracyPopcnt.hpp:
- Implement TracyCountBits using ARM NEON intrinsics.
- Implement TracyLzcnt using _BitScanReverse64().
The chat regenerate/trash button handling had a monolithic lock that
blocked the worker thread during token counting. The token counting
is performed by calling TracyLlmApi::Tokenize(), which issues a
blocking HTTP POST request to the LLM API - on the UI thread.
To avoid blocking both the worker and freezing the UI during HTTP
calls, the lock was split into m_jobsLock and m_chatLock. However,
this introduced a race condition.
For the assistant role (regenerate message action), the code:
1. Stops the current job and queues a new one (under m_jobsLock)
2. Releases the lock
3. Performs token counting via HTTP calls (no lock held)
4. Re-acquires m_jobsLock and stops m_currentJob again
Between steps 2 and 4, the worker thread can complete the old job
and pick up the newly queued job. The stop at step 4 then incorrectly
stops the new regenerate job instead of the old one.
Under the original monolithic lock, the worker couldn't access
m_currentJob until after step 4, so the stop always applied to the
correct job. After the split, the second stop became harmful for
the assistant case.
The fix makes the second stop conditional on user role only (thrash
action), since that's the case where no new job is queued and we
genuinely need to stop any running generation before the user edits
their message.
Listen for browser paste events and directly inject clipboard text via
AddInputCharactersUTF8. Also suppress character input when Ctrl/Meta is
held to prevent 'v' from being typed on Cmd+V.
On macOS with Retina displays, dpiScale is 2.0 but gets overridden to
userScale alone under __APPLE__. The early-return check compared
prevScale against the pre-override value (dpiScale * userScale), which
for 50% zoom evaluates to 2.0 * 0.5 = 1.0, matching the previous
prevScale of 1.0 and causing an early return before the scale change
could take effect. Moving the __APPLE__ override before the early-return
check ensures the comparison uses the actual effective scale.
Ideally I think we might want to have TRACY_HW_TIMER mean only TSC/CNTVCT, and define TRACY_TIMER_FALLBACK for platforms that don't have them or have a special case such as iOS.
But for now, keep the same behaviour.
The Emscripten backend was missing Platform_SetClipboardTextFn/GetClipboardTextFn,
so ImGui::SetClipboardText was a no-op in the browser.
Hook navigator.clipboard.writeText for writes and keep a local copy for reads.
The "sort" button for threads uses an unstable sort, so threads with
the same name will shuffle around without ever stabilizing.
Use `id` to sort after comparing the name.
The valid check here is for the source file index being set (active).
The line number might be zero.
This fixes the "black" unknown source location in assembly listing.
Increase the context quota from 70% to 80%, as the previous value was too
conservative with large contexts.
Add a minimum bound of 4K to make small context workable.
TracyDebug() is in the profiler init path, and logging the message causes
the profiler to be initialized (the second time), which deadlocks in
GetProfilerData().
LM Studio will prefix the assistant's content with '\n\n'. While this is
not a problem when proper text follows (the markdown parser will ignore
this), the empty check does fail.
A typical use case would be $(HOME)/.cache/cpm/somelib/file.h.
Special care is needed to avoid filtering out dot-dot path elements: /../
While these have been normalized for some time now on the client-side, old
traces might still contain the dot-dot elements.
Before this commit, vertical scroll was always discrete. At least on
Wayland, this caused extremely fast scrolling on touchpads (that send
lots of small axis events) and on mice with high-resolution wheels (that
also send lots of small axis events). After this commit, all of this
scrolling works correctly, at a speed matching regular wheels.
Regular mice send a value of 15 for one wheel tick, not 8.
This currently doesn't change anything about vertical scrolling since
it's handled discretely, but that will change in the next commit.
In most cases this is not needed. However, some models, like Gemma3 or
Devstral require that user and assistant messages alternate.
The only case where this can happen in Tracy is when an attachment is added:
[
{
"role": "user",
"content": "<attachment>\n..."
},
{
"role": "user",
"content": "Tell me something about..."
}
]
It is trivial to glue these messages together. This is only done when sending
the data in the REST request, as the chat rendering logic expects these to be
separate and it would be too much work unnecessary work to do it "proper".
Nemotron 3 Nano outputs these spaces in the text. The currently used font
(or is it ImGui?) is not able to render this, and draws replacement character
instead.
Previously reasoning and tool calls were rendered first, followed by
content. The tool call response was then rendered in the second reasoning
section. This made the tool call and response disjoint and, with the
current reasoning hiding logic, not visible at the same time.
AltGr (right alt) is used to enter characters, at least with the Polish
keymap. ImGui uses alt to switch between some of the controls. Keep the
interaction on the (left) Alt key, and leave AltGr to do composition.
This is not removed later on, mainly to enable caching of previous replies
(changing any earlier message would require processing everything that's
later), but also to give the LLM a sense of passing time.
This also replaces `___tracy_emit_message*` by `___tracy_emit_logString`.
The `TracyCMessage*` defines no long include the `;`, which may be a breaking change even though we did already require semi-colon since 0.9.0. See #493 and #592
This now uses a single path for all cases (filter/threads changed, new message).
Note this no longer discriminates against `m_messageFilter.IsActive()` to skip the message filter. `ImGui::PassFilter already` early outs, and if performance is a concern we might as well start by caching the result of `VisibleMsgThread` which always does a hashmap lookup.
There are two changes to the protocol:
- `QueueMessageLiteral*` were changed and what used to be addresses are now addresses+metadata
- Other messages now send `QueueMessage*Metadata` with added metadata.
This will later be used to store and transmit message sources, level, etc.
Extended Tracy's CUPTI callback registration to track CUDA Driver API
memory operations that were previously missing.
Registration of these events allow users to trace applications that directly
call the Driver API.
- In the connection state, retrieve the FrameImage while owning the data lock.
- Use actual image data pointer as caching key instead of the address of ImageCache which may change during executation (unstable).
- Fixes scale Messages image tooltip scale.
- Free the connection image
OfflineSymbolResolverDbgHelper.cpp uses IMAGEHLP_LINE but
SymGetLineFromAddr64 expects IMAGEHLP_LINE64. On 64-bit Windows these are typedef'd to the same thing, but on 32-bit they're different.
1. fix bug build error can not find head file `#include <tracy/Tracy.hpp>`
2. when not enable TRACY_ENABLE macro, build error can not find `tracy::CUDACtx` type
Atomic variables need to be initialize with direct-initialization
instead of copy-initialization lest the compilation fails complaining
about missing constructor for the atomic type.
description: Build, serve, and drive the Tracy Profiler's emscripten (web) GUI in a headless browser with verified mouse/keyboard semantics. Use when developing or testing the profiler GUI. No X11/Wayland/DISPLAY required — the browser is the display server.
---
# Test the Tracy web GUI in a browser
Build the emscripten profiler GUI into an instrumented temporary workspace,
serve it, and drive it with the browser tool. The harness exports
(`debug_snapshot`/`debug_clear`) expose the app-side mouse/ID state — they
are the ground truth for targeting and verification, not screenshots.
Applies to this repo's emscripten build with ImGui 1.92.9b-docking and
Emscripten 5.0.7. Constants marked M are workstation-dependent — re-verify
them. Everything else is stable; do not re-derive it.
## Setup
```sh
sh <this-skill-dir>/setup.sh # workspace=/tmp/tracy-protocol
# or: TRACY_WEB_WS=/elsewhere TRACY_WEB_REPO=<repo> sh setup.sh
```
`<this-skill-dir>` is the project-scoped `.omp/skills/tracy-gui-verify/`
under the active project root — not a fixed home directory. If multiple
copies exist (one per project that has used this skill), use the copy
inside the repo being tested; `TRACY_WEB_REPO` controls which repo is
copied into the workspace.
The script: fresh source copy (excludes `.git` and build dirs) → applies
set_option(TRACY_LIBUNWIND_BACKTRACE"Use libunwind backtracing where supported"OFF)
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE"Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution"OFF)
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT"Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation"OFF)
set_option(TRACY_DISALLOW_HW_TIMER"Disallow hardware timer (may be useful on VMs). Requires TRACY_TIMER_FALLBACK=ON"OFFTracyClient)
set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported"OFFTracyClient)
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE"Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution"OFFTracyClient)
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT"Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation"OFFTracyClient)
set_option(TRACY_IGNORE_MEMORY_FAULTS"Ignore instrumentation errors from memory free events that do not have a matching allocation, or from repeated allocations of the same address"OFFTracyClient)
set_option(TRACY_OPENGL_AUTO_CALIBRATION"Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)"OFFTracyClient)
# advanced
set_option(TRACY_VERBOSE"[advanced] Verbose output from the profiler"OFF)
set_option(TRACY_VERBOSE"[advanced] Verbose output from the profiler"OFFTracyClient)
mark_as_advanced(TRACY_VERBOSE)
set_option(TRACY_DEMANGLE"[advanced] Don't use default demangling function - You'll need to provide your own"OFF)
set_option(TRACY_NO_INTERNAL_MESSAGE"[advanced] Prevent the profiler from logging messages"OFFTracyClient)
mark_as_advanced(TRACY_NO_INTERNAL_MESSAGE)
set_option(TRACY_DEMANGLE"[advanced] Don't use default demangling function - You'll need to provide your own"OFFTracyClient)
mark_as_advanced(TRACY_DEMANGLE)
if(rocprofiler-sdk_FOUND)
set_option(TRACY_ROCPROF_CALIBRATION"[advanced] Use continuous calibration of the Rocprof GPU time."OFF)
set_option(TRACY_ROCPROF_CALIBRATION"[advanced] Use continuous calibration of the Rocprof GPU time."OFFTracyClient)
mark_as_advanced(TRACY_ROCPROF_CALIBRATION)
endif()
# handle incompatible combinations
if(TRACY_MANUAL_LIFETIMEANDNOTTRACY_DELAYED_INIT)
message(FATAL_ERROR"TRACY_MANUAL_LIFETIME can not be activated with disabled TRACY_DELAYED_INIT")
### A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling profiler for games and other applications.
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphic APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphics/compute APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA, WebGPU.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
- [Documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf) for usage and build process instructions
- [Releases](https://github.com/wolfpld/tracy/releases) containing the documentation (`tracy.pdf`) and compiled Windows x64 binaries (`Tracy-<version>.7z`) as assets
printf("\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n");
return1;
}
if(handshake==tracy::HandshakeNotAvailable)
{
printf("\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n");
return2;
}
if(handshake==tracy::HandshakeDropped)
{
printf("\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n");
@@ -106,7 +56,7 @@ int main( int argc, char** argv )
}
#endif
InitIsStdoutATerminal();
InitTerminalDetection();
booloverwrite=false;
constchar*address="127.0.0.1";
@@ -165,26 +115,8 @@ int main( int argc, char** argv )
printf("Connecting to %s:%i...",address,port);
fflush(stdout);
tracy::Workerworker(address,port,memoryLimit);
while(!worker.HasData())
{
constautohandshake=worker.GetHandshakeStatus();
if(handshake==tracy::HandshakeProtocolMismatch)
{
printf("\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n");
return1;
}
if(handshake==tracy::HandshakeNotAvailable)
{
printf("\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n");
return2;
}
if(handshake==tracy::HandshakeDropped)
{
printf("\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n");
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
-//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
+#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_TIME_FUNCTIONS // Don't setup default platform_io.Platform_SessionDate value using time(), localtime_r().
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
+ // An Index of -1 means this column had no "Column N" line in the .ini file (columns without saved
+ // data are not written). Keep the display order as initialized (identity for non-reorderable tables). (#9519)
column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults()
if ((load_flags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
crates_left=-1;// the menu never opens an exit portal
}
}
World::~World()=default;
voidWorld::tick()
{
map_->tick(*this);
if(player_)
player_->tick(*this);
}
voidWorld::draw()
{
map_->draw();
if(player_)
player_->draw();
}
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.