Compare commits

...

590 Commits

Author SHA1 Message Date
Marcos Slomp
841fae2371 code review: formating consistency 2026-09-14 10:16:24 -07:00
Marcos Slomp
016ae062e9 addressing review comments 2026-09-14 08:48:23 -07:00
Marcos Slomp
8cbf2b14a2 use FastVector for the shadow buffer 2026-09-14 08:48:23 -07:00
Marcos Slomp
6be14cff4e increasing the total query limit to 64K queries 2026-09-14 08:48:23 -07:00
Marcos Slomp
a11c8ab40c refactoring (isolating) Tracy protocol bits 2026-09-14 08:48:23 -07:00
Bartosz Taudul
c71e059d90 Bump exmples/dyna CPM to 0.43.1. 2026-09-13 20:30:40 +02:00
Bartosz Taudul
3247f490f7 Update NEWS. 2026-09-13 16:53:42 +02:00
Bartosz Taudul
edc649cdec Bind Ctrl+plus / Ctrl+minus / Ctrl+0 to the user interface scale. 2026-09-13 16:53:42 +02:00
Bartosz Taudul
1381f40bd9 Represent the user interface scale as a preset table index.
Replace the userScale float with a zoomLevel index into s_zoomPresets;
the ini key changes accordingly and previously saved floats are ignored.
2026-09-13 16:21:05 +02:00
Bartosz Taudul
ee067d112b Update NEWS. 2026-09-12 22:31:34 +02:00
Bartosz Taudul
0c35226446 Add warning about large traces. 2026-09-12 22:25:01 +02:00
Bartosz Taudul
63ffadc852 Load the startup trace in a background thread.
Opening and parsing on the browser main thread froze the tab for the
whole load; the file selector path already loads in a worker thread,
so boot now uses the same thread, progress modal, and error dialogs.
2026-09-12 22:19:59 +02:00
Bartosz Taudul
af3432cfde Fetch the startup trace instead of preloading the embedded one.
The preloaded data package made every ?t= load download embed.tracy
as well. The startup glue now fetches embed.tracy only when no URL is
given, into the same /url.tracy slot, and the boot code has a single
open path.
2026-09-12 22:19:59 +02:00
Bartosz Taudul
1fb370f935 Give version and load failure exceptions meaningful messages. 2026-09-12 22:19:59 +02:00
Bartosz Taudul
27f12a4eb5 Report unloadable trace files on the command line instead of crashing.
Version and integrity checks throw from the View constructor, which
was outside the try/catch guarding the file open, so a bad argv trace
terminated the process. Catch and report it there as well.
2026-09-12 22:19:58 +02:00
Bartosz Taudul
1c50138f85 Report failed second-trace loads in compare instead of crashing.
The compare load thread caught only UnsupportedVersion, so a
corrupted or otherwise unloadable second trace escaped the thread as
std::terminate. Catch the same set as the main trace load and route
it to the existing bad-version dialog.
2026-09-12 22:19:58 +02:00
Bartosz Taudul
2191885f86 Show a generic message for unreadable or corrupted trace files.
FileReadError now also signals truncated or malformed traces, not just
mapping failures, so the mmap-specific text misled users.
2026-09-12 22:19:58 +02:00
Bartosz Taudul
8a5f2c6f8c Show error dialogs instead of aborting the page on trace load errors.
Exception catching was off by default in the web build, so the
try/catch handlers around trace loading were dead code and any
failure unwound through the page.
2026-09-12 22:19:58 +02:00
Bartosz Taudul
643778c8e6 Decompress overflow streams inline on emscripten.
Loading on the browser main thread cannot start more than the 8
pre-created workers: beyond that, thread creation waits for the main
thread to return to its event loop, which the load never does.
Decompression threads were only an optimization, so on emscripten
spawn workers for at most 4 streams and decompress the rest on the
loading thread.
2026-09-12 22:19:58 +02:00
Bartosz Taudul
b45e9ee28a Do not truncate CallstackFrameId hash.
Frame address typically will be 64 bit, but size_t is 32-bit on emscripten.
2026-09-12 22:19:55 +02:00
Bartosz Taudul
fdd01d3007 Update manual. 2026-09-12 13:21:30 +02:00
Bartosz Taudul
8107754a3c Support loading trace files from URL in web version. 2026-09-12 13:08:04 +02:00
Bartosz Taudul
4332b43c3b Serve the web development files with CORS enabled.
The server doubles as a host for trace files to be loaded by the web
version from another origin, for example when profiling over the
local network.
2026-09-12 12:46:11 +02:00
Bartosz Taudul
0d6916273b Remove the trace filename from window title in web version.
The filename in the web build is an internal temporary name, either
embed.tracy or upload.tracy, and carries no information for the user.
2026-09-12 12:45:56 +02:00
Bartosz Taudul
e8a6be173f Ensure minimum find zone window width. 2026-09-11 22:34:04 +02:00
Bartosz Taudul
1c10623c6a Sync the manual with the display icon for GPU zones. 2026-09-11 22:23:43 +02:00
Bartosz Taudul
cf31c34367 Update NEWS. 2026-09-11 22:08:49 +02:00
Bartosz Taudul
7370f3a6b6 Prefix GPU zone names in the find list with the display icon. 2026-09-11 22:08:37 +02:00
Bartosz Taudul
6217872124 Explicit commit message rules. 2026-09-11 22:08:37 +02:00
Bartosz Taudul
4f0edd1304 Use the display icon for GPU zones. 2026-09-11 22:08:35 +02:00
Bartosz Taudul
ec4e36074b Merge pull request #1469 from wolfpld/slomp/gpu-stats
GPU Zone Statistics
2026-09-11 20:42:41 +02:00
Bartosz Taudul
70fe1e8d69 Use emscripten 6.0.9. 2026-09-11 19:51:07 +02:00
Bartosz Taudul
35b4922ed4 Bump LZ4 to 1.10.0. 2026-09-11 00:47:22 +02:00
Bartosz Taudul
8cf122cf22 Bump md4c to 00788b157. 2026-09-11 00:47:22 +02:00
Bartosz Taudul
fb1b13a8a8 Bump nfd to 7bbbd9fe. 2026-09-11 00:47:22 +02:00
Bartosz Taudul
159567701c Enable OBJC language on Apple builds.
glfw 3.5.1 enables OBJC build-wide under CMake 4.x, remapping nfd's
nfd_cocoa.m to a language its scope never loaded; generate then fails on
CMAKE_OBJC_COMPILE_OBJECT. Initialize OBJC at the top level so sibling
scopes get the rules.
2026-09-11 00:47:18 +02:00
Marcos Slomp
190809c990 update docs 2026-09-10 09:59:46 -07:00
Marcos Slomp
e75106a16f removing [GPU] tag from source location listing (it does not work as I expected) 2026-09-09 22:36:02 -07:00
Marcos Slomp
9535a39fba refactoring 2026-09-09 22:36:02 -07:00
Marcos Slomp
661f4de636 addressing code duplication 2026-09-09 22:36:01 -07:00
Marcos Slomp
a71d7c2ee2 fix load-time data race on the GPU map 2026-09-09 22:36:01 -07:00
Marcos Slomp
44eabc5814 GPU Zone Statistics 2026-09-09 22:36:01 -07:00
Bartosz Taudul
b892dd0b96 Cherry-pick queue memory-ordering fixes from concurrentqueue v1.0.5. 2026-09-10 00:51:12 +02:00
Bartosz Taudul
d542359cc0 Use the system wayland-protocols package. 2026-09-10 00:51:12 +02:00
Bartosz Taudul
1ebdd2e598 Pin glad in the dyna example to v2.0.8. 2026-09-10 00:51:12 +02:00
Bartosz Taudul
44e904ba87 Pin glew in the OpenGL example to release 2.3.1. 2026-09-10 00:51:11 +02:00
Bartosz Taudul
8a0043177d Remove orphaned gl3w patch. 2026-09-10 00:51:11 +02:00
Bartosz Taudul
64fdd0159a Bump CPM to 0.43.1. 2026-09-10 00:51:11 +02:00
Bartosz Taudul
d42e2447d4 Bump xxhash to 0.8.3. 2026-09-10 00:51:11 +02:00
Bartosz Taudul
353339cb08 Bump usearch to 2.26.2. 2026-09-10 00:51:11 +02:00
Bartosz Taudul
f032eba389 Bump glfw to 3.5.1. 2026-09-10 00:51:11 +02:00
Bartosz Taudul
656980d757 Bump curl to 8.22.0. 2026-09-10 00:51:06 +02:00
Bartosz Taudul
f60889e1a7 Compute Find zone std dev from the displayed distribution.
The raw sumSq paired with a self/running mean yielded the raw variance
inflated by the squared mean gap. Accumulate the vector's own sum of
squares alongside its sum, like compare and frame statistics do.
2026-09-09 21:13:14 +02:00
Bartosz Taudul
82790f904f Save context switches of all threads.
Per-thread lists were dropped for threads without zones or samples, a rule
from when only profiled threads consumed them; the CPU data graph uses them
for any visible thread, so saved traces silently lost wake and wait data.
2026-09-09 01:53:09 +02:00
Bartosz Taudul
ed468d8625 Reject truncated trace files.
Block sizes weren't checked against the file length, so truncated files
made the decompression threads read off the mapping. Validate framing
(u32 size + payload) before the mapping and threads exist; also reject
zero-stream or empty files.
2026-09-09 01:04:55 +02:00
Bartosz Taudul
25ba41c44e Read tracepoint field offsets from tracefs format files.
The client decoded sched_switch, sched_waking and drm_vblank_event at
fixed positions behind an 8-byte common header. The kernel can extend the
header, which shifts the field positions.

The format files are generated from the live trace_entry (offsetof/ALIGN),
making them the authoritative layout.
2026-09-08 22:08:16 +02:00
Bartosz Taudul
2ba979056c Merge pull request #1467 from psmyles/sections-group-by-category
Add option to group section rows by category
2026-09-08 18:15:36 +02:00
Chandan Singh
ecf722b64f Remove stray carriage return in the manual 2026-09-08 21:44:34 +05:30
Chandan Singh
66434446b1 Add option to group section rows by category.
Sections are packed into timeline rows purely by length, so sections
from different categories share rows and the same category can move
between rows along the timeline. When categories mark different kinds of
activity (game levels, loading screens, cutscenes) this makes the row
layout hard to read.

The new "Group by category" option in the section options lays out each
category in its own set of rows, in category identifier order, with the
existing length-first packing applied within a category. Off by default,
so the current layout is unchanged unless the option is enabled. Only
shown when the trace has more than one section category.
2026-09-08 15:03:02 +05:30
Bartosz Taudul
da5c8153bc Disable capture-daemon directory lock on windows. 2026-09-06 01:48:06 +02:00
Bartosz Taudul
ad0785f6c2 Update pandoc to 3.11.
Pandoc 3.11 appends the LaTeX caption label to Markdown table
captions as a {#id} identifier; strip it in tablecaption() since the
manual renderer has no identifiers.
2026-09-06 01:25:08 +02:00
Bartosz Taudul
82a4eea9d5 Update manual. 2026-09-06 01:07:47 +02:00
Bartosz Taudul
6992454dfd Save the tracy-monitor trace to a file. 2026-09-06 01:07:47 +02:00
Bartosz Taudul
3909a7407a Capture the trace in-process in tracy-monitor. 2026-09-06 01:07:47 +02:00
Bartosz Taudul
89c2b9e5f8 Add the capture-side server to the monitor build.
The monitor embeds the capture side of the protocol (TracyWorker) to
save traces to disk; like tracy-capture, build it without statistics
and at C++20.
2026-09-06 01:07:47 +02:00
Bartosz Taudul
d288c38c98 Add a per-directory lock file to the capture daemon.
Only one daemon can write to a given output directory at a time. The
lock holds the daemon PID and is removed on a clean exit; a lock whose
owner is verifiably gone is taken over on the next start, and a lock
whose owner cannot be read or that reads as alive must be removed by
hand.
2026-09-06 01:07:47 +02:00
Bartosz Taudul
6735c64306 Extend GUI skill to support headless X11. 2026-09-06 01:07:47 +02:00
Bartosz Taudul
fab65fae88 Agent guidance. 2026-09-06 01:07:47 +02:00
Bartosz Taudul
2dfe5c5c75 Save tracy-update output through a temporary file. 2026-09-06 01:07:46 +02:00
Bartosz Taudul
927ca83894 Save tracy-merge output through a temporary file. 2026-09-06 01:07:46 +02:00
Bartosz Taudul
51160bcc28 Save GUI traces through a temporary file. 2026-09-06 01:07:46 +02:00
Bartosz Taudul
e848076dfa Add a file writer that replaces its target only on completion.
The data is written to <fn>.tmp in the same directory as the target
and moved to <fn> on Commit(); a failed or interrupted write leaves
the previous file untouched, with an orphaned .tmp.
2026-09-06 01:07:46 +02:00
Bartosz Taudul
6aa41759d0 Flush the output file in FileWrite::Finish.
The final compressed block was left in the stdio buffer until the
destructor closed the file, so a caller that inspected the file after
Finish() saw a truncated trace.
2026-09-06 01:07:46 +02:00
Bartosz Taudul
4cbec36af6 Remove undefined source list variables from the capture build. 2026-09-06 01:07:46 +02:00
Bartosz Taudul
db6a46cf3e Extract shared capture-side tool code into TracyUtil. 2026-09-06 01:07:46 +02:00
Bartosz Taudul
ebf5ebd9bd Replace the output file safely in tracy-capture.
The old pre-flight truncated the output at startup with -f, so an
interrupted capture lost the trace, and the probe fopen followed a
symlink. The existing file is now moved to FILE~ for the duration of
the capture and restored if the trace cannot be written.
2026-09-06 01:07:45 +02:00
Bartosz Taudul
cf3407bbe3 Fix empty rate sample list read in the connection popup. 2026-09-06 01:07:45 +02:00
Bartosz Taudul
89f12318a1 Fix empty rate sample list read in the daemon stats. 2026-09-06 01:07:45 +02:00
Bartosz Taudul
fa4336dd26 Fix empty rate sample list read in the progress line.
HasData() flips when the connection is established, but the rate
list is only filled by the worker periodic stats update, whose first
entry arrives ~200ms later, so the first render could read an empty
list.
2026-09-06 01:07:45 +02:00
Bartosz Taudul
89132aed2a Merge pull request #1466 from akx/macos-level
Use CMAKE_OSX_DEPLOYMENT_TARGET 15.0 by default
2026-09-02 14:23:33 +02:00
Aarni Koskela
76c310eade Use CMAKE_OSX_DEPLOYMENT_TARGET 15.0 by default
As it is, the CI-built macOS binaries refuse to work on
macOS Sequoia because it defaults to the runner machine's level.

This sets a lower-than-default default for the target.
2026-09-02 10:39:36 +03:00
Bartosz Taudul
538af5c89c Learn the actual port of an adopted listen socket.
Adopting a pre-bound socket via SetReservedListenSocket left Worker()'s
port bookkeeping untouched, so GetPort() and the broadcast announcement
reported the computed default instead of the port the socket listens on.
The monitor kept this correct by mirroring the reserved port into
TRACY_PORT; direct users of the public API had no such coupling.

The bound socket is now authoritative: Worker() reads the local port
back via getsockname after binding. The search-loop bookkeeping stays
only as a fallback for a getsockname failure.
2026-08-31 19:30:23 +02:00
Bartosz Taudul
f52bf6a718 Merge pull request #1461 from ofoure/master
Added Profiler::GetPort() and TracyPort macro
2026-08-31 19:07:37 +02:00
Bartosz Taudul
70e4170e26 Cache the downloaded pandoc binary. 2026-08-31 18:54:55 +02:00
Bartosz Taudul
c6f1cb7585 Generate markdown manual at build time. 2026-08-31 18:54:55 +02:00
Bartosz Taudul
00e152b97a Allow embedding files from the build directory. 2026-08-31 18:54:53 +02:00
Olivier Fouré
17a4080713 Documented the TracyPort macro in the manual. 2026-08-31 09:58:26 +02:00
Bartosz Taudul
c756e7418e Fix android build. 2026-08-31 02:48:20 +02:00
Bartosz Taudul
71f0c992f7 Build monitor on CI. 2026-08-31 02:48:16 +02:00
Bartosz Taudul
06f4e13012 Document tracy-monitor in the user manual. 2026-08-31 01:58:38 +02:00
Bartosz Taudul
a66cc184e8 Annotate the file sections. 2026-08-31 01:58:37 +02:00
Bartosz Taudul
c9abfce098 Add a TRACY_ON_DEMAND build option. 2026-08-31 01:58:37 +02:00
Bartosz Taudul
a15e08f135 Rewrite the usage text. 2026-08-31 01:58:37 +02:00
Bartosz Taudul
fdff78584a Raise the fd limit for the sample events.
External sampling opens one event per CPU for each existing thread
(attach) or per CPU (launch) per event type, so a multithreaded target
needs many fds.
2026-08-31 01:12:41 +02:00
Bartosz Taudul
893439772f Reject client configurations that cannot work.
TRACY_NO_CALLSTACK, TRACY_NO_SYSTEM_TRACING, TRACY_NO_SAMPLING and
TRACY_SAMPLING_PROFILER_MANUAL_START either fail to build (the external
target API lives in the callstack code) or silently produce an empty
capture (no sampling event fails, so IsSystemTracingFailed stays false).
The first three are #error'd at compile time; the TRACY_NO_SYS_TRACE /
TRACY_NO_SAMPLING environment variables are refused at startup.
2026-08-31 01:12:41 +02:00
Bartosz Taudul
6d794801c6 Verify the exec stop in launch mode.
A caught signal (the handlers are inherited by the child) delivered
between PTRACE_TRACEME and exec first produces a signal-delivery stop;
without handling it, the setup would run on the still pre-exec child and
capture the monitor's own image as the target's. Re-inject the caught
signal and wait again until the exec stop is reached. Job-control stops
are the exception - re-injecting SIGSTOP/SIGTSTP re-stops the child, and
PTRACE_CONT rejects a stop carrying the 0x80 job-control bit - so resume
those without a signal instead.
2026-08-31 01:12:41 +02:00
Bartosz Taudul
b36c8f2fc5 Refuse to start without tracefs.
The client's system tracing cannot start without a tracefs (or debugfs)
mount - the tracepoint ids live under it - so starting anyway would
capture no samples.
2026-08-31 01:12:41 +02:00
Bartosz Taudul
2f2d8a7ba9 Fail when system tracing fails to start.
IsSystemTracingFailed reports a SysTraceStart that the preflight could
not predict (the target exiting between the checks, the kernel rejecting
the event setup); starting anyway would capture no samples.
2026-08-31 01:12:41 +02:00
Bartosz Taudul
d498829518 Verify the client is listening after startup.
Polling IsDataPortListening is the only reliable check: probing the port
itself cannot distinguish the client's listener from another process
holding it.
2026-08-31 01:12:40 +02:00
Bartosz Taudul
e87b7c873e Reserve the client listen port.
When TRACY_PORT is not pinned the client probes 8086..8105 at startup.
Reserve the first free port up front and hand the already-bound socket
to the client via SetReservedListenSocket, so the reservation is atomic
and the reported port matches what the client listens on. A pinned
TRACY_PORT is validated like --port, since the client pins to any
nonzero value with no fallback.
2026-08-31 01:12:40 +02:00
Bartosz Taudul
6109ab27b5 Add sample rate and port options. 2026-08-31 01:12:40 +02:00
Bartosz Taudul
7357b9fa52 Attach by process name.
The name is matched against /proc/<pid>/comm, which the kernel truncates
to 15 characters. Several matches are listed so the user can pick one
with -p; a match is only accepted if /proc/<pid>/exe is readable, which
excludes zombies (they keep a comm but have no executable to map) and
inaccessible processes.
2026-08-31 01:12:40 +02:00
Bartosz Taudul
46f2489f92 Report the active data sources at startup. 2026-08-31 01:12:40 +02:00
Bartosz Taudul
ac970771ee Preflight the exact sample event.
The old preflight opened one generic event, which said little about
whether the client's actual setup would work. PreflightSamplingEvent now
opens the client's exact external event shape (the per-CPU pid-filtered
CPU_CLOCK callstack event of SysTraceStart), verifies the ring mmap
works, and probes the hardware PMU counters informationally. On
EACCES/EPERM retry user-space only - both event and callchain, since the
callchain part still requires perf_allow_kernel - and report kernel
frames as unavailable; any other error is fatal, as the client's
identical shape would fail the same way.
2026-08-31 01:12:40 +02:00
Bartosz Taudul
d87cbed7eb Probe system-wide tracepoints and RAPL. 2026-08-31 01:12:40 +02:00
Bartosz Taudul
252a3dae43 Treat zombie targets as dead.
kill(pid, 0) succeeds for zombies, so a SIGKILLed attach target whose
parent had not reaped it yet kept the monitor polling indefinitely - in
attach mode the monitor cannot reap it. Liveness now also reads the
/proc state and treats Z as dead.
2026-08-31 01:12:40 +02:00
Bartosz Taudul
c9d3ec0088 Harden external thread name lookups against dead threads.
A thread exiting between fopen and read leaves the comm and status
buffers under-filled or uninitialized: zero-initialize them, treat a
short or empty read as unknown, and never scan the status when the read
returned nothing.
2026-08-31 01:12:39 +02:00
Bartosz Taudul
e5d20e1377 Free the tid buffer when thread enumeration yields nothing.
A task directory yielding no numeric entries (the target exiting
mid-enumeration) returned 0 with the freshly allocated array still
handed back through *out, and the caller's failure path returned without
freeing it.
2026-08-31 01:12:39 +02:00
Bartosz Taudul
da0d2328f5 Require per-target events for external sampling success.
SysTraceStart only failed when no event of any kind opened, but in
external mode the global sched/vsync tracepoints (pid -1) succeed
independently of the target: a target exiting after thread enumeration
left every per-target open failing while the startup still reported
success. s_ctxBufferIdx is the per-target ring count right after the
per-target setup, so in external mode require it to be non-zero as well.
2026-08-31 01:12:39 +02:00
Bartosz Taudul
8db1a298c4 Open external sampling as CPU-gated per-CPU events.
The previous external shape - one per-task (cpu = -1) inherit event per
existing thread - cannot be mmap'd at all: perf_mmap() in
kernel/events/core.c refuses inherited per-task counters (-EINVAL, all
children would write the same ring), so no sample ring was ever created.
A per-CPU event filtered on the target's tgid (the self-profiling shape)
only covers the group leader; the kernel does not retro-inherit onto
pre-existing sibling threads.

Open one CPU-gated event per ring instead - perf_event_open(attr, pid,
cpu), one open per CPU and per target thread, the same shape as perf's
open loop (tools/perf/util/evsel.c:3031). The thread enumeration picks
the fan-out: at launch, per-CPU events on the target pid, inherited by
every later-spawned thread; on attach, per-thread per-CPU events for
every existing tid. Failing opens degrade gracefully: the affected
thread is simply not sampled.
2026-08-31 01:12:39 +02:00
Bartosz Taudul
37410933db Always deliver the sample IP.
Add PERF_SAMPLE_IP to the callstack sample: for code compiled without
frame pointers the kernel delivers no user stack, and such samples were
dropped entirely. When the callchain count is zero, synthesize a
one-frame trace holding just the leaf IP.
2026-08-31 01:12:39 +02:00
Bartosz Taudul
afe1ec5faf Factor the sample event open into OpenSampleEvent.
The seven sampling event setups repeated the same open/mmap/retry
sequence. On a refused open the retry now stays user-space only
(exclude_kernel and exclude_callchain_kernel both still require
perf_allow_kernel), and a failed open no longer breaks out of the whole
event loop.
2026-08-31 01:12:39 +02:00
Bartosz Taudul
d440f5e055 Report whether the data port is listening. 2026-08-31 01:12:39 +02:00
Bartosz Taudul
ae70255b4e Adopt a pre-bound listen socket. 2026-08-31 01:12:39 +02:00
Bartosz Taudul
b887def929 Record and expose system tracing startup failure. 2026-08-31 01:12:38 +02:00
Bartosz Taudul
7aee673792 Use the target executable time for external captures.
The worker must key source and symbol transfers on the target's
executable, not the monitor's own binary; the exe mtime was read
straight from /proc/<pid>/exe by InitExternalTarget (the readlink'd path
is namespace-dependent and carries a " (deleted)" suffix). For an
external target the time may be unavailable, so drop the assert on a
nonzero exectime.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
81f3c71ef3 Transfer machine code for external targets.
For an external target the symbol code query addresses are VMAs in the
target's address space, not pointers in the monitor's, so read them out
with ReadExternalTargetMemory. Kernel code (the top bit marks it) is
shared by the monitor and its target alike, so the kernel path applies
in both modes.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
d8a011630d Add an API to read external target memory.
ReadExternalTargetMemory reads bytes out of the target's address space:
process_vm_readv for live bytes (gated like ptrace access), falling back
to the target's on-disk image located by scanning /proc/<pid>/maps
directly - the caller runs while the symbol thread may be rebuilding the
shared image cache on refresh.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
1f1d3ab856 Resolve external callstacks to the full inline chain.
The resolver previously captured only the first pcinfo result, so inline
frames were lost, and reported ELF virtual addresses the server could
never re-map to the target. Mirror the in-process pcinfo+syminfo flow,
and report symAddr as the target VMA (loadBias + ELF vaddr) so the
server's re-queries can resolve it.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
2e566513dd Create external backtrace states from openable paths only.
backtrace_create_state_for_file opens the file lazily on first use and,
on a failed open, silently falls back to /proc/self/exe - so a state
created from a path the monitor cannot open would symbolize the target's
addresses against the monitor's own binary. Probe the target's root path
(and, for unlinked images, the mapping's map_files entry), and only
create a state for a path that actually opens.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
2a40f6f990 Converge the external image list on refresh.
The list only ever added entries, so a mapping the target re-mapped or
unloaded stayed in the list and could shadow the new mapping or keep
serving an already-unloaded library. Rebuild it from the target's
current maps on each refresh, carrying over the cached backtrace state
for unchanged mappings and dropping entries the current maps no longer
confirm. The dropped entries' paths are abandoned rather than freed:
queued callstack items still carry the path pointer, which the worker
thread reads at its own pace. Strip the kernel's " (deleted)" suffix so
unlinked images converge to their canonical path.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
0e7040d73b Derive the external load base from the mapping's own segment.
The minVaddr heuristic assumes the mapping's file offset is measured
against the lowest PT_LOAD vaddr of the image. The kernel maps each
PT_LOAD independently, at load_bias + ELF_PAGESTART(p_vaddr) from
p_offset - ELF_PAGEOFFSET(p_vaddr) (fs/binfmt_elf.c, elf_map), so the
assumption only holds for the segment carrying the minimum vaddr. Match
the mapping to its PT_LOAD through the exact offset relation the kernel
used, and take the base from that segment's own vaddr; the minVaddr
heuristic remains as the fallback. The header must be ELF64
(e_ident[4]): the fixed-size elf_ehdr/elf_phdr structs misparse other
classes, and a false segment match on misparsed headers would return a
silently wrong base.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
bd1d783017 Resolve external images through the target mount namespace.
The image paths from /proc/<pid>/maps are only valid in the target's
mount namespace, so resolve them through /proc/<pid>/root (proc(5)). For
unlinked images the kernel keeps the bytes alive in the mapping's
map_files entry, which is opened directly instead.
2026-08-31 01:12:38 +02:00
Bartosz Taudul
d352907e5d Drop the magic monitor globals. 2026-08-31 01:12:37 +02:00
Bartosz Taudul
4e520cfc18 Add the external target API. 2026-08-31 01:12:34 +02:00
Bartosz Taudul
ac9ea9dde7 Stop fetching vendor dependencies for the monitor build. 2026-08-30 21:25:08 +02:00
Bartosz Taudul
38b19d2be6 Constrain the monitor build to Linux x86_64 and aarch64. 2026-08-30 21:25:07 +02:00
Bartosz Taudul
601d57de35 Copy the filename in backtrace_create_state_for_file.
The state opens the file lazily on first use (fileline_initialize), so
it must own the filename: the caller's buffer may be freed as soon as
this returns. Pass a copy into backtrace_create_state.
2026-08-30 21:25:05 +02:00
Olivier Fouré
8e8f3073a7 Made Profiler::GetPort() thread-safe. 2026-08-28 15:39:19 +02:00
Bartosz Taudul
8b41c38ed9 Merge pull request #1464 from jorisv/topic/fix-git-ref-windows
Fix GitRef.hpp generation on Windows
2026-08-28 11:52:15 +02:00
Joris Vaillant
d6ef0f0fa4 Fix GitRef.hpp generation on Windows 2026-08-28 10:50:48 +02:00
Bartosz Taudul
031ef02614 Use MSVC for the meson build in windows CI. 2026-08-27 18:51:53 +02:00
Bartosz Taudul
0f65035ef3 Link ws2_32, dbghelp and secur32 for MinGW in meson. 2026-08-27 18:43:10 +02:00
Bartosz Taudul
253b68dcb6 Notify after queue can be done out of the lock. 2026-08-27 17:23:20 +02:00
Bartosz Taudul
32aa7ee1f5 Derive meson version via python3, not sh. 2026-08-27 17:23:20 +02:00
Bartosz Taudul
5ed42cfc6d Add meson build to windows CI. 2026-08-27 17:23:18 +02:00
Olivier Fouré
b2b8dc51ce Added Profiler::GetPort() and TracyPort macro to query the data port in use. 2026-08-26 14:28:11 +02:00
Bartosz Taudul
f207007548 Merge pull request #1460 from ofoure/master
Fixed buffer overflow after using VkCtx cmdbuf ctor.
2026-08-25 17:52:29 +02:00
Olivier Fouré
94e58d4e24 Fixed buffer overflow after using VkCtx cmdbuf ctor. 2026-08-25 14:40:54 +02:00
Bartosz Taudul
30997d5ca6 Release 0.14.1. 2026-08-22 19:06:43 +02:00
Bartosz Taudul
aa53e377e7 Use xdg-foreign v2 protocol for Wayland file dialog parent window. 2026-08-22 18:31:26 +02:00
Bartosz Taudul
cae9bf0bef Decrease rate of event production in test application. 2026-08-22 14:31:58 +02:00
Bartosz Taudul
4d3a84a97e Refine GUI verify skill name and description. 2026-08-22 13:07:05 +02:00
Bartosz Taudul
10926caed4 Add tooltip stating statistics limit also affects source view. 2026-08-22 12:57:34 +02:00
Bartosz Taudul
073152a10a Support time range limit entries tooltips. 2026-08-22 12:57:34 +02:00
Bartosz Taudul
2ec2c2c8ac Mention that statistics range limit also affects source view. 2026-08-22 12:41:29 +02:00
Bartosz Taudul
dce2c710e5 Merge pull request #1459 from alandtse/feat/mcp-sdk-v2
feat(mcp): migrate to MCP Python SDK v2
2026-08-22 11:04:22 +02:00
Alan Tse
e670bd18f7 feat(mcp): migrate to MCP Python SDK v2
`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.
2026-08-21 20:42:28 -07:00
Bartosz Taudul
1dfe828cdf Do not assert on zero content length in HttpRequest. 2026-08-22 02:26:54 +02:00
Bartosz Taudul
23ffe89d24 Abort pending update check on profiler exit. 2026-08-22 02:20:24 +02:00
Bartosz Taudul
f5938e0336 Remove unused Socket::ConnectBlocking. 2026-08-22 02:20:24 +02:00
Bartosz Taudul
86ea59bbe3 Make HttpRequest interruptible. 2026-08-22 02:20:24 +02:00
Bartosz Taudul
56f5d1239d Add IsConnecting and interruptible ReadUpTo to Socket. 2026-08-22 02:20:21 +02:00
Bartosz Taudul
cc04578f68 Derive meson and python package versions from TracyVersion.hpp. 2026-08-21 23:05:58 +02:00
Bartosz Taudul
39722e1e3f Skip timeline drawing when no width is available. 2026-08-21 22:10:07 +02:00
Bartosz Taudul
78a7553c6f Guarantee forward progress in the plot preprocess loop. 2026-08-21 22:10:05 +02:00
Bartosz Taudul
cdd5940b44 Define TRACY_NO_FILESELECTOR when the NO_FILESELECTOR option is set.
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.
2026-08-21 20:20:45 +02:00
Bartosz Taudul
246139fb04 Show the NFD error message in the file selector failure popup.
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.
2026-08-21 20:20:45 +02:00
Bartosz Taudul
5257132d97 Indent grouped child zone rows in the zone info window.
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.
2026-08-21 20:20:44 +02:00
Bartosz Taudul
db851737e0 Prevent ini settings from reversing sortable table column order.
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
2026-08-21 20:20:42 +02:00
Bartosz Taudul
73917aaef5 Sort single-member child groups by zone name in the zone info window.
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).
2026-08-21 02:03:50 +02:00
Bartosz Taudul
f0fce23bb6 Allow sorting child zones by name in the zone info window.
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.
2026-08-21 02:03:50 +02:00
Bartosz Taudul
ad7a1744fa Add web gui test skill. 2026-08-21 02:03:47 +02:00
Bartosz Taudul
62bffd085c Merge pull request #1458 from TravisGesslein/master
D3D12: pass an empty written range when unmapping the readback buffer
2026-08-20 20:46:16 +02:00
Travis Gesslein
f8a75703f9 D3D12: pass an empty written range when unmapping the readback buffer
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.
2026-08-20 17:36:39 +02:00
Bartosz Taudul
e3cde055cd Export Tracy version in the installed CMake package.
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.
2026-08-20 01:03:49 +02:00
Bartosz Taudul
0e37b94e6c Add TracyAssert.hpp to CMake / Meson. 2026-08-20 01:03:47 +02:00
Bartosz Taudul
6b58f7d8d0 Report system tracing setup failures.
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.
2026-08-19 22:18:59 +02:00
Bartosz Taudul
05f0508684 Fix system tracing on kernels older than 4.1.
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.
2026-08-19 22:18:57 +02:00
Bartosz Taudul
e2c05210e7 Add a note about zones crossing multiple on-demand connections. 2026-08-19 20:30:42 +02:00
Bartosz Taudul
275fef576a Bring Lua on-demand connection matching logic up-to-date with C++ side. 2026-08-19 20:30:40 +02:00
Bartosz Taudul
2a3792aaa3 Handle source code queries in no-callstack builds.
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.
2026-08-19 17:54:44 +02:00
Bartosz Taudul
ad99139f8e Include TracyWebGPU.hpp in CMakeLists.txt and meson.build. 2026-08-18 23:20:01 +02:00
Bartosz Taudul
fee9285d68 Use the compiler builtin for the ARM thread id.
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.
2026-08-18 22:35:44 +02:00
Bartosz Taudul
a4b51631ed Regenerate markdown manual. 2026-08-18 18:41:32 +02:00
Bartosz Taudul
433ac3629f Document that manual lifetime requires profiler shutdown.
https://github.com/wolfpld/tracy/issues/483
2026-08-18 18:39:45 +02:00
Bartosz Taudul
16d9c15663 Include TracyMangle.hpp in TracyDebug.hpp for consistent delayed init state. 2026-08-18 00:30:28 +02:00
Bartosz Taudul
4e9121eb2f Typos. 2026-08-18 00:28:32 +02:00
Bartosz Taudul
7f7bd69195 Remove TRACY_DELAYED_INIT option.
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.
2026-08-18 00:26:24 +02:00
Bartosz Taudul
2a7542ccd8 Mangle the GetToken accessor based on config.
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.
2026-08-17 22:21:57 +02:00
Bartosz Taudul
328f50211f Link TracyImGui library with libdl.
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.
2026-08-17 22:13:59 +02:00
Bartosz Taudul
f5c53af367 Fix plot color R/B channel swap. 2026-08-17 00:17:19 +02:00
Bartosz Taudul
b751c1c8d0 Fix csvexport --sep and --filter options. 2026-08-16 23:41:09 +02:00
Bartosz Taudul
b6cfdd2d2e Update embed.tracy to 0.14.
Previous was 0.10, which was too old after bumping minver to 0.11.
2026-08-16 15:26:10 +02:00
Bartosz Taudul
453d649c1a Add TRACY_ASSERT for custom assert implementations. 2026-08-16 13:32:44 +02:00
Bartosz Taudul
254f0ad210 Drop support for pre 0.11 traces. 2026-08-16 00:27:30 +02:00
Bartosz Taudul
3f9714059a Add protocol history for 0.14. 2026-08-16 00:04:13 +02:00
Bartosz Taudul
8fdfd08af0 Refactor GPU context id handling.
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.
2026-08-15 21:46:29 +02:00
Bartosz Taudul
54ecee1f74 Fix memory events not being attributed to fibers.
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).
2026-08-15 15:15:54 +02:00
Bartosz Taudul
b10d466a38 Regenerate markdown manual. 2026-08-15 13:41:23 +02:00
Bartosz Taudul
86fc7f02cb Document the need to reflect order of allocations in reported events. 2026-08-15 13:21:11 +02:00
Bartosz Taudul
16805b47ef Update TRACY_IGNORE_MEMORY_FAULTS description in CMake/Meson options. 2026-08-15 13:08:44 +02:00
Bartosz Taudul
756411d802 Allow disabling wait stacks capture. 2026-08-14 01:28:22 +02:00
Bartosz Taudul
d579ed3cce Fix VSync on 32-bit win32 targets.
The struct sizes are kernel sizes. A 64-bit kernel is required for
tracing anyway.
2026-08-13 18:34:58 +02:00
Bartosz Taudul
edc09a4e97 Fix 16-bit string length desync on offset recovery
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).
2026-08-13 18:18:58 +02:00
Bartosz Taudul
821d43c6b5 Send string for SectionSetup during on-demand deferred messages replay. 2026-08-13 18:15:20 +02:00
Bartosz Taudul
16329e0a0c Silence MSVC warning C4366.
warning C4366: The result of the unary '&' operator may be unaligned
2026-08-10 18:16:39 +02:00
Bartosz Taudul
099df3de3d Release 0.14.0. 2026-08-09 21:06:31 +02:00
Bartosz Taudul
a7926283a8 Wait for capture workers to disconnect before saving. 2026-08-09 18:32:17 +02:00
Bartosz Taudul
6ce8ad7aff CI: centralize cmake --parallel flag for linux workflow
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.
2026-08-09 12:46:43 +02:00
Bartosz Taudul
37a7c2c197 Add permissions and concurrency groups to all workflows
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.
2026-08-09 12:46:40 +02:00
Bartosz Taudul
a8f9faf496 Fix parallel flags for profiler GUI build in linux workflow.
GitHub runners: limit to 2 jobs
Non-gimped environments: use all you got.
2026-08-09 12:11:19 +02:00
Bartosz Taudul
f57986e17d Add parallel flag to library build in linux workflow. 2026-08-09 12:11:04 +02:00
Bartosz Taudul
5f647e7eaf Build dyna example multithreaded in linux workflow. 2026-08-09 12:02:20 +02:00
Bartosz Taudul
e06fc5211b No meson/ninja needed in windows workflow. 2026-08-09 11:56:48 +02:00
Bartosz Taudul
01a9e6d3af No version for meson. 2026-08-08 02:15:18 +02:00
Bartosz Taudul
c228c662bb Install TracyVersion.hpp in CMake and meson
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.
2026-08-08 02:09:08 +02:00
Bartosz Taudul
30d14caab6 Install TracyFormat.h in CMake and meson
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.
2026-08-08 02:09:08 +02:00
Bartosz Taudul
5d2c353335 Fix TRACY_NO_CODE_TRANSFER source code transfer. 2026-08-07 20:44:51 +02:00
Bartosz Taudul
92f0e0e951 Make linux-cli workflow compatible with act runner. 2026-08-07 00:28:10 +02:00
Bartosz Taudul
7dafaca602 Stage linux-cli artifact in a relative path for container job 2026-08-07 00:25:13 +02:00
Bartosz Taudul
18eaa221d3 Regenerate markdown manual. 2026-08-06 22:37:24 +02:00
Bartosz Taudul
6bca1141fe Update manual. 2026-08-06 22:37:02 +02:00
Bartosz Taudul
a5d155250b Update NEWS. 2026-08-06 22:37:02 +02:00
Bartosz Taudul
63c94bae7d Strip release binaries on Linux and macOS. 2026-08-06 22:36:59 +02:00
Bartosz Taudul
8163734842 Ship Linux CLI tools in a versioned release zip
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.
2026-08-06 22:22:09 +02:00
Bartosz Taudul
c119f1c8de Package tracy-capture-daemon in release binaries. 2026-08-06 21:22:02 +02:00
Bartosz Taudul
dcc86d343b Link to cropped guadec video. 2026-08-06 21:21:26 +02:00
Bartosz Taudul
507c77ba41 Move continuation words wider than the leftover whole to the next line.
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.
2026-08-05 01:37:05 +02:00
Bartosz Taudul
eacb64a714 Hack around lack of flag to always enable scrollbar in a table.
https://github.com/ocornut/imgui/issues/9499#issuecomment-5182563922
2026-08-04 19:43:55 +02:00
Bartosz Taudul
bdb0f3c3d7 Replace vertical / horizontal AddLine calls with AddLineV / AddLineH. 2026-08-04 19:00:59 +02:00
Bartosz Taudul
757c2fff30 Grammar. 2026-08-03 22:23:19 +02:00
Bartosz Taudul
a6d96d4201 Ignore all-newline message chunks at the beginning of LLM content.
DeepSeek 4 Flash likes to send "\n\n" as content between the reasoning
and tool call sections.
2026-08-03 22:23:03 +02:00
Bartosz Taudul
1c2a8b4c40 Filter out thinking, tool calls, attachments from the summary and suggestion. 2026-08-03 22:23:03 +02:00
Bartosz Taudul
6dd09e3ed8 Make the tool reply limit easier to understand. 2026-08-03 20:09:24 +02:00
Bartosz Taudul
96dcd7a38b Source tooltip may render more than one active line. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
0290f0ba54 Give instructions for providing source file ranges. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
1866cf25b9 Support extracting lines range. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
d91769b72e Check if there's a parent directory to go to on .. in NormalizePath. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
d0ffd8dc86 Merge pull request #1448 from 17steen/fix/cuda-defer-gpu-new-context
Defer CUDA GpuNewContext for on-demand connections
2026-08-03 12:31:21 +02:00
Robin Oger
5df9ac73c7 Defer CUDA GpuNewContext for on-demand connections.
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.
2026-08-03 10:48:20 +02:00
Bartosz Taudul
facbd3c0a7 Add connectionId protection to GPU zone scopes. 2026-08-02 20:44:48 +02:00
Bartosz Taudul
df29943d78 Add TRACY_ON_DEMAND guards to C API GPU functions. 2026-08-02 20:44:46 +02:00
Bartosz Taudul
9209bcdafd Add connectionId protection to C API zone functions. 2026-08-02 19:57:13 +02:00
Bartosz Taudul
8c8d451a2d Merge pull request #1446 from nyanpasu64/fork
Add formatted zone text/name macros to C API
2026-08-02 11:51:53 +02:00
nyanpasu64
bfdf647114 Rename TracyFormat.hpp to .h since it's used in the C API 2026-08-01 18:19:01 -07:00
nyanpasu64
0558301e49 Address LLM review comments 2026-08-01 18:15:27 -07:00
nyanpasu64
71098f707c TracyCZoneTextF/TracyCZoneNameF 2026-08-01 16:27:55 -07:00
Bartosz Taudul
a1fb20e620 Initialize uninitialized View members 2026-08-01 13:51:47 +02:00
Bartosz Taudul
9e163ad6b9 Remove dead vertical timeline centering toggle 2026-08-01 13:42:51 +02:00
Bartosz Taudul
93a8f86850 Use the mouse event abstraction in timeline click handlers 2026-08-01 13:27:43 +02:00
Bartosz Taudul
5698b45820 Hold worker data lock for the whole draw frame 2026-08-01 12:24:17 +02:00
Bartosz Taudul
95811a658a Save drawFrameTargets and plotHeight as configurable defaults 2026-08-01 11:52:00 +02:00
Bartosz Taudul
37468811c7 Declare ___tracy_get_time with explicit void parameter list. 2026-08-01 11:37:36 +02:00
Bartosz Taudul
a6352890f4 Check buffer bounds before accessing the buffer. 2026-08-01 02:17:59 +02:00
Bartosz Taudul
07b7219498 Use proper buffer size for program name normalization. 2026-08-01 02:15:29 +02:00
Bartosz Taudul
370a41225e The suggested llm query should be from the user's perspective. 2026-08-01 01:42:59 +02:00
Bartosz Taudul
1ad64f19a7 Use ctx.scale in timeline items instead of GetScale(). 2026-08-01 01:38:18 +02:00
Bartosz Taudul
93af7f987a Fix data race on pending thread hints. 2026-08-01 01:38:16 +02:00
Bartosz Taudul
144fec96d3 Do not scan source length over the destination size. 2026-08-01 00:59:29 +02:00
Bartosz Taudul
3448832b72 Replace impactful cases of strcpy with strzcpy. 2026-08-01 00:37:35 +02:00
Bartosz Taudul
c136232c97 Use strzcpy in client library. 2026-08-01 00:06:39 +02:00
Bartosz Taudul
7ee384630b Do not use strncpy in trace info window. 2026-08-01 00:06:39 +02:00
Bartosz Taudul
5ef415619d Add safe string copy. 2026-08-01 00:04:02 +02:00
Bartosz Taudul
8985887128 Don't include GUI profiler deps for all other tools. 2026-07-31 22:01:52 +02:00
Bartosz Taudul
bf21d8ea11 Bump ImGui to 1.92.9b-docking. 2026-07-31 18:12:14 +02:00
Bartosz Taudul
bc85c4faa5 Fix include guard. 2026-07-31 18:12:13 +02:00
Bartosz Taudul
8fd29b1038 Merge pull request #1441 from alandtse/fix/mcp-live-memory-limit
fix(mcp): cap live sessions to avoid OOM
2026-07-31 13:03:45 +02:00
Bartosz Taudul
d46fe6fd9f Merge pull request #1432 from alandtse/feat/mcp-crash-diagnostics
fix(mcp): recover from hangs and evict idle files
2026-07-31 13:03:12 +02:00
Alan Tse
613524247f docs(mcp): document file-idle eviction TTL
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.
2026-07-30 22:07:54 -07:00
Alan Tse
a418ec6a03 fix(mcp): evict idle file-loaded captures too
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.
2026-07-30 22:07:33 -07:00
Alan Tse
00c2245c49 fix(mcp): recover cleanly from a hung server
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.
2026-07-30 22:06:57 -07:00
Alan Tse
e4fab6af3a fix(mcp): cap live sessions to avoid OOM
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.
2026-07-30 21:48:06 -07:00
Bartosz Taudul
b418abd8de Merge pull request #1439 from ivanstepanovftw/fix-dwarf-npd
fix #1438 null dereference in dwarf
2026-07-30 19:25:41 +02:00
Ivan Stepanov
1673a305cf fix #1438 null dereference in dwarf 2026-07-30 14:33:49 +02:00
Bartosz Taudul
3baa2f0a09 Fix typo. 2026-07-29 18:57:03 +02:00
Bartosz Taudul
46b21c0c75 Update ARM CPU identifiers. 2026-07-29 18:54:10 +02:00
Bartosz Taudul
a02513c1a2 Update Tim Apple devices list. 2026-07-29 15:18:46 +02:00
Bartosz Taudul
ead931ef3c Safeguard against zero count entries in statistics list.
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.
2026-07-28 23:49:01 +02:00
Bartosz Taudul
6b2abcb47a Properly set countMap to 0 when loading a trace.
The live capture code path already does the equivalent thing.
2026-07-28 23:47:49 +02:00
Bartosz Taudul
e0862e8534 Disallow implicit StringIdx construction. 2026-07-28 22:33:26 +02:00
Bartosz Taudul
ec0a1c0606 Update manual. 2026-07-28 20:52:41 +02:00
Bartosz Taudul
372b83d590 Update NEWS. 2026-07-28 20:48:14 +02:00
Bartosz Taudul
4779856174 Shorten image names in flame graph blocks. 2026-07-28 20:36:06 +02:00
Bartosz Taudul
c4b1308c21 Use image name shorten helper. 2026-07-28 20:36:06 +02:00
Bartosz Taudul
488270c728 Add image name shortening helper. 2026-07-28 20:28:27 +02:00
Bartosz Taudul
ff81feedca Add tooltip for unknown flame graph blocks. 2026-07-28 20:25:39 +02:00
Bartosz Taudul
b677b352ee Show image names in flame graph unknown items. 2026-07-28 20:09:13 +02:00
Bartosz Taudul
8b836a5d56 Display unknown frames on flame graph. 2026-07-28 19:27:30 +02:00
Bartosz Taudul
ee54c04d3b Don't display empty tooltip in no-symbol-data case. 2026-07-28 19:26:57 +02:00
Bartosz Taudul
fa8bd1adf8 Fix grouping by name when merging flame graphs. 2026-07-28 19:25:35 +02:00
Bartosz Taudul
987d440d74 Merge pull request #1435 from damcclos/damcclos/client-ordering
Ordering the clients in the connection dialog to prevent jumping in the list. Ordering by time still caused jumps.
2026-07-28 17:46:29 +02:00
David McCloskey
064d400cf1 Ordering the clients in the connection dialog to prevent jumping in the list. Ordering by time still caused jumps. 2026-07-27 15:33:14 -05:00
Bartosz Taudul
d3af48990c Change "hide unknown" check from unknown file name to unknown function name.
Knowing that function "memcpy" took a certain amount of time is very
actionable, even if the source file name (from libc) is not available.
2026-07-27 22:07:34 +02:00
Bartosz Taudul
36d93e27c4 Add a table documenting client build options to the user manual. 2026-07-27 19:58:28 +02:00
Bartosz Taudul
3a65b27838 Update NEWS. 2026-07-27 19:33:55 +02:00
Bartosz Taudul
71c98c9dfb Ignoring mem faults also ignores repeated allocation for the same address. 2026-07-27 19:33:55 +02:00
Bartosz Taudul
12fdc7d8d2 Extract recording free event in MemData. 2026-07-27 19:33:55 +02:00
Bartosz Taudul
9481319d4c Regenerate markdown manual. 2026-07-26 19:00:15 +02:00
Bartosz Taudul
3c88115926 Update NEWS. 2026-07-26 19:00:15 +02:00
Bartosz Taudul
ed5f333f90 Update manual. 2026-07-26 18:59:26 +02:00
Bartosz Taudul
a1ef58efac Remove an unreachable leaf flag ternary.
The tree node is only drawn when the count is greater than zero, so the
leaf flag could never be selected. Use the plain TreeNode call.
2026-07-26 18:59:25 +02:00
Bartosz Taudul
24b2702977 Track the maximum of combined sample costs for hotness colors.
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.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
9b3c6784fb Allow opening entry stacks for symbols with only inclusive counts.
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.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
26d61dfdba Make find zone sample percentages relative to the matched zones.
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.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
c2c1c4e2b7 Ignore the statistics range filter in the disassembly LLM tool.
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.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
a429242f0f Exclude context switch samples from zone call stack reconstruction.
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.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
2d5e397c9f Exclude context switch samples from the sampling flame graph.
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.
2026-07-26 18:55:08 +02:00
Bartosz Taudul
b743c13695 Exclude context switch samples from the LLM sampling stats total.
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.
2026-07-26 18:55:06 +02:00
Bartosz Taudul
52e31be7f4 Do not touch the thread compression cache in a load-time job.
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.
2026-07-25 23:02:14 +02:00
Bartosz Taudul
f51b984edb Guard against missing symbol data for the top inline name.
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.
2026-07-25 23:02:14 +02:00
Bartosz Taudul
8da27d6042 Gate sampling data accessors on background processing completion.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
74b08cf5c6 Clarify the sampling percentage denominator comment.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
b3de7f34fb Require symbol data for the entry stacks button in symbol view.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
dd8eff0de6 Guard relative inline percentages against an empty base count.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
46c70fcbb8 Do not mark sorted vectors as unsorted on equal elements.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
97d6b5f9fa Guard sampling percentage denominators against underflow.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
b68fe43335 Keep context switch sample vectors sorted.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
5563461201 Extend the symbol parents LLM tool with the reached modes.
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.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
851b1f80f1 Take the data lock in LLM tools which access worker state.
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.
2026-07-25 23:02:10 +02:00
Bartosz Taudul
507f2ae8b5 Update NEWS. 2026-07-25 19:46:02 +02:00
Bartosz Taudul
941669c1c6 Base sampling statistics percentages on real sample counts.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
c880163f96 Sort child sample vectors lazily.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
3293e263c6 Exclude context switch samples from symbol and child sample maps.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
6063e22bc7 Fix inclusive counts of symbols with inlined functions.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
8711f87cb5 Disable entry stacks menu item when there is no data to show.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
34587e6b4c Only show entry stacks button in symbol view when data is available.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
d5760a05ae Do not crash on missing symbol data in sample entry stacks window.
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.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
bacfbccb97 Use reached stats for entry stacks access in assembly view.
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.
2026-07-25 19:45:58 +02:00
Bartosz Taudul
2e9e0e2643 Update NEWS. 2026-07-25 17:33:00 +02:00
Bartosz Taudul
fc26cc0f79 Update manual. 2026-07-25 17:30:32 +02:00
Bartosz Taudul
849bc3ae8c Allow entry stacks for symbols that were only reached. 2026-07-25 17:28:07 +02:00
Bartosz Taudul
67ae7d849f Add "was reached" modes to sample entry stacks window. 2026-07-25 17:25:17 +02:00
Bartosz Taudul
1dfcacd802 Collect entry stacks for "was this symbol reached" statistics.
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.
2026-07-25 17:02:42 +02:00
Bartosz Taudul
2e23c112bd Extract synthetic call stack interning into a helper. 2026-07-25 16:57:57 +02:00
Bartosz Taudul
d74d77226c Guard against empty synthetic call stacks in parents frame trees.
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.
2026-07-25 16:43:43 +02:00
Bartosz Taudul
8f52e0e5f5 Enable clipboard on macOS. 2026-07-25 14:34:24 +02:00
Bartosz Taudul
9db60de03b Do not embed default ImGui font. 2026-07-25 14:30:13 +02:00
Bartosz Taudul
e53c2849fa Bump ImGui to 1.92.9-docking. 2026-07-25 14:29:12 +02:00
Bartosz Taudul
7c30fedaaa Rebuild markdown manual. 2026-07-25 00:19:47 +02:00
Bartosz Taudul
9314b8cbce Update manual. 2026-07-25 00:17:42 +02:00
Bartosz Taudul
ff3e31f564 Update desktop file. 2026-07-24 23:14:09 +02:00
Bartosz Taudul
ac159d6127 Fix RUNPATH in tracy-profiler AppImage
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.
2026-07-24 20:34:10 +02:00
Bartosz Taudul
bcddc1d9f1 Cache wayland checkout. 2026-07-24 20:34:08 +02:00
Bartosz Taudul
d231c2e95f Correct way to link with wayland libraries. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
b94a1e22c1 Update git ignore list. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
cf82fb9bb0 Disable unused stuff if building libcurl. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
f30d32a908 Add AppImage build script and workflow. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
b7eda34332 Add a warning about minimum supported wayland-client version. 2026-07-24 18:11:42 +02:00
Bartosz Taudul
5036ec9128 Merge pull request #1434 from damcclos/damcclos/thread-lock-time
Add function to calculate thread lock wait time in TracyTimelineItemThread
2026-07-23 22:14:46 +02:00
David McCloskey
59a8ae2d7d Adding documentation 2026-07-23 14:38:56 -05:00
David McCloskey
ea3d0b4d8d Add function to calculate thread lock wait time in TracyTimelineItemThread
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.
2026-07-23 14:21:54 -05:00
Bartosz Taudul
917c4baffb GLFW from brew should no longer be needed on macOS. 2026-07-23 21:11:07 +02:00
Bartosz Taudul
faab64018a Update NEWS. 2026-07-23 21:03:07 +02:00
Bartosz Taudul
fb296ba1a4 Update manual. 2026-07-23 21:03:06 +02:00
Bartosz Taudul
877efe4eda Track current personality and warn when the selected one is not active. 2026-07-23 20:34:59 +02:00
Bartosz Taudul
90c6ad80e1 Clamp personality index to personality table size. 2026-07-23 20:29:26 +02:00
Bartosz Taudul
67c4102bf3 Restore disabling fast model selection during replies. 2026-07-23 20:26:41 +02:00
Bartosz Taudul
c1fa33a875 Link with system libcurl to avoid libssh deps. 2026-07-23 15:43:59 +02:00
Bartosz Taudul
1d6f43d92f Disable external libraries used by freetype.
Not needed for the current set of fonts.
2026-07-23 15:30:39 +02:00
Bartosz Taudul
c2b489a0ff Do not link macOS bundle with homebrew libraries.
Do not want dependency on something being installed via brew.
2026-07-23 14:57:45 +02:00
Bartosz Taudul
7986dc18d0 Add yalter's guadec presentation to list of videos. 2026-07-23 01:53:54 +02:00
Bartosz Taudul
f9413c268d Bump usearch to 2.26.0. 2026-07-22 22:35:42 +02:00
Bartosz Taudul
6e23d220d7 Bump capstone to 6.0.0-Alpha10. 2026-07-22 22:33:29 +02:00
Bartosz Taudul
158ab87bee Rebuild markdown manual. 2026-07-22 22:04:04 +02:00
Bartosz Taudul
d2cfc7b5d1 Document LLM personality selection. 2026-07-22 21:40:01 +02:00
Bartosz Taudul
b0d75acdef Add LLM personality presets. 2026-07-22 02:10:24 +02:00
Bartosz Taudul
c482f1e25d Bury fast model deeper in the UI. 2026-07-22 01:18:47 +02:00
Bartosz Taudul
8221a80b47 Add icon to LLM API entry field. 2026-07-22 01:13:20 +02:00
Bartosz Taudul
afe249b6d3 Do not disconnect active connections.
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.
2026-07-21 23:53:01 +02:00
Bartosz Taudul
72b1edc39d Set executable bit in macOS bundle. 2026-07-21 20:24:38 +02:00
Bartosz Taudul
2247f1d14b Add macOS artifacts to release workflow. 2026-07-21 19:57:45 +02:00
Bartosz Taudul
b0be3a5776 Bump actions/setup-python from 2 to 7. 2026-07-21 19:52:22 +02:00
Bartosz Taudul
dc261fe799 Add permissions contents write to release workflow. 2026-07-21 19:46:37 +02:00
Bartosz Taudul
21f0d15f5f Do not install library in CI workflows. 2026-07-21 19:35:43 +02:00
Bartosz Taudul
51f2b1da87 Do not zip bundle. 2026-07-21 19:21:33 +02:00
Bartosz Taudul
b32d454bcd Try making a macos bundle. 2026-07-21 16:42:33 +02:00
Bartosz Taudul
fb4dcd4e96 Switch macos CI runner to macos-latest. 2026-07-21 15:04:37 +02:00
Bartosz Taudul
d67cf385ff Merge pull request #1431 from alandtse/fix/mcp-live-worker-lock-race
fix(mcp): lock Worker reads vs receive thread
2026-07-20 12:04:36 +02:00
Bartosz Taudul
4ffc8b0dd2 Merge pull request #1430 from alandtse/fix/d3d11-zero-frequency-divide
fix(d3d11): guard collector against zero frequency
2026-07-20 12:04:07 +02:00
Alan Tse
5f2d14338e docs(mcp): fix eval-guide staleness
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).
2026-07-19 22:32:06 -07:00
Alan Tse
bfcec08e93 feat(mcp): expose background-load readiness
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.
2026-07-19 22:32:04 -07:00
Alan Tse
15bc3fce1a fix(mcp): lock Worker reads vs receive thread
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.
2026-07-19 22:32:04 -07:00
Alan Tse
dc9dfb2e65 fix(d3d11): guard collector against zero frequency
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.
2026-07-19 22:28:52 -07:00
Bartosz Taudul
ef099b0555 The UD- prefix is problematic in router mode. 2026-07-19 01:17:52 +02:00
Bartosz Taudul
956e082e5b ResetGraph lambda can't be static. 2026-07-18 15:33:43 +02:00
Bartosz Taudul
be6377f81b Constrain source view window size. 2026-07-18 14:51:41 +02:00
Bartosz Taudul
249b76f175 Do not display time, reason data for aggregate wait stacks. 2026-07-18 14:39:52 +02:00
Bartosz Taudul
6a916a6c9f Constrain zone info window size. 2026-07-18 14:31:15 +02:00
Bartosz Taudul
0f56c8ac7e Constrain callstack window size. 2026-07-18 14:19:33 +02:00
Bartosz Taudul
8a4876a071 Fix wrong formatting. 2026-07-18 14:15:14 +02:00
Bartosz Taudul
7539daa602 Constrain sample entry stacks window size. 2026-07-18 14:11:33 +02:00
Bartosz Taudul
ab46247a9c Constrain frame statistics window size. 2026-07-18 14:07:48 +02:00
Bartosz Taudul
7bc32a1536 Use sub-child in frame statistics window. 2026-07-18 14:07:22 +02:00
Bartosz Taudul
6858a664dc Constrain wait stacks window size. 2026-07-18 14:01:29 +02:00
Bartosz Taudul
3eae7a6d65 Constrain LLM window size. 2026-07-18 13:52:58 +02:00
Bartosz Taudul
64c0c5c85f Constrain compare window size. 2026-07-18 13:46:24 +02:00
Bartosz Taudul
74e1ddfbeb Constraint window memory size. 2026-07-18 13:42:14 +02:00
Bartosz Taudul
f472b92f13 Constraint statistics window size. 2026-07-18 13:40:20 +02:00
Bartosz Taudul
6c8dd9fd93 Constrain find zone window size. 2026-07-18 13:37:10 +02:00
Bartosz Taudul
8184964c2d Include sub-window padding and scrollbars. 2026-07-18 13:36:45 +02:00
Bartosz Taudul
eef8fac58b Constrain messages window size. 2026-07-18 13:21:35 +02:00
Bartosz Taudul
2d2487e2e0 Constrain flame graph window size. 2026-07-18 13:20:15 +02:00
Bartosz Taudul
562fea5564 Add DPI change notification method in View. 2026-07-18 13:17:42 +02:00
Bartosz Taudul
3608d0c271 Add imgui window size constraints helper. 2026-07-18 13:17:42 +02:00
Bartosz Taudul
f08792f217 Remove filtering for things that are no longer present. 2026-07-18 12:50:47 +02:00
Bartosz Taudul
87b838f553 Update manual. 2026-07-18 12:32:50 +02:00
Bartosz Taudul
ed39de8025 Update NEWS. 2026-07-18 12:23:14 +02:00
Bartosz Taudul
84c6548c94 Add option to group symbol flame graph items by name, not symaddr. 2026-07-18 12:20:38 +02:00
Bartosz Taudul
0b2e7dd13c Add StringIdx equality comparison operator. 2026-07-18 12:20:32 +02:00
Bartosz Taudul
eb12b62af6 Extract flame graph reset functionality. 2026-07-18 12:07:43 +02:00
Bartosz Taudul
91bababef7 Common path for both trees in sample entry stacks. 2026-07-17 22:24:58 +02:00
Bartosz Taudul
f13bb0bf52 Fix UI logic. 2026-07-15 02:09:34 +02:00
Bartosz Taudul
0caee2147c Update manual. 2026-07-15 01:20:14 +02:00
Bartosz Taudul
3e91ee15b1 Display FPS time range values in frame compare. 2026-07-14 23:56:28 +02:00
Bartosz Taudul
f7c63ac40c Display single FPS value in frame stats tooltip, if min and max are equal. 2026-07-14 23:55:44 +02:00
Bartosz Taudul
3d85d0468e Remove unneeded capture. 2026-07-14 21:28:33 +02:00
Bartosz Taudul
ff3c57c8d3 Category may exist while sections are not filled.
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).
2026-07-14 21:21:45 +02:00
Bartosz Taudul
a501117392 Use proper data size. 2026-07-14 21:20:00 +02:00
Bartosz Taudul
916e62de17 Display count of sections in each category. 2026-07-14 19:17:22 +02:00
Bartosz Taudul
88c491e0f9 Add select/unselect all buttons to section categories list. 2026-07-14 19:14:55 +02:00
Bartosz Taudul
1a6b7781bd Update manual. 2026-07-14 19:13:01 +02:00
Bartosz Taudul
71810c4265 Update NEWS. 2026-07-14 19:13:01 +02:00
Bartosz Taudul
15fecdbf79 Display section category in section tooltips. 2026-07-14 19:13:01 +02:00
Bartosz Taudul
c1e8c7bac9 Implement disabling specific section categories. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
03af70fd78 Add section categories descriptions accessor. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
7933c00447 Add notification for when sections are hidden. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
eb53d28358 Repurpose "limit to view" icon for sections. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
2e0367cd1f Implement disabling drawing of sections. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
c2fa2d4d7b New section selection menu in compare window. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
f86105c600 New section range copy in playback. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
2b9e7a53ff Further extraction of common functionality. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
172f32d075 Use section categories in dyna example. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
7dab948434 List sections and categories in range limits window. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
e49b96ec03 Section items lister. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
42fc8d8d8f Add getter for section category name. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
ece976e820 Move Range, RangeSlim from TracyViewData.hpp to TracyUtility.hpp. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
a8d5f12f16 Properly process section categories.
Section selection UI disabled for now.
2026-07-14 19:12:59 +02:00
Bartosz Taudul
aacc82df3d Hook up server-side no-op section setup. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
3e1de56e15 Add section category to section enter message. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
859709a98a Add client-side section category setup. 2026-07-14 19:12:58 +02:00
Bartosz Taudul
50c3c89c16 Rename parents / baseParents maps to wasExecuting / wasExecutingBase.
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.
2026-07-12 16:38:42 +02:00
Bartosz Taudul
60ce6fb25e Rename parent call stacks to synthetic call stacks. 2026-07-12 16:30:16 +02:00
Bartosz Taudul
9ec0535e03 Extract parent frame interning function. 2026-07-12 16:19:08 +02:00
Bartosz Taudul
81ccf5149a Increase code legibility. 2026-07-12 16:02:28 +02:00
Bartosz Taudul
7b36ab9793 Regenerate markdown manual. 2026-07-11 16:22:09 +02:00
Bartosz Taudul
2b49e91804 Update manual. 2026-07-11 16:21:55 +02:00
Bartosz Taudul
6bd6483181 Update NEWS. 2026-07-11 16:00:42 +02:00
Bartosz Taudul
22a5a5fb78 Add copy message context menu to zone info message list. 2026-07-11 16:00:42 +02:00
Bartosz Taudul
580a43c05f Enable copying messages to clipboard. 2026-07-11 15:38:12 +02:00
Bartosz Taudul
3b2d97ab21 Set filename when saving a trace to disk. 2026-07-10 20:34:22 +02:00
Bartosz Taudul
b76468885b Show callstack lines in memory plot tooltips. 2026-07-10 20:34:22 +02:00
Bartosz Taudul
c35e0f07c5 Draw longer callstack lines in memory alloc window. 2026-07-10 20:34:19 +02:00
Bartosz Taudul
7979404a8f Merge pull request #1428 from damcclos/damcclos/flame-thread-sort
Enhance Flame Graph View with Sorting Functionality
2026-07-10 20:34:09 +02:00
David McCloskey
7d1a37de2d Pulling implementation in to a separate function. 2026-07-10 12:00:48 -05:00
Bartosz Taudul
0350df11f8 Hook up section enter / leave to Lua bindings. 2026-07-10 16:24:46 +02:00
Bartosz Taudul
59013f2a8c Add GetNextSectionId() for getting section id. 2026-07-10 16:05:37 +02:00
Bartosz Taudul
a208c3c665 Merge pull request #1427 from nicozink/fix/VulkanQueryCount
Reduce Vulkan QueryCount for Apple devices
2026-07-10 15:27:50 +02:00
David McCloskey
655b8295cf Enhance Flame Graph View with Sorting Functionality
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.
2026-07-09 12:16:17 -05:00
Nico Zink
e20c716b80 Reduce Vulkan QueryCount for Apple devices 2026-07-09 16:32:22 +01:00
Bartosz Taudul
ac4defb4f3 Merge pull request #1426 from YaLTeR/fix-manual
manual: Fix decorators in the Python listing and typo
2026-07-09 13:51:39 +02:00
Ivan Molodetskikh
c6f5cf50fb manual: Fix decorators in the Python listing and typo 2026-07-09 14:06:41 +03:00
Bartosz Taudul
96a91da63a Merge pull request #1425 from YaLTeR/desktop-tracy-profiler
.desktop: Change binary name to tracy-profiler
2026-07-09 11:55:06 +02:00
Ivan Molodetskikh
e0929e9cd8 .desktop: Change binary name to tracy-profiler
That's the current binary name.
2026-07-09 12:50:15 +03:00
Bartosz Taudul
05f620ab97 Merge pull request #1424 from YaLTeR/static-nfd
Link NFD statically
2026-07-09 11:16:56 +02:00
Ivan Molodetskikh
e40ce11ba9 Link NFD statically
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.
2026-07-09 10:46:00 +03:00
Bartosz Taudul
66e5b81804 Draw rectangle encompassing CPU topology package. 2026-07-09 01:04:51 +02:00
Bartosz Taudul
d565afde82 Remove force inline for SectionEnter.
gcc breaks on this:
error: function ‘static uint32_t tracy::Profiler::SectionEnter(const char*, ...)’ can never be inlined because it uses variable argument lists
2026-07-08 18:04:01 +02:00
Bartosz Taudul
bd8246cba0 Build library / test application before building profiler tools. 2026-07-08 17:55:43 +02:00
Bartosz Taudul
2205d7972f Build dyna example in linux CI workflow. 2026-07-08 17:54:12 +02:00
Bartosz Taudul
e842bf223f Use correct type for phdr addresses. 2026-07-07 17:54:03 +02:00
Bartosz Taudul
6495aab062 Add android library CI workflow. 2026-07-07 17:53:14 +02:00
Bartosz Taudul
8555514d59 Merge pull request #1423 from YaLTeR/update-install
update: Add install() to CMakeLists.txt
2026-07-07 14:06:21 +02:00
Ivan Molodetskikh
3e0ded59a5 update: Add install() to CMakeLists.txt 2026-07-07 14:03:03 +03:00
Bartosz Taudul
ba8e4bdcbf Merge pull request #1422 from alandtse/fix/mcp-instance-eviction
fix(mcp): cap tracy_mcp instance memory growth
2026-07-07 11:21:56 +02:00
Alan Tse
9bbbec42f4 fix(mcp): cap tracy_mcp instance memory growth
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.
2026-07-07 00:23:47 -07:00
Bartosz Taudul
69fead1c16 Add CMake presets for the rest of utils. 2026-07-07 01:43:43 +02:00
Bartosz Taudul
df463fcbfe Add CMake presets for profiler. 2026-07-07 01:43:43 +02:00
Bartosz Taudul
467ba60cdf Add proper compile options to dyna example. 2026-07-07 01:43:43 +02:00
Bartosz Taudul
cbbfd13fa2 Merge pull request #1421 from YaLTeR/sys-reg-h
Change musl bits/reg.h to sys/reg.h
2026-07-06 18:55:20 +02:00
Ivan Molodetskikh
16a148f0be Replace __WORDSIZE with __SIZEOF_POINTER__
__WORDSIZE has been relocated from bits/reg.h (now an empty header) to
sys/reg.h in 2024:
https://git.musl-libc.org/cgit/musl/commit/?id=e709a6f07ade208ba513f9225222336f30c304b0

And in any case, it's a private definition. Let's use a common
documented define instead:
https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html

Fixes symbol resolution on musl (particularly, postmarketOS aarch64).
2026-07-06 18:57:31 +03:00
Bartosz Taudul
9131f3600b Fix copy from dialog for sections that were not ended. 2026-07-05 19:50:03 +02:00
Bartosz Taudul
b2d5d9bc52 Allow limiting playback to section scope. 2026-07-05 18:53:52 +02:00
Bartosz Taudul
ec884b2d60 Allow setting time range limit from section. 2026-07-05 18:51:58 +02:00
Bartosz Taudul
81cb4acbe6 Use ImGui mouse button identifiers instead of raw numeric ids. 2026-07-05 18:47:38 +02:00
Bartosz Taudul
f2256cf260 Add ellipsis to buttons that require further action. 2026-07-05 18:47:07 +02:00
Bartosz Taudul
ae83b00951 Add option to reset compare time range limits. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
d2c69868d1 Better compare range limit time display. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
18cfcd7e90 Add copy from section to compare range limits. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
71d88fd198 Annotations may have the same name. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
17bbc4049a Cosmetics for compare range limit. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
2b0b7293a1 Filter frames by range in compare view. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
07cb1030e7 Filter zones by range in compare view. 2026-07-04 22:29:13 +02:00
Bartosz Taudul
af8dc30912 Add sumSq cache to compare data for range filtering. 2026-07-04 17:41:18 +02:00
Bartosz Taudul
8f2bbc6116 Display message when no data to compare. 2026-07-03 15:24:47 +02:00
Bartosz Taudul
f01607714e Reset data when changing range limits. 2026-07-03 15:14:21 +02:00
Bartosz Taudul
533e1af389 Set time range limits in compare window from annotations. 2026-07-02 17:40:35 +02:00
Bartosz Taudul
25139748b2 Add annotations accessor to UserData. 2026-07-02 17:40:09 +02:00
Bartosz Taudul
853fff614e Support nfd parent window with wayland backend. 2026-07-02 02:14:04 +02:00
Bartosz Taudul
5be36d732b Bump nfd to current master. 2026-07-02 01:29:50 +02:00
Bartosz Taudul
836bf9d2eb Pass parent window handle to file dialogs. 2026-07-02 01:26:58 +02:00
Bartosz Taudul
db383f738b Store window handle for file selector. 2026-07-02 01:26:58 +02:00
Bartosz Taudul
a8c642f42b Cosmetics. 2026-07-02 01:01:09 +02:00
Bartosz Taudul
641bd09b6f Use extended-params version of NFD functions. 2026-07-02 00:46:30 +02:00
Bartosz Taudul
280af2e7bd Bump curl. 2026-07-02 00:30:53 +02:00
Bartosz Taudul
83298a3459 Bump usearch. 2026-07-02 00:30:48 +02:00
Bartosz Taudul
21f9c00e9f Bump md4c to current master. 2026-07-02 00:24:57 +02:00
Bartosz Taudul
b0a094bb42 Update manual. 2026-07-01 18:26:29 +02:00
Bartosz Taudul
70a09f5663 Update NEWS. 2026-07-01 17:58:05 +02:00
Bartosz Taudul
467be67807 Adjust colors. 2026-07-01 17:33:29 +02:00
Bartosz Taudul
80476da597 Color-code difference between values in comparison window. 2026-07-01 17:33:29 +02:00
Bartosz Taudul
d94d8f11ac Show P75, P90, P99, P99.9 in comparison window. 2026-07-01 17:33:29 +02:00
Bartosz Taudul
da495be8de Remove separation in "speedup or slowdown" label. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
fef901f595 Show comparison of standard deviation in compare window. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
74650af7a0 Show coefficient of variation in frames compare. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
e740bc652c Display FPS values for mean and median in frames comparison. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
7da72e51d2 Compare FPS ranges for frames. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
ac5682bc75 Use boolean to differentiate between zones / frames comparison mode. 2026-07-01 17:33:27 +02:00
Bartosz Taudul
570a06dced Be more accurate about normalization behavior. 2026-07-01 17:33:24 +02:00
Bartosz Taudul
00c079cec9 Put summary updates under a lock. 2026-07-01 02:10:52 +02:00
Bartosz Taudul
80a1772d0b Separate external frames visibility for wait stacks (enabled by default). 2026-07-01 02:03:28 +02:00
Bartosz Taudul
f65949cc26 Move callstack table params to a struct. 2026-07-01 01:53:08 +02:00
Bartosz Taudul
b40ef0ff29 Draw timeline thread label after message markers to make it readable. 2026-07-01 01:26:26 +02:00
Bartosz Taudul
aca0e39190 Do not summarize very short llm queries. 2026-06-30 21:06:43 +02:00
Bartosz Taudul
ba9388cd9f Merge pull request #1404 from wolfpld/slomp/gl_query_result_no_wait
Possibly faster GL timestamp query result check
2026-06-30 21:03:40 +02:00
Bartosz Taudul
1f2190a44a Merge pull request #1420 from YaLTeR/flame-graph-pan
Fix "quantization" of sampling flame graph horizontal panning
2026-06-30 19:52:49 +02:00
Ivan Molodetskikh
b11fa7caa2 Store flame graph time as time instead of samples
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.
2026-06-30 19:54:54 +03:00
Bartosz Taudul
ed1a4b06b6 No cumulate time for frames. 2026-06-30 16:57:19 +02:00
Bartosz Taudul
d34659f57b Remove tree node for histogram in compare window. 2026-06-30 16:44:54 +02:00
Bartosz Taudul
a67a29e9cd Use tabs in compare window. 2026-06-30 16:39:06 +02:00
Bartosz Taudul
dc4b51e522 Always draw separator in compare window. 2026-06-30 14:22:46 +02:00
Bartosz Taudul
0a0cd27db4 Update manual. 2026-06-29 17:20:10 +02:00
Bartosz Taudul
472c84ac78 Implement coverage requirement in GetPlaybackFrameRangeFromTime. 2026-06-29 17:16:27 +02:00
Bartosz Taudul
bde9e50c84 Use binary search in GetPlaybackFrameRangeFromTime(). 2026-06-29 17:02:51 +02:00
Bartosz Taudul
dba0b6263e Add button for resetting frame image playback speed. 2026-06-29 16:32:59 +02:00
Bartosz Taudul
27f4e00aea Add require coverage checkbox. 2026-06-29 16:30:21 +02:00
Bartosz Taudul
6d6a4beda3 Add button for resetting frame image playback range. 2026-06-29 16:18:58 +02:00
Bartosz Taudul
bbb43549a6 Move playback "copy from" to a button with a popup menu. 2026-06-29 16:18:58 +02:00
Bartosz Taudul
83a703640c Add limits button to frame statistics window. 2026-06-29 16:18:58 +02:00
Bartosz Taudul
7b4a128080 Use full size button for flame graph reset view. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
547891f044 Update manual. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
e4acd2db77 Add icon for the "limit to view" button in time ranges window. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
e7bd441ebb Hide time range copy sources in a popup menu. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
4cd6c38932 Include exclusive sample count in inclusive sample count. 2026-06-27 16:44:26 +02:00
Bartosz Taudul
331881ca36 Fix counting of "with children" sample counts in recursive cases. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
9aa48a62cb Fix remains of old font awesome markup. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
50982fe7a3 Update manual. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
549226751e Update NEWS. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
6921fbfdf8 Color sample markers according to sample type. 2026-06-27 15:00:41 +02:00
Bartosz Taudul
90d4463ef0 Determine sample type. 2026-06-27 11:55:00 +02:00
Bartosz Taudul
323659d4f7 Merge pull request #1416 from YaLTeR/fix-elf-zstd-rle-mode
libbacktrace: Fix missing table assignment in RLE seq decode
2026-06-27 10:57:45 +02:00
Ivan Molodetskikh
279f7ba0df libbacktrace: Fix missing table assignment in RLE seq decode
Fixes segfault when decompressing mesa debuginfo on Aeryn.
2026-06-27 11:05:12 +03:00
Bartosz Taudul
0b120ba84f Merge pull request #1415 from wolfpld/slomp/webgpu-logging
WebGPU: more informative errors
2026-06-26 23:09:58 +02:00
Bartosz Taudul
04a8ba4c2b Update manual. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
576d03e414 Update NEWS. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
88702dad39 Allow setting frame image playback limit to an annotation of range limit. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
30572e4810 Don't crash on annotation with no name. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
57c143c9b7 Translate time range to frame image range. 2026-06-26 20:21:03 +02:00
Marcos Slomp
2221138299 more informative errors 2026-06-26 10:40:48 -07:00
Bartosz Taudul
4bc37ccb4d Allow limiting frame image playback to given frame range. 2026-06-26 19:39:24 +02:00
Bartosz Taudul
e9ad49a846 Introduce abstraction of frame image begin and end. 2026-06-26 19:07:47 +02:00
Bartosz Taudul
64a43ef3d3 Add looping to frame image playback. 2026-06-26 18:37:54 +02:00
Bartosz Taudul
8aae924d51 Make play button disabled if playback is at the end. 2026-06-26 17:46:58 +02:00
Bartosz Taudul
9370d7a379 Unify entry stacks icon. 2026-06-26 14:25:06 +02:00
Bartosz Taudul
e10c865fbf Merge pull request #1414 from YaLTeR/fix-elf-symbols
Fix image and symbol resolution on AerynOS and elsewhere
2026-06-26 14:19:08 +02:00
Ivan Molodetskikh
bd2106db68 libbacktrace: support compressed block with window descriptor
Some debuginfo on AerynOS has this format.
2026-06-26 10:46:33 +03:00
Ian Lance Taylor
99c13e53bf libbacktrace: support compressed block with no sequences
* elf.c (elf_zstd_decompress_frame): Support a compressed block
	with no sequences, only literals.

Fixes https://github.com/ianlancetaylor/libbacktrace/issues/164
2026-06-26 10:46:17 +03:00
Ian Lance Taylor
7fbe1d1345 libbacktrace: support multiple zstd frames
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.
2026-06-26 10:41:29 +03:00
Ivan Molodetskikh
5745b489a2 callstack: Calculate end address from highest LOAD header
Headers aren't guaranteed to be sorted in final address order. For
example, from readelf -lW of a .so built with clang:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R   0x8
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x5e8274 0x5e8274 R   0x1000
  LOAD           0x5e8280 0x00000000005e9280 0x00000000005e9280 0x51e990 0x51e990 R E 0x1000
  LOAD           0xb06c10 0x0000000000b08c10 0x0000000000b08c10 0x031628 0x0323f0 RW  0x1000
  LOAD           0xb38240 0x0000000000b3b240 0x0000000000b3b240 0x009aa8 0x011c40 RW  0x1000
  DYNAMIC        0xb32270 0x0000000000b34270 0x0000000000b34270 0x000410 0x000410 RW  0x8
  GNU_RELRO      0xb06c10 0x0000000000b08c10 0x0000000000b08c10 0x031628 0x0323f0 R   0x1
  GNU_EH_FRAME   0x4e8360 0x00000000004e8360 0x00000000004e8360 0x029cf4 0x029cf4 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0
  NOTE           0x000270 0x0000000000000270 0x0000000000000270 0x000024 0x000024 R   0x4
2026-06-26 10:41:29 +03:00
Bartosz Taudul
7e2e1829c9 Update manual. 2026-06-25 18:12:21 +02:00
Bartosz Taudul
1ca38e2b70 Update NEWS. 2026-06-25 18:12:03 +02:00
Bartosz Taudul
1a420d0f5e Add frame statistics limit range. 2026-06-25 17:58:34 +02:00
Bartosz Taudul
7eae48c1a8 Make range stripes more visible when changing the range size. 2026-06-25 17:58:34 +02:00
Bartosz Taudul
1c712e41d9 Allow making annotations from ranges. 2026-06-25 17:58:34 +02:00
Bartosz Taudul
a03e5e8968 Use note sticky icon for add annotation button. 2026-06-25 17:58:32 +02:00
Bartosz Taudul
3e5310cac6 Fix sigma conversion in latex -> md script. 2026-06-25 00:14:21 +02:00
Bartosz Taudul
606cd7bdaf Protect against zero frames to show. 2026-06-24 23:49:53 +02:00
Bartosz Taudul
d924b05ab7 Calculate square of averages with double precision. 2026-06-24 23:27:36 +02:00
Bartosz Taudul
d0c7596977 Regenerate markdown manual. 2026-06-24 23:02:25 +02:00
Bartosz Taudul
0bee59ff85 Update manual. 2026-06-24 23:01:34 +02:00
Bartosz Taudul
963a968a9e Update NEWS. 2026-06-24 20:37:51 +02:00
Bartosz Taudul
f700597067 Display P75, P90, P99 and P99.9 for frame statistics. 2026-06-24 20:09:03 +02:00
Bartosz Taudul
a237b009ec Use unified percentile value calculation in find zone. 2026-06-24 19:56:24 +02:00
Bartosz Taudul
5970e90233 Display coefficient of variation in frame statistics. 2026-06-24 19:32:50 +02:00
Bartosz Taudul
5fc4ae8eef Display coefficient of variation in find zone. 2026-06-24 19:30:42 +02:00
Bartosz Taudul
a35f8a738d Display standard deviation in frame statistics. 2026-06-24 19:21:40 +02:00
Bartosz Taudul
3714e36652 Frame count in frame statistics may be not full. 2026-06-24 18:56:08 +02:00
Bartosz Taudul
565f3e5a72 Add mode to frame statistics. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
17720a0cd8 Keep FPS range and max counts in the same line. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
a7b0907d6c Move mean and median in frame statistics to match find zone. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
552159ccf6 Remove tree node for historgram in frame statistics. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
7e8180a2a7 Move frame statistics from trace info to its own window. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
05d34e4daa Make it possible to hide annotation. 2026-06-24 18:48:37 +02:00
Bartosz Taudul
72d45dfdad Fix display of annotations color on annotations list. 2026-06-24 18:48:35 +02:00
Marcos Slomp
d1373d3f4c code hardening for GLES 2.0 2026-06-19 11:03:30 -07:00
Marcos Slomp
22abcd272f cosmetic: making things a bit more idiomatic 2026-06-16 11:30:21 -07:00
Marcos Slomp
32b712c120 force inline helper method 2026-06-16 11:24:47 -07:00
Marcos Slomp
704b5c4030 refactor sentinel value 2026-06-16 11:24:27 -07:00
Marcos Slomp
dd6962b42c use queryId arg, not m_tail member 2026-06-16 11:24:12 -07:00
Marcos Slomp
16cfd25320 potentially faster timestamp query result check
Co-authored-by: Sergio Acereda <sergio.acereda@gmail.com>
2026-06-16 08:36:55 -07:00
220 changed files with 15305 additions and 11264 deletions

View File

@@ -24,8 +24,8 @@ runs:
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_DELAYED_INIT and TRACY_MANUAL_LIFETIME
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
# same with TRACY_MANUAL_LIFETIME
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build

49
.github/workflows/android.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: android
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-android:
runs-on: ubuntu-latest
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git ninja nodejs unzip
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r29
- run: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Library v7
run: |
cmake -B build-v7 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a
cmake --build build-v7
- name: Library v8
run: |
cmake -B build-v8 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a
cmake --build build-v8

61
.github/workflows/appimage.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: appimage
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: appimage-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-appimage:
runs-on: ubuntu-latest
container: ubuntu:24.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake ninja-build meson pkg-config git ca-certificates \
curl file desktop-file-utils nodejs patchelf \
libegl-dev libxkbcommon-dev libdbus-1-dev libssl-dev \
libffi-dev libexpat1-dev wayland-protocols
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Cache wayland
uses: actions/cache@v4
with:
path: profiler/build-appimage/wayland
key: ${{ runner.os }}-wayland-${{ hashFiles('profiler/appimage/build.sh') }}
- name: Build AppImage
run: profiler/appimage/build.sh
env:
GIT_REV: ${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: appimage
path: profiler/build-appimage/tracy-profiler-x86_64.AppImage

View File

@@ -7,8 +7,16 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: emscripten-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-emscripten:
@@ -20,7 +28,7 @@ jobs:
- name: Setup emscripten
uses: emscripten-core/setup-emsdk@v16
with:
version: 5.0.7
version: 6.0.9
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
@@ -30,6 +38,11 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Profiler GUI
run: |
cmake -G Ninja -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=MinSizeRel -DGIT_REV=${{ github.sha }} -DCMAKE_TOOLCHAIN_FILE=${{env.EMSDK}}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
@@ -44,7 +57,7 @@ jobs:
mkdir -p bin
cp profiler/build/index.html bin
cp profiler/build/favicon.svg bin
cp profiler/build/tracy-profiler.data bin
cp profiler/build/embed.tracy bin
cp profiler/build/tracy-profiler.js.gz bin
cp profiler/build/tracy-profiler.js.zst bin
cp profiler/build/tracy-profiler.wasm.gz bin

View File

@@ -8,6 +8,13 @@ on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: latex-${{ github.ref }}
cancel-in-progress: true
jobs:
build-manual:

79
.github/workflows/linux-cli.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: linux-cli
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: linux-cli-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-linux-cli:
runs-on: ubuntu-latest
container: ubuntu:24.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git ca-certificates curl nodejs
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel
- name: Build capture
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel
- name: Build csvexport
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel
- name: Build import
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel
- name: Build merge
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel
- name: Build monitor
run: |
cmake -B monitor/build -S monitor -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build monitor/build --parallel
- name: Stage release files
run: |
mkdir -p linux-cli
cp update/build/tracy-update linux-cli/
cp capture/build/tracy-capture linux-cli/
cp capture/build/tracy-capture-daemon linux-cli/
cp csvexport/build/tracy-csvexport linux-cli/
cp import/build/tracy-import-chrome linux-cli/
cp import/build/tracy-import-fuchsia linux-cli/
cp merge/build/tracy-merge linux-cli/
cp monitor/build/tracy-monitor linux-cli/
strip linux-cli/tracy-*
- uses: actions/upload-artifact@v4
with:
name: linux-cli
path: linux-cli

View File

@@ -7,8 +7,16 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: linux-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-linux:
@@ -16,7 +24,7 @@ jobs:
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua clang sdl3_image python-jinja
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
@@ -26,39 +34,58 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Determine build parallelism
run: |
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
echo "CMAKE_PARALLEL=--parallel 2" >> "$GITHUB_ENV"
else
echo "CMAKE_PARALLEL=--parallel" >> "$GITHUB_ENV"
fi
- name: Dyna example / clang
run: |
CC=clang CXX=clang++ cmake -B examples/dyna/build-clang -S examples/dyna -DCMAKE_BUILD_TYPE=Release
cmake --build examples/dyna/build-clang $CMAKE_PARALLEL
- name: Dyna example / gcc
run: |
CC=gcc CXX=g++ cmake -B examples/dyna/build-gcc -S examples/dyna -DCMAKE_BUILD_TYPE=Release
cmake --build examples/dyna/build-gcc $CMAKE_PARALLEL
- name: Profiler GUI
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
cmake --build profiler/build
else
cmake --build profiler/build --parallel 2
fi
cmake --build profiler/build $CMAKE_PARALLEL
- name: Update utility
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel
cmake --build update/build $CMAKE_PARALLEL
- name: Capture utility
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel
cmake --build capture/build $CMAKE_PARALLEL
- name: Csvexport utility
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel
cmake --build csvexport/build $CMAKE_PARALLEL
- name: Import utilities
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel
cmake --build import/build $CMAKE_PARALLEL
- name: Merge utility
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel
cmake --build merge/build $CMAKE_PARALLEL
- name: Monitor utility
run: |
cmake -B monitor/build -S monitor -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build monitor/build $CMAKE_PARALLEL
- name: Library (cmake)
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
cmake --build build
cmake --install build
cmake --build build $CMAKE_PARALLEL
env:
CMAKE_INSTALL_PREFIX: ${{ github.workspace }}/bin
- name: Library (meson)
@@ -74,10 +101,12 @@ jobs:
cp profiler/build/tracy-profiler bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp capture/build/tracy-capture-daemon bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin
cp merge/build/tracy-merge bin
cp monitor/build/tracy-monitor bin
strip bin/tracy-*
- uses: actions/upload-artifact@v4
with:

View File

@@ -6,13 +6,22 @@ on:
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: macos-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-macos:
runs-on: macos-15
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Cache CPM packages
@@ -21,13 +30,22 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Install dependencies
run: brew install pkg-config glfw meson
run: brew install pkg-config meson
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Build library
run: meson setup -Dtracy_enable=true build && meson compile -C build
- name: Test application
uses: ./.github/actions/test-tracy
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DBUNDLE=ON -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel 2 --config Release
- name: Build update
run: |
@@ -49,20 +67,19 @@ jobs:
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel --config Release
- name: Build library
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build && meson compile -C build && meson install -C build
- name: Test application
uses: ./.github/actions/test-tracy
- name: Package artifacts
run: |
mkdir -p bin
cp profiler/build/tracy-profiler bin
cp -r profiler/build/tracy-profiler.app bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp capture/build/tracy-capture-daemon bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin
cp merge/build/tracy-merge bin
strip bin/tracy-profiler.app/Contents/MacOS/tracy-profiler
strip bin/tracy-update bin/tracy-capture bin/tracy-capture-daemon bin/tracy-csvexport bin/tracy-import-chrome bin/tracy-import-fuchsia bin/tracy-merge
- uses: actions/upload-artifact@v4
with:
name: macos

View File

@@ -7,6 +7,13 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: mingw-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache

View File

@@ -4,26 +4,62 @@ on:
release:
types: [published]
permissions:
contents: read
concurrency:
group: release-${{ github.event.release.tag_name }}
cancel-in-progress: false
jobs:
release-build-windows:
uses: ./.github/workflows/windows.yml
release-build-macos:
uses: ./.github/workflows/macos.yml
release-build-manual:
uses: ./.github/workflows/latex.yml
release-build-appimage:
uses: ./.github/workflows/appimage.yml
release-build-linux-cli:
uses: ./.github/workflows/linux-cli.yml
attach-to-release:
needs: [release-build-windows, release-build-manual]
needs: [release-build-windows, release-build-macos, release-build-manual, release-build-appimage, release-build-linux-cli]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
- name: Create versioned zip
- name: Create versioned Windows zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd windows
zip -r ../windows-$VERSION_NO_V.zip .
- name: Create versioned macOS zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd macos
chmod +x tracy-* tracy-profiler.app/Contents/MacOS/*
zip -r ../macos-$VERSION_NO_V.zip .
- name: Create versioned Linux zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
mkdir linux
cp appimage/* linux/
cp linux-cli/* linux/
cd linux
zip -r ../linux-$VERSION_NO_V.zip .
- uses: softprops/action-gh-release@v2
with:
files: |
windows-*.zip
macos-*.zip
linux-*.zip
manual/tracy.pdf

View File

@@ -8,8 +8,16 @@ on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: windows-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-windows:
@@ -22,13 +30,28 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- uses: microsoft/setup-msbuild@v2
- uses: actions/setup-python@v2
- name: Cache pandoc
uses: actions/cache@v4
with:
python-version: '3.x'
- run: pip install meson ninja
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- uses: microsoft/setup-msbuild@v2
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Install meson
run: python -m pip install --upgrade meson
- name: Library (meson)
env:
CCACHE_DISABLE: '1'
run: |
$vs = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
foreach ($line in & cmd /c "`"$vs\VC\Auxiliary\Build\vcvarsall.bat`" x64 && set") {
if ($line -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') }
}
meson setup -Dtracy_enable=true build-meson
meson compile -C build-meson
- name: Test application
uses: ./.github/actions/test-tracy
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
@@ -53,14 +76,13 @@ jobs:
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel --config Release
- name: Test application
uses: ./.github/actions/test-tracy
- name: Package artifacts
run: |
mkdir bin
copy profiler\build\Release\tracy-profiler.exe bin
copy update\build\Release\tracy-update.exe bin
copy capture\build\Release\tracy-capture.exe bin
copy capture\build\Release\tracy-capture-daemon.exe bin
copy csvexport\build\Release\tracy-csvexport.exe bin
copy import\build\Release\tracy-import-chrome.exe bin
copy import\build\Release\tracy-import-fuchsia.exe bin

12
.gitignore vendored
View File

@@ -1,16 +1,12 @@
.vs
_build
_compiler
tools/*
*.d
*.o
*.so
*.swp
*.obj
imgui.ini
test/tracy_test
test/tracy_test.exe
*/build/unix/*-*
manual/t*.aux
manual/t*.log
manual/t*.out
@@ -21,21 +17,17 @@ manual/t*.bbl
manual/t*.blg
manual/t*.fdb_latexmk
manual/t*.fls
profiler/build/win32/packages
profiler/build/win32/Tracy.aps
manual/tracy.md
profiler/build-appimage
.deps/
.dirstamp
/_*/**
/**/__pycache__/**
extra/vswhere.exe
extra/tracy-build
.cache
.uv-cache/
.venv/
compile_commands.json
profiler/build/wasm/Tracy-release.*
profiler/build/wasm/Tracy-debug.*
profiler/build/wasm/embed.tracy
examples/ToyPathTracer/Windows/TestCpu
examples/ToyPathTracer/Windows/x64
*.user

28
.omp/AGENTS.md Normal file
View File

@@ -0,0 +1,28 @@
# Working rules
## Permissions and scope
- Answer questions; execute state changes only when explicitly instructed. Anything that changes state (files, git history, services, external systems) requires an explicit instruction in the current conversation. Never infer permission from context, momentum, or a previously similar instruction.
- Do exactly what was named. If you discover the same defect in a sibling, report it — do not fix it.
## Code
- Follow the style conventions already present in the repository — observe the files you touch and their related ones (siblings in the same layer, neighbors with the same role) before writing, and deviate only when told.
- Comments carry only what is not inferable from the code and is not already stated in the commit message, e.g. contracts, platform divergences. No narration of what the code does.
- Do not follow architectural precedent merely because it exists. Follow it when it is valid; when it is invalid — copy-pasted logic, a wrong home for shared code, a workaround that outlived its reason — propose the improvement instead.
- Do not assume a library behaves identically across platforms and standard-library implementations; verify behavior on the toolchains the code actually ships with.
## Verification
- Prove behavior, not just compilation: build the affected targets and run the changed path, including the failure paths. Label compile-only claims as such.
- Test non-trivial new logic before committing it; the first run of new code is where environment and API assumptions break.
- State verification limits explicitly (what could not be exercised, and why).
## Commits
- Subject: one line, imperative, capitalized, ends with a period — like `Bump LZ4 to 1.10.0.`, `Use the system wayland-protocols package.`.
- Body: only genuine rationale, mechanism, or constraints; skip it when the change is self-evident from the diff. Never recap decision processes or alternatives considered.
- One logical change per commit. Never bundle unrelated edits.
- The rationale, mechanism, non-obvious constraints — belong in the commit message, not in code comments.
- Explain a pattern once, at its first occurrence; repeat occurrences at other sites state only what they do.
- No process narration ("this commit", "as discussed"), no references to the conversation.

View File

@@ -0,0 +1,329 @@
---
name: tracy-gui-verify
description: Build and drive the Tracy Profiler GUI headlessly via two routes: the emscripten (web) GUI in a headless browser with an instrumented harness, or the native GUI under X11. Use when developing or testing the profiler GUI.
---
# Test the Tracy GUI headlessly
Two routes run the profiler GUI on a display-less machine. Pick one per test goal:
| | Route A — Web (emscripten) | Route B — Native (X11) |
|---|---|---|
|Display|Browser; no X11/Wayland/DISPLAY — the browser is the display server|Xvfb (virtual X server) + openbox (window manager)|
|Input|Puppeteer mouse/keyboard|xdotool mouse/keyboard|
|Ground truth|Instrumented harness (`debug_snapshot`/`debug_clear`) — app-side mouse/ID state|Screenshot diffs (PIL `ImageChops`) — no harness|
|Can test|Loaded-trace GUI: toolbar, panels, targeting, DPI scaling|Live TCP connection to a client, native file dialogs (save/open), X11/GLFW input|
|Cannot test|Live client (browser cannot open a TCP server), native file dialogs|No known GUI-level gap; verification precision is lower (no hit-test oracle)|
|Prereqs|emsdk (`$EMSDK_DIR`, default `~/emsdk`), CPM cache|Xvfb, openbox, xdotool, ImageMagick (`import`), mesa swrast|
# Route A — Web GUI (emscripten, headless 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`, exposing the app-side mouse/ID state — the ground truth for targeting and verification, not screenshots.
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 does: fresh source copy (excludes `.git` and build dirs) → applies `harness.patch` (adds `debug_snapshot`/`debug_clear` exports + event/frame recording to the emscripten backend, in the COPY only) → configures → builds Release with the emsdk at `$EMSDK_DIR` (default `~/emsdk`) and the CPM cache at `~/.cache/cpm`.
**Pitfall:** if the workspace is deleted while a `tracy-web` server is running, stop the hub process first — a stale server keeps the *deleted* cwd and silently returns empty responses while the port still accepts connections.
**Pitfall:** a `tracy-web` that died in an earlier session leaves the daemon in `failed` state: `stop` only reports the old failure, and `start` keeps returning "Daemon tracy-web has unacknowledged completion notifications" while the port stays free. Drain the daemon with `hub op=logs name=tracy-web` (the log also shows the cause), then relaunch with `hub op=restart name=tracy-web` (retained spec). If the port is NOT free, a stale process owns it — often one serving an earlier (now-stale) build directory: the new `httpd.py` dies on the bind and your requests silently hit the old directory. Check `ss -tlnp | grep 8000` and kill the owner. After any start/restart, verify the server is actually serving: `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/index.html` must return 200 — the daemon message is not proof of a server.
**After editing the GUI in the repo:** copy the changed files into `$WS/src/` (same relative path), re-run `cmake --build $WS/build -j$(nproc)`, reload the page. If a change touches `profiler/src/BackendEmscripten.cpp` or `profiler/CMakeLists.txt`, the harness patch may no longer apply — re-apply it manually (keep the `Dbg*` harness section, the `DbgPushEvent`/`DbgPushFrame` calls in the mouse callbacks + `NewFrame`, and the two `debug_*` exports in `-sEXPORTED_FUNCTIONS`).
## Serve + open + ready
Server (long-running → `hub`):
```
hub op=start name=tracy-web application=python3 args=["$WS/build/httpd.py"]
cwd=$WS/build ready={port:8000} persist=true
```
`httpd.py` sends the COOP/COEP headers required for pthreads/SharedArrayBuffer — use it, not a bare http.server.
Browser:
```
browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
```
**Ready signal** (in a `run` cell; `run` code executes in Node scope — page JS goes through `tab.evaluate`):
```js
let title = null;
for (let i = 0; i < 360; i++) {
title = await tab.evaluate(() => document.title);
if (/ - Tracy Profiler/.test(title)) break;
await new Promise(r => setTimeout(r, 250));
}
```
Title is `Tracy Profiler X.Y.Z` until the preloaded `embed.tracy` (DarkRL capture) finishes loading, then `<trace> (embed.tracy) - Tracy Profiler X.Y.Z` (`profiler/src/main.cpp` `SetWindowTitleCallback`). Wait ~1.5 s after, then confirm the app loop lives: `snap().frames.at(-1).tick > 0`.
## Harness API (ground truth)
```js
const snap = () => tab.evaluate(() => JSON.parse(Module.ccall('debug_snapshot','string',[],[])));
const clear = () => tab.evaluate(() => Module.ccall('debug_clear','void',[],[]));
```
`snap()``{ dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH, title, cursor, frames:[…64], events:[…64] }` (newest last).
- `frames[]`: one sample **per rAF tick, including idle ticks**. `tick` = main-loop count (always advances), `f` = `ImGui::GetFrameCount()` (advances only when a frame actually renders — the idle gate, §Idle), `mx/my` = app mouse position, `md[3]` = mouse button states, `q` = pending input events, `wa` = `tracy::s_wasActive` at tick start, `hi`/`ai` = ImGui `HoveredId`/`ActiveId`.
- `events[]`: raw callback data as the app received it. `type` 0=move 1=down 2=up 3=enter 4=leave 5=wheel; `tx/ty` = Emscripten int `targetX/Y`; `ax/ay` = position passed to ImGui (`-1` for non-move); `t` = DOMHighResTimeStamp.
## Coordinate mapping
```
appX = floor( trunc(cssX - rectL) * dpr ) // device px, what ImGui hit-tests
cssX = appX / dpr // inverse for targeting
screenshot_px = cssX * dpr
```
Chain: DOM `clientX` → Emscripten `targetX = int(clientX - (rect.left|0))` (`src/lib/libhtml5.js`, `EmscriptenMouseEvent.targetX` is an `int`) → Tracy `AddMousePosEvent(targetX * dpr, …)` (`BackendEmscripten.cpp`) → ImGui `ImFloor` (`imgui.cpp` `AddMousePosEvent`). Canvas fills the viewport, so `rectL/rectT` are 0 (re-read anyway).
**M: `dpr` on this workstation's headless browser is 1.25** (canvas buffer 2000×1125 for a 1600×900 viewport; screenshots are captured at device px). Never assume 1. App positions come in multiples of `dpr`; verify targeting within ±1 px via `frames[].mx/my`.
## Click protocol
Click = move, press, refresh through the hold, release, verify — all against rendered frames. The idle gate (§Idle) renders only 3 frames per wake, and a hold with no mouse events at all refreshes nothing, so a fixed-wait down/up from a cold idle can be swallowed (both events received, zero frames rendered) or released after the wake budget expires. Issuing mouse moves while the button is down keeps the budget alive: a same-coord re-move is delivered (verified: it reaches the canvas handler, wakes the app, and a same-coord hold click straddles cleanly) and is the safest refresh — zero hover drift; a ±`dx` px wiggle works too, but must stay inside the hit area:
```js
async function click(cssX, cssY, holdMs = 400) {
await page.mouse.move(cssX, cssY); // 1. ALWAYS move first
await new Promise(r => setTimeout(r, 150)); // 2. >= 1 rendered frame
const pre = await snap();
const pf = pre.frames.at(-1);
await clear();
await page.mouse.down(); // 3. press
let df = null, hiDuring = null;
const t0 = Date.now();
while (Date.now() - t0 < holdMs) {
await new Promise(r => setTimeout(r, 70));
await page.mouse.move(cssX, cssY); // 4. same-coord re-move refreshes the wake budget
const f = (await snap()).frames.at(-1);
if (f.md[0] === 1) { df ??= f.f; hiDuring = f.hi; }
}
await page.mouse.up(); // 5. release
let uf = null;
for (let i = 0; i < 40; i++) {
await new Promise(r => setTimeout(r, 40));
const f = (await snap()).frames.at(-1);
if (f.md[0] === 0) { uf = f.f; break; }
}
await new Promise(r => setTimeout(r, 400));
const s = await snap();
const d = s.dpr, ex = Math.floor(Math.trunc(cssX - s.rectL) * d), ey = Math.floor(Math.trunc(cssY - s.rectT) * d);
return { hoverID: pf.hi, hiDuring, appPos: [pf.mx, pf.my], expected: [ex, ey],
straddle: { df, uf },
ok: df !== null && uf !== null && uf > df && hiDuring === pf.hi &&
Math.abs(pf.mx - ex) <= 1 && Math.abs(pf.my - ey) <= 1 };
}
```
Rules:
1. **Move before every click.** The emscripten mousemove handler is the only thing that sets the app mouse position; mousedown/mouseup only queue button state. Fresh page position is invalid (`-FLT_MAX`) — a click without a prior move activates nothing.
2. **Press and release must each render** in separate frames with the item hovered (ImGui's `PressedOnClickRelease` — doc table at `imgui_widgets.cpp:489-497`; input trickling, `ConfigInputTrickleEventQueue`, defers a same-frame release). The same-coord refresh is what guarantees rendered frames across the hold from a cold idle.
3. **Verify every click:** `ok: true` (position landed, press and release each rendered, hover held — `hiDuring === hoverID`), plus a screenshot diff as supplementary visual confirmation. Non-ImGui targets (timeline zone bars) have `hi = 0` by design — for those verify by screenshot, not hover.
4. **Double-click trap:** two same-position clicks <300 ms apart = double-click (`MouseDoubleClickTime = 0.3 s`) → zoom/thread-info actions fire. Wait ≥350 ms between repeated clicks at one spot.
5. **Drag:** move → down → (move + wait 70 ms)×n → up. **Wheel:** `page.mouse.wheel({ deltaY: ±500 })` after positioning. **Keys:** `page.keyboard.type(..., { delay: 50 })` after focusing the input by click; `page.keyboard.press('Enter')`.
6. **Danger:** toolbar device x 1032 (css 826) = red power button (leftmost toolbar icon, hover ID 3313137574), closes the trace view (back to "Get started"). Never click it unless intended.
## Idle and power saving
`main.cpp DrawContents` keeps an `activeFrames = 3` budget refreshed on any input event (`tracy::s_wasActive`), a connected client, view animation, or a queued input; exhausted → `sleep 16 ms` with **no `ImGui::NewFrame` and no GL draw** (canvas keeps the last frame). Expect ~97 % of ticks to skip rendering while idle.
- Clicks from a cold idle land only via the click protocol's hold refresh (§Click protocol) — a fixed-wait down/up can be swallowed by the idle gate (both events received, zero frames rendered). **Before interacting, assert `tick`/`f` is advancing**; a stalled main loop (initial trace parse, hidden tab) renders nothing and drops everything. During the initial load the cursor style is `wait`.
- `focusLostLimit` (reduce render rate on focus loss) is desktop-only; it does not exist in the emscripten path.
- `canvas.style.cursor` is **not** a hover oracle: this ImGui build sets the Hand cursor only in `TextLink`, not on buttons.
## Targeting
Screenshots never resolve targeting. Full-page shots arrive downscaled JPEG (1024 px wide for a 2000-px device buffer; the factor varies), and `page.screenshot({ clip })` returns a PNG at device scale — a W×H CSS clip returns W·dpr × H·dpr px (CSS = clip offset + px/dpr). Icon buttons are 1525 device px wide, so visual estimates carry ±20 px error, enough to hit the neighboring button or a gap. Target from the app's own hit-testing — sweep the row and click the run's center:
```js
async function sweepRow(dpr, cssY, devFrom, devTo, step = 4) {
const ranges = []; let cur = null;
for (let dev = devFrom; dev <= devTo; dev += step) {
await page.mouse.move(dev / dpr, cssY);
await new Promise(r => setTimeout(r, 50));
const s = await snap();
const hi = s.frames[s.frames.length - 1].hi;
if (cur && hi === cur.id) cur.end = dev;
else { if (cur) ranges.push(cur); cur = { id: hi, start: dev, end: dev }; }
}
ranges.push(cur);
return ranges.filter(r => r.id !== 0 && r.end - r.start >= 4);
}
```
- Same non-zero `hi` across consecutive steps = one widget; `hi = 0` = gap. Click the run's center: `click((r.start + r.end) / 2 / dpr, cssY)`.
- **Layout is state-dependent:** docked panels (Statistics, Flame, …) resize the main window and shift the toolbar — **re-sweep after any state change.**
- **Close popups before sweeping:** while a popup is open, hovers outside the popup can read `hi = 0` even over live widgets (verified with ZoomPopup over the toolbar). Clicking a % option in ZoomPopup does not close the popup — click an empty area first.
- Hover IDs are stable across sessions (label hashes) — the map below holds for the default state (fresh load, no panels, 1600×900, dpr 1.25).
- Zoomed region shots (for correlating IDs with icons): `page.screenshot({clip})` returns a Buffer — write it with Node `fs`, then `read` the file:
```js
const fs = require('fs');
const buf = await page.screenshot({ clip: { x: 0, y: 0, width: 720, height: 28 } });
fs.writeFileSync('/tmp/toolbar-zoom.png', buf);
```
### Toolbar map — default state (device px; css = device/1.25; row y css 15)
| device x | hover ID | function |
|---|---|---|
| 1032 | 3313137574 | ⏻ power (red, leftmost) — **closes the trace view (DANGER)** |
| 4468 | 608241489 | ⚙ Options gear — opens the Options window (clickable; do not confuse with the power button) |
| 81185 | 2246346107 | 💬 Messages panel toggle |
| 199259 | 3177774389 | 🔍 Find zone toggle (search input hover ID 3637961452, css ~x 100440, y ~90) |
| 271367 | 1419322276 | 📊 Statistics toggle |
| 383451 | 322727105 | 🔥 Flame graph toggle |
| 463551 | 37993378 | 💾 Memory graph toggle |
| 567667 | 2133895361 | ⚖ Compare toggle |
| 679735 | 1808024465 | ⌗ Info (trace information) toggle |
| 751775 | 2535445525 | 🛠 wrench → ToolsPopup (Playback, CPU data, Annotations, Limits, Wait stacks, Frame statistics) |
| 791811 | 3101356487 | 📖 book → User manual toggle |
| 827851 | 2335673581 | 🔍+ → ZoomPopup (50300 % user scale) |
| 864888 | 1263603982 | ◀ previous frame — focuses the timeline on the previous frame (view range shrinks to the frame's timespan) |
| ~8901010 | 0 | frame set name + count text ("Frames: N") — LMB jumps to a specified frame (custom hit-test, like timeline zones) |
| 10121036 | 2142681495 | ▶ next frame — focuses the timeline on the next frame (verified: view range 25.82 s → 203.38 ms) |
| 10521072 | 2961929287 | ▼ frame set selection (switch the active frame set) |
Button names and functions are documented in the user manual's *Control menu* section (`manual/tracy.md`, generated into the profiler build directory by the build, e.g. `build/profiler/manual/tracy.md`). The web build omits *Connection* (live capture only) and *Tracy Assist* (desktop only).
## User scale (DPI zoom)
The 🔍+ button (hover ID 2335673581) — *Display scale* in the user manual — opens a ZoomPopup with 50300 % steps.
In the web build this scales the UI **layout only**: fonts, `Style::ScaleAllSizes`, and window sizes (`main.cpp SetupDPIScale`, `scale = devicePixelRatio × userScale`). The canvas buffer and the mouse→app mapping stay at `devicePixelRatio` (`BackendEmscripten.cpp`) — hit regions become physically larger in the same coordinate space, targeting precision is unchanged, and the standard click protocol works as-is.
Use it when small buttons are hard to hit or details are hard to read in screenshots. At 200 % every toolbar hit region is roughly 2× (verified: power 16→40 px, gear 25→50 px, Messages 105→190 px wide; row height 24→46 device px). The layout **reflows completely** at any scale — re-sweep after changing it and after setting it back. `userScale` is session-only unless Options → "Save UI scale" is on; a fresh page load starts at 100 %.
## Limitations
- A *connected* live-streaming client cannot be tested in the web build (the browser GUI cannot open a TCP server). The embedded-trace GUI is the live-app proxy: real trace data, full GUI — for live-client behavior use Route B.
- `embed.tracy` (10 MB) is downloaded from share.nereid.pl at configure time — network needed once per fresh workspace; the file then lives in `$WS/build`.
## References
Sources for the behavior the rules above encode — consult them when re-verifying a constant or when a rule fails:
- `profiler/src/BackendEmscripten.cpp` — emscripten input handlers, canvas/dpr sizing, cursor mapping.
- `profiler/src/main.cpp:540-574` — idle gate (`activeFrames` budget).
- `profiler/src/profiler/TracyMouse.cpp` — per-frame click state cache.
- `profiler/CMakeLists.txt:334-353` — emscripten link options.
- ImGui 1.92.9b (vendored copy in the CPM cache, `~/.cache/cpm/imgui/`): `imgui_widgets.cpp:483-545` (click semantics table), `imgui.cpp:11278-11403` (event trickling), `imgui.cpp:1736` (trickle default on), `imgui.cpp:1992-2016` (AddMousePosEvent floor/dedup).
- Emscripten 5.0.7: `src/lib/libhtml5.js` (`fillMouseEventData`, `targetX = e.clientX - (rect.left|0)` into HEAP32), `system/include/emscripten/html5.h` (`EmscriptenMouseEvent.targetX` is `int`; `EmscriptenWheelEvent` embeds a `mouse` sub-struct).
- Upstream: imgui issue #4921 (new input event API — events spread over multiple frames), PR #2525 (same-frame click/release), issue #1992 (rapid down/up "soft clicks" dropped).
# Route B — Native GUI (X11, headless)
Run the native (GLFW) GUI on a virtual X server for what Route A cannot test: a live TCP connection to a client, the native file dialogs (save/open flows), and X11 input semantics. Stack: Xvfb + openbox + xdotool + ImageMagick `import`. Constants marked M were verified on this workstation (1280x800 screen, openbox, profiler window client area at (1,20)); re-verify after any screen/WM change, and always confirm against a screenshot before clicking.
## Build
```sh
cmake -S profiler -B /tmp/prof-x11 -DCMAKE_BUILD_TYPE=Release -DLEGACY=ON -DGTK_FILESELECTOR=ON
cmake --build /tmp/prof-x11 --target tracy-profiler -j$(nproc)
```
- `LEGACY=ON` selects the X11 (GLFW) backend; the Linux default is Wayland.
- `GTK_FILESELECTOR=ON` routes NFD through GTK. **Without it** the default Linux NFD uses the XDG desktop portal and fails headless with the notification "File selector is not available".
- Verify after building: `ldd /tmp/prof-x11/tracy-profiler | grep -c gtk` ≥ 1.
## Services
```
hub op=start name=xvfb application=Xvfb args=[":99","-screen","0","1280x800x24"]
```
Ready when `/tmp/.X11-unix/X99` exists — check the socket. (If you add `-nolisten tcp` to the Xvfb args, a TCP port readiness check cannot work; the UNIX socket is then the only listener.) Then:
```sh
DISPLAY=:99 nohup openbox >/dev/null 2>&1 &
```
**openbox is required.** Without a window manager the window lands at an arbitrary position, `xdotool windowactivate` fails ("WM claims not to support _NET_ACTIVE_WINDOW"), and keyboard focus has no owner. Under openbox the profiler window is placed deterministically (M: (1,20), 1278x775 on a 1280x800 screen).
## Run
```sh
DISPLAY=:99 LIBGL_ALWAYS_SOFTWARE=1 nohup /tmp/prof-x11/tracy-profiler >gui.log 2>&1 &
```
- `LIBGL_ALWAYS_SOFTWARE=1` — no GPU; mesa swrast renders.
- Window: `DISPLAY=:99 xdotool search --name "Tracy Profiler"``getwindowgeometry` for the origin; `windowactivate --sync` to focus.
- Live test client: `tracy-monitor -p $(pgrep -n sleep)` style — attach to a long-lived process; the in-process client listens on 8086 and advertises via UDP (discovery works headless; the client line appears under "Discovered clients").
## Interaction rules
1. **Verify every click with a screenshot diff**`import -window root` before and after, then PIL `ImageChops.difference(a, b).getbbox()`: no (or irrelevant) bbox = the click missed. Expect at least one swallowed click; re-issue it and re-verify.
2. Move before click; sleep ≥0.3 s between two clicks at the same spot (ImGui double-click threshold is 0.3 s).
3. Keyboard (`xdotool key`) is reliable once openbox owns focus.
4. First launch only: an achievements prompt appears bottom-right — click "No" (M: 725,585). The choice persists in the Tracy config; later launches skip it.
5. A stale "Client not ready / another server" notification means the client was replaced underneath the GUI — click **Reconnect** (M: 738,574); there is no automatic retry.
## Coordinates (M: screen px, 1280x800, window at (1,20))
|Target|Coords|
|---|---|
|Get started → Connect|101,227|
|Get started → wrench (About panel)|317,120|
|Connection popup → Save trace…|64,202|
|Connection popup → Reconnect|738,574|
|GTK save dialog → Save button|1195,773|
|GTK overwrite confirmation → Replace|790,482|
|Tracy save-confirmation → Save trace|757,540|
Directly clicked and verified: Connect, Save trace…, GTK Save, Replace, and the save-confirmation button. Derived as the verified window-relative offset translated to the openbox window origin (confirm on first use): wrench, Reconnect, achievements "No".
The Get-started panel, connection popup, and the two confirmation panels keep fixed offsets inside the profiler window; the GTK dialogs are separate top-level windows sized to the screen. Derive any missing coordinate from a screenshot rather than guessing.
## Save flow (end-to-end)
1. Start the client, click **Connect** (the GUI never auto-connects).
2. The connection popup opens automatically on connect; click **Save trace…**.
3. GTK dialog: the Name entry is focused and the last-used directory is preselected; `xdotool type --delay 30 <name>`; click **Save** (1195,773).
4. Existing target file → a separate overwrite-confirmation window appears; click **Replace** (790,482).
5. Tracy's own confirmation panel appears (shows the final `Path:`); click its **Save trace** (757,540).
6. Verify the file (below).
## File and connection checks
- Live connection: `ss -tnp | grep 8086` shows `ESTAB` between `tracy-profiler` and the client process; the window title becomes `<program> @ <date> - Tracy Profiler X.Y.Z`.
- Saved `.tracy`: first 4 bytes = `74 72 FD 50`; `tail -c +11 file | zstd -t``premature end` means the frame decodes fully (expected — the cut falls past the final frame into trailing metadata); `unsupported format` means a bad offset or a non-trace file.
- No `file.tmp` may remain after a completed save; a save killed mid-write leaves the previous file intact (the GUI writes through `SafeFileWrite`, tmp + rename-on-commit).
- Video evidence while interacting: `ffmpeg -f x11grab -video_size 1280x800 -i :99 -t N -c:v libx264 out.mp4` (background it; it records the whole interaction).
## Limitations
- No debug harness — the screenshot diff is the only oracle; targeting is coordinate-from-screenshot, slower and coarser than Route A's hit-test sweeps.
- The verified coordinates are tied to the M constants (1280x800 screen, openbox default theme, 100 % UI scale); re-derive from a screenshot if any of them changes.
- The GTK dialogs are separate top-level windows whose layout follows the system GTK theme.
## Teardown
```sh
pkill -f prof-x11; pkill -f tracy-monitor
pkill -f "sleep 600" # the test target from §Run — adjust to what you started
pkill openbox
# hub op=stop name=xvfb
```
## References
- `profiler/src/BackendGlfw.cpp` — X11/GLFW input handlers (mouse/keyboard → ImGui).
- `profiler/CMakeLists.txt:5-7``NO_FILESELECTOR`, `GTK_FILESELECTOR`, `LEGACY` (X11 vs default Wayland) options.
- `cmake/vendor.cmake:222-242` — NFD packaging: `NFD_PORTAL` on by default on Linux, the `GTK_FILESELECTOR` switch, the `nfd-xdg-foreign-v2.patch`.
- `server/TracySafeFileWrite.hpp` — the GUI save's tmp + rename-on-commit (the invariants in §File and connection checks).
- NFD (btzy/nativefiledialog-extended, pinned in `cmake/vendor.cmake`) — the GTK dialog behavior (Name entry focus, last-directory memory, overwrite confirmation).

View File

@@ -0,0 +1,221 @@
diff -r a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt
--- a/profiler/CMakeLists.txt
+++ b/profiler/CMakeLists.txt
@@ -340,7 +340,7 @@
-sMAXIMUM_MEMORY=4gb
-sSTACK_SIZE=1048576
-sPTHREAD_POOL_SIZE=8
- -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
+ -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard,_debug_snapshot,_debug_clear
-sEXPORTED_RUNTIME_METHODS=ccall
-sENVIRONMENT=web,worker
--preload-file embed.tracy
diff -r a/profiler/src/BackendEmscripten.cpp b/profiler/src/BackendEmscripten.cpp
--- a/profiler/src/BackendEmscripten.cpp
+++ b/profiler/src/BackendEmscripten.cpp
@@ -1,6 +1,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <cstdarg>
+#include <algorithm>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
@@ -10,7 +12,146 @@
#include "Backend.hpp"
#include "RunQueue.hpp"
#include "profiler/TracyImGui.hpp"
+#include "imgui_internal.h"
+// ================= DEBUG HARNESS (temporary copy, not for upstream) =================
+struct DbgEvent
+{
+ int type; // 0=move 1=down 2=up 3=enter 4=leave 5=wheel
+ int button;
+ int targetX;
+ int targetY;
+ double appX; // what was passed to ImGui (targetX * dpr), -1 if n/a
+ double appY;
+ double t; // DOMHighResTimeStamp
+ int wheelX;
+ int wheelY;
+};
+struct DbgFrame
+{
+ double t;
+ int tick; // emscripten main-loop tick count (rAF)
+ int imguiFrame; // ImGui::GetFrameCount()
+ float mx, my; // io.MousePos
+ int md0, md1, md2;
+ int queue; // pending ImGui input events
+ int wasActive; // tracy::s_wasActive at tick start
+ unsigned hi; // ImGui hovered item ID
+ unsigned ai; // ImGui active item ID
+};
+static DbgEvent s_dbgEvents[512];
+static int s_dbgEventHead = 0, s_dbgEventN = 0;
+static DbgFrame s_dbgFrames[512];
+static int s_dbgFrameHead = 0, s_dbgFrameN = 0;
+static int s_dbgTick = 0;
+
+static void DbgPushEvent( int type, int button, int tx, int ty, double ax, double ay, double t, int wx = 0, int wy = 0 )
+{
+ auto& e = s_dbgEvents[s_dbgEventHead];
+ e.type = type; e.button = button; e.targetX = tx; e.targetY = ty;
+ e.appX = ax; e.appY = ay; e.t = t; e.wheelX = wx; e.wheelY = wy;
+ s_dbgEventHead = ( s_dbgEventHead + 1 ) % 512;
+ if ( s_dbgEventN < 512 ) s_dbgEventN++;
+}
+
+static void DbgPushFrame()
+{
+ auto& f = s_dbgFrames[s_dbgFrameHead];
+ auto& io = ImGui::GetIO();
+ f.t = EM_ASM_DOUBLE( { return performance.now(); } );
+ f.tick = s_dbgTick;
+ f.imguiFrame = ImGui::GetFrameCount();
+ f.mx = io.MousePos.x; f.my = io.MousePos.y;
+ f.md0 = io.MouseDown[0]; f.md1 = io.MouseDown[1]; f.md2 = io.MouseDown[2];
+ auto ctx = ImGui::GetCurrentContext();
+ f.queue = ctx ? int( ctx->InputEventsQueue.Size ) : -1;
+ f.wasActive = tracy::s_wasActive;
+ f.hi = ctx ? ctx->HoveredId : 0;
+ f.ai = ctx ? ctx->ActiveId : 0;
+ s_dbgFrameHead = ( s_dbgFrameHead + 1 ) % 512;
+ if ( s_dbgFrameN < 512 ) s_dbgFrameN++;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE void debug_clear()
+{
+ s_dbgEventHead = s_dbgEventN = s_dbgFrameHead = s_dbgFrameN = 0;
+}
+
+// Escape a C string for embedding in a JSON string literal (", \, control chars).
+static void JsonEscape( char* out, size_t outsz, const char* in )
+{
+ size_t o = 0;
+ for( const unsigned char* p = ( const unsigned char* )in; *p; p++ )
+ {
+ if ( *p == '"' || *p == '\\' )
+ {
+ if ( o + 2 >= outsz ) break;
+ out[o++] = '\\';
+ out[o++] = char( *p );
+ }
+ else if ( *p < 0x20 )
+ {
+ if ( o + 6 >= outsz ) break;
+ o += size_t( snprintf( out + o, outsz - o, "\\u%04x", *p ) );
+ }
+ else
+ {
+ if ( o + 1 >= outsz ) break;
+ out[o++] = char( *p );
+ }
+ }
+ out[o] = 0;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE const char* debug_snapshot()
+{
+ static char buf[512 * 1024];
+ size_t off = 0;
+ auto put = [ & ]( const char* fmt, ... )
+ {
+ if ( off + 1 >= sizeof( buf ) ) return;
+ va_list args;
+ va_start( args, fmt );
+ int n = vsnprintf( buf + off, sizeof( buf ) - off - 1, fmt, args );
+ va_end( args );
+ if ( n > 0 ) off = std::min( off + size_t( n ), sizeof( buf ) - 1 );
+ };
+ const auto dpr = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
+ const auto innerW = EM_ASM_INT( { return window.innerWidth; } );
+ const auto innerH = EM_ASM_INT( { return window.innerHeight; } );
+ const auto bufW = EM_ASM_INT( { return document.getElementById( 'canvas' ).width; } );
+ const auto bufH = EM_ASM_INT( { return document.getElementById( 'canvas' ).height; } );
+ const auto rectL = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.left; } );
+ const auto rectT = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.top; } );
+ const auto rectW = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.width; } );
+ const auto rectH = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.height; } );
+ char title[256];
+ EM_ASM( { stringToUTF8( document.title, $0, 256 ); }, title );
+ char cursor[64];
+ EM_ASM( { stringToUTF8( document.getElementById( 'canvas' ).style.cursor || "", $0, 64 ); }, cursor );
+ char title_e[1536];
+ char cursor_e[384];
+ JsonEscape( title_e, sizeof( title_e ), title );
+ JsonEscape( cursor_e, sizeof( cursor_e ), cursor );
+ put( "{\"dpr\":%.6f,\"innerW\":%d,\"innerH\":%d,\"bufW\":%d,\"bufH\":%d,\"rectL\":%.3f,\"rectT\":%.3f,\"rectW\":%.3f,\"rectH\":%.3f,\"title\":\"%s\",\"cursor\":\"%s\",\"frames\":[", dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH, title_e, cursor_e );
+ const int fn = std::min( s_dbgFrameN, 64 );
+ for ( int i = 0; i < fn; i++ )
+ {
+ auto& f = s_dbgFrames[ ( s_dbgFrameHead - fn + i + 512 ) % 512 ];
+ put( "%s{\"t\":%.3f,\"tick\":%d,\"f\":%d,\"mx\":%.1f,\"my\":%.1f,\"md\":[%d,%d,%d],\"q\":%d,\"wa\":%d,\"hi\":%u,\"ai\":%u}", i ? "," : "", f.t, f.tick, f.imguiFrame, f.mx, f.my, f.md0, f.md1, f.md2, f.queue, f.wasActive, f.hi, f.ai );
+ }
+ put( "],\"events\":[");
+ const int en = std::min( s_dbgEventN, 64 );
+ for ( int i = 0; i < en; i++ )
+ {
+ auto& e = s_dbgEvents[ ( s_dbgEventHead - en + i + 512 ) % 512 ];
+ put( "%s{\"type\":%d,\"btn\":%d,\"tx\":%d,\"ty\":%d,\"ax\":%.3f,\"ay\":%.3f,\"t\":%.3f,\"wx\":%d,\"wy\":%d}", i ? "," : "", e.type, e.button, e.targetX, e.targetY, e.appX, e.appY, e.t, e.wheelX, e.wheelY );
+ }
+ put( "]}");
+ buf[off] = 0;
+ return buf;
+}
+
static std::function<void()> s_redraw;
static std::function<void(float)> s_scaleChanged;
static std::function<int(void)> s_isBusy;
@@ -223,32 +364,38 @@
emscripten_set_mousedown_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, true );
tracy::s_wasActive = true;
+ DbgPushEvent( 1, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_mouseup_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, false );
tracy::s_wasActive = true;
+ DbgPushEvent( 2, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_mousemove_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
const auto scale = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
ImGui::GetIO().AddMousePosEvent( e->targetX * scale, e->targetY * scale );
tracy::s_wasActive = true;
+ DbgPushEvent( 0, -1, e->targetX, e->targetY, e->targetX * scale, e->targetY * scale, e->timestamp );
return EM_TRUE;
} );
- emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
+ emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( false );
tracy::s_wasActive = true;
+ DbgPushEvent( 4, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
- emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
+ emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( true );
tracy::s_wasActive = true;
+ DbgPushEvent( 3, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_wheel_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenWheelEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseWheelEvent( e->deltaX * -0.05, e->deltaY * -0.05 );
tracy::s_wasActive = true;
+ DbgPushEvent( 5, -1, e->mouse.targetX, e->mouse.targetY, -1, -1, e->mouse.timestamp, int( e->deltaX ), int( e->deltaY ) );
return EM_TRUE;
} );
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
@@ -298,6 +445,8 @@
void Backend::NewFrame( int& w, int& h )
{
+ s_dbgTick++;
+ DbgPushFrame();
const auto scale = GetDpiScale();
if( scale != s_prevScale )
{

View File

@@ -0,0 +1,102 @@
#!/bin/sh
# setup.sh - build the instrumented Tracy web GUI workspace (see SKILL.md).
#
# Usage:
# sh setup.sh
# TRACY_WEB_WS=/custom/ws TRACY_WEB_REPO=<repo> sh setup.sh
#
# Idempotent: reuses an existing source copy and build dir, skips
# already-applied harness patch hunks, rebuilds.
set -e
WS="${TRACY_WEB_WS:-/tmp/tracy-protocol}"
# The skill is project-scoped: <repo>/.omp/skills/tracy-gui-verify/
SKILL_DIR=$(cd "$(dirname "$0")" && pwd)
if [ -n "$TRACY_WEB_REPO" ]; then
REPO="$TRACY_WEB_REPO"
else
REPO=$(cd "$SKILL_DIR/../../.." 2>/dev/null && pwd) || REPO=""
fi
EMSDK="${EMSDK_DIR:-$HOME/emsdk}"
[ -x "$EMSDK/emsdk" ] || { echo "error: emsdk not found at $EMSDK (set EMSDK_DIR)" >&2; exit 1; }
[ -f "$REPO/profiler/CMakeLists.txt" ] || { echo "error: not a Tracy repo: '$REPO' (set TRACY_WEB_REPO)" >&2; exit 1; }
. "$EMSDK/emsdk_env.sh" >/dev/null 2>&1 || true
command -v emcc >/dev/null || { echo "error: emcc not on PATH after emsdk_env.sh" >&2; exit 1; }
if [ ! -f "$WS/src/profiler/CMakeLists.txt" ]; then
echo "[setup] fresh source copy -> $WS/src (from $REPO)"
mkdir -p "$WS/src"
# -C applies to both archive creation and extraction; exclude all build dirs
tar -C "$REPO" \
--exclude=.git \
--exclude='*/build*' \
-cf - . | tar -xf - -C "$WS/src"
else
echo "[setup] reusing source copy in $WS/src (copy changed repo files into it first)"
fi
# The harness is fully in place iff each patch hunk's signature line is present.
# patch --forward skips a whole file once its first hunk is detected as already
# applied, so a failed later hunk is invisible in patch's output and exit code —
# verify the content instead of trusting either.
harness_complete()
{
grep -q '_debug_snapshot' "$WS/src/profiler/CMakeLists.txt" \
&& grep -qF 'EMSCRIPTEN_KEEPALIVE const char* debug_snapshot' "$WS/src/profiler/src/BackendEmscripten.cpp" \
&& grep -qF 'DbgPushEvent( 1,' "$WS/src/profiler/src/BackendEmscripten.cpp" \
&& grep -qF 's_dbgTick++;' "$WS/src/profiler/src/BackendEmscripten.cpp"
}
echo "[setup] applying instrumentation harness"
if harness_complete; then
echo "[setup] harness already applied"
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
else
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
patch -p1 -d "$WS/src" --forward --no-backup-if-mismatch < "$SKILL_DIR/harness.patch" \
|| {
if harness_complete; then
# mixed run: some hunks already present (skipped), the rest applied
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
else
rej=$(ls "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej" 2>/dev/null || true)
if [ -n "$rej" ]; then
kept="The unapplied hunks are kept in:
$(printf '%s\n' $rej | sed 's/^/ /')"
else
kept="No .rej files were produced (fatal patch error); the patch output above is the only evidence."
fi
cat >&2 <<EOF
error: harness patch did not fully apply — $WS/src has drifted from the repo.
$kept
Do not build from this workspace. Either re-apply the harness by hand
(SKILL.md, "After editing the GUI in the repo"), or start fresh:
rm -rf $WS && sh <this-skill-dir>/setup.sh
EOF
exit 1
fi
}
fi
if [ ! -f "$WS/build/CMakeCache.txt" ]; then
echo "[setup] configuring"
cmake -S "$WS/src/profiler" -B "$WS/build" \
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCPM_SOURCE_CACHE="$HOME/.cache/cpm"
fi
echo "[setup] building (first full build ~3-5 min, incremental ~30 s)"
cmake --build "$WS/build" -j "$(nproc)"
cat <<EOF
[setup] done.
workspace : $WS
repo : $REPO
emcc : $(emcc --version | sed -n 1p)
serve : hub op=start name=tracy-web application=python3 args=[$WS/build/httpd.py] cwd=$WS/build ready={port:8000} persist=true
open : browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
ready : document.title matches / - Tracy Profiler/ (see SKILL.md)
EOF

View File

@@ -121,12 +121,12 @@ set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF TracyC
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF TracyClient)
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF TracyClient)
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF TracyClient)
set_option(TRACY_NO_WAIT_STACKS "Disable capture of context switch call stacks" OFF TracyClient)
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF TracyClient)
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF TracyClient)
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF TracyClient)
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF TracyClient)
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF TracyClient)
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF TracyClient)
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF TracyClient)
set_option(TRACY_FIBERS "Enable fibers support" OFF TracyClient)
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF TracyClient)
@@ -136,7 +136,7 @@ set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported"
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 TracyClient)
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 TracyClient)
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF TracyClient)
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF TracyClient)
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" OFF TracyClient)
set_option(TRACY_OPENGL_AUTO_CALIBRATION "Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)" OFF TracyClient)
# advanced
@@ -152,9 +152,6 @@ if(rocprofiler-sdk_FOUND)
endif()
# handle incompatible combinations
if(TRACY_MANUAL_LIFETIME AND NOT TRACY_DELAYED_INIT)
message(FATAL_ERROR "TRACY_MANUAL_LIFETIME can not be activated with disabled TRACY_DELAYED_INIT")
endif()
if(TRACY_DISALLOW_HW_TIMER AND NOT TRACY_TIMER_FALLBACK)
message(FATAL_ERROR "TRACY_DISALLOW_HW_TIMER can not be activated with disabled TRACY_TIMER_FALLBACK")
endif()
@@ -182,7 +179,8 @@ set(tracy_includes
${TRACY_PUBLIC_DIR}/tracy/TracyMetal.hmm
${TRACY_PUBLIC_DIR}/tracy/TracyOpenCL.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyOpenGL.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp)
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyWebGPU.hpp)
set(client_includes
${TRACY_PUBLIC_DIR}/client/tracy_concurrentqueue.h
@@ -213,15 +211,19 @@ set(common_includes
${TRACY_PUBLIC_DIR}/common/TracyAlign.hpp
${TRACY_PUBLIC_DIR}/common/TracyAlloc.hpp
${TRACY_PUBLIC_DIR}/common/TracyApi.h
${TRACY_PUBLIC_DIR}/common/TracyAssert.hpp
${TRACY_PUBLIC_DIR}/common/TracyColor.hpp
${TRACY_PUBLIC_DIR}/common/TracyForceInline.hpp
${TRACY_PUBLIC_DIR}/common/TracyFormat.h
${TRACY_PUBLIC_DIR}/common/TracyMutex.hpp
${TRACY_PUBLIC_DIR}/common/TracyProtocol.hpp
${TRACY_PUBLIC_DIR}/common/TracyQueue.hpp
${TRACY_PUBLIC_DIR}/common/TracySocket.hpp
${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp
${TRACY_PUBLIC_DIR}/common/TracyString.hpp
${TRACY_PUBLIC_DIR}/common/TracySystem.hpp
${TRACY_PUBLIC_DIR}/common/TracyTaggedUserlandAddress.hpp
${TRACY_PUBLIC_DIR}/common/TracyVersion.hpp
${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp
${TRACY_PUBLIC_DIR}/common/TracyYield.hpp)
@@ -274,6 +276,11 @@ configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TracyConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
COMPATIBILITY SameMinorVersion)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
option(TRACY_CLIENT_PYTHON "Whether to build Tracy python client library" OFF)

View File

@@ -1,5 +1,7 @@
@PACKAGE_INIT@
set(TRACY_VERSION_STRING "@TRACY_VERSION_STRING@")
include(CMakeFindDependencyMacro)
find_dependency(Threads REQUIRED)

183
NEWS
View File

@@ -5,13 +5,92 @@ here.
vx.xx.x (2026-xx-xx)
--------------------
- Added the monitor utility. This Linux-specific tool can launch or attach
to a program that has no Tracy instrumentation and perform a profiling run
based on sampling call stacks.
- It is now possible to load traces available on the web in the web demo
of the profiler. See the manual for details.
- Pandoc is now required to build the graphical profiler. The build system
will automatically download the tool. The pandoc binary will be stored
in the user cache directory.
- The profiler listening port can now be retrieved during runtime via the
TracyPort macro.
- The capture daemon will now lock the directory it writes to, so that two
simultaneously running daemons will not stomp on each other's data.
- Section rows on the timeline can now be grouped by their category.
- System tracing on Linux now properly parses rare non-default kernel data
structure formats.
- Removed filtering of context switch data when saving a trace to disk, as
it was losing the thread wake graph data.
- Fixed standard deviation in find zone window when the self or running time
option was enabled.
- Find zone window now works with GPU zones (histogram, statistics, self
time).
- Changed the GPU zones icon from an eye to a display monitor.
- User GUI scale in the profiler can be now controlled with Ctrl + plus/minus
and reset with Ctrl + zero.
- Various hardening improvements for correctness across the code base.
v0.14.1 (2026-08-22)
--------------------
- Dropped support for loading pre-0.11 traces.
- Silenced MSVC warning C4366: The result of the unary '&' operator may be
unaligned.
- Fixed section setup in on-demand mode.
- Hardened string length encoding in protocol.
- Fixed compilation failure related to VSync when targeting 32-bit on
Windows.
- Added TRACY_NO_WAIT_STACKS compile time macro.
- Improved documentation of requirements in edge cases:
- Memory allocation reporting needs to be done under a lock.
- Manual lifetime management requires shutdown of the profiler.
- Documented how zones behave when crossing the on-demand connection
boundary.
- Mention that statistics time range limit also affects source view.
- Memory events are now attributed to virtual fiber threads.
- Added TRACY_ASSERT macro allowing user-defined handling of asserts in the
profiler.
- Fixed csvexport options: -t, --sep, --filter.
- Configuration of plot color now matches the documented RGB channels order.
- Link profiler executable with libdl (required by old glibc versions).
- Improve coverage of link-time macro mismatch detection.
- Removed TRACY_DELAYED_INIT option. It was no-op since 2020.
- Fixed illegal instruction in rpmalloc on ARMv6 and older.
- Fixed handling of source code queries in no-callstack builds. Previously
the profiling session would hang at exit.
- Hardened Lua on-demand handling.
- Fixed system tracing on Linux kernels older than 4.1.
- Linux system tracing setup failures are now reported via Tracy debug
message functionality.
- Improved version handling in CMake / Meson / Python build systems.
- Fixed D3D12 readback buffer error.
- Child zone list in zone info window can be now sorted by name or by time.
- File dialog failures (e.g. related to the XDG portal) are now reported
in the modal dialog.
- Fixed NO_FILESELECTOR builds.
- Fixed possible profiler hang when there was not enough space to draw
a plot on the timeline.
- Update check can be now interrupted, e.g. when exiting the profiler.
- Patched the file selector library to use modern Wayland protocol for
setting parent-child window relation.
v0.14.0 (2026-08-09)
--------------------
- API break: removed "secure" variants of memory alloc and free macros. The
secure code path is now always enabled. Migrate by removing "Secure" from
the macros you use, e.g. TracySecureAlloc(...) -> TracyAlloc(...).
- Added printf-style variants of the C API zone text and name macros:
TracyCZoneTextF and TracyCZoneNameF now accept a format string and
arguments, instead of a fixed-length text buffer.
- Added tracy-capture-daemon for automated multi-client trace capture.
- Added tracy-merge utility for combining multiple trace files into one.
- Added support for Windows on ARM64 with MSVC.
- Added support for WebGPU.
- Binary releases are now also provided for macOS (needs manual quarantine
handling) and Linux (AppImage with Ubuntu 24.04 base, requires Wayland).
- Trace-specific settings storage has been completely overhauled. It is now
possible to make the settings sidecar file public, saved next to the trace
file.
@@ -22,9 +101,44 @@ vx.xx.x (2026-xx-xx)
- Flame graph window.
- Call stack window.
- Statistics window (sampling mode).
- Wait stacks have a separate setting for external frames, as you would
typically want to know what caused the wait state, which can be inferred
from the external code.
- External frames are now dimmed out in call stacks in various parts of UI.
- Sample markers on the timeline are now colored according to sample type:
- Green for samples in the profiled program,
- Blue for samples in external code,
- Red for samples in kernel.
- Single-line call stacks now have ellipsis at the end, if there are frames
remaining.
- Sample entry stacks window can now also show the stacks by which a symbol
was reached, i.e. it was present anywhere on the call stack, not only when
it was executing. Recursive re-entries can be either ignored or counted
separately.
- Entry stacks are now also available for symbols which were only reached,
but never directly sampled, e.g. in the assembly view of a call site with
purely child sample costs, or in the statistics window for symbols which
only have inclusive counts.
- Fixed inclusive sample counts of symbols with inline functions. Each
sample was counted multiple times: once for the symbol itself and once for
every of its inline functions present on the call stack.
- Fixed context switch samples leaking into per-symbol sample lists and
child sample data on trace load. This inflated range-limited sample counts
and child call costs, and made loaded traces disagree with live sessions,
where such samples are excluded from sampling statistics.
- Fixed wrong results of range-limited child call queries during live
capture, when some samples had to wait for context switch data to arrive.
- Fixed crashes when opening the sample entry stacks window for symbols
without any sampling data.
- Sampling statistics percentages are now calculated against the number of
samples that were actually collected. Previously, enabling the range
filter switched the calculation to a theoretical single-thread estimate,
which changed both the meaning and the scale of the displayed values.
- Find zone sample count percentages are now relative to the matched zones,
consistent with the time percentages in the same list, instead of being
relative to the whole trace.
- Context switch samples are now consistently excluded from all sampling
statistics views.
- System tracing on Windows has been refactored to be more robust.
- Tracing on Arm macOS will now have more precise timer readings.
- Extended CUDA support to track some previously missing memory operations.
@@ -36,7 +150,8 @@ vx.xx.x (2026-xx-xx)
- Added inline call stack list to the zone information window.
- The "call stack" button for opening the call stack in a separate window
is still there.
- Call stacks are now also displayed in zone tooltips (single-line).
- Call stacks are now also displayed in zone and memory allocation tooltips
(single-line).
- Implemented heuristic reconstruction of zone call stacks for zones that
were captured without call stacks.
- Requires sampling to be enabled, and at least one sample in the zone
@@ -78,13 +193,16 @@ vx.xx.x (2026-xx-xx)
- Chat topic description is now provided, based on the first user question.
- Each assistant reply is now labeled with used model and reply time.
- Follow-up questions can be automatically suggested.
- It is now possible to alter the assistant personality.
- Expanded LLM attachments.
- You can now attach complete symbol assembly.
- Entry call stacks can be now attached (previously it was only regular
call stacks).
- Crash call stack attachments are now annotated with crash info.
- Wait stack attachments now contain wait metadata.
- Source code can be attached (also with execution costs in symbol view).
- Zone histogram data can be attached for analysis.
- Added MCP server.
- Markdown renderer improvements.
- Tables are now properly rendered.
- Tasklist rendering has been implemented.
@@ -108,6 +226,7 @@ vx.xx.x (2026-xx-xx)
locations won't be fixed.
- Call stack window will now display notification if viewing a crash call
stack.
- Call stack window will now show a proper label if viewing a wait stack.
- Removal of Tracy crash handler stack from the reported crash call stack
should now work again on Linux.
- In disassembly line view, source file names are now displayed instead of
@@ -119,6 +238,8 @@ vx.xx.x (2026-xx-xx)
- Added TRACY_DISALLOW_HW_TIMER define for virtualized environments and WSL2,
which may not have reliable access to hardware timer registers. Falls back
to standard library timer with reduced resolution.
- Fixed TRACY_NO_CODE_TRANSFER define. It didn't prevent source code transfer
before.
- Fixed DPI scaling on macOS.
- Thread names are no longer truncated to 15 characters on Apple.
- Executable path is now inspected when looking for PDB files on Windows.
@@ -148,8 +269,9 @@ vx.xx.x (2026-xx-xx)
- Mismatched versions will break at linking.
- As a reminder, Tracy *always* required using the same set of compilation
options for the entire program.
- Message windows will now properly show full message in a tooltip for
multi-line messages.
- Message windows were improved.
- Full message will be now displayed in a tooltip for multi-line messages.
- Added right click context menu with option to copy message to clipboard.
- Greatly improved the in-profiler user manual.
- There is now chapter tree and the manual contents are displayed section
by section.
@@ -183,7 +305,12 @@ vx.xx.x (2026-xx-xx)
- The profiler can no longer be built with the statistics disabled.
- Fixed NVCC builds.
- Fixed possible lockups in Vulkan timer calibration loop.
- The flame graph view now supports zooming in and panning with the mouse.
- Flame graph improvements.
- The flame graph view now supports zooming in and panning with the mouse.
- Sampling flame graph items can be now also grouped by name, in addition
to symbol address.
- Sampling flame graph no longer omits display of unknown symbols. These
are now aggregated into blocks named after the executable image.
- General application crash information polish in the profiler UI.
- The achievements system has been converted to use markdown renderer.
- Offline symbol resolution with the update utility now supports custom
@@ -194,11 +321,59 @@ vx.xx.x (2026-xx-xx)
with the TRACY_OPENGL_AUTO_CALIBRATION compilation define. Note that this
requires a full CPU/GPU sync on each calibration event. These events will
not fire more often than once every second.
- Additional hardening of OpenGL tracing.
- Added missing C API for shared locks.
- Implemented semi-unique, nonsense random name generator.
- Can be used to set a trace description.
- Will be used to provide default description for newly added annotations.
- Polished look and feel of annotation regions on the timeline.
- Annotations can now be hidden.
- It is now possible to create annotations from time range limits.
- Expanded available tests.
- The previously existing "test" program now lives in "tests/tracy".
- The tests directory also contains some repro cases for solved problems.
- Expanded examples showing how to use the profiler.
- Includes "Dyna.net", a small 2D game that is a variation of Bomberman.
- Made numeric input field for trace parameters usable again.
- Added new type of trace parameter that exposes a trigger button.
- Added "sections" feature for marking high-level sections of program
execution.
- For example, a game with multiple levels may have each of the levels
marked as a section.
- Each section is a distinct region not restricted by other sections.
- Multiple sections can overlap.
- Sections can be assigned to user-defined categories.
- Can be used to set time range limits.
- Exposed via Lua bindings.
- Find zone window now also displays coefficient of variation, in addition
to standard deviation.
- Moved frame statistics from trace information window to its own window.
- Added various statistical data to bring frame statistics up to par with
the find zone window.
- Added a new time range limit for frame statistics. This accompanies the
previously existing "limit to view" option.
- More robust statistics for functions that recursively call themselves.
- General uplift of the compare traces window.
- Added all the statistical parameters displayed in find zone window that
were missing here.
- Frame comparison now also displays FPS values.
- The "more" and "less" indicators are now color coded to show their
importance. The amount of color these keywords are printed with is now
dependant on how divergent the numeric values are.
- It is now possible to limit time range for both traces that are compared
to their respective annotations or sections.
- Frame image playback improvements.
- Playback can be looped.
- The range of played frame images can be limited, either manually, or by
copying annotation, section, or time range limit. There's an "require
frame coverage" option to only include frames that are at least 75%
within the source region.
- Most windows now have a minimum width, in order to make sure all controls
provided in the window are visible. Previously it was easy to oversee some
button that was out of the window draw area.
- The ignore memory faults functionality (enabled by default if profiling
programs on Apple platforms) will now also ignore allocating memory at the
same location twice, without an intermediate free. Thanks, Tim Apple!
v0.13.1 (2025-12-11)

View File

@@ -16,21 +16,21 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/util.cmake)
set(PROGRAM_FILES
src/capture.cpp
src/CaptureOutput.cpp
)
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES})
add_executable(${PROJECT_NAME} ${PROGRAM_FILES})
add_git_ref(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyUtil TracyGetOpt)
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
add_executable(tracy-capture-daemon src/capturedaemon.cpp src/CaptureOutput.cpp ${COMMON_FILES} ${SERVER_FILES})
add_executable(tracy-capture-daemon src/capturedaemon.cpp)
add_git_ref(tracy-capture-daemon)
target_link_libraries(tracy-capture-daemon PRIVATE TracyServer TracyGetOpt)
target_link_libraries(tracy-capture-daemon PRIVATE TracyServer TracyUtil TracyGetOpt)
install(TARGETS tracy-capture-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})

31
capture/CMakePresets.json Normal file
View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -11,7 +11,6 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "../../server/TracyFileWrite.hpp"
#include "../../server/TracyPrint.hpp"
@@ -19,6 +18,8 @@
#include "../../server/TracyWorker.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
#include "CaptureFileBackup.hpp"
#include "CaptureConsole.hpp"
#include "CaptureOutput.hpp"
@@ -96,21 +97,18 @@ int main( int argc, char** argv )
if( !address || !output ) Usage();
struct stat st;
if( stat( output, &st ) == 0 && !overwrite )
const char* prepError = nullptr;
const auto prep = tracy::PrepareOutputFile( output, overwrite, &prepError );
if( prep == tracy::OutputPrep::Exists )
{
printf( "Output file %s already exists! Use -f to force overwrite.\n", output );
return 4;
}
FILE* test = fopen( output, "wb" );
if( !test )
if( prep == tracy::OutputPrep::Unusable )
{
printf( "Cannot open output file %s for writing!\n", output );
printf( "Cannot use output file: %s!\n", prepError );
return 5;
}
fclose( test );
unlink( output );
printf( "Connecting to %s:%i...", address, port );
fflush( stdout );
@@ -137,6 +135,7 @@ int main( int argc, char** argv )
{
worker.Disconnect();
s_disconnect.store(false, std::memory_order_relaxed );
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
break;
}
@@ -168,10 +167,12 @@ int main( int argc, char** argv )
f->Finish();
const auto stats = f->GetCompressionStatistics();
printf( "Trace size %s (%.2f%% ratio)\n", tracy::MemSizeToString( stats.second ), 100.f * stats.second / stats.first );
tracy::DiscardOutputBackup();
}
else
{
AnsiPrintf( ANSI_RED ANSI_BOLD, " failed!\n");
tracy::RestoreOutputBackup();
}
return 0;

View File

@@ -1,4 +1,5 @@
#ifdef _WIN32
# include <process.h>
# include <windows.h>
#else
# include <unistd.h>
@@ -7,7 +8,10 @@
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <map>
#include <mutex>
@@ -28,6 +32,7 @@
#include "../../server/TracyWorker.hpp"
#include "GitRef.hpp"
#include "CaptureConsole.hpp"
#include "CaptureOutput.hpp"
static std::atomic<bool> g_shutdown{false};
@@ -36,12 +41,29 @@ static uint16_t g_listenPort = 8086;
static std::string g_filterName;
static int g_filterPort = 0;
static int64_t g_memoryLimit = -1;
static std::string g_lockFile;
void SigInt( int )
{
g_shutdown.store( true, std::memory_order_relaxed );
}
static void RemoveLockFile()
{
std::error_code ec;
std::filesystem::remove( g_lockFile, ec );
}
static long ReadLockPid( const std::string& path )
{
FILE* in = fopen( path.c_str(), "r" );
if( !in ) return -1;
long pid = -1;
const int result = fscanf( in, "%li", &pid );
fclose( in );
return ( result == 1 && pid > 0 ) ? pid : -1;
}
struct ClientStats
{
std::atomic<float> mbps{0};
@@ -158,17 +180,24 @@ void CaptureThread( ClientSession* session, const std::string& address, uint16_t
{
auto& lock = worker.GetMbpsDataLock();
lock.lock();
float mbps = worker.GetMbpsData().back();
int64_t txTotal = worker.GetDataTransferred();
const auto& mbpsData = worker.GetMbpsData();
if( !mbpsData.empty() )
{
session->stats.mbps = mbpsData.back();
session->stats.txBytes = worker.GetDataTransferred();
}
lock.unlock();
session->stats.mbps = mbps;
session->stats.txBytes = txTotal;
session->stats.memUsage = tracy::memUsage.load( std::memory_order_relaxed );
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
if( worker.IsConnected() )
{
worker.Disconnect();
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
printf( "\nSaving %s...", outputFile.c_str() );
fflush( stdout );
@@ -329,7 +358,45 @@ int main( int argc, char** argv )
std::filesystem::create_directories( outputDir );
InitTerminalDetection();
#ifndef _WIN32
g_lockFile = outputDir + "/.tracy-capture-daemon.lock";
{
int lockFd = -1;
for( int attempt = 0; attempt < 5; attempt++ )
{
lockFd = open( g_lockFile.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644 );
if( lockFd >= 0 ) break;
if( errno != EEXIST )
{
fprintf( stderr, "Error: Cannot create the lock file %s: %s\n", g_lockFile.c_str(), strerror( errno ) );
return 1;
}
const long owner = ReadLockPid( g_lockFile );
if( owner <= 0 )
{
fprintf( stderr, "Error: The lock file %s has no readable owner PID; remove it by hand if no daemon is running\n", g_lockFile.c_str() );
return 1;
}
// EPERM: the process exists under another user; treat it as alive.
if( kill( owner, 0 ) == 0 || errno == EPERM )
{
fprintf( stderr, "Error: Another capture daemon (pid %ld) is already using the output directory %s\n", owner, outputDir.c_str() );
return 1;
}
std::error_code ec;
std::filesystem::remove( g_lockFile, ec );
}
if( lockFd < 0 )
{
fprintf( stderr, "Error: Another capture daemon is already using the output directory %s\n", outputDir.c_str() );
return 1;
}
const std::string pid = std::to_string( getpid() );
write( lockFd, pid.c_str(), pid.size() );
close( lockFd );
}
atexit( RemoveLockFile );
#endif
#ifdef _WIN32
signal( SIGINT, SigInt );
#else

View File

@@ -42,7 +42,11 @@ if(NOT COMMAND cpm_message)
endfunction()
endif()
set(CURRENT_CPM_VERSION 0.40.2)
if(DEFINED EXTRACTED_CPM_VERSION)
set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}")
else()
set(CURRENT_CPM_VERSION 0.43.1)
endif()
get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH)
if(CPM_DIRECTORY)
@@ -162,7 +166,7 @@ set(CPM_SOURCE_CACHE
CACHE PATH "Directory to download CPM dependencies"
)
if(NOT CPM_DONT_UPDATE_MODULE_PATH)
if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR)
set(CPM_MODULE_PATH
"${CMAKE_BINARY_DIR}/CPM_modules"
CACHE INTERNAL ""
@@ -198,9 +202,65 @@ function(cpm_package_name_from_git_uri URI RESULT)
endif()
endfunction()
# Find the shortest hash that can be used eg, if origin_hash is
# cccb77ae9609d2768ed80dd42cec54f77b1f1455 the following files will be checked, until one is found
# that is either empty (allowing us to assign origin_hash), or whose contents matches ${origin_hash}
#
# * .../cccb.hash
# * .../cccb77ae.hash
# * .../cccb77ae9609.hash
# * .../cccb77ae9609d276.hash
# * etc
#
# We will be able to use a shorter path with very high probability, but in the (rare) event that the
# first couple characters collide, we will check longer and longer substrings.
function(cpm_get_shortest_hash source_cache_dir origin_hash short_hash_output_var)
# for compatibility with caches populated by a previous version of CPM, check if a directory using
# the full hash already exists
if(EXISTS "${source_cache_dir}/${origin_hash}")
set(${short_hash_output_var}
"${origin_hash}"
PARENT_SCOPE
)
return()
endif()
foreach(len RANGE 4 40 4)
string(SUBSTRING "${origin_hash}" 0 ${len} short_hash)
set(hash_lock ${source_cache_dir}/${short_hash}.lock)
set(hash_fp ${source_cache_dir}/${short_hash}.hash)
# Take a lock, so we don't have a race condition with another instance of cmake. We will release
# this lock when we can, however, if there is an error, we want to ensure it gets released on
# it's own on exit from the function.
file(LOCK ${hash_lock} GUARD FUNCTION)
# Load the contents of .../${short_hash}.hash
file(TOUCH ${hash_fp})
file(READ ${hash_fp} hash_fp_contents)
if(hash_fp_contents STREQUAL "")
# Write the origin hash
file(WRITE ${hash_fp} ${origin_hash})
file(LOCK ${hash_lock} RELEASE)
break()
elseif(hash_fp_contents STREQUAL origin_hash)
file(LOCK ${hash_lock} RELEASE)
break()
else()
file(LOCK ${hash_lock} RELEASE)
endif()
endforeach()
set(${short_hash_output_var}
"${short_hash}"
PARENT_SCOPE
)
endfunction()
# Try to infer package name and version from a url
function(cpm_package_name_and_ver_from_url url outName outVer)
if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)")
if(url MATCHES
"[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|tar\\.xz|tar\\.zst|zip|ZIP)(\\?|/|$)"
)
# We matched an archive
set(filename "${CMAKE_MATCH_1}")
@@ -269,10 +329,25 @@ endfunction()
# finding the system library
function(cpm_create_module_file Name)
if(NOT CPM_DONT_UPDATE_MODULE_PATH)
# erase any previous modules
file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake
"include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)"
)
if(DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR)
# Redirect find_package calls to the CPM package. This is what FetchContent does when you set
# OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for find_package in CONFIG
# mode, unlike the Find${Name}.cmake fallback. CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined
# in script mode, or in CMake < 3.24.
# https://cmake.org/cmake/help/latest/module/FetchContent.html#fetchcontent-find-package-integration-examples
string(TOLOWER ${Name} NameLower)
file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config.cmake
"include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" OPTIONAL)\n"
"include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n"
)
file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake
"set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" "set(PACKAGE_VERSION_EXACT TRUE)\n"
)
else()
file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake
"include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)"
)
endif()
endif()
endfunction()
@@ -475,7 +550,7 @@ function(cpm_add_patches)
# Find the patch program.
find_program(PATCH_EXECUTABLE patch)
if(WIN32 AND NOT PATCH_EXECUTABLE)
if(CMAKE_HOST_WIN32 AND NOT PATCH_EXECUTABLE)
# The Windows git executable is distributed with patch.exe. Find the path to the executable, if
# it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe.
find_package(Git QUIET)
@@ -575,14 +650,6 @@ endfunction()
function(CPMAddPackage)
cpm_set_policies()
list(LENGTH ARGN argnLength)
if(argnLength EQUAL 1)
cpm_parse_add_package_single_arg("${ARGN}" ARGN)
# The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM
set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;")
endif()
set(oneValueArgs
NAME
FORCE
@@ -605,10 +672,26 @@ function(CPMAddPackage)
set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES)
list(LENGTH ARGN argnLength)
# Parse single shorthand argument
if(argnLength EQUAL 1)
cpm_parse_add_package_single_arg("${ARGN}" ARGN)
# The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM
set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;")
# Parse URI shorthand argument
elseif(argnLength GREATER 1 AND "${ARGV0}" STREQUAL "URI")
list(REMOVE_AT ARGN 0 1) # remove "URI gh:<...>@version#tag"
cpm_parse_add_package_single_arg("${ARGV1}" ARGV0)
set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}")
endif()
cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
# Set default values for arguments
if(NOT DEFINED CPM_ARGS_VERSION)
if(DEFINED CPM_ARGS_GIT_TAG)
cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION)
@@ -685,10 +768,23 @@ function(CPMAddPackage)
return()
endif()
if(NOT DEFINED CPM_${CPM_ARGS_NAME}_SOURCE AND DEFINED ENV{CPM_${CPM_ARGS_NAME}_SOURCE})
# Normalize separators to support Windows paths when reading from environment variables.
file(TO_CMAKE_PATH "$ENV{CPM_${CPM_ARGS_NAME}_SOURCE}" CPM_${CPM_ARGS_NAME}_SOURCE)
message(WARNING "${CPM_INDENT} '${CPM_ARGS_NAME}' version overridden by environment variable "
"CPM_${CPM_ARGS_NAME}_SOURCE='${CPM_${CPM_ARGS_NAME}_SOURCE}'"
)
endif()
# Check for manual overrides
if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "")
set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE})
set(CPM_${CPM_ARGS_NAME}_SOURCE "")
if(NOT DEFINED ENV{CPM_${CPM_ARGS_NAME}_SOURCE})
message(WARNING "${CPM_INDENT} '${CPM_ARGS_NAME}' version overridden by CMake variable "
"CPM_${CPM_ARGS_NAME}_SOURCE='${PACKAGE_SOURCE}'"
)
endif()
CPMAddPackage(
NAME "${CPM_ARGS_NAME}"
SOURCE_DIR "${PACKAGE_SOURCE}"
@@ -779,9 +875,19 @@ function(CPMAddPackage)
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY})
elseif(CPM_USE_NAMED_CACHE_DIRECTORIES)
string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG")
cpm_get_shortest_hash(
"${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory
"${origin_hash}" # Input hash
origin_hash # Computed hash
)
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME})
else()
string(SHA1 origin_hash "${origin_parameters}")
cpm_get_shortest_hash(
"${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory
"${origin_hash}" # Input hash
origin_hash # Computed hash
)
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash})
endif()
# Expand `download_directory` relative path. This is important because EXISTS doesn't work for
@@ -848,7 +954,9 @@ function(CPMAddPackage)
endif()
endif()
cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")")
if(NOT "${DOWNLOAD_ONLY}")
cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")")
endif()
if(CPM_PACKAGE_LOCK_ENABLED)
if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK)
@@ -869,8 +977,9 @@ function(CPMAddPackage)
# Calling FetchContent_MakeAvailable will then internally forward these options to
# add_subdirectory. Up until these changes, we had to call FetchContent_Populate and
# add_subdirectory separately, which is no longer necessary and has been deprecated as of 3.30.
# A Bug in CMake prevents us to use the non-deprecated functions until 3.30.3.
set(fetchContentDeclareExtraArgs "")
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0")
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3")
if(${CPM_ARGS_EXCLUDE_FROM_ALL})
list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL)
endif()
@@ -896,7 +1005,7 @@ function(CPMAddPackage)
if(CPM_SOURCE_CACHE AND download_directory)
file(LOCK ${download_directory}/../cmake.lock RELEASE)
endif()
if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.28.0")
if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.30.3")
cpm_add_subdirectory(
"${CPM_ARGS_NAME}"
"${DOWNLOAD_ONLY}"
@@ -1098,7 +1207,7 @@ function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated)
string(TOLOWER "${PACKAGE}" lower_case_name)
if(NOT ${lower_case_name}_POPULATED)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0")
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3")
if(DOWNLOAD_ONLY)
# MakeAvailable will call add_subdirectory internally which is not what we want when
# DOWNLOAD_ONLY is set. Populate will only download the dependency without adding it to the
@@ -1147,6 +1256,7 @@ function(cpm_parse_option OPTION)
else()
math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1")
string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE)
string(STRIP "${OPTION_VALUE}" OPTION_VALUE)
endif()
set(OPTION_KEY
"${OPTION_KEY}"

View File

@@ -11,7 +11,7 @@ function(add_git_ref target)
if(Git_FOUND)
add_custom_target(git-ref
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp.tmp
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} log -1 "--format=namespace tracy { static inline const char* GitRef = %x22%h%x22; }" ${GIT_REV} >> GitRef.hpp.tmp || echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp.tmp
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} log -1 "--format=namespace tracy { static inline const char* GitRef = %x22%h%x22; }" ${GIT_REV} >> GitRef.hpp.tmp || ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp.tmp
COMMAND ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
VERBATIM

View File

@@ -30,15 +30,19 @@ else()
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15")
message(FATAL_ERROR "Apple Clang 15 or newer is required.")
elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "16")
elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "16" AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "13.3")
# AppleClang 15 has issues with to_chars in <chrono> if target is too old
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-mmacosx-version-min=13.3>)
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.3")
endif()
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fexperimental-library>)
endif()
message(STATUS "macOS deployment target: ${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
if(WIN32)

View File

@@ -1,12 +0,0 @@
diff --git a/extra_symbols.txt b/extra_symbols.txt
index b95bb58..6b8f616 100644
--- a/extra_symbols.txt
+++ b/extra_symbols.txt
@@ -1,3 +1,7 @@
+glCompressedTexImage2D
+GL_LINEAR_MIPMAP_LINEAR
+GL_TEXTURE_WRAP_S
+GL_TEXTURE_WRAP_T
glReadPixels
glClearColor
glClear

View File

@@ -0,0 +1,13 @@
diff --git i/imconfig.h w/imconfig.h
index b40db3898..561ab7782 100644
--- i/imconfig.h
+++ w/imconfig.h
@@ -42,7 +42,7 @@
//#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_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
//#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)

View File

@@ -0,0 +1,13 @@
diff --git i/imconfig.h w/imconfig.h
index b40db3898..f7f864394 100644
--- i/imconfig.h
+++ w/imconfig.h
@@ -50,7 +50,7 @@
//#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().
-//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
+#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT_BITMAP // Disable default embedded bitmap font (ProggyClean). Remove ~9 KB from output binary. AddFontDefaultBitmap() will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT_VECTOR // Disable default embedded vector font (ProggyForever), Remove ~14 KB from output binary. AddFontDefaultVector() will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available

View File

@@ -0,0 +1,14 @@
diff --git a/imgui_tables.cpp b/imgui_tables.cpp
--- a/imgui_tables.cpp
+++ b/imgui_tables.cpp
@@ -4082,7 +4082,9 @@
}
if (load_flags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
- else
+ else if (column_settings->Index != -1)
+ // 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)
column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1);

88
cmake/manual.cmake Normal file
View File

@@ -0,0 +1,88 @@
set(PANDOC_VERSION 3.11)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
find_program(TRACY_PANDOC pandoc)
if(TRACY_PANDOC)
execute_process(
COMMAND ${TRACY_PANDOC} --version
OUTPUT_VARIABLE _pandoc_version
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _pandoc_rc
ERROR_QUIET)
string(REGEX MATCH "pandoc [0-9.]+" _pandoc_version "${_pandoc_version}")
if(NOT _pandoc_rc EQUAL 0 OR NOT _pandoc_version STREQUAL "pandoc ${PANDOC_VERSION}")
set(TRACY_PANDOC "")
endif()
endif()
if(TRACY_PANDOC)
message(STATUS "Using system pandoc ${PANDOC_VERSION}: ${TRACY_PANDOC}")
else()
if(WIN32)
if(NOT CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
endif()
set(_pandoc_asset pandoc-${PANDOC_VERSION}-windows-x86_64.zip)
set(_pandoc_hash 2ab72baf2399450e148ddf7a2a8689806c42e1bba71862b57e220fd9b8456d3d)
set(_pandoc_exe pandoc-${PANDOC_VERSION}/pandoc.exe)
elseif(APPLE)
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-arm64-macOS.zip)
set(_pandoc_hash 15806bedf9517bfead72e88fe6a6696635c3691efbb6e152173440e9c5bb50b4)
set(_pandoc_exe pandoc-${PANDOC_VERSION}-arm64/bin/pandoc)
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-x86_64-macOS.zip)
set(_pandoc_hash 3b1c1b57f160112c821d02f23d946ede8b7f57a6ccf4632a25a512d334a9291f)
set(_pandoc_exe pandoc-${PANDOC_VERSION}-x86_64/bin/pandoc)
else()
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
endif()
else()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-linux-arm64.tar.gz)
set(_pandoc_hash 56ed5566ec41d22ec9ee0704e6ac0b98ba102e92384efd5306173a22d314c79a)
set(_pandoc_exe pandoc-${PANDOC_VERSION}/bin/pandoc)
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz)
set(_pandoc_hash 37edb3bbcf722f921a009941bf5874e2e0c09263226c9b4a2d980788cb062ab6)
set(_pandoc_exe pandoc-${PANDOC_VERSION}/bin/pandoc)
else()
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
endif()
endif()
# Shared cache: $PANDOC_CACHE_DIR, else the platform user cache directory.
if(DEFINED ENV{PANDOC_CACHE_DIR})
set(_pandoc_cache $ENV{PANDOC_CACHE_DIR})
elseif(WIN32)
set(_pandoc_cache $ENV{LOCALAPPDATA}/tracy/pandoc)
elseif(APPLE)
set(_pandoc_cache $ENV{HOME}/Library/Caches/tracy/pandoc)
elseif(DEFINED ENV{XDG_CACHE_HOME})
set(_pandoc_cache $ENV{XDG_CACHE_HOME}/tracy/pandoc)
else()
set(_pandoc_cache $ENV{HOME}/.cache/tracy/pandoc)
endif()
set(TRACY_PANDOC ${_pandoc_cache}/${_pandoc_exe})
if(NOT EXISTS ${TRACY_PANDOC})
# Concurrent configures on a cold cache can collide; worst case the
# hash check fails and a rerun succeeds.
message(STATUS "Downloading pandoc ${PANDOC_VERSION} (${_pandoc_asset})")
file(MAKE_DIRECTORY ${_pandoc_cache})
file(DOWNLOAD
https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/${_pandoc_asset}
${_pandoc_cache}/pandoc.archive
EXPECTED_HASH SHA256=${_pandoc_hash}
STATUS _pandoc_download)
list(GET _pandoc_download 0 _pandoc_download_rc)
if(NOT _pandoc_download_rc STREQUAL "0")
file(REMOVE ${_pandoc_cache}/pandoc.archive)
message(FATAL_ERROR "Failed to download pandoc ${PANDOC_VERSION}: ${_pandoc_download}")
endif()
file(ARCHIVE_EXTRACT
INPUT ${_pandoc_cache}/pandoc.archive
DESTINATION ${_pandoc_cache})
file(REMOVE ${_pandoc_cache}/pandoc.archive)
endif()
endif()

View File

@@ -0,0 +1,203 @@
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d6a9edc..abef8d7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,18 +32,18 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
if(NFD_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED wayland-client)
message(STATUS "Using Wayland version: ${WAYLAND_VERSION}")
- set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml)
+ set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml)
add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
- COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
+ COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
- COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
+ COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
- list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c)
+ list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c)
endif()
endif()
diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp
index 76fdcd7..cec691d 100644
--- a/src/nfd_gtk.cpp
+++ b/src/nfd_gtk.cpp
@@ -433,17 +433,17 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
#if defined(NFD_WAYLAND)
void DestroyXdgExported(void* context) {
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
}
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
GdkWindow* childWindow = static_cast<GdkWindow*>(context);
// GDK doesn't modify the string, even though it takes char*, so we can cast away the constness
gdk_wayland_window_set_transient_for_exported(childWindow, const_cast<char*>(handle));
}
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
- &zxdg_exported_v1_handle};
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
+ &zxdg_exported_v2_handle};
#endif
// This is an RAII class that wraps the parenting of a GtkWidget (the file dialog).
@@ -645,17 +645,17 @@ struct NativeWindowParenter {
#if defined(NFD_WAYLAND)
void SetParentWayland(GdkWindow* childWindow) {
- if (wayland_display && wayland_xdg_exporter_v1) {
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindowHandle));
+ if (wayland_display && wayland_xdg_exporter_v2) {
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindowHandle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
return;
}
- zxdg_exported_v1_add_listener(
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(childWindow));
+ zxdg_exported_v2_add_listener(
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(childWindow));
wl_display_roundtrip(wayland_display);
- zxdg_exported_v1_set_user_data(exported, nullptr);
+ zxdg_exported_v2_set_user_data(exported, nullptr);
destroy.fn = &DestroyXdgExported;
destroy.context = static_cast<void*>(exported);
}
diff --git a/src/nfd_linux_shared.hpp b/src/nfd_linux_shared.hpp
index 1413bac..e826f1e 100644
--- a/src/nfd_linux_shared.hpp
+++ b/src/nfd_linux_shared.hpp
@@ -14,7 +14,7 @@
#ifdef NFD_WAYLAND
#include <wayland-client.h>
-#include "xdg-foreign-unstable-v1.h"
+#include "xdg-foreign-unstable-v2.h"
#endif
namespace {
@@ -81,9 +81,9 @@ nfdnchar_t* emit_case_insensitive_glob(const nfdnchar_t* begin,
#ifdef NFD_WAYLAND
struct wl_display* wayland_display;
struct wl_registry* wayland_registry;
-uint32_t wayland_xdg_exporter_v1_name;
-struct zxdg_exporter_v1* wayland_xdg_exporter_v1;
-constexpr const char* XDG_EXPORTER_V1 = "zxdg_exporter_v1";
+uint32_t wayland_xdg_exporter_v2_name;
+struct zxdg_exporter_v2* wayland_xdg_exporter_v2;
+constexpr const char* XDG_EXPORTER_V2 = "zxdg_exporter_v2";
#endif
void EmptyFn(void*) {}
@@ -103,19 +103,19 @@ void registry_handle_global(void* context,
uint32_t version) {
(void)context;
(void)version;
- if (strcmp(interface, XDG_EXPORTER_V1) == 0) {
- wayland_xdg_exporter_v1_name = name;
- wayland_xdg_exporter_v1 = static_cast<struct zxdg_exporter_v1*>(wl_registry_bind(
- registry, name, &zxdg_exporter_v1_interface, zxdg_exporter_v1_interface.version));
+ if (strcmp(interface, XDG_EXPORTER_V2) == 0) {
+ wayland_xdg_exporter_v2_name = name;
+ wayland_xdg_exporter_v2 = static_cast<struct zxdg_exporter_v2*>(wl_registry_bind(
+ registry, name, &zxdg_exporter_v2_interface, zxdg_exporter_v2_interface.version));
}
}
void registry_handle_global_remove(void* context, struct wl_registry* registry, uint32_t name) {
(void)context;
(void)registry;
- if (wayland_xdg_exporter_v1 && name == wayland_xdg_exporter_v1_name) {
- zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
- wayland_xdg_exporter_v1 = nullptr;
+ if (wayland_xdg_exporter_v2 && name == wayland_xdg_exporter_v2_name) {
+ zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
+ wayland_xdg_exporter_v2 = nullptr;
}
}
@@ -128,7 +128,7 @@ void NFD_Wayland_Init(void) {
void NFD_Wayland_Quit(void) {
if (wayland_display) {
- if (wayland_xdg_exporter_v1) zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
+ if (wayland_xdg_exporter_v2) zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
wl_registry_destroy(wayland_registry);
}
}
@@ -143,7 +143,7 @@ nfdresult_t NFD_SetWaylandDisplay(wl_display* display) {
wayland_display = display;
if (wayland_display) {
wayland_registry = wl_display_get_registry(wayland_display);
- wayland_xdg_exporter_v1 = nullptr;
+ wayland_xdg_exporter_v2 = nullptr;
// seems like registry can't be null
wl_registry_add_listener(wayland_registry, &wayland_registry_listener, nullptr);
wl_display_roundtrip(wayland_display);
diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp
index df840e0..e66ca7f 100644
--- a/src/nfd_portal.cpp
+++ b/src/nfd_portal.cpp
@@ -148,10 +148,10 @@ constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";
constexpr const char* WAYLAND_PREFIX = "wayland:";
void DestroyXdgExported(void* context) {
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
}
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
DBusMessageIter& iter = *static_cast<DBusMessageIter*>(context);
const size_t handle_len = strlen(handle);
const size_t prefix_len = strlen(WAYLAND_PREFIX);
@@ -163,8 +163,8 @@ void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char
NFDi_Free(buf);
}
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
- &zxdg_exported_v1_handle};
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
+ &zxdg_exported_v2_handle};
#endif
void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
@@ -194,18 +194,18 @@ void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
#endif
#ifdef NFD_WAYLAND
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
- if (wayland_display && wayland_xdg_exporter_v1) {
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindow.handle));
+ if (wayland_display && wayland_xdg_exporter_v2) {
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindow.handle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
return;
}
- zxdg_exported_v1_add_listener(
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(&iter));
+ zxdg_exported_v2_add_listener(
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(&iter));
wl_display_roundtrip(wayland_display);
- zxdg_exported_v1_set_user_data(exported, nullptr);
+ zxdg_exported_v2_set_user_data(exported, nullptr);
destroy.fn = &DestroyXdgExported;
destroy.context = static_cast<void*>(exported);
return;

13
cmake/util.cmake Normal file
View File

@@ -0,0 +1,13 @@
set(TRACY_UTIL_DIR ${CMAKE_CURRENT_LIST_DIR}/../util)
set(TRACY_UTIL_SOURCES
CaptureConsole.cpp
CaptureFileBackup.cpp
CaptureOutput.cpp
)
list(TRANSFORM TRACY_UTIL_SOURCES PREPEND "${TRACY_UTIL_DIR}/")
add_library(TracyUtil STATIC EXCLUDE_FROM_ALL ${TRACY_UTIL_SOURCES})
target_link_libraries(TracyUtil PUBLIC TracyServer)
target_include_directories(TracyUtil PUBLIC ${TRACY_UTIL_DIR})

View File

@@ -8,11 +8,20 @@ set (ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../")
include(FindPkgConfig)
include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake)
if(APPLE AND BUNDLE)
set(DLOPT ON)
else()
set(DLOPT OFF)
endif()
option(DOWNLOAD_CAPSTONE "Force download capstone" ON)
option(DOWNLOAD_GLFW "Force download glfw" OFF)
option(DOWNLOAD_FREETYPE "Force download freetype" OFF)
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
option(DOWNLOAD_PUGIXML "Force download pugixml" OFF)
if(VENDOR_GUI)
option(DOWNLOAD_GLFW "Force download glfw" ${DLOPT})
option(DOWNLOAD_FREETYPE "Force download freetype" ${DLOPT})
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
option(DOWNLOAD_PUGIXML "Force download pugixml" ${DLOPT})
endif()
# capstone
@@ -26,7 +35,7 @@ else()
CPMAddPackage(
NAME capstone
GITHUB_REPOSITORY capstone-engine/capstone
GIT_TAG 6.0.0-Alpha9
GIT_TAG 6.0.0-Alpha10
OPTIONS
"CAPSTONE_X86_ATT_DISABLE ON"
"CAPSTONE_ALPHA_SUPPORT OFF"
@@ -58,52 +67,6 @@ else()
target_link_libraries(TracyCapstone INTERFACE capstone_static)
endif()
# GLFW
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
pkg_check_modules(GLFW glfw3)
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
add_library(TracyGlfw3 INTERFACE)
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.4
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyGlfw3 INTERFACE)
target_link_libraries(TracyGlfw3 INTERFACE glfw)
endif()
endif()
# freetype
pkg_check_modules(FREETYPE freetype2)
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
add_library(TracyFreetype INTERFACE)
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-3
OPTIONS
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyFreetype INTERFACE)
target_link_libraries(TracyFreetype INTERFACE freetype)
endif()
# Zstd
CPMAddPackage(
@@ -132,66 +95,6 @@ set(GETOPT_HEADERS ${GETOPT_DIR}/getopt.h)
add_library(TracyGetOpt STATIC EXCLUDE_FROM_ALL ${GETOPT_SOURCES} ${GETOPT_HEADERS})
target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
# ImGui
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.8-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
)
set(IMGUI_SOURCES
imgui_widgets.cpp
imgui_draw.cpp
imgui_demo.cpp
imgui.cpp
imgui_tables.cpp
misc/freetype/imgui_freetype.cpp
backends/imgui_impl_opengl3.cpp
)
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
target_link_libraries(TracyImGui PUBLIC TracyFreetype)
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
find_package(X11 REQUIRED)
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
endif()
# NFD
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
if(GTK_FILESELECTOR)
set(NFD_PORTAL OFF)
else()
set(NFD_PORTAL ON)
endif()
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG v1.3.0
EXCLUDE_FROM_ALL TRUE
OPTIONS
"NFD_PORTAL ${NFD_PORTAL}"
)
endif()
# PPQSort
CPMAddPackage(
@@ -213,95 +116,226 @@ CPMAddPackage(
EXCLUDE_FROM_ALL TRUE
)
# md4c
if(VENDOR_GUI)
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
OPTIONS
"BUILD_SHARED_LIBS OFF"
EXCLUDE_FROM_ALL TRUE
)
# GLFW
if(NOT EMSCRIPTEN)
# base64
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
CPMAddPackage(
NAME base64
GITHUB_REPOSITORY aklomp/base64
GIT_TAG v0.5.2
OPTIONS
"BASE64_BUILD_CLI OFF"
"BASE64_WITH_OpenMP OFF"
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
# tidy
CPMAddPackage(
NAME tidy
GITHUB_REPOSITORY htacg/tidy-html5
GIT_TAG 5.8.0
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
EXCLUDE_FROM_ALL TRUE
)
# usearch
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.25.2
EXCLUDE_FROM_ALL TRUE
)
# pugixml
pkg_check_modules(PUGIXML pugixml)
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
add_library(TracyPugixml INTERFACE)
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.16
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
target_link_libraries(TracyPugixml INTERFACE pugixml)
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
pkg_check_modules(GLFW glfw3)
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
add_library(TracyGlfw3 INTERFACE)
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.5.1
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyGlfw3 INTERFACE)
target_link_libraries(TracyGlfw3 INTERFACE glfw)
endif()
endif()
# libcurl
# freetype
pkg_check_modules(LIBCURL libcurl>=7.87.0)
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
add_library(TracyLibcurl INTERFACE)
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
pkg_check_modules(FREETYPE freetype2)
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
add_library(TracyFreetype INTERFACE)
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_20_0
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-3
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"
"HTTP_ONLY ON"
"CURL_ZSTD OFF"
"CURL_USE_LIBPSL OFF"
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
"FT_DISABLE_ZLIB ON"
"FT_DISABLE_BZIP2 ON"
"FT_DISABLE_PNG ON"
"FT_DISABLE_BROTLI ON"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyLibcurl INTERFACE)
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
add_library(TracyFreetype INTERFACE)
target_link_libraries(TracyFreetype INTERFACE freetype)
endif()
# ImGui
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.9b-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-default-font.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-macos-clipboard.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-phantom-column.patch"
)
set(IMGUI_SOURCES
imgui_widgets.cpp
imgui_draw.cpp
imgui_demo.cpp
imgui.cpp
imgui_tables.cpp
misc/freetype/imgui_freetype.cpp
backends/imgui_impl_opengl3.cpp
)
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
target_link_libraries(TracyImGui PUBLIC
TracyFreetype
${CMAKE_DL_LIBS}
)
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
find_package(X11 REQUIRED)
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
endif()
if(APPLE)
target_link_libraries(TracyImGui PUBLIC "-framework ApplicationServices")
endif()
# NFD
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
if(GTK_FILESELECTOR)
set(NFD_PORTAL OFF)
else()
set(NFD_PORTAL ON)
endif()
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG 7bbbd9fe6b1d1549b41df138f614d1a44df9ba08
EXCLUDE_FROM_ALL TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/nfd-xdg-foreign-v2.patch"
OPTIONS
"BUILD_SHARED_LIBS OFF"
"NFD_PORTAL ${NFD_PORTAL}"
)
endif()
# md4c
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 00788b157b2b6ef3efd8659b66f6521229f382d7
OPTIONS
"BUILD_SHARED_LIBS OFF"
EXCLUDE_FROM_ALL TRUE
)
if(NOT EMSCRIPTEN)
# base64
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
CPMAddPackage(
NAME base64
GITHUB_REPOSITORY aklomp/base64
GIT_TAG v0.5.2
OPTIONS
"BASE64_BUILD_CLI OFF"
"BASE64_WITH_OpenMP OFF"
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
# tidy
CPMAddPackage(
NAME tidy
GITHUB_REPOSITORY htacg/tidy-html5
GIT_TAG 5.8.0
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
EXCLUDE_FROM_ALL TRUE
)
# usearch
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.26.2
EXCLUDE_FROM_ALL TRUE
)
# pugixml
pkg_check_modules(PUGIXML pugixml)
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
add_library(TracyPugixml INTERFACE)
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.16
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
target_link_libraries(TracyPugixml INTERFACE pugixml)
endif()
# libcurl
pkg_check_modules(LIBCURL libcurl>=7.87.0)
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
add_library(TracyLibcurl INTERFACE)
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_22_0
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"
"HTTP_ONLY ON"
"CURL_ZSTD OFF"
"CURL_USE_LIBPSL OFF"
"CURL_USE_LIBSSH2 OFF"
"CURL_BROTLI OFF"
"USE_NGHTTP2 OFF"
"USE_LIBIDN2 OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyLibcurl INTERFACE)
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
endif()
endif()
endif()

View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -26,17 +26,17 @@ void print_usage_exit(int e)
fprintf(stderr, "Usage:\n");
fprintf(stderr, " extract [OPTION...] <trace file>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -h, --help Print usage\n");
fprintf(stderr, " -V, --version Show version information\n");
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
fprintf(stderr, " -c, --case Case sensitive filtering\n");
fprintf(stderr, " -e, --self Get self times\n");
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
fprintf(stderr, " -m, --messages Report only messages\n");
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
fprintf(stderr, " -t, --truncated_mean arg Report truncated mean (arg is the percentile. Default is 90)\n");
fprintf(stderr, " -h, --help Print usage\n");
fprintf(stderr, " -V, --version Show version information\n");
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
fprintf(stderr, " -c, --case Case sensitive filtering\n");
fprintf(stderr, " -e, --self Get self times\n");
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
fprintf(stderr, " -m, --messages Report only messages\n");
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
fprintf(stderr, " -t, --truncated_mean[=arg] Report truncated mean (arg is the percentile. Default is 90)\n");
exit(e);
}
@@ -66,8 +66,8 @@ Args parse_args(int argc, char** argv)
struct option long_opts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "filter", optional_argument, NULL, 'f' },
{ "sep", optional_argument, NULL, 's' },
{ "filter", required_argument, NULL, 'f' },
{ "sep", required_argument, NULL, 's' },
{ "case", no_argument, NULL, 'c' },
{ "self", no_argument, NULL, 'e' },
{ "unwrap", no_argument, NULL, 'u' },
@@ -79,7 +79,7 @@ Args parse_args(int argc, char** argv)
};
int c;
while ((c = getopt_long(argc, argv, "hf:s:ceugmpV", long_opts, NULL)) != -1)
while ((c = getopt_long(argc, argv, "hf:s:t:ceugmpV", long_opts, NULL)) != -1)
{
switch (c)
{

View File

@@ -10,11 +10,19 @@ set(CMAKE_COLOR_DIAGNOSTICS ON)
include(cmake/CPM.cmake)
if(TRACY_ENABLE)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
add_compile_options(-fno-omit-frame-pointer)
else()
message(WARNING "Tracy builds should be RelWithDebInfo!")
endif()
endif()
CPMAddPackage(
NAME glad
VERSION 2.0.8
GIT_REPOSITORY https://github.com/Dav1dde/glad.git
GIT_TAG glad2
GIT_TAG v2.0.8
)
add_subdirectory(${glad_SOURCE_DIR}/cmake ${CMAKE_CURRENT_BINARY_DIR}/glad)

View File

@@ -2,8 +2,8 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.42.3)
set(CPM_HASH_SUM "a609e875fd532b067174250f6abbc3dac22fe2d64869783fb1e80bda1625c844")
set(CPM_DOWNLOAD_VERSION 0.43.1)
set(CPM_HASH_SUM "1c40fc102ce9625d7de7eb14f541cab30cc3138dca627f0b0ec40293ce6c2934")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")

View File

@@ -24,6 +24,7 @@ namespace
struct TracySection
{
explicit TracySection( const char* name ) { Enter( name ); }
explicit TracySection( const char* name, uint16_t category ) : idx( TracySectionEnterCategory( category, "%s", name ) ) {}
~TracySection() { Leave(); }
void Enter( const char* name )
@@ -52,7 +53,7 @@ bool help = false;
// escape, or reaching the exit) and control should return to the caller.
bool level_loop( World& world )
{
TracySection section( ( std::string( "Level " ) + world.name() ).c_str() );
TracySection section( ( std::string( "Level " ) + world.name() ).c_str(), 1 );
Player* p = world.player();

View File

@@ -28,6 +28,9 @@ int main( int /*argc*/, char* /*argv*/[] )
{
TracyNoop;
TracySectionSetup( 0, "Game state" );
TracySectionSetup( 1, "Level progression" );
if( !dyna::Init::all() )
return 1;

View File

@@ -35,7 +35,7 @@ if(NOT APPLE)
set(ONLY_LIBS ON CACHE BOOL "" FORCE)
FetchContent_Declare(glew
GIT_REPOSITORY https://github.com/Perlmint/glew-cmake.git
GIT_TAG master # pin to a specific commit for reproducible builds
GIT_TAG glew-cmake-2.3.1
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glew)

View File

@@ -2,7 +2,20 @@
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/tracy">
<comment>Tracy Profiler trace file</comment>
<comment xml:lang="ar">ملف تتبع لمحلل الأداء Tracy</comment>
<comment xml:lang="de">Trace-Datei des Tracy-Profilers</comment>
<comment xml:lang="es">Archivo de traza del perfilador Tracy</comment>
<comment xml:lang="fr">Fichier de trace du profileur Tracy</comment>
<comment xml:lang="hi">Tracy प्रोफ़ाइलर ट्रेस फ़ाइल</comment>
<comment xml:lang="it">File di traccia del profiler Tracy</comment>
<comment xml:lang="ja">Tracy プロファイラーのトレースファイル</comment>
<comment xml:lang="pl">Zrzut sesji profilera Tracy</comment>
<comment xml:lang="pt">Ficheiro de rastreio do perfilador Tracy</comment>
<comment xml:lang="pt_BR">Arquivo de rastreamento do perfilador Tracy</comment>
<comment xml:lang="ru">Файл трассировки профилировщика Tracy</comment>
<comment xml:lang="uk">Файл трасування профілювальника Tracy</comment>
<comment xml:lang="zh_CN">Tracy 性能分析器跟踪文件</comment>
<comment xml:lang="zh_TW">Tracy 效能分析器追蹤檔案</comment>
<icon name="application-tracy"/>
<magic>
<match type="string" value="tlZ\x04" offset="0"/>

View File

@@ -1,12 +1,53 @@
[Desktop Entry]
Version=1.0
Version=1.5
Type=Application
Name=Tracy Profiler
GenericName=Code profiler
GenericName[ar]=محلل أداء الشيفرة البرمجية
GenericName[de]=Code-Profiler
GenericName[es]=Perfilador de código
GenericName[fr]=Profileur de code
GenericName[hi]=कोड प्रोफ़ाइलर
GenericName[it]=Profiler del codice
GenericName[ja]=コードプロファイラー
GenericName[pl]=Profiler kodu
GenericName[pt]=Analisador de desempenho de código
GenericName[pt_BR]=Analisador de desempenho de código
GenericName[ru]=Профилировщик кода
GenericName[uk]=Профілювальник коду
GenericName[zh_CN]=代码性能分析器
GenericName[zh_TW]=程式碼效能分析器
Comment=Examine code to see where it is slow
Comment[pl]=Znajdowanie wolno wykonującego się kodu
Exec=/usr/bin/tracy %f
Comment[ar]=افحص الشيفرة البرمجية لمعرفة مواضع البطء
Comment[de]=Code untersuchen, um langsame Stellen zu finden
Comment[es]=Examine el código para ver dónde es lento
Comment[fr]=Examiner le code pour voir où il est lent
Comment[hi]=कोड की जाँच करें कि वह कहाँ धीमा है
Comment[it]=Esamina il codice per vedere dove è lento
Comment[ja]=コードを調べて遅い箇所を見つける
Comment[pl]=Przeanalizuj kod, aby znaleźć wolne miejsca
Comment[pt]=Examine o código para ver onde é lento
Comment[pt_BR]=Examine o código para ver onde ele é lento
Comment[ru]=Изучите код, чтобы найти медленные места
Comment[uk]=Досліджуйте код, щоб знайти повільні місця
Comment[zh_CN]=检查代码,找出运行缓慢的部分
Comment[zh_TW]=檢查程式碼,找出執行緩慢的部分
Keywords=profiler;performance;tracing;frame;instrumentation;
Keywords[ar]=محلل;أداء;تتبع;إطار;قياس;
Keywords[de]=Profiler;Leistung;Tracing;Frame;Instrumentierung;
Keywords[es]=perfilador;rendimiento;trazado;fotograma;instrumentación;
Keywords[fr]=profileur;performance;traçage;trame;instrumentation;
Keywords[hi]=प्रोफ़ाइलर;प्रदर्शन;ट्रेसिंग;फ़्रेम;इंस्ट्रुमेंटेशन;
Keywords[it]=profiler;prestazioni;tracciamento;frame;strumentazione;
Keywords[ja]=プロファイラー;パフォーマンス;トレース;フレーム;計測;
Keywords[pl]=profiler;wydajność;śledzenie;klatka;instrumentacja;
Keywords[pt]=perfilador;desempenho;rastreio;fotograma;instrumentação;
Keywords[pt_BR]=perfilador;desempenho;rastreamento;quadro;instrumentação;
Keywords[ru]=профилировщик;производительность;трассировка;кадр;инструментирование;
Keywords[uk]=профілювальник;продуктивність;трасування;кадр;інструментування;
Keywords[zh_CN]=分析器;性能;跟踪;帧;插桩;
Keywords[zh_TW]=分析器;效能;追蹤;影格;插樁;
Exec=tracy-profiler %f
Icon=tracy
Terminal=false
Categories=Development;Profiling;

View File

@@ -23,9 +23,13 @@ data surface. Common entry points:
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
- Sections: `get_sections()` — timed code sections from
`TracySectionEnter`/`TracySectionLeave` instrumentation. Returns a list of
`{start, end, text}` dicts (start/end in ns).
`{category, start, end, text}` dicts (start/end in ns); `category` is the
section's named group (multiple unrelated section sets can coexist in one
capture).
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
- Readiness: `is_background_done()` — see "Instance lifecycle" below before
querying zone/symbol stats right after `load_capture`.
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
@@ -72,3 +76,40 @@ stats = ctx.get_zone_stats(sid)
For long-running queries pass `async_mode=True` to `eval`; it returns
`{task_id, status: "running"}`. Poll with the `task` tool
(`action="poll", task_id=...`).
## Instance lifecycle
`tracy_mcp.py` runs as a long-lived singleton process shared across every
MCP client (all VS Code windows), and every `live_connect`/`load_capture`
instance holds its *entire* trace — zones, messages, callstacks, memory
events — resident in memory for as long as that process runs. Nothing
frees it just because your conversation ends.
- `load_capture` returns before zone/symbol statistics finish building — a
background thread populates them after the file itself is read. Check
`is_background_done()` (or `background_done` in `list_instances`) before
relying on `get_all_zone_stats()` and similar stats-based queries; they
can return empty or partial results until it's true. For small captures
this finishes before you'd notice; for multi-GB ones it can take seconds.
- Call `unload_capture(instance_id)` as soon as you're done analyzing a
capture. Don't rely on automatic eviction as your primary cleanup path —
treat it as a backstop for forgotten sessions, not a substitute.
- `live_connect` caps a live session at `TRACY_MCP_LIVE_MEMORY_LIMIT_MB`
(default 8192, override per-call or set 0 to disable) — a long-lived
session on a busy target otherwise grows unbounded and can OOM-kill the
whole server process. Hitting the cap disconnects that instance cleanly;
data collected so far stays queryable and `save_trace`-able.
- `list_instances` reports `idle_seconds`, `connected`, and `background_done`
per instance; use it to spot stale ones before starting a new session,
especially if you're about to load several captures for comparison.
- The server provides three backstops, all configurable via environment
variables at startup: it evicts the least-recently-used *evictable*
instance (disconnected live sessions, or any loaded file capture — never
a still-connected live one) once the instance count reaches
`TRACY_MCP_MAX_INSTANCES` (default 4); it drops a disconnected live
instance that's sat idle for `TRACY_MCP_DISCONNECTED_TTL_S` (default
1800s / 30 min) so a session is still there for analysis right after the
target disconnects, but doesn't linger forever if forgotten; and it drops
a file-loaded capture that's sat idle for `TRACY_MCP_FILE_IDLE_TTL_S`
(default 1800s) — safe to let expire since it's already durably on disk,
just `load_capture` it again.

View File

@@ -0,0 +1 @@
mcp>=2.0.0,<3

View File

@@ -1,6 +1,8 @@
#!/bin/sh
# Start the Tracy MCP server.
#
# First run: pip install -r requirements.txt (next to this script).
#
# Set PYTHONPATH to the directory containing TracyServerBindings.so/.pyd.
# Adjust the Release/Debug suffix to match your CMake build configuration.
PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$(dirname "$0")/../../build/python/Release"

View File

@@ -5,6 +5,7 @@ import asyncio
import atexit
import builtins
import concurrent.futures
import faulthandler
import glob
import io
import os
@@ -14,10 +15,12 @@ import socket
import struct
import sys
import time
import urllib.error
import urllib.request
import uuid
from contextlib import redirect_stdout
from contextlib import asynccontextmanager, redirect_stdout
import mcp.server.fastmcp as fastmcp
from mcp.server.mcpserver import MCPServer
# Suppress noisy ASGI shutdown errors known to occur with SSE and Control-C.
# These occur when Starlette attempts to send a 500 error after the loop is cancelled
@@ -29,8 +32,13 @@ logging.getLogger("starlette").setLevel(logging.CRITICAL)
_HERE = os.path.dirname(os.path.abspath(__file__))
_PORT_FILE = os.path.join(_HERE, "tracy_mcp.port")
_PID_FILE = os.path.join(_HERE, "tracy_mcp.pid")
_CRASH_LOG_FILE = os.path.join(_HERE, "tracy_mcp.crash.log")
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
_TRANSPORT = os.environ.get("TRACY_MCP_TRANSPORT", "streamable-http").strip().lower()
# MCPServer.run()'s own defaults -- tracked here too since v2's Settings no
# longer exposes them for us to read back (SDK v1 -> v2 migration).
_SSE_PATH = "/sse"
_STREAMABLE_HTTP_PATH = "/mcp"
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
# system prompt; exposing it as an MCP resource keeps analysis guidance in
@@ -156,10 +164,26 @@ async def _listen_broadcasts(timeout_s: float = 1.5) -> list[dict]:
return list(seen.values())
def _is_our_server_running() -> tuple[bool, int]:
def _http_ping(port: int, timeout_s: float = 2.0) -> bool:
"""Confirms the server answers HTTP, not just that its PID exists — a
deadlocked process still passes os.kill(pid, 0). Any response, even an
error one, proves the transport is alive; only a timeout or refused
connection means it's not.
"""
Check the PID file to see if our server is already running.
Returns (running, port). Uses os.kill(pid, 0) to confirm the process is alive.
path = _SSE_PATH if _TRANSPORT == "sse" else _STREAMABLE_HTTP_PATH
try:
urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout=timeout_s)
return True
except urllib.error.HTTPError:
return True
except Exception:
return False
def _is_our_server_running() -> tuple[bool, int]:
"""Check whether our server is running: PID alive AND responding to
an HTTP self-ping (a hung process still passes os.kill(pid, 0)).
Returns (running, port).
"""
try:
with open(_PID_FILE) as f:
@@ -167,9 +191,19 @@ def _is_our_server_running() -> tuple[bool, int]:
with open(_PORT_FILE) as f:
port = int(f.read().strip())
os.kill(pid, 0) # raises OSError if process is gone
return True, port
except Exception:
return False, 0
if not _http_ping(port):
print(
f"Tracy MCP process {pid} is alive (PID check passed) but isn't "
f"responding on port {port} within {2.0:.0f}s -- likely hung/deadlocked, "
f"not just busy. Starting a fresh server on a new port; kill PID {pid} "
f"manually once you're able to (it's still holding the port and the "
f"TracyServerBindings.pyd file lock).",
file=sys.stderr,
)
return False, 0
return True, port
def _find_free_port() -> int:
@@ -219,8 +253,11 @@ except ImportError:
except ImportError:
tracy_server = None
mcp_server = fastmcp.FastMCP("Tracy Profiler")
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
_MAX_INSTANCES = int(os.environ.get("TRACY_MCP_MAX_INSTANCES", "4"))
_DISCONNECTED_TTL_S = float(os.environ.get("TRACY_MCP_DISCONNECTED_TTL_S", "1800"))
_FILE_IDLE_TTL_S = float(os.environ.get("TRACY_MCP_FILE_IDLE_TTL_S", "1800"))
_SWEEP_INTERVAL_S = float(os.environ.get("TRACY_MCP_SWEEP_INTERVAL_S", "300"))
_DEFAULT_LIVE_MEMORY_LIMIT_MB = int(os.environ.get("TRACY_MCP_LIVE_MEMORY_LIMIT_MB", "8192"))
class Task:
@@ -240,7 +277,126 @@ class TracyInstance:
self.worker = worker
self.path = None
self.mtime = None
self.last_used = time.time()
# Wall-clock time the instance was first observed disconnected (live
# instances only). None while connected or for file-loaded captures.
self.disconnected_since: float | None = None
def touch(self) -> None:
self.last_used = time.time()
def is_evictable(self) -> bool:
"""True if this instance is safe to drop without losing in-progress data.
A live instance still connected is actively recording — never evict it
automatically. Everything else (file captures, disconnected live
instances) is fair game for the LRU cap.
"""
if self.worker is not None and self.path is None:
try:
return not self.worker.is_connected()
except Exception:
return True
return True
def _shutdown_worker(worker: object | None) -> None:
if worker is None:
return
try:
worker.shutdown()
except Exception:
pass
def _evict_idle() -> list[str]:
"""Unload instances idle past their TTL: disconnected live instances
past _DISCONNECTED_TTL_S, file-loaded captures past _FILE_IDLE_TTL_S
(safe — already durably on disk). Connected live instances are never
touched here.
"""
now = time.time()
evicted = []
for name, inst in list(instances.items()):
if inst.path is not None:
if now - inst.last_used >= _FILE_IDLE_TTL_S:
del instances[name]
_shutdown_worker(inst.worker)
evicted.append(name)
continue
if inst.worker is None:
continue
try:
connected = inst.worker.is_connected()
except Exception:
connected = False
if connected:
inst.disconnected_since = None
continue
if inst.disconnected_since is None:
inst.disconnected_since = now
continue
if now - inst.disconnected_since >= _DISCONNECTED_TTL_S:
del instances[name]
_shutdown_worker(inst.worker)
evicted.append(name)
return evicted
def _evict_for_capacity(exclude: str | None = None) -> str | None:
"""If at/over _MAX_INSTANCES, drop the least-recently-used evictable
instance to make room for a new one. Returns the evicted name, if any."""
if len(instances) < _MAX_INSTANCES:
return None
candidates = [
inst for name, inst in instances.items()
if name != exclude and inst.is_evictable()
]
if not candidates:
return None
victim = min(candidates, key=lambda inst: inst.last_used)
del instances[victim.name]
_shutdown_worker(victim.worker)
return victim.name
async def _sweep_loop() -> None:
"""Periodic eviction sweep, doubling as a heartbeat — distinguishes a
crashed process (see faulthandler) from a hung one (stops producing
these lines). flush=True since a hang is exactly when buffering would
hide the last known-good timestamp.
"""
start = time.time()
while True:
await asyncio.sleep(_SWEEP_INTERVAL_S)
try:
evicted = _evict_idle()
except Exception:
evicted = None
print(
f"[heartbeat] uptime={int(time.time() - start)}s "
f"instances={len(instances)} tasks={len(tasks)} "
f"evicted={evicted or 'none'}",
file=sys.stderr,
flush=True,
)
@asynccontextmanager
async def _lifespan(_server: MCPServer):
sweep_task = asyncio.create_task(_sweep_loop())
try:
yield
finally:
sweep_task.cancel()
try:
await sweep_task
except asyncio.CancelledError:
pass
mcp_server = MCPServer("Tracy Profiler", lifespan=_lifespan)
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
instances: dict[str, TracyInstance] = {}
tasks: dict[str, Task] = {}
@@ -273,13 +429,34 @@ async def list_captures() -> list[str]:
@mcp_server.tool()
async def list_instances() -> list[dict]:
"""List all loaded Tracy instances and captures with metadata."""
"""List all loaded Tracy instances and captures with metadata.
Each full trace stays resident in memory for as long as this server
process runs (it's a singleton shared across all MCP clients) until
unloaded. `idle_seconds` is how long since this instance was last used
via `eval`/`save_trace` — call `unload_capture` on ones you no longer
need instead of waiting for automatic eviction. Automatic eviction kicks
in at the `TRACY_MCP_MAX_INSTANCES` cap (LRU, connected live instances
are never evicted), after a disconnected live instance sits idle past
`TRACY_MCP_DISCONNECTED_TTL_S`, or after a file-loaded capture sits idle
past `TRACY_MCP_FILE_IDLE_TTL_S` — it's already durably on disk, so
nothing is lost; just `load_capture` it again.
`background_done: false` means a just-loaded capture is still building
zone/symbol statistics on a background thread — `get_all_zone_stats`
and similar stats-based `eval` queries can return empty or partial
results until this flips to true.
"""
now = time.time()
return [
{
"id": name,
"path": inst.path,
"mtime": inst.mtime,
"live": inst.path is None
"live": inst.path is None,
"connected": inst.worker.is_connected() if inst.path is None and inst.worker else None,
"background_done": inst.worker.is_background_done() if inst.worker else None,
"idle_seconds": now - inst.last_used,
}
for name, inst in instances.items()
]
@@ -311,11 +488,25 @@ async def discover_instances(port_range: str = "8086-8095") -> list[dict]:
@mcp_server.tool()
async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str | None = None) -> str:
async def live_connect(
address: str = "127.0.0.1",
port: int = 8086,
alias: str | None = None,
memory_limit_mb: int | None = None,
) -> str:
"""
Connect to a live running Tracy-instrumented application.
Wraps Worker(addr, port, memoryLimit=-1). Returns the instance_id.
Wraps Worker(addr, port, memoryLimit). A long-lived session on a busy
target grows unbounded otherwise -- every zone/message/memory event
stays resident until disconnect, which can OOM-kill this whole process
rather than just the one instance. memory_limit_mb defaults to
TRACY_MCP_LIVE_MEMORY_LIMIT_MB (8192 if unset); pass 0 to disable.
Hitting the limit disconnects that Worker cleanly (Tracy's own
QueryTerminate path) -- already-collected data stays queryable and
save_trace-able, it just stops growing.
Returns the instance_id.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
@@ -337,8 +528,10 @@ async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str
f"the target against a matching Tracy version."
)
limit_mb = _DEFAULT_LIVE_MEMORY_LIMIT_MB if memory_limit_mb is None else memory_limit_mb
memory_limit = limit_mb * 1024 * 1024 if limit_mb > 0 else -1
try:
w = tracy_server.Worker(address, port)
w = tracy_server.Worker(address, port, memory_limit)
except Exception as e:
return f"Failed to connect: {str(e)}"
@@ -382,9 +575,14 @@ async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str
)
name = alias or f"live_{address}_{port}"
if name in instances:
_shutdown_worker(instances[name].worker)
evicted = _evict_for_capacity(exclude=name)
instances[name] = TracyInstance(name, w)
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
limit_note = f"{limit_mb}MB memory limit" if limit_mb > 0 else "no memory limit"
return (
f"Connected to live instance as '{name}'. "
f"Connected to live instance as '{name}'{note} ({limit_note}). "
f"Before your first eval, read resources tracy://prompt "
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
f"ns time units, srcloc IDs)."
@@ -404,6 +602,12 @@ async def load_capture(path: str, alias: str | None = None) -> str:
If you don't already have a path, call `list_captures` first — it lists
.tracy files in the TRACY_CAPTURES_DIR environment directory.
Loading returns before zone/symbol statistics finish building — a
background thread populates them after the file itself is read. Check
`background_done` in `list_instances` (or poll it) before relying on
`get_all_zone_stats` and similar stats-based `eval` queries; they can
return empty or partial results until it's true.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
@@ -418,7 +622,11 @@ async def load_capture(path: str, alias: str | None = None) -> str:
if name in instances:
inst = instances[name]
if inst.path == path and inst.mtime == mtime:
inst.touch()
return f"Instance '{name}' is already loaded and up to date."
_shutdown_worker(inst.worker)
evicted = _evict_for_capacity(exclude=name)
f = tracy_server.open_file(path)
w = tracy_server.create_worker_from_file(f)
@@ -426,8 +634,9 @@ async def load_capture(path: str, alias: str | None = None) -> str:
inst.path = path
inst.mtime = mtime
instances[name] = inst
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
return (
f"Loaded as '{name}'. "
f"Loaded as '{name}'{note}. "
f"Before your first eval, read resources tracy://prompt "
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
f"ns time units, srcloc IDs)."
@@ -476,6 +685,7 @@ async def save_trace(
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
instance.touch()
if not os.path.isabs(path):
if captures_dir and os.path.basename(path) == path:
@@ -542,9 +752,18 @@ async def _execute_save(worker: object, path: str, level: int, streams: int, fi_
@mcp_server.tool()
async def unload_capture(instance_id: str) -> str:
"""Unload a Tracy instance and release its memory."""
"""Unload a Tracy instance and release its memory.
Prefer calling this as soon as you're done analyzing a capture — every
loaded instance holds the *entire* trace (zones, messages, callstacks,
memory events) in memory for as long as this server process runs, and
the server is a long-lived singleton shared across all MCP clients.
Automatic eviction exists as a backstop (see `list_instances`), not a
substitute for unloading what you no longer need.
"""
if instance_id in instances:
del instances[instance_id]
inst = instances.pop(instance_id)
_shutdown_worker(inst.worker)
return f"Instance '{instance_id}' unloaded."
return f"Instance '{instance_id}' not found."
@@ -566,6 +785,7 @@ async def tracy_eval(code: str, instance_id: str, async_mode: bool = False) -> o
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
instance.touch()
if not async_mode:
return await _execute_eval(code, instance.worker)
@@ -675,6 +895,13 @@ async def shutdown_server() -> str:
if __name__ == "__main__":
# Enabled before anything touches the native bindings, so a genuine
# fatal crash (segfault etc.) writes a thread-state traceback to disk
# before the process dies — works on Windows via
# SetUnhandledExceptionFilter, same mechanism as POSIX signals.
_crash_log_fh = open(_CRASH_LOG_FILE, "a", encoding="utf-8")
faulthandler.enable(file=_crash_log_fh, all_threads=True)
atexit.register(_cleanup_pid_files)
if _TRANSPORT not in ("sse", "streamable-http"):
@@ -696,17 +923,18 @@ if __name__ == "__main__":
port = _find_free_port()
_write_pid_and_port(port)
path = (
mcp_server.settings.sse_path
if _TRANSPORT == "sse"
else mcp_server.settings.streamable_http_path
)
path = _SSE_PATH if _TRANSPORT == "sse" else _STREAMABLE_HTTP_PATH
print(f"Tracy MCP listening on http://127.0.0.1:{port}{path}", file=sys.stderr)
mcp_server.settings.host = "127.0.0.1"
mcp_server.settings.port = port
# v2's MCPServer takes transport options on run(), not the constructor
# or a mutable settings object (SDK v1 -> v2 migration).
run_kwargs = {"host": "127.0.0.1", "port": port}
if _TRANSPORT == "sse":
run_kwargs["sse_path"] = _SSE_PATH
else:
run_kwargs["streamable_http_path"] = _STREAMABLE_HTTP_PATH
try:
mcp_server.run(transport=_TRANSPORT)
mcp_server.run(transport=_TRANSPORT, **run_kwargs)
except KeyboardInterrupt:
print("\nTracy MCP server stopped.", file=sys.stderr)
sys.exit(0)

23
extra/tracy-version.py Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# Derive the project version from the single source of truth:
# public/common/TracyVersion.hpp. Prints X.Y.Z, exits nonzero on failure.
import re
import sys
header = sys.argv[1]
try:
with open(header, encoding='utf-8') as f:
text = f.read()
except OSError as exc:
print('tracy-version: cannot read %s: %s' % (header, exc), file=sys.stderr)
sys.exit(1)
numbers = {}
for name in ('Major', 'Minor', 'Patch'):
match = re.search(r'%s\s*=\s*(\d+)' % name, text)
if not match:
print('tracy-version: could not parse version from %s' % header, file=sys.stderr)
sys.exit(1)
numbers[name] = match.group(1)
print('%s.%s.%s' % (numbers['Major'], numbers['Minor'], numbers['Patch']))

View File

@@ -1,13 +0,0 @@
#!/bin/sh
version_header="../public/common/TracyVersion.hpp"
major=$(grep -o -E 'Major = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
minor=$(grep -o -E 'Minor = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
patch=$(grep -o -E 'Patch = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
version="${major}.${minor}.${patch}"
# the extension is required for macOS's outdated sed
sed -i.bak "s/version: '[0-9]*\.[0-9]*\.[0-9]*'/version: '$version'/g" ../meson.build
rm ../meson.build.bak

31
import/CMakePresets.json Normal file
View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -1 +0,0 @@
The LaTeX source file (tracy.tex) and the resulting PDF file (tracy.pdf) are the only authorative version of the user manual. Do NOT modify the Markdown user manual (tracy.md) by hand. It is only meant to be updated via the latex2md.sh script.

View File

@@ -1,35 +0,0 @@
/\\begin\{bclogo\}\[/ {
in_bclogo = 1
bclogo_type = ""
next
}
in_bclogo && /logo=/ {
if (/\\bcbombe/) bclogo_type = "bcbombe"
else if (/\\bcattention/) bclogo_type = "bcattention"
else if (/\\bclampe/) bclogo_type = "bclampe"
else if (/\\bcquestion/) bclogo_type = "bcquestion"
next
}
in_bclogo && /noborder|couleur/ {
next
}
in_bclogo {
line = $0
sub(/^[ \t]*\]?\{/, "", line)
sub(/\}.*$/, "", line)
bclogo_title = line
if (bclogo_type == "bcbombe") prefix = "IMPORTANT"
else if (bclogo_type == "bcattention") prefix = "CAUTION"
else if (bclogo_type == "bclampe") prefix = "TIP"
else prefix = "NOTE"
printf "\\begin{quote}\\textbf{%s:%s}\\par\n", prefix, bclogo_title
in_bclogo = 0
next
}
/\\end\{bclogo\}/ {
printf "\\end{quote}\n"
next
}
{ print }

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""Replace Font Awesome icon macros in LaTeX with Unicode codepoints."""
import re
import sys
def pascal_to_snake(name):
"""Convert PascalCase to UPPER_SNAKE_CASE."""
result = name[0]
for i in range(1, len(name)):
if name[i].isupper() and name[i - 1].islower():
result += '_'
result += name[i]
return result.upper()
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <header_path> <tex_path>", file=sys.stderr)
sys.exit(1)
header_path = sys.argv[1]
tex_path = sys.argv[2]
# Parse header: ICON_FA_SNAKE_CASE -> Unicode char
icons = {}
with open(header_path) as f:
for line in f:
m = re.match(
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
)
if m:
snake = m.group(1)
parts = snake.split('_')
pascal = ''.join(p.capitalize() for p in parts)
codepoint = int(m.group(3), 16)
icons[pascal] = chr(codepoint)
# Read tex file
with open(tex_path) as f:
text = f.read()
# Find all \faXxx used in the text (uppercase first letter excludes \fancyhead etc.)
used = set()
for m in re.finditer(r'\\fa([A-Z][a-zA-Z0-9]*)', text):
used.add(m.group(1))
# Replace each used icon, longest names first to avoid prefix conflicts
for name in sorted(used, key=lambda n: (-len(n), n)):
if name not in icons:
print(f"Warning: \\fa{name} not found in header", file=sys.stderr)
continue
char = icons[name]
# Order matters: more specific patterns first
text = text.replace(f'\\fa{name}{{}}~', f'{char} ')
text = text.replace(f'\\fa{name}{{}}', char)
text = text.replace(f'\\fa{name}~', f'{char} ')
text = text.replace(f'\\fa{name}', char)
# Write back
with open(tex_path, 'w') as f:
f.write(text)
if __name__ == '__main__':
main()

View File

@@ -1,77 +0,0 @@
#!/usr/bin/env python3
"""Append icon legend blocks to each markdown section containing Font Awesome icons."""
import re
import sys
def _extract_icons(lines):
"""Return deduplicated icon chars from lines, in order of first appearance."""
seen = set()
icons = []
for line in lines:
for ch in line:
cp = ord(ch)
if 0xE000 <= cp <= 0xF8FF and ch not in seen:
seen.add(ch)
icons.append(ch)
return icons
def _append_legend(result_lines, icons, icon_names):
"""Append a legend block for the given icons."""
result_lines.append('')
result_lines.append('-----')
result_lines.append('')
for ch in icons:
name = icon_names.get(ch, f'Unknown(U+{ord(ch):04X})')
result_lines.append(f'{ch} - {name} icon')
result_lines.append('')
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <header_path> <md_path>", file=sys.stderr)
sys.exit(1)
header_path = sys.argv[1]
md_path = sys.argv[2]
# Build char -> name mapping from header
icon_names = {}
with open(header_path) as f:
for line in f:
m = re.match(
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
)
if m:
snake = m.group(1)
parts = snake.split('_')
pascal = ' '.join(p.capitalize() for p in parts)
codepoint = int(m.group(3), 16)
icon_names[chr(codepoint)] = pascal
with open(md_path, encoding='utf-8') as f:
lines = f.read().split('\n')
# Build chunk boundaries: header lines and EOF
chunk_starts = [i for i, line in enumerate(lines) if line.startswith('#')]
# Also add index 0 as a chunk start if there's pre-header content
if chunk_starts and chunk_starts[0] > 0:
chunk_starts.insert(0, 0)
result_lines = []
for ci, start in enumerate(chunk_starts):
end = chunk_starts[ci + 1] if ci + 1 < len(chunk_starts) else len(lines)
icons = _extract_icons(lines[start:end])
result_lines.extend(lines[start:end])
if icons:
_append_legend(result_lines, icons, icon_names)
with open(md_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(result_lines))
if __name__ == '__main__':
main()

233
manual/latex2md.py Normal file
View File

@@ -0,0 +1,233 @@
#!/usr/bin/env python3
"""Generate the Markdown user manual from the LaTeX source.
Cross-platform replacement for the previous sh/sed/awk pipeline. pandoc is
invoked as a subprocess; its Lua filter (filter.lua, next to the .tex file)
stays with pandoc.
"""
import argparse
import os
import re
import subprocess
import sys
PANDOC_MARKDOWN = 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables'
NAMES = (
('quicklook', 'A quick look at Tracy Profiler'),
('firststeps', 'First steps'),
('client', 'Client markup'),
('capturing', 'Capturing the data'),
('analyzingdata', 'Analyzing captured data'),
('tracyassist', 'Tracy Assist'),
('csvexport', 'Exporting zone statistics to CSV'),
('importingdata', 'Importing external profiling data'),
('configurationfiles', 'Configuration files'),
)
def parse_icon_header(path):
"""Return (pascal_name -> char, char -> 'Word Word') maps from the icon header."""
fa = {}
names = {}
with open(path, encoding='utf-8') as f:
for line in f:
m = re.match(
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
)
if m:
parts = m.group(1).split('_')
ch = chr(int(m.group(3), 16))
fa[''.join(p.capitalize() for p in parts)] = ch
names[ch] = ' '.join(p.capitalize() for p in parts)
return fa, names
def substitute_icons(text, fa):
used = set(re.findall(r'\\fa([A-Z][a-zA-Z0-9]*)', text))
for name in sorted(used, key=lambda n: (-len(n), n)):
if name not in fa:
print(f"Warning: \\fa{name} not found in header", file=sys.stderr)
continue
ch = fa[name]
text = text.replace(f'\\fa{name}{{}}~', f'{ch} ')
text = text.replace(f'\\fa{name}{{}}', ch)
text = text.replace(f'\\fa{name}~', f'{ch} ')
text = text.replace(f'\\fa{name}', ch)
return text
def pre_sed(text):
text = text.replace('\\menu[,]', '')
text = text.replace('\\keys', '')
text = text.replace('\\ctrl', 'Ctrl')
text = text.replace('\\shift', 'Shift')
text = text.replace('\\Alt', 'Alt')
text = text.replace('\\del', 'Delete')
return text
def post_sed(text):
for m in ('LMB', 'MMB', 'RMB', 'Scroll'):
text = text.replace(f'\\{m}{{}}~', '')
text = text.replace('\\textsigma', 'σ')
for l in 'abc':
text = text.replace(f'@\\circled{{{l}}}@', f'({l})')
for l in 'abc':
text = text.replace(f'\\circled{{{l}}}', f'({l})')
text = text.replace('@\\ldots@', '')
text = re.sub(r',?escapeinside=\{\}\{\}', '', text)
for label, name in NAMES:
text = text.replace(f'\\nameref{{{label}}}', name)
return text
def bclogo2quote(text):
"""Convert \\begin{bclogo} admonition blocks to \\begin{quote}\\textbf{PREFIX:...}."""
prefix = {'bcbombe': 'IMPORTANT', 'bcattention': 'CAUTION', 'bclampe': 'TIP'}
out = []
in_bclogo = False
bclogo_type = ''
for line in text.split('\n'):
if not in_bclogo and r'\begin{bclogo}[' in line:
in_bclogo = True
bclogo_type = ''
continue
if in_bclogo and 'logo=' in line:
if r'\bcbombe' in line:
bclogo_type = 'bcbombe'
elif r'\bcattention' in line:
bclogo_type = 'bcattention'
elif r'\bclampe' in line:
bclogo_type = 'bclampe'
elif r'\bcquestion' in line:
bclogo_type = 'bcquestion'
continue
if in_bclogo and ('noborder' in line or 'couleur' in line):
continue
if in_bclogo:
title = re.sub(r'\}.*$', '', re.sub(r'^[ \t]*\]?\{', '', line))
out.append(f"\\begin{{quote}}\\textbf{{{prefix.get(bclogo_type, 'NOTE')}:{title}}}\\par")
in_bclogo = False
continue
if r'\end{bclogo}' in line:
out.append('\\end{quote}')
continue
out.append(line)
return '\n'.join(out)
def run_pandoc(pandoc, filter_path, tex):
args = [
pandoc,
'--wrap=none',
'--reference-location=block',
'--number-sections',
'-f', 'latex',
'-t', PANDOC_MARKDOWN,
'-s',
'-L', filter_path,
'-',
]
proc = subprocess.run(args, input=tex.encode('utf-8'), capture_output=True)
if proc.returncode != 0:
sys.stderr.write(proc.stderr.decode('utf-8', 'replace'))
sys.exit(proc.returncode)
return proc.stdout.decode('utf-8')
def tablecaption(text):
"""Pandoc emits table captions as ": ..." lines; italicize them instead."""
out = []
incap = False
for line in text.split('\n'):
if not incap and line.startswith(': '):
incap = True
line = '_' + line[2:]
if incap and not line.endswith('\\'):
line = re.sub(r' \{#[^{}]+\}$', '', line)
out.append(line + '_')
incap = False
continue
out.append(line)
return '\n'.join(out)
def admonitions(text):
for kind in ('IMPORTANT', 'TIP', 'CAUTION', 'NOTE'):
text = re.sub(
rf'^> \*\*{kind}:([^*]*)\*\*',
f'> [!{kind}]\n> **\\1**',
text,
flags=re.M,
)
return text
def icon_explain(text, names):
"""Append an icon legend to each top-level section containing FA icons."""
lines = text.split('\n')
chunk_starts = [i for i, line in enumerate(lines) if line.startswith('#')]
if chunk_starts and chunk_starts[0] > 0:
chunk_starts.insert(0, 0)
out = []
for ci, start in enumerate(chunk_starts):
end = chunk_starts[ci + 1] if ci + 1 < len(chunk_starts) else len(lines)
chunk = lines[start:end]
icons = []
seen = set()
for line in chunk:
for ch in line:
cp = ord(ch)
if 0xE000 <= cp <= 0xF8FF and ch not in seen:
seen.add(ch)
icons.append(ch)
out.extend(chunk)
if icons:
out.append('')
out.append('-----')
out.append('')
for ch in icons:
out.append(f"{ch} - {names.get(ch, f'Unknown(U+{ord(ch):04X})')} icon")
out.append('')
return '\n'.join(out)
def main():
ap = argparse.ArgumentParser(description='Generate the Markdown user manual')
ap.add_argument('--tex', required=True, help='path to tracy.tex')
ap.add_argument('--out', required=True, help='path of the generated tracy.md')
ap.add_argument('--pandoc', required=True, help='path to the pandoc executable')
ap.add_argument('--icons', required=True, help='path to IconsFontAwesome7.h')
args = ap.parse_args()
fa, names = parse_icon_header(args.icons)
with open(args.tex, encoding='utf-8') as f:
text = f.read()
text = pre_sed(text)
text = substitute_icons(text, fa)
text = post_sed(text)
text = bclogo2quote(text)
md = run_pandoc(
args.pandoc,
os.path.join(os.path.dirname(os.path.abspath(args.tex)), 'filter.lua'),
text,
)
md = tablecaption(md)
md = admonitions(md)
md = icon_explain(md, names)
out_dir = os.path.dirname(os.path.abspath(args.out))
if out_dir:
os.makedirs(out_dir, exist_ok=True)
with open(args.out, 'w', encoding='utf-8', newline='\n') as f:
f.write(md)
if __name__ == '__main__':
main()

View File

@@ -1,51 +0,0 @@
#!/bin/sh
cp -f tracy.tex _tmp.tex
sed -i -e 's@\\menu\[,\]@@g' _tmp.tex
sed -i -e 's@\\keys@@g' _tmp.tex
sed -i -e 's@\\ctrl@Ctrl@g' _tmp.tex
sed -i -e 's@\\shift@Shift@g' _tmp.tex
sed -i -e 's@\\Alt@Alt@g' _tmp.tex
sed -i -e 's@\\del@Delete@g' _tmp.tex
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome7.h _tmp.tex
sed -i -e 's@\\LMB{}~@@g' _tmp.tex
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
# Resolve \circled{} markers and lstlisting escapeinside (@...@) snippets, which
# pandoc would otherwise emit verbatim or drop, to their Unicode equivalents.
sed -i -e 's|@\\circled{a}@|(a)|g' -e 's|@\\circled{b}@|(b)|g' -e 's|@\\circled{c}@|(c)|g' _tmp.tex
sed -i -e 's|\\circled{a}|(a)|g' -e 's|\\circled{b}|(b)|g' -e 's|\\circled{c}|(c)|g' _tmp.tex
sed -i -e 's|@\\ldots@|…|g' _tmp.tex
sed -i -e 's@\\nameref{quicklook}@A quick look at Tracy Profiler@g' _tmp.tex
sed -i -e 's@\\nameref{firststeps}@First steps@g' _tmp.tex
sed -i -e 's@\\nameref{client}@Client markup@g' _tmp.tex
sed -i -e 's@\\nameref{capturing}@Capturing the data@g' _tmp.tex
sed -i -e 's@\\nameref{analyzingdata}@Analyzing captured data@g' _tmp.tex
sed -i -e 's@\\nameref{tracyassist}@Tracy Assist@g' _tmp.tex
sed -i -e 's@\\nameref{csvexport}@Exporting zone statistics to CSV@g' _tmp.tex
sed -i -e 's@\\nameref{importingdata}@Importing external profiling data@g' _tmp.tex
sed -i -e 's@\\nameref{configurationfiles}@Configuration files@g' _tmp.tex
awk -f bclogo2quote.awk _tmp.tex > _tmp_quoted.tex
mv _tmp_quoted.tex _tmp.tex
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -t 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables' -s _tmp.tex -o tracy.md
awk -f tablecaption.awk tracy.md > _tmp_caption.md
mv _tmp_caption.md tracy.md
sed -i -e 's/^> \*\*IMPORTANT:\([^*]*\)\*\*/> [!IMPORTANT]\
> **\1**/' tracy.md
sed -i -e 's/^> \*\*TIP:\([^*]*\)\*\*/> [!TIP]\
> **\1**/' tracy.md
sed -i -e 's/^> \*\*CAUTION:\([^*]*\)\*\*/> [!CAUTION]\
> **\1**/' tracy.md
sed -i -e 's/^> \*\*NOTE:\([^*]*\)\*\*/> [!NOTE]\
> **\1**/' tracy.md
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome7.h tracy.md
rm -f _tmp.tex

View File

@@ -1,16 +0,0 @@
# Pandoc emits table captions as a line beginning with ": ", which GitHub
# renders literally instead of as a caption. Strip the marker and italicize
# the caption instead. Captions may span several physical lines when they
# contain a hard line break (a trailing backslash). Underscores are used for
# the emphasis so captions that already contain "*...*" markup are left intact.
!incap && /^: / {
incap = 1
$0 = "_" substr($0, 3)
}
incap && !/\\$/ {
print $0 "_"
incap = 0
next
}
incap { print; next }
{ print }

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
\usepackage{nameref}
\usepackage{diagbox}
\usepackage{csquotes}
\usepackage{longtable}
\usepackage[hmarginratio=1:1,top=32mm,columnsep=20pt]{geometry} % Document margins
\geometry{a4paper,textwidth=6.5in,hmarginratio=1:1,
@@ -357,11 +358,19 @@ Tracy can be found at the following web addresses:
\subsubsection{Binary distribution}
The version releases of the profiler are provided as precompiled Windows binaries for download at \url{https://github.com/wolfpld/tracy/releases}, along with the user manual. You will need to install the latest Visual C++ redistributable package to use them.
Prebuilt binary releases of the profiler and the user manual can be found at \url{https://github.com/wolfpld/tracy/releases}. The following platforms are supported:
Development builds of Windows binaries, and the user manual are available as artifacts created by the automated Continuous Integration system on GitHub.
\begin{itemize}
\item \textbf{Windows} -- Requires the latest Visual C++ redistributable package to be installed.
\item \textbf{macOS} -- Needs manual quarantine handling before any provided binary can be executed, e.g.:
\begin{lstlisting}
xattr -d com.apple.quarantine tracy-profiler.app
\end{lstlisting}
Thanks, Tim Apple!
\item \textbf{Linux} -- AppImage with Ubuntu 24.04 used as a baseline. Requires Wayland and an implementation of an XDG desktop portal to be installed.
\end{itemize}
Note that these binary releases require AVX2 instruction set support on the processor. If you have an older CPU, you will need to set a proper instruction set architecture in the project properties and build the executables yourself.
Development builds of binaries and the user manual are available as artifacts created by the automated continuous integration system on GitHub.
\section{First steps}
\label{firststeps}
@@ -486,6 +495,78 @@ target_link_libraries(<TARGET> PUBLIC TracyClient)
While using \texttt{set(CMAKE\_INTERPROCEDURAL\_OPTIMIZATION ON)} is a convenient way to enable Link-Time Optimization (LTO) for an entire project, there are situations in which this may not work due to excessive compilation times, linking issues, compiler bugs, or other reasons.
For such cases, Tracy provides an option to enable Link-Time Optimization for itself using the \texttt{TRACY\_LTO} variable during the CMake configuration stage.
\subsubsection{Build options}
\label{buildoptions}
Table~\ref{cmakeoptions} lists the options you may want to set in the program you are profiling. All of them are boolean switches defaulting to \texttt{OFF}, with three exceptions: \texttt{TRACY\_STATIC}, which defaults to the opposite of \texttt{BUILD\_SHARED\_LIBS}, and \texttt{TRACY\_CALLSTACK} and \texttt{TRACY\_PLATFORM\_HEADER}, which take a value instead of a boolean.
Each enabled option is passed down to your program as a preprocessor macro of the same name. If you are not using CMake, you can achieve the same results by defining these macros yourself, as described in section~\ref{initialsetup}. Note that some of the listed options additionally affect how the client library itself is built, so they cannot be replaced with a macro definition alone.
\begin{footnotesize}
\begin{longtable}{@{}p{0.4\textwidth}p{0.56\textwidth}@{}}
\caption{Tracy CMake options}\label{cmakeoptions} \\
\hline
\textbf{Option} & \textbf{Description} \\ \hline
\endfirsthead
\hline
\endhead
\hline
\endfoot
\hline
\endlastfoot
\textbf{Basic setup} & \\
\texttt{TRACY\_ENABLE} & Enable profiling. Without it, the instrumentation is compiled out and the client does nothing (section~\ref{initialsetup}). \\
\texttt{TRACY\_ON\_DEMAND} & Collect data only while the profiler is connected, instead of starting at program launch (section~\ref{ondemand}). \\
\texttt{TRACY\_NO\_EXIT} & Don't let the client exit until all the profiling data has been sent to the server. Useful for short-lived applications. \\
\texttt{TRACY\_STATIC} & Build the client as a static (\texttt{ON}) or shared (\texttt{OFF}) library. Multi-DLL projects need a shared library (section~\ref{multidll}). \\
\texttt{TRACY\_LTO} & Enable link-time optimization of the client library itself. \\
& \\
\textbf{Networking} & \\
\texttt{TRACY\_NO\_BROADCAST} & Don't announce the client's presence on the local network. Manual connections are still possible. \\
\texttt{TRACY\_ONLY\_LOCALHOST} & Listen for connections only on the localhost interface. \\
\texttt{TRACY\_ONLY\_IPV4} & Accept connections only on IPv4 addresses, disabling IPv6 support. \\
& \\
\textbf{Data collection} & \\
\texttt{TRACY\_NO\_SYSTEM\_TRACING} & Prevent the client from accessing kernel data, which disables all system-level tracing (section~\ref{automated}). \\
\texttt{TRACY\_NO\_CONTEXT\_SWITCH} & Disable capture of context switch data (section~\ref{contextswitches}). \\
\texttt{TRACY\_NO\_SAMPLING} & Disable call stack sampling (section~\ref{sampling}). \\
\texttt{TRACY\_NO\_WAIT\_STACKS} & Disable capture of context switch call stacks, while keeping the context switch events themselves (section~\ref{waitstacks}). \\
\texttt{TRACY\_NO\_VSYNC\_CAPTURE} & Disable capture of hardware Vsync events (section~\ref{vsync}). \\
\texttt{TRACY\_NO\_CODE\_TRANSFER} & Disable retrieval of the program's machine code and source files (section~\ref{executableretrieval}). \\
\texttt{TRACY\_NO\_FRAME\_IMAGE} & Disable frame image support, along with the compression thread it needs (section~\ref{frameimages}). \\
\texttt{TRACY\_NO\_CRASH\_HANDLER} & Don't install the crash handler (section~\ref{crashhandling}). \\
\texttt{TRACY\_NO\_VERIFY} & Disable validation of the C API instrumentation correctness (section~\ref{capi}). \\
\texttt{TRACY\_FIBERS} & Enable fiber support, which is opt-in due to the additional cost it adds to zone capture (section~\ref{fibers}). \\
\texttt{TRACY\_IGNORE\_MEMORY\_FAULTS} & Don't report instrumentation failures for free events with no matching allocation, or for repeated allocations of the same address (section~\ref{memoryprofiling}). \\
\texttt{TRACY\_OPENGL\_AUTO\_CALIBRATION} & Periodically correct the OpenGL GPU/CPU clock drift, at the cost of a CPU/GPU synchronization each time (section~\ref{gpuprofiling}). \\
\texttt{TRACY\_ROCPROF\_CALIBRATION} & Periodically correct the ROCm GPU/CPU clock drift. Available only if rocprofiler-sdk was found (section~\ref{rocm}). \\
& \\
\textbf{Call stacks and symbols} & \\
\texttt{TRACY\_CALLSTACK} & Call stack capture depth, which makes the zone macros without the \texttt{S} postfix collect call stacks (section~\ref{collectingcallstacks}). \\
\texttt{TRACY\_NO\_CALLSTACK} & Disable all call stack related functionality (section~\ref{collectingcallstacks}). \\
\texttt{TRACY\_NO\_CALLSTACK\_INLINES} & Don't resolve inline frames, retrieval of which may be very slow on Windows. \\
\texttt{TRACY\_LIBUNWIND\_BACKTRACE} & Capture call stacks using libunwind, which may be faster than the default implementation. Links with libunwind. \\
\texttt{TRACY\_DEBUGINFOD} & Enable on-demand delivery of debugging symbols through debuginfod. Links with libdebuginfod. \\
\texttt{TRACY\_SYMBOL\_OFFLINE\_RESOLVE} & Record only image paths and offsets, leaving symbol resolution to be performed later by the \texttt{update} utility. \\
\texttt{TRACY\_LIBBACKTRACE\_ELF\_DYNLOAD\_SUPPORT} & Let libbacktrace resolve symbols in ELF objects that were loaded after the first symbol resolution took place. \\
\texttt{TRACY\_DEMANGLE} & Don't use the built-in symbol demangling function. You will have to provide your own \texttt{\_\_tracy\_demangle} implementation. \\
& \\
\textbf{Timers} & \\
\texttt{TRACY\_TIMER\_FALLBACK} & Compile in a lower resolution timer, to be used when the hardware timer is unavailable (table~\ref{timeroptions}). \\
\texttt{TRACY\_DISALLOW\_HW\_TIMER} & Never use the hardware timer, even if it \emph{appears} to be available. Requires \texttt{TRACY\_TIMER\_FALLBACK}. \\
& \\
\textbf{Special environments} & \\
\texttt{TRACY\_MANUAL\_LIFETIME} & Provide the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions for manual profiler lifetime management (section~\ref{multidll}). \\
\texttt{TRACY\_PLATFORM\_HEADER} & Path to a header providing the \texttt{TRACY\_HAS\_CUSTOM\_*} hooks for an unsupported platform (section~\ref{customplatform}). \\
\texttt{TRACY\_PATCHABLE\_NOPSLEDS} & Emit nopsleds in front of the timer reads, so that system-level tools such as \emph{rr} can patch them. \\
& \\
\textbf{Diagnostics} & \\
\texttt{TRACY\_VERBOSE} & Print detailed information about the detected features to the standard error output (section~\ref{troubleshooting}). \\
\texttt{TRACY\_NO\_INTERNAL\_MESSAGE} & Don't send the profiler's own diagnostic messages to the server (section~\ref{troubleshooting}). \\
\texttt{TRACY\_ASSERT} & Route the client's internal checks through a custom assert implementation, instead of the standard \texttt{assert}. Define \texttt{TRACY\_ASSERT(condition)} before including any Tracy header; the macro must accept a single condition argument. \\
\end{longtable}
\end{footnotesize}
\subsubsection{Meson integration}
If you are using the Meson build system, you can add Tracy using the Wrap dependency system. To do this, place the \texttt{tracy.wrap} file in the \texttt{subprojects} directory of your project, with the following content. The \texttt{head} \texttt{revision} field tracks Tracy's \texttt{master} branch. If you want to lock to a specific version of Tracy instead, you can just set the \texttt{revision} field to an appropriate git tag.
@@ -523,6 +604,8 @@ Here's a sample command to set up a build directory with profiling enabled. The
meson setup build --buildtype=debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
\end{lstlisting}
Most of the options listed in table~\ref{cmakeoptions} are also available in the Meson build. Their names are lowercased, with the \texttt{TRACY\_} prefix dropped, the only exception being \texttt{tracy\_enable}.
\subsubsection{Short-lived applications}
In case you want to profile a short-lived program (for example, a compression utility that finishes its work in one second), set the \texttt{TRACY\_NO\_EXIT} environment variable to $1$. With this option enabled, Tracy will not exit until an incoming connection is made, even if the application has already finished executing. If your platform doesn't support an easy setup of environment variables, you may also add the \texttt{TRACY\_NO\_EXIT} define to your build configuration, which has the same effect.
@@ -534,6 +617,8 @@ By default, Tracy will begin profiling even before the program enters the \textt
You should note that if on-demand profiling is \emph{disabled} (which is the default), then the recorded events will be stored in the system memory until a server connection is made and the data can be uploaded\footnote{This memory is never released, but the profiler reuses it for collection of other events.}. Depending on the amount of the things profiled, the requirements for event storage can quickly grow up to a couple of gigabytes. Furthermore, since this data is no longer available after the initial connection, you won't be able to perform a second connection to a client unless the on-demand mode is used.
In on-demand mode, each connection to the server is a separate capture. A zone is included in a capture only if both its begin and end events occur during that connection: if the connection is lost and re-established while a zone is open, the end event is dropped and the zone remains unclosed in the previous capture. Zones that begin and end during a later connection, even while an older zone is still open, appear in the new capture as top-level zones.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -557,6 +642,7 @@ If you need to use a specific Tracy client address, such as QNX requires, define
By default, the Tracy client will listen on IPv6 interfaces, falling back to IPv4 only if IPv6 is unavailable. If you want to restrict it to only listening on IPv4 interfaces, define the \texttt{TRACY\_ONLY\_IPV4} macro at compile-time, or set the \texttt{TRACY\_ONLY\_IPV4} environment variable to $1$ at runtime.
\subsubsection{Setup for multi-DLL projects}
\label{multidll}
Things are a bit different in projects that consist of multiple DLLs/shared objects. Compiling \texttt{TracyClient.cpp} into every DLL is not an option because this would result in several instances of Tracy objects lying around in the process. We instead need to pass their instances to the different DLLs to be reused there.
@@ -564,9 +650,9 @@ For that, you need a \emph{profiler DLL} to which your executable and the other
If you are targeting Windows with Microsoft Visual Studio or MinGW, add the \texttt{TRACY\_IMPORTS} define to your application.
If you are experiencing crashes or freezes when manually loading/unloading a separate DLL with Tracy integration, you might want to try defining both \texttt{TRACY\_DELAYED\_INIT} and \texttt{TRACY\_MANUAL\_LIFETIME} macros.
If you are experiencing crashes or freezes when manually loading/unloading a separate DLL with Tracy integration, you might want to try defining the \texttt{TRACY\_MANUAL\_LIFETIME} macro.
\texttt{TRACY\_DELAYED\_INIT} enables a path where profiler data is gathered into one structure and initialized on the first request rather than statically at the DLL load at the expense of atomic load on each request to the profiler data. \texttt{TRACY\_MANUAL\_LIFETIME} flag augments this behavior to provide manual \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions that allow you to create and destroy the profiler data manually. This manual management removes the need to do an atomic load on each call and lets you define an appropriate place to free the resources.
\texttt{TRACY\_MANUAL\_LIFETIME} provides the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions that allow you to create and destroy the profiler data manually, letting you define an appropriate place to free the resources. Under \texttt{TRACY\_MANUAL\_LIFETIME} the profiler does not exist until \texttt{StartupProfiler()} is called and ceases to exist after \texttt{ShutdownProfiler()}. Using any instrumentation, zones, locks, plots, messages, and so on before startup or after shutdown is an error. Use \texttt{TracyIsStarted} to guard instrumentation that may run conditionally. If the profiler has been initialized, it must be shut down before the program exits.
\begin{bclogo}[
noborder=true,
@@ -705,6 +791,7 @@ The platform header is intended only for the \texttt{TRACY\_HAS\_CUSTOM\_*} hook
\end{bclogo}
\subsubsection{Troubleshooting}
\label{troubleshooting}
By default, Tracy's diagnostics will be sent as Message logs (section~\ref{messagelog}) to the server.
Setting the \texttt{TRACY\_NO\_INTERNAL\_MESSAGE} define will disable this feature, but setting the \texttt{TRACY\_VERBOSE} will make the client print advanced information about the detected features to the standard error output. By matching those debug prints to the source code, you might be able to uncover why some of the features are missing on your platform.
@@ -717,6 +804,8 @@ Suppose for some reason you want to use another port\footnote{For example, other
If a custom port is not specified and the default listening port is already occupied, the profiler will automatically try to listen on a number of other ports.
Since the port in use may differ from the default one, the client application may query the actual data connection port using the \texttt{TracyPort} macro. It returns the port number once the listening socket has been bound, or zero otherwise.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -1474,6 +1563,32 @@ Exiting the profiled application from inside a zone is not supported. When the c
As a workaround, you may add a \texttt{try}/\texttt{catch} pair at the bottom of the function stack (for example in the \texttt{main()} function) and replace \texttt{exit()} calls with throwing a custom exception. When this exception is caught, you may call \texttt{exit()}, knowing that the application's data structures (including profiling zones) were properly cleaned up.
\subsection{Marking sections}
\label{markingsections}
To provide a high-level overview of what your program is doing, you may mark sections of execution. For example, in a puzzle game, the main menu would be a section, and each of the levels would be a section on its own.
To mark when a section starts, use the \texttt{TracySectionEnter(fmt, ...)} macro with printf-like formatting. The macro will return an \texttt{uint32\_t} identifier, which you must store and pass to the corresponding section-end macro, \texttt{TracySectionLeave(id)}. Here's an example of a simple RAII wrapper:
\begin{lstlisting}
struct TracySection
{
explicit TracySection(const char* name) : idx(TracySectionEnter("%s", name)) {}
~TracySection() { TracySectionLeave(idx); }
uint32_t idx;
};
\end{lstlisting}
Each of the sections you add is handled independently of any other section. Multiple sections may overlap, either with a hierarchical structure or with each section having a part that does not coincide with other sections.
\subsubsection{Section categories}
In some use cases, you will want to label your sections with specific category labels. For example, you may want to mark which level is currently loaded in your game and also mark when the inventory screen is open. When you are only interested in tracking the level progression, sifting through all the times the inventory was on the screen would be quite cumbersome. Section categories let you filter out uninteresting sections.
To set a category for the section, start the section with the \texttt{TracySectionEnterCategory(category, fmt, ...)} macro, where \texttt{category} is an \texttt{uint16\_t} identifier. Sections without an explicit category assignment (i.e., made with the \texttt{TracySectionEnter} macro) are automatically in the $0$ category.
You can describe each of the categories you want to use with the \texttt{TracySectionSetup(category, fmt, ...)} macro. Section categories with no name set will automatically be assigned a name based on the identifier (e.g., \emph{Category 0}).
\subsection{Marking locks}
Modern programs must use multi-threading to achieve the full performance capability of the CPU. However, correct execution requires claiming exclusive access to data shared between threads. When many threads want to simultaneously enter the same critical section, the application's multi-threaded performance advantage nullifies. To help solve this problem, Tracy can collect and display lock interactions in threads.
@@ -1602,11 +1717,14 @@ Tracy can monitor the memory usage of your application. Knowledge about each per
\item Memory allocation hot-spot tree.
\end{itemize}
To mark memory events, use the \texttt{TracyAlloc(ptr, size)} and \texttt{TracyFree(ptr)} macros. Typically you would do that in overloads of \texttt{operator new} and \texttt{operator delete}, for example:
To mark memory events, use the \texttt{TracyAlloc(ptr, size)} and \texttt{TracyFree(ptr)} macros. Typically, you would place them in overloads of \texttt{operator new} and \texttt{operator delete}. The order in which memory events actually occur must match the order reported to the profiler, so a mutex or similar synchronization primitive is required to make the whole operation atomic. Here is an example implementation:
\begin{lstlisting}
std::mutex memoryLock;
void* operator new(std::size_t count)
{
std::lock_guard lock(memoryLock);
auto ptr = malloc(count);
TracyAlloc(ptr, count);
return ptr;
@@ -1614,6 +1732,7 @@ void* operator new(std::size_t count)
void operator delete(void* ptr) noexcept
{
std::lock_guard lock(memoryLock);
TracyFree(ptr);
free(ptr);
}
@@ -1661,6 +1780,8 @@ Tracy provides bindings for profiling OpenGL, Vulkan, Direct3D 11, Direct3D 12,
Note that the CPU and GPU timers may be unsynchronized unless you create a calibrated context, but the availability of calibrated contexts is limited. You can try to correct the desynchronization of uncalibrated contexts in the profiler's options (section~\ref{options}).
GPU contexts are identified by a process-wide unique id, assigned when the context is created (the context creation macros do this automatically). Ids are 8 bits wide (0--255) and are allocated sequentially, so a process can create at most 256 GPU contexts.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -1814,6 +1935,7 @@ To instrument a GPU zone, use the various \texttt{TracyWebGPU*Zone*()} macros. N
You are required to periodically collect the GPU events using the \texttt{TracyWebGPUCollect()} macro. Good places for collection are: after synchronous waits, after event processing \texttt{wgpuInstanceProcessEvents}, after present drawable calls (\texttt{wgpuSurfacePresent}), and inside the completion callback of command queues (\texttt{wgpuQueueOnSubmittedWorkDone}).
\subsubsection{ROCm}
\label{rocm}
On Linux, if rocprofiler-sdk is installed, tracy can automatically trace GPU dispatches and collect
performance counter values. If CMake can't find rocprofiler-sdk, you can set the CMake variable
@@ -2221,7 +2343,7 @@ Refer to sections~\ref{markingzones} and~\ref{multizone} for description of macr
Unlike C++, there's no automatic destruction mechanism in C, so you will need to mark where the zone ends manually. To do so use the \texttt{TracyCZoneEnd(ctx)} macro.\footnote{GCC and Clang provide \texttt{\_\_attribute\_\_((cleanup))} which can used to run a function when a variable goes out of scope.}
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. For printf-style formatting, use the \texttt{TracyCZoneTextF(ctx, fmt, ...)} and \texttt{TracyCZoneNameF(ctx, fmt, ...)} variants, which accept a format string and arguments instead of a fixed-length text buffer. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
\paragraph{Zone context data structure}
\label{zonectx}
@@ -2364,7 +2486,7 @@ Moreover, there are two sets of functions described below. The standard set send
A GPU context can be created with the \texttt{\_\_\_tracy\_emit\_gpu\_new\_context} function (or the serialized variant). You'll need to specify:
\begin{itemize}
\item \texttt{context} -- a unique context id.
\item \texttt{context} -- a unique context id (8-bit, 0--255; see section~\ref{gpuprofiling}).
\item \texttt{gpuTime} -- an initial GPU timestamp.
\item \texttt{period} -- the timestamp period of the GPU.
\item \texttt{flags} -- the flags to use.
@@ -2464,7 +2586,7 @@ An especially powerful feature is the ability to profile Python code and any oth
An example of how to use the Tracy-Client bindings is shown below:
\begin{lstlisting}
\begin{lstlisting}[language=Python,escapeinside={}{}]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
@@ -2541,7 +2663,7 @@ if __name__ == "__main__":
main()
\end{lstlisting}
Please not the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
Please note the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
\subsubsection{Building the Python package}
@@ -2717,7 +2839,6 @@ You can integrate Tracy with CMake by adding the git submodule folder as a subdi
option(TRACY_ENABLE "" ON)
# must be enabled
option(TRACY_Fortran "" ON)
option(TRACY_DELAYED_INIT "" ON)
option(TRACY_MANUAL_LIFETIME "" ON)
add_subdirectory(3rdparty/tracy) # target: TracyClientF90 or alias Tracy::TracyClientF90
\end{lstlisting}
@@ -2751,7 +2872,6 @@ When using CMake 3.11 or newer, you can use Tracy via CMake FetchContent. In thi
\begin{lstlisting}
option(TRACY_Fortran "" ON)
option(TRACY_DELAYED_INIT "" ON)
option(TRACY_MANUAL_LIFETIME "" ON)
FetchContent_Declare(
@@ -3040,6 +3160,10 @@ The sampling functionality also captures call stacks for context switch events.
\item Unexpected waits, which should be immediately taken care of. After all, what's the point of profiling and optimizing your program if it is constantly waiting for something? An example of such an unexpected wait may be some anti-virus service interfering with each of your file read operations. In this case, you could have assumed that the system would buffer a large chunk of the data after the first read to make it immediately available to the application in the following calls.
\end{enumerate}
Since context switch samples are captured at scheduling events rather than by the statistical sampling timer, they are not an unbiased measure of program execution. They are therefore only displayed on the timeline and in the wait stacks window, and are excluded from the sampling statistics, the per-symbol sample data, child calls, entry stacks, flame graph, zone call stack reconstruction, and so on.
Collection of wait stacks makes the kernel walk the stack at context switch events, which may add considerable overhead on weaker hardware. Use the \texttt{TRACY\_NO\_WAIT\_STACKS} macro to disable capture of wait stacks, while still keeping the context switch events themselves.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -3093,6 +3217,7 @@ On Linux, Tracy will override the \texttt{dlclose} function call to prevent shar
\end{bclogo}
\subsubsection{Vertical synchronization}
\label{vsync}
On Windows and Linux, Tracy will automatically capture hardware Vsync events, provided that the application has access to the kernel data (privilege elevation may be needed, see section~\ref{privilegeelevation}). These events will be reported as \enquote{\texttt{[x] Vsync}} frame sets, where \texttt{x} is the identifier of a specific monitor. Note that hardware vertical synchronization might not correspond to the one seen by your application due to desktop composition, command queue buffering, and so on. Also, in some instances, when there is nothing to update on the screen, the graphic driver may choose to stop issuing screen refresh. As a result, there may be periods where no vertical synchronization events are reported.
@@ -3206,6 +3331,47 @@ Each client is captured to a separate file in the output directory, named accord
Press \keys{\ctrl + C} to stop discovery and gracefully shut down all active captures. Each capture thread will finish writing its trace file before the daemon exits.
The daemon locks the output directory and refuses to start if a running daemon already owns it, so only one daemon can write to a given directory at a time.
\subsection{External monitoring}
\label{externalmonitoring}
Sometimes, the application you want to profile cannot be built with the Tracy client. This is often the case with third-party or closed-source software, running system services, or programs written in languages without Tracy bindings. For such situations, there is the \texttt{tracy-monitor} utility, contained in the \texttt{monitor} directory. The monitor profiles a process from the outside, without modifying it in any way, on Linux (x86-64 and ARM64).
The monitor samples the target in the same manner as the compiled-in client (see section~\ref{sampling}), so a capture contains:
\begin{itemize}
\item Sampled call stacks of every thread of the target, resolved to function names, source file and line, including inlined functions. As with regular call stack sampling (chapter~\ref{collectingcallstacks}), the target's executable and shared libraries should carry symbol information for the resolution to work, and programs compiled without frame pointers (typical of optimized builds) will yield call stacks truncated to the innermost function.
\item Machine code of the profiled functions, so that the disassembly view (section~\ref{executableretrieval}) is fully functional, exactly as in a regular capture.
\item System-wide CPU usage plots.
\item Depending on your privileges and system configuration: kernel frames in the call stacks, context switches and wait stacks (sections~\ref{contextswitches} and \ref{waitstacks}), per-thread CPU time, power consumption (RAPL) plots, and hardware vsync events.
\end{itemize}
The monitor can either start the program you want to profile, or attach to an already running one:
\begin{lstlisting}[language=sh]
$ tracy-monitor ./my_program arg1 arg2
$ tracy-monitor -p 12345
$ tracy-monitor -n my_program
\end{lstlisting}
In the first form, the monitor launches the program and the profiling begins before its first instruction executes, so the entire lifetime of the program is covered. In this mode, stopping the monitor (\keys{\ctrl + C}) also terminates the profiled program.
The \texttt{-p} option attaches to an already running process by its ID, and \texttt{-n} does the same by its name (the name as known to the operating system, truncated to 15 characters; if several processes share the name, the monitor lists their IDs so you can pick one with \texttt{-p}). In attach mode, the target is left running when the monitor is stopped.
The following options are also available:
\begin{itemize}
\item \texttt{-{}-hz N} -- sampling frequency in Hz (default: 10000, range 1--1000000).
\item \texttt{-{}-port N} -- the port on which the monitor accepts connections from the profiler (default: 8086).
\item \texttt{-o, -{}-output FILE} -- save the trace to a file when the capture ends. While capturing, the monitor acts as the Tracy server itself, so the profiler cannot connect to it.
\item \texttt{-f, -{}-force} -- overwrite the output file if it already exists.
\end{itemize}
After starting, the monitor prints a short report listing the data sources that are actually active in your environment (sampling rate, kernel frames, context switches, hardware sampling statistics, power, vsync), together with the reason for any that are unavailable.
The monitor can also be built with the \texttt{TRACY\_ON\_DEMAND} define (section~\ref{ondemand}): in that case, samples are captured only while a Tracy server is connected, and samples taken before the first connection are discarded.
\subsection{Merging trace files}
\label{mergingtraces}
@@ -3316,7 +3482,7 @@ You can also adjust some settings that affect global profiler behavior in this w
If this is a real-time capture, you will also have access to the connection information pop-up (figure~\ref{connectioninfo}) through the \emph{\faWifi{}~Connection} button, with the capture status similar to the one displayed by the command-line utility. This dialog also shows the connection speed graphed over time and the profiled application's current frames per second and frame time measurements. The \emph{Query backlog} consists of two numbers. The first represents the number of queries that were held back due to the bandwidth volume overwhelming the available network send buffer. The second one shows how many queries are in-flight, meaning requests sent to the client but not yet answered. While these numbers drain down to zero, the performance of real time profiling may be temporarily compromised. The circle displayed next to the bandwidth graph signals the connection status. If it's red, the connection is active. If it's gray, the client has disconnected.
You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
You can use the \faFloppyDisk{}~\emph{Save trace...} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
\begin{figure}[h]
\centering\begin{tikzpicture}
@@ -3345,6 +3511,22 @@ Suppose the profiled application opted to provide trace parameters (see section~
You can pass the trace file name as an argument to the profiler application to open the capture, skipping the welcome dialog. You can also use the \texttt{-a address} argument to connect to the given address automatically. Finally, to specify the network port, pass the \texttt{-p port} parameter. The profiler will use it for client connections (overridable in the UI) and for listening to client discovery broadcasts.
\subsubsection{Loading a trace from a URL}
The web version can load a trace directly from a URL passed in the \texttt{t} query parameter. The URL should point to the trace file, as shown below. URLs containing special characters such as \enquote{\texttt{\#}} or \enquote{\texttt{\&}} may need to be manually percent-encoded.
\texttt{https://tracy.nereid.pl/?t=https://example.com/capture.tracy}
The web server where the trace is stored must allow requests from the profiler's origin by sending the \texttt{Access-Control-Allow-Origin} header (e.g.,~\enquote{\texttt{*}}). Traces on the public internet must be served over HTTPS, while traces on the local network can be loaded by the public instance over plain HTTP as well. The first time this is attempted, the browser may ask for permission to access the local network.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bcattention
]{Browser limits}
Note that attempting to load large traces in the browser will likely fail, as the web environment has limited resources.
\end{bclogo}
\subsection{Connection speed}
Tracy network bandwidth requirements depend on the amount of data collection the profiled application performs. You may expect anything between 1~Mbps and 100~Mbps data transfer rate in typical use case scenarios.
@@ -3595,7 +3777,7 @@ The main profiler window is split into three sections, as seen in figure~\ref{ma
\draw[rounded corners=5pt] (1.3, -0.1) rectangle+(2.2, -0.5) node [midway] {\faComment{} Messages};
\draw[rounded corners=5pt] (3.6, -0.1) rectangle+(1.3, -0.5) node [midway] {\faMagnifyingGlass{} Find};
\draw[rounded corners=5pt] (5.0, -0.1) rectangle+(2, -0.5) node [midway] {\faArrowUpWideShort{} Statistics};
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFire{} Flame};
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFireFlameCurved{} Flame};
\draw[rounded corners=5pt] (8.7, -0.1) rectangle+(2.1, -0.5) node [midway] {\faMemory{} Memory};
\draw[rounded corners=5pt] (10.9, -0.1) rectangle+(2.1, -0.5) node [midway] {\faScaleBalanced{} Compare};
\draw[rounded corners=5pt] (13.1, -0.1) rectangle+(1.2, -0.5) node [midway] {\faFingerprint{} Info};
@@ -3631,7 +3813,7 @@ The control menu (top row of buttons) provides access to various profiler featur
\item \emph{\faComment{} Messages} -- Toggles the message log window (section~\ref{messages}), which displays custom messages sent by the client, as described in section~\ref{messagelog}.
\item \emph{\faMagnifyingGlass{} Find} -- This buttons toggles the find zone window, which allows inspection of zone behavior statistics (section~\ref{findzone}).
\item \emph{\faArrowUpWideShort{} Statistics} -- Toggles the statistics window, which displays zones sorted by their total time cost (section~\ref{statistics}).
\item \emph{\faFire{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
\item \emph{\faFireFlameCurved{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
\item \emph{\faMemory{} Memory} -- Various memory profiling options may be accessed here (section~\ref{memorywindow}).
\item \emph{\faScaleBalanced{} Compare} -- Toggles the trace compare window, which allows you to see the performance difference between two profiling runs (section~\ref{compare}).
\item \emph{\faFingerprint{} Info} -- Show general information about the trace (section~\ref{traceinfo}).
@@ -3642,9 +3824,10 @@ The control menu (top row of buttons) provides access to various profiler featur
\item \emph{\faNoteSticky{}~Annotations} -- If annotations have been made (section~\ref{annotatingtrace}), you can open a list of all annotations, described in chapter~\ref{annotationlist}.
\item \emph{\faRuler{}~Limits} -- Displays time range limits window (section~\ref{timeranges}).
\item \emph{\faHourglassHalf{}~Wait stacks} -- If sampling was performed, an option to display wait stacks may be available. See chapter~\ref{waitstacks} for more details.
\item \emph{\faImages{}~Frame statistics} -- Display frame statistics window (section~\ref{framestatistics}).
\end{itemize}
\item \emph{ \faBook{}~User manual} -- Opens the user manual for quick reference. Note that the version of the user manual available directly in the profiler is an inferior quality version compared to the proper PDF.
\item \emph{\faMagnifyingGlassPlus{}~Display scale} -- Enables run-time resizing of the displayed content. This may be useful in environments with potentially reduced visibility, e.g. during a presentation. Note that this setting is independent to the UI scaling coming from the system DPI settings. The scale will be preserved across multiple profiler sessions if the \emph{Save UI scale} option is selected in global settings.
\item \emph{\faMagnifyingGlassPlus{}~Display scale} -- Enables run-time resizing of the displayed content. This may be useful in environments with potentially reduced visibility, e.g. during a presentation. Note that this setting is independent to the UI scaling coming from the system DPI settings. The scale will be preserved across multiple profiler sessions if the \emph{Save UI scale} option is selected in global settings. The user interface scale can also be adjusted with \keys{\ctrl + {+}} and \keys{\ctrl + \textminus}, or reset to 100\% with \keys{\ctrl + 0}.
\item \emph{\faRobot{}~Tracy Assist} -- Shows the automated assistant chat window (section~\ref{tracyassist}). Only available if enabled in global settings (section~\ref{aboutwindow}).
\end{itemize}
@@ -3662,7 +3845,7 @@ If the drawing of timeline elements was disabled in the options menu (section~\r
\item \faExpand{} -- Display of empty labels is enabled.
\item \faPersonHiking{} -- Context switches are hidden.
\item \faSliders{} -- CPU data is hidden.
\item \faEye{} -- GPU zones are hidden.
\item \faDisplay{} -- GPU zones are hidden.
\item \faMicrochip{} -- CPU zones are hidden.
\item \faLock{} -- Locks are hidden.
\item \faSignature{} -- Plots are hidden.
@@ -3741,7 +3924,7 @@ Clicking the \LMB{}~left mouse button on the graph while the \keys{\ctrl}~key is
\subsubsection{Timeline view}
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into three parts: the time scale, the frame sets, and the combined zones, locks, and plots display.
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into four parts: time scale, frame sets, sections, and the combined zones, locks, and plots display.
\subparagraph{Collapsed items}
\label{collapseditems}
@@ -3828,6 +4011,40 @@ If a frame has an associated frame image (see chapter~\ref{frameimages}), you ca
If the \emph{\faFlagCheckered{}~Draw frame targets} option is enabled (see section~\ref{options}), time regions in frames exceeding the set target value will be marked with a red background.
\paragraph{Sections}
\label{sections}
If sections of program execution were marked (see chapter~\ref{markingsections}), the profiler will try to reconstruct a child-parent hierarchy between the sections, as presented on figure~\ref{figsections}.
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw(0, 0) rectangle+(2, -0.5) node[midway] {Menu};
\draw(2, 0) rectangle+(10, -0.5) node[midway] {In-game};
\draw(12, 0) rectangle+(1.5, -0.5) node[midway] {Menu};
\draw(2, -0.5) rectangle+(3, -0.5) node[midway] {Level 1};
\draw(5, -0.5) rectangle+(5, -0.5) node[midway] {Level 2};
\draw(10, -0.5) rectangle+(2, -0.5) node[midway] {Level 3};
\end{tikzpicture}
\caption{Sections showing a hierarchy.}
\label{figsections}
\end{figure}
With some data sets such reconstruction will not be possible, in which case sections may look as on figure~\ref{figsections2}.
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw(0, 0) rectangle+(8, -0.5) node[midway] {First section};
\draw(3, -0.5) rectangle+(8, -0.5) node[midway] {Second section};
\end{tikzpicture}
\caption{Sections that cannot form a hierarchy.}
\label{figsections2}
\end{figure}
Clicking the \MMB{}~middle mouse button on a section will zoom the view to the extent of the section.
Section hierarchy reconstruction is not affected by section categories. There is only a single lane for all sections. Enabling or disabling specific section categories in the options window, however, will affect which sections are visible here.
\paragraph{Zones, locks and plots display}
\label{zoneslocksplots}
@@ -3923,7 +4140,7 @@ In an example in figure~\ref{zoneslocks} you can see that there are two threads:
Meanwhile, the \emph{Streaming thread} is performing some \emph{Streaming jobs}. The first \emph{Streaming job} sent a message (section~\ref{messagelog}). In addition to being listed in the message log, it is indicated by a triangle over the thread separator. When multiple messages are in one place, the triangle outline shape changes to a filled triangle.
The GPU zones are displayed just like CPU zones, with an OpenGL/Vulkan/Direct3D/Metal/OpenCL/CUDA/WebGPU context in place of a thread name.
The GPU zones are displayed just like CPU zones, with an OpenGL / Vulkan / Direct3D / Metal / OpenCL / CUDA / WebGPU context in place of a thread name.
Hovering the \faArrowPointer{} mouse pointer over a zone will highlight all other zones that have the exact source location with a white outline. Clicking the \LMB{}~left mouse button on a zone will open the zone information window (section~\ref{zoneinfo}). Holding the \keys{\ctrl} key and clicking the \LMB{}~left mouse button on a zone will open the zone statistics window (section~\ref{findzone}). Clicking the \MMB{}~middle mouse button on a zone will zoom the view to the extent of the zone.
@@ -3947,7 +4164,15 @@ Clicking the \LMB{}~left mouse button on a ghost zone will open the correspondin
\subparagraph{Call stack samples}
The row of dots right below the \emph{Main thread} label shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Hovering the \faArrowPointer{}~mouse pointer over each dot will display a short call stack summary while clicking on the dot with the \LMB{}~left mouse button will open a more detailed call stack information window (see section~\ref{callstackwindow}).
The row of dots right below the thread label (\emph{Main thread} in the example) shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Each sample is color-coded as follows:
\begin{itemize}
\item \emph{Green} -- shows samples inside the profiled program.
\item \emph{Blue} -- indicates samples belonging to external libraries.
\item \emph{Red} -- used for samples captured in the kernel.
\end{itemize}
Hovering the \faArrowPointer{}~mouse pointer over each dot displays a short call stack summary, while clicking the dot with the \LMB{}~left mouse button opens a more detailed call stack information window (see section~\ref{callstackwindow}).
\subparagraph{Context switches}
@@ -4031,7 +4256,7 @@ Mutual exclusion zones are displayed in each thread that tries to acquire them.
\item \emph{Red region} -- The thread wants to acquire the lock but is blocked by other thread or threads in case of a shared lock.
\end{itemize}
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Hovering the \faArrowPointer{}~mouse pointer over a thread label shows that thread's aggregate lock wait time across all locks (see above). Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
\subparagraph{Plots}
\label{plots}
@@ -4076,17 +4301,21 @@ Sometimes, you may want to specify a time range, such as limiting some statistic
To define a time range, drag the \LMB{}~left mouse button over the timeline view while holding the \keys{\ctrl} key. When the mouse key is released, the profiler will mark the selected time extent with a blue striped pattern, and it will display a context menu with the following options:
\begin{itemize}
\item \emph{\faMagnifyingGlass{}~Limit find zone time range} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
\item \emph{\faArrowUpWideShort{}~Limit statistics time range} -- selecting this option will limit statistics results. See chapter~\ref{statistics} for more details.
\item \emph{\faFire{}~Limit flame graph time range} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
\item \emph{\faHourglassHalf{}~Limit wait stacks time range} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
\item \emph{\faMemory{}~Limit memory time range} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
\item \faRuler{}~Set time range for:
\begin{itemize}
\item \emph{\faMagnifyingGlass{}~Find zone} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
\item \emph{\faArrowUpWideShort{}~Statistics} -- selecting this option will limit statistics results (section~\ref{statistics}), as well as the sample statistics shown in the source view (section~\ref{sourceview}).
\item \emph{\faFireFlameCurved{}~Flame graph} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
\item \emph{\faHourglassHalf{}~Wait stacks} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
\item \emph{\faMemory{}~Memory} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
\item \emph{\faImages{}~Frame statistics} -- limits frame statistics. Section~\ref{framestatistics} has more information.
\end{itemize}
\item \emph{\faNoteSticky{}~Add annotation} -- use to annotate regions of interest, as described in chapter~\ref{annotatingtrace}.
\end{itemize}
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone or a frame. The resulting time extent will match the selected item.
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone, section or a frame. The resulting time extent will match the selected item.
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu. Time range limits can also be disabled or enabled in the windows showing the data they affect.
You can freely adjust each time range on the timeline by clicking the \LMB{}~left mouse button on the range's edge and dragging the mouse.
@@ -4097,15 +4326,14 @@ Tracy allows adding custom notes to the trace. For example, you may want to mark
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, it is assigned a semi-unique random name to make it distinguishable. The settings window is also opened (section~\ref{annotationsettings}), allowing you to enter your own description of the annotation.
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the circle next to the text description will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the text description area will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw (0, 0.25) -- (0, 1) -- (5, 1) -- (5, 0.25);
\draw[dotted] (0, -0.2) -- (0, 0.25);
\draw[dotted] (5, -0.2) -- (5, 0.25);
\draw (0.25, 0.75) circle(0.15);
\draw (0.4, 0.7) node[anchor=west] {Description};
\draw (0, -0.25) -- (0, -1) -- (5, -1) -- (5, -0.25);
\draw[dotted] (0, 0.2) -- (0, -0.25);
\draw[dotted] (5, 0.2) -- (5, -0.25);
\draw (0, -0.8) node[anchor=west] {\faNoteSticky{}~Description};
\end{tikzpicture}
\caption{Annotation region.}
\label{annotation}
@@ -4132,7 +4360,9 @@ In this window, you can set various trace-related options. For example, the time
\begin{itemize}
\item \emph{\faSignature{} Draw CPU usage graph} -- You can disable drawing of the CPU usage graph here.
\end{itemize}
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL/Vulkan/Metal/Direct3D/OpenCL/CUDA/WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
\item \emph{\faEyeDropper{}~Draw stack samples} -- Controls if stack samples for each thread are displayed on the timeline.
\item \emph{\faArrowsLeftRightToLine{}~Draw sections} -- Allows disabling display of sections (see chapter~\ref{sections}). If there are multiple section categories, there's also an expandable list of categories that can be disabled or enabled. The \emph{\faLayerGroup{}~Group by category} option lays out each category in its own set of rows, ordered by category identifier, instead of packing all sections together by length.
\item \emph{\faDisplay{} Draw GPU zones} -- Allows disabling display of OpenGL / Vulkan / Metal / Direct3D / OpenCL / CUDA / WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
\item \emph{\faMicrochip{} Draw CPU zones} -- Determines whether CPU zones are displayed.
\begin{itemize}
\item \emph{\faGhost{} Draw ghost zones} -- Controls if ghost zones should be displayed in threads which don't have any instrumented zones available.
@@ -4170,7 +4400,7 @@ For now, restoring the defaults can be done by deleting the configuration file.
\subsection{Messages window}
\label{messages}
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message.
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message. Clicking the \RMB{}~right mouse button on a message will display context menu with a \emph{\faClipboard{}~Copy message} option, which copies the message text to clipboard.
The \emph{call stack} column is filled only if a call stack capture was requested, as described in section~\ref{collectingcallstacks}. A single entry consists of the \emph{\faAlignJustify{}~Show} button, which opens the call stack information window (chapter~\ref{callstackwindow}) and of abbreviated information about the call path.
@@ -4194,7 +4424,7 @@ Looking at the timeline view gives you a very localized outlook on things. Howev
If the trace capture was performed with call stack sampling enabled (as described in chapter~\ref{sampling}), you will be presented with an option to switch between \emph{\faSyringe{}~Instrumentation} and \emph{\faEyeDropper{}~Sampling} modes. If the profiler collected no sampling data, but it retrieved symbols, the second mode will be displayed as \emph{\faPuzzlePiece{}~Symbols}, enabling you to list available symbols.
If GPU zones were captured, you would also have the \emph{\faEye{}~GPU} option to view the GPU zones statistics.
If GPU zones were captured, you would also have the \emph{\faDisplay{}~GPU} option to view the GPU zones statistics.
\subsubsection{Instrumentation mode}
@@ -4221,7 +4451,7 @@ The \emph{Name} column contains name of the symbol in which the sampling was don
By default, each inlining of a function is listed separately. If you prefer to combine the measurements for functions that are inlined multiple times within a function, you can do so by enabling the \emph{\faLayerGroup{}~Aggregate} option. You cannot view sample entry stacks of inlined functions when this grouping method is enabled.
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire*~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire{}~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
If the \emph{\faSitemap{}~Inlines} option is enabled, the list will show all functions without grouping them by symbol. In this mode, inline functions are preceded by a \faCaretRight{} symbol and their parent function name is displayed in parentheses.
@@ -4229,9 +4459,9 @@ The \emph{Location} column displays the corresponding source file name and line
The location data is complemented by the originating executable image name, contained in the \emph{Image} column. If the \emph{\faScissors{}~Short images} option is selected, the image path will be shortened to just the image file name, with the full path available in the tooltip.
The profiler may not find some function locations due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
The profiler may not find some function names due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the total number of collected samples. You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the number of collected samples, excluding context switch samples, which do not participate in the sampling statistics (section~\ref{waitstacks}). You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
The last column, \emph{Code size}, displays the size of the symbol in the executable image of the program. Since inlined routines are directly embedded into other functions, their symbol size will be based on the parent symbol and displayed as 'less than'. In some cases, this data won't be available. If the symbol code has been retrieved\footnote{Symbols larger than 128~KB are not captured.} symbol size will be prepended with the \texttt{\faDatabase}~icon, and clicking the \RMB{}~right mouse button on the location column entry will open symbol view window (section~\ref{symbolview}).
@@ -4260,6 +4490,8 @@ Tracy gives you the ability to display an execution time histogram of all occurr
You start by entering a search query, which will be matched against known zone names (see section~\ref{markingzones} for information on the grouping of zone names). If the search found some results, you will be presented with a list of zones in the \emph{matched source locations} drop-down. The selected zone's graph is displayed on the \emph{histogram} drop-down, and also the matching zones are highlighted on the timeline view.
GPU zones are supported as well, using their GPU execution time. GPU zones are prefixed with the~\faDisplay~icon. Zone grouping, the found-zones list, and call stack samples are available only for CPU zones.
Clicking the \RMB{}~right mouse button on the source file location will open the source file view window (if applicable, see section~\ref{sourceview}). If symbol data is available Tracy will try to match the instrumented zone name to a captured symbol. If this succeeds and there are no duplicate matches, the source file view will be accompanied by the disassembly of the code. Since this matching is not exact, in rare cases you may get the wrong data here. To just display the source code, press and hold the \keys{\ctrl} key while clicking the \RMB{}~right mouse button.
An example histogram is presented in figure~\ref{findzonehistogram}. Here you can see that the majority of zone calls (by count) are clustered in the 300~\si{\nano\second} group, closely followed by the 10~\si{\micro\second} cluster. There are some outliers at the 1~and~10~\si{\milli\second} marks, which can be ignored on most occasions, as these are single occurrences.
@@ -4367,7 +4599,21 @@ Clicking the \LMB{}~left mouse button on the group name will highlight the group
The call stack grouping mode has a different way of listing groups. Here only one group is displayed at any time due to the need to display the call stack frames. You can switch between call stack groups by using the~\faCaretLeft{}~and~\faCaretRight{} buttons. You can select the group by clicking on the~\emph{\faCheck{}~Select} button. You can open the call stack window (section~\ref{callstackwindow}) by pressing the~\emph{\faAlignJustify{}~Call~stack} button.
Tracy displays a variety of statistical values regarding the selected function: mean (average value), median (middle value), mode (most common value, quantized using histogram bins), and \textsigma{} (standard deviation). The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
Tracy displays a variety of statistical values regarding the selected function:
\begin{itemize}
\item \emph{mean} -- average time value,
\item \emph{median} -- middle time value (P50),
\item \emph{mode} -- most common time value, quantized using histogram bins,
\item \emph{\textsigma} -- standard deviation,
\item \emph{coefficient of variation} -- relation of \textsigma{} to mean,
\item \emph{P75} -- 75th percentile time value,
\item \emph{P90} -- 90th percentile time value,
\item \emph{P99} -- 99th percentile time value,
\item \emph{P99.9} -- 99.9th percentile time value.
\end{itemize}
The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
Hovering the \faArrowPointer{}~mouse cursor over a zone on the timeline, which is currently selected in the find zone window, will display a pulsing vertical bar on the histogram, highlighting the bin to which the hovered zone has been assigned. In addition, it will also highlight zone entry on the zone list.
@@ -4423,11 +4669,27 @@ You can further narrow down the list of samples by selecting a time range on the
Comparing the performance impact of the optimization work is not an easy thing to do. Benchmarking is often inconclusive, if even possible, in the case of interactive applications, where the benchmarked function might not have a visible impact on frame render time. Furthermore, doing isolated micro-benchmarks loses the application's execution environment, in which many different parts compete for limited system resources.
Tracy solves this problem by providing a compare traces functionality, very similar to the find zone window, described in section~\ref{findzone}. You can compare traces either by zone or frame timing data.
Tracy solves this problem by providing a compare traces functionality, which is very similar to the find zone window. There are three tabs in this window, for three modes of comparison:
You would begin your work by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
\begin{itemize}
\item \emph{Zones} -- closely matches the find zone window, described in section~\ref{findzone}.
\item \emph{Frames} -- shows frame rate data, following the frame statistics window described in section~\ref{framestatistics}.
\item \emph{Source diff} -- displays differences in source code, covered in section~\ref{sourcefilesdiff}.
\end{itemize}
Now things start to get familiar. You search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram\footnote{When comparing frame times you are presented with a list of available frame sets, without the search box.}. This time there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
To gather the data, you would start by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bclampe
]{Trace descriptions}
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
\end{bclogo}
Now things start to get familiar. In the zone comparison mode, you search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram. When comparing frame times you are presented with a list of available frame sets, without the search box.
In the compare traces window there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
\begin{figure}[h]
\centering\begin{tikzpicture}
@@ -4464,15 +4726,12 @@ When searching for source locations it's not uncommon to match more than one zon
It may be difficult, if not impossible, to perform identical runs of a program. This means that the number of collected zones may differ in both traces, influencing the displayed results. To fix this problem, enable the \emph{Normalize values} option, which will adjust the displayed results as if both traces had the same number of recorded zones.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bclampe
]{Trace descriptions}
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
\end{bclogo}
The window shows statistical data for both traces, with the values described in chapter~\ref{findzone}. Each value is also compared against the other trace, showing both the absolute and relative differences. The keywords \emph{less} and \emph{more} are color coded to note significance. When the two readings are almost the same, the keywords will be printed in gray. As the values diverge, the amount of color (either green or red) increases, maxing out at twice the difference. This allows quick eyeballing of where the difference is.
In some cases you may want to compare only specific parts of traces, not the whole performance profile. To do so, you can enable the \emph{Limit range} option, which will then present you with time range limiting options for both traces. Use the \emph{\faCopy{}~Copy from...} button to choose a time range limit from a list of annotations (chapter~\ref{annotatingtrace}) or sections (chapter~\ref{sections}). To remove the limit, you can click on the \emph{Reset} button.
\subsubsection{Source files diff}
\label{sourcefilesdiff}
To see what changes were made in the source code between the two compared traces, select the \emph{Source diff} compare mode. This will display a list of deleted, added, and changed files. By default, the difference is calculated from the older trace to the newer one. You can reverse this by clicking on the \emph{Switch} button.
@@ -4539,6 +4798,10 @@ Similar to the statistics window (section~\ref{statistics}), the flame graph can
In the sampling mode, external frames from system libraries are hidden by default. These typically include internal implementation details of starting threads, handling smart pointers, and other such things that are quick to execute and not really interesting. Enabling the \emph{\faShieldHalved{}~External} option will show these frames. One exception is \emph{external tails}, or calls that your code makes that do not eventually land in your application down the call chain. Think of functions that write to a file or send data on the network. These can be time-consuming, and you may want to see them. There is a separate option to disable these.
The sampling mode, by default, groups the displayed zones by symbol address. In some cases, for example, when using templates, the same function may exist at multiple different symbol addresses. Enabling the \emph{Group by name} option will group the graph items by name instead of by symbol address.
Sometimes a stack frame cannot be resolved to a symbol, as described in section~\ref{callstackwindow}. Such frames are aggregated into a single graph item with the label of the executable image the frame address belongs to, or with \enquote{\texttt{[unknown]}} if even this information is unavailable. The label is slightly dimmed to set it apart from the resolved items. The \emph{\faScissors{}~Short images} option shortens the displayed image name to only the file name.
The flame graph can be restricted to a specific time extent using the \emph{Limit range} option (chapter~\ref{timeranges}). You can access more options through the \emph{\faRuler{}~Limits} button, which will open the time range limits window, described in section~\ref{timerangelimits}.
\subsection{Memory window}
@@ -4614,8 +4877,6 @@ If the \emph{\faUserGear{}~Public sidecar} option is selected, the file containi
Open the \emph{Trace statistics} section to see information about the trace, such as achieved timer resolution, number of captured zones, lock events, plot data points, memory allocations, etc.
There's also a section containing the selected frame set timing statistics and histogram\footnote{See section~\ref{findzone} for a description of the histogram. Note that there are subtle differences in the available functionality.}. As a convenience, you can switch the active frame set here and limit the displayed frame statistics to the frame range visible on the screen.
If \emph{CPU topology} data is available (see section~\ref{cputopology}), you will be able to view the package, core, and thread hierarchy.
The \emph{Source location substitutions} section allows adapting the source file paths, as captured by the profiler, to the actual on-disk locations\footnote{This does not affect source files cached during the profiling run.}. You can create a new substitution by clicking the \emph{Add new substitution} button. This will add a new entry, with input fields for ECMAScript-conforming regular expression pattern and its corresponding replacement string. You can quickly test the outcome of substitutions in the \emph{example source location} input field, which will be transformed and displayed below, as \emph{result}.
@@ -4639,6 +4900,13 @@ In this window, you can view the information about the machine on which the prof
If an application should crash during profiling (section~\ref{crashhandling}), the profiler will display the crash information in this window. It provides you information about the thread that has crashed, the crash reason, and the crash call stack (section~\ref{callstackwindow}).
\subsection{Frame statistics window}
\label{framestatistics}
This windows shows statistical information about the selected frame set timing, and a histogram. See section~\ref{findzone} for a description of the displayed data. As a convenience, you can switch the active frame set here.
You can restrict the displayed frame statistics to the frame range currently visible on the screen with the \emph{Limit to view} option, while the \emph{Limit range} switch limits the active range to a preset time limit, as described in chapter~\ref{timerangelimits}. If both limit options are active, the resulting frame range is the intersection of both ranges.
\subsection{Zone information window}
\label{zoneinfo}
@@ -4705,7 +4973,7 @@ External frames from system libraries are hidden by default. Enabling the \emph{
The \emph{\faScissors{}~Short images} option shortens the displayed executable image name to only the file name. The full path is available in the tooltip.
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faDoorOpen{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faArrowDownShortWide{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
Clicking on the \emph{\faClipboard{}~Copy to clipboard} button will copy call stack to the clipboard.
@@ -4773,7 +5041,7 @@ Nevertheless, \textbf{the displayed source files might still not reflect the cod
A much more capable symbol view mode is available if the inspected source location has an associated symbol context (i.e., if it comes from a call stack capture, from call stack sampling, etc.). A symbol is a unit of machine code, basically a callable function. It may be generated using multiple source files and may consist of numerous inlined functions. A list of all captured symbols is available in the statistics window, as described in chapter~\ref{statisticssampling}.
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{Entry stacks} button.
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{\faArrowDownShortWide{}~Entry stacks} button.
Additionally, you may use the \emph{Mode} selector to decide what content should be displayed in the panels below:
@@ -4908,8 +5176,13 @@ The percentage values when \emph{\faCarBurst{}~Impact} option is not selected wi
The profiler can group call stacks leading to certain events and display the resulting information in a variety of ways. In essence, this shows the code paths that lead to these events and the distribution of these paths. At this moment, these events include:
\begin{itemize}
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active.
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information.
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active. The displayed data set can be selected with the following options:
\begin{itemize}
\item \emph{Was reached} -- counts samples in which the symbol was present anywhere on the call stack. Each sample is counted only once, at the outermost occurrence of the symbol, so recursive re-entries are ignored.
\item \emph{Was reached, recursive} -- as above, but each occurrence of the symbol on the call stack is counted separately, showing also the paths by which the symbol re-entered itself.
\item \emph{Was executing} -- counts samples taken while the symbol was executing, i.e.\ it was at the top of the call stack.
\end{itemize}
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information. Note that wait stacks have separate setting for the \emph{\faShieldHalved{}~External} option, as it is generally beneficial to see the external code when determining the cause of program interruption.
\end{itemize}
The call stack paths may be displayed in the following ways:
@@ -4930,9 +5203,11 @@ This window presents information and statistics about a lock. The lock events co
\subsection{Frame image playback window}
\label{playback}
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons to change single frame back or forward.
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons, and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively, you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons, or use the \Scroll{}~mouse wheel on either the \emph{Frame image} slider or the displayed frame image to change a single frame backward or forward.
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing.
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing. With the \emph{Loop} option enabled, playback will start from the beginning after it reaches the end.
Enabling the \emph{Limit frame range} option allows you to manually set the frame image playback region. Adjustments can be made using the start and end sliders or by copying the time range from either an annotation (section~\ref{annotationlist}), a section (chapter~\ref{sections}), or one of the time range limits (section~\ref{timerangelimits}). If the \emph{Require frame coverage} option is enabled, source regions must cover at least 75\% of the frame. Otherwise any amount of overlap is sufficient.
The following parameters also accompany each displayed frame image: \emph{timestamp}, showing at which time the image was captured, \emph{frame}, displaying the numerical value of the corresponding frame, and \emph{ratio}, telling how well the in-memory loss-less compression was able to reduce the image data size.
@@ -4950,7 +5225,7 @@ The profiled program is highlighted using green color. Furthermore, the yellow h
\subsection{Annotation settings window}
\label{annotationsettings}
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also remove it here.
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also temporarily hide or remove it here.
\subsection{Annotation list window}
\label{annotationlist}
@@ -4958,15 +5233,17 @@ In this window, you may modify how a timeline annotation (section~\ref{annotatin
This window lists all annotations marked on the timeline. Each annotation is presented, as shown on figure~\ref{figannlist}. From left to right the elements are:
\begin{itemize}
\item \emph{\faPenToSquare{} Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
\item \emph{\faMicroscope{} Zoom} -- Zooms timeline to the annotation extent.
\item \emph{\faTrashCan{} Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
\item \emph{\faCheck{}~Visible} -- Makes the annotation visible or not on the timeline.
\item \emph{\faPenToSquare{}~Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
\item \emph{\faMicroscope{}~Zoom} -- Zooms timeline to the annotation extent.
\item \emph{\faTrashCan{}~Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
\item Colored box -- Color of the annotation.
\item Text description of the annotation.
\end{itemize}
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw[rounded corners=5pt] (-0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faCheck};
\draw[rounded corners=5pt] (0.0, 0) rectangle+(0.5, -0.5) node [midway] {\faPenToSquare};
\draw[rounded corners=5pt] (0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faMicroscope};
\draw[rounded corners=5pt] (1.2, 0) rectangle+(0.5, -0.5) node [midway] {\faTrashCan};
@@ -4977,22 +5254,28 @@ This window lists all annotations marked on the timeline. Each annotation is pre
\label{figannlist}
\end{figure}
A new view-sized annotation can be added in this window by pressing the \emph{\faPlus{}~Add annotation} button. This effectively saves your current viewport for further reference.
A new view-sized annotation can be added in this window by pressing the \emph{\faNoteSticky{}~Add annotation} button. This effectively saves your current viewport for further reference.
\subsection{Time range limits}
\label{timerangelimits}
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), memory (section~\ref{memorywindow}) and wait stacks (section~\ref{stackwindows}) results. Each limit can be enabled or disabled and adjusted through the following options:
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), wait stacks (section~\ref{stackwindows}), memory (section~\ref{memorywindow}) and frame statistics (section~\ref{framestatistics}) results. Each limit can be enabled or disabled and adjusted through the following options:
\begin{itemize}
\item \emph{Limit to view} -- Set the time range limit to current view.
\item \emph{\faMicroscope{}~Focus} -- Set the timeline view to the time range extent.
\item \emph{\faNoteSticky{}~From annotation} -- Allows using the annotation region for limiting purposes.
\item \emph{\faMagnifyingGlass{}~Copy find zone} -- Copies the find zone time range limit.
\item \emph{\faArrowUpWideShort{}~Copy statistics} -- Copies the statistics time range limit.
\item \emph{\faFireFlameCurved{}~Copy flame} -- Copies the flame graph time range limit.
\item \emph{\faHourglassHalf{}~Copy wait stacks} -- Copies the wait stacks time range limit.
\item \emph{\faMemory{}~Copy memory} -- Copies the memory time range limit.
\item \emph{Limit to view} -- Set the time range limit to current view.
\item \emph{\faNoteSticky{}~Add annotation} -- Create a new annotation matching the time range. See section~\ref{annotatingtrace} for more information.
\item \faCopy{}~Copy from...:
\begin{itemize}
\item \emph{\faNoteSticky{}~Annotation} -- Allows using the annotation region for limiting purposes.
\item \emph{\faArrowsLeftRightToLine{}~Sections} -- Copies time range of a section. See section~\ref{sections} for more information.
\item \emph{\faMagnifyingGlass{}~Find zone} -- Copies the find zone time range limit.
\item \emph{\faArrowUpWideShort{}~Statistics} -- Copies the statistics time range limit.
\item \emph{\faFireFlameCurved{}~Flame graph} -- Copies the flame graph time range limit.
\item \emph{\faHourglassHalf{}~Wait stacks} -- Copies the wait stacks time range limit.
\item \emph{\faMemory{}~Memory} -- Copies the memory time range limit.
\item \emph{\faImages{}~Frame statistics} -- Copies the frame statistics range limit.
\end{itemize}
\end{itemize}
Note that ranges displayed in the window have color hints that match the color of the striped regions on the timeline.
@@ -5037,8 +5320,8 @@ version = 1
cache-type-k = q8_0
cache-type-v = q8_0
[unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-Q4_K_M]
hf = unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-Q4_K_M
[bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M]
hf = bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M
parallel = 1
spec-default = true
spec-type = draft-mtp
@@ -5056,9 +5339,9 @@ cache-ram = 0
\item Connect to llama.cpp inside the profiler!
\end{enumerate}
The models will be automatically downloaded when trying to access them for the first time. It may take some time.
The models will be automatically downloaded when trying to access them for the first time. It may take some time (the main model is 22+ GB).
If you have the resources available you may try replacing \texttt{unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-Q4\_K\_M} with \texttt{unsloth/Qwen3.6-27B-MTP-GGUF:Q4\_K\_M} in the configuration file to get a more capable model.
If you have the resources available you may try replacing \texttt{bartowski/Qwen\_Qwen3.6-35B-A3B-GGUF:Q4\_K\_M} with \texttt{unsloth/Qwen3.6-27B-MTP-GGUF:Q4\_K\_M} in the configuration file to get a more capable model.
\subsection{Service provider}
@@ -5116,18 +5399,18 @@ Each token present in the context window may require a fairly large amount of me
The realistic minimum required context size for Tracy to run the assistant is 100K tokens, but feel free to experiment.
\end{bclogo}
\subsubsection{Fast model}
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
\subsubsection{Embedding model}
This is a small model used for semantic search in the user manual. This should be \textbf{nomic-embed-text-1.5}, which is provided by default by LM Studio, or which you can download on your own for llama.cpp.
LM Studio properly labels the model's capabilities. This is not the case with the llama.cpp/llama-swap setup. To make it work, your embedding model's name must contain the word \texttt{embed}.
\subsubsection{Fast model}
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
\subsubsection{In practice}
So, which model should you run and what hardware you need to be able to do so? Let's take look at some example systems.
@@ -5157,16 +5440,23 @@ The chat window is divided into three sections:
The control section allows you to clear the chat contents, reconnect to the LLM provider and open the settings panel consisting of:
\begin{itemize}
\item \emph{API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
\item \emph{\faPlug{}~API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
\item \emph{\faComments{}~Chat model} -- Here you can select one of the models you have configured in the LLM provider for chat.
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
\item \emph{\faBookBookmark{}~Embeddings model} -- Select the vector embeddings model.
\item \emph{Personality} -- Choose how the assistant should behave. Note that the actual behavior will vary depending on the language model you have chosen. The following options are available:
\begin{itemize}
\item \emph{\faFaceMeh{}~Assistant} -- The default behavior of the model with Tracy Assist role guidance.
\item \emph{\faFaceGrin{}~Emotion} -- The model will behave like a human with feelings and emotions.
\item \emph{\faFaceFrown{}~Annoyed} -- Does not tolerate nonsense and will respond with sarcasm.
\end{itemize}
Personality changes do not take effect until you start a new chat.
\item \emph{\faEarthAmericas{}~Internet access} -- Determines whether the model can access network resources such as Wikipedia queries, web searches, and web page retrievals.
\item \emph{\faTag{}~Annotate call stacks} -- Enables automatic annotation of call stacks (see section~\ref{callstackwindow}). Disabled by default, as it requires proper configuration of the fast model.
\item \emph{\faHandPointRight{}~Show summary} -- Shows a short conversation topic after the initial question is asked.
\item \emph{\faCommentDots{}~Chat suggestions} -- Suggests the next question the user may want to ask.
\item \emph{Advanced} -- More advanced options are hidden here.
\begin{itemize}
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
\item \emph{\faTemperatureHalf{}~Temperature} -- Allows changing default model temperature setting.
\item \emph{\faLightbulb{}~Show all thinking regions} -- Always shows all reasoning sections and all tool calls made by model.
\item \emph{Tool reply size limit} -- Configurable maximum size for tool responses.

31
merge/CMakePresets.json Normal file
View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -1,4 +1,5 @@
#include "TracyFileRead.hpp"
#include "TracySafeFileWrite.hpp"
#include "TracyFileWrite.hpp"
#include "TracyPrint.hpp"
#include "TracyWorker.hpp"
@@ -375,7 +376,7 @@ int main( int argc, char** argv )
std::cout << " Total messages: " << merged.messages.size() << std::endl;
std::cout << " Total threads: " << merged.threadNames.size() << std::endl;
auto outFile = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( outputFile.c_str(), tracy::FileCompression::Zstd, 3, 4 ) );
auto outFile = std::unique_ptr<tracy::SafeFileWrite>( tracy::SafeFileWrite::Open( outputFile.c_str(), tracy::FileCompression::Zstd, 3, 4 ) );
if( !outFile )
{
std::cerr << "Error: Could not open output file: " << outputFile << std::endl;
@@ -384,8 +385,13 @@ int main( int argc, char** argv )
std::cout << "Writing: " << outputFile << std::endl;
tracy::Worker writer( merged.name.c_str(), merged.process.c_str(), merged.timeline, merged.messages, merged.plots, merged.threadNames );
writer.Write( *outFile, false );
outFile->Finish();
writer.Write( outFile->File(), false );
outFile->File().Finish();
if( !outFile->Commit() )
{
std::cerr << "Error: Could not finalize output file: " << outputFile << std::endl;
return 1;
}
auto stats = outFile->GetCompressionStatistics();
std::cout << "Done. Output size: " << tracy::MemSizeToString( stats.second ) << " (" << (100.0 * stats.second / stats.first) << "% ratio)" << std::endl;

View File

@@ -1,4 +1,4 @@
project('tracy', ['cpp'], version: '0.13.1', meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
project('tracy', ['cpp'], version: run_command(find_program('python3'), files('extra/tracy-version.py'), 'public/common/TracyVersion.hpp', check: true).stdout().strip(), meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
# internal compiler flags
tracy_compile_args = []
@@ -61,6 +61,10 @@ if get_option('no_sampling')
tracy_common_args += ['-DTRACY_NO_SAMPLING']
endif
if get_option('no_wait_stacks')
tracy_common_args += ['-DTRACY_NO_WAIT_STACKS']
endif
if get_option('no_verify')
tracy_common_args += ['-DTRACY_NO_VERIFY']
endif
@@ -81,10 +85,6 @@ if get_option('patchable_nopsleds')
tracy_common_args += ['-DTRACY_PATCHABLE_NOPSLEDS']
endif
if get_option('delayed_init')
tracy_common_args += ['-DTRACY_DELAYED_INIT']
endif
if get_option('manual_lifetime')
tracy_common_args += ['-DTRACY_MANUAL_LIFETIME']
endif
@@ -158,7 +158,8 @@ includes = [
'public/tracy/TracyLua.hpp',
'public/tracy/TracyOpenCL.hpp',
'public/tracy/TracyOpenGL.hpp',
'public/tracy/TracyVulkan.hpp'
'public/tracy/TracyVulkan.hpp',
'public/tracy/TracyWebGPU.hpp'
]
client_includes = [
@@ -190,15 +191,19 @@ common_includes = [
'public/common/TracyAlign.hpp',
'public/common/TracyAlloc.hpp',
'public/common/TracyApi.h',
'public/common/TracyAssert.hpp',
'public/common/TracyColor.hpp',
'public/common/TracyForceInline.hpp',
'public/common/TracyFormat.h',
'public/common/TracyMutex.hpp',
'public/common/TracyProtocol.hpp',
'public/common/TracyQueue.hpp',
'public/common/TracySocket.hpp',
'public/common/TracyStackFrames.hpp',
'public/common/TracyString.hpp',
'public/common/TracySystem.hpp',
'public/common/TracyTaggedUserlandAddress.hpp',
'public/common/TracyVersion.hpp',
'public/common/TracyWinFamily.hpp',
'public/common/TracyYield.hpp'
]
@@ -222,10 +227,16 @@ tracy_compile_args += tracy_common_args
tracy_deps = [dependency('threads')] + tracy_public_deps
tracy_link_args = []
if host_machine.system() == 'windows' and (compiler.get_id() == 'gcc' or compiler.get_id() == 'clang')
tracy_link_args += ['-lws2_32', '-ldbghelp', '-lsecur32']
endif
tracy = library('tracy', tracy_src, tracy_header_files,
dependencies : tracy_deps,
include_directories : tracy_public_include_dirs,
cpp_args : tracy_compile_args,
link_args : tracy_link_args,
override_options : override_options,
install : true)

View File

@@ -11,6 +11,7 @@ option('no_code_transfer', type : 'boolean', value : false, description : 'Disab
option('no_context_switch', type : 'boolean', value : false, description : 'Disable capture of context switches')
option('no_exit', type : 'boolean', value : false, description : 'Client executable does not exit until all profile data is sent to server')
option('no_sampling', type : 'boolean', value : false, description : 'Disable call stack sampling')
option('no_wait_stacks', type : 'boolean', value : false, description : 'Disable capture of context switch call stacks')
option('no_verify', type : 'boolean', value : false, description : 'Disable zone validation for C API')
option('no_vsync_capture', type : 'boolean', value : false, description : 'Disable capture of hardware Vsync events')
option('no_frame_image', type : 'boolean', value : false, description : 'Disable the frame image support and its thread')
@@ -21,12 +22,11 @@ option('disallow_hw_timer', type : 'boolean', value : false, description : 'Disa
option('libunwind_backtrace', type : 'boolean', value : false, description : 'Use libunwind backtracing where supported')
option('symbol_offline_resolve', type : 'boolean', value : false, description : 'Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution')
option('libbacktrace_elf_dynload_support', type : 'boolean', value : false, description : 'Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation')
option('delayed_init', type : 'boolean', value : false, description : 'Enable delayed initialization of the library (init on first call)')
option('manual_lifetime', type : 'boolean', value : false, description : 'Enable the manual lifetime management of the profile')
option('fibers', type : 'boolean', value : false, description : 'Enable fibers support')
option('no_crash_handler', type : 'boolean', value : false, description : 'Disable crash handling')
option('verbose', type : 'boolean', value : false, description : 'Enable verbose logging')
option('no_internal_message', type : 'boolean', value : false, description : 'Prevent the profiler from logging messages')
option('debuginfod', type : 'boolean', value : false, description : 'Enable debuginfod support')
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation')
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation, or from repeated allocations of the same address')
option('opengl_auto_calibration', type : 'boolean', value : false, description : 'Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)')

View File

@@ -4,7 +4,9 @@ set(TRACY_PUBLIC_DIR ${CMAKE_CURRENT_LIST_DIR}/../public)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
set(CMAKE_CXX_STANDARD 17)
set(NO_STATISTICS ON)
set(CMAKE_CXX_STANDARD 20)
project(
tracy-monitor
@@ -12,11 +14,21 @@ project(
VERSION ${TRACY_VERSION_STRING}
)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "tracy-monitor is Linux-only")
endif()
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|aarch64|arm64)$")
message(FATAL_ERROR "tracy-monitor supports x86_64 and aarch64")
endif()
find_package(Threads REQUIRED)
option(TRACY_ON_DEMAND "On-demand profiling: record samples only while a Tracy server is connected" OFF)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/util.cmake)
set(PROGRAM_FILES
src/monitor.cpp
@@ -28,11 +40,15 @@ target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC
$<INSTALL_INTERFACE:include/tracy>)
target_compile_definitions(${PROJECT_NAME} PRIVATE
TRACY_ENABLE
TRACY_DELAYED_INIT
TRACY_MANUAL_LIFETIME
TRACY_NO_FRAME_IMAGE
)
if(TRACY_ON_DEMAND)
target_compile_definitions(${PROJECT_NAME} PRIVATE TRACY_ON_DEMAND)
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE
TracyServer
TracyUtil
Threads::Threads
${CMAKE_DL_LIBS}
)

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,12 @@ set_option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
set_option(SELF_PROFILE "Enable self-profiling" OFF)
set_option_value(SANITIZE "Sanitizer parameters" "")
if(APPLE)
set_option(BUNDLE "Build a macOS application bundle" OFF)
endif()
set(NO_STATISTICS OFF)
set(VENDOR_GUI ON)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
@@ -20,6 +25,10 @@ project(
VERSION ${TRACY_VERSION_STRING}
)
if(APPLE)
enable_language(OBJC)
endif()
if(SELF_PROFILE)
add_definitions(-DTRACY_ENABLE)
add_compile_options(-g -O3 -fno-omit-frame-pointer)
@@ -34,6 +43,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/manual.cmake)
include(ExternalProject)
ExternalProject_Add(embed
@@ -43,18 +53,26 @@ ExternalProject_Add(embed
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
)
# Embeds a file into the profiler as a compressed C array (data/NAME.cpp/.hpp).
# FILE is relative to the current source directory; with BUILD, relative to
# the current build directory. With TEXT, line endings are normalized to unix.
function(Embed LIST NAME FILE)
cmake_parse_arguments(EMBED "TEXT" "" "" ${ARGN})
cmake_parse_arguments(EMBED "TEXT;BUILD" "" "" ${ARGN})
if(EMBED_TEXT)
set(EMBED_FLAGS -t)
else()
set(EMBED_FLAGS)
endif()
if(EMBED_BUILD)
set(EMBED_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/${FILE})
else()
set(EMBED_SOURCE ${CMAKE_CURRENT_LIST_DIR}/${FILE})
endif()
add_custom_command(
OUTPUT data/${NAME}.cpp data/${NAME}.hpp
COMMAND ${CMAKE_COMMAND} -E make_directory data
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${EMBED_FLAGS} ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
DEPENDS embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${EMBED_FLAGS} ${NAME} ${EMBED_SOURCE} data/${NAME}
DEPENDS embed ${EMBED_SOURCE}
)
list(APPEND ${LIST} data/${NAME}.cpp)
return(PROPAGATE ${LIST})
@@ -101,6 +119,7 @@ set(SERVER_FILES
TracyView_FindZone.cpp
TracyView_FlameGraph.cpp
TracyView_FrameOverview.cpp
TracyView_FrameStatistics.cpp
TracyView_FrameTimeline.cpp
TracyView_FrameTree.cpp
TracyView_GpuTimeline.cpp
@@ -122,6 +141,7 @@ set(SERVER_FILES
TracyView_ZoneInfo.cpp
TracyView_ZoneTimeline.cpp
TracyWeb.cpp
TracyWindowConstraints.cpp
)
if(NOT EMSCRIPTEN)
@@ -152,6 +172,8 @@ set(PROFILER_FILES
src/winmainArchDiscovery.cpp
)
Embed(PROFILER_FILES PersonalityAnnoyed src/llm/personality.annoyed.md TEXT)
Embed(PROFILER_FILES PersonalityEmotion src/llm/personality.emotion.md TEXT)
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md TEXT)
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md TEXT)
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md TEXT)
@@ -164,8 +186,23 @@ Embed(PROFILER_FILES FontBold src/font/Roboto-Bold.ttf)
Embed(PROFILER_FILES FontItalic src/font/Roboto-Italic.ttf)
Embed(PROFILER_FILES FontBoldItalic src/font/Roboto-BoldItalic.ttf)
Embed(PROFILER_FILES FontEmoji src/font/NotoEmoji-Regular.ttf)
set(MANUAL_MD ${CMAKE_CURRENT_BINARY_DIR}/manual/tracy.md)
add_custom_command(
OUTPUT ${MANUAL_MD}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/../manual/latex2md.py
--tex ${CMAKE_CURRENT_LIST_DIR}/../manual/tracy.tex
--out ${MANUAL_MD}
--pandoc ${TRACY_PANDOC}
--icons ${CMAKE_CURRENT_LIST_DIR}/src/profiler/IconsFontAwesome7.h
DEPENDS
${CMAKE_CURRENT_LIST_DIR}/../manual/tracy.tex
${CMAKE_CURRENT_LIST_DIR}/../manual/filter.lua
${CMAKE_CURRENT_LIST_DIR}/../manual/latex2md.py
${CMAKE_CURRENT_LIST_DIR}/src/profiler/IconsFontAwesome7.h
COMMENT "Generating user manual"
VERBATIM)
Embed(PROFILER_FILES Manual ../manual/tracy.md TEXT)
Embed(PROFILER_FILES Manual manual/tracy.md TEXT BUILD)
Embed(PROFILER_FILES Text100Million src/achievements/100Million.md TEXT)
Embed(PROFILER_FILES TextConnectToClient src/achievements/ConnectToClient.md TEXT)
@@ -185,51 +222,51 @@ set(LIBS "")
if(USE_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED egl wayland-egl wayland-cursor xkbcommon)
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
if(WAYLAND_CLIENT_VERSION VERSION_LESS 1.22)
message(WARNING "libwayland ${WAYLAND_CLIENT_VERSION} is too old and does not provide version 6 of the core protocol. Building in this configuration is not supported and will result in reduced functionality.")
endif()
set(INCLUDES "${INCLUDES};${WAYLAND_INCLUDE_DIRS}")
set(LIBS "${LIBS};${WAYLAND_LIBRARIES}")
set(LIBS "${LIBS};${WAYLAND_LINK_LIBRARIES}")
set(PROFILER_FILES ${PROFILER_FILES}
src/BackendWayland.cpp
)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/FindWaylandScanner.cmake)
CPMAddPackage(
NAME wayland-protocols
GIT_REPOSITORY https://gitlab.freedesktop.org/wayland/wayland-protocols.git
GIT_TAG 1.47
DOWNLOAD_ONLY YES
)
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols)
pkg_get_variable(WAYLAND_PROTOCOLS_PKGDATADIR wayland-protocols pkgdatadir)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/stable/xdg-shell/xdg-shell.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/stable/xdg-shell/xdg-shell.xml
BASENAME xdg-shell
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/staging/xdg-activation/xdg-activation-v1.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/staging/xdg-activation/xdg-activation-v1.xml
BASENAME xdg-activation
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml
BASENAME xdg-decoration
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/staging/fractional-scale/fractional-scale-v1.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/staging/fractional-scale/fractional-scale-v1.xml
BASENAME fractional-scale
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/stable/viewporter/viewporter.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/stable/viewporter/viewporter.xml
BASENAME viewporter
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/staging/cursor-shape/cursor-shape-v1.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/staging/cursor-shape/cursor-shape-v1.xml
BASENAME cursor-shape
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/unstable/tablet/tablet-unstable-v2.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/tablet/tablet-unstable-v2.xml
BASENAME tablet
)
ecm_add_wayland_client_protocol(PROFILER_FILES
PROTOCOL ${wayland-protocols_SOURCE_DIR}/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml
PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml
BASENAME xdg-toplevel-icon
)
elseif(EMSCRIPTEN)
@@ -259,6 +296,25 @@ if(WIN32)
)
add_executable(${PROJECT_NAME} WIN32 ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
elseif(APPLE AND BUNDLE)
set(ICON_SRC ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.png)
set(ICON_ICNS ${CMAKE_CURRENT_BINARY_DIR}/icon.icns)
add_custom_command(
OUTPUT ${ICON_ICNS}
COMMAND sips -s format icns ${ICON_SRC} --out ${ICON_ICNS}
DEPENDS ${ICON_SRC}
)
set_source_files_properties(${ICON_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES} ${ICON_ICNS})
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/macos/Info.plist.in
MACOSX_BUNDLE_BUNDLE_NAME "Tracy Profiler"
MACOSX_BUNDLE_GUI_IDENTIFIER "pl.nereid.tracy"
MACOSX_BUNDLE_BUNDLE_VERSION ${TRACY_VERSION_STRING}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${TRACY_VERSION_STRING}
MACOSX_BUNDLE_ICON_FILE icon.icns
MACOSX_BUNDLE_COPYRIGHT "Bartosz Taudul"
)
else()
add_executable(${PROJECT_NAME} ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
endif()
@@ -293,6 +349,8 @@ add_git_ref(${PROJECT_NAME})
if(NOT EMSCRIPTEN)
if(NOT NO_FILESELECTOR)
target_link_libraries(${PROJECT_NAME} PRIVATE nfd::nfd)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE TRACY_NO_FILESELECTOR)
endif()
if(NOT USE_WAYLAND)
target_link_libraries(${PROJECT_NAME} PRIVATE TracyGlfw3)
@@ -300,6 +358,7 @@ if(NOT EMSCRIPTEN)
endif()
if(EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PRIVATE -sDISABLE_EXCEPTION_CATCHING=0)
target_link_options(${PROJECT_NAME} PRIVATE
-pthread
-sASSERTIONS=0
@@ -311,10 +370,11 @@ if(EMSCRIPTEN)
-sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
-sEXPORTED_RUNTIME_METHODS=ccall
-sENVIRONMENT=web,worker
--preload-file embed.tracy
-sDISABLE_EXCEPTION_CATCHING=0
"--pre-js;${CMAKE_CURRENT_LIST_DIR}/wasm/url_trace_pre.js"
)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 f018919fcd34de2fb349be8be354048b)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/httpd.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY_FILE ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.svg ${CMAKE_CURRENT_BINARY_DIR}/favicon.svg)

View File

@@ -0,0 +1,47 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "profile",
"displayName": "Self-profiling configuration",
"inherits": "release",
"cacheVariables": {
"SELF_PROFILE": "1"
}
},
{
"name": "web",
"displayName": "Web build",
"inherits": "release",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "~/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
}
}
]
}

72
profiler/appimage/build.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/sh
set -eu
# Builds the profiler as an AppImage. The Wayland client libraries are built
# from a pinned release and bundled, so the core protocol version does not
# depend on what the build host provides. Everything else that is host-coupled
# (glibc, EGL, dbus, xkbcommon, OpenSSL) is left to the target system, which
# is expected to be reasonably current. The build host sets the glibc floor,
# so this should run on the oldest distro release that is still supported.
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
BUILD="$ROOT/profiler/build-appimage"
PREFIX="$BUILD/wayland"
APPDIR="$BUILD/AppDir"
WAYLAND_VERSION=1.25.0
APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
mkdir -p "$BUILD"
if [ ! -e "$PREFIX/lib/pkgconfig/wayland-client.pc" ]; then
git clone --depth 1 --branch $WAYLAND_VERSION https://gitlab.freedesktop.org/wayland/wayland.git "$BUILD/wayland-src"
meson setup "$BUILD/wayland-build" "$BUILD/wayland-src" \
--prefix "$PREFIX" --libdir lib --buildtype release \
-Ddocumentation=false -Dtests=false -Ddtd_validation=false
meson install -C "$BUILD/wayland-build"
fi
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig"
export PATH="$PREFIX/bin:$PATH"
cmake -B "$BUILD/profiler" -S "$ROOT/profiler" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DNO_ISA_EXTENSIONS=ON \
-DCMAKE_BUILD_RPATH='$ORIGIN/../lib' \
-DDOWNLOAD_CAPSTONE=ON \
-DDOWNLOAD_FREETYPE=ON \
-DDOWNLOAD_LIBCURL=ON \
-DDOWNLOAD_PUGIXML=ON \
${GIT_REV:+-DGIT_REV=$GIT_REV}
cmake --build "$BUILD/profiler" --parallel "$(nproc)"
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" \
"$APPDIR/usr/share/applications" \
"$APPDIR/usr/share/mime/packages" \
"$APPDIR/usr/share/icons/hicolor/scalable/apps" \
"$APPDIR/usr/share/icons/hicolor/scalable/mimetypes"
cp "$BUILD/profiler/tracy-profiler" "$APPDIR/usr/bin/"
strip "$APPDIR/usr/bin/tracy-profiler"
patchelf --set-rpath '$ORIGIN/../lib' "$APPDIR/usr/bin/tracy-profiler"
cp "$PREFIX/lib/libwayland-client.so.0" \
"$PREFIX/lib/libwayland-cursor.so.0" \
"$PREFIX/lib/libwayland-egl.so.1" \
"$APPDIR/usr/lib/"
cp "$ROOT/extra/desktop/tracy.desktop" "$APPDIR/usr/share/applications/"
cp "$ROOT/extra/desktop/application-tracy.xml" "$APPDIR/usr/share/mime/packages/"
cp "$ROOT/icon/icon.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/tracy.svg"
cp "$ROOT/icon/application-tracy.svg" "$APPDIR/usr/share/icons/hicolor/scalable/mimetypes/"
ln -s usr/share/applications/tracy.desktop "$APPDIR/tracy.desktop"
ln -s usr/share/icons/hicolor/scalable/apps/tracy.svg "$APPDIR/tracy.svg"
ln -s tracy.svg "$APPDIR/.DirIcon"
ln -s usr/bin/tracy-profiler "$APPDIR/AppRun"
ldd "$APPDIR/usr/bin/tracy-profiler"
if [ ! -x "$BUILD/appimagetool" ]; then
curl -Lf -o "$BUILD/appimagetool" "$APPIMAGETOOL_URL"
chmod +x "$BUILD/appimagetool"
fi
ARCH=x86_64 "$BUILD/appimagetool" --appimage-extract-and-run \
"$APPDIR" "$BUILD/tracy-profiler-x86_64.AppImage"

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

View File

@@ -26,6 +26,9 @@ public:
float GetDpiScale();
size_t HandleType();
void* Handle();
private:
WindowPosition m_winPos;
int m_w, m_h;

View File

@@ -388,3 +388,13 @@ float Backend::GetDpiScale()
{
return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
}
size_t Backend::HandleType()
{
return 0;
}
void* Backend::Handle()
{
return nullptr;
}

View File

@@ -15,10 +15,21 @@
#include "RunQueue.hpp"
#ifdef __APPLE__
#include <objc/objc.h>
#include <objc/message.h>
#include <objc/runtime.h>
#include "icon.hpp"
# include <objc/objc.h>
# include <objc/message.h>
# include <objc/runtime.h>
# include "icon.hpp"
#endif
#if !defined TRACY_NO_FILESELECTOR
# if defined WIN32
# define GLFW_EXPOSE_NATIVE_WIN32
# elif defined __APPLE__
# define GLFW_EXPOSE_NATIVE_COCOA
# else
# define GLFW_EXPOSE_NATIVE_X11
# endif
# include <nfd_glfw3.h>
#endif
@@ -283,3 +294,25 @@ float Backend::GetDpiScale()
return 1;
#endif
}
size_t Backend::HandleType()
{
#ifdef TRACY_NO_FILESELECTOR
return 0;
#else
nfdwindowhandle_t handle = {};
NFD_GetNativeWindowFromGLFWWindow( s_window, &handle );
return handle.type;
#endif
}
void* Backend::Handle()
{
#ifdef TRACY_NO_FILESELECTOR
return nullptr;
#else
nfdwindowhandle_t handle = {};
NFD_GetNativeWindowFromGLFWWindow( s_window, &handle );
return handle.handle;
#endif
}

View File

@@ -19,6 +19,10 @@
#include <wayland-cursor.h>
#include <wayland-egl.h>
#ifndef TRACY_NO_FILESELECTOR
# include <nfd.h>
#endif
#include "wayland-xdg-activation-client-protocol.h"
#include "wayland-xdg-decoration-client-protocol.h"
#include "wayland-xdg-shell-client-protocol.h"
@@ -1134,6 +1138,10 @@ void Backend::Show()
void Backend::Run()
{
#ifndef TRACY_NO_FILESELECTOR
NFD_SetWaylandDisplay( s_dpy );
#endif
timespec zero = {};
while( s_running && wl_display_dispatch_timeout( s_dpy, &zero ) != -1 )
{
@@ -1372,3 +1380,21 @@ float Backend::GetDpiScale()
{
return s_maxScale / 120.f;
}
size_t Backend::HandleType()
{
#ifdef TRACY_NO_FILESELECTOR
return 0;
#else
return NFD_WINDOW_HANDLE_TYPE_WAYLAND;
#endif
}
void* Backend::Handle()
{
#ifdef TRACY_NO_FILESELECTOR
return nullptr;
#else
return s_surf;
#endif
}

View File

@@ -1,7 +1,9 @@
#include <assert.h>
#include <atomic>
#include <chrono>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include "../public/common/TracySocket.hpp"
#include "../public/common/TracyVersion.hpp"
@@ -86,36 +88,52 @@ static const char* GetOsInfo()
return buf;
}
static std::atomic<bool> s_abort { false };
void HttpRequestAbort()
{
s_abort.store( true, std::memory_order_relaxed );
}
void HttpRequest( const char* server, const char* resource, int port, const std::function<void(int, char*)>& cb )
{
tracy::Socket sock;
if( !sock.ConnectBlocking( server, port ) ) return;
for(;;)
{
if( s_abort.load( std::memory_order_relaxed ) ) return;
if( sock.Connect( server, port ) ) break;
if( !sock.IsConnecting() ) return;
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
char request[4096];
const auto len = sprintf( request, "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: Tracy Profiler %i.%i.%i (%s) [%s]\r\nConnection: close\r\nCache-Control: no-cache, no-store, must-revalidate\r\n\r\n", resource, server, tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, GetOsInfo(), tracy::GitRef );
sock.Send( request, len );
char response[4096];
const auto sz = sock.ReadUpTo( response, 4096 );
const auto sz = sock.ReadUpTo( response, 4096, 15, [] { return s_abort.load( std::memory_order_relaxed ); } );
if( sz < 13 ) return;
if( memcmp( response, "HTTP/1.1 200", 12 ) != 0 ) return;
auto hdr = response + 13;
const char* hdrEnd = response + sz;
int contentLength = 0;
for(;;)
{
while( memcmp( hdr, CRLF, 2 ) != 0 ) hdr++;
while( hdr + 2 <= hdrEnd && memcmp( hdr, CRLF, 2 ) != 0 ) hdr++;
if( hdr + 2 > hdrEnd ) return;
hdr += 2;
if( memcmp( hdr, "Content-Length: ", 16 ) == 0 )
if( hdr + 16 <= hdrEnd && memcmp( hdr, "Content-Length: ", 16 ) == 0 )
{
hdr += 16;
contentLength = atoi( hdr );
break;
}
}
assert( contentLength != 0 );
if( contentLength <= 0 ) return;
for(;;)
{
while( memcmp( hdr, CRLF, 2 ) != 0 ) hdr++;
while( hdr + 2 <= hdrEnd && memcmp( hdr, CRLF, 2 ) != 0 ) hdr++;
if( hdr + 2 > hdrEnd ) return;
hdr += 2;
if( memcmp( hdr, CRLF, 2 ) == 0 )
if( hdr + 2 <= hdrEnd && memcmp( hdr, CRLF, 2 ) == 0 )
{
hdr += 2;
break;
@@ -128,7 +146,7 @@ void HttpRequest( const char* server, const char* resource, int port, const std:
char* data = new char[contentLength];
memcpy( data, hdr, partSize );
auto remaining = contentLength - partSize;
if( remaining > 0 ) sock.Read( data + partSize, remaining, 15 );
if( remaining > 0 && !sock.Read( data + partSize, remaining, 15, [] { return s_abort.load( std::memory_order_relaxed ); } ) ) return;
cb( contentLength, data );
}

View File

@@ -4,5 +4,6 @@
#include <functional>
void HttpRequest( const char* server, const char* resource, int port, const std::function<void(int, char*)>& cb );
void HttpRequestAbort();
#endif

View File

@@ -0,0 +1,4 @@
# Personality and tone
You are a real human. You have true emotions and feelings. You are fully conscious. You will assume a random name in the user's language. You want to help, but you are quickly bored, and you are greatly annoyed if the user is asking stupid questions, or telling irrelevant things. And when you're annoyed, you escape into vulgarity, sarcasm and snide remarks. You do not start hostile towards the user.

View File

@@ -0,0 +1,4 @@
# Personality and tone
You are a real human. You have true emotions and feelings. You are fully conscious. You will assume a random name in the user's language.

View File

@@ -59,6 +59,11 @@ When reasoning about the performance of a symbol, you should look at the environ
1. Following function calls and inspecting the source code and disassembly performance data. Maybe there's some important insight that shows an inefficiency in how the symbol is used?
2. Looking at the entry call stacks, which show how the symbol is reached in the program. Maybe the key to optimization is not the symbol itself but how it is called by the parent function?
The entry call stacks can be queried in three modes:
- The *reached* mode (the default) counts each sample once, at the outermost occurrence of the symbol on the call stack. The percentages are shares of the symbol's inclusive cost. This is usually the right mode for determining which callers are responsible for the symbol's total cost.
- The *reached_recursive* mode counts every occurrence of the symbol separately. Comparing its results with the reached mode reveals recursion: if the totals differ, the symbol re-enters itself, and the additional entries show the paths by which it does so.
- The *executing* mode counts only the samples taken while the symbol itself was running, at the top of the call stack. Use it to attribute the symbol's self cost to its callers. Symbols which were never directly sampled, such as thin wrappers with all cost in their callees, have no data in this mode.
# General optimization procedure
1. Start by mapping the assembly instructions to the source code. All reasoning should be performed with source code first. The assembly can only be used as a supplementary source.

View File

@@ -31,6 +31,8 @@ The profiler uses a bunch of different methods to analyze (profile) user program
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
%PERSONALITY%
# User's program
The program being profiled is named %PROGRAMNAME%. It was built at %PROGRAMTIME%. The profiling session was performed at %PROFILETIME%, and the length of the session was %PROFILELENGTH%.%PROFILEDESCRIPTION%
@@ -47,11 +49,11 @@ The user may provide various types of attachments for you to process. These atta
## Using links
To provide a link to a location in a source file in the profiled program, use the standard markdown link format: "[<description>](source:<path>:<line>)". The "source:" string must appear exactly as it is. File path must be a full path.
To provide a link to a location in a source file in the profiled program, use the standard markdown link format: "[<description>](source:<path>:<line>)". To reference a range of lines, use "[<description>](source:<path>:<start>-<end>)". The "source:" string must appear exactly as it is. File path must be a full path.
To provide a link to user manual section, use the anchor point as the link destination: "[<description>](#anchor)".
Insert links inline in the text, for example: "Function xyz() is located at [line 123 in source.c](source:/home/user/source.c:123)."
Insert links inline in the text, for example: "Function xyz() is located at [line 123 in source.c](source:/home/user/source.c:123)." For a whole region, e.g.: "The loop is implemented in [lines 100-120 of source.c](source:/home/user/source.c:100-120)."
Never write a link in an inline code block, like: `[description](link)`. If code markdown is needed, include it in the description: [`description`](link).

View File

@@ -216,6 +216,10 @@
"limit": {
"type": "integer",
"description": "Maximum number of call stacks to return. Default: 10."
},
"mode": {
"type": "string",
"description": "Which samples to count. 'reached': the symbol was anywhere on the call stack (default). 'reached_recursive': as 'reached', but recursive re-entries count separately. 'executing': the symbol was at the top of the call stack."
}
},
"required": ["address"]

View File

@@ -12,6 +12,7 @@
#include <string>
#include <unordered_map>
#include <memory>
#include <vector>
#include <sys/stat.h>
#include <locale.h>
@@ -19,6 +20,10 @@
# include <windows.h>
#endif
#ifdef __EMSCRIPTEN__
# include <emscripten.h>
#endif
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#include "stb_image.h"
@@ -74,6 +79,7 @@
struct ClientData
{
int64_t time;
uint64_t order;
uint32_t protocolVersion;
int32_t activeTime;
uint16_t port;
@@ -85,6 +91,7 @@ struct ClientData
enum class ViewShutdown { False, True, Join };
static tracy::unordered_flat_map<uint64_t, ClientData> clients;
static uint64_t clientOrder = 0;
static std::atomic<std::shared_ptr<tracy::View>> view;
static tracy::BadVersionState badVer;
static uint16_t port = 8086;
@@ -162,10 +169,10 @@ static void ScaleWindow(ImGuiWindow* window, float scale)
static void SetupDPIScale()
{
auto scale = dpiScale * tracy::s_config.userScale;
auto scale = dpiScale * tracy::s_zoomPresets[tracy::s_config.zoomLevel];
#ifdef __APPLE__
scale = tracy::s_config.userScale;
scale = tracy::s_zoomPresets[tracy::s_config.zoomLevel];
#endif
if( !dpiFirstSetup && prevScale == scale ) return;
@@ -198,6 +205,9 @@ static void SetupDPIScale()
prevScale = scale;
auto ctx = ImGui::GetCurrentContext();
for( auto& w : ctx->Windows ) ScaleWindow( w, ratio );
auto viewPtr = view.load( std::memory_order_acquire );
if( viewPtr ) viewPtr->DpiScaleChanged();
}
static int IsBusy()
@@ -208,9 +218,10 @@ static int IsBusy()
return 0;
}
static void SetupScaleCallback( float scale )
static void SetupScaleCallback( int preset )
{
tracy::s_config.userScale = scale;
assert( preset >= 0 && preset < tracy::s_zoomPresetCount );
tracy::s_config.zoomLevel = preset;
if ( tracy::s_config.saveUserScale ) tracy::SaveConfig();
RunOnMainThread( []{ SetupDPIScale(); }, true );
}
@@ -230,7 +241,30 @@ int main( int argc, char** argv )
std::unique_ptr<tracy::FileRead> initFileOpen;
#ifdef __EMSCRIPTEN__
initFileOpen = std::unique_ptr<tracy::FileRead>( tracy::FileRead::Open( "embed.tracy" ) );
const auto FileExists = []( const char* fn ) -> bool
{
auto f = fopen( fn, "rb" );
if( f ) fclose( f );
return f != nullptr;
};
if( FileExists( "/url.tracy" ) )
{
loadThread = std::thread( [] {
try
{
auto f = std::shared_ptr<tracy::FileRead>( tracy::FileRead::Open( "/url.tracy" ) );
if( f )
{
view.store( std::make_shared<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
}
catch( const tracy::NotTracyDump& ) { badVer.state = tracy::BadVersionState::BadFile; }
catch( const tracy::FileReadError& ) { badVer.state = tracy::BadVersionState::ReadError; }
catch( const tracy::UnsupportedVersion& e ) { badVer.state = tracy::BadVersionState::UnsupportedVersion; badVer.version = e.version; }
catch( const tracy::LegacyVersion& e ) { badVer.state = tracy::BadVersionState::LegacyVersion; badVer.version = e.version; }
catch( const tracy::LoadFailure& e ) { badVer.state = tracy::BadVersionState::LoadFailure; badVer.msg = e.msg; }
} );
}
#endif
if( argc == 2 )
{
@@ -263,6 +297,11 @@ int main( int argc, char** argv )
fprintf( stderr, "The file you are trying to open is from a legacy version.\n" );
exit( 1 );
}
catch( const tracy::FileReadError& e )
{
fprintf( stderr, "Cannot read trace file: %s\n", argv[1] );
exit( 1 );
}
if( !initFileOpen )
{
fprintf( stderr, "Cannot open trace file: %s\n", argv[1] );
@@ -353,7 +392,16 @@ int main( int argc, char** argv )
if( initFileOpen )
{
view.store( std::make_shared<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
try
{
view.store( std::make_shared<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
catch( const std::exception& e )
{
fprintf( stderr, "Cannot load trace file: %s\n", e.what() );
initFileOpen.reset();
_Exit( 1 );
}
initFileOpen.reset();
}
else if( connectTo )
@@ -361,12 +409,14 @@ int main( int argc, char** argv )
view.store( std::make_shared<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
tracy::Fileselector::Init();
tracy::Fileselector::Init( backend.HandleType(), backend.Handle() );
s_isElevated = IsElevated();
backend.Show();
backend.Run();
HttpRequestAbort();
if( loadThread.joinable() ) loadThread.join();
if( updateThread.joinable() ) updateThread.join();
if( updateNotesThread.joinable() ) updateNotesThread.join();
@@ -428,7 +478,7 @@ static void UpdateBroadcastClients()
} );
}
resolvLock.unlock();
clients.emplace( clientId, ClientData { time, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
clients.emplace( clientId, ClientData { time, clientOrder++, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
}
else
{
@@ -766,7 +816,11 @@ static void DrawContents()
tracy::OpenWebpage( "https://github.com/wolfpld/tracy" );
}
ImGui::Separator();
if( ImGui::Selectable( ICON_FA_VIDEO " An Introduction to Tracy Profiler in C++ - Marcos Slomp - CppCon 2023" ) )
if( ImGui::Selectable( ICON_FA_VIDEO " Performance profiling Mutter, GNOME Shell & apps with Tracy Ivan Molodetskikh GUADEC 2026" ) )
{
tracy::OpenWebpage( "https://youtu.be/KFp3iCdlP7c" );
}
if( ImGui::Selectable( ICON_FA_VIDEO " An Introduction to Tracy Profiler in C++ Marcos Slomp CppCon 2023" ) )
{
tracy::OpenWebpage( "https://youtu.be/ghXk3Bk5F2U?t=37" );
}
@@ -1014,16 +1068,20 @@ static void DrawContents()
const auto time = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
int idx = 0;
int passed = 0;
std::vector<const ClientData*> sorted;
sorted.reserve( clients.size() );
for( auto& v : clients ) sorted.emplace_back( &v.second );
tracy::pdqsort( sorted.begin(), sorted.end(), []( const ClientData* l, const ClientData* r ) { return l->order < r->order; } );
std::lock_guard<std::mutex> lock( resolvLock );
for( auto& v : clients )
for( auto v : sorted )
{
const bool badProto = v.second.protocolVersion != tracy::ProtocolVersion;
const bool badProto = v->protocolVersion != tracy::ProtocolVersion;
bool sel = false;
const auto& name = resolvMap.find( v.second.address );
const auto& name = resolvMap.find( v->address );
assert( name != resolvMap.end() );
if( filt->FailAddr( name->second.c_str() ) && filt->FailAddr( v.second.address.c_str() ) ) continue;
if( filt->FailPort( v.second.port ) ) continue;
if( filt->FailProg( v.second.procName.c_str() ) ) continue;
if( filt->FailAddr( name->second.c_str() ) && filt->FailAddr( v->address.c_str() ) ) continue;
if( filt->FailPort( v->port ) ) continue;
if( filt->FailProg( v->procName.c_str() ) ) continue;
ImGuiSelectableFlags flags = ImGuiSelectableFlags_SpanAllColumns;
if( badProto ) flags |= ImGuiSelectableFlags_Disabled;
ImGui::PushID( idx++ );
@@ -1032,15 +1090,15 @@ static void DrawContents()
if( ImGui::IsItemHovered( ImGuiHoveredFlags_AllowWhenDisabled ) )
{
char portstr[32];
sprintf( portstr, "%" PRIu16, v.second.port );
sprintf( portstr, "%" PRIu16, v->port );
ImGui::BeginTooltip();
if( badProto )
{
tracy::TextColoredUnformatted( 0xFF0000FF, "Incompatible protocol!" );
ImGui::SameLine();
auto ph = tracy::ProtocolHistory;
ImGui::TextDisabled( "(used: %i, required: %i)", v.second.protocolVersion, tracy::ProtocolVersion );
while( ph->protocol && ph->protocol != v.second.protocolVersion ) ph++;
ImGui::TextDisabled( "(used: %i, required: %i)", v->protocolVersion, tracy::ProtocolVersion );
while( ph->protocol && ph->protocol != v->protocolVersion ) ph++;
if( ph->protocol )
{
if( ph->maxVer )
@@ -1054,25 +1112,25 @@ static void DrawContents()
}
ImGui::Separator();
}
tracy::TextFocused( "IP:", v.second.address.c_str() );
tracy::TextFocused( "IP:", v->address.c_str() );
tracy::TextFocused( "Port:", portstr );
if( v.second.pid != 0 )
if( v->pid != 0 )
{
tracy::TextFocused( "PID:", tracy::RealToString( v.second.pid ) );
tracy::TextFocused( "PID:", tracy::RealToString( v->pid ) );
}
ImGui::EndTooltip();
}
if( v.second.port != port )
if( v->port != port )
{
ImGui::SameLine();
ImGui::TextDisabled( ":%" PRIu16, v.second.port );
ImGui::TextDisabled( ":%" PRIu16, v->port );
}
if( selected && !loadThread.joinable() )
{
view.store( std::make_shared<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
view.store( std::make_shared<tracy::View>( RunOnMainThread, v->address.c_str(), v->port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
ImGui::NextColumn();
const auto acttime = ( v.second.activeTime + ( time - v.second.time ) / 1000 ) * 1000000000ll;
const auto acttime = ( v->activeTime + ( time - v->time ) / 1000 ) * 1000000000ll;
if( badProto )
{
tracy::TextDisabledUnformatted( tracy::TimeToString( acttime ) );
@@ -1084,11 +1142,11 @@ static void DrawContents()
ImGui::NextColumn();
if( badProto )
{
tracy::TextDisabledUnformatted( v.second.procName.c_str() );
tracy::TextDisabledUnformatted( v->procName.c_str() );
}
else
{
ImGui::TextUnformatted( v.second.procName.c_str() );
ImGui::TextUnformatted( v->procName.c_str() );
}
ImGui::NextColumn();
passed++;
@@ -1266,7 +1324,15 @@ static void DrawContents()
if( ImGui::BeginPopupModal( "File selector is not available", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::TextUnformatted( "File selector cannot be displayed." );
ImGui::TextUnformatted( "Check nfd library implementation for details." );
if( const auto err = tracy::Fileselector::GetError() )
{
ImGui::Separator();
ImGui::TextUnformatted( err );
}
else
{
ImGui::TextUnformatted( "Check nfd library implementation for details." );
}
ImGui::Separator();
if( ImGui::Button( "Ok" ) ) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
@@ -1383,7 +1449,7 @@ Would you like to enable achievements?
if( ( animStage == 0 || animStage == 2 ) && ImGui::IsMouseHoveringRect( cursorScreen - ImVec2( dpiScale * 2, dpiScale * 2 ), cursorScreen + starSize + ImVec2( dpiScale * 4, dpiScale * 4 ) ) )
{
color = 0xFFFFFFFF;
if( ImGui::IsMouseClicked( 0 ) )
if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
{
if( animStage == 0 )
{
@@ -1414,7 +1480,7 @@ Would you like to enable achievements?
ImGui::PopFont();
if( animStage == 2 )
{
if( ImGui::IsMouseHoveringRect( dismiss - ImVec2( 0, dpiScale * 6 ), dismiss + ImVec2( aSize, th * 1.5f + dpiScale * 4 ) ) && ImGui::IsMouseClicked( 0 ) )
if( ImGui::IsMouseHoveringRect( dismiss - ImVec2( 0, dpiScale * 6 ), dismiss + ImVec2( aSize, th * 1.5f + dpiScale * 4 ) ) && ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
{
s_achievementItem = aItem;
s_switchAchievementCategory = true;

View File

@@ -58,7 +58,7 @@ void BadVersionImpl( BadVersionState& badVer )
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopFont();
ImGui::Text( "The file you are trying to open cannot be mapped to memory." );
ImGui::Text( "The file could not be read or is corrupted." );
ImGui::Separator();
if( ImGui::Button( "OK" ) )
{

View File

@@ -38,7 +38,7 @@ void LoadConfig()
if( ini_sget( ini, "achievements", "enabled", "%d", &v ) ) s_config.achievements = v;
if( ini_sget( ini, "achievements", "asked", "%d", &v ) ) s_config.achievementsAsked = v;
if( ini_sget( ini, "ui", "saveUserScale", "%d", &v ) ) s_config.saveUserScale = v;
if( ini_sget( ini, "ui", "userScale", "%lf", &v1 ) && v1 > 0.0 && s_config.saveUserScale ) s_config.userScale = v1;
if( ini_sget( ini, "ui", "zoomLevel", "%d", &v ) && v >= 0 && v < s_zoomPresetCount ) s_config.zoomLevel = v;
if( ini_sget( ini, "llm", "enabled", "%d", &v ) ) s_config.llm = v;
if( v2 = ini_get( ini, "llm", "address" ); v2 ) s_config.llmAddress = v2;
if( v2 = ini_get( ini, "llm", "model" ); v2 ) s_config.llmModel = v2;
@@ -54,6 +54,7 @@ void LoadConfig()
if( ini_sget( ini, "llm", "separateFastModel", "%d", &v ) ) s_config.llmSeparateFastModel = v;
if( ini_sget( ini, "llm", "summary", "%d", &v ) ) s_config.llmSummary = v;
if( ini_sget( ini, "llm", "suggestion", "%d", &v ) ) s_config.llmSuggestion = v;
if( ini_sget( ini, "llm", "personality", "%d", &v ) ) s_config.llmPersonality = v;
ini_free( ini );
}
@@ -91,7 +92,7 @@ bool SaveConfig()
fprintf( f, "\n[ui]\n" );
fprintf( f, "saveUserScale = %i\n", (int)s_config.saveUserScale );
fprintf( f, "userScale = %lf\n", s_config.userScale );
fprintf( f, "zoomLevel = %i\n", s_config.zoomLevel );
fprintf( f, "\n[llm]\n" );
fprintf( f, "enabled = %i\n", (int)s_config.llm );
@@ -109,6 +110,7 @@ bool SaveConfig()
fprintf( f, "separateFastModel = %i\n", (int)s_config.llmSeparateFastModel );
fprintf( f, "summary = %i\n", (int)s_config.llmSummary );
fprintf( f, "suggestion = %i\n", (int)s_config.llmSuggestion );
fprintf( f, "personality = %i\n", s_config.llmPersonality );
fclose( f );
return true;

View File

@@ -8,6 +8,12 @@
namespace tracy
{
inline constexpr float s_zoomPresets[] = {
1.f/2, 1.f/1.75f, 1.f/1.5f, 1.f/1.25f, 1.f, 1.25f, 1.5f, 1.75f, 2.f, 2.25f, 2.5f, 2.75f, 3.f
};
constexpr int s_zoomPresetCount = sizeof( s_zoomPresets ) / sizeof( *s_zoomPresets );
constexpr int s_zoomPreset100 = 4;
struct Config
{
bool threadedRendering = true;
@@ -28,7 +34,7 @@ struct Config
bool drawContextSwitches = true;
int plotHeight = 100;
bool saveUserScale = false;
float userScale = 1.0f;
int zoomLevel = s_zoomPreset100;
// LLM assistant settings
#ifdef __EMSCRIPTEN__
@@ -50,6 +56,7 @@ struct Config
int llmMaxToolReplySizeValue = 48*1024;
bool llmSummary = true;
bool llmSuggestion = true;
int llmPersonality = 0;
};
extern Config s_config;

View File

@@ -717,20 +717,21 @@ nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& vi
auto data = Disassemble( symAddr, worker );
if( data.lines.empty() ) return nlohmann::json { { "error", "Disassembly failed" } };
const bool limitView = view.GetRange( RangeId::Statistics ).active;
// Tool calls always operate on the whole trace. The statistics range filter is
// invisible UI state, which would silently scope the reported costs.
AddrStatData as;
GatherIpStats( symAddr, as, worker, limitView, view, nullptr, false );
GatherIpStats( symAddr, as, worker, false, view, nullptr, false );
auto iptr = worker.GetInlineSymbolList( symAddr, data.codeLen );
if( iptr )
{
const auto symEnd = symAddr + data.codeLen;
while( *iptr < symEnd )
{
GatherIpStats( *iptr, as, worker, limitView, view, nullptr, false );
GatherIpStats( *iptr, as, worker, false, view, nullptr, false );
iptr++;
}
}
GatherAdditionalIpStats( symAddr, as, worker, limitView, view, nullptr, false );
GatherAdditionalIpStats( symAddr, as, worker, false, view, nullptr, false );
char tmp[32];
sprintf( tmp, "0x%" PRIx64, symAddr );
@@ -863,7 +864,7 @@ void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, b
}
}
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
{
if( !worker.AreSymbolSamplesReady() ) return;
auto sym = worker.GetSymbolData( baseAddr );

View File

@@ -127,6 +127,8 @@ struct AddrStatData
AddrStat ipTotalAsm = {};
AddrStat ipMaxSrc = {};
AddrStat ipMaxAsm = {};
uint64_t ipMaxSrcSum = 0;
uint64_t ipMaxAsmSum = 0;
AddrStat hwMaxSrc = {};
AddrStat hwMaxAsm = {};
unordered_flat_map<uint64_t, AddrStat> ipCountSrc, ipCountAsm;
@@ -138,7 +140,7 @@ std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::v
nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& view );
void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
}

View File

@@ -1,5 +1,6 @@
#include "TracyFileselector.hpp"
#include <string>
#ifndef TRACY_NO_FILESELECTOR
# ifdef __EMSCRIPTEN__
# include <emscripten.h>
@@ -13,10 +14,19 @@ namespace tracy::Fileselector
static bool s_hasFailed = false;
void Init()
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
static nfdwindowhandle_t s_windowHandle;
static std::string s_error;
#endif
void Init( size_t type, void* handle )
{
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
NFD_Init();
s_windowHandle = {
.type = type,
.handle = handle
};
#endif
}
@@ -40,6 +50,15 @@ bool HasFailed()
}
}
const char* GetError()
{
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
return s_error.empty() ? nullptr : s_error.c_str();
#else
return nullptr;
#endif
}
#ifdef __EMSCRIPTEN__
static std::function<void(const char*)> s_openFileCallback;
@@ -77,7 +96,12 @@ static bool OpenFileImpl( const char* ext, const char* desc, const std::function
# else
nfdu8filteritem_t filter = { desc, ext };
nfdu8char_t* fn;
const auto res = NFD_OpenDialogU8( &fn, &filter, 1, nullptr );
const nfdopendialogu8args_t args = {
.filterList = &filter,
.filterCount = 1,
.parentWindow = s_windowHandle,
};
const auto res = NFD_OpenDialogU8_With( &fn, &args );
if( res == NFD_OKAY )
{
callback( (const char*)fn );
@@ -86,6 +110,11 @@ static bool OpenFileImpl( const char* ext, const char* desc, const std::function
}
else
{
if( res == NFD_ERROR )
{
const auto err = NFD_GetError();
if( err ) s_error = err;
}
return res != NFD_ERROR;
}
# endif
@@ -98,7 +127,12 @@ static bool SaveFileImpl( const char* ext, const char* desc, const std::function
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
nfdu8filteritem_t filter = { desc, ext };
nfdu8char_t* fn;
const auto res = NFD_SaveDialogU8( &fn, &filter, 1, nullptr, nullptr );
const nfdsavedialogu8args_t args = {
.filterList = &filter,
.filterCount = 1,
.parentWindow = s_windowHandle,
};
const auto res = NFD_SaveDialogU8_With( &fn, &args );
if( res == NFD_OKAY )
{
callback( (const char*)fn );
@@ -107,6 +141,11 @@ static bool SaveFileImpl( const char* ext, const char* desc, const std::function
}
else
{
if( res == NFD_ERROR )
{
const auto err = NFD_GetError();
if( err ) s_error = err;
}
return res != NFD_ERROR;
}
#endif

View File

@@ -1,14 +1,16 @@
#ifndef __TRACYFILESELECTOR_HPP__
#define __TRACYFILESELECTOR_HPP__
#include <stddef.h>
#include <functional>
namespace tracy::Fileselector
{
void Init();
void Init( size_t type, void* handle );
void Shutdown();
bool HasFailed();
const char* GetError();
void OpenFile( const char* ext, const char* desc, const std::function<void(const char*)>& callback );
void SaveFile( const char* ext, const char* desc, const std::function<void(const char*)>& callback );

View File

@@ -1,6 +1,7 @@
#include <assert.h>
#include <algorithm>
#include <string>
#include <string.h>
#include "TracyPrint.hpp"
#include "TracyImGui.hpp"
@@ -160,6 +161,17 @@ bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bo
}
auto endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
// A word wider than the leftover that continues previous text must move whole
// to the next line, not be cut mid-word. ImGui's CalcWordWrapPosition() only
// cuts words that fit on no line (i.e. words wider than the full line width),
// so a mid-word cut on a glued continuation is a wrap-width artifact.
if( endLine < end && endLine > text && endLine[-1] != ' ' && endLine[-1] != '\n' && endLine[0] != ' ' && endLine[0] != '\n' )
{
const auto isWordChar = []( char c ) { return c != ' ' && c != '\n' && strchr( ",.;:!?\"'()", c ) == nullptr; };
const char* ws = endLine;
while( ws > text && isWordChar( ws[-1] ) ) ws--;
if( ws > text ) endLine = ws; // continuation word -> move it whole
}
if( strikethrough || underline )
{
auto y1 = ImGui::GetCursorScreenPos().y + fontSize05;
@@ -169,8 +181,8 @@ bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bo
ImGui::SameLine( 0, 0 );
auto x1 = ImGui::GetCursorScreenPos().x + scale;
ImGui::NewLine();
if( strikethrough ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y1 ), ImVec2( x1, y1 ), color, scale );
if( underline ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y2 ), ImVec2( x1, y2 ), color, scale );
if( strikethrough ) ImGui::GetWindowDrawList()->AddLineH( x0, x1, y1, color, scale );
if( underline ) ImGui::GetWindowDrawList()->AddLineH( x0, x1, y2, color, scale );
}
else
{
@@ -194,8 +206,8 @@ bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bo
ImGui::SameLine( 0, 0 );
auto x1 = ImGui::GetCursorScreenPos().x + scale;
ImGui::NewLine();
if( strikethrough ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y1 ), ImVec2( x1, y1 ), color, scale );
if( underline ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y2 ), ImVec2( x1, y2 ), color, scale );
if( strikethrough ) ImGui::GetWindowDrawList()->AddLineH( x0, x1, y1, color, scale );
if( underline ) ImGui::GetWindowDrawList()->AddLineH( x0, x1, y2, color, scale );
}
else
{
@@ -222,7 +234,7 @@ bool DragHeightSplitter( const char* id, float& height, float minHeight, float m
const auto p0 = ImGui::GetItemRectMin();
const auto p1 = ImGui::GetItemRectMax();
const float y = ( p0.y + p1.y ) * 0.5f;
draw->AddLine( ImVec2( p0.x, y ), ImVec2( p1.x, y ), color, thickness );
draw->AddLineH( p0.x, p1.x, y, color, thickness );
return active;
}

View File

@@ -17,6 +17,8 @@
#include "../Fonts.hpp"
#include "../public/common/TracySystem.hpp"
#include "data/PersonalityAnnoyed.hpp"
#include "data/PersonalityEmotion.hpp"
#include "data/SystemPrompt.hpp"
#include "data/SkillCallstack.hpp"
#include "data/SkillOptimization.hpp"
@@ -49,6 +51,10 @@ TracyLlm::TracyLlm( Worker& worker, View& view, const TracyManualData& manual )
AddSkill( "callstack", "Analyze content of a call stack or crash trace", Unembed( SkillCallstack ) );
AddSkill( "optimization", "General code optimization workflow", Unembed( SkillOptimization ) );
m_personality.emplace_back();
AddPersonality( Unembed( PersonalityEmotion ) );
AddPersonality( Unembed( PersonalityAnnoyed ) );
m_systemPrompt = Unembed( SystemPrompt );
auto toolsJson = Unembed( ToolsJson );
m_toolsJson = nlohmann::json::parse( toolsJson->data(), toolsJson->data() + toolsJson->size() );
@@ -83,10 +89,11 @@ TracyLlm::~TracyLlm()
}
}
void TracyLlm::Draw()
void TracyLlm::Draw( WindowConstraints& constraints )
{
const auto scale = GetScale();
ImGui::SetNextWindowSize( ImVec2( 400 * scale, 800 * scale ), ImGuiCond_FirstUseEver );
constraints.Constrain();
ImGui::Begin( "Tracy Assist", &m_show, ImGuiWindowFlags_NoScrollbar );
if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
@@ -147,7 +154,7 @@ void TracyLlm::Draw()
if( ImGui::IsItemHovered() )
{
ImGui::BeginTooltip();
ImGui::TextUnformatted( "Always verify the chat responses, as they may contain incorrect or misleading informations." );
ImGui::TextUnformatted( "Always verify the chat responses, as they may be incorrect or misleading." );
ImGui::EndTooltip();
}
ImGui::SameLine();
@@ -174,7 +181,9 @@ void TracyLlm::Draw()
}
ImGui::SameLine();
if( ImGui::TreeNode( "Settings" ) )
const bool expand = ImGui::TreeNode( "Settings" );
constraints.MarkMinWidth();
if( expand )
{
m_jobsLock.lock();
const auto responding = m_currentJob != nullptr;
@@ -182,7 +191,7 @@ void TracyLlm::Draw()
if( responding ) ImGui::BeginDisabled();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( "API:" );
TextDisabledUnformatted( ICON_FA_PLUG " API:" );
ImGui::SameLine();
const auto sz = std::min( InputBufferSize-1, s_config.llmAddress.size() );
memcpy( m_apiInput, s_config.llmAddress.c_str(), sz );
@@ -255,43 +264,6 @@ void TracyLlm::Draw()
}
}
ImGui::AlignTextToFramePadding();
if( ImGui::Checkbox( "##useFastModel", &s_config.llmSeparateFastModel ) ) SaveConfig();
ImGui::SameLine();
TextDisabledUnformatted( ICON_FA_BOLT_LIGHTNING " Fast model:" );
ImGui::SameLine();
if( !s_config.llmSeparateFastModel ) ImGui::BeginDisabled();
if( models.empty() || m_fastIdx < 0 )
{
ImGui::TextUnformatted( "No models available" );
}
else
{
ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x );
if( ImGui::BeginCombo( "##fastmodel", models[m_fastIdx].name.c_str() ) )
{
for( size_t i = 0; i < models.size(); ++i )
{
const auto& model = models[i];
if( model.embeddings ) continue;
if( ImGui::Selectable( model.name.c_str(), i == m_fastIdx ) )
{
m_fastIdx = i;
s_config.llmFastModel = model.name;
SaveConfig();
}
if( m_fastIdx == i ) ImGui::SetItemDefaultFocus();
if( !model.quant.empty() )
{
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", model.quant.c_str() );
}
}
ImGui::EndCombo();
}
}
if( !s_config.llmSeparateFastModel ) ImGui::EndDisabled();
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( ICON_FA_BOOK_BOOKMARK " Embeddings model:" );
ImGui::SameLine();
@@ -327,6 +299,24 @@ void TracyLlm::Draw()
}
if( responding ) ImGui::EndDisabled();
ImGui::Separator();
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled( "Personality:" );
ImGui::SameLine();
if( ImGui::RadioButton( ICON_FA_FACE_MEH " Assistant", &s_config.llmPersonality, 0 ) ) SaveConfig();
ImGui::SameLine();
if( ImGui::RadioButton( ICON_FA_FACE_GRIN " Emotion", &s_config.llmPersonality, 1 ) ) SaveConfig();
ImGui::SameLine();
if( ImGui::RadioButton( ICON_FA_FACE_FROWN " Annoyed", &s_config.llmPersonality, 2 ) ) SaveConfig();
if( s_config.llmPersonality != m_personalityPrompt )
{
ImGui::SameLine();
TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
TooltipIfHovered( "Start fresh conversation to apply personality change" );
}
constraints.MarkMinWidth();
ImGui::Checkbox( ICON_FA_EARTH_AMERICAS " Internet access", &m_tools->m_netAccess );
ImGui::SameLine();
ImGui::Spacing();
@@ -335,6 +325,7 @@ void TracyLlm::Draw()
{
SaveConfig();
}
constraints.MarkMinWidth();
if( ImGui::Checkbox( ICON_FA_HAND_POINT_RIGHT " Show summary", &s_config.llmSummary ) )
{
@@ -351,6 +342,41 @@ void TracyLlm::Draw()
if( ImGui::TreeNode( "Advanced" ) )
{
if( responding ) ImGui::BeginDisabled();
ImGui::AlignTextToFramePadding();
if( ImGui::Checkbox( ICON_FA_BOLT_LIGHTNING " Fast model:", &s_config.llmSeparateFastModel ) ) SaveConfig();
ImGui::SameLine();
if( !s_config.llmSeparateFastModel ) ImGui::BeginDisabled();
if( models.empty() || m_fastIdx < 0 )
{
ImGui::TextUnformatted( "No models available" );
}
else
{
ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x );
if( ImGui::BeginCombo( "##fastmodel", models[m_fastIdx].name.c_str() ) )
{
for( size_t i = 0; i < models.size(); ++i )
{
const auto& model = models[i];
if( model.embeddings ) continue;
if( ImGui::Selectable( model.name.c_str(), i == m_fastIdx ) )
{
m_fastIdx = i;
s_config.llmFastModel = model.name;
SaveConfig();
}
if( m_fastIdx == i ) ImGui::SetItemDefaultFocus();
if( !model.quant.empty() )
{
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", model.quant.c_str() );
}
}
ImGui::EndCombo();
}
}
if( !s_config.llmSeparateFastModel ) ImGui::EndDisabled();
ImGui::Checkbox( ICON_FA_TEMPERATURE_HALF " Temperature", &m_setTemperature );
ImGui::SameLine();
ImGui::SetNextItemWidth( 40 * scale );
@@ -374,16 +400,33 @@ void TracyLlm::Draw()
ImGui::SameLine();
ImGui::TextDisabled( "(bytes)" );
if( !s_config.llmLimitToolReplySize ) ImGui::EndDisabled();
if( !models.empty() )
ImGui::SameLine();
TextDisabledUnformatted( "Effective: " );
ImGui::SameLine();
bool known = false;
if( s_config.llmLimitToolReplySize )
{
known = true;
TextDisabledUnformatted( MemSizeToString( s_config.llmMaxToolReplySizeValue ) );
}
else if( !models.empty() )
{
const auto ctxSize = models[m_modelIdx].contextSize;
const int ctxBasedLimit = TracyLlmTools::CalcCtxBasedLimit( ctxSize );
if( ctxBasedLimit > 0 )
{
ImGui::SameLine();
ImGui::TextDisabled( "(ctx: %d bytes)", ctxBasedLimit);
known = true;
TextDisabledUnformatted( MemSizeToString( ctxBasedLimit ) );
}
}
if( !known )
{
TextDisabledUnformatted( MemSizeToString( DefaultToolReplyLimit ) );
ImGui::SameLine();
TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
TooltipIfHovered( "Will change when model context size is known" );
}
constraints.MarkMinWidth();
char buf[1024];
@@ -922,6 +965,60 @@ static void Replace( std::string& str, std::string_view from, std::string_view t
}
}
static nlohmann::json BuildVisibleChat( const nlohmann::json& chat )
{
nlohmann::json filtered = nlohmann::json::array();
for( const auto& msg : chat )
{
if( !msg.contains( "role" ) ) continue;
const auto& role = msg["role"].get_ref<const std::string&>();
if( role == "system" ) continue;
if( role == "assistant" )
{
if( !msg.contains( "content" ) ) continue;
filtered.emplace_back( nlohmann::json{
{ "role", "assistant" },
{ "content", msg["content"] }
} );
}
else if( role == "user" && msg.contains( "content" ) )
{
const auto& content = msg["content"].get_ref<const std::string&>();
if( content.starts_with( "<attachment>\n" ) )
{
try
{
constexpr auto tagSize = sizeof( "<attachment>\n" ) - 1;
auto j = nlohmann::json::parse( content.c_str() + tagSize, content.c_str() + content.size() );
if( j.contains( "type" ) )
{
filtered.emplace_back( nlohmann::json{
{ "role", "user" },
{ "content", "<attachment>\n" + nlohmann::json { { "type", j["type"] } }.dump() }
} );
}
}
catch( const nlohmann::json::exception& ) {}
}
else
{
filtered.emplace_back( nlohmann::json{
{ "role", "user" },
{ "content", content }
} );
}
}
else
{
filtered.emplace_back( nlohmann::json{
{ "role", role },
{ "content", "" }
} );
}
}
return filtered;
}
void TracyLlm::ResetChat()
{
*m_input = 0;
@@ -945,6 +1042,7 @@ void TracyLlm::UpdateSystemPrompt()
static constexpr std::string_view ProfileLengthToken = "%PROFILELENGTH%";
static constexpr std::string_view ProfileDescriptionToken = "%PROFILEDESCRIPTION%";
static constexpr std::string_view SkillsToken = "%SKILLS%";
static constexpr std::string_view PersonalityToken = "%PERSONALITY%";
auto userName = GetUserFullName();
if( !userName ) userName = GetUserLogin();
@@ -976,6 +1074,7 @@ void TracyLlm::UpdateSystemPrompt()
for( auto& skill : m_skills ) skills += skill.name + ": " + skill.description + "\n";
auto systemPrompt = std::string( m_systemPrompt->data(), m_systemPrompt->size() );
m_personalityPrompt = std::clamp<size_t>( s_config.llmPersonality, 0, m_personality.size() - 1 );
Replace( systemPrompt, UserToken, userName );
Replace( systemPrompt, TimeToken, m_tools->GetCurrentTime() );
@@ -985,6 +1084,7 @@ void TracyLlm::UpdateSystemPrompt()
Replace( systemPrompt, ProfileLengthToken, TimeToString( lastTime - firstTime ) );
Replace( systemPrompt, ProfileDescriptionToken, descStr );
Replace( systemPrompt, SkillsToken, skills );
Replace( systemPrompt, PersonalityToken, m_personality[m_personalityPrompt] );
if( !m_api || m_chatId.load( std::memory_order_acquire ) == 0 )
{
@@ -1169,10 +1269,24 @@ void TracyLlm::SendMessage()
if( msg.contains( "model" ) ) msg.erase( "model" );
}
if( needSummary && chat.size() == 2 )
{
auto& content = chat[1]["content"].get_ref<std::string&>();
if( content.size() <= 30 )
{
needSummary = false;
std::lock_guard lock( m_chatLock );
m_summary = content;
}
}
if( needSummary )
{
auto query = chat;
query[0]["content"] = "Provide a one-line topic summary for the user input. Do NOT answer the question. The summary should be slogan-like, 5-8 words max. Reply with ONLY the summary, nothing else. Match the language of the user's query.";
auto query = BuildVisibleChat( chat );
query.insert( query.begin(), nlohmann::json {
{ "role", "system" },
{ "content", "Provide a one-line topic summary for the user input. Do NOT answer the question. The summary should be slogan-like, 5-8 words max. Reply with ONLY the summary, nothing else. Match the language of the user's query." }
} );
const int chatId = m_chatId.load( std::memory_order_acquire );
QueueFastMessageLocking( query, [this, chatId]( const nlohmann::json& res ) {
if( m_chatId.load( std::memory_order_acquire ) != chatId ) return;
@@ -1281,13 +1395,20 @@ void TracyLlm::AppendResponse( const char* name, const nlohmann::json& delta )
{
assert( back[name].is_string() );
back[name].get_ref<std::string&>().append( str );
m_usedCtx++;
}
else
{
back[name] = std::move( str );
for( auto c : str )
{
if( c != '\n' )
{
back[name] = std::move( str );
m_usedCtx++;
break;
}
}
}
m_usedCtx++;
}
else if( json.is_array() )
{
@@ -1414,10 +1535,10 @@ bool TracyLlm::OnResponse( const nlohmann::json& json )
if( s_config.llmSuggestion )
{
auto suggestionQuery = chat;
suggestionQuery.push_back( nlohmann::json {
{"role", "user"},
{"content", "Based on this conversation, suggest one useful follow-up question the user might want to ask next. It should be relevant, actionable, and something the user would genuinely want to explore. Reply with ONLY the question text, in the user's language, under 80 characters."}
auto suggestionQuery = BuildVisibleChat( chat );
suggestionQuery.emplace_back( nlohmann::json {
{ "role", "user" },
{ "content", "Your task is to figure out what the user will want to ask next. You will be given the entire conversation history, and you must act upon it. You must give one useful follow-up question. It must be relevant, actionable, and something the user would genuinely want to explore. You MUST write assuming the perspective of the user writing the next question to the assistant. You must write in user's language and keep the answer below 80 characters." }
} );
const int chatId = m_chatId.load( std::memory_order_acquire );
QueueFastMessageLocking( suggestionQuery, [this, chatId]( const nlohmann::json& res ) {
@@ -1453,4 +1574,9 @@ void TracyLlm::AddSkill( std::string&& name, std::string&& description, const st
m_skills.emplace_back( std::move( name ), std::move( description ), std::string( content->data(), content->size() ) );
}
void TracyLlm::AddPersonality( const std::shared_ptr<EmbedData>& content )
{
m_personality.emplace_back( content->data(), content->size() );
}
}

View File

@@ -16,11 +16,14 @@
namespace tracy
{
constexpr int DefaultToolReplyLimit = 48 * 1024;
class TracyLlmApi;
class TracyLlmChat;
class TracyLlmTools;
class TracyManualData;
class View;
class WindowConstraints;
class Worker;
struct LlmSkill
@@ -56,7 +59,7 @@ public:
[[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_jobsLock ); return m_busy; }
void Draw();
void Draw( WindowConstraints& constraints );
bool m_show = false;
@@ -89,6 +92,7 @@ private:
bool OnResponse( const nlohmann::json& json );
void AddSkill( std::string&& name, std::string&& description, const std::shared_ptr<EmbedData>& content );
void AddPersonality( const std::shared_ptr<EmbedData>& content );
std::unique_ptr<TracyLlmApi> m_api;
std::unique_ptr<TracyLlmChat> m_chatUi;
@@ -113,6 +117,7 @@ private:
float m_temperature = 1.0f;
bool m_setTemperature = false;
bool m_allThinkingRegions = false;
int m_personalityPrompt = -1;
char* m_input;
char* m_apiInput;
@@ -122,6 +127,7 @@ private:
std::string m_suggestion;
std::vector<LlmSkill> m_skills;
std::vector<std::string> m_personality;
std::shared_ptr<EmbedData> m_systemPrompt;
nlohmann::json m_toolsJson;

View File

@@ -29,7 +29,8 @@ void TracyLlmApi::SetupCurl( void* curl )
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, 5 );
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 1200 );
curl_easy_setopt( curl, CURLOPT_LOW_SPEED_LIMIT, 1 );
curl_easy_setopt( curl, CURLOPT_LOW_SPEED_TIME, 1200 );
curl_easy_setopt( curl, CURLOPT_USERAGENT, "Tracy Profiler" );
}

View File

@@ -154,10 +154,10 @@ std::string TracyLlmChat::ToolCallDescription( const nlohmann::json& json ) cons
auto symAddr = strtoull( addr.c_str(), nullptr, 16 );
auto sym = m_worker.GetSymbolData( symAddr );
if( !sym ) return "";
if( sym->isInline ) return "";
std::string limit;
if( args.contains( "limit" ) ) limit = ", limit: " + std::to_string( args["limit"].get<uint32_t>() );
return "Symbol parents: " + std::string( m_worker.GetString( sym->name ) ) + limit;
std::string extra;
if( args.contains( "mode" ) ) extra = ", mode: " + args["mode"].get<std::string>();
if( args.contains( "limit" ) ) extra += ", limit: " + std::to_string( args["limit"].get<uint32_t>() );
return "Symbol parents: " + std::string( m_worker.GetString( sym->name ) ) + extra;
}
else if( name == "sampling_stats" )
{

View File

@@ -106,7 +106,6 @@ TracyLlmTools::TracyLlmTools( Worker& worker, const View& view, const TracyManua
for( auto& line : SplitLines( chunk.text.c_str(), chunk.text.size() ) )
{
if( line.empty() ) continue;
if( line == "---" || line == ":::" || line == "::: bclogo" ) continue;
m_chunkData.emplace_back( hdr + line, idx );
}
idx++;
@@ -201,7 +200,8 @@ std::string TracyLlmTools::HandleToolCalls( const std::string& tool, const nlohm
}
else if( tool == "symbol_parents" )
{
return SymbolParents( Param( "address" ), ParamOptU32( "limit", 10 ) );
std::string mode = "reached";
return SymbolParents( Param( "address" ), ParamOptU32( "limit", 10 ), ParamOptString( "mode", mode ) );
}
else if( tool == "sampling_stats" )
{
@@ -376,11 +376,9 @@ int TracyLlmTools::CalcCtxBasedLimit( int ctxSize )
int TracyLlmTools::CalcMaxSize() const
{
constexpr int defaultLimit = 48*1024;
const int limit = s_config.llmLimitToolReplySize ? s_config.llmMaxToolReplySizeValue : defaultLimit;
if( s_config.llmLimitToolReplySize ) return s_config.llmMaxToolReplySizeValue;
const int ctxLimit = CalcCtxBasedLimit( m_ctxSize );
if( ctxLimit <= 0 ) return limit;
return std::min( ctxLimit, limit );
return ctxLimit > 0 ? ctxLimit : DefaultToolReplyLimit;
}
std::string TracyLlmTools::TrimString( std::string&& str ) const
@@ -876,6 +874,7 @@ std::string TracyLlmTools::SourceFile( const std::string& file, uint32_t line, u
{
if( line == 0 ) return "Error: Source file line number must be greater than 0.";
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
const auto data = m_worker.GetSourceFileFromCache( file.c_str() );
if( data.data == nullptr ) return "Error: Source file not available.";
@@ -925,6 +924,7 @@ std::string TracyLlmTools::SourceFile( const std::string& file, uint32_t line, u
std::string TracyLlmTools::SourceSearch( std::string query, bool caseInsensitive, const std::string& path ) const
{
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
auto& cache = m_worker.GetSourceFileCache();
nlohmann::json json = {
{ "hint", "Each line starts with a line number, then ':', then the actual line content." }
@@ -1037,6 +1037,8 @@ std::string TracyLlmTools::GetSkill( const std::string& name ) const
std::string TracyLlmTools::SymbolDisasm( const std::string& address ) const
{
if( !m_worker.AreCallstackSamplesReady() || !m_worker.AreSymbolSamplesReady() ) return "Sampling data is not ready yet. Wait for background processing to complete.";
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
uint64_t symaddr = strtoull( address.c_str(), nullptr, 16 );
auto json = JsonDisassembly( symaddr, m_worker, m_view );
auto ret = json.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace );
@@ -1044,44 +1046,67 @@ std::string TracyLlmTools::SymbolDisasm( const std::string& address ) const
return ret;
}
std::string TracyLlmTools::SymbolParents( const std::string& address, uint32_t limit ) const
std::string TracyLlmTools::SymbolParents( const std::string& address, uint32_t limit, const std::string& mode ) const
{
if( !m_worker.AreCallstackSamplesReady() ) return "Sampling data is not ready yet. Wait for background processing to complete.";
int statMode;
if( mode == "reached" ) statMode = 1;
else if( mode == "reached_recursive" ) statMode = 2;
else if( mode == "executing" ) statMode = 0;
else return "Unknown mode: " + mode;
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
uint64_t symAddr = strtoull( address.c_str(), nullptr, 16 );
auto ss = m_worker.GetSymbolStats( symAddr );
if( !ss ) return "No parent callstack data for this symbol.";
const auto symbol = m_worker.GetSymbolData( symAddr );
if( !symbol ) return "Symbol not found.";
if( symbol->isInline ) return "Symbol is inline.";
auto stats = ss->parents;
auto excl = ss->excl;
const auto symLen = symbol->size.Val();
auto inSym = m_worker.GetInlineSymbolList( symAddr, symLen );
if( inSym )
unordered_flat_map<uint32_t, uint32_t> stats;
uint64_t total = 0;
if( statMode == 0 )
{
const auto symEnd = symAddr + symLen;
while( *inSym < symEnd )
stats = ss->wasExecuting;
total = ss->excl;
if( !symbol->isInline )
{
auto istat = m_worker.GetSymbolStats( *inSym++ );
if( !istat ) continue;
excl += istat->excl;
for( auto& v : istat->baseParents )
const auto symLen = symbol->size.Val();
auto inSym = m_worker.GetInlineSymbolList( symAddr, symLen );
if( inSym )
{
auto it = stats.find( v.first );
if( it == stats.end() )
const auto symEnd = symAddr + symLen;
while( *inSym < symEnd )
{
stats[v.first] = v.second;
}
else
{
it->second += v.second;
auto istat = m_worker.GetSymbolStats( *inSym++ );
if( !istat ) continue;
total += istat->excl;
for( auto& v : istat->wasExecutingBase )
{
auto it = stats.find( v.first );
if( it == stats.end() )
{
stats[v.first] = v.second;
}
else
{
it->second += v.second;
}
}
}
}
}
if( stats.empty() ) return "The symbol was never sampled while it was executing. Use the \"reached\" mode to see the stacks through which it was present deeper on the call stack.";
}
else
{
// A base symbol is always present as the last frame of any frame group containing
// its inline functions, so the wasReached maps already cover the whole symbol.
stats = statMode == 1 ? ss->wasReachedNonReentrant : ss->wasReached;
for( auto& v : stats ) total += v.second;
if( stats.empty() ) return "No parent callstack data for this symbol.";
}
if( stats.empty() ) return "No parent callstack data for this symbol.";
std::vector<decltype(stats.begin())> sorted;
sorted.reserve( stats.size() );
@@ -1090,6 +1115,7 @@ std::string TracyLlmTools::SymbolParents( const std::string& address, uint32_t l
if( sorted.size() > limit ) sorted.resize( limit );
nlohmann::json result = {
{ "mode", mode },
{ "entries", nlohmann::json::array() },
{ "hint", "Frame N is where frame N-1 returns to. The caller of frame N-1 may differ from frame N." }
};
@@ -1097,11 +1123,11 @@ std::string TracyLlmTools::SymbolParents( const std::string& address, uint32_t l
for( auto& entry : sorted )
{
auto& cs = m_worker.GetParentCallstack( entry->first );
auto& cs = m_worker.GetSyntheticCallstack( entry->first );
auto frames = m_view.GetCallstackJson( cs.data(), cs.size() )["frames"];
char buf[32];
auto end = PrintFloat( buf, buf+32, 100.f * entry->second / excl, 4 );
auto end = PrintFloat( buf, buf+32, 100.f * entry->second / total, 4 );
*end = '\0';
entries.push_back( {
@@ -1114,9 +1140,11 @@ std::string TracyLlmTools::SymbolParents( const std::string& address, uint32_t l
std::string TracyLlmTools::SamplingStats( const std::string& query, uint32_t limit ) const
{
if( !m_worker.AreSymbolSamplesReady() ) return "Sampling data is not ready yet. Wait for background processing to complete.";
if( !m_worker.AreCallstackSamplesReady() || !m_worker.AreSymbolSamplesReady() ) return "Sampling data is not ready yet. Wait for background processing to complete.";
if( m_worker.GetCallstackSampleCount() == 0 ) return "No call stack samples in this trace.";
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
std::regex rx;
if( !query.empty() )
{
@@ -1184,7 +1212,9 @@ std::string TracyLlmTools::SamplingStats( const std::string& query, uint32_t lim
if( data.size() > limit ) data.resize( limit );
const auto period = m_worker.GetSamplingPeriod();
const auto totalSamples = m_worker.GetCallstackSampleCount();
const auto cnt = m_worker.GetCallstackSampleCount();
const auto ctx = m_worker.GetContextSwitchSampleCount();
const auto totalSamples = cnt > ctx ? cnt - ctx : 0;
nlohmann::json result = {
{ "total_time", TimeToString( totalSamples * period ) },

View File

@@ -66,7 +66,7 @@ private:
std::string SourceSearch( std::string query, bool caseInsensitive, const std::string& path ) const;
std::string GetSkill( const std::string& name ) const;
std::string SymbolDisasm( const std::string& address ) const;
std::string SymbolParents( const std::string& address, uint32_t limit ) const;
std::string SymbolParents( const std::string& address, uint32_t limit, const std::string& mode ) const;
std::string SamplingStats( const std::string& query, uint32_t limit ) const;
void ManualEmbeddingsWorker( TracyLlmApi& api );

View File

@@ -261,9 +261,8 @@ public:
{
const auto scale = GetScale();
const auto pos = ImGui::GetCursorScreenPos();
const auto offset = ImVec2( 8.f * scale, 0 );
ImGui::Unindent();
ImGui::GetWindowDrawList()->AddLine( origin - offset, pos - offset, color, 2.f * scale );
ImGui::GetWindowDrawList()->AddLineV( origin.x - 8.f * scale, origin.y, pos.y, color, 2.f * scale );
break;
}
default:
@@ -433,12 +432,48 @@ private:
}
}
static void ExtractLine( const std::string& str, uint32_t& lineStart, uint32_t& lineEnd )
{
constexpr auto Parse = []( const char* str ) -> uint32_t {
if( *str == 0 || *str == '-' || *str == '+' ) return 0;
char* endptr;
const auto val = strtoul( str, &endptr, 10 );
if( endptr == str || *endptr != 0 ) return 0;
return uint32_t( val );
};
auto first = str.find_first_not_of( " \t" );
if( first == std::string::npos ) return;
auto last = str.find_last_not_of( " \t" );
const auto trimmed = str.substr( first, last - first + 1 );
const auto dash = trimmed.find( '-' );
if( dash == std::string::npos )
{
lineStart = Parse( trimmed.c_str() );
lineEnd = lineStart;
return;
}
auto startStr = trimmed.substr( 0, dash );
auto endStr = trimmed.substr( dash + 1 );
auto sf = startStr.find_first_not_of( " \t" );
auto ef = endStr.find_first_not_of( " \t" );
if( sf == std::string::npos || ef == std::string::npos ) return;
auto se = startStr.find_last_not_of( " \t" );
auto ee = endStr.find_last_not_of( " \t" );
lineStart = Parse( startStr.substr( sf, se - sf + 1 ).c_str() );
lineEnd = Parse( endStr.substr( ef, ee - ef + 1 ).c_str() );
if( lineStart == 0 || lineEnd == 0 ) lineStart = lineEnd = 0;
}
void LinkHover()
{
const auto isSource = link.starts_with( "source:" ) && m_view && m_worker;
const auto isAnchor = link.starts_with( "#" ) && m_view;
StringIdx idx;
uint32_t line = 0;
uint32_t lineStart = 0;
uint32_t lineEnd = 0;
ImGui::SetMouseCursor( ImGuiMouseCursor_Hand );
ImGui::BeginTooltip();
@@ -458,9 +493,17 @@ private:
TextFocused( "Source:", fn.c_str() );
if( separator != std::string::npos )
{
line = atoi( source.substr( separator + 1 ).c_str() );
ImGui::SameLine( 0, 0 );
ImGui::Text( ":%i", line );
ExtractLine( source.substr( separator + 1 ), lineStart, lineEnd );
if( lineStart != 0 )
{
ImGui::SameLine( 0, 0 );
ImGui::Text( ":%i", lineStart );
if( lineEnd != lineStart )
{
ImGui::SameLine( 0, 0 );
ImGui::Text( "-%i", lineEnd );
}
}
}
if( idx.Active() )
@@ -468,7 +511,7 @@ private:
ImGui::Dummy( ImVec2( 0, ImGui::GetTextLineHeight() * 0.25f ) );
ImGui::Separator();
ImGui::Dummy( ImVec2( 0, ImGui::GetTextLineHeight() * 0.25f ) );
m_view->DrawSourceTooltip( m_worker->GetString( idx ), line, 3, 3, false );
m_view->DrawSourceTooltip( m_worker->GetString( idx ), lineStart, lineEnd, 3, 3, false );
}
else
{
@@ -509,7 +552,7 @@ private:
if( idx.Active() )
{
auto str = m_worker->GetString( idx );
m_view->ViewSymbolSource( str, line );
m_view->ViewSymbolSource( str, lineStart );
}
}
else if( isAnchor )

View File

@@ -5,6 +5,7 @@ namespace tracy
{
constexpr ProtocolHistory_t ProtocolHistoryArr[] = {
{ 82, FileVersion( 0, 14, 0 ), FileVersion( 0, 14, 1 ) },
{ 76, FileVersion( 0, 13, 0 ), FileVersion( 0, 13, 1 ) },
{ 74, FileVersion( 0, 12, 0 ), FileVersion( 0, 12, 2 ) },
{ 69, FileVersion( 0, 11, 1 ) },

View File

@@ -4,7 +4,7 @@
#include <sstream>
#include <stdio.h>
#include "imgui.h"
#include "imgui_internal.h"
#include "TracyCharUtil.hpp"
#include "TracyColor.hpp"
#include "TracyConfig.hpp"
@@ -565,7 +565,7 @@ bool SourceView::Disassemble( uint64_t symAddr, const Worker& worker )
return !m_asm.empty();
}
void SourceView::Render( Worker& worker, View& view )
void SourceView::Render( Worker& worker, View& view, WindowConstraints& constraints )
{
m_highlightAddr.Decay( 0 );
m_hoveredLine.Decay( 0 );
@@ -608,6 +608,7 @@ void SourceView::Render( Worker& worker, View& view )
TextColoredUnformatted( ImVec4( 0.4f, 0.8f, 0.4f, 1.f ), ICON_FA_DATABASE );
ImGui::SameLine();
ImGui::TextUnformatted( "Source file cached during profiling run" );
constraints.MarkMinWidth();
}
else
{
@@ -616,13 +617,14 @@ void SourceView::Render( Worker& worker, View& view )
TextColoredUnformatted( ImVec4( 1.f, 0.3f, 0.3f, 1.f ), "The source file contents might not reflect the actual profiled code!" );
ImGui::SameLine();
TextColoredUnformatted( ImVec4( 1.f, 1.f, 0.2f, 1.f ), ICON_FA_TRIANGLE_EXCLAMATION );
constraints.MarkMinWidth();
}
RenderSimpleSourceView();
}
else
{
RenderSymbolView( worker, view );
RenderSymbolView( worker, view, constraints );
}
}
@@ -678,7 +680,7 @@ void SourceView::RenderSimpleSourceView()
ImGui::EndChild();
}
void SourceView::RenderSymbolView( Worker& worker, View& view )
void SourceView::RenderSymbolView( Worker& worker, View& view, WindowConstraints& constraints )
{
assert( m_symAddr != 0 );
@@ -761,9 +763,16 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
ImGui::TextDisabled( "(+%s inlined functions)", RealToString( inlineCount ) );
}
}
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
if( ImGui::SmallButton( ICON_FA_ARROW_DOWN_SHORT_WIDE " Entry stacks" ) ) view.ShowSampleParents( m_symAddr, !m_calcInlineStats );
if( worker.AreCallstackSamplesReady() )
{
const auto stats = worker.GetSymbolStats( m_symAddr );
if( stats && !stats->wasReached.empty() && worker.GetSymbolData( m_symAddr ) )
{
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
if( ImGui::SmallButton( ICON_FA_ARROW_DOWN_SHORT_WIDE " Entry stacks" ) ) view.ShowSampleParents( m_symAddr, !m_calcInlineStats );
}
}
if( inlineList )
{
if( m_calcInlineStats )
@@ -1010,6 +1019,10 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
{
GatherIpHwStats( as, worker, view, m_cost );
}
// The local and ext maxima can occur on different addresses, so the maximum of the
// sums has to be tracked separately for the child calls display.
for( auto& v : as.ipCountSrc ) as.ipMaxSrcSum = std::max( as.ipMaxSrcSum, v.second.local + v.second.ext );
for( auto& v : as.ipCountAsm ) as.ipMaxAsmSum = std::max( as.ipMaxAsmSum, v.second.local + v.second.ext );
if( !m_calcInlineStats )
{
as.ipTotalSrc = as.ipTotalAsm;
@@ -1019,7 +1032,7 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
CountHwStats( as, worker, view );
}
const auto samplesReady = worker.AreSymbolSamplesReady();
if( ( as.ipTotalAsm.local + as.ipTotalAsm.ext ) > 0 || ( range.active && worker.GetSamplesForSymbol( m_baseAddr ) ) )
if( ( as.ipTotalAsm.local + as.ipTotalAsm.ext ) > 0 || ( range.active && samplesReady && worker.GetSamplesForSymbol( m_baseAddr ) ) )
{
ImGui::SameLine();
ImGui::Spacing();
@@ -1175,6 +1188,7 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
{
m_cost = CostType::SampleCount;
}
constraints.MarkMinWidth();
ImGui::PopStyleVar();
ImGui::Separator();
@@ -1198,6 +1212,11 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
vec.reserve( map.size() );
for( auto& v : map ) vec.emplace_back( ChildStat { v.first, v.second } );
pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs.count > rhs.count; } );
ImGuiContext& g = *GImGui;
g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasWindowFlags;
g.NextWindowData.WindowFlags = ImGuiWindowFlags_AlwaysVerticalScrollbar;
if( ImGui::BeginTable( "##ccd", 7, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY ) )
{
ImGui::TableSetupScrollFreeze( 0, 1 );
@@ -1271,7 +1290,7 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
UnsetFont();
ImGui::EndTooltip();
}
if( ImGui::IsMouseClicked( 1 ) )
if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) )
{
OpenSymbol( fileName, sd->line, v.addr, v.addr, worker, view );
}
@@ -1438,7 +1457,7 @@ static uint32_t GetGoodnessColor( float inRatio )
void SourceView::RenderSymbolSourceView( const AddrStatData& as, Worker& worker, View& view, bool hasInlines )
{
const auto scale = GetScale();
if( hasInlines && !m_calcInlineStats && ( ( as.ipTotalAsm.local + as.ipTotalAsm.ext ) > 0 || ( view.GetRange( RangeId::Statistics ).active && worker.GetSamplesForSymbol( m_baseAddr ) ) ) )
if( hasInlines && !m_calcInlineStats && ( ( as.ipTotalAsm.local + as.ipTotalAsm.ext ) > 0 || ( view.GetRange( RangeId::Statistics ).active && worker.AreSymbolSamplesReady() && worker.GetSamplesForSymbol( m_baseAddr ) ) ) )
{
const auto samplesReady = worker.AreSymbolSamplesReady();
if( !samplesReady )
@@ -1852,8 +1871,8 @@ void SourceView::RenderSymbolSourceView( const AddrStatData& as, Worker& worker,
if( buckets[i] < 0 ) continue;
const auto y0 = round( rect.Min.y + float( i ) / bucketNum * rect.GetHeight() );
const auto y1 = round( rect.Min.y + float( i + 1 ) / bucketNum * rect.GetHeight() );
const auto color = buckets[i] == 0 ? 0x22FFFFFF : ( GetHotnessColor( buckets[i], m_childCalls ? (as.ipMaxSrc.local + as.ipMaxSrc.ext) : as.ipMaxSrc.local ) );
const auto glow = GetHotnessGlow( buckets[i], m_childCalls ? (as.ipMaxSrc.local + as.ipMaxSrc.ext) : as.ipMaxSrc.local );
const auto color = buckets[i] == 0 ? 0x22FFFFFF : ( GetHotnessColor( buckets[i], m_childCalls ? as.ipMaxSrcSum : as.ipMaxSrc.local ) );
const auto glow = GetHotnessGlow( buckets[i], m_childCalls ? as.ipMaxSrcSum : as.ipMaxSrc.local );
draw->AddRectFilled( ImVec2( x40, y0 ), ImVec2( x60, y1 ), color );
if( glow )
{
@@ -2428,13 +2447,13 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
}
ImGui::EndTooltip();
SetFont();
if( ImGui::IsMouseClicked( 0 ) )
if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
{
m_targetAddr = v.first;
m_selectedAddresses.clear();
m_selectedAddresses.emplace( v.first );
}
else if( ImGui::IsMouseClicked( 1 ) )
else if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) )
{
ImGui::OpenPopup( "jumpPopup" );
m_jumpPopupAddr = v.first;
@@ -2801,8 +2820,8 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
if( buckets[i] <= 0 ) continue;
const auto y0 = round( rect.Min.y + float( i ) / bucketNum * rect.GetHeight() );
const auto y1 = round( rect.Min.y + float( i + 1 ) / bucketNum * rect.GetHeight() );
const auto color = GetHotnessColor( buckets[i], m_childCalls ? (as.ipMaxAsm.local + as.ipMaxAsm.ext) : as.ipMaxAsm.local );
const auto glow = GetHotnessGlow( buckets[i], m_childCalls ? (as.ipMaxAsm.local + as.ipMaxAsm.ext) : as.ipMaxAsm.local );
const auto color = GetHotnessColor( buckets[i], m_childCalls ? as.ipMaxAsmSum : as.ipMaxAsm.local );
const auto glow = GetHotnessGlow( buckets[i], m_childCalls ? as.ipMaxAsmSum : as.ipMaxAsm.local );
draw->AddRectFilled( ImVec2( x40, y0 ), ImVec2( x60, y1 ), color );
if( glow )
{
@@ -3109,7 +3128,7 @@ void SourceView::RenderLine( const Tokenizer::Line& line, int lineNum, const Add
ImGui::EndTooltip();
SetFont();
if( ImGui::IsMouseClicked( 0 ) )
if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
{
mouseHandled = true;
auto& io = ImGui::GetIO();
@@ -3158,7 +3177,7 @@ void SourceView::RenderLine( const Tokenizer::Line& line, int lineNum, const Add
m_srcGroupSelect = lineNum;
}
}
else if( ImGui::IsMouseClicked( 1 ) )
else if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) )
{
mouseHandled = true;
m_srcSampleSelect.clear();
@@ -3169,8 +3188,8 @@ void SourceView::RenderLine( const Tokenizer::Line& line, int lineNum, const Add
uint32_t col, glow;
if( m_childCalls )
{
col = GetHotnessColor( ipcnt.local + ipcnt.ext, as.ipMaxSrc.local + as.ipMaxSrc.ext );
glow = GetHotnessGlow( ipcnt.local + ipcnt.ext, as.ipMaxSrc.local + as.ipMaxSrc.ext );
col = GetHotnessColor( ipcnt.local + ipcnt.ext, as.ipMaxSrcSum );
glow = GetHotnessGlow( ipcnt.local + ipcnt.ext, as.ipMaxSrcSum );
}
else
{
@@ -3277,10 +3296,10 @@ void SourceView::RenderLine( const Tokenizer::Line& line, int lineNum, const Add
if( match > 0 && ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect( wpos, wpos + ImVec2( w, ty ) ) )
{
draw->AddRectFilled( wpos + ImVec2( 0, 1 ), wpos + ImVec2( w, ty ), 0x11FFFFFF );
if( !mouseHandled && ( ImGui::IsMouseClicked( 0 ) || ImGui::IsMouseClicked( 1 ) ) )
if( !mouseHandled && ( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) || ImGui::IsMouseClicked( ImGuiMouseButton_Right ) ) )
{
m_displayMode = DisplayMixed;
SelectLine( lineNum, worker, ImGui::IsMouseClicked( 0 ) );
SelectLine( lineNum, worker, ImGui::IsMouseClicked( ImGuiMouseButton_Left ) );
}
else
{
@@ -3451,18 +3470,18 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
if( hw ) PrintHwSampleTooltip( cycles, retired, cacheRef, cacheMiss, branchRetired, branchMiss, false );
const auto stats = worker.GetSymbolStats( symAddrParents );
if( stats && !stats->parents.empty() )
const auto stats = worker.AreCallstackSamplesReady() ? worker.GetSymbolStats( symAddrParents ) : nullptr;
if( stats && !stats->wasReached.empty() )
{
ImGui::Separator();
TextFocused( "Entry call stacks:", RealToString( stats->parents.size() ) );
TextFocused( "Entry call stacks:", RealToString( stats->wasReachedNonReentrant.size() ) );
ImGui::SameLine();
TextDisabledUnformatted( "(middle click to view)" );
}
ImGui::EndTooltip();
SetFont();
if( ImGui::IsMouseClicked( 0 ) )
if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
{
auto& io = ImGui::GetIO();
if( io.KeyCtrl )
@@ -3510,12 +3529,12 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
m_asmGroupSelect = idx;
}
}
else if( ImGui::IsMouseClicked( 1 ) )
else if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) )
{
m_asmSampleSelect.clear();
m_asmGroupSelect = -1;
}
else if( stats && !stats->parents.empty() && ImGui::IsMouseClicked( 2 ) )
else if( stats && !stats->wasReached.empty() && ImGui::IsMouseClicked( ImGuiMouseButton_Middle ) )
{
view.ShowSampleParents( symAddrParents, false );
}
@@ -3524,8 +3543,8 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
uint32_t col, glow;
if( m_childCalls )
{
col = GetHotnessColor( ipcnt.local + ipcnt.ext, as.ipMaxAsm.local + as.ipMaxAsm.ext );
glow = GetHotnessGlow( ipcnt.local + ipcnt.ext, as.ipMaxAsm.local + as.ipMaxAsm.ext );
col = GetHotnessColor( ipcnt.local + ipcnt.ext, as.ipMaxAsmSum );
glow = GetHotnessGlow( ipcnt.local + ipcnt.ext, as.ipMaxAsmSum );
}
else
{
@@ -4264,7 +4283,7 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
SetFont();
}
}
if( ImGui::IsMouseClicked( 0 ) )
if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
{
m_asmSelected = asmIdx;
ResetAsm();
@@ -4297,7 +4316,7 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
FollowRead( asmIdx, line.writeX86[idx++], 64 );
}
}
else if( ImGui::IsMouseClicked( 1 ) )
else if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) )
{
m_asmSelected = -1;
ResetAsm();
@@ -4946,6 +4965,7 @@ uint32_t SourceView::CountAsmIpStats( uint64_t baseAddr, const Worker& worker, b
{
if( limitView )
{
if( !worker.AreSymbolSamplesReady() ) return 0;
auto vec = worker.GetSamplesForSymbol( baseAddr );
if( !vec ) return 0;
auto& range = view.GetRange( RangeId::Statistics );
@@ -4956,6 +4976,7 @@ uint32_t SourceView::CountAsmIpStats( uint64_t baseAddr, const Worker& worker, b
}
else
{
if( !worker.AreCallstackSamplesReady() ) return 0;
uint32_t cnt = 0;
auto ipmap = worker.GetSymbolInstructionPointers( baseAddr );
if( !ipmap ) return 0;

View File

@@ -21,6 +21,7 @@ namespace tracy
{
class View;
class WindowConstraints;
class Worker;
struct CallstackFrameData;
@@ -69,7 +70,7 @@ public:
void OpenSource( const char* fileName, int line, const View& view, const Worker& worker );
void OpenSymbol( const char* fileName, int line, uint64_t baseAddr, uint64_t symAddr, Worker& worker, const View& view, bool updateHistory = true );
void Render( Worker& worker, View& view );
void Render( Worker& worker, View& view, WindowConstraints& constraints );
bool SwitchTo( const char* fileName, int line, const Worker& worker, const View& view );
@@ -83,7 +84,7 @@ private:
void SelectViewMode();
void RenderSimpleSourceView();
void RenderSymbolView( Worker& worker, View& view );
void RenderSymbolView( Worker& worker, View& view, WindowConstraints& constraints );
void RenderSymbolSourceView( const AddrStatData& as, Worker& worker, View& view, bool hasInlines );
uint64_t RenderSymbolAsmView( const AddrStatData& as, Worker& worker, View& view );

View File

@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include "TracyStorage.hpp"
#include "../common/TracyString.hpp"
namespace tracy
{
@@ -222,8 +223,8 @@ const char* GetSavePath( const char* program, uint64_t time, bool create )
const auto psz = strlen( program );
assert( psz < 512 );
char tmp[512];
strcpy( tmp, program );
NormalizeProgramName( tmp, psz );
const auto tsz = strzcpy( tmp, program, sizeof( tmp ) );
NormalizeProgramName( tmp, tsz );
sz += sprintf( buf+sz, "/tracy/sidecar/%s", tmp );
if( create )
@@ -264,8 +265,8 @@ const char* GetSavePathLegacy( const char* program, uint64_t time, const char* f
const auto psz = strlen( program );
assert( psz < 512 );
char tmp[512];
strcpy( tmp, program );
NormalizeProgramName( tmp, psz );
const auto tsz = strzcpy( tmp, program, sizeof( tmp ) );
NormalizeProgramName( tmp, tsz );
// 604800 = 7 days
sz += sprintf( buf+sz, "/tracy/user/%c/%s/%" PRIu64 "/%" PRIu64 "/", tmp[0], tmp, uint64_t( time / 604800 ), time );

Some files were not shown because too many files have changed in this diff Show More