Compare commits

...

923 Commits

Author SHA1 Message Date
Bartosz Taudul
19519bbeb0 Merge pull request #1394 from bruno-dasilva/bruno/opengl-drift-correction
fix: add opengl drift correction for gpu zones
2026-06-07 22:50:40 +02:00
Bruno Da Silva
fc4f52e61d add opengl drift correction option to meson.options/meson.build 2026-06-07 20:14:36 +00:00
Bruno Da Silva
e2ac8f7973 fix: add opengl drift correction for gpu zones 2026-06-07 00:23:14 +00:00
Bartosz Taudul
e5aa8eba51 Merge pull request #1387 from Lectem/wip/offline-res-for-any-toolchain
Offline resolution for any toolchain
2026-06-06 14:48:54 +02:00
Clément Grégoire
7437c41514 Escape provided addr2line tool path 2026-06-06 14:47:24 +02:00
Bartosz Taudul
f441a5070b Wrap achievements column contents into child windows. 2026-06-06 13:19:56 +02:00
Bartosz Taudul
00b6abd67b Move achievements text to markdown files. 2026-06-06 13:19:55 +02:00
Bartosz Taudul
e4e3d75eb8 Add PDF link to built-in manual viewer. 2026-06-06 10:56:25 +02:00
Bartosz Taudul
fc5318dcad Some more markdown user manual compatibility fixes. 2026-06-06 01:53:05 +02:00
Bartosz Taudul
661c664b75 Converte LaTeX math in markdown to plain text. 2026-06-06 01:44:27 +02:00
Bartosz Taudul
6dbebca666 Reset user manual view scroll position after changing section from toc. 2026-06-06 01:28:24 +02:00
Bartosz Taudul
73d78ad517 Fix tables in markdown manual. 2026-06-06 01:25:49 +02:00
Bartosz Taudul
e5371d7987 Icons description separator must start at newline.
Otherwise it will clash with wrong things, like middle-of-line table header
separators.
2026-06-06 01:25:08 +02:00
Bartosz Taudul
9806f35714 Increase spacing between admonition icon and header text. 2026-06-06 00:43:09 +02:00
Bartosz Taudul
d40289d594 Add support for markdown admonitions. 2026-06-05 23:28:09 +02:00
Bartosz Taudul
86fbe529ed Bump font awesome to 7.2. 2026-06-05 23:28:09 +02:00
Bartosz Taudul
9b169ef3f9 Merge pull request #1391 from wolfpld/slomp/cuda-examples
Adding CUDA examples
2026-06-05 21:55:02 +02:00
Marcos Slomp
64797dc735 adding basic CUDA graph example 2026-06-05 12:52:08 -07:00
Bartosz Taudul
76797799c0 Merge pull request #1390 from wolfpld/slomp/cuda-tests
Relocating existing CUDA tests
2026-06-05 21:50:11 +02:00
Marcos Slomp
19549693a0 removing Makefile 2026-06-05 12:46:10 -07:00
Marcos Slomp
10d64d69b5 better ctest integration across the board 2026-06-05 12:46:10 -07:00
Marcos Slomp
d89c956394 CUPTI DLL paths... 2026-06-05 12:46:10 -07:00
Marcos Slomp
79467b4b31 cmake version shenanigans 2026-06-05 12:46:10 -07:00
Marcos Slomp
ae275f239d adding cmake recipe file 2026-06-05 12:46:10 -07:00
Marcos Slomp
77fb86155f relocating CUDAGraphRepro "example" to the tests folder 2026-06-05 12:46:10 -07:00
Bartosz Taudul
e627fcce98 Merge pull request #1386 from wolfpld/slomp/test-relocation
Relocating Tracy test app, plus GitHub actions
2026-06-05 21:19:22 +02:00
Bartosz Taudul
e80893ac20 Reset font size when displaying markdown tooltip. 2026-06-05 19:19:37 +02:00
Bartosz Taudul
912f8c048c Render footnotes in smaller size font. 2026-06-05 19:17:46 +02:00
Bartosz Taudul
d16f627cbc Header sizes are 1-6, remove extra entry. 2026-06-05 19:12:04 +02:00
Clément Grégoire
7cb98245ce Batch addr2line invocations by command-line length
The fixed batches of 1024 addresses could overflow the platform's command-line limit (`La ligne de commande est trop longue.` from cmd.exe on Windows, whose limit is ~8191 characters). Build each batch by appending addresses until a length budget is reached instead. A single conservative budget of 8000 stays under the smallest limit on every platform, and keeps batches in the same ballpark as before (several hundred addresses per invocation).
2026-06-05 19:10:19 +02:00
Bartosz Taudul
3974cc8026 Add support for proper rendering of markdown footnotes. 2026-06-05 19:09:49 +02:00
Clément Grégoire
55d5436fb9 Add option to reset callstack frame symbols to the unresolved state
The new `-R` option of tracy-update sets every callstack frame back to `[unresolved]` / `[unknown]`. Since failed lookups leave frames untouched and the image-relative offset in `symAddr` survives patching, this makes it possible to chain several resolution passes over the same capture, each with different `-p` path substitutions (e.g. one pass per symbol directory).
2026-06-05 19:03:35 +02:00
Clément Grégoire
2b11785b05 Allow offline symbol resolution with any addr2line-compatible tool
The addr2line backend of tracy-update now builds on every platform, including Windows, and can be pointed at any addr2line-compatible executable:

- `-a`: path to a custom symbol resolution tool (e.g. `llvm-addr2line` or a cross-compilation toolchain's `addr2line`). Works on all platforms and takes precedence over the platform default (DbgHelp on Windows, the `addr2line` found in `PATH` elsewhere). Path-like values are validated up front so a wrong path fails with an actionable message instead of a cryptic, localized shell error.
- `-A`: extra arguments passed verbatim to the tool, e.g. `--relative-address` so `llvm-addr2line`/`llvm-symbolizer` accept the image-relative offsets Tracy records for images with a non-zero preferred base (PE, Mach-O).
- `-v`: verbose output while patching symbols.
2026-06-05 19:03:35 +02:00
Bartosz Taudul
715815374d Rebuild markdown manual. 2026-06-05 18:42:50 +02:00
Bartosz Taudul
4f64b974c6 Update manual. 2026-06-05 18:42:24 +02:00
Bartosz Taudul
7c58db4c0a Add public sidecar icon. 2026-06-05 18:40:18 +02:00
Marcos Slomp
f9f772e507 quick (attempted) fix for the Linux workflow linkage error 2026-06-05 09:14:22 -07:00
Marcos Slomp
af2a369dff re-documenting each action step 2026-06-05 09:08:07 -07:00
Bartosz Taudul
bebf20846f Expose setting public sidecar in UI. 2026-06-05 17:45:58 +02:00
Bartosz Taudul
5f6bc2238a Public user data sidecar support. 2026-06-05 17:45:58 +02:00
Bartosz Taudul
4564a626b2 UserData::Save() returns success status. 2026-06-05 17:40:22 +02:00
Bartosz Taudul
e95a757e6c Store trace file path in UserData. 2026-06-05 17:32:46 +02:00
Marcos Slomp
2ebf18be7f adjusting relative path 2026-06-05 08:25:59 -07:00
Bartosz Taudul
fc97af4c68 Redirect unlink to _unlink with msvc. 2026-06-05 17:23:58 +02:00
Bartosz Taudul
8eada19734 Add a separate function to retrieve sidecar path. 2026-06-05 17:21:24 +02:00
Marcos Slomp
a56b08e539 adding test application to windows and macos github workflows 2026-06-05 08:19:07 -07:00
Marcos Slomp
9c9dca8ea5 relocating tracy test app, and consolidating github test action 2026-06-05 08:17:29 -07:00
Bartosz Taudul
268cab7f89 Remove UserData::GetConfigLocation(). 2026-06-05 17:03:23 +02:00
Bartosz Taudul
aeda64d36b Move saving user data to a separate function. 2026-06-05 15:58:07 +02:00
Bartosz Taudul
f5526d01a2 Merge pull request #1385 from nitrix/bump-cmake-313
Bump CMake min version to 3.13
2026-06-05 00:01:22 +02:00
Bartosz Taudul
20e6835da6 Fix remaining MemWrites. 2026-06-05 00:00:04 +02:00
Bartosz Taudul
6b92ec1a23 Message queue item is already in the right format.
After swapping ptr and tag in TaggedUserlandAddress, the tag part aligns
exactly with the metadata field.
2026-06-04 20:08:07 +02:00
Bartosz Taudul
45e90549aa Rewrite TaggedUserlandAddress.
Drop dead code. Move tag to low bits, keep ptr at high bits. Protocol bump,
as TaggedUserlandAddress is used to transmit "literal" messages.
2026-06-04 19:37:47 +02:00
Bartosz Taudul
1690ac0d9d Force inlining of TaggedUserlandAddress. 2026-06-04 12:31:08 +02:00
Bartosz Taudul
a8fab8c977 Cosmetics. 2026-06-04 12:30:51 +02:00
Bartosz Taudul
a0b50ee68e Tabs to spaces. 2026-06-04 12:27:03 +02:00
Bartosz Taudul
896a59d00b Simplify. 2026-06-04 12:11:14 +02:00
Bartosz Taudul
d3db50c201 Mingw fixes. 2026-06-04 12:08:53 +02:00
Alex Belanger
21434e9877 Bump CMake min version to 3.13 2026-06-04 00:27:43 -04:00
Bartosz Taudul
185482c0d9 MemRead deduces returned type from pointer. 2026-06-04 00:49:02 +02:00
Bartosz Taudul
a27dae3e88 Enforce same type in MemWrite. 2026-06-04 00:49:02 +02:00
Bartosz Taudul
7d139a7bf1 Use proper data types for MemWrite. 2026-06-04 00:49:02 +02:00
Bartosz Taudul
02e279bd38 Unaligned memory reads must go via MemRead. 2026-06-04 00:00:08 +02:00
Bartosz Taudul
4e593d91f5 Enable color diagnostics when building library. 2026-06-03 23:48:05 +02:00
Bartosz Taudul
75e721cf0c More subtle range limits visualization. 2026-06-03 23:38:58 +02:00
Bartosz Taudul
c4321bc83c Merge pull request #1383 from wolfpld/slomp/cuda-version
bumping "down" CUDA version
2026-06-03 20:35:54 +02:00
Marcos Slomp
224ff6d0e8 bumping "down" CUDA version 2026-06-03 11:05:06 -07:00
Bartosz Taudul
cc76d0f60e Save per-trace user options in a json sidecar. 2026-06-03 02:03:07 +02:00
Bartosz Taudul
d4d1f78263 Refactor TracyUserData. 2026-06-03 00:00:45 +02:00
Bartosz Taudul
c570288145 Store annotation pointers as shared_ptr. 2026-06-02 01:13:33 +02:00
Bartosz Taudul
98b61e0096 Rename Save* to Store* in TracyUserData. 2026-06-02 01:00:31 +02:00
Bartosz Taudul
5c75032cad Fix typo. 2026-06-02 00:09:27 +02:00
Bartosz Taudul
1381b427db Merge pull request #1379 from wolfpld/slomp/cuda-version-doc
Document and check for required CUDA version
2026-06-01 22:21:40 +02:00
Marcos Slomp
21b9f50cea CUDA version check 2026-06-01 11:25:40 -07:00
Marcos Slomp
cbad012980 typo 2026-06-01 11:09:01 -07:00
Marcos Slomp
d4298f7794 document CUDA version 2026-06-01 11:06:59 -07:00
Bartosz Taudul
f113bbb212 Do not mix loading file data with regex validation. 2026-06-01 00:44:11 +02:00
Bartosz Taudul
0b4128f76c Move source regex validation to a helper function. 2026-06-01 00:40:08 +02:00
Bartosz Taudul
bab8c8eb54 Remove compatibility with vastly obsolete user data file format. 2026-06-01 00:33:39 +02:00
Bartosz Taudul
cbe5347593 Cleanup. 2026-06-01 00:32:05 +02:00
Bartosz Taudul
189e8a1a89 Regenerate markdown manual. 2026-05-30 19:00:39 +02:00
Bartosz Taudul
a5316d525c Crash marker on the timeline also should directly open crash call stack. 2026-05-30 18:58:27 +02:00
Bartosz Taudul
c3e9ff17da Update manual. 2026-05-30 18:34:42 +02:00
Bartosz Taudul
0cf438a78e Add arrows to bottom-up / top-down tree labels. 2026-05-30 18:13:14 +02:00
Bartosz Taudul
e37b12aacb Add icon notification for chat suggestions. 2026-05-30 17:36:33 +02:00
Bartosz Taudul
dd53f721ac Go directly to crash callstack from crash notification. 2026-05-30 15:59:57 +02:00
Bartosz Taudul
058d5ca7c3 Center timeline on crash if clicked on crash text in callstack window. 2026-05-30 15:58:19 +02:00
Bartosz Taudul
eef525243d Add crash tooltip to crash callstack window. 2026-05-30 15:55:16 +02:00
Bartosz Taudul
1f2bbe918f Move Tracy Assist subsection to a section in the user manual. 2026-05-30 15:24:10 +02:00
Bartosz Taudul
0089fab94c Update NEWS. 2026-05-30 15:07:15 +02:00
Bartosz Taudul
0778ef85c6 Draw external frames dimmed in sample entry stacks. 2026-05-30 02:00:45 +02:00
Bartosz Taudul
5e6d872940 Symbol function address column was too narrow. 2026-05-30 01:26:43 +02:00
Bartosz Taudul
b7ed5bd9ef Dim external entries in symbol functions lists. 2026-05-30 01:24:10 +02:00
Bartosz Taudul
de1f84d52b Fix positioning of hotness indicators. 2026-05-29 18:16:14 +02:00
Bartosz Taudul
9f88bc6d04 Bump capstone. 2026-05-29 18:16:14 +02:00
Bartosz Taudul
bd0ba00513 Merge pull request #1377 from siliceum/fix/non-desktop-win32
Fix non desktop win32 build
2026-05-29 17:55:11 +02:00
Clément Grégoire
0e52e387bd gate GetUserNameExA behind TRACY_WIN32_NO_DESKTOP
Same as `GetUserNameA` in `GetUserLogin`
2026-05-29 17:51:33 +02:00
Clément Grégoire
e93ddd2aa7 When using the MSVC C runtime, fileno doesn't actually exist, use _fileno instead.
OLDNAMES.lib may not be linked if you use /NODEFAULTLIB.
Note: This uses `_MSC_VER` as a gate and not _WIN32 as MinGW apparently uses `fileno` and not `_fileno`.
2026-05-29 17:51:30 +02:00
Bartosz Taudul
33905b2f15 Dim external frames in local call stacks. 2026-05-29 00:34:05 +02:00
Bartosz Taudul
d06755652f Merge pull request #1374 from rmarker/flameView
Allow zooming and panning the flamegraph view.
2026-05-28 20:18:45 +02:00
Bartosz Taudul
dbdbd710d8 Add readme file for the user manual directory. 2026-05-28 18:39:00 +02:00
rmarker
15ee99ae41 Add animated zooming to the flame graph view. 2026-05-28 22:02:11 +09:30
rmarker
17f6be4ad4 Extract UpdateZoomAnimation.
This will allow for it to be used in the flame graph.
2026-05-28 22:01:03 +09:30
rmarker
30fd92de0f Handle partial panning in flame graph view.
When zoomed in very far the panning resolution can be so small that it
is less than one unit. In order to continue panning, we store partial
pans so that they can accumulate across frames.
2026-05-28 21:59:33 +09:30
rmarker
0b27b9ec1a Allow zooming in further to the flame graph. 2026-05-28 21:51:41 +09:30
rmarker
cc4b7dcea9 Add a reset view button to the flame graph. 2026-05-28 21:50:19 +09:30
rmarker
4a9e3ea095 Add a horizontal position bar to flame graph.
Now that the ruler just shows the delta time across the view it doesn't
indicate where the view is currently looking.
The new position bar fills this role to allow orientating oneself.
2026-05-28 21:48:14 +09:30
rmarker
55de5bc5ca Update flame graph ruler to measure the time shown in the current view.
It now starts at 0 and shows the time delta across the view.
2026-05-28 21:39:03 +09:30
rmarker
2e87eecc67 Support the live capture case in the flame graph view.
Zooming and panning in the flame graph view shouldn't cause visual
glitches when live data is being captured.
2026-05-28 21:37:01 +09:30
Bartosz Taudul
74eea83051 Merge pull request #1375 from wolfpld/slomp/vulkan-calibration-patch
GPU: Vulkan: upper-bound to the calibration loop
2026-05-28 11:37:00 +02:00
Marcos Slomp
c9fa58f2bb keep preprocessor directives idented to the margin 2026-05-27 16:12:24 -07:00
Marcos Slomp
b7fdc8c0eb newlines 2026-05-27 15:54:03 -07:00
Bartosz Taudul
f5581d7dcb Focus opened manual chapter in the manual tree. 2026-05-27 22:34:04 +02:00
Bartosz Taudul
b682a77f82 Focus the manual window on chapter change. 2026-05-27 22:34:04 +02:00
Bartosz Taudul
800334a953 Show nicer title page of embedded user manual. 2026-05-27 22:34:04 +02:00
Bartosz Taudul
9da66d4c6b Add icon texture accessor. 2026-05-27 22:33:56 +02:00
Bartosz Taudul
04c1a84159 Do not display icon explainer section in the built-in manual viewer. 2026-05-27 01:52:11 +02:00
Bartosz Taudul
b736e4590e Rebuild markdown manual. 2026-05-27 01:52:11 +02:00
Bartosz Taudul
6b28296ef3 Add icon explanations to aid in helping icons by what they show. 2026-05-27 01:52:11 +02:00
Bartosz Taudul
c11fd010d8 Rebuild markdown manual. 2026-05-27 00:10:33 +02:00
Bartosz Taudul
121e10c837 Convert latex font awesome markup to unicode codepoints matching the C defines. 2026-05-26 23:56:27 +02:00
Marcos Slomp
e57c0869df (take care of dead code warning) 2026-05-26 14:49:32 -07:00
Marcos Slomp
4cf754f3fe Calibrate no takes a max sample count 2026-05-26 14:39:12 -07:00
Bartosz Taudul
22d1c2d3c3 Properly convert bclogo blocks into markdown. 2026-05-26 22:34:45 +02:00
Marcos Slomp
884415264b error check 2026-05-26 07:53:01 -07:00
Marcos Slomp
ea8cbc849f adding an upper-bound to the calibration loop 2026-05-26 07:15:54 -07:00
Bartosz Taudul
20d7135c24 Merge pull request #1373 from siliceum/feature/unify-set-options
Options.cmake: unify `set_options*` and `tracy_set_options`
2026-05-26 12:58:45 +02:00
Clément Grégoire
aaf1304308 Options.cmake: unify set_options* and tracy_set_options
Renamed `tracy_*` versions to short version without prefix
2026-05-26 12:50:19 +02:00
rmarker
37750e27ab Allow zooming and panning the flamegraph view.
It could be challenging to examine fine details within the flamegraph.
The flamegraph has been enhanced so that it allows zooming with the
mouse wheel, and then panning around with the right mouse button.
This provides a familiar experience to the timeline view.
2026-05-26 19:30:29 +09:30
Bartosz Taudul
ab62c00be5 Add limit on disassembly tool reply. 2026-05-25 00:57:19 +02:00
Bartosz Taudul
757b582ae7 Include external function status in sampling stats. 2026-05-25 00:46:46 +02:00
Bartosz Taudul
b81ef061f8 Display LLM address during connection attempt. 2026-05-24 17:02:03 +02:00
Bartosz Taudul
702b16977b Set 5 second connection timeout on LLM API connection. 2026-05-24 17:02:02 +02:00
Bartosz Taudul
3f1e572a23 Update LLM API input hint to llama.cpp default port. 2026-05-24 17:02:02 +02:00
Bartosz Taudul
00d4e9ba21 Don't try connecting to LLM after each keystroke.
When typing in e.g. "127.0.0.1" the first character "1" as a valid address
that does not immediately fail the connection attempt. The result was that
any further interaction with the UI (including completing the input) was
blocked by the "please wait" screen during connection attempt.
2026-05-24 17:01:59 +02:00
Bartosz Taudul
ee37bb40f0 Merge pull request #1372 from wolfpld/slomp/nvcc-msvc-fix
NVCC (with MSVC/cl.exe) build fix
2026-05-24 16:29:58 +02:00
Bartosz Taudul
215199239f Merge pull request #1370 from siliceum/feature/custom-platform-hooks
Add `TRACY_PLATFORM_HEADER` hook for unsupported platforms
2026-05-24 15:48:43 +02:00
Clément Grégoire
f93d17a96f Add TRACY_PLATFORM_HEADER hook for unsupported platforms.
Extension point so private/unsupported platforms can plug in their own implementations of the kernel/libc primitives Tracy depends on, without patching the `#if`/`#elif` chains.

Projects supply a platform header via `-DTRACY_PLATFORM_HEADER="\"my_platform.h\""` at build time. Tracy includes it in any TU that needs the hooks. The header toggles per-category `TRACY_HAS_CUSTOM_*` macros and declares matching `tracy::Platform*` functions.

Available hooks:

- `TRACY_HAS_CUSTOM_THREAD_ID` → `PlatformGetThreadId`
- `TRACY_HAS_CUSTOM_USER_INFO` → `PlatformGetHostname`, `PlatformGetUserLogin`, `PlatformGetUserFullName`
- `TRACY_HAS_CUSTOM_SAFE_COPY` → `PlatformSafeMemcpy`
- `TRACY_HAS_CUSTOM_ALLOCATOR` → `PlatformMalloc`, `PlatformFree`, `PlatformRealloc`, `PlatformAllocatorInit`, `PlatformAllocatorThreadInit`, `PlatformAllocatorFinalize`, `PlatformAllocatorThreadFinalize`

Each hook is wired as the first arm of its respective `#if`/`#elif` chain, so existing supported platforms are unaffected.

Template files in `examples/CustomPlatform/` and a new subsection in `manual/tracy.tex` document the mechanism.
2026-05-24 15:42:42 +02:00
Clément Grégoire
84570487bf Move CMake option wrappers into cmake/options.cmake.
Move `tracy_set_option` and `tracy_set_option_value` from `CMakeLists.txt` into `cmake/options.cmake`. Add `tracy_set_option_value_as_string` for options whose value is embedded as a C string literal. All three accept an optional trailing target argument; when provided, the option is also propagated as a PUBLIC compile definition on that target.

Existing `set_option`/`set_option_value` are unchanged but will be replaced later by the `tracy_*` versions.
2026-05-24 15:42:42 +02:00
Bartosz Taudul
03227f7ae1 Update identify.cpp build instructions. 2026-05-24 15:35:43 +02:00
Bartosz Taudul
05c2638467 Remove option to build profiler GUI with no statistics. 2026-05-24 15:07:53 +02:00
Clément Grégoire
fd68959223 Rename InitRpmalloc to InitAllocator.
The function is about to dispatch between rpmalloc and a pluggable allocator hook, so the rpmalloc-specific name no longer fits. Pure rename plus a small consequence: the SymbolWorker call site no longer needs the TRACY_USE_RPMALLOC guard, since the no-op static-inline fallback in TracyAlloc.hpp makes InitAllocator() safe to call unconditionally.
2026-05-24 15:04:31 +02:00
Bartosz Taudul
5c0263c2f0 Disable display of hw samples by default. 2026-05-24 14:57:31 +02:00
Bartosz Taudul
5fd0dc569b Make impact option active only if hw samples are enabled. 2026-05-24 14:55:59 +02:00
Bartosz Taudul
ec1c2d4267 Display aggregation counts in bottom / up sample trees. 2026-05-24 14:23:59 +02:00
Bartosz Taudul
b7e405ebbd Use proper wait stacks grouping variable. 2026-05-24 14:15:53 +02:00
Bartosz Taudul
e8a0676c0b Fix regression that made propagate inlines always disabled if no child calls. 2026-05-24 13:35:48 +02:00
Bartosz Taudul
32b46a3b90 Cleanup. 2026-05-23 23:53:22 +02:00
Bartosz Taudul
cffd0007c5 Bump libs. 2026-05-23 23:41:39 +02:00
Marcos Slomp
564e0e1e71 removing scope qualifiers 2026-05-23 14:20:06 -07:00
Bartosz Taudul
6eb9bf81ad Fix out of bounds read. 2026-05-23 13:40:18 +02:00
Bartosz Taudul
f99a02505a Fix possible div-by-zero.
If as.ipMaxAsm.local is 0 and m_childCalls is false, GetHotnessColor(count, 0)
performs float(2 * count) / 0. The old code explicitly guarded against this
with the as.ipMaxAsm.local != 0 check.
2026-05-23 13:33:16 +02:00
Bartosz Taudul
f63c25fb79 Add glow to gutter markers. 2026-05-23 13:11:02 +02:00
Bartosz Taudul
a0af290db5 Rework symbol view gutter markers logic. 2026-05-23 12:26:24 +02:00
Bartosz Taudul
65db47ed0d Implement viewing assembly attachments. 2026-05-23 01:35:11 +02:00
Bartosz Taudul
d06c4ce816 Fix broken callstack display in zone tooltips if no local functions. 2026-05-23 00:37:57 +02:00
Bartosz Taudul
c91215bd99 Add function for checking if there are local functions in a callstack. 2026-05-23 00:30:47 +02:00
Bartosz Taudul
dfac4cb8a4 Defer initial system prompt update to the first draw. 2026-05-22 19:15:47 +02:00
Bartosz Taudul
34a1bb6107 DrawSourceTooltip() doesn't work with free-floating strings. 2026-05-22 17:54:26 +02:00
Bartosz Taudul
6957f968a4 Include trace time span in llm system message. 2026-05-22 02:06:58 +02:00
Bartosz Taudul
896cdc9462 Fade-out outdated assistant replies.
The "outdated" concept is strictly for chain of assistant replies with
nothing in between, i.e.:

"I will check this..."      <- outdated
<tool call>                 <- not displayed
"Now I will do that..."     <- outdated
<tool call>                 <- not displayed
"Let me consider..."        <- outdated
<reasoning>                 <- not displayed
"Now I have the answer..."

The first three messages are at this point considered outdated, as the
model provided a more recent message.

Note that in chain such as below there are NO outdated messages:

"How can I help..."
<user input>
"Ah, I see..."
<user input>
"You may try to..."

Similarly, if the tool calls or reasoning sections are explicitly enabled
in the chat UI, the messages are also not considered outdated.
2026-05-21 21:23:14 +02:00
Bartosz Taudul
efc33a09d9 Fix potential issue with last chat turn detection. 2026-05-21 20:51:58 +02:00
Bartosz Taudul
61875e1bd0 Add LLM tool for listing sampling statistics. 2026-05-21 20:27:54 +02:00
Bartosz Taudul
e003885946 Merge pull request #1369 from siliceum/feature/no-sys-param-for-bsd
Drop sys/param.h dependency for BSD detection
2026-05-21 17:57:16 +02:00
Bartosz Taudul
d40806caff Merge pull request #1368 from siliceum/fix/timeline-depth
Prevent unlimited recursion leading to stack overflow
2026-05-21 17:50:58 +02:00
Clément Grégoire
e7c71c991c Drop sys/param.h dependency for BSD detection
Replace `#ifdef BSD` (which requires including `<sys/param.h>` first) with explicit checks for `__FreeBSD__`, `__NetBSD__`, `__OpenBSD__` and `__DragonFly__`, matching how these BSDs are already enumerated elsewhere in the codebase (OS name strings, thread id helpers, etc.).

This also avoids leaking the `sys/param.h` requirement through public headers (`TracySysTime.hpp`, `TracyCallstack.h`), where consumers would otherwise need it to correctly see `TRACY_HAS_SYSTIME` / `TRACY_HAS_CALLSTACK`.
`libbacktrace/config.h` is left as-is — it's third-party and only included from .c files where the `BSD` macro can still be picked up locally.

Note: for `setsockopt( m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&val, sizeof( val ) );` I added `__APPLE__` too since this was the only place where it was not checked explicitely.
2026-05-21 10:18:30 +02:00
Clément Grégoire
69855a1416 Prevent unlimited recursion leading to stack overflow
This can happen notably when the user does not call ZoneEnd.

I used 256 arbitrarily as it seemed higher values would just make the UI freeze anyway due to perf reasons.
I added a warning in the notification area so that users can locate it.
2026-05-21 09:38:43 +02:00
Bartosz Taudul
60247b68d3 Merge pull request #1364 from siliceum/fix/zone-runtime
Fix and refactor zone running time
2026-05-20 19:29:29 +02:00
Bartosz Taudul
b48ef9a2ab Merge pull request #1366 from siliceum/fix/ondemand-no-callstack-hang
Fix ProfilerWorker hang when TRACY_HAS_CALLSTACK is not defined
2026-05-20 18:05:42 +02:00
Clément Grégoire
e59601cf1c Fix ProfilerWorker hang when TRACY_HAS_CALLSTACK is not defined 2026-05-20 17:51:42 +02:00
Bartosz Taudul
43472f1226 Merge pull request #1365 from siliceum/feature/fiber-tests
Add fiber to test app
2026-05-20 12:34:45 +02:00
Clément Grégoire
c1177a0cde Add fiber to test app
This simulates fibers on different threads. Was used to discover an issue reported on discord which led to #1364
2026-05-20 12:07:05 +02:00
Clément Grégoire
91c0b1e42b Fix and refactor zone running time
Many of the zones would have a negative running time due to a missing `cs->IsEndValid()` check.
This could end reporting context switches before the zone start, due to `cs->End()` returning -1.

This happened when systrace dropped event, or when using Fibers and `TracyFiberEnter` is called on the new thread once the fiber has been scheduled. (The manual actually does not really hint this is wrong, we should probably fix the manual or the server code.)

In both cases, we assume runtime to be 0 for that context switch. Since we have no actual information. Both options (counting full runtime or no runtime) are wrong, and most of the code handling `!cs->IsEndValid()` uses `Start` instead so that's what I did. This is still a net improvement over displaying negative values. If we want to change this handling, we'd need to review the other places that do `it->IsEndValid() ? it->End() : it->Start()` as well.

It also seems two different concepts were being mixed:
1. Do we have any context switch data at all ? (`it != ctx->v.end()` ie `count != 0`)
2. Do we have complete data for the last context switch (`eit != ctx->v.end()`)

This led to some places of the code not displaying or counting running time at all, notably when hovering a zone.

I think most of the time we wanted 1, as it reports correctly and assumes the last context switch is still running, which is a fair assumption if we didn't see one putting the thread to sleep.

I also fixed a case where we were overcounting runtime when range start was during a sleep.
2026-05-20 12:02:38 +02:00
Bartosz Taudul
14a1a3227e Add guidance about function context. 2026-05-20 02:26:14 +02:00
Bartosz Taudul
8582715fa5 Add information about addresses in callstacks. 2026-05-20 02:26:14 +02:00
Bartosz Taudul
d51634ad24 Include ip and base addr in json callstack frames. 2026-05-20 02:26:14 +02:00
Bartosz Taudul
4bbfe5edbd Add tool for getting symbol parent calls. 2026-05-20 02:26:14 +02:00
Bartosz Taudul
55c5348fad Make GetCallstackJson public. 2026-05-20 00:41:20 +02:00
Bartosz Taudul
5b4dcd4655 Add support for parent callstacks to GetCallstackJson. 2026-05-20 00:27:24 +02:00
Bartosz Taudul
a380046e17 Switch symbol disassembly tool to use symbol address parameter. 2026-05-19 23:52:20 +02:00
Bartosz Taudul
6494285283 Include hex address ofthe symbol in disassembly json. 2026-05-19 23:41:36 +02:00
Bartosz Taudul
ec92f2fac3 Update NEWS. 2026-05-19 22:50:11 +02:00
Bartosz Taudul
07b2600c08 Do not spam wikipedia. 2026-05-19 21:58:15 +02:00
Bartosz Taudul
562a120087 Merge pull request #1362 from alandtse/feature/mcp-save-trace
Add save_trace MCP tool for snapshotting live or loaded captures
2026-05-19 12:21:46 +02:00
Alan Tse
33fe84532e Add save_trace MCP tool for snapshotting live or loaded captures.
- save_worker binding: wraps Worker::Write under
  Worker::ObtainLockForMainThread() so live instances yield their
  receive thread cooperatively for the save's duration — the same
  pattern View::Save uses in the GUI.
- save_trace MCP tool: defaults to async_mode=True for multi-GB
  traces; reuses the existing Task/executor machinery so callers
  poll via the task tool. Path resolution mirrors load_capture.
- manual/tracy.tex: add save_trace bullet to the MCP tool list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:23:31 -07:00
Bartosz Taudul
1f1738c221 Make glow bigger. 2026-05-18 01:00:38 +02:00
Bartosz Taudul
eb411b1138 Merge pull request #1361 from rmehri01/patch-2
fix: header definition of start/end sampling profiling
2026-05-17 21:57:22 +02:00
Bartosz Taudul
cf5325032e Make the symbol view hotness indicators more chonky.
The previous uniform glow rectangle was not appropriate anymore, so the
glow was changed to be a proper gradient.
2026-05-17 21:53:06 +02:00
Ryan Mehri
f74a452b89 fix: header definition of start/end sampling profiling 2026-05-17 15:16:45 -04:00
Bartosz Taudul
365c99e601 Restore frameData->imageName.Active() checks. 2026-05-17 18:58:29 +02:00
Bartosz Taudul
b8f68e3fae Add local call stacks to sample statistics inlines list. 2026-05-17 18:13:52 +02:00
Bartosz Taudul
5d4ba366ba DrawSourceTooltip reports if source was printed. 2026-05-17 17:58:22 +02:00
Bartosz Taudul
a7937d2710 Move local callstack printing to a separate function. 2026-05-17 17:56:14 +02:00
Bartosz Taudul
efd7ec262d Set default LLM address to llama.cpp port. 2026-05-17 17:50:33 +02:00
Bartosz Taudul
f0f579172b Force inline fast check. 2026-05-17 16:06:56 +02:00
Bartosz Taudul
4c6157d249 Remember last retrieved external check result. 2026-05-17 15:43:26 +02:00
Bartosz Taudul
6ae6fb741e Check if image is external once, before checking subframe filenames.
Cache is shared between image names and source file names, because the
underlying StringIdx storage makes indices unique. Both name sets should
be completely separate, but if you have conflicts here, you have much
more pressing problems to solve.
2026-05-17 15:30:03 +02:00
Bartosz Taudul
4ab7ef301e Split IsFrameExternalImpl into image + filename parts. 2026-05-17 15:30:03 +02:00
Bartosz Taudul
41f1172774 Change order of IsFrameExternal checks.
Check image first, then perform the expensive filename check.
2026-05-17 14:30:45 +02:00
Bartosz Taudul
b18d81cbbe Explicitly state which code paths are important to consider. 2026-05-17 14:08:54 +02:00
Bartosz Taudul
03e60c902f Clamp max percentage bar width. 2026-05-16 13:30:51 +02:00
Bartosz Taudul
0b88cbaa3c Fix z/x keys in source view when the child panes (src/asm) are focused. 2026-05-16 13:29:26 +02:00
Bartosz Taudul
5b180fdd5f Restore const qualifiers dropped in 744bd21 for global cache. 2026-05-14 22:59:10 +02:00
Bartosz Taudul
03f737a923 Use local external frame cache in BuildFlameGraph.
In terms of how much BuildFrameGraph execution time was spent in
IsFrameExternal:

1. no cache: 67%
2. global + shared_mutex: 84%
3. global + mutex: 80%
4. local: 41% (this commit)
2026-05-14 22:44:59 +02:00
Bartosz Taudul
4e0259148f Change IsFrameExternal interface to work with external cache.
Locks are dominating the execution time, making the global cache non-viable.
2026-05-14 22:29:51 +02:00
Bartosz Taudul
a3cadb2fce Protect against localtime failures. 2026-05-14 22:06:22 +02:00
Bartosz Taudul
6789e7d6f9 Cosmetics. 2026-05-14 20:36:55 +02:00
Bartosz Taudul
c3e3ea98ad Add basic information about the profiling session to system prompt. 2026-05-14 19:51:42 +02:00
Bartosz Taudul
c11c36f9f7 Add trace filename and user data accessors to View. 2026-05-14 19:51:41 +02:00
Bartosz Taudul
150ec1534c Store View in TracyLlm. 2026-05-14 19:51:41 +02:00
Bartosz Taudul
0dfd7fb20b Cache IsFrameExternal() queries. 2026-05-14 19:51:41 +02:00
Bartosz Taudul
b90e44a5f1 Add raw data accessor to StringIdx. 2026-05-14 19:33:21 +02:00
Bartosz Taudul
744bd21423 Change IsFrameExternal() interface to operate on StringIdx, move to Worker. 2026-05-14 19:01:04 +02:00
Marcos Slomp
4a58c42e2d Merge pull request #1316 from slomp/slomp/d3d12-ring
GPU: D3D12: remove NewFrame() API + drop unresolved queries when under pressure
2026-05-14 09:16:32 -07:00
Marcos Slomp
af9802e3f2 debugbreak only for msvc 2026-05-14 08:24:54 -07:00
Marcos Slomp
ad9c6f2f18 addressing code review 2026-05-14 08:13:59 -07:00
Bartosz Taudul
eb3df3b411 Fix blurred web page at fractional scaling. 2026-05-13 21:53:58 +02:00
Bartosz Taudul
7e9f66c987 Prefer switching to source/line within symbol in links from markdown. 2026-05-13 00:33:43 +02:00
Bartosz Taudul
f4b89278ef Switching to a specified filename and line within already opened symbol. 2026-05-13 00:32:54 +02:00
Bartosz Taudul
e0a8499376 Add information about symbol vs source code. 2026-05-12 18:03:45 +02:00
Bartosz Taudul
6ded0d1d7b Bump imgui to 1.92.8-docking. 2026-05-12 18:03:45 +02:00
Bartosz Taudul
77152da627 Make child sample counts consistent with child sample time formatting. 2026-05-12 02:03:16 +02:00
Bartosz Taudul
2a51d4acf1 Disable child calls controls if there are no child calls. 2026-05-12 02:02:05 +02:00
Bartosz Taudul
ea651eacce Give guidance for looking at external function calls. 2026-05-12 01:22:09 +02:00
Bartosz Taudul
3edcf37c58 Add guidance about links in inline code. 2026-05-12 01:03:17 +02:00
Bartosz Taudul
b0f00d20c8 Proper floating point charconv is available since libstdc++ 11 (April 2021). 2026-05-12 00:30:49 +02:00
Bartosz Taudul
453f2c1871 Display child calls time as a percentage. 2026-05-12 00:25:09 +02:00
Bartosz Taudul
e40497e02e No 'if' for BeginChild. 2026-05-12 00:16:05 +02:00
Bartosz Taudul
e124ae985d Add child call percentage relative to total symbol time. 2026-05-12 00:11:27 +02:00
Bartosz Taudul
efa1b1ef48 Convert child call distribution list to a table, add image column. 2026-05-12 00:11:27 +02:00
Bartosz Taudul
ec6c2618ca Add missing separator. 2026-05-11 23:23:34 +02:00
Bartosz Taudul
fe4c920794 Skip inline symbols. 2026-05-11 23:10:06 +02:00
Bartosz Taudul
afcefc7855 Add llm tool for getting disassembly + profiling data for a symbol. 2026-05-11 22:23:29 +02:00
Bartosz Taudul
22ec55f1d5 Store View reference in TracyLlmTools. 2026-05-11 22:09:27 +02:00
Bartosz Taudul
c026c98bbb Source file name is not needed for gathering assembly data. 2026-05-11 22:01:25 +02:00
Bartosz Taudul
205983e87f Recalculate AddrStatData in AttachRangeToLlm.
For consistency, always provide the OS-level sampling data, not the hardware
samples.

Always disable inline propagation, which is intended as a local area help
for a human. It does not make sense in context of an llm.
2026-05-11 21:48:00 +02:00
Bartosz Taudul
c9a9bbdab2 Move Gather{Additional,}IpStats from TracySourceView to TracyDisassembly. 2026-05-11 21:22:53 +02:00
Bartosz Taudul
ae56d79c7e Extract disassembly line formatting to a separate function. 2026-05-11 21:11:39 +02:00
Bartosz Taudul
860724d794 Cosmetics. 2026-05-11 20:27:53 +02:00
Bartosz Taudul
40199a42b3 Properly emit empty cost separators when line addres is not in ipCountAsm. 2026-05-11 20:27:04 +02:00
Bartosz Taudul
7c6e656fda Use existing reference to array entry. 2026-05-11 20:01:40 +02:00
Bartosz Taudul
9611069a4f Remove guidance about user replies from assembly attachments. 2026-05-11 19:51:39 +02:00
Bartosz Taudul
825960553a Give reasoning for always providing source file name + line number. 2026-05-11 19:51:06 +02:00
Bartosz Taudul
325fe71d30 Move AddrStatData from TracySourceView to TracyDisassembly. 2026-05-11 19:22:37 +02:00
Bartosz Taudul
beec259520 Extract symbol code disassembly into a separate source file.
Two bugs fixed in the process:

1. X86_REG_BPL is now properly set (previously there were duplicate
   X86_REG_BP entries).
2. maxLine is now properly calculated, instead of being set to the
   last line value.
2026-05-11 01:30:28 +02:00
Bartosz Taudul
1dcad83790 Include call costs in assembly attachments. 2026-05-10 14:05:31 +02:00
Bartosz Taudul
8dd606009c Require window focus for z/x key activation in source view. 2026-05-10 13:24:20 +02:00
Bartosz Taudul
17b6656c85 Provide program optimization strategies for llm. 2026-05-10 12:44:49 +02:00
Bartosz Taudul
edb5241bee Be more efficient with missing values in asm attachments. 2026-05-10 11:35:31 +02:00
Bartosz Taudul
5cb283073e Store kernel symbol end address, not size.
The end address is now readily available in lower_bound search, instead
of needing to be calculated constantly in the lambda.

The size can be equivalently calculated from the end address, but this
only happens once, after the symbol is found.
2026-05-10 00:15:18 +02:00
Bartosz Taudul
a6c72f1cc7 Do not repeatedly read from all ring buffers to find minimum time entry. 2026-05-09 20:41:29 +02:00
Bartosz Taudul
95416a4b9c Since active[] order doesn't matter, memmove can be replaced with a swap. 2026-05-09 17:48:57 +02:00
Bartosz Taudul
f7ab78893c Don't query inline-symbol frames as native addresses.
Frames whose symbol data is shipped inline with the callstack payload
(sel=1, e.g. Lua-side stack entries) were being passed to
GetCanonicalPointer() in the AddCallstackAllocPayload() query loop,
tripping its sel==0 assertion. They have no native pointer to query
and were already registered in callstackFrameMap earlier in the same
function, so just skip them.

Regression from c704f909, which hoisted the per-call-site dedup into
QueryCallstackFrame(). Three of the four updated call sites were
equivalent before and after, because the old guard and the new one
keyed on the same value. The fourth, this one, was not: the old guard
tested the frame as-is and matched the entry inserted a few lines above,
short-circuiting before GetCanonicalPointer() ran. The new guard keys on
PackPointer(addr), so GetCanonicalPointer() must run first to compute
addr, and the assert fires.
2026-05-09 12:13:45 +02:00
Bartosz Taudul
7de27ebffa Force inline RingBuffer::Read(). 2026-05-09 01:05:33 +02:00
Bartosz Taudul
70535435f2 Use masking instead of calculating remainder.
size is power-of-two (asserted).
2026-05-09 00:58:16 +02:00
Bartosz Taudul
61a6fb6780 Batch ring buffer reads. 2026-05-09 00:50:50 +02:00
Marcos Slomp
d5bce07a90 oopsie 2026-05-08 15:33:12 -07:00
Marcos Slomp
027a37409b addressing code review comments 2026-05-08 14:30:24 -07:00
Bartosz Taudul
1bfd3beab9 Add "how to fix this crash?" preset question to call stack window. 2026-05-08 01:57:43 +02:00
Bartosz Taudul
b864ff47e5 Implement disabling LLM summary and suggestion. 2026-05-08 01:48:19 +02:00
Bartosz Taudul
4d34163ade Allow accepting chat suggestion via Enter or Send button. 2026-05-08 01:17:31 +02:00
Bartosz Taudul
8ce05917db Prevent stale summary after chat reset. 2026-05-08 01:07:59 +02:00
Bartosz Taudul
d59c1e7bd2 Prevent stale suggestion after chat reset. 2026-05-08 01:07:54 +02:00
Bartosz Taudul
6fb7f6d095 Add LLM chat suggestions. 2026-05-08 00:34:14 +02:00
Bartosz Taudul
db8bfc9ba9 Prevent in-flight query notification flicker. 2026-05-07 22:34:48 +02:00
Bartosz Taudul
305382453d Add callstack sample events with 32 and 16 bit timestamps. 2026-05-07 02:16:53 +02:00
Bartosz Taudul
ccaef5ba0b ZoneBegin / ZoneBeginCallstack with 32 and 16 bit time data. 2026-05-07 02:16:53 +02:00
Bartosz Taudul
4d094c108d Add zone end messages with 32 and 16 byte time deltas.
Change in test application:
    compressed data: 130 Mpbps -> 105 Mbps
    uncompressed: 830 Mbps -> 740 Mbps
2026-05-06 19:09:19 +02:00
Bartosz Taudul
b1768b98a5 Protocol was bumped to 77 after 0.13.1. 2026-05-06 01:37:54 +02:00
Bartosz Taudul
d6e77b3f40 Remove server query fast path.
The profiler will typically want to send bursts of queries (e.g. 3 queries
to retrieve source location strings, or multiple queries to get all the call
stack frames, etc.).

Each of these queries will be sent immediately, if available space in the
network buffer permits. Each of these sends is a separate syscall.

Remove this and instead batch all queries with the already existing network
buffer overflow handling functionality.
2026-05-06 00:42:24 +02:00
Bartosz Taudul
58eaa330af Set TCP_NODELAY on profiler data channel. 2026-05-06 00:12:21 +02:00
Bartosz Taudul
8c2a970222 Merge pull request #1352 from wolfpld/slomp/apple-systrace
System tracing (callstack sampling) for Apple devices
2026-05-05 19:05:41 +02:00
Bartosz Taudul
173c58bb6e Merge pull request #1353 from siliceum/fix/winsdk-compat
Fix #1243 compatibility with WinSDK < 10.0.26100
2026-05-05 18:05:51 +02:00
Marcos Slomp
6a214ed419 refactoring globals 2026-05-05 08:28:15 -07:00
Marcos Slomp
df20e8ad84 refactoring 2026-05-05 08:05:56 -07:00
Marcos Slomp
761d50e8f5 debug cleanup 2026-05-05 07:49:15 -07:00
Clément Grégoire
2997a78872 Fix #1243 compatibility with WinSDK < 10.0.26100
After investigating (downloading and installing) all publicly available SDKs at https://learn.microsoft.com/en-us/windows/apps/windows-sdk/downloads-archive I concluded the `TRACEHANDLE` deprecation started in `10.0.26100`.
This defines `PROCESSTRACE_HANDLE` and `CONTROLTRACE_ID` as done by the SDK when using older versions. Using `WDK_NTDDI_VERSION` (and not `NTDDI_VERSION` which may change based on `_WIN32_WINNT` or user input seems to be the most reliable way to do it. While it says "WDK" it's been part of the SDK in `shared\sdkddkver.h`. Note it doesn't work for MinGW because it updates half of its sdk files for some reason.
Tested with both 10.0.26100 and 10.0.22621.0 which is the last one I found without the new types.
Also changes CONTROLTRACE_ID to ULONG64 on mingw which is correct (type used by `TRACEHANDLE` too in mingw fe2763863a/mingw-w64-headers/include/evntrace.h (L60) )
2026-05-05 16:14:21 +02:00
Marcos Slomp
6b4ed90323 remove modern c++ 2026-05-04 21:43:27 -07:00
Marcos Slomp
ace8f18adb handle ARM64 PAC 2026-05-04 16:21:19 -07:00
Marcos Slomp
8692f2a650 updating manual 2026-05-04 16:18:00 -07:00
Marcos Slomp
ee3120010a cleanup 2026-05-04 16:18:00 -07:00
Marcos Slomp
3d6129f8b0 dependent function ordering 2026-05-04 16:18:00 -07:00
Marcos Slomp
dbb17e50e3 relaxing atomics in hot multi-threaded path 2026-05-04 16:18:00 -07:00
Marcos Slomp
8b9c25f19c handle case where constructor (context initializaion) may fail 2026-05-04 16:18:00 -07:00
Marcos Slomp
33b5404033 early exit when MapTimestampBuffer fails 2026-05-04 16:18:00 -07:00
Marcos Slomp
1e5926b9c0 debug cleanup 2026-05-04 16:18:00 -07:00
Marcos Slomp
b452e84e53 removing debug code 2026-05-04 16:18:00 -07:00
Marcos Slomp
bb4c6bc5a9 removing debug pragma 2026-05-04 16:18:00 -07:00
Marcos Slomp
474b3dbdfb comments 2026-05-04 16:18:00 -07:00
Marcos Slomp
80e6087ff3 claim shared ownership of the queue to keep the queue (and device) alive 2026-05-04 16:18:00 -07:00
Marcos Slomp
c4d18c8291 refactoring Drain/Collect 2026-05-04 16:18:00 -07:00
Marcos Slomp
bf1222c4b1 re-enabling assert 2026-05-04 16:17:59 -07:00
Marcos Slomp
0ec1b29a66 debug cleanup 2026-05-04 16:17:59 -07:00
Marcos Slomp
035a2ba30d debug cleanup 2026-05-04 16:17:59 -07:00
Marcos Slomp
2242811004 refactoring 2026-05-04 16:17:59 -07:00
Marcos Slomp
099af6dd74 simplified private API 2026-05-04 16:17:59 -07:00
Marcos Slomp
2a7e2215e0 handling special wait cases, and misc comments 2026-05-04 16:17:59 -07:00
Marcos Slomp
8fc8b42221 simplify debug ZoneValue 2026-05-04 16:17:59 -07:00
Marcos Slomp
f538ac49c6 nomenclature 2026-05-04 16:17:59 -07:00
Marcos Slomp
c35acce915 debugger check 2026-05-04 16:17:59 -07:00
Marcos Slomp
77be1c8616 minor refactoring 2026-05-04 16:17:59 -07:00
Marcos Slomp
0fdc70a75f implementing Drain/Wait 2026-05-04 16:17:59 -07:00
Marcos Slomp
2b4e5250ed refactoring debug macros 2026-05-04 16:17:59 -07:00
Marcos Slomp
760eeb40e8 cleanup includes 2026-05-04 16:17:59 -07:00
Marcos Slomp
303680257f debugging 2026-05-04 16:17:59 -07:00
Marcos Slomp
952da01f33 comments about ResolveQueryData 2026-05-04 16:17:59 -07:00
Marcos Slomp
601c55bad2 track latest known GPU timestamp (to avoid "time-travel" later on in the server) 2026-05-04 16:17:59 -07:00
Marcos Slomp
ea7fb726ac refactoring Collect 2026-05-04 16:17:59 -07:00
Marcos Slomp
4f9de46d3d cleanup 2026-05-04 16:17:59 -07:00
Marcos Slomp
60935f2a25 refactoring 2026-05-04 16:17:59 -07:00
Marcos Slomp
7e70154e4e cleanup 2026-05-04 16:17:59 -07:00
Marcos Slomp
d94c484adc adopting new producer-consumer "panic" strategy 2026-05-04 16:17:59 -07:00
Marcos Slomp
0a27c61313 debugging 2026-05-04 16:17:59 -07:00
Marcos Slomp
b34966fe62 refactoring 2026-05-04 16:17:59 -07:00
Marcos Slomp
85789a8a13 minor refactoring 2026-05-04 16:17:59 -07:00
Marcos Slomp
333e4bcb05 comments and debugging 2026-05-04 16:17:58 -07:00
Marcos Slomp
b80fa52ba2 switching to a circular buffer with individual timeouts 2026-05-04 16:17:58 -07:00
Marcos Slomp
b638a3adff opportunistically collect next window whenever possible 2026-05-04 16:17:58 -07:00
Marcos Slomp
9b2f1266ea cosmetic changes 2026-05-04 16:17:58 -07:00
Marcos Slomp
bde92ffed2 timestamp relax (disabled) 2026-05-04 16:17:58 -07:00
Marcos Slomp
3bf371c4fb refactoring of Collect core 2026-05-04 16:17:58 -07:00
Marcos Slomp
c39d1d6441 fudge factors... 2026-05-04 16:17:58 -07:00
Marcos Slomp
1d8022c301 comments, debugging 2026-05-04 16:17:58 -07:00
Marcos Slomp
0afe331d1e consolidating map/unmap buffer logic 2026-05-04 16:17:58 -07:00
Marcos Slomp
19e48a843d conditional clarity 2026-05-04 16:17:58 -07:00
Marcos Slomp
9649302da8 Distance utility 2026-05-04 16:17:58 -07:00
Marcos Slomp
f06989e17c cleanup 2026-05-04 16:17:58 -07:00
Marcos Slomp
e1357d7a1b reworking destructor 2026-05-04 16:17:58 -07:00
Marcos Slomp
4f62a115fa adopting a "collect window" scheme to avoid race conditions and heavy synchronization 2026-05-04 16:17:58 -07:00
Marcos Slomp
cd1a2c7c6e re-enabbling post-collect calibration; rephrasing comments about race condition 2026-05-04 16:17:58 -07:00
Marcos Slomp
37628ed305 explaining the race condition 2026-05-04 16:17:58 -07:00
Marcos Slomp
e5cdb8b361 persistent map, debug toggle, etc 2026-05-04 16:17:58 -07:00
Marcos Slomp
65cb45ea97 re-enable range assert 2026-05-04 16:17:58 -07:00
Marcos Slomp
4f44b954ab reveting overflow suppresion 2026-05-04 16:17:58 -07:00
Marcos Slomp
d8aaa91379 disabling debug dump 2026-05-04 16:17:58 -07:00
Marcos Slomp
43b8a98091 timestamp aging and race debugging 2026-05-04 16:17:58 -07:00
Marcos Slomp
db9e82691c debugging the UI freaking out 2026-05-04 16:17:58 -07:00
Marcos Slomp
e7a503c47f misc 2026-05-04 16:17:58 -07:00
Marcos Slomp
7e0855d56b keep track of the last known emitted gpu timestamp, and use it to emit makeshift timestamps for dropped timestamps 2026-05-04 16:17:58 -07:00
Marcos Slomp
b0b3c8a335 collect timestamps in pairs 2026-05-04 16:17:58 -07:00
Marcos Slomp
1c2c1b5c17 comments and eminders 2026-05-04 16:17:57 -07:00
Marcos Slomp
b50876e77c eliminate NewFrame, and account for "abandoned" and "out-of-order" timestamp queries 2026-05-04 16:17:57 -07:00
Marcos Slomp
c877d0bcb4 TracySysTrace already manages the wprker thread lifetime... 2026-05-04 15:45:56 -07:00
Marcos Slomp
5a168f7ae4 initial prototype of system tracing for Apple devices 2026-05-04 15:29:24 -07:00
Bartosz Taudul
5bbafeabd7 Patch ppqsort to fix binary_semaphore release race. 2026-05-04 01:13:53 +02:00
Bartosz Taudul
04728994a5 Shorten external frames in call stack tooltips. 2026-05-04 01:05:02 +02:00
Bartosz Taudul
600227a30b More compact assembly code form.
For Qwen3.6-27B tokenizer:

AStar<> (3018 byte binary): 25546 -> 21099 tokens (82.6%)
LZ4_compress_fast_continue (10.47 KB): 84140 -> 69016 tokens (82%)
2026-05-02 16:31:59 +02:00
Bartosz Taudul
6d4a080da5 More compact symbol view source attachments. 2026-05-02 16:04:29 +02:00
Bartosz Taudul
7fac1ef8c9 Update NEWS. 2026-05-02 15:44:26 +02:00
Bartosz Taudul
7e043e21e7 Enable resizing of child call distribution box. 2026-05-02 15:42:34 +02:00
Bartosz Taudul
40b328dc95 Add drawable height splitter widget. 2026-05-02 15:42:18 +02:00
Bartosz Taudul
0a64f16c2d More compact time range limits window. 2026-05-02 12:41:12 +02:00
Bartosz Taudul
9965aff723 Use fractional max rows for visible threads display to imply scrolling. 2026-05-02 01:41:28 +02:00
Bartosz Taudul
6276f1b12a Rebuild markdown manual. 2026-05-02 00:38:44 +02:00
Bartosz Taudul
335403d860 Always dim out external frames. 2026-05-02 00:38:34 +02:00
Bartosz Taudul
4e6f7c2152 More return stack reinforcement. 2026-05-02 00:31:53 +02:00
Bartosz Taudul
8877a2527d Proper count of possibly visible threads in wait stacks window. 2026-05-01 22:03:39 +02:00
Bartosz Taudul
71fb624ec4 Update NEWS. 2026-05-01 21:23:14 +02:00
Bartosz Taudul
4394d79c39 Limit "visible threads" rows displayed at once. 2026-05-01 21:20:51 +02:00
Bartosz Taudul
3900890ef4 Show visible threads list in columns.
Options list is excluded, as that's drag-n-drop, and that's too much
complication.
2026-05-01 21:20:51 +02:00
Bartosz Taudul
a11e417e43 Provide detailed call stack analysis instructions. 2026-05-01 17:56:36 +02:00
Bartosz Taudul
78bac8dcbc Use correct tid field name. 2026-05-01 17:56:35 +02:00
Alan Tse
9a7233ced5 Add MCP server for AI-assisted trace analysis (#1347)
* Add MCP server for AI-assisted trace analysis.

Introduce an optional Model Context Protocol (MCP) server that lets AI
assistants analyze Tracy captures and live sessions through Tracy's own
server engine. The server runs as a Python sidecar and talks to the
existing C++ analysis code through new pybind11 bindings.

- python/bindings/ServerModule.cpp: TracyServerBindings module exposing
  Worker, file I/O, zones, GPU zones, frame data, plots, messages, locks,
  source locations, and summary statistics (zone/GPU child stats, frame
  timing, etc.).
- python/CMakeLists.txt: builds and installs TracyServerBindings alongside
  TracyClientBindings.
- extra/mcp/tracy_mcp.py: FastMCP SSE singleton with dynamic port
  discovery, PID-file based singleton detection, session-isolated worker
  instances, synchronous and background eval, task polling, and a
  shutdown tool to release the .pyd lock during development.
- extra/mcp/start_mcp.sh, .gitignore: launcher with local override hook;
  ignores generated port/pid files.
- manual/tracy.md: documents building, running, and integrating the
  server with an AI assistant.

* Improve Tracy MCP cold-start guidance.

Cold-start usability testing showed an LLM agent burned ~7 exploratory
calls discovering the ctx object model, time-unit conventions, and join
keys before producing useful analysis. Surface that information up front
through MCP resources and entry-point tool guidance.

- extra/mcp/eval_guide.md: new bindings-layer reference covering the
  Worker object graph (zone / GPU zone / frame / thread / message /
  plot / lock / memory entry points), nanosecond time units, ZoneStats
  field semantics including self-time via get_child_zone_stats, the
  opaque 'name (addr)[arch] <srcloc_id>' key format, and worked
  examples translating common queries into ctx Python.
- extra/mcp/tracy_mcp.py: expose system.prompt.md and eval_guide.md as
  MCP resources (tracy://prompt and tracy://eval-guide) so external
  agents and Tracy Assist share the same guidance source. Resource
  content is re-read per request — edits propagate without a server
  restart.
- Point load_capture and live_connect return values plus the eval tool
  description at the resources, so the agent reads them before its
  first eval rather than introspecting blind.
- Expand load_capture docstring: name the path parameter explicitly,
  show Windows path syntax, and direct agents to list_captures plus
  TRACY_CAPTURES_DIR for capture discovery.
- Probe is_connected() briefly after Worker construction in
  live_connect and surface an actionable error on silent handshake
  failures (typically a Tracy client/server version mismatch or
  TRACY_ON_DEMAND) instead of returning misleading success.

Reduces a fresh agent's cold-start overhead from 7 exploratory calls
to 4, where the remaining 4 are unavoidable harness/schema-fetch
overhead, not API-design friction.

* Detect Tracy protocol mismatches via UDP broadcast pre-flight.

Tracy clients announce themselves on UDP port 8086 every ~3 seconds with
a BroadcastMessage carrying the protocol version, listen port, and
program name (public/common/TracyProtocol.hpp). The Tracy GUI reads this
and refuses to attempt a TCP connection on protocol mismatch, surfacing
a precise error. live_connect previously had no equivalent check, so a
mismatch produced an opaque 2-second handshake timeout with no
diagnostic about what was wrong.

- Add a broadcast parser handling versions 0-3, with variable-length
  programName (Tracy sends only the actual name + null terminator on
  the wire, not the full 64-byte buffer).
- Add a non-blocking UDP listener that binds 8086 with SO_REUSEADDR
  and waits up to 3.5s — enough to guarantee catching at least one
  beat at the 3s broadcast cadence.
- Read our bindings' ProtocolVersion at startup by parsing
  TracyProtocol.hpp, so the comparison stays in sync with the build
  without new C++ wiring.
- live_connect runs the broadcast pre-flight before constructing
  Worker. On a matched listen_port with a differing protocol_version,
  it returns a single-line error naming the program, both versions,
  and the remediation, without ever opening a TCP connection. If no
  matching broadcast arrives, it falls through to the existing
  handshake probe, which now reports any other broadcasts seen as a
  hint (helpful when the target uses a non-default port).

* Add MCP Server section to LaTeX manual.

The markdown manual is auto-generated from the LaTeX source; add the
corresponding \subsection{MCP Server} so the two stay in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove hand-written MCP section from tracy.md.

tracy.md is generated from tracy.tex via latex2md.sh. The MCP section
was previously written by hand directly in the markdown; now that the
LaTeX source has been updated, the markdown section should be
regenerated by running latex2md.sh rather than maintained manually.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:17:55 +02:00
Bartosz Taudul
460352d0d5 Allow opening callstack window from LLM attachment. 2026-04-30 21:42:22 +02:00
Bartosz Taudul
89bd8b2ba5 Add public View method for opening callstack window. 2026-04-30 21:41:53 +02:00
Bartosz Taudul
cf2c07d905 Mark GetSelectThread() const. 2026-04-30 21:38:33 +02:00
Bartosz Taudul
529c688ff2 Callstack thread display is not gated by LLM. 2026-04-30 18:40:54 +02:00
Bartosz Taudul
af665ef3dc Update NEWS. 2026-04-30 18:13:11 +02:00
Bartosz Taudul
6c5e0cdf1d Draw call stack thread name in call stack window. 2026-04-30 18:08:15 +02:00
Bartosz Taudul
80f504019f Store callstack window callstack id together with origin thread id. 2026-04-30 17:06:59 +02:00
Bartosz Taudul
6ccad8a94d Generic thread id naming in crash callstack attachments. 2026-04-30 12:48:58 +02:00
Bartosz Taudul
f0df6112dc Move callstack summary in callstack window to its own line. 2026-04-30 12:46:22 +02:00
Bartosz Taudul
170b0b4edf More compact listing format. 2026-04-30 12:19:13 +02:00
Bartosz Taudul
39cd16a92b Move specialized LLM skills out of the system prompt. 2026-04-30 02:41:48 +02:00
Bartosz Taudul
eaf66e7af9 Cleanup. 2026-04-28 00:27:27 +02:00
Bartosz Taudul
ab75aeeac9 Add section headers to the in-program user manual. 2026-04-27 23:32:34 +02:00
Bartosz Taudul
4fe2949033 Instruct LLM how to provide user manual links. 2026-04-27 23:21:49 +02:00
Bartosz Taudul
ac20896f57 Add links to manual search results. 2026-04-27 23:15:52 +02:00
Bartosz Taudul
afddce2538 Implement going to manual link. 2026-04-27 23:05:43 +02:00
Bartosz Taudul
14aa5dafe5 Add manual section link tooltips. 2026-04-27 22:47:10 +02:00
Bartosz Taudul
4d62b3a573 Move view and worker checks to initial isSource check. 2026-04-27 22:46:35 +02:00
Bartosz Taudul
d4241d987d Pass view, worker to user manual markdown renderer. 2026-04-27 22:23:08 +02:00
Bartosz Taudul
04d30fb487 Add manual chunk retrieval function. 2026-04-27 22:20:58 +02:00
Bartosz Taudul
d653e984b5 Really fix first-word-continuation word wrap.
The previous solution (75c173) didn't account for the fact that the text
to print may start with a space, in which case the text width calculation
results in 0. In effect, first word length was never greater than the
space left for printing, and the problem was still there.

Fix by walking through all initial spaces in firstWord.

If fwLen > left, then a line break is needed. In this case ignore initial
spaces in text.
2026-04-26 00:40:55 +02:00
Bartosz Taudul
afe51ec5f3 Merge pull request #1348 from Casqade/fix-multiline-messages-tooltips
Show tooltips for multiline messages
2026-04-25 18:54:24 +02:00
casqade
29d21fe1f7 Show tooltips for multiline messages 2026-04-25 17:27:13 +03:00
Bartosz Taudul
43643ba0b6 Update NEWS. 2026-04-25 13:08:01 +02:00
Bartosz Taudul
efb1973210 Dim out external frames in callstack tooltips. 2026-04-25 13:04:49 +02:00
Bartosz Taudul
0bd56feb2d Only reset start time when role changes to non-assistant.
Fixes time reset on tool replies.
2026-04-25 12:49:44 +02:00
Bartosz Taudul
83a31730ce Reply timing logic is only relevant for assistant replies. 2026-04-25 12:49:10 +02:00
Bartosz Taudul
5f7a36cf44 Do not assert on early TracyDebug calls.
TracyDebug fires from SysPower's ctor while it scans intel-rapl, which
runs as a Profiler member initializer -- before s_instance is set in
the Profiler ctor body. Under TRACY_MANUAL_LIFETIME without
TRACY_ON_DEMAND, the TracyInternalMessage path guarded this with
assert(ProfilerAvailable()), which aborted tracy-monitor whenever it
was run as root (only then is intel-rapl readable, so the log is
actually reached).

Soften the assert to an early-out, matching the TRACY_ON_DEMAND branch.
A TracyDebug issued before the profiler is up now silently skips
instead of aborting.
2026-04-24 21:27:58 +02:00
Bartosz Taudul
46bccd9a92 Refresh external image cache on symbolization misses.
FindExternalImageRefresh already re-parsed /proc/<pid>/maps on miss,
but only one of three external decode paths used it. Switch the
DecodeCallstackPtrFastExternal and DecodeSymbolAddressExternal paths
over so symbol-name and file/line lookups stay fresh after the target
dlopens a library.

Rate-limit the re-parse to once per wall-clock second so samples
landing on permanently unresolvable regions (JIT, vDSO, stacks) do
not trigger a full parse each time.
2026-04-24 21:16:42 +02:00
Bartosz Taudul
7448c0fbe1 Cover all target threads in tracy-monitor.
perf_event_open(pid>0, cpu>=0) binds to a single task, so the previous
setup only sampled the target's main thread. In monitor mode, enumerate
/proc/<pid>/task/ and open one per-task event per existing thread with
cpu=-1; inherit=1 then covers every descendant. Self-profiling behavior
is preserved byte-for-byte: the iter list becomes (currentPid, i) for
each CPU, exactly what the old code did inline.
2026-04-24 21:14:33 +02:00
Bartosz Taudul
2755166543 Flush stdout before perf preflight, so it's printed before stderr. 2026-04-24 21:10:11 +02:00
Bartosz Taudul
cbfa625fb9 Harden tracy-monitor startup and shutdown paths.
- Loop startup waitpid on EINTR; kill and reap the child on fatal error
  or when interrupted, instead of leaking a ptrace-stopped process.
- Treat PTRACE_DETACH failure as fatal -- otherwise the child is stuck
  stopped forever.
- Zero-initialize procName so the memcpy into ___tracy_magic_process_name
  does not copy uninitialized stack past the NUL.
- Forward SIGINT to the child from the signal handler when in forked
  mode, so Ctrl-C during a blocking waitpid unblocks cleanly.
- Preflight perf_event_open on the target before StartupProfiler so
  permission failures surface with actionable guidance instead of
  silently producing no samples.
- Also handle SIGHUP and SIGQUIT.
2026-04-24 21:07:41 +02:00
Bartosz Taudul
23930e998b Display label with assistant model name and reply duration for each message. 2026-04-24 19:53:45 +02:00
Bartosz Taudul
9b708c433f Store model and response time for assistant messages. 2026-04-24 18:15:49 +02:00
Bartosz Taudul
2c6adfb416 Regenerate markdown manual. 2026-04-23 00:28:59 +02:00
Bartosz Taudul
bde6e06cd7 Update manual. 2026-04-23 00:24:09 +02:00
Bartosz Taudul
4022494934 Update NEWS. 2026-04-22 22:07:39 +02:00
Bartosz Taudul
68ee8704d2 Merge pull request #1335 from siliceum/feature/check-macros-mismatch
Detecting macro definitions mismatches at link time
2026-04-21 18:12:32 +02:00
Bartosz Taudul
8d9a8494b8 Merge pull request #1344 from siliceum/fix/etw-compat
Fix TracyETW_compat.h structs and values based on docs
2026-04-21 18:11:01 +02:00
Clément Grégoire
c1ab158f6c Update manual with mismatch detection info 2026-04-21 13:51:24 +02:00
Clément Grégoire
ca076b4a60 Add TracyMangle.hpp file to centralize config name mangling
Also rename MANGLED_NAME_BASED_ON_DEFINES => MANGLED_NAME_BASED_ON_CONFIG
2026-04-21 13:28:48 +02:00
Clément Grégoire
a4f245550b Macro incompatibility experiment
We redirect GetProfiler() (most likely used by any project consuming tracy, since it's used by `tracy::ScopedZone`) to its implementation which now has a different function name based on the macros that can impact ABI (and enabled/disabled).
That way, when linking with mismatched defines you'd get an error such as

> main.obj : error LNK2019: unresolved external symbol "int __cdecl GetProfiler_CFG_E0_OD0_DI0_ML0_F0_DHT0_TF0(void)" (?GetProfiler_CFG_E0_OD0_DI0_ML0_F0_DHT0_TF0@@YAHXZ) referenced in function "int __cdecl GetProfiler(void)" (?GetProfiler@@YAHXZ)

Or

>[build] /usr/bin/ld: CMakeFiles/app.dir/main.cpp.o: in function `GetProfiler()':
[build] /..../TracyProfiler.hpp:143: undefined reference to `GetProfiler_CFG_E1_OD0_DI0_ML0_F0_DHT0_TF0()'

Reason for going with acronym+0/1 instead of just acronym when enabled is for us to be able to tell users easily which define is wrong by just looking at the error if needed.

The only thing we don't really detect is user not having TRACY_ENABLE but tracy having been built with it. This is because macros become noops in that case, with no reference to `GetProfiler`.
There may be a way to do it by introducing a local variable into each TU, but I don't really like that idea.
We could also add pragma detect mismatch for a more user-friendly error on windows (https://learn.microsoft.com/en-us/cpp/preprocessor/detect-mismatch?view=msvc-170).
2026-04-21 13:28:48 +02:00
Clément Grégoire
1711d024dd Fix TracyETW_compat.h structs and values based on docs
https://learn.microsoft.com/fr-fr/windows/win32/api/evntprov/ns-evntprov-event_filter_event_id
https://learn.microsoft.com/fr-fr/windows/win32/etw/system-providers

Note: WinSDK does not use ULL in keyword constants so I removed them too.
2026-04-21 13:20:48 +02:00
Bartosz Taudul
217bdcf5a9 Merge pull request #1343 from siliceum/fix/better-tracefs-detection
Better tracefs detection
2026-04-21 11:55:10 +02:00
Clément Grégoire
4aac9e677d Fix formating/whitespaces in SysTraceStart 2026-04-21 09:51:43 +02:00
Clément Grégoire
aba343e429 Pick first debugfs entry only 2026-04-21 09:51:42 +02:00
Bartosz Taudul
25f09bee2c Merge pull request #1342 from siliceum/fix/1337-respect-max-sample-rate
Fix #1337 : On Linux respect max sample rate
2026-04-20 19:33:14 +02:00
Clément Grégoire
cb9ef7814e Use ReadFile and atoi 2026-04-20 18:05:31 +02:00
Clément Grégoire
8f208d732a Fix extra space 2026-04-20 18:04:41 +02:00
Clément Grégoire
8816dd0557 Allow finding tracefs through debugfs as a fallback for older kernels/systems that only mount debugfs 2026-04-20 15:04:33 +02:00
Clément Grégoire
0a5647b20f Use mnt_type instead of mnt_fsname for tracefs discovery
This is to be consistent with what libtracefs does: 6fad6a14ba/src/tracefs-utils.c (L104)
In theory one may mount tracefs with another name, though unlikely.
2026-04-20 15:00:37 +02:00
Clément Grégoire
d48794024d Log why SysTrace does not start on Linux 2026-04-20 14:49:57 +02:00
Clément Grégoire
95b6fdeed3 Fixes #1337: On Linux, respect perf_event_max_sample_rate to avoid lost events
This may be especially useful for low performance machines. We also warn about this behaviour through TracyDebug which ends up in Messages.
2026-04-20 14:31:41 +02:00
Clément Grégoire
e550e15ce6 Turn GetSamplingPeriod into SamplingFrequencyToPeriodNs
This forces to (re)use frequency values as input, which may be changed by the platform code later on. This way we have a single "source of truth" for sample freq.
Also removed the Win32 `GetSamplingInterval` which was a wrapper above `GetSamplingPeriod` but its value would be divided again anyway.
2026-04-20 14:29:35 +02:00
Bartosz Taudul
4e670fcaf5 Merge pull request #1338 from imsarllc/fix_32bit_arm_issue
Add static cast to uint64_t for std::min
2026-04-17 18:44:09 +02:00
Erik van Zwol
70fc86536c Add static cast to uint64_t for std::min
On 32-bit arm, phdr.p_vaddr is 32-bit, which causes a compilation
error because std::min expects both arguments to be of the same
type. Adding the static cast handles this case explicitly.
2026-04-17 09:45:43 -06:00
Bartosz Taudul
1c3691a57b Add llm chat topic. 2026-04-17 01:27:38 +02:00
Bartosz Taudul
ebf3f02264 Merge pull request #1330 from bmilanich/cuda-graph-fallback
TracyCUDA: show GPU zones for CUDA Graph-launched kernels
2026-04-15 21:14:09 +02:00
Basil Milanich
0a0566abc5 Update stale MEMORY2 comment to reflect cbid tracker removal
The comment still described consuming/not-consuming cudaCallSiteInfo
entries, but memory CBIDs are no longer tracked so no entry exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:28:13 -05:00
Basil Milanich
58809f95ff Use atomic flag for retirement check to avoid mutex on hot path
Replace the mutex-guarded empty check in OnBufferCompleted with an
std::atomic<bool> dirty flag. The mutex is now only acquired when
there is actual retirement work to do. Also update stale comment
on cudaGraphCurrentLaunch that said "let them leak".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:23:42 -05:00
Basil Milanich
8f6249c12f Restore insert_or_assign over operator[] for graphLaunchCache
operator[] on ConcurrentHashMap returns a reference after releasing the
read lock — the subsequent assignment happens with no lock held. This is
a latent data race if the map is ever accessed from multiple threads.

insert_or_assign performs the lookup and assignment atomically under a
single write lock, which is the correct pattern for a ConcurrentHashMap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:23:42 -05:00
Basil Milanich
da878d29d1 Address PR review feedback from slomp
- Add `using GraphID = uint32_t` typedef and use it throughout for
  graphId-typed variables (PersistentState, matchGraphActivityToAPICall,
  getGraphIdFromRecord, retirement set, buffer loop).

- Move matchError from matchGraphActivityToAPICall to caller sites
  (KERNEL, MEMCPY, MEMSET handlers). Keeping the error at the caller
  provides more debugging context about which activity kind failed.
  Remove the now-unnecessary `kind` parameter from the function.

- Replace insert_or_assign with operator[] assignment in
  matchGraphActivityToAPICall. Access to graphLaunchCache is
  single-threaded (CUPTI worker), so the simpler syntax is sufficient.
  Remove the insert_or_assign method from ConcurrentHashMap entirely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:23:42 -05:00
Basil Milanich
b22c8e86ba Remove memory API calls from cbid tracker maps
cudaMalloc/cudaFree (and driver equivalents) were tracked in
cbidRuntimeTrackers/cbidDriverTrackers, creating a cudaCallSiteInfo
entry on each API call. But the MEMORY2 handler never calls
matchActivityToAPICall (and never calls EmitGpuZone) — it only needs
the address, size, and timestamp from the activity record itself. Since
no activity handler consumes these entries, they leaked indefinitely.

Remove the 6 memory API CBIDs from both tracker maps so no entry is
created. This eliminates the leak with no change in visible behavior:
the MEMORY2 handler already operates independently of cudaCallSiteInfo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 10:23:42 -05:00
Basil Milanich
ec7805fd92 Retire cudaGraphCurrentLaunch entries on cudaGraphExecDestroy
Without retirement, the cache grows by one entry per unique exec handle
ever launched and never shrinks. While bounded by the number of distinct
execs in the application, long-running programs creating and destroying
many exec handles accumulate stale entries indefinitely.

Retirement mechanism:
- At cudaGraphExecDestroy (ENTER, while handle is still valid): call
  cuptiGetGraphExecId to translate exec handle → graphId and add to a
  pending-retirement set. Works for both runtime (cudaGraphExecDestroy)
  and driver (cuGraphExecDestroy) APIs. No new subscription needed —
  the existing cuptiEnableDomain already routes all API callbacks here.

- Deferral in OnBufferCompleted: erasure is not done immediately because
  cudaGraphExecDestroy does not wait for GPU completion. CUPTI may still
  have undelivered activity records for the last launch in its internal
  buffers. We defer the erase until a full buffer arrives that contains
  no records bearing the retired graphId, indicating all in-flight
  records have been delivered.

- getGraphIdFromRecord: new helper that extracts the graphId field from
  CONCURRENT_KERNEL / MEMCPY / MEMSET activity records (the three kinds
  that carry a graphId) for use in the per-buffer tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 10:23:42 -05:00
Bartosz Taudul
a64b9a2029 Proper mutex wrapper use.
In the JSON exception catch handler, m_jobsLock.lock() is called directly
on the mutex instead of through the jobsLock unique_lock. When the function
returns, jobsLock's destructor runs but it doesn't own the lock (it was
unlocked earlier at line 1134), and m_jobsLock is never released. This
causes a permanent deadlock the next time anything tries to acquire
m_jobsLock.
2026-04-11 17:29:44 +02:00
Bartosz Taudul
0269a196a4 Add TracyTaggedUserlandAddress.hpp to include list in meson. 2026-04-11 13:16:47 +02:00
Bartosz Taudul
010d25be06 Merge pull request #1332 from Brainzman/fix-missing-installed-include
Fix missing TracyTaggedUserlandAddress.hpp include due to it not being installed
2026-04-11 13:15:24 +02:00
Jonas Holzman
0de97789dd Fix missing TracyTaggedUserlandAddress.hpp due to it not being installed
Currently including the Tracy.hpp header from a set of installed Tracy
headers will result in the following error:

In file included from <...>/tracy/include/tracy/tracy/Tracy.hpp:133:
In file included from <...>/tracy/include/tracy/tracy/../client/TracyLock.hpp:9:
In file included from <...>/tracy/include/tracy/tracy/../client/TracyProfiler.hpp:18:
<...>/tracy/include/tracy/tracy/../client/../common/TracyQueue.hpp:6:10: fatal error: 'TracyTaggedUserlandAddress.hpp' file not found
    6 | #include "TracyTaggedUserlandAddress.hpp"
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Apparently introduced in f981330, which included the
TracyTaggedUserlandAddress.hpp header in TracyQueue.hpp without adding
it to the list of installed common header. Fixed by making the necessary
CMake change to install the header.

Ran into this issue while integrating Tracy as a dependency within
Blender[^1], where we use the latest main instead of stable for WoA
support, and use the install target to harvest the static lib and
headers for our libraries.

[^1]: https://projects.blender.org/blender/blender/pulls/156661
2026-04-11 12:27:40 +02:00
Bartosz Taudul
f71c74aaf7 Change TRACY_ENABLE default to OFF to match documentation.
The documentation states that Tracy is disabled by default, but the
build system defaults were ON/true. Change CMake and Meson defaults to
OFF/false. Projects that need profiling enabled must now opt in
explicitly. Add explicit TRACY_ENABLE=ON / tracy_enable=true to CI
steps and the test project to preserve existing behavior.
2026-04-10 18:49:04 +02:00
Bartosz Taudul
82560c8c0e Fix emscripten CI job. 2026-04-10 17:06:34 +02:00
Basil Milanich
12dc23f67e Add graphId recycle investigation test
Tests whether CUPTI recycles graphId values after cudaGraphExecDestroy,
which would be the only scenario where the graphLaunchCache in TracyCUDA
could serve stale entries for a non-matching exec handle.

Result (H100, CUDA 12, CUPTI): graphId is a monotonically increasing
counter that is never recycled. 22 create/instantiate/launch/destroy
cycles produced unique IDs ranging from 2 to 65 (incrementing by 3 per
cycle — one unit per node created during graph construction).

This confirms that the stale-cache concern raised in code review is not
a real risk in practice: two distinct exec handles always have distinct
graphIds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:34:51 -05:00
Basil Milanich
c807367099 Add correlationId investigation test for CUDA Graph launches
Tests two questions:
1. Does relaunching the same cudaGraphExec produce a new correlationId
   each time, or is it reused?
2. Do two different cudaGraphExec handles from the same cudaGraph share
   a graphId?

Results on H100, CUDA 13.1:
- Each launch of the same exec handle gets a strictly unique, monotonically
  increasing correlationId. CPU callback corrId == GPU activity corrId.
  This is formally documented in cupti_activity.h:
    "Each graph launch is assigned a unique correlation ID that is
     identical to the correlation ID in the driver API activity record
     that launched the graph."
- graphId identifies the exec handle (instantiation), not the graph
  definition. Two cudaGraphInstantiate calls on the same graph produce
  different graphIds.

These findings confirm that the cudaGraphCurrentLaunch cache in
matchGraphActivityToAPICall is always refreshed by the first activity
of each new launch before the graphId fallback path is ever used.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:28:34 -05:00
Basil Milanich
9e36e5d1ad Fix MEMORY2 handler: don't consume cudaCallSiteInfo entry
Calling matchActivityToAPICall in the MEMORY2 handler was consuming
the cudaCallSiteInfo entry for the graph launch correlationId. If a
graph mixes alloc nodes with kernel/memcpy nodes, all activities share
the same correlationId — consuming it here would cause
matchGraphActivityToAPICall to fail for the kernel/memcpy records that
follow, silently dropping their GPU zones.

Since apiCall is never used by the MEMORY2 handler (only address, size,
and timestamp from the activity record are needed), remove the call
entirely and leave the entry for the kernel/memcpy to consume and cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 16:01:41 -05:00
Basil Milanich
b6222ae28b Fix MEMORY2 handler: don't gate memory tracking on API call correlation
CUpti_ActivityMemory3 has no graphId field, so graph-launched alloc
nodes and pre-profiling allocations can't be correlated to an API call.
The handler only needs address, size, and timestamp from the activity
record — apiCall is never used. Remove the early return so memory
tracking works in all cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:33:41 -05:00
Basil Milanich
703df05529 Document known MEMORY2 limitation for graph-launched alloc nodes
CUpti_ActivityMemory3 has no graphId field, so matchGraphActivityToAPICall
cannot be applied. Graph-launched cudaGraphAddMemAllocNode emits multiple
MEMORY2 records sharing the launch correlationId; only the first is
tracked, subsequent ones fire a spurious matchError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:30:27 -05:00
Basil Milanich
c8ebc6f21e Review fixes: correct stale comments, fix fetch() missing lock, tidy helper
- Fix wrong comments on graph launch tracker entries: they claimed
  correlation works "via CUPTI_ACTIVITY_KIND_GRAPH_TRACE", but that
  approach was rejected (GRAPH_TRACE suppresses per-kernel records).
  The actual mechanism is the shared correlationId across all nodes
  in one graph launch.
- Fix ConcurrentHashMap::fetch() missing its read lock — a pre-existing
  data race now exercised by the new graph correlation hot path.
- Cache PersistentState::Get().cudaGraphCurrentLaunch in a local ref
  inside matchGraphActivityToAPICall instead of calling Get() twice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:00:01 -05:00
Basil Milanich
0b6e27934d ConcurrentHashMap: add insert_or_assign to replace erase+emplace
The cudaGraphCurrentLaunch cache update was acquiring the write lock
twice (once for erase, once for emplace). Wrapping
std::unordered_map::insert_or_assign under a single write lock lets
the caller do it in one operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:48:53 -05:00
Basil Milanich
d9a1cc06c1 Fix repro build: add -arch=native to use correct GPU architecture
NVCC 13.1 defaults to a PTX version incompatible with the installed
driver (580.105.08), causing kernels to silently fail with "provided
PTX was compiled with an unsupported toolchain". Use -arch=native so
NVCC auto-detects the target GPU (H100, sm_90) at build time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:33:05 -05:00
Basil Milanich
f74dd21573 Refactor: extract matchGraphActivityToAPICall helper
The kernel, memcpy, and memset cases all had identical logic for
handling graph-launched activities. Extract it into a single helper
next to matchActivityToAPICall.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:08:39 -05:00
Basil Milanich
4ccaea9f08 Expand repro: multiple graphs, multiple kernels, interleaved launches
Tests:
- Two distinct graphs (different graphIds) on the same stream
- Graph A: kernel + memcpy + kernel (3 nodes)
- Graph B: scale + add + scale (3 nodes)
- 5 interleaved launches of each, stressing the graphId cache
- Expected 30 graph GPU zones total

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:54:08 -05:00
Basil Milanich
d36ca27041 Fix graph correlation: use shared correlationId, not GRAPH_TRACE
CUPTI discovery: all kernels launched by one cuGraphLaunch share the
same correlationId as the launch call itself. GRAPH_TRACE was the
wrong approach — enabling it suppresses per-kernel CONCURRENT_KERNEL
records entirely, replacing them with graph-level summaries.

New approach:
- Drop CUPTI_ACTIVITY_KIND_GRAPH_TRACE (it conflicts with CONCURRENT_KERNEL)
- Drop two-pass buffer processing (no longer needed)
- On the first kernel/memcpy/memset from a graph launch, matchActivityToAPICall
  succeeds (consuming the cuGraphLaunch entry) and the result is cached in
  cudaGraphCurrentLaunch[graphId]
- Subsequent operations from the same launch find the cached entry via graphId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:36:14 -05:00
Basil Milanich
2e5b076175 Also track cudaGraphLaunch runtime API for graph correlation
The repro uses cudaGraphLaunch (runtime API) not cuGraphLaunch (driver
API). Add cudaGraphLaunch_v10000 and its _ptsz variant to
cbidRuntimeTrackers so that graphs launched via the runtime API also
get their CPU call site captured for GPU zone correlation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:18:20 -05:00
Basil Milanich
7bca9dcd90 Fix CUDA Graph GPU zones with proper cuGraphLaunch correlation
Replace the synthetic APICallInfo hack with proper correlation via
CUPTI_ACTIVITY_KIND_GRAPH_TRACE. When cuGraphLaunch fires an API
callback, its correlationId is stored in cudaCallSiteInfo. The
GRAPH_TRACE activity record carries the same correlationId plus the
graphId, which lets us build a graphId→APICallInfo map. Kernel/memcpy/
memset activities then look up this map via their graphId field.

Key changes:
- Add cuGraphLaunch/cuGraphLaunch_ptsz to cbidDriverTrackers so the
  API callback machinery captures the CPU call site
- Enable CUPTI_ACTIVITY_KIND_GRAPH_TRACE and handle it in
  DoProcessDeviceEvent to populate cudaGraphCurrentLaunch[graphId]
- Add cudaGraphCurrentLaunch map to PersistentState
- Two-pass buffer processing in OnBufferCompleted so GRAPH_TRACE
  records (which complete last on GPU) are processed before the
  kernel/memcpy/memset records that depend on them
- Replace graphId=0 fallback in kernel/memcpy/memset with proper
  cudaGraphCurrentLaunch lookup; fall through to matchError if
  the graphId is not found
- Update repro to include TracyCUDA headers and properly test
  GPU zone correlation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:03:58 -05:00
Bartosz Taudul
00a069d608 Perform meson build in mingw workflow. 2026-04-03 21:11:03 +02:00
Bartosz Taudul
adf3a5b998 Build test application with CMake on mingw. 2026-04-03 20:10:06 +02:00
Bartosz Taudul
09d6a674c4 No -rdynamic on Windows. 2026-04-03 20:10:06 +02:00
Bartosz Taudul
ab61777896 Fix mingw GUID definitions. 2026-04-03 20:10:05 +02:00
Bartosz Taudul
c564b163d3 Client code must target C++11. 2026-04-03 20:10:03 +02:00
Bartosz Taudul
43c0f67353 Enable mingw CI builds on push and pull request. 2026-04-03 02:11:54 +02:00
Bartosz Taudul
44b1de2d71 Target win10 with mingw. 2026-04-03 02:11:16 +02:00
Bartosz Taudul
803b10fd6b Do not block vsync capture on mingw. 2026-04-03 02:11:13 +02:00
Bartosz Taudul
3ac364ebcb Add mingw ETW compat header. 2026-04-03 01:56:46 +02:00
Marcos Slomp
842420439d fix LogString wire in the DequeueSerial path (#1328) 2026-04-03 00:37:16 +02:00
Marcos Slomp
ee976f8146 Prevent CMake from re-generating files in the data folder every time (#1327) 2026-04-03 00:24:39 +02:00
Bartosz Taudul
bb1d4ad580 Revert usearch bump – broken on windows. 2026-04-02 21:05:35 +02:00
Bartosz Taudul
d5eb85e4c4 Bump deps. 2026-04-02 21:00:14 +02:00
Bartosz Taudul
08f82dc0be Add mingw CI build setup. 2026-04-02 01:02:18 +02:00
Bartosz Taudul
0fb2f8d75d Build client library with CMake and Meson again on Linux. 2026-04-02 00:42:16 +02:00
Bartosz Taudul
a861c6671c Reenable LTO, disable parallel build on GitHub runners. 2026-04-01 00:38:40 +02:00
Bartosz Taudul
b18b7461e4 Disable LTO on Linux CI. 2026-03-31 23:15:15 +02:00
Bartosz Taudul
d8139f2058 Allow disabling LTO, mold linker, ccache. 2026-03-31 23:15:15 +02:00
Bartosz Taudul
af5f1c4d52 Use CMake option macros. 2026-03-31 23:15:15 +02:00
Bartosz Taudul
5877db5411 Move NO_ISA_EXTENSIONS option to config.cmake. 2026-03-31 23:15:14 +02:00
Bartosz Taudul
eb81bdc3f8 NO_STATISTICS is not an option in most of the tools. 2026-03-31 23:15:14 +02:00
Bartosz Taudul
b6b1eed1d5 Externalize option setting macros. Add one for non-booleans. 2026-03-31 23:15:14 +02:00
Bartosz Taudul
e89eadf91a Fix copy pasta. 2026-03-31 23:15:11 +02:00
Bartosz Taudul
785eaeac86 Merge pull request #1324 from siliceum/fix/linux-comp-issue
Fix #1321 linux compilation issue
2026-03-31 14:02:28 +02:00
Clément Grégoire
313d5b681e Fix #1321 linux compilation issue introduc 2026-03-31 13:43:36 +02:00
Bartosz Taudul
16f7cd3c68 Merge pull request #1321 from siliceum/refactor/tracycallstack
Small cosmetic changes + symsrv.dll detection
2026-03-31 12:25:12 +02:00
Bartosz Taudul
a0b3a71212 Add monitor utility. 2026-03-31 02:06:01 +02:00
Bartosz Taudul
850cfd90f7 Add callstack decode functionality for external processes. 2026-03-31 01:18:51 +02:00
Bartosz Taudul
f8c6f4a7ba Cosmetics. 2026-03-31 01:18:51 +02:00
Bartosz Taudul
eca9609f52 Shut up about anonymous namespaces. 2026-03-31 01:18:50 +02:00
Bartosz Taudul
739123d005 Add a way to override profiled program name and PID. 2026-03-31 01:18:50 +02:00
Bartosz Taudul
53439ea5e2 Add external image cache machinery. 2026-03-31 01:18:50 +02:00
Clément Grégoire
da4b95bf59 Use CopyString(Fast) where possible in TracyCallstack 2026-03-30 22:33:43 +02:00
Clément Grégoire
79f7d99b02 Add SymSrv.dll check
This is often a source of missing symbols or incomprehension as to why they are not getting resolved. Having a debug log will help debugging such cases.
2026-03-30 22:33:43 +02:00
Clément Grégoire
8c5724d125 Fix formating in DbgHelpInit 2026-03-30 22:33:43 +02:00
Clément Grégoire
cf33cbd33f Extract MakeUnresolvedCallstackEntryData 2026-03-30 22:33:42 +02:00
Bartosz Taudul
c53d94f538 Set color diagnostics in test CMakeLists.txt. 2026-03-30 21:47:45 +02:00
Bartosz Taudul
b3aaac43ca Move ELF headers definition to a separate header file. 2026-03-30 20:56:45 +02:00
Bartosz Taudul
7974a74580 Add external_file support to libbacktrace
- Add external_file field to backtrace_state struct
- Add backtrace_create_state_for_file() function that marks
  state for external ELF files not loaded in current process
- In backtrace_initialize, use external_file flag to:
  - Pass exe=0 to elf_add for ET_DYN files (allows DWARF loading)
  - Skip dl_iterate_phdr enumeration (avoids noise from current process)

This enables symbol resolution from arbitrary ELF files on disk,
with caller responsible for address translation.
2026-03-30 19:52:12 +02:00
Bartosz Taudul
2f1fc19f88 libbacktrace: use correct names in #undef of ELF macros (b9e4006) 2026-03-30 19:40:30 +02:00
Bartosz Taudul
52607f70fe libbacktrace: add casts to avoid undefined shifts (78af4ff, 0034e33) 2026-03-30 19:38:45 +02:00
Bartosz Taudul
d79b7d4176 libbacktrace: don't get confused by overlapping address ranges (3d0be55) 2026-03-30 19:33:37 +02:00
Bartosz Taudul
6010770879 Update uops.info data. 2026-03-30 02:23:33 +02:00
Bartosz Taudul
c8a93adb01 Add merge utility to CI builds for linux, macos, and windows 2026-03-28 22:43:07 +01:00
Bartosz Taudul
1dee03e4f2 Rename workflow job names for uniqueness across workflows 2026-03-28 22:30:29 +01:00
Bartosz Taudul
0312a77390 Add GIT_REV to tools config in CI setup. 2026-03-28 21:59:10 +01:00
Bartosz Taudul
fddbb5d990 Print tracy version in all tools. 2026-03-28 21:38:47 +01:00
Bartosz Taudul
8bd49317ca Set include list for GitRef. 2026-03-28 21:38:27 +01:00
Bartosz Taudul
1b34592d80 Move git ref CMake extractor to a separate file. 2026-03-28 19:23:15 +01:00
Bartosz Taudul
6f91104542 Include git ref in profiler's --help output. 2026-03-28 12:44:12 +01:00
Bartosz Taudul
e300d56f68 Force resort of possibly broken plots. 2026-03-27 20:16:27 +01:00
Bartosz Taudul
8351daea73 Add ability to mark the SortedVector unsorted. 2026-03-27 20:16:27 +01:00
Bartosz Taudul
b5a322d122 Fix SortedVector regression.
Sort unsorted part of the vector, not the already sorted part.
2026-03-27 17:49:49 +01:00
Bartosz Taudul
77a53e2b5b No need to do strcmp here. 2026-03-24 23:47:40 +01:00
Bartosz Taudul
ef41e12ba8 Fix missing outer loop exit. 2026-03-24 22:51:02 +01:00
Basil Milanich
6c6999bf01 Add CUDA Graph GPU zone repro
Minimal reproducer showing that CUDA Graph-launched kernels produce
0 GPU zones in Tracy. The repro creates a simple graph (2 kernels +
1 memcpy), launches it 10 times, and expects ~30 GPU zones. Without
the fallback patch, all activity records are dropped by matchError().

Tested on NVIDIA H100, CUDA 13.1.
2026-03-24 09:37:50 -05:00
Basil Milanich
aa657d5438 Fix missing GPU zones for CUDA Graph-launched kernels
When kernels are launched via CUDA Graphs (cuGraphLaunch), CUPTI delivers
CONCURRENT_KERNEL, MEMCPY, and MEMSET activity records but no
corresponding API callback fires for the individual operations. This
means matchActivityToAPICall() always fails, and every GPU activity
record is silently dropped by matchError().

Fix this by falling back to a synthetic APICallInfo using the GPU
timestamps from the activity record when no API correlation exists.
This produces correct GPU zones with kernel names and timing — just
without the CPU-to-GPU launch correlation arrow.

Tested on NVIDIA H100 with CUDA 13.1: before this fix, 0 GPU zones
appeared for CUDA Graph workloads; after, all kernel and memcpy zones
are visible in the Tracy timeline.
2026-03-24 09:24:23 -05:00
Bartosz Taudul
e1143fd985 Try to remove excess frames from reconstructed callstack. 2026-03-24 01:14:38 +01:00
Bartosz Taudul
fb9912db5c Merge pull request #1315 from slomp/slomp/d3d12-opsie
D3D12 opsie
2026-03-23 23:45:51 +01:00
Marcos Slomp
a59ff2dcbb Update TracyD3D12.hpp
opsie
2026-03-23 15:43:36 -07:00
Bartosz Taudul
e792752104 Ignore completely non-local samples. 2026-03-22 17:04:31 +01:00
Bartosz Taudul
3c283187c3 Extract functionality for counting local frames in a callstack. 2026-03-22 17:00:15 +01:00
Bartosz Taudul
1967f2c79b Do proper frame matching instead of relying just on callstack size. 2026-03-22 16:51:55 +01:00
Bartosz Taudul
f5df9f9c24 Remove dead code. 2026-03-21 23:36:15 +01:00
Bartosz Taudul
5040412813 Simplify search for max-local call stack root. 2026-03-21 22:35:25 +01:00
Bartosz Taudul
9326227bcd Display reconstructed callstack in zone info window. 2026-03-21 01:48:00 +01:00
Bartosz Taudul
b52af15504 Change DrawCallstackTable interface to accept data + size pair. 2026-03-21 01:47:26 +01:00
Bartosz Taudul
e1381d1beb Draw reconstructed callstack trace in zone tooltips. 2026-03-21 01:27:12 +01:00
Bartosz Taudul
c4f42b1bc9 Basic heuristics for reconstruction of zone callstack from sample data. 2026-03-21 01:26:40 +01:00
Bartosz Taudul
d0222ef7d9 Worker::GetZoneEnd() can be const. 2026-03-19 19:53:58 +01:00
Bartosz Taudul
87781fd75c Merge pull request #1312 from YaLTeR/patch-1
Add missing cstdint include
2026-03-19 18:18:50 +01:00
Ivan Molodetskikh
16af373a7e Add missing cstdint include
Fixes build on new gcc on Fedora 44.
2026-03-19 17:57:57 +03:00
Amy
e9314add15 clang visibility for TRACY_API (#1310) 2026-03-18 23:59:33 +01:00
Bartosz Taudul
145efd63c9 Change GetCallstackJson interface to data ptr + size. 2026-03-18 00:52:34 +01:00
Bartosz Taudul
0905e203e3 Add DrawCallstackCalls( data, size, ... ) interface. 2026-03-18 00:42:21 +01:00
Bartosz Taudul
4fbbfb410d Merge pull request #1307 from slomp/slomp/d3d12-callstack-patch
Fix callstack and transient zones in D3D12
2026-03-17 21:20:08 +01:00
Marcos Slomp
36aaa19ae9 instrumenting relevant events of D3D12 2026-03-17 13:14:15 -07:00
Marcos Slomp
8b1e413db5 fix handling of D3D12 callstacks and/or transient zones 2026-03-17 13:13:24 -07:00
Bartosz Taudul
54f778c204 Display ellipsis in single-line callstack printout, when frames remain. 2026-03-17 02:29:12 +01:00
Bartosz Taudul
3876d33a15 Display callstacks in zone tooltips. 2026-03-17 01:32:24 +01:00
Bartosz Taudul
94e5ff2cfb Add inline callstack display to zone info window. 2026-03-17 01:00:21 +01:00
Bartosz Taudul
79d165190a Improve parent zones display. 2026-03-17 00:07:47 +01:00
Bartosz Taudul
83b7d3a267 Simplify "zone trace" into "parent zones". 2026-03-16 01:44:57 +01:00
Bartosz Taudul
a51efcc974 Proper CMake setup for TRACY_CALLSTACK. 2026-03-15 22:17:26 +01:00
Bartosz Taudul
1476afb992 Do not force enable CMake configure on open in VS Code. 2026-03-15 22:17:18 +01:00
Bartosz Taudul
80b8e8284e Allow attaching zone histogram to llm. 2026-03-11 00:42:53 +01:00
Bartosz Taudul
8f9392cae7 Merge pull request #1304 from slomp/slomp/windows-opsie
Windows x64 vs ARM opsie
2026-03-09 21:35:48 +01:00
Marcos Slomp
01bd20c30a Windows x64 vs ARM opsie 2026-03-09 13:24:08 -07:00
Bartosz Taudul
b0dd2b291d Make a separate fast model optional. 2026-03-08 14:42:18 +01:00
Bartosz Taudul
9f432d5fa2 Cosmetics. 2026-03-08 13:20:15 +01:00
Bartosz Taudul
2f90aac0ac Force enable thinking for chat model, force disable for fast model. 2026-03-08 13:16:14 +01:00
Bartosz Taudul
869ad0a02d Include news, locations, videos from brave search results. 2026-03-07 22:33:32 +01:00
Bartosz Taudul
928de7ae6c Use common functionality for gathering brave search results. 2026-03-07 22:33:32 +01:00
Bartosz Taudul
feb07e476a Merge pull request #1289 from slomp/slomp/windows-on-arm
Support for Windows on ARM
2026-03-06 23:55:02 +01:00
Bartosz Taudul
460944ef42 Cosmetics. 2026-03-06 23:53:13 +01:00
Bartosz Taudul
f1bc3ef4c7 Merge pull request #1300 from slomp/slomp/c-api-get-time
Adding C API for Profiler::GetTime()
2026-03-06 12:18:05 +01:00
Marcos Slomp
cc21988537 adding C API for Profiler::GetTime() 2026-03-06 01:33:01 -08:00
Bartosz Taudul
db0082de3f Add brave search. 2026-03-06 02:07:21 +01:00
Bartosz Taudul
bbc937c309 Refactor FetchWebPage to FetchHttp with custom headers support 2026-03-05 23:59:27 +01:00
Bartosz Taudul
71c5494414 Move web search engine implementations to separate functions. 2026-03-05 20:57:44 +01:00
Bartosz Taudul
2157e0a1b6 Better handle duckduckgo error responses. 2026-03-05 20:57:44 +01:00
Bartosz Taudul
cf09a04e92 Add missing include. 2026-03-05 20:57:01 +01:00
Bartosz Taudul
aba6efd24a Merge pull request #1296 from slomp/slomp/spsc-notify-wait
Add notify/wait to SPSCQueue (reduce latency of Symbol Worker)
2026-03-05 20:14:50 +01:00
Marcos Slomp
4460e64f3a addressing code review 2026-03-05 11:10:43 -08:00
Marcos Slomp
c696cb7f63 rever tracy_SPSCQueue.h 2026-03-04 21:42:14 -08:00
Marcos Slomp
332bbf2310 don't modify tracy_SPSCQueue.h 2026-03-04 17:35:05 -08:00
Marcos Slomp
ab2c32e52f use SPSC queue notify/wait in the symbol queue to reduce the response latency of the symbol worker 2026-03-04 12:14:19 -08:00
Marcos Slomp
eaa376be05 adding minimalist notify/wait interface and implementation to SPSC queue 2026-03-04 12:13:20 -08:00
Bartosz Taudul
53fa2ba67b Fix includes. 2026-03-02 23:58:16 +01:00
Bartosz Taudul
80c849a2aa Fix client identification in tracy-capture-daemon
Use '<pid>_<ip>_<port>' string as client ID instead of IP+port hash.
This allows:
- Same program restarting (new PID) to be recognized as new client
- Multiple instances of same program (different PIDs) to capture separately
2026-03-02 23:00:42 +01:00
Bartosz Taudul
add9a77396 Document tracy-capture-daemon and reorganize merge section
- Add subsection about tracy-capture-daemon in the capturing section
- Move merge tool documentation to follow capture daemon section
- Both tools are now documented as part of the capture workflow
2026-03-02 22:39:18 +01:00
Bartosz Taudul
e6aae0c18e Add tracy-capture-daemon for multi-client capture
A discovery-and-capture daemon that listens for UDP broadcasts from
Tracy clients, automatically connects to discovered clients, and
captures each to a separate file.

Features:
- Continuous discovery until Ctrl+C
- Per-client capture threads
- Terminal display with per-client stats
- Output files named: <program>_<ip>_<port>.tracy
- Collision handling with _1, _2 suffix
- Graceful shutdown on signal

Based on the multicapture design by Grégoire Roussel, but simplified
to output separate files instead of merging (use tracy-merge for that).

Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
2026-03-02 22:32:45 +01:00
Bartosz Taudul
03f01b9c49 Include fixes. 2026-03-02 21:34:35 +01:00
Bartosz Taudul
2341dee04a Refactor capture utilities into CaptureOutput
Extract common output functions from capture.cpp into a proper library:

- InitTerminalDetection()/IsTerminal() - terminal detection
- AnsiPrintf() - printf with ANSI escape codes
- WaitForConnection() - blocks until connected, returns error code
- PrintCaptureProgress() - prints throughput/memory/time stats
- PrintWorkerFailure() - prints failure details with callstack

Functions are declared in CaptureOutput.hpp and implemented in
CaptureOutput.cpp. Both tracy-capture and future tools can share
this code.

Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
2026-03-02 21:28:22 +01:00
Bartosz Taudul
f5545f864d Document tracy-merge utility in the manual 2026-03-02 20:36:22 +01:00
Bartosz Taudul
2e82525fb4 Fix plot handling in tracy-merge
- Always export plots (remove -p/--export-plots option)
- Add plot name disambiguation: prefix with process name
- Include PID in name when same process/plot appears in multiple traces
2026-03-02 20:35:23 +01:00
Bartosz Taudul
0dc6be27f3 Add tracy-merge tool for combining trace files
Merges multiple .tracy files into a single combined trace using the
Import API. Each trace's threads are remapped using compound TIDs
encoding (pid << 32) | tid to prevent collisions.

Thread names are prefixed with process name. If the same process/thread
name combination appears in multiple traces, PID is included to
disambiguate (e.g., myapp[12345]/MainThread).

Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
2026-03-02 20:35:20 +01:00
Bartosz Taudul
49590756a0 Extract broadcast message parsing into TracyBroadcast
Move broadcast message parsing logic from profiler/src/main.cpp into
server/TracyBroadcast.cpp/hpp. This reduces code duplication and enables
reuse by other tools (e.g., multi-capture).

ParseBroadcastMessage() handles all broadcast protocol versions (0-3) and
returns std::optional<BroadcastMessage>. ClientUniqueID() generates a unique
identifier from IP address and port.

Co-authored-by: Grégoire Roussel <gregoire.roussel@wandercraft.eu>
2026-03-02 19:45:48 +01:00
Bartosz Taudul
9442517f30 Remove trailing whitespace. 2026-03-02 19:40:41 +01:00
Marcos Slomp
93036b6877 code review: implement in header, but don't bring windows.h along for the ride 2026-03-01 08:14:10 -08:00
Marcos Slomp
42b0512517 fix copy-pasta... 2026-02-28 20:02:21 -08:00
Marcos Slomp
83707a9c2f updated support list 2026-02-28 20:02:17 -08:00
Marcos Slomp
0f170f51c6 report GUI initialization error before terminating 2026-02-28 20:00:11 -08:00
Marcos Slomp
960911f263 update manual with remarks about Windows on ARM 2026-02-28 20:00:11 -08:00
Marcos Slomp
e41ae15f27 using ARM64 intrinsics on Windows on ARM 2026-02-28 20:00:11 -08:00
Bartosz Taudul
aa30c611a8 Merge pull request #1286 from slomp/slomp/touchpad-zoom-fix
Have Touchpad gestures either scroll or zoom the timeline, but not both together
2026-02-28 22:55:25 +01:00
Marcos Slomp
20932eae88 formatting 2026-02-27 19:30:35 -08:00
Marcos Slomp
54c63c11d4 ensure touchpad gestures either scroll or zoom the timeline, but not both simultaneously 2026-02-27 19:30:35 -08:00
Bartosz Taudul
d6620568e5 Make tool reply size limit configurable in LLM settings
Add option to configure the maximum tool reply size in the advanced
settings of the LLM assistant. The limit can be enabled/disabled via
a checkbox, with the value stored in bytes. The context-based limit
is now displayed alongside the configured limit for transparency.
2026-02-26 22:40:51 +01:00
Bartosz Taudul
ad659b8f8e Update manual. 2026-02-26 22:33:50 +01:00
Bartosz Taudul
8420a1d7d6 Let's try automatic upload of release artifacts. 2026-02-20 00:27:41 +01:00
Bartosz Taudul
730dce67e6 Split build.yml into windows.yml and macos.yml. 2026-02-20 00:10:57 +01:00
Bartosz Taudul
a9070da72e Cache CPM on CI. 2026-02-19 23:07:00 +01:00
Bartosz Taudul
0abf0083be Enable workflow dispatch for CI jobs. 2026-02-19 23:06:33 +01:00
Bartosz Taudul
2bc3b87a2d Fix TracyLlm initialization when LLM is disabled. 2026-02-19 18:33:45 +01:00
Bartosz Taudul
95661c24df Revert "Bump usearch to 2.24.0."
This reverts commit 6de054002d.
2026-02-19 18:03:51 +01:00
Bartosz Taudul
27a4091975 Merge pull request #1284 from slomp/slomp/syminit-path-win32
Ensure executable path is inspected when looking for PDB files
2026-02-19 12:27:42 +01:00
Marcos Slomp
853144214b buffer checks and typo 2026-02-18 16:13:26 -08:00
Bartosz Taudul
574e0d17ec Reorder symbol statistics checkboxes and disable kernel when external is off.
External symbols checkbox now appears before kernel symbols, and kernel
is disabled when external is not selected since kernel symbols are a
subset of external symbols.
2026-02-19 00:51:04 +01:00
Bartosz Taudul
cea95bdede Do not show external symbols, frame by default. 2026-02-19 00:45:44 +01:00
Marcos Slomp
12da626fd1 ensure executable path is inspected when looking for pdb files 2026-02-17 14:27:07 -08:00
Bartosz Taudul
6de054002d Bump usearch to 2.24.0. 2026-02-17 22:06:06 +01:00
Bartosz Taudul
9079baf0b3 Bump capstone to 6.0.0-Alpha7. 2026-02-17 22:01:39 +01:00
Bartosz Taudul
a754bbe3a4 Bump imgui to 1.92.6-docking. 2026-02-17 21:49:11 +01:00
Bartosz Taudul
2ecb45d8c0 Font data must be always available. 2026-02-17 21:48:43 +01:00
Bartosz Taudul
ebd9df6ab9 Merge pull request #1283 from navvyswethgraphics/support_windows_arm64_msvc
Add native Windows ARM64 (MSVC) support across profiler, server, and ToyPathTracer sample
2026-02-17 17:38:19 +01:00
Naveen Regulla
99b502f9a4 simplify and clarify leading zeros calculation 2026-02-17 17:13:36 +05:30
Naveen Regulla
9879a31fc5 Add Windows on ARM64 with MSVC support for Tracy Profiler
Introduce Windows ARM64(native) support across ToyPathTracer,
profiler, and server code paths when building with MSVC(_M_ARM64).

Key changes:
- MathSimd.h/Maths.h:
   - Fix NEON movemask constants for MSVC/ARM64 by loading from a uint32_t[]
    via vld1q_u32() and using vdupq_n_u32() for highbit.
- enkiTS/TaskScheduler.cpp:
   - Provide Pause() implementation on _M_ARM64 using __yield().
- profiler/winmain.cpp:
   -  AVX feature checks to x86/x64 only and skip on ARM64.
- server/TracyPopcnt.hpp:
   - Implement TracyCountBits using ARM NEON intrinsics.
   - Implement TracyLzcnt using _BitScanReverse64().
2026-02-17 16:42:50 +05:30
Bartosz Taudul
75320a3353 Fix race condition when regenerating LLM reply.
The chat regenerate/trash button handling had a monolithic lock that
blocked the worker thread during token counting. The token counting
is performed by calling TracyLlmApi::Tokenize(), which issues a
blocking HTTP POST request to the LLM API - on the UI thread.

To avoid blocking both the worker and freezing the UI during HTTP
calls, the lock was split into m_jobsLock and m_chatLock. However,
this introduced a race condition.

For the assistant role (regenerate message action), the code:
1. Stops the current job and queues a new one (under m_jobsLock)
2. Releases the lock
3. Performs token counting via HTTP calls (no lock held)
4. Re-acquires m_jobsLock and stops m_currentJob again

Between steps 2 and 4, the worker thread can complete the old job
and pick up the newly queued job. The stop at step 4 then incorrectly
stops the new regenerate job instead of the old one.

Under the original monolithic lock, the worker couldn't access
m_currentJob until after step 4, so the stop always applied to the
correct job. After the split, the second stop became harmful for
the assistant case.

The fix makes the second stop conditional on user role only (thrash
action), since that's the case where no new job is queued and we
genuinely need to stop any running generation before the user edits
their message.
2026-02-17 00:04:53 +01:00
Bartosz Taudul
c1556a0a73 Merge pull request #1281 from slomp/slomp/fix-zoom-hang
Fix UI hangs due to mouse-wheel zooming
2026-02-12 18:55:35 +01:00
Bartosz Taudul
02a229b12f Merge pull request #1282 from DaniPopes/dani/fix-wasm-paste
fix: handle paste events in wasm backend
2026-02-12 13:24:38 +01:00
DaniPopes
7d27fe6ff9 fix: handle paste events in wasm backend
Listen for browser paste events and directly inject clipboard text via
AddInputCharactersUTF8. Also suppress character input when Ctrl/Meta is
held to prevent 'v' from being typed on Cmd+V.
2026-02-11 23:42:45 -05:00
Marcos Slomp
f61fa7b0e4 ensure that t0 <= t1 to prevent time-point crossover (leading to UI hangs) 2026-02-11 14:45:38 -08:00
Bartosz Taudul
0d3b5b8a04 Merge pull request #1280 from DaniPopes/dani/apple-thread-name-no-truncate
Don't truncate thread names to 15 chars on Apple platforms
2026-02-11 22:39:06 +01:00
Bartosz Taudul
2d9bed27f2 Merge pull request #1279 from DaniPopes/dani/fix-macos-zoom-scale
fix: UI zoom scale not applying on macOS
2026-02-11 22:34:56 +01:00
DaniPopes
d47344187d fix: don't truncate thread names to 15 chars on Apple platforms 2026-02-11 16:25:42 -05:00
DaniPopes
12a964d3be fix: UI zoom scale not applying on macOS
On macOS with Retina displays, dpiScale is 2.0 but gets overridden to
userScale alone under __APPLE__. The early-return check compared
prevScale against the pre-override value (dpiScale * userScale), which
for 50% zoom evaluates to 2.0 * 0.5 = 1.0, matching the previous
prevScale of 1.0 and causing an early return before the scale change
could take effect. Moving the __APPLE__ override before the early-return
check ensures the comparison uses the actual effective scale.
2026-02-10 16:33:02 -05:00
Bartosz Taudul
ea8eca6a45 Merge pull request #1273 from siliceum/WSL-and-vm-support
Better support for WSL2/VMs
2026-02-09 11:45:55 +01:00
Clément Grégoire
b890719fdd Remove stray newline in TracySysTrace.cpp 2026-02-09 11:44:16 +01:00
Clément Grégoire
e9073800d5 Remove extraneous newline in meson.build 2026-02-09 11:41:11 +01:00
Clément Grégoire
d089c3e1b0 CMake: Enforce TRACY_TIMER_FALLBACK when TRACY_DISALLOW_HW_TIMER is set. 2026-02-09 11:40:07 +01:00
Clément Grégoire
b778b67676 Fix trailing whitespace 2026-02-09 11:37:07 +01:00
Clément Grégoire
2b2fafbcfc Start cleaning TRACY_HW_TIMER checks.
Ideally I think we might want to have TRACY_HW_TIMER mean only TSC/CNTVCT, and define TRACY_TIMER_FALLBACK for platforms that don't have them or have a special case such as iOS.
But for now, keep the same behaviour.
2026-02-09 11:30:24 +01:00
Bartosz Taudul
21086a25d6 Merge pull request #1275 from The-Briel-Deal/message_l_regress
Fix regression in TracyCMessageL*() functions
2026-02-08 23:04:54 +01:00
Gabriel Ford
08969fde4b Fix regression in TracyCMessageL*() functions
Fixes: https://github.com/wolfpld/tracy/issues/1274
2026-02-08 16:42:13 -05:00
Clément Grégoire
b27561c21e Add TRACY_DISALLOW_HW_TIMER for VMs / WSL2 2026-02-08 17:10:24 +01:00
Clément Grégoire
79cb60025d Monotonic clock fallback for systrace 2026-02-08 17:07:00 +01:00
Bartosz Taudul
04f24cb222 Merge pull request #1272 from DaniPopes/wasm-clipboard
Implement clipboard support for WASM backend
2026-02-07 12:58:09 +01:00
DaniPopes
a23e910d5f Implement clipboard support for WASM backend
The Emscripten backend was missing Platform_SetClipboardTextFn/GetClipboardTextFn,
so ImGui::SetClipboardText was a no-op in the browser.
Hook navigator.clipboard.writeText for writes and keep a local copy for reads.
2026-02-07 03:36:25 +01:00
Bartosz Taudul
c93c9bce6f Improve how tool calls are presented to user.
1. Tool replies are now integrated with tool calls.
2. Tool calls now have textual descriptions.
2026-02-06 18:37:01 +01:00
Bartosz Taudul
0b02b097a9 Pass current and end chat iterators to TracyLlmChat::Turn(). 2026-02-06 17:33:59 +01:00
Bartosz Taudul
9d78cca5ff Redo LLM locks. 2026-02-06 02:28:14 +01:00
Bartosz Taudul
a31ab53d26 Check if the response contains finish_reason before getting its value. 2026-02-05 18:31:05 +01:00
Bartosz Taudul
66cc77bd5f Don't access ghost zones before they are ready. 2026-02-05 00:42:42 +01:00
Bartosz Taudul
5a41f422fe Merge pull request #1269 from DaniPopes/thread-sort
Sort threads by ID after name
2026-02-04 14:08:26 +01:00
DaniPopes
5c5af7c1e5 Sort threads by ID after name
The "sort" button for threads uses an unstable sort, so threads with
the same name will shuffle around without ever stabilizing.

Use `id` to sort after comparing the name.
2026-02-04 06:51:43 +01:00
Bartosz Taudul
ca5ef812a3 Apple is also busted. 2026-02-01 20:39:53 +01:00
Bartosz Taudul
a9ea7ec1c9 Workaround libc++ issues. 2026-02-01 18:57:49 +01:00
Bartosz Taudul
8f0c83dbf9 Fix View ptr assignment. 2026-02-01 18:55:53 +01:00
Bartosz Taudul
2b201e6f59 RetrieveThread() uses a cache that cannot be shared between threads.
Note: IsThreadFiber() uses the same functionality, but is only called from
the main thread.
2026-02-01 18:01:39 +01:00
Bartosz Taudul
c86549a3bb View is accessed concurrently. 2026-02-01 17:29:34 +01:00
Bartosz Taudul
aba3ae2869 View's m_wasActive is used concurrently. 2026-02-01 16:55:46 +01:00
Bartosz Taudul
d1e831f69d Proper way of setting sanitizer parameters. 2026-02-01 16:44:15 +01:00
Bartosz Taudul
1d2b6cca24 Pack source code attachments.
Example source file, glm-4.7-flash:

5519 -> 4699 tokens (85%)
2026-02-01 16:37:42 +01:00
Bartosz Taudul
d71a38632c Nice formatting for host info. 2026-02-01 14:53:22 +01:00
Bartosz Taudul
d4d9339d95 Provide name of the profiled program to LLM. 2026-02-01 14:53:22 +01:00
Bartosz Taudul
0ad8767903 Explicitly mark non-inline frames as non-inline in LLM attachment. 2026-02-01 14:53:19 +01:00
Bartosz Taudul
1c383d8589 Merge pull request #1268 from siliceum/reenable-tracy-debug-as-log
Reenable TracyDebug LogString except under TRACY_DELAYED_INIT and not TRACY_MANUAL_LIFETIME
2026-02-01 14:53:02 +01:00
Clément Grégoire
55ce01c69e Reenable TracyDebug LogString except under TRACY_DELAYED_INIT and not TRACY_MANUAL_LIFETIME 2026-02-01 14:50:37 +01:00
Bartosz Taudul
e1fa883fbb Merge pull request #1267 from siliceum/fix/test-suppport-manual-lifetime
Tests should work under TRACY_MANUAL_LIFETIME.
2026-02-01 14:46:13 +01:00
Clément Grégoire
be78dc4bfb Tests should work under TRACY_MANUAL_LIFETIME.
This calls tracy::StartupProfiler(); before any other tracy call (in static init) if TRACY_MANUAL_LIFETIME is set
2026-02-01 14:45:00 +01:00
Bartosz Taudul
bc122d5c3d Do not call Glue() in PrintTextExt(). 2026-02-01 02:35:26 +01:00
Bartosz Taudul
b60ab86fa5 Extend link support to code sections, separators, etc. 2026-02-01 02:14:42 +01:00
Bartosz Taudul
32fae40c4e Extract link hover functionality to a separate function. 2026-02-01 02:05:08 +01:00
Bartosz Taudul
324ecdcb99 Change source code separator in LLM attachments. 2026-02-01 01:48:55 +01:00
Bartosz Taudul
c886c8fa00 Remove remains of vision in tool replies. 2026-01-31 19:21:24 +01:00
Bartosz Taudul
925732914a Draw source code preview in source file link tooltip. 2026-01-31 19:17:44 +01:00
Bartosz Taudul
d1ae380cc2 Make View::DrawSourceTooltip() public. 2026-01-31 19:17:07 +01:00
Bartosz Taudul
9d33668c9d Proper check for source file validity. 2026-01-31 18:54:43 +01:00
Bartosz Taudul
903b6b198f Retrieve source file string index and line number only once. 2026-01-31 18:42:07 +01:00
Bartosz Taudul
30ea4ed341 Print a nice tooltip for source links. 2026-01-31 18:35:37 +01:00
Bartosz Taudul
5b7a6bf447 Add instructions for providing source file links. 2026-01-31 18:03:31 +01:00
Bartosz Taudul
4fe8d16a20 Add support for source file links in markdown renderer. 2026-01-31 18:03:11 +01:00
Bartosz Taudul
7cc7e484b9 Make source search results more compact. 2026-01-30 22:55:55 +01:00
Bartosz Taudul
cfcc8af5c3 Remove indentation from tool replies. 2026-01-30 22:37:50 +01:00
Bartosz Taudul
3ce2905582 Default value does not need to be repeated. 2026-01-30 22:36:16 +01:00
Bartosz Taudul
67b75ac7fb Restore attachment indentation when viewing contents in UI. 2026-01-30 22:31:25 +01:00
Bartosz Taudul
99b70f7a6e Remove indentation from LLM attachments.
This has quite large effect on number of tokens. Example assembly attachment:

glm-4.7-flash: 7452 -> 5799 (77%)
gpt-oss-20b:   6896 -> 5245 (76%)
2026-01-30 22:27:55 +01:00
Bartosz Taudul
de002ed071 Update NEWS. 2026-01-30 21:49:17 +01:00
Bartosz Taudul
50eae09cda Source location may be missing a line number.
The valid check here is for the source file index being set (active).
The line number might be zero.

This fixes the "black" unknown source location in assembly listing.
2026-01-30 21:44:24 +01:00
Bartosz Taudul
b9a4bf4f7b Use the intended weighting order in ManageContext(). 2026-01-30 20:58:56 +01:00
Bartosz Taudul
d6e62b3c1f Spell out (long!) source file names in assembly attachments only once. 2026-01-30 01:38:20 +01:00
Bartosz Taudul
e017f504c6 Split source_file parameter context into context and context_back. 2026-01-30 01:07:15 +01:00
Bartosz Taudul
81e8b1a4d8 Update NEWS. 2026-01-29 23:05:40 +01:00
Bartosz Taudul
b7c54253ff Include TracyCrashHandler in a list of frames to cut on crash on Linux. 2026-01-29 22:44:23 +01:00
Bartosz Taudul
eb18dafb22 Check for a substring match in CutCallstack. 2026-01-29 22:44:13 +01:00
Bartosz Taudul
3ddb96b9af Change CutCallstack/SendCallstack interface to accept an array. 2026-01-29 22:41:21 +01:00
Bartosz Taudul
0c6b551afa Make TracyCrashHandler visible externally. 2026-01-29 22:39:24 +01:00
Bartosz Taudul
5a1ff2496c Rename CrashHandler to TracyCrashHandler. 2026-01-29 22:19:50 +01:00
Bartosz Taudul
7ef92c3547 Add crash reason and thread information to LLM attachment, if appropriate. 2026-01-29 22:09:01 +01:00
Bartosz Taudul
73572e6d7f Display notification that crash callstack is crash callstack. 2026-01-29 21:50:47 +01:00
Bartosz Taudul
ebc2c9e28a Use ranges. 2026-01-29 21:48:17 +01:00
Bartosz Taudul
ea9dbe5643 Prefer removal of older tool responses. 2026-01-28 21:34:19 +01:00
Bartosz Taudul
237058420f Make the forget message explain what happened. 2026-01-28 21:25:23 +01:00
Bartosz Taudul
b0704ba35f Adjust minimum available context logic.
Increase the context quota from 70% to 80%, as the previous value was too
conservative with large contexts.

Add a minimum bound of 4K to make small context workable.
2026-01-28 21:15:59 +01:00
Bartosz Taudul
9b98bce193 Reword no network access response. 2026-01-28 21:15:00 +01:00
Bartosz Taudul
41dd81ef7e Tighter packing of source file contents. 2026-01-28 20:59:33 +01:00
Bartosz Taudul
55f4f5f704 Change LLM API timeout from 5 to 20 minutes. 2026-01-28 18:47:14 +01:00
Bartosz Taudul
a371325346 Bump dependencies. 2026-01-26 20:48:15 +01:00
Bartosz Taudul
d1148377ae Insert hint in a valid way. 2026-01-26 11:56:43 +01:00
Bartosz Taudul
cf001a38df Adjust max size calculation. 2026-01-25 21:05:36 +01:00
Bartosz Taudul
841af4795c System prompt update. 2026-01-25 21:01:09 +01:00
Bartosz Taudul
daef2f4c6b Remove span tag duplication in retrieved web pages. 2026-01-25 20:12:34 +01:00
Bartosz Taudul
0a58d88be2 Go back to regex paths in source search. 2026-01-25 16:29:53 +01:00
Bartosz Taudul
27ecaed76e Use regex in source search. 2026-01-25 16:12:45 +01:00
Bartosz Taudul
beb4cd4ea6 Perform query transformation only once. 2026-01-25 16:12:25 +01:00
Bartosz Taudul
d4a56c9a37 Tweak system prompt. 2026-01-25 15:47:08 +01:00
Bartosz Taudul
a4324d595d Add optional file globbing to SourceSearch. 2026-01-25 02:41:00 +01:00
Bartosz Taudul
63c6e48562 Optional string parameter macro. 2026-01-25 02:14:45 +01:00
Bartosz Taudul
1f4b2dd45f Try to show some search results, if no space for full data. 2026-01-25 02:07:25 +01:00
Bartosz Taudul
7a2aaa6e8e Refine system prompt. 2026-01-25 01:14:16 +01:00
Bartosz Taudul
b2dcd55d95 Better handling of error responses. 2026-01-25 00:35:18 +01:00
Bartosz Taudul
9c096a31f0 Fix copy paste error. 2026-01-24 19:57:33 +01:00
Bartosz Taudul
cdec805cc9 Underline links. 2026-01-24 14:23:15 +01:00
Bartosz Taudul
7c54c824ab Add support for markdown strikethrough. 2026-01-24 14:16:16 +01:00
Bartosz Taudul
a7ed5e1fb3 Support rendering markdown task lists. 2026-01-24 13:50:39 +01:00
Bartosz Taudul
73694c7a24 Cleanup enums. 2026-01-24 01:50:11 +01:00
Bartosz Taudul
bc6cf23f08 No line breaks in Lua locations. 2026-01-24 01:25:35 +01:00
Bartosz Taudul
b624ada00a Cosmetics. 2026-01-24 01:16:28 +01:00
Bartosz Taudul
5e078ed0dd Fix Lua integration with test. 2026-01-24 01:10:56 +01:00
Bartosz Taudul
26b1782b89 Remove TracyDebug() logging.
TracyDebug() is in the profiler init path, and logging the message causes
the profiler to be initialized (the second time), which deadlocks in
GetProfilerData().
2026-01-24 00:49:25 +01:00
Bartosz Taudul
cfb5f5595a Rewrite system prompt. 2026-01-23 23:22:59 +01:00
Bartosz Taudul
b8cb7a1883 Merge pull request #1262 from kubkon/capture-queue-backlog
capture: Display current query backlog
2026-01-23 17:00:47 +01:00
Jakub Konka
e5646b1f6a capture: Display current query backlog 2026-01-23 15:26:07 +01:00
Bartosz Taudul
07147111b2 Merge pull request #1258 from liungkejin/patch-1
fix a nullptr crash on android devices
2026-01-18 12:28:53 +01:00
Kejin
d6597b0bdd Check if pw_gecos is non-empty on Android
Ensure pw_gecos is not empty before returning.
2026-01-18 14:01:19 +08:00
Bartosz Taudul
805d4bf6fd Better table sizing flags? 2026-01-17 01:55:26 +01:00
Bartosz Taudul
faade0f871 Merge pull request #1259 from davidkern/macos-scaling
fix: 2x scaling on macos with ImGui 1.92
2026-01-16 02:42:45 +01:00
David Kern
0a438193ba fix: 2x scaling on macos with ImGui 1.92 2026-01-15 17:01:40 -08:00
Bartosz Taudul
ba677b725b Merge pull request #1230 from slomp/slomp/etw-refactor-2
Toggling EnableFlags after the "NT Kernel Logger" session has started
2026-01-15 23:00:51 +01:00
Bartosz Taudul
00ce663136 Allow attaching symbol source code to LLM, with execution costs. 2026-01-15 22:14:10 +01:00
Bartosz Taudul
2bc09a42cb Use contains(). 2026-01-15 21:56:19 +01:00
Bartosz Taudul
a34d7fb01a Add icons to models. 2026-01-15 21:40:47 +01:00
Bartosz Taudul
c4207dec75 Allow attaching source code to LLM in simple view. 2026-01-15 21:38:12 +01:00
Bartosz Taudul
ef77600bae Description is optional. 2026-01-15 20:55:13 +01:00
Marcos Slomp
839b38d2ec removing implicit/default argument value 2026-01-15 11:29:49 -08:00
Marcos Slomp
44696c47c6 formatting 2026-01-15 11:27:49 -08:00
Marcos Slomp
dacb3dbae6 support for toggling EnableFlags after the singleton kernel session has started 2026-01-15 11:20:06 -08:00
Bartosz Taudul
eeacbac8bc Workaround issues with LM Studio.
LM Studio will prefix the assistant's content with '\n\n'. While this is
not a problem when proper text follows (the markdown parser will ignore
this), the empty check does fail.
2026-01-15 17:59:00 +01:00
Kejin
1cad2903a1 fix a nullptr crash on android devices 2026-01-15 21:54:24 +08:00
Bartosz Taudul
fc63779b18 Reduce nested divs in retrieved webpages. 2026-01-15 02:37:47 +01:00
Bartosz Taudul
3817db0dc2 Fix newline removal in retrieved webpages. 2026-01-15 02:33:45 +01:00
Bartosz Taudul
fa8a57af86 Include search excerpt in wikipedia search results.
This directly references the search keywords in articles that may seem
unrelated based on their title or description alone.
2026-01-15 02:25:03 +01:00
Bartosz Taudul
01d1850b0f Return 10 results in wikipedia search. 2026-01-15 02:22:08 +01:00
Bartosz Taudul
c153f598b6 Request prompt caching. 2026-01-14 18:48:28 +01:00
Bartosz Taudul
23e752a03b Rework LLM summary error display in callstack window. 2026-01-14 02:50:09 +01:00
Bartosz Taudul
828d32521d Return proper error response from TracyLlmApi::SendMessage(). 2026-01-14 02:49:38 +01:00
Bartosz Taudul
227c7133c3 Add animated waiting dots in place of ellipsis in call stack window. 2026-01-14 02:25:00 +01:00
Bartosz Taudul
b19d9622b3 Add small version of DrawWaitingDots().
The normal one is fit for lines with full size buttons, entry fields, etc.
2026-01-14 02:24:02 +01:00
Bartosz Taudul
5b568d7bce Add in-line waiting dots drawer. 2026-01-14 02:17:56 +01:00
Bartosz Taudul
a991b4fd50 Cosmetics. 2026-01-14 02:14:25 +01:00
Bartosz Taudul
24ea5118e0 Rename DrawWaitingDots() to DrawWaitingDotsCentered(). 2026-01-14 02:12:00 +01:00
Bartosz Taudul
38b73254e9 Add emoji font. 2026-01-14 01:51:32 +01:00
Bartosz Taudul
3547009d1c Don't lock user manual window size. 2026-01-14 01:23:39 +01:00
Bartosz Taudul
ce74512b92 Update manual. 2026-01-14 00:58:07 +01:00
Bartosz Taudul
bc33767aab Update NEWS. 2026-01-14 00:21:37 +01:00
Bartosz Taudul
040b4d0e33 Merge pull request #1256 from siliceum/test/add-lua-test
Add lua to test/test.cpp
2026-01-13 15:21:35 +01:00
Bartosz Taudul
43c0fe9b61 Merge pull request #1255 from siliceum/fix/lua-callstack-depth
Don't try to send callstacks of depth 0 (would trigger assert in `tracy::Callstack`)
2026-01-13 15:19:11 +01:00
Bartosz Taudul
1fa1a4f5e7 Merge pull request #1254 from siliceum/fix/constexpr-string
Use string_view as constexpr std::string requires recent compilers
2026-01-13 15:18:12 +01:00
Clément Grégoire
9acc186ceb Don't try to send callstacks of depth 0 (would trigger assert in tracy::Callstack) 2026-01-13 11:28:38 +01:00
Clément Grégoire
9b135b53b4 Add lua to test/test.cpp 2026-01-13 11:25:44 +01:00
Clément Grégoire
5b79a9a825 Use string_view as constexpr std::string requires recent compilers
This was not building with visual studio Version 17.14.13 (August 2025)
2026-01-13 10:57:18 +01:00
Bartosz Taudul
76e0ab135b Keep window redrawing while waiting for message reply. 2026-01-11 22:32:25 +01:00
Bartosz Taudul
ed8fc7690f Implement automatic callstack annotation. 2026-01-11 20:54:10 +01:00
Bartosz Taudul
0b9dcc0fbe Add callstack annotations option. 2026-01-11 20:50:37 +01:00
Bartosz Taudul
b53e10b25e Fake a progress indicator. 2026-01-11 20:42:13 +01:00
Bartosz Taudul
3008cb8ad7 Add LLM summaries for callstacks. 2026-01-11 20:07:09 +01:00
Bartosz Taudul
1918667bbd Use proper types for RangeSlim init. 2026-01-11 19:37:38 +01:00
Bartosz Taudul
6f06a25669 Change messages icon to a single-way conversation. 2026-01-11 19:11:20 +01:00
Bartosz Taudul
3ae9db27de Change chat icon to a two-way conversation. 2026-01-11 19:10:58 +01:00
Bartosz Taudul
2ef21b93c5 More compact call stack window controls. 2026-01-11 19:06:01 +01:00
Bartosz Taudul
2bf0a3c7f9 Implement fast message queries. 2026-01-11 18:56:19 +01:00
Bartosz Taudul
9b5cbf835d Implement separate-channel non-streamed chat completion requests. 2026-01-11 18:54:11 +01:00
Bartosz Taudul
761cb1041b Add fast model selection to the UI. 2026-01-11 17:58:22 +01:00
Bartosz Taudul
caba47a66b Don't copy the string to print if no replacements are needed. 2026-01-11 16:52:35 +01:00
Bartosz Taudul
bf61589f3d Separate wrapped text printer from text fixup. 2026-01-11 16:20:06 +01:00
Bartosz Taudul
b26fefd325 Smaller header font sizes. 2026-01-10 15:22:29 +01:00
Bartosz Taudul
a04b0e515a Undo newline when the word position is already at the start of the line. 2026-01-10 15:10:49 +01:00
Bartosz Taudul
ec2ac9f227 Fix table rendering. 2026-01-10 01:14:03 +01:00
Bartosz Taudul
7268cd8c32 Include external jump destination names in LLM attachment. 2026-01-10 00:32:40 +01:00
Bartosz Taudul
c0acafea63 Assume paths containing hidden files are external.
A typical use case would be $(HOME)/.cache/cpm/somelib/file.h.

Special care is needed to avoid filtering out dot-dot path elements: /../
While these have been normalized for some time now on the client-side, old
traces might still contain the dot-dot elements.
2026-01-09 03:05:04 +01:00
Bartosz Taudul
aeadeace0f Check for both versions of Program Files in external paths. 2026-01-09 01:06:32 +01:00
Bartosz Taudul
d4c88dc7c4 Allow filtering out external functions in sampling statistics view. 2026-01-09 01:05:17 +01:00
Bartosz Taudul
560f8f935d Add ability to attach entry call stacks to LLM. 2026-01-08 22:35:03 +01:00
Bartosz Taudul
723bdc71dc Make GetCallstackJson available via View. 2026-01-08 22:34:45 +01:00
Bartosz Taudul
d9200351ef Allow case-insensitive code search. 2026-01-08 21:29:26 +01:00
Bartosz Taudul
d1a4746076 Add ParamOptBool(). 2026-01-08 21:19:08 +01:00
Bartosz Taudul
517366bec9 Use templates for GetParam() implementation. 2026-01-08 21:18:47 +01:00
Bartosz Taudul
c1ffbe8e0d Check if mouse is over the window before acting on mouse click. 2026-01-08 20:50:26 +01:00
Bartosz Taudul
825ab7f411 Implement search in code. 2026-01-08 20:48:19 +01:00
Bartosz Taudul
e4ff8d34be Move IsFrameExternal from View to TracyUtility.hpp. 2026-01-08 20:28:16 +01:00
Bartosz Taudul
a5e5e8a435 Workaround emscripten scroll issues.
Obviously not the correct fix, but whatever.
2026-01-07 23:29:35 +01:00
Bartosz Taudul
1413bb4b4d Merge pull request #1250 from YaLTeR/fix-scrolling
Fix scrolling speed for touchpads and high-res wheels on Wayland
2026-01-07 23:16:19 +01:00
Bartosz Taudul
e37d58c60c Better printing of tool calls. 2026-01-07 17:47:52 +01:00
Bartosz Taudul
2903fcabe4 Change default source_file context to 50 lines. 2026-01-07 17:03:42 +01:00
Bartosz Taudul
26c5999a6e Add callstack inspection workflow. 2026-01-07 17:02:24 +01:00
Bartosz Taudul
9774fdd017 System prompt reword. 2026-01-07 17:02:12 +01:00
Bartosz Taudul
14f0ed1cba Add hard limit on context usage.
Big MoE models + big context size = slow prompt processing. Limit tool
reply size to a reasonable amount.
2026-01-07 16:31:48 +01:00
Bartosz Taudul
f00694fae0 Add optional context parameter to source_file calls. 2026-01-07 16:24:34 +01:00
Bartosz Taudul
3c82b63046 Allow optional parameters with a default value. 2026-01-07 16:24:10 +01:00
Bartosz Taudul
82d47db47d Give LLM profiling instructions. 2026-01-07 16:13:00 +01:00
Ivan Molodetskikh
203f6cc508 timeline: Use fractional vertical scroll amount in calculation
Before this commit, vertical scroll was always discrete. At least on
Wayland, this caused extremely fast scrolling on touchpads (that send
lots of small axis events) and on mice with high-resolution wheels (that
also send lots of small axis events). After this commit, all of this
scrolling works correctly, at a speed matching regular wheels.
2026-01-05 19:46:37 +03:00
Ivan Molodetskikh
218265ad37 wayland: Adjust scroll scaling
Regular mice send a value of 15 for one wheel tick, not 8.

This currently doesn't change anything about vertical scrolling since
it's handled discretely, but that will change in the next commit.
2026-01-05 19:44:42 +03:00
Bartosz Taudul
189a4fc203 Glue together adjacent user messages.
In most cases this is not needed. However, some models, like Gemma3 or
Devstral require that user and assistant messages alternate.

The only case where this can happen in Tracy is when an attachment is added:

[
  {
    "role": "user",
    "content": "<attachment>\n..."
  },
  {
    "role": "user",
    "content": "Tell me something about..."
  }
]

It is trivial to glue these messages together. This is only done when sending
the data in the REST request, as the chat rendering logic expects these to be
separate and it would be too much work unnecessary work to do it "proper".
2026-01-05 13:45:50 +01:00
Bartosz Taudul
711771bc27 Replace narrow no-break space with no-break space in markdown text.
Nemotron 3 Nano outputs these spaces in the text. The currently used font
(or is it ImGui?) is not able to render this, and draws replacement character
instead.
2026-01-05 13:35:51 +01:00
Bartosz Taudul
1851743c9d Include offset data in assembly attachments. 2026-01-04 23:16:17 +01:00
Bartosz Taudul
405778acf3 Add button for attaching complete assembly to LLM. 2026-01-04 23:10:44 +01:00
Bartosz Taudul
4d7670bac5 Extract LLM assembly range attaching to a separate function. 2026-01-04 23:04:16 +01:00
Bartosz Taudul
51cee7f07d Inject sampled execution cost into assembly attachments. 2026-01-04 22:53:59 +01:00
Bartosz Taudul
14bdad425f No need for nesting source information. 2026-01-04 22:18:15 +01:00
Bartosz Taudul
29f304554d Explicitly say the frame and subframe numbers for each frame. 2026-01-04 21:56:19 +01:00
Bartosz Taudul
b79f3232b6 Properly update system prompt on first message. 2026-01-04 15:29:05 +01:00
Bartosz Taudul
3f84749b05 Implement system prompt updates without clearing chat. 2026-01-04 15:28:49 +01:00
Bartosz Taudul
d146714185 Merge pull request #1249 from siliceum/fix/lua-message
Fix `TracyLua.hpp` compilation
2026-01-04 01:28:11 +01:00
Clément Grégoire
e5d251a0be Fix TracyLua.hpp build 2026-01-03 22:19:49 +01:00
Bartosz Taudul
9d5b380ea9 Implement markdown tables. 2026-01-03 21:22:38 +01:00
Bartosz Taudul
6f5fb3044a Add separation between multiple assistant responses. 2026-01-02 12:40:22 +01:00
Bartosz Taudul
b6f03ef75d Change assistant render order to reasoning -> content -> tool calls.
Previously reasoning and tool calls were rendered first, followed by
content. The tool call response was then rendered in the second reasoning
section. This made the tool call and response disjoint and, with the
current reasoning hiding logic, not visible at the same time.
2026-01-02 12:25:25 +01:00
Bartosz Taudul
b7a51d265c Update system prompt. 2026-01-02 03:05:01 +01:00
Bartosz Taudul
6323f1642d Refresh system prompt immediately before sending first message. 2026-01-01 17:36:46 +01:00
Bartosz Taudul
58dbde7b2f Move system prompt update to a separate function. 2026-01-01 17:33:23 +01:00
Bartosz Taudul
c2431d5c17 Bump copyright year. 2026-01-01 02:12:05 +01:00
Bartosz Taudul
63a8b4cfb4 Use proper UTF ellipsis. 2025-12-31 18:28:57 +01:00
Bartosz Taudul
34faaeb314 Display short snippet of thinking process. 2025-12-31 18:22:13 +01:00
Bartosz Taudul
523d2fb8bf Hide LLM thinking regions by default. 2025-12-31 17:47:12 +01:00
Bartosz Taudul
c01d5d5dd2 Don't create assistant entries with empty content. 2025-12-31 17:38:25 +01:00
Bartosz Taudul
09aa695048 Tool response labels now have unique ids in them. 2025-12-31 17:10:04 +01:00
Bartosz Taudul
3d9b228177 Allow disabling rendering of thinking regions. 2025-12-31 17:08:35 +01:00
Bartosz Taudul
96961f696b Unify internet icon with the welcome window. 2025-12-31 16:51:05 +01:00
Bartosz Taudul
52179c4428 Hide LLM temperature setting in advanced options. 2025-12-31 15:58:58 +01:00
Bartosz Taudul
8520c79749 Set proper width of LLM input text fields. 2025-12-31 15:57:30 +01:00
Bartosz Taudul
50e6497390 Enable horizontal scroll bars in markdown code segments. 2025-12-31 15:35:59 +01:00
Bartosz Taudul
b17002a9c0 Update markdown manual. 2025-12-31 15:28:05 +01:00
Bartosz Taudul
d9e977ed42 Fix spacing when think scope is not the first item in reply. 2025-12-31 15:26:37 +01:00
Bartosz Taudul
217906b63b Drop Ollama. 2025-12-31 15:20:28 +01:00
Bartosz Taudul
b4ff3b1c83 Do not pass AltGr to ImGui.
AltGr (right alt) is used to enter characters, at least with the Polish
keymap. ImGui uses alt to switch between some of the controls. Keep the
interaction on the (left) Alt key, and leave AltGr to do composition.
2025-12-31 14:00:01 +01:00
Bartosz Taudul
ad09ae8790 Use variables to inject data into system prompt. 2025-12-31 13:54:44 +01:00
Bartosz Taudul
5bc2e4b95c Implement full user name retrieval on win32. 2025-12-30 15:17:08 +01:00
Bartosz Taudul
9016911a04 Fix clash with winapi define crap. 2025-12-30 14:49:23 +01:00
Bartosz Taudul
4661233904 Add missing include for UNLEN definition. 2025-12-30 14:48:17 +01:00
Bartosz Taudul
f6ba6cbd4e Fix typo. 2025-12-30 14:48:11 +01:00
Bartosz Taudul
20b9d002dc Check for empty full name. 2025-12-30 14:23:37 +01:00
Bartosz Taudul
94c1614416 Store full user name in trace info. 2025-12-30 14:19:40 +01:00
Bartosz Taudul
e1b426efe6 Pass full user name to LLM. 2025-12-30 14:17:10 +01:00
Bartosz Taudul
299b641173 Implement getting full user name. 2025-12-30 14:16:53 +01:00
Bartosz Taudul
6933780aed Provide current user name to the LLM. 2025-12-30 13:52:33 +01:00
Bartosz Taudul
da85325686 Move user name retrieval to a common API. 2025-12-30 13:41:19 +01:00
Bartosz Taudul
6a26472278 Reformulate current time statement. 2025-12-30 13:26:23 +01:00
Bartosz Taudul
47806d00c8 Don't mention tool use. 2025-12-30 13:22:31 +01:00
Bartosz Taudul
fc35fa6a1d Give instructions to use multiple tools. 2025-12-30 01:50:31 +01:00
Bartosz Taudul
e889fa17f3 Bump usearch to 2.22.0. 2025-12-29 20:46:48 +01:00
Bartosz Taudul
c996c67390 Take the thinking and tool use context into account in LLM chat rendering. 2025-12-29 20:43:28 +01:00
Bartosz Taudul
ac5dabbb94 Drop user debug role. 2025-12-29 20:43:28 +01:00
Bartosz Taudul
cd9585d713 Less verbose system prompt. 2025-12-29 20:43:28 +01:00
Bartosz Taudul
237e90fe64 Adapt context management to tool role usage. 2025-12-29 20:43:27 +01:00
Bartosz Taudul
7a43036e6e Process tool calls table and respond with tool role. 2025-12-29 20:43:27 +01:00
Bartosz Taudul
26cf9591e7 Remove tool and thinking instructions from the system prompt. 2025-12-29 20:43:27 +01:00
Bartosz Taudul
167779392c Include reasoning in context removal calculation.
The calculations here are so-so at best. The only reliable source of context
usage are the server responses.
2025-12-29 20:43:27 +01:00
Bartosz Taudul
fe567e5b42 Add support for streaming reasoning and tool call responses. 2025-12-29 20:43:27 +01:00
Bartosz Taudul
edffd4af81 Check for content being available when deleting user message. 2025-12-29 20:43:27 +01:00
Bartosz Taudul
f4f3c63755 Move time information to system prompt.
This also changes the context of the information a bit. Previously it was
the current time, now it is the time when the conversation started.
2025-12-29 20:43:27 +01:00
Bartosz Taudul
0a983cb24c Check for "content" in error and attachment messages. 2025-12-29 20:43:27 +01:00
Bartosz Taudul
dedc50e848 Support adding messages completely defined by json. 2025-12-29 20:43:26 +01:00
Bartosz Taudul
c9443db053 Provide LLM tools using a standard API. 2025-12-29 20:43:26 +01:00
Bartosz Taudul
e7288604ea Including ranges is not necessary. 2025-12-29 20:43:26 +01:00
Bartosz Taudul
79ee17d2f6 Inject current time into each user message.
This is not removed later on, mainly to enable caching of previous replies
(changing any earlier message would require processing everything that's
later), but also to give the LLM a sense of passing time.
2025-12-29 20:43:26 +01:00
Bartosz Taudul
9183aef645 Do not display system prompt injected into user messages. 2025-12-29 20:43:26 +01:00
Bartosz Taudul
0246cec0a7 System reminder is no longer in use. 2025-12-29 20:43:26 +01:00
Bartosz Taudul
d385328a16 Remove prefill of assistant messages.
Does not work with models implementing reasoning in a standarized way.
2025-12-29 20:43:26 +01:00
Bartosz Taudul
1708a88445 Remove AssistantDebug LLM role. 2025-12-29 20:43:25 +01:00
Bartosz Taudul
e2a86c1756 Merge pull request #1246 from slomp/slomp/logstring-fix
fix for TRACY_FIBERS
2025-12-29 20:37:59 +01:00
Marcos Slomp
12fa5aeb5e fix for TRACY_FIBERS 2025-12-29 11:23:08 -08:00
Bartosz Taudul
6f9504c50a Redo message filtering UI. 2025-12-28 16:56:09 +01:00
Bartosz Taudul
2a9ab49d13 Use constexpr for tables. 2025-12-28 16:26:19 +01:00
Bartosz Taudul
f649943e93 Fix text formatting. 2025-12-28 16:03:44 +01:00
Bartosz Taudul
f43b86e3b3 Merge pull request #1180 from siliceum/feature/logging
Support source/severity for messages
2025-12-28 15:17:03 +01:00
Clément Grégoire
5ef64841cc Remove MessageMetadata type and replace by uint8_t everywhere 2025-12-28 15:04:18 +01:00
Clément Grégoire
2df7c53c26 Keep tracy::Profiler::Message in the stack frames that need to be ignored
This may still appear in saved traces.
2025-12-28 15:04:18 +01:00
Clément Grégoire
4cceab5ad8 Remove Profiler::Messsage since it's not part of the public API
This also replaces `___tracy_emit_message*` by `___tracy_emit_logString`.
The `TracyCMessage*` defines no long include the `;`, which may be a breaking change even though we did already require semi-colon since 0.9.0. See #493 and #592
2025-12-28 15:04:05 +01:00
Clément Grégoire
88c926d723 TracyMessage* Macros now use tracy::Profiler::LogString 2025-12-28 15:01:00 +01:00
Clément Grégoire
1a4cdef4b6 Add TracyLog to the test example 2025-12-28 15:01:00 +01:00
Clément Grégoire
f17bd3f444 TracyDebug now uses TracyInternalMessage by default unless TRACY_VERBOSE or TRACY_NO_INTERNAL_MESSAGE is defined 2025-12-28 15:00:59 +01:00
Clément Grégoire
13261b9bdc ETWErrorAction now uses message source and severity 2025-12-28 14:50:41 +01:00
Clément Grégoire
b2a7bb64ff Add tooltip for message source filters 2025-12-28 14:50:41 +01:00
Clément Grégoire
3e99b0e7c2 Add severity name tooltip 2025-12-28 14:50:41 +01:00
Clément Grégoire
a0016be7ff Allow filtering Messages by source and severity 2025-12-28 14:50:41 +01:00
Clément Grégoire
35a05aea62 Optimize message filtering a bit
Instead of decompressing and checking visibility of the thread all the time, cache the previous result.
2025-12-28 14:50:41 +01:00
Clément Grégoire
615db1a78c Refactor message filtering
This now uses a single path for all cases (filter/threads changed, new message).
Note this no longer discriminates against `m_messageFilter.IsActive()` to skip the message filter. `ImGui::PassFilter already` early outs, and if performance is a concern we might as well start by caching the result of `VisibleMsgThread` which always does a hashmap lookup.
2025-12-28 14:50:41 +01:00
Clément Grégoire
1e61dc88de Add source and severity to the server's MessageData + bump minor version for serialization
Note this does not change `sizeof(MessageData)` as there were 5 bytes left due to alignment. (now 3)
2025-12-28 14:50:38 +01:00
Clément Grégoire
f650b69591 tracy_force_inline for message metadata conversion 2025-12-28 14:44:40 +01:00
Clément Grégoire
f981330f66 Replace all messages text addr by TaggedUserlandAddress and send metadata over the network
There are two changes to the protocol:

- `QueueMessageLiteral*` were changed and what used to be addresses are now addresses+metadata
- Other messages now send `QueueMessage*Metadata` with added metadata.

This will later be used to store and transmit message sources, level, etc.
2025-12-28 14:44:40 +01:00
Clément Grégoire
9c57b229ea Introduce tracy::TaggedUserlandAddress to pack metadata with pointers 2025-12-28 14:42:40 +01:00
Bartosz Taudul
a602127edd Merge pull request #1242 from wvbbreu/cupti-driverapi-memtrace
Add CUDA Driver API memory operations to Tracy CUPTI profiling
2025-12-25 13:16:37 +01:00
Wiebe van Breukelen
66b23bb19a Add CUDA Driver API memory operations to Tracy CUPTI profiling
Extended Tracy's CUPTI callback registration to track CUDA Driver API
memory operations that were previously missing.
Registration of these events allow users to trace applications that directly
call the Driver API.
2025-12-24 12:05:51 +01:00
Bartosz Taudul
1ad248892d Fix time range limits button id overlap. 2025-12-23 14:03:54 +01:00
Bartosz Taudul
b63f605ec1 Merge pull request #1239 from slomp/slomp/macos-arm64-tsc
Apple Silicon / macOS: use ARM64 CNTVCT_EL0 instruction in GetTime()
2025-12-23 12:31:15 +01:00
Marcos Slomp
2d3fa2141e removing parentheses from define 2025-12-22 12:22:26 -08:00
Marcos Slomp
f55bd056a9 switch to using ARM64's CNTVCT_EL0 instruction instead of std::chrono::high_resolution_clock on macOS 2025-12-22 11:43:51 -08:00
Bartosz Taudul
6cd7751479 Merge pull request #1236 from siliceum/fix/frame-image-race-condition
Fix frame image race condition + refactor
2025-12-18 16:36:41 +01:00
Bartosz Taudul
9ced780715 Merge pull request #1235 from alexandergunnarson/master
A few minor Windows-specific fixes
2025-12-18 16:34:18 +01:00
Clément Grégoire
0eb9f4acb6 Fix frame image race condition + refactor
- In the connection state, retrieve the FrameImage while owning the data lock.
- Use actual image data pointer as caching key instead of the address of ImageCache which may change during executation (unstable).
- Fixes scale Messages image tooltip scale.
- Free the connection image
2025-12-18 15:44:58 +01:00
Alex Gunnarson
921d426488 Add conditional compilation option for MSVC 2025-12-17 15:26:58 -07:00
Alex Gunnarson
90bc94f237 Use builtins before MSVC intrinsics 2025-12-17 15:24:04 -07:00
Alex Gunnarson
dffa18378b Update IMAGEHLP_LINE to IMAGEHLP_LINE64
OfflineSymbolResolverDbgHelper.cpp uses IMAGEHLP_LINE but
SymGetLineFromAddr64 expects IMAGEHLP_LINE64. On 64-bit Windows these are typedef'd to the same thing, but on 32-bit they're different.
2025-12-17 15:19:00 -07:00
Bartosz Taudul
8b2019830a Merge pull request #1234 from siliceum/fix/etw-vsync
VSyncDPC events do not have the Keyword::DxgKrnlPresent bit set, so using it as MatchAllKeyword excluded it
2025-12-17 18:07:42 +01:00
Bartosz Taudul
2c19b82d72 Merge pull request #1233 from slomp/slomp/etw-happiness
Ensure all ETW StackWalk requests are satisfied
2025-12-17 18:07:26 +01:00
Clément Grégoire
d59e6cdfad VSyncDPC events do not have the Keyword::DxgKrnlPresent bit set, so using it as MatchAllKeyword excluded it 2025-12-17 17:45:52 +01:00
Marcos Slomp
4a6c75d8f8 ensure all stackwalk requests are satisfied 2025-12-17 08:03:34 -08:00
Bartosz Taudul
3ac8242d72 Merge pull request #1227 from WangFengtu1996/master
fixbug build cuda demo error
2025-12-17 16:43:27 +01:00
Fengtu Wang
4745db1553 Fix include statement for Tracy.hpp 2025-12-17 09:47:19 +08:00
Bartosz Taudul
3258599899 Merge pull request #1229 from slomp/slomp/etw-error-check-patch
ETW initialization: more explicit error check
2025-12-17 00:26:00 +01:00
Marcos Slomp
db745611c4 more explicit error check 2025-12-16 13:38:42 -08:00
Bartosz Taudul
1977beafe6 Merge pull request #1228 from siliceum/fix/test-memory-free
Fix unknown allocation message in debug due to alloc name litteral not pointing to the same location
2025-12-16 20:19:19 +01:00
Clément Grégoire
16558f2915 Fix unknown allocation message in debug due to alloc name litteral not pointing to the same location 2025-12-16 19:49:08 +01:00
Bartosz Taudul
7976e6ab0b Merge pull request #1213 from slomp/slomp/etw-refactor
Refactoring ETW kernel session
2025-12-16 18:59:56 +01:00
Marcos Slomp
2403438bd9 error flow control for VSync tracing 2025-12-16 09:11:11 -08:00
Marcos Slomp
bb3b5b6a4f reuse StopSession 2025-12-16 09:10:19 -08:00
Fengtu Wang
d2ffc9b9c6 fixbug build cuda demo error
1. fix bug build error can not find head file `#include <tracy/Tracy.hpp>`
2.  when not enable TRACY_ENABLE macro, build error  can not find  `tracy::CUDACtx` type
2025-12-16 18:32:10 +08:00
Marcos Slomp
a0092a793a abandoning the private kernel session for now 2025-12-15 16:21:42 -08:00
Marcos Slomp
c0ac36918a adding interfaces for singleton kernel logger session, and regular user sessions 2025-12-15 16:20:57 -08:00
Marcos Slomp
22d2b7407f emit ETW errors in english 2025-12-15 16:19:45 -08:00
Bartosz Taudul
0e3b3c57fc Merge pull request #1223 from siliceum/fix/fix-example-alloc-tracking
Test: Fix unknown allocation message in debug due to arena name litteral not pointing to the same location
2025-12-12 16:55:31 +01:00
Clément Grégoire
9c66d27777 Test: Fix unknown allocation message in debug due to arena name litteral not pointing to the same location 2025-12-12 16:40:14 +01:00
Bartosz Taudul
a7c6b7f02a Merge pull request #1221 from martymichal/wip/martymichal/fix-atomic-thread-init
public/client: Correctly initialize atomic thread reference
2025-12-12 12:21:34 +01:00
Bartosz Taudul
f1efafe825 Do not show external frames in DrawCallstackCalls(). 2025-12-12 01:23:36 +01:00
Bartosz Taudul
4fe1e00696 IsFrameExternal() can be const. 2025-12-12 01:23:19 +01:00
Bartosz Taudul
49ab593247 Change DrawCallstackCalls() limit logic. 2025-12-12 01:16:14 +01:00
Marcos Slomp
cc7790af04 add check to determine if provider has already been enabled by other sessions 2025-12-10 13:50:28 -08:00
Marcos Slomp
aaa695611b code formatting 2025-12-10 13:42:43 -08:00
Marcos Slomp
1be738ba10 expanding Tracy macros 2025-12-08 11:30:03 -08:00
Marcos Slomp
86a6b9b671 add admin check (and don't log it if it fails). 2025-12-08 08:40:26 -08:00
Ondřej Míchal
d79b6d040e public/client: Correctly initialize atomic thread reference
Atomic variables need to be initialize with direct-initialization
instead of copy-initialization lest the compilation fails complaining
about missing constructor for the atomic type.
2025-12-08 15:06:26 +02:00
Marcos Slomp
04c32562a0 adressing review comments 2025-12-07 15:51:07 -08:00
Marcos Slomp
d363ddde2a missing events 2025-12-05 12:46:27 -08:00
Marcos Slomp
7b5de9e2b9 relocating ETW events (structs), and adding checks and comments 2025-12-05 12:03:57 -08:00
Marcos Slomp
16724b39c5 handle ProcessTrace pending (draining/flushing) condition 2025-12-05 09:49:11 -08:00
Marcos Slomp
9bb2be27cf details 2025-12-03 14:55:08 -08:00
Marcos Slomp
f0b8658346 splitting event consumer 2025-12-03 14:55:08 -08:00
Marcos Slomp
f6f9ec49d8 comments about the choice of default arguments 2025-12-03 14:55:08 -08:00
Marcos Slomp
bca011966d code formatting 2025-12-03 14:55:08 -08:00
Marcos Slomp
e5ca27966e ASCII 2025-12-03 14:55:08 -08:00
Marcos Slomp
d1f63dbca8 notify that events have been lost 2025-12-03 14:55:08 -08:00
Marcos Slomp
95658d532e isolating error actions 2025-12-03 14:55:08 -08:00
Marcos Slomp
f6965c62a9 reminder 2025-12-03 14:55:07 -08:00
Marcos Slomp
0270177a85 typo 2025-12-03 14:55:07 -08:00
Marcos Slomp
16e2f9f3bb Using ASCII version of the ETW API 2025-12-03 14:55:07 -08:00
Marcos Slomp
0adb0dbc57 refactoring ETW kernel session 2025-12-03 14:55:07 -08:00
230 changed files with 245473 additions and 183651 deletions

View File

@@ -20,6 +20,7 @@ Checks:
-google-readability-namespace-comments,
-misc-confusable-identifiers,
-misc-no-recursion,
-misc-use-anonymous-namespace,
-misc-use-internal-linkage,
-modernize-avoid-c-arrays,
-modernize-deprecated-headers,

35
.github/actions/test-tracy/action.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: 'Test Tracy'
description: 'Build the Tracy test application with various cmake flag combinations'
inputs:
extra_cmake_flags:
description: 'Additional cmake flags appended to each configure command (e.g. cross-compilation flags)'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Test application
shell: bash
run: |
# test compilation with different flags
# we clean the build folder to reset cached variables between runs
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_ON_DEMAND
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON ${{ inputs.extra_cmake_flags }}
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 }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_DEMANGLE
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build

View File

@@ -5,24 +5,31 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build:
build-emscripten:
runs-on: ubuntu-latest
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git unzip python ninja zstd
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git unzip python ninja zstd nodejs
- name: Setup emscripten
uses: mymindstorm/setup-emsdk@v14
uses: emscripten-core/setup-emsdk@v16
with:
version: 4.0.10
- 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: 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
@@ -48,7 +55,7 @@ jobs:
path: bin
deploy:
runs-on: ubuntu-latest
needs: build
needs: build-emscripten
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/download-artifact@v4

View File

@@ -5,9 +5,11 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
jobs:
build:
build-manual:
runs-on: ubuntu-latest

View File

@@ -5,64 +5,68 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build:
build-linux:
runs-on: ubuntu-latest
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
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua
- 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: Profiler GUI
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
cmake --build profiler/build
else
cmake --build profiler/build --parallel
fi
- name: Update utility
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel
- name: Capture utility
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel
- name: Csvexport utility
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel
- name: Import utilities
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel
- name: Library
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib build && meson compile -C build && meson install -C build
- name: Test application
- name: Merge utility
run: |
# test compilation with different flags
# we clean the build folder to reset cached variables between runs
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release
cmake --build test/build --parallel
rm -rf test/build
# same with TRACY_ON_DEMAND
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON .
cmake --build test/build --parallel
rm -rf test/build
# same with TRACY_DELAYED_INIT TRACY_MANUAL_LIFETIME
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON .
cmake --build test/build --parallel
rm -rf test/build
# same with TRACY_DEMANGLE
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON .
cmake --build test/build --parallel
rm -rf test/build
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel
- name: Library (cmake)
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
cmake --build build
cmake --install build
env:
CMAKE_INSTALL_PREFIX: ${{ github.workspace }}/bin
- name: Library (meson)
run: |
meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build-meson
meson compile -C build-meson
- name: Test application
uses: ./.github/actions/test-tracy
- name: Find Artifacts
id: find_artifacts
run: |
@@ -73,6 +77,7 @@ jobs:
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-*
- uses: actions/upload-artifact@v4
with:

69
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: macos
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-macos:
runs-on: macos-15
steps:
- 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: Install dependencies
run: brew install pkg-config glfw meson
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel --config Release
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel --config Release
- name: Build capture
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel --config Release
- name: Build csvexport
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel --config Release
- name: Build import
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel --config Release
- name: Build merge
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 update/build/tracy-update bin
cp capture/build/tracy-capture 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
- uses: actions/upload-artifact@v4
with:
name: macos
path: bin

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

@@ -0,0 +1,61 @@
name: build-mingw
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-mingw:
runs-on: ubuntu-latest
container: archlinux:base-devel
steps:
- name: Install dependencies
run: |
pacman -Syu --noconfirm
pacman -S --noconfirm --needed mingw-w64-gcc cmake git nodejs meson
- 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 TracyClient
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
cmake --build build
- name: Library (meson)
run: |
cat > mingw-cross.txt << 'EOF'
[binaries]
c = '/usr/bin/x86_64-w64-mingw32-gcc'
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
EOF
meson setup build-meson --cross-file mingw-cross.txt -Ddefault_library=static -Dtracy_enable=true
meson compile -C build-meson
- name: Test application
uses: ./.github/actions/test-tracy
with:
extra_cmake_flags: >-
-DCMAKE_SYSTEM_NAME=Windows
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++

29
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: release
on:
release:
types: [published]
jobs:
release-build-windows:
uses: ./.github/workflows/windows.yml
release-build-manual:
uses: ./.github/workflows/latex.yml
attach-to-release:
needs: [release-build-windows, release-build-manual]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- name: Create versioned zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd windows
zip -r ../windows-$VERSION_NO_V.zip .
- uses: softprops/action-gh-release@v2
with:
files: |
windows-*.zip
manual/tracy.pdf

View File

@@ -1,74 +1,61 @@
name: build
name: windows
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build:
strategy:
matrix:
os: [ windows-latest, macos-15 ]
runs-on: ${{ matrix.os }}
continue-on-error: true
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- if: startsWith(matrix.os, 'windows')
uses: microsoft/setup-msbuild@v2
- if: startsWith(matrix.os, 'windows')
uses: actions/setup-python@v2
- 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-
- uses: microsoft/setup-msbuild@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- if: startsWith(matrix.os, 'windows')
run: pip install meson ninja
- if: startsWith(matrix.os, 'macos')
name: Install macos dependencies
run: brew install pkg-config glfw meson
- run: pip install meson ninja
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Profiler GUI
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel --config Release
- name: Update utility
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel --config Release
- name: Capture utility
- name: Build capture
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel --config Release
- name: Csvexport utility
- name: Build csvexport
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel --config Release
- name: Import utilities
- name: Build import
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel --config Release
- if: ${{ !startsWith(matrix.os, 'windows') }}
name: Library
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib build && meson compile -C build && meson install -C build
- if: ${{ !startsWith(matrix.os, 'windows') }}
name: Find Artifacts
id: find_artifacts
- name: Build merge
run: |
mkdir -p bin
cp profiler/build/tracy-profiler bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin
- if: startsWith(matrix.os, 'windows')
name: Find Artifacts
id: find_artifacts_windows
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
@@ -77,7 +64,8 @@ jobs:
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
copy merge\build\Release\tracy-merge.exe bin
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
name: windows
path: bin

View File

@@ -1,10 +1,10 @@
{
"cmake.configureOnOpen": true,
"cmake.sourceDirectory": [
"${workspaceFolder}/profiler",
"${workspaceFolder}/capture",
"${workspaceFolder}/csvexport",
"${workspaceFolder}/import",
"${workspaceFolder}/merge",
"${workspaceFolder}/update",
"${workspaceFolder}/test",
"${workspaceFolder}",

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.13)
# Run version helper script
include(cmake/version.cmake)
@@ -78,7 +78,8 @@ endif()
# Public dependency on some libraries required when using Mingw
if(WIN32 AND ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU|Clang")
target_link_libraries(TracyClient PUBLIC ws2_32 dbghelp)
target_link_libraries(TracyClient PUBLIC ws2_32 dbghelp secur32)
target_compile_definitions(TracyClient PUBLIC WINVER=0x0A00 _WIN32_WINNT=0x0A00)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
@@ -105,51 +106,48 @@ if(TRACY_Fortran)
add_library(Tracy::TracyClient_Fortran ALIAS TracyClientF90)
endif()
macro(set_option option help value)
option(${option} ${help} ${value})
if(${option})
message(STATUS "${option}: ON")
target_compile_definitions(TracyClient PUBLIC ${option})
else()
message(STATUS "${option}: OFF")
endif()
endmacro()
include(cmake/options.cmake)
set_option(TRACY_ENABLE "Enable profiling" ON)
set_option(TRACY_ON_DEMAND "On-demand profiling" OFF)
set_option(TRACY_CALLSTACK "Enforce callstack collection for tracy regions" OFF)
set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF)
set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF)
set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF)
set_option(TRACY_NO_BROADCAST "Disable client discovery by broadcast to local network" OFF)
set_option(TRACY_ONLY_IPV4 "Tracy will only accept connections on IPv4 addresses (disable IPv6)" OFF)
set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF)
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF)
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF)
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF)
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF)
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF)
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF)
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF)
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF)
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF)
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF)
set_option(TRACY_FIBERS "Enable fibers support" OFF)
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF)
set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF)
set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF)
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF)
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF)
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF)
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF)
set_option(TRACY_ENABLE "Enable profiling" OFF TracyClient)
set_option(TRACY_ON_DEMAND "On-demand profiling" OFF TracyClient)
set_option_value(TRACY_CALLSTACK "Override the callstack collection depth for tracy zones" "" TracyClient)
set_option_value_as_string(TRACY_PLATFORM_HEADER "Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform" "" TracyClient)
set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF TracyClient)
set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF TracyClient)
set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF TracyClient)
set_option(TRACY_NO_BROADCAST "Disable client discovery by broadcast to local network" OFF TracyClient)
set_option(TRACY_ONLY_IPV4 "Tracy will only accept connections on IPv4 addresses (disable IPv6)" OFF TracyClient)
set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF TracyClient)
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_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)
set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF TracyClient)
set_option(TRACY_DISALLOW_HW_TIMER "Disallow hardware timer (may be useful on VMs). Requires TRACY_TIMER_FALLBACK=ON" OFF TracyClient)
set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF TracyClient)
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_OPENGL_AUTO_CALIBRATION "Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)" OFF TracyClient)
# advanced
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF)
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF TracyClient)
mark_as_advanced(TRACY_VERBOSE)
set_option(TRACY_DEMANGLE "[advanced] Don't use default demangling function - You'll need to provide your own" OFF)
set_option(TRACY_NO_INTERNAL_MESSAGE "[advanced] Prevent the profiler from logging messages" OFF TracyClient)
mark_as_advanced(TRACY_NO_INTERNAL_MESSAGE)
set_option(TRACY_DEMANGLE "[advanced] Don't use default demangling function - You'll need to provide your own" OFF TracyClient)
mark_as_advanced(TRACY_DEMANGLE)
if(rocprofiler-sdk_FOUND)
set_option(TRACY_ROCPROF_CALIBRATION "[advanced] Use continuous calibration of the Rocprof GPU time." OFF)
set_option(TRACY_ROCPROF_CALIBRATION "[advanced] Use continuous calibration of the Rocprof GPU time." OFF TracyClient)
mark_as_advanced(TRACY_ROCPROF_CALIBRATION)
endif()
@@ -157,6 +155,9 @@ endif()
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()
if(NOT TRACY_STATIC)
target_compile_definitions(TracyClient PRIVATE TRACY_EXPORTS)
@@ -196,6 +197,7 @@ set(client_includes
${TRACY_PUBLIC_DIR}/client/TracyDxt1.hpp
${TRACY_PUBLIC_DIR}/client/TracyFastVector.hpp
${TRACY_PUBLIC_DIR}/client/TracyLock.hpp
${TRACY_PUBLIC_DIR}/client/TracyMangle.hpp
${TRACY_PUBLIC_DIR}/client/TracyProfiler.hpp
${TRACY_PUBLIC_DIR}/client/TracyRingBuffer.hpp
${TRACY_PUBLIC_DIR}/client/TracyScoped.hpp
@@ -219,6 +221,7 @@ set(common_includes
${TRACY_PUBLIC_DIR}/common/TracySocket.hpp
${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp
${TRACY_PUBLIC_DIR}/common/TracySystem.hpp
${TRACY_PUBLIC_DIR}/common/TracyTaggedUserlandAddress.hpp
${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp
${TRACY_PUBLIC_DIR}/common/TracyYield.hpp)
@@ -281,3 +284,7 @@ if(TRACY_CLIENT_PYTHON)
add_subdirectory(python)
endif()
if(PROJECT_IS_TOP_LEVEL)
set(CMAKE_COLOR_DIAGNOSTICS ON)
endif()

View File

@@ -1,7 +1,7 @@
Tracy Profiler (https://github.com/wolfpld/tracy) is licensed under the
3-clause BSD license.
Copyright (c) 2017-2025, Bartosz Taudul <wolf@nereid.pl>
Copyright (c) 2017-2026, Bartosz Taudul <wolf@nereid.pl>
All rights reserved.
Redistribution and use in source and binary forms, with or without

172
NEWS
View File

@@ -2,6 +2,178 @@ Note: There is no guarantee that version mismatched client and server will
be able to talk with each other. Network protocol breakages won't be listed
here.
vx.xx.x (2026-xx-xx)
--------------------
- 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.
- External frames are now omitted in the single-line call stack list visible
in messages list, or in memory allocation info window.
- External frames are now hidden by default in various contexts where they
were previously enabled:
- Flame graph window.
- Call stack window.
- Statistics window (sampling mode).
- External frames are now dimmed out in call stacks in various parts of UI.
- Single-line call stacks now have ellipsis at the end, if there are frames
remaining.
- 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.
- Added support for setting message's source and severity, through the
TracyLogString macro.
- The "zone trace" list in zone information window has been removed. It was
never convenient to use properly. It was replaced by "parent zones" list,
which is basically a less convoluted equivalent.
- 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).
- 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
extent.
- Since this is a heuristic, the result can be wrong.
- The reconstructed call stack can be displayed in the zone tooltip and in
the inline call stack view in zone information window.
- Reconstructed call stacks are indicated with the "magic wand" icon.
- Various LLM integration improvements.
- The protocol has been updated to use model templates. As a result, tools
are now specified in a common way and the reasoning is performed in a
separate content stream.
- Several new tools were added, which in concert enable the assistant to
answer very general questions, such as "how to optimize this program?".
- Smaller models are now viable to use. Models as small as 4B parameters do
now work really well. You can run such models on virtually all hardware.
- Added horizontal scroll bars to code segments.
- LLM thinking regions are now hidden by default.
- The assistant may notify the user about its current findings, then
resume thinking, after which it may give a more complete answer. In
such cases, the initial part of the reply will be faded out.
- Sampled execution costs are now included in assembly attachments.
- Source code retrieval now has an optional line context parameter.
- Added ability to search the code for keywords.
- Calls in assembly attachments are now annotated with function names.
- Wikipedia search will now return 10 results, not only the top one.
- Brave search engine is now available as an alternative web search option.
- Added emoji font.
- Maximum tool reply size has been tweaked to better work with larger
contexts.
- Tool reply size limit is now configurable in LLM settings.
- Tool reply eviction logic for context management has been adjusted to
better work with larger contexts. Additional logic was added to prefer
eviction of old responses.
- Certain LLM actions want to run in a fast mode, with reasoning disabled.
In most scenarios the default chat model will have to do here. If you
have the memory to spare, you can optionally load two models at once,
setting the "fast" model to a smaller and much quicker one.
- 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.
- 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.
- Source code can be attached (also with execution costs in symbol view).
- Zone histogram data can be attached for analysis.
- Markdown renderer improvements.
- Tables are now properly rendered.
- Tasklist rendering has been implemented.
- Strikethrough is now supported.
- Clickable links are now underlined.
- Tweaked high-resolution scroll handling on Wayland.
- Touchpad gestures on the timeline now either scroll or zoom, but not both
simultaneously.
- Full user name is now stored in trace info.
- External functions can be filtered out in the sampling statistics view.
- Tweaked external paths heuristics.
- Check for both 64-bit and 32-bit versions of Program Files directory.
- Hidden unix files and directories are now also considered external. For
example: $(HOME)/.cache/cpm/somelib/file.h.
- Call stack window can now provide LLM summaries.
- These summaries can be performed automatically. Enable in LLM settings.
- The capture utility is now displaying query backlog, just like the profiler
GUI.
- Lua source locations that are script code will now have newlines removed.
This is a capture-time change, so previously captured broken Lua source
locations won't be fixed.
- Call stack window will now display notification if viewing a crash call
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
"unknown", in case the source line number is not known.
- Trace host info is now properly formatted.
- It is now possible to sort the order of threads on the timeline ("visible
threads" in trace settings).
- Added clipboard support to emscripten backend.
- 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 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.
- Added ___tracy_get_time() C API as an equivalent for Profiler::GetTime().
- D3D12 instrumentation improvements.
- CUDA instrumentation improvements.
- Properly set API visibility attributes on MSVC + clang.
- Fixed regression in data sorting algorithm that could cause broken (going
back in time) plots. A retroactive fix is included for previously broken
traces.
- All tools provided by the project now report the version number and git sha
revision on the command line help output.
- Microarchitectural data has been updated to include the latest uops.info
measurements.
- Added validation check for SymSrv.dll on Windows.
- Various CMake options are now available to control optional build settings:
- NO_LTO disables link-time optimization.
- NO_MOLD_LINKER disables use of the mold linker.
- NO_CCACHE disables use of ccache (compiler cache).
- The Tracy library now has TRACY_ENABLED unset by default in the CMake and
Meson default configurations. This now matches what the documentation was
always saying. Some build setups may need updating.
- Adjust to max sampling rate on Linux.
- Further improvements to tracefs mount path discovery robustness.
- Macro mismatch detection between Tracy configuration and client code.
- Tracy client build settings change the ABI of the library.
- 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.
- Greatly improved the in-profiler user manual.
- There is now chapter tree and the manual contents are displayed section
by section.
- Links to chapters are now properly working.
- The "bclogo" blocks are now correctly processed.
- The font awesome icons now show as in the rest of the UI.
- Call stack window will now show the thread viewed call stack originates
from (if possible).
- "Visible threads" checkboxes in messages, flame graph and wait stacks
windows are now displayed in multiple columns, and the maximum number of
visible rows is limited, with fallback to scrollable view.
- Improved child call distribution list in the symbol view window.
- The visible area can be now resized horizontally.
- The list is now displayed as a table with resizable columns, etc.
- Child calls time percentage is now shown as a percentage of calls (as
it was before), and also as a percentage of total symbol time.
- Child calls time is now also displayed as a percentage.
- Prototype implementation of system tracing on Apple devices.
- Local (inline) call stack printouts were added to tooltips in statistics
window, in sampling mode.
- Ironed out some code corners to make integration of closed gaming console
platforms easier. Added support for custom platform headers.
- Bottom and top sample trees (in wait stacks, or in entry call stacks)
now display aggregation counts if "group by function name" is enabled.
- HW sample view in symbol view are now disabled by default.
- 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.
v0.13.1 (2025-12-11)
--------------------

View File

@@ -1,7 +1,6 @@
cmake_minimum_required(VERSION 3.16)
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
option(NO_STATISTICS "Disable calculation of statistics" ON)
set(NO_STATISTICS ON)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
@@ -16,13 +15,22 @@ project(
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)
set(PROGRAM_FILES
src/capture.cpp
src/CaptureOutput.cpp
)
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES})
add_git_ref(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
add_executable(tracy-capture-daemon src/capturedaemon.cpp src/CaptureOutput.cpp ${COMMON_FILES} ${SERVER_FILES})
add_git_ref(tracy-capture-daemon)
target_link_libraries(tracy-capture-daemon PRIVATE TracyServer TracyGetOpt)
install(TARGETS tracy-capture-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@@ -0,0 +1,202 @@
#ifdef _WIN32
# include <io.h>
# include <windows.h>
#else
# include <unistd.h>
#endif
#include <atomic>
#include <chrono>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <inttypes.h>
#include <thread>
#include "CaptureOutput.hpp"
#include "../../public/common/TracyProtocol.hpp"
#include "../../public/common/TracyStackFrames.hpp"
#include "../../server/TracyMemory.hpp"
#include "../../server/TracyPrint.hpp"
#include "../../server/TracyWorker.hpp"
static bool s_isTerminal = false;
void InitTerminalDetection()
{
#ifdef _WIN32
s_isTerminal = _isatty( fileno( stdout ) );
#else
s_isTerminal = isatty( fileno( stdout ) );
#endif
}
bool IsTerminal()
{
return s_isTerminal;
}
void AnsiPrintf( const char* ansiEscape, const char* format, ... )
{
if( IsTerminal() )
{
char buf[256];
va_list args;
va_start( args, format );
vsnprintf( buf, sizeof buf, format, args );
va_end( args );
printf( "%s%s" ANSI_RESET, ansiEscape, buf );
}
else
{
va_list args;
va_start( args, format );
vfprintf( stdout, format, args );
va_end( args );
}
}
int WaitForConnection( tracy::Worker& worker )
{
while( !worker.HasData() )
{
const auto handshake = worker.GetHandshakeStatus();
if( handshake == tracy::HandshakeProtocolMismatch )
{
printf( "\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n" );
return 1;
}
if( handshake == tracy::HandshakeNotAvailable )
{
printf( "\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n" );
return 2;
}
if( handshake == tracy::HandshakeDropped )
{
printf( "\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n" );
return 3;
}
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
return 0;
}
void PrintWorkerFailure( tracy::Worker& worker )
{
const auto& failure = worker.GetFailureType();
if( failure == tracy::Worker::Failure::None ) return;
AnsiPrintf( ANSI_RED ANSI_BOLD, "\nInstrumentation failure: %s", tracy::Worker::GetFailureString( failure ) );
auto& fd = worker.GetFailureData();
if( !fd.message.empty() )
{
printf( "\nContext: %s", fd.message.c_str() );
}
if( fd.callstack != 0 )
{
AnsiPrintf( ANSI_BOLD, "\nFailure callstack:\n" );
auto& cs = worker.GetCallstack( fd.callstack );
int fidx = 0;
for( auto& entry : cs )
{
auto frameData = worker.GetCallstackFrame( entry );
if( !frameData )
{
printf( "%3i. %p\n", fidx++, (void*)worker.GetCanonicalPointer( entry ) );
}
else
{
const auto fsz = frameData->size;
for( uint8_t f = 0; f < fsz; f++ )
{
const auto& frame = frameData->data[f];
auto txt = worker.GetString( frame.name );
if( fidx == 0 && f != fsz - 1 )
{
auto test = tracy::s_tracyStackFrames;
bool match = false;
do
{
if( strcmp( txt, *test ) == 0 )
{
match = true;
break;
}
}
while( *++test );
if( match ) continue;
}
if( f == fsz - 1 )
{
printf( "%3i. ", fidx++ );
}
else
{
AnsiPrintf( ANSI_BLACK ANSI_BOLD, "inl. " );
}
AnsiPrintf( ANSI_CYAN, "%s ", txt );
txt = worker.GetString( frame.file );
if( frame.line == 0 )
{
AnsiPrintf( ANSI_YELLOW, "(%s)", txt );
}
else
{
AnsiPrintf( ANSI_YELLOW, "(%s:%" PRIu32 ")", txt, frame.line );
}
if( frameData->imageName.Active() )
{
AnsiPrintf( ANSI_MAGENTA, " %s\n", worker.GetString( frameData->imageName ) );
}
else
{
printf( "\n" );
}
}
}
}
}
}
void PrintCaptureProgress( tracy::Worker& worker, int64_t firstTime, int64_t memoryLimit )
{
if( !IsTerminal() ) return;
auto& lock = worker.GetMbpsDataLock();
lock.lock();
const auto mbps = worker.GetMbpsData().back();
const auto compRatio = worker.GetCompRatio();
const auto netTotal = worker.GetDataTransferred();
const auto queueSize = worker.GetSendQueueSize();
lock.unlock();
const char* unit = "Mbps";
float unitsPerMbps = 1.f;
if( mbps < 0.1f )
{
unit = "Kbps";
unitsPerMbps = 1000.f;
}
AnsiPrintf( ANSI_ERASE_LINE ANSI_CYAN ANSI_BOLD, "\r%7.2f %s", mbps * unitsPerMbps, unit );
printf( " /" );
AnsiPrintf( ANSI_CYAN ANSI_BOLD, "%5.1f%%", compRatio * 100.f );
printf( " =" );
AnsiPrintf( ANSI_YELLOW ANSI_BOLD, "%7.2f Mbps", mbps / compRatio );
printf( " | " );
AnsiPrintf( ANSI_YELLOW, "Tx: " );
AnsiPrintf( ANSI_GREEN, "%s", tracy::MemSizeToString( netTotal ) );
printf( " | " );
AnsiPrintf( ANSI_RED ANSI_BOLD, "%s", tracy::MemSizeToString( tracy::memUsage.load( std::memory_order_relaxed ) ) );
if( memoryLimit > 0 )
{
printf( " / " );
AnsiPrintf( ANSI_BLUE ANSI_BOLD, "%s", tracy::MemSizeToString( memoryLimit ) );
}
printf( " | " );
AnsiPrintf( ANSI_RED, "%s", tracy::TimeToString( worker.GetLastTime() - firstTime ) );
printf( " | " );
AnsiPrintf( ANSI_RED ANSI_BOLD, "%s query backlog", tracy::RealToString( queueSize ) );
fflush( stdout );
}

View File

@@ -0,0 +1,33 @@
#ifndef __CAPTUREOUTPUT_HPP__
#define __CAPTUREOUTPUT_HPP__
#include <stdint.h>
#define ANSI_RESET "\033[0m"
#define ANSI_BOLD "\033[1m"
#define ANSI_BLACK "\033[30m"
#define ANSI_RED "\033[31m"
#define ANSI_GREEN "\033[32m"
#define ANSI_YELLOW "\033[33m"
#define ANSI_BLUE "\033[34m"
#define ANSI_MAGENTA "\033[35m"
#define ANSI_CYAN "\033[36m"
#define ANSI_ERASE_LINE "\033[2K"
namespace tracy { class Worker; }
void InitTerminalDetection();
bool IsTerminal();
#ifdef __GNUC__
[[gnu::format( __printf__, 2, 3 )]]
#endif
void AnsiPrintf( const char* ansiEscape, const char* format, ... );
int WaitForConnection( tracy::Worker& worker );
void PrintWorkerFailure( tracy::Worker& worker );
void PrintCaptureProgress( tracy::Worker& worker, int64_t firstTime, int64_t memoryLimit );
#endif

View File

@@ -1,6 +1,5 @@
#ifdef _WIN32
# include <windows.h>
# include <io.h>
#else
# include <unistd.h>
#endif
@@ -8,21 +7,20 @@
#include <atomic>
#include <chrono>
#include <inttypes.h>
#include <mutex>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "../../public/common/TracyProtocol.hpp"
#include "../../public/common/TracyStackFrames.hpp"
#include "../../server/TracyFileWrite.hpp"
#include "../../server/TracyMemory.hpp"
#include "../../server/TracyPrint.hpp"
#include "../../server/TracySysUtil.hpp"
#include "../../server/TracyWorker.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
#include "CaptureOutput.hpp"
#ifdef _WIN32
# include "../../getopt/getopt.h"
@@ -38,60 +36,12 @@ static std::atomic<bool> s_disconnect { false };
void SigInt( int )
{
// Relaxed order is closest to a traditional `volatile` write.
// We don't need stronger ordering since this signal handler doesn't do
// anything else that would need to be ordered relatively to this.
s_disconnect.store(true, std::memory_order_relaxed);
}
static bool s_isStdoutATerminal = false;
void InitIsStdoutATerminal() {
#ifdef _WIN32
s_isStdoutATerminal = _isatty( fileno( stdout ) );
#else
s_isStdoutATerminal = isatty( fileno( stdout ) );
#endif
}
bool IsStdoutATerminal() { return s_isStdoutATerminal; }
#define ANSI_RESET "\033[0m"
#define ANSI_BOLD "\033[1m"
#define ANSI_BLACK "\033[30m"
#define ANSI_RED "\033[31m"
#define ANSI_GREEN "\033[32m"
#define ANSI_YELLOW "\033[33m"
#define ANSI_BLUE "\033[34m"
#define ANSI_MAGENTA "\033[35m"
#define ANSI_CYAN "\033[36m"
#define ANSI_ERASE_LINE "\033[2K"
// Like printf, but if stdout is a terminal, prepends the output with
// the given `ansiEscape` and appends ANSI_RESET.
void AnsiPrintf( const char* ansiEscape, const char* format, ... ) {
if( IsStdoutATerminal() )
{
// Prepend ansiEscape and append ANSI_RESET.
char buf[256];
va_list args;
va_start( args, format );
vsnprintf( buf, sizeof buf, format, args );
va_end( args );
printf( "%s%s" ANSI_RESET, ansiEscape, buf );
}
else
{
// Just a normal printf.
va_list args;
va_start( args, format );
vfprintf( stdout, format, args );
va_end( args );
}
}
[[noreturn]] void Usage()
{
printf( "tracy-capture %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
printf( "Usage: capture -o output.tracy [-a address] [-p port] [-f] [-s seconds] [-m memlimit]\n" );
exit( 1 );
}
@@ -106,7 +56,7 @@ int main( int argc, char** argv )
}
#endif
InitIsStdoutATerminal();
InitTerminalDetection();
bool overwrite = false;
const char* address = "127.0.0.1";
@@ -165,26 +115,8 @@ int main( int argc, char** argv )
printf( "Connecting to %s:%i...", address, port );
fflush( stdout );
tracy::Worker worker( address, port, memoryLimit );
while( !worker.HasData() )
{
const auto handshake = worker.GetHandshakeStatus();
if( handshake == tracy::HandshakeProtocolMismatch )
{
printf( "\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n" );
return 1;
}
if( handshake == tracy::HandshakeNotAvailable )
{
printf( "\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n" );
return 2;
}
if( handshake == tracy::HandshakeDropped )
{
printf( "\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n" );
return 3;
}
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
int result = WaitForConnection( worker );
if( result != 0 ) return result;
printf( "\nTimer resolution: %s\n", tracy::TimeToString( worker.GetResolution() ) );
#ifdef _WIN32
@@ -197,59 +129,18 @@ int main( int argc, char** argv )
#endif
const auto firstTime = worker.GetFirstTime();
auto& lock = worker.GetMbpsDataLock();
const auto t0 = std::chrono::high_resolution_clock::now();
while( worker.IsConnected() )
{
// Relaxed order is sufficient here because `s_disconnect` is only ever
// set by this thread or by the SigInt handler, and that handler does
// nothing else than storing `s_disconnect`.
if( s_disconnect.load( std::memory_order_relaxed ) )
{
worker.Disconnect();
// Relaxed order is sufficient because only this thread ever reads
// this value.
s_disconnect.store(false, std::memory_order_relaxed );
break;
}
lock.lock();
const auto mbps = worker.GetMbpsData().back();
const auto compRatio = worker.GetCompRatio();
const auto netTotal = worker.GetDataTransferred();
lock.unlock();
// Output progress info only if destination is a TTY to avoid bloating
// log files (so this is not just about usage of ANSI color codes).
if( IsStdoutATerminal() )
{
const char* unit = "Mbps";
float unitsPerMbps = 1.f;
if( mbps < 0.1f )
{
unit = "Kbps";
unitsPerMbps = 1000.f;
}
AnsiPrintf( ANSI_ERASE_LINE ANSI_CYAN ANSI_BOLD, "\r%7.2f %s", mbps * unitsPerMbps, unit );
printf( " /");
AnsiPrintf( ANSI_CYAN ANSI_BOLD, "%5.1f%%", compRatio * 100.f );
printf( " =");
AnsiPrintf( ANSI_YELLOW ANSI_BOLD, "%7.2f Mbps", mbps / compRatio );
printf( " | ");
AnsiPrintf( ANSI_YELLOW, "Tx: ");
AnsiPrintf( ANSI_GREEN, "%s", tracy::MemSizeToString( netTotal ) );
printf( " | ");
AnsiPrintf( ANSI_RED ANSI_BOLD, "%s", tracy::MemSizeToString( tracy::memUsage.load( std::memory_order_relaxed ) ) );
if( memoryLimit > 0 )
{
printf( " / " );
AnsiPrintf( ANSI_BLUE ANSI_BOLD, "%s", tracy::MemSizeToString( memoryLimit ) );
}
printf( " | ");
AnsiPrintf( ANSI_RED, "%s", tracy::TimeToString( worker.GetLastTime() - firstTime ) );
fflush( stdout );
}
PrintCaptureProgress( worker, firstTime, memoryLimit );
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
if( seconds != -1 )
@@ -257,90 +148,13 @@ int main( int argc, char** argv )
const auto dur = std::chrono::high_resolution_clock::now() - t0;
if( std::chrono::duration_cast<std::chrono::seconds>(dur).count() >= seconds )
{
// Relaxed order is sufficient because only this thread ever reads
// this value.
s_disconnect.store(true, std::memory_order_relaxed );
}
}
}
const auto t1 = std::chrono::high_resolution_clock::now();
const auto& failure = worker.GetFailureType();
if( failure != tracy::Worker::Failure::None )
{
AnsiPrintf( ANSI_RED ANSI_BOLD, "\nInstrumentation failure: %s", tracy::Worker::GetFailureString( failure ) );
auto& fd = worker.GetFailureData();
if( !fd.message.empty() )
{
printf( "\nContext: %s", fd.message.c_str() );
}
if( fd.callstack != 0 )
{
AnsiPrintf( ANSI_BOLD, "\nFailure callstack:\n" );
auto& cs = worker.GetCallstack( fd.callstack );
int fidx = 0;
for( auto& entry : cs )
{
auto frameData = worker.GetCallstackFrame( entry );
if( !frameData )
{
printf( "%3i. %p\n", fidx++, (void*)worker.GetCanonicalPointer( entry ) );
}
else
{
const auto fsz = frameData->size;
for( uint8_t f=0; f<fsz; f++ )
{
const auto& frame = frameData->data[f];
auto txt = worker.GetString( frame.name );
if( fidx == 0 && f != fsz-1 )
{
auto test = tracy::s_tracyStackFrames;
bool match = false;
do
{
if( strcmp( txt, *test ) == 0 )
{
match = true;
break;
}
}
while( *++test );
if( match ) continue;
}
if( f == fsz-1 )
{
printf( "%3i. ", fidx++ );
}
else
{
AnsiPrintf( ANSI_BLACK ANSI_BOLD, "inl. " );
}
AnsiPrintf( ANSI_CYAN, "%s ", txt );
txt = worker.GetString( frame.file );
if( frame.line == 0 )
{
AnsiPrintf( ANSI_YELLOW, "(%s)", txt );
}
else
{
AnsiPrintf( ANSI_YELLOW, "(%s:%" PRIu32 ")", txt, frame.line );
}
if( frameData->imageName.Active() )
{
AnsiPrintf( ANSI_MAGENTA, " %s\n", worker.GetString( frameData->imageName ) );
}
else
{
printf( "\n" );
}
}
}
}
}
}
PrintWorkerFailure( worker );
printf( "\nFrames: %" PRIu64 "\nTime span: %s\nZones: %s\nElapsed time: %s\nSaving trace...",
worker.GetFrameCount( *worker.GetFramesBase() ), tracy::TimeToString( worker.GetLastTime() - firstTime ), tracy::RealToString( worker.GetZoneCount() ),

View File

@@ -0,0 +1,438 @@
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
#endif
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <map>
#include <mutex>
#include <signal.h>
#include <string>
#include <thread>
#include <unordered_set>
#include <vector>
#include "../../getopt/getopt.h"
#include "../../public/common/TracySocket.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "../../server/TracyBroadcast.hpp"
#include "../../server/TracyFileWrite.hpp"
#include "../../server/TracyMemory.hpp"
#include "../../server/TracyPrint.hpp"
#include "../../server/TracySysUtil.hpp"
#include "../../server/TracyWorker.hpp"
#include "GitRef.hpp"
#include "CaptureOutput.hpp"
static std::atomic<bool> g_shutdown{false};
static std::mutex g_clientsMutex;
static uint16_t g_listenPort = 8086;
static std::string g_filterName;
static int g_filterPort = 0;
static int64_t g_memoryLimit = -1;
void SigInt( int )
{
g_shutdown.store( true, std::memory_order_relaxed );
}
struct ClientStats
{
std::atomic<float> mbps{0};
std::atomic<int64_t> txBytes{0};
std::atomic<int64_t> memUsage{0};
std::atomic<int64_t> firstTime{-1};
};
struct ClientSession
{
std::string id;
std::string programName;
std::string address;
uint16_t port;
std::string outputFile;
std::thread thread;
std::atomic<bool> active{true};
std::atomic<bool> finished{false};
ClientStats stats;
std::atomic<uint64_t> fileSize{0};
};
static std::map<std::string, ClientSession*> g_clients;
static std::unordered_set<std::string> g_outputFiles;
[[noreturn]] void Usage()
{
printf( "tracy-capture-daemon %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
printf( "Usage: tracy-capture-daemon -o <output_dir> [options]\n\n" );
printf( "Options:\n" );
printf( " -o, --output <dir> Output directory (required)\n" );
printf( " -p, --port <port> UDP listen port (default: 8086)\n" );
printf( " -m, --memory <limit> Memory limit per client as %% of system RAM\n" );
printf( " --filter-name <pattern> Only capture clients matching program name\n" );
printf( " --filter-port <port> Only capture clients with specific data port\n" );
printf( " -h, --help Show this help\n" );
printf( " -V, --version Show version information\n" );
exit( 1 );
}
std::string SanitizeName( const std::string& name )
{
std::string result;
for( char c : name )
{
if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= '0' && c <= '9' ) || c == '_' || c == '-' )
{
result += c;
}
else if( c == ' ' || c == '\t' )
{
result += '_';
}
}
if( result.empty() ) result = "unknown";
return result;
}
std::string GenerateOutputFilename( const std::string& outputDir, const std::string& programName, const std::string& address, uint16_t port )
{
std::string base = SanitizeName( programName ) + "_" + address + "_" + std::to_string( port );
std::string candidate = base + ".tracy";
std::string path = outputDir + "/" + candidate;
int idx = 0;
while( g_outputFiles.count( path ) || std::filesystem::exists( path ) )
{
idx++;
candidate = base + "_" + std::to_string( idx ) + ".tracy";
path = outputDir + "/" + candidate;
}
g_outputFiles.insert( path );
return path;
}
bool MatchesFilters( const tracy::BroadcastMessage& msg )
{
if( !g_filterName.empty() )
{
if( strstr( msg.programName, g_filterName.c_str() ) == nullptr )
{
return false;
}
}
if( g_filterPort > 0 && msg.listenPort != g_filterPort )
{
return false;
}
return true;
}
void CaptureThread( ClientSession* session, const std::string& address, uint16_t port, int64_t memoryLimit, const std::string& outputFile )
{
printf( "Connecting to %s:%u...\n", address.c_str(), port );
fflush( stdout );
tracy::Worker worker( address.c_str(), port, memoryLimit );
int result = WaitForConnection( worker );
if( result != 0 )
{
session->active = false;
session->finished = true;
return;
}
printf( "Connected to %s (%s:%u)\n", session->programName.c_str(), address.c_str(), port );
int64_t firstTime = worker.GetFirstTime();
session->stats.firstTime = firstTime;
while( session->active && worker.IsConnected() )
{
auto& lock = worker.GetMbpsDataLock();
lock.lock();
float mbps = worker.GetMbpsData().back();
int64_t txTotal = 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 ) );
}
printf( "\nSaving %s...", outputFile.c_str() );
fflush( stdout );
auto file = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( outputFile.c_str(), tracy::FileCompression::Zstd, 3, 4 ) );
if( file )
{
worker.Write( *file, false );
file->Finish();
auto stats = file->GetCompressionStatistics();
session->fileSize = stats.second;
AnsiPrintf( ANSI_GREEN ANSI_BOLD, " done!\n" );
}
else
{
AnsiPrintf( ANSI_RED ANSI_BOLD, " failed!\n" );
}
session->finished = true;
session->active = false;
}
void RefreshDisplay( const std::string& listenAddr )
{
if( !IsTerminal() ) return;
printf( "\033[H\033[J" );
size_t clientCount = 0;
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
clientCount = g_clients.size();
}
printf( "[%zu client%s] Listening on %s:%u... Press Ctrl+C to stop\n\n", clientCount, clientCount == 1 ? "" : "s", listenAddr.c_str(), g_listenPort );
int idx = 1;
float totalMbps = 0;
int64_t totalTx = 0;
int64_t totalMem = 0;
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
for( auto& [id, session] : g_clients )
{
printf( " [%d] %s @ %s:%u ", idx, session->programName.c_str(), session->address.c_str(), session->port );
if( session->finished )
{
printf( "finished (" );
printf( "%s", tracy::MemSizeToString( session->fileSize.load() ) );
printf( ")" );
}
else if( session->active )
{
float mbps = session->stats.mbps.load();
int64_t tx = session->stats.txBytes.load();
int64_t mem = session->stats.memUsage.load();
int64_t firstTime = session->stats.firstTime.load();
printf( "%.1f Mbps | %s | %s", mbps, tracy::MemSizeToString( tx ), tracy::MemSizeToString( mem ) );
totalMbps += mbps;
totalTx += tx;
totalMem += mem;
}
else
{
printf( "connecting..." );
}
printf( "\n" );
idx++;
}
}
printf( "\nTotal: %.1f Mbps | %s | Mem: %s", totalMbps, tracy::MemSizeToString( totalTx ), tracy::MemSizeToString( totalMem ) );
fflush( stdout );
}
void PrintSummary()
{
printf( "\n\n=== Capture Summary ===\n" );
std::lock_guard<std::mutex> lock( g_clientsMutex );
int idx = 1;
int64_t totalSize = 0;
for( auto& [id, session] : g_clients )
{
int64_t size = session->fileSize.load();
totalSize += size;
printf( " [%d] %s @ %s:%u -> %s (%s)\n", idx++, session->programName.c_str(), session->address.c_str(), session->port, session->outputFile.c_str(), tracy::MemSizeToString( size ) );
}
printf( "\nTotal: %zu files, %s\n", g_clients.size(), tracy::MemSizeToString( totalSize ) );
}
int main( int argc, char** argv )
{
#ifdef _WIN32
if( !AttachConsole( ATTACH_PARENT_PROCESS ) )
{
AllocConsole();
SetConsoleMode( GetStdHandle( STD_OUTPUT_HANDLE ), 0x07 );
}
#endif
std::string outputDir;
static struct option longOptions[] = {
{ "output", required_argument, nullptr, 'o' },
{ "port", required_argument, nullptr, 'p' },
{ "memory", required_argument, nullptr, 'm' },
{ "filter-name", required_argument, nullptr, 1 },
{ "filter-port", required_argument, nullptr, 2 },
{ "help", no_argument, nullptr, 'h' },
{ "version", no_argument, nullptr, 'V' },
{ nullptr, 0, nullptr, 0 }
};
int c;
while( ( c = getopt_long( argc, argv, "o:p:m:hV", longOptions, nullptr ) ) != -1 )
{
switch( c )
{
case 'o':
outputDir = optarg;
break;
case 'p':
g_listenPort = atoi( optarg );
break;
case 'm':
g_memoryLimit = std::clamp( atoll( optarg ), 1ll, 999ll ) * tracy::GetPhysicalMemorySize() / 100;
break;
case 1:
g_filterName = optarg;
break;
case 2:
g_filterPort = atoi( optarg );
break;
case 'h':
Usage();
break;
case 'V':
printf( "tracy-capture-daemon %i.%i.%i / %s\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
exit( 0 );
default:
Usage();
break;
}
}
if( outputDir.empty() )
{
fprintf( stderr, "Error: Output directory is required (-o)\n\n" );
Usage();
}
std::filesystem::create_directories( outputDir );
InitTerminalDetection();
#ifdef _WIN32
signal( SIGINT, SigInt );
#else
struct sigaction sigint, oldsigint;
memset( &sigint, 0, sizeof( sigint ) );
sigint.sa_handler = SigInt;
sigaction( SIGINT, &sigint, &oldsigint );
#endif
tracy::UdpListen udpSocket;
if( !udpSocket.Listen( g_listenPort ) )
{
fprintf( stderr, "Error: Failed to listen on port %u\n", g_listenPort );
return 1;
}
printf( "Listening on 0.0.0.0:%u... Press Ctrl+C to stop\n", g_listenPort );
printf( "Output directory: %s\n", outputDir.c_str() );
const std::string listenAddr = "0.0.0.0";
auto lastDisplay = std::chrono::steady_clock::now();
while( !g_shutdown )
{
tracy::IpAddress clientAddr;
size_t len;
const char* msg = udpSocket.Read( len, clientAddr, 100 );
if( msg )
{
auto parsed = tracy::ParseBroadcastMessage( msg, len );
if( parsed )
{
std::string clientId = std::to_string( parsed->pid ) + "_" + clientAddr.GetText() + "_" + std::to_string( parsed->listenPort );
bool isNew = false;
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
isNew = g_clients.find( clientId ) == g_clients.end();
}
if( isNew && MatchesFilters( *parsed ) )
{
std::string addressStr = clientAddr.GetText();
std::string outputFile = GenerateOutputFilename( outputDir, parsed->programName, addressStr, parsed->listenPort );
auto session = new ClientSession();
session->id = clientId;
session->programName = parsed->programName;
session->address = addressStr;
session->port = parsed->listenPort;
session->outputFile = outputFile;
session->active = true;
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
g_clients[clientId] = session;
}
session->thread = std::thread( CaptureThread, session, addressStr, parsed->listenPort, g_memoryLimit, outputFile );
}
}
}
auto now = std::chrono::steady_clock::now();
if( std::chrono::duration_cast<std::chrono::milliseconds>( now - lastDisplay ).count() >= 100 )
{
RefreshDisplay( listenAddr );
lastDisplay = now;
}
}
printf( "\n\nShutting down... waiting for %zu client(s) to finish\n", g_clients.size() );
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
for( auto& [id, session] : g_clients )
{
session->active = false;
}
}
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
for( auto& [id, session] : g_clients )
{
if( session->thread.joinable() )
{
session->thread.join();
}
}
}
PrintSummary();
{
std::lock_guard<std::mutex> lock( g_clientsMutex );
for( auto& [id, session] : g_clients )
{
delete session;
}
g_clients.clear();
}
return 0;
}

37
cmake/GitRef.cmake Normal file
View File

@@ -0,0 +1,37 @@
function(add_git_ref target)
if(NOT DEFINED GIT_REV)
set(GIT_REV "HEAD")
endif()
get_property(_git_ref_created GLOBAL PROPERTY _GIT_REF_CREATED)
if(NOT _git_ref_created)
set_property(GLOBAL PROPERTY _GIT_REF_CREATED TRUE)
find_package(Git)
set_property(GLOBAL PROPERTY _GIT_FOUND "${Git_FOUND}")
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 ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
VERBATIM
)
else()
message(WARNING "git not found, using 'unknown' as git ref.")
add_custom_command(
OUTPUT GitRef.hpp
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp
COMMAND ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp
VERBATIM
)
endif()
endif()
target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
get_property(_git_found GLOBAL PROPERTY _GIT_FOUND)
if(_git_found)
add_dependencies(${target} git-ref)
else()
target_sources(${target} PUBLIC GitRef.hpp)
endif()
endfunction()

View File

@@ -1,8 +1,15 @@
include(${CMAKE_CURRENT_LIST_DIR}/options.cmake)
set_option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
set_option(NO_LTO "Disable interprocedural optimization (LTO)" OFF)
set_option(NO_MOLD_LINKER "Disable mold linker (use default linker)" OFF)
set_option(NO_CCACHE "Disable ccache acceleration" OFF)
if (NOT NO_ISA_EXTENSIONS)
include(CheckCXXCompilerFlag)
if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
CHECK_CXX_COMPILER_FLAG("-mcpu=native" COMPILER_SUPPORTS_MCPU_NATIVE)
if(COMPILER_SUPPORTS_MARCH_NATIVE)
if(COMPILER_SUPPORTS_MCPU_NATIVE)
add_compile_options(-mcpu=native)
endif()
else()
@@ -36,18 +43,21 @@ endif()
if(WIN32)
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN -D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
add_compile_options(/MP)
# /MP is MSVC-specific for multi-processor compilation
if(MSVC)
add_compile_options(/MP)
endif()
endif()
if(EMSCRIPTEN)
add_compile_options(-pthread -DIMGUI_IMPL_OPENGL_ES2)
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT EMSCRIPTEN)
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT EMSCRIPTEN AND NOT NO_LTO)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT NO_MOLD_LINKER)
find_program(MOLD_LINKER mold)
if(MOLD_LINKER)
set(CMAKE_LINKER_TYPE "MOLD")
@@ -57,10 +67,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
endif()
endif()
find_program(CCACHE ccache)
if(CCACHE)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
if(NOT NO_CCACHE)
find_program(CCACHE ccache)
if(CCACHE)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif()
endif()
file(GENERATE OUTPUT .gitignore CONTENT "*")

48
cmake/options.cmake Normal file
View File

@@ -0,0 +1,48 @@
# Reusable option macros for Tracy CMake projects
#
# Usage:
# set_option(OPTION_NAME "Help text" ON/OFF [TARGET]) - for boolean options
# set_option_value(VAR_NAME "Help text" "value" [TARGET]) - for value options (CACHE STRING)
# set_option_value_as_string(VAR_NAME "Help text" "value" [TARGET]) - for value options as C string literals
#
# [TARGET] is optional and specifies a target to which the option will
# be added as a compile definition (e.g., -DOPTION_NAME or -DVAR_NAME=value).
# Boolean option (ON/OFF).
macro(set_option option help value)
option(${option} ${help} ${value})
if(${option})
message(STATUS "${option}: ON")
if(${ARGC} GREATER 3)
target_compile_definitions(${ARGV3} PUBLIC ${option})
endif()
else()
message(STATUS "${option}: OFF")
endif()
endmacro()
# Value option (string/number).
macro(set_option_value var help default)
set(${var} ${default} CACHE STRING "${help}")
if(${var})
message(STATUS "${var}: ${${var}}")
if(${ARGC} GREATER 3)
target_compile_definitions(${ARGV3} PUBLIC ${var}=${${var}})
endif()
else()
message(STATUS "${var}: (not set)")
endif()
endmacro()
# Value option embedded as a C string literal (VAR="value").
macro(set_option_value_as_string var help default)
set(${var} ${default} CACHE STRING "${help}")
if(${var})
message(STATUS "${var}: ${${var}}")
if(${ARGC} GREATER 3)
target_compile_definitions(${ARGV3} PUBLIC "${var}=\"${${var}}\"")
endif()
else()
message(STATUS "${var}: (not set)")
endif()
endmacro()

View File

@@ -0,0 +1,14 @@
diff --git i/include/ppqsort/parallel/cpp/thread_pool.h w/include/ppqsort/parallel/cpp/thread_pool.h
--- i/include/ppqsort/parallel/cpp/thread_pool.h
+++ w/include/ppqsort/parallel/cpp/thread_pool.h
@@ -134,7 +134,9 @@ namespace ppqsort::impl::cpp {
alignas(parameters::cacheline_size) std::atomic<std::size_t> pending_tasks_{0};
alignas(parameters::cacheline_size) std::atomic<std::size_t> total_tasks_{0};
alignas(parameters::cacheline_size) std::atomic<bool> to_stop_{false};
- std::binary_semaphore threads_done_semaphore_{0}; // used to wait for all tasks to finish
+ // counting_semaphore: multiple workers may concurrently observe total_tasks_ == 0
+ // and call release(); a binary_semaphore would assert when the count exceeds 1.
+ std::counting_semaphore<> threads_done_semaphore_{0};
std::mutex mtx_priority_;
bool stopped = false;
};

View File

@@ -14,6 +14,7 @@ list(TRANSFORM TRACY_COMMON_SOURCES PREPEND "${TRACY_COMMON_DIR}/")
set(TRACY_SERVER_DIR ${CMAKE_CURRENT_LIST_DIR}/../server)
set(TRACY_SERVER_SOURCES
TracyBroadcast.cpp
TracyMemory.cpp
TracyMmap.cpp
TracyPrint.cpp

View File

@@ -26,7 +26,7 @@ else()
CPMAddPackage(
NAME capstone
GITHUB_REPOSITORY capstone-engine/capstone
GIT_TAG 6.0.0-Alpha5
GIT_TAG 6.0.0-Alpha9
OPTIONS
"CAPSTONE_X86_ATT_DISABLE ON"
"CAPSTONE_ALPHA_SUPPORT OFF"
@@ -94,7 +94,7 @@ else()
CPMAddPackage(
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-1
GIT_TAG VER-2-14-3
OPTIONS
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
@@ -137,7 +137,7 @@ target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.5-docking
GIT_TAG v1.92.8-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
@@ -160,6 +160,7 @@ 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)
@@ -183,7 +184,7 @@ if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG v1.2.1
GIT_TAG v1.3.0
EXCLUDE_FROM_ALL TRUE
OPTIONS
"NFD_PORTAL ${NFD_PORTAL}"
@@ -198,6 +199,7 @@ CPMAddPackage(
VERSION 1.0.6
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/ppqsort-nodebug.patch"
"${CMAKE_CURRENT_LIST_DIR}/ppqsort-semaphore.patch"
EXCLUDE_FROM_ALL TRUE
)
@@ -215,7 +217,7 @@ CPMAddPackage(
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG release-0.5.2
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
EXCLUDE_FROM_ALL TRUE
)
@@ -252,7 +254,7 @@ if(NOT EMSCRIPTEN)
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.21.3
GIT_TAG v2.25.2
EXCLUDE_FROM_ALL TRUE
)
@@ -285,7 +287,7 @@ if(NOT EMSCRIPTEN)
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_17_0
GIT_TAG curl-8_20_0
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"

View File

@@ -1,7 +1,5 @@
cmake_minimum_required(VERSION 3.16)
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
set(NO_STATISTICS OFF)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
@@ -17,12 +15,14 @@ project(
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)
set(PROGRAM_FILES
src/csvexport.cpp
)
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES})
add_git_ref(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})

View File

@@ -16,14 +16,18 @@
#include "../../server/TracyFileRead.hpp"
#include "../../server/TracyWorker.hpp"
#include "../../getopt/getopt.h"
#include "../../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
void print_usage_exit(int e)
{
fprintf(stderr, "tracy-csvexport %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef);
fprintf(stderr, "Extract statistics from a trace to a CSV format\n");
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");
@@ -61,6 +65,7 @@ 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' },
{ "case", no_argument, NULL, 'c' },
@@ -74,13 +79,16 @@ Args parse_args(int argc, char** argv)
};
int c;
while ((c = getopt_long(argc, argv, "hf:s:ceugmp", long_opts, NULL)) != -1)
while ((c = getopt_long(argc, argv, "hf:s:ceugmpV", long_opts, NULL)) != -1)
{
switch (c)
{
case 'h':
print_usage_exit(0);
break;
case 'V':
printf( "tracy-csvexport %i.%i.%i / %s\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
exit( 0 );
case 'f':
args.filter = optarg;
break;

View File

@@ -0,0 +1,57 @@
// Template implementations of the tracy::Platform* hooks. Pair with the
// platform header (see CustomPlatform.h) and link this into your final
// binary.
#include <stdlib.h>
#include <string.h>
#include "CustomPlatform.h"
namespace tracy
{
uint32_t PlatformGetThreadId()
{
return 0;
}
void PlatformGetHostname( char* buf, size_t size )
{
const char* placeholder = "(?)";
if( size == 0 ) return;
const size_t n = strlen( placeholder );
const size_t copy = n < size - 1 ? n : size - 1;
memcpy( buf, placeholder, copy );
buf[copy] = '\0';
}
const char* PlatformGetUserLogin()
{
return "(?)";
}
const char* PlatformGetUserFullName()
{
return nullptr;
}
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size )
{
// Stub: report failure so Tracy skips the snapshot. Real impls use SEH
// on Win32, pipe(2) on POSIX, or an equivalent probe-and-copy primitive.
(void)dst; (void)src; (void)size;
return false;
}
// Stubs forward to the C runtime. Swap in the allocator you actually want.
void* PlatformMalloc( size_t size ) { return malloc( size ); }
void PlatformFree( void* ptr ) { free( ptr ); }
void* PlatformRealloc( void* ptr, size_t size ) { return realloc( ptr, size ); }
void PlatformAllocatorInit() {}
void PlatformAllocatorThreadInit() {}
void PlatformAllocatorFinalize() {}
void PlatformAllocatorThreadFinalize(){}
}

View File

@@ -0,0 +1,73 @@
// Template platform header for unsupported targets.
//
// Copy into your project, fill in the sections you need, and point Tracy at
// it via -DTRACY_PLATFORM_HEADER="\"my_platform.h\"". Provide the
// implementations in any TU linked into your final binary (see
// CustomPlatform.cpp).
//
// Use this only for the TRACY_HAS_CUSTOM_* hooks and matching Platform*
// declarations — don't set unrelated TRACY_* options here. Some are checked
// before this header is included, so the result would depend on which TU
// consulted them; set those at the build system level instead.
//
// For platform-specific features without a custom hook (call stacks,
// context switches, crash handling, system tracing, etc.), disable them at
// the build system level with the matching TRACY_NO_* macro.
#ifndef __MY_TRACY_PLATFORM_H__
#define __MY_TRACY_PLATFORM_H__
#include <stddef.h>
#include <stdint.h>
namespace tracy
{
// --- Thread id --------------------------------------------------------------
//
// Required if defaults in TracySystem.cpp do not matches your platform.
// Note pthread_self() is NOT suitable, it returns a library handle, not a kernel id.
//#define TRACY_HAS_CUSTOM_THREAD_ID
uint32_t PlatformGetThreadId();
// --- User info --------------------------------------------------------------
//
// Identifies the machine and user in the trace header. Return placeholder
// strings (e.g. "(?)") from any of these if your platform has no equivalent
// notion.
//#define TRACY_HAS_CUSTOM_USER_INFO
void PlatformGetHostname( char* buf, size_t size );
const char* PlatformGetUserLogin();
const char* PlatformGetUserFullName();
// --- Safe memory copy -------------------------------------------------------
//
// Tracy uses this to snapshot potentially-unmapped memory during sampling.
// Must not crash on unreadable input — return false instead. Plain memcpy()
// is NOT a valid implementation.
//#define TRACY_HAS_CUSTOM_SAFE_COPY
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size );
// --- Allocator --------------------------------------------------------------
//
// Replaces Tracy's internal allocator. Drop in the system allocator, an
// in-house one, or any third-party allocator you like. Malloc/Free/Realloc
// must be thread-safe; ThreadInit is an optional prime, not a precondition.
// Finalize must also tear down the calling thread's per-thread state, the
// way rpmalloc_finalize() does — Tracy does not call ThreadFinalize for the
// shutdown thread before Finalize.
//#define TRACY_HAS_CUSTOM_ALLOCATOR
void* PlatformMalloc( size_t size );
void PlatformFree( void* ptr );
void* PlatformRealloc( void* ptr, size_t size );
void PlatformAllocatorInit();
void PlatformAllocatorThreadInit();
void PlatformAllocatorFinalize();
void PlatformAllocatorThreadFinalize();
}
#endif

View File

@@ -8,7 +8,7 @@
#define kSimdWidth 4
#if !defined(__arm__) && !defined(__arm64__) && !defined(__EMSCRIPTEN__)
#if !defined(__arm__) && !defined(__arm64__) && !defined(__EMSCRIPTEN__) && !defined(_M_ARM64)
// ---- SSE implementation
@@ -141,8 +141,14 @@ VM_INLINE float hmin(float4 v)
// Returns a 4-bit code where bit0..bit3 is X..W
VM_INLINE unsigned mask(float4 v)
{
#if defined(_M_ARM64)
static const uint32_t values[4] = { 1u, 2u, 4u, 8u };
const uint32x4_t movemask = vld1q_u32( values );
const uint32x4_t highbit = vdupq_n_u32( 0x80000000u );
#else
static const uint32x4_t movemask = { 1, 2, 4, 8 };
static const uint32x4_t highbit = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 };
#endif
uint32x4_t t0 = vreinterpretq_u32_f32(v.m);
uint32x4_t t1 = vtstq_u32(t0, highbit);
uint32x4_t t2 = vandq_u32(t1, movemask);

View File

@@ -12,7 +12,7 @@
#if DO_FLOAT3_WITH_SIMD
#if !defined(__arm__) && !defined(__arm64__)
#if !defined(__arm__) && !defined(__arm64__) && !defined(_M_ARM64)
// ---- SSE implementation
@@ -223,8 +223,14 @@ VM_INLINE float3 cross(float3 a, float3 b)
// Returns a 3-bit code where bit0..bit2 is X..Z
VM_INLINE unsigned mask(float3 v)
{
#if defined(_M_ARM64)
static const uint32_t values[4] = { 1u, 2u, 4u, 8u };
const uint32x4_t movemask = vld1q_u32( values );
const uint32x4_t highbit = vdupq_n_u32( 0x80000000u );
#else
static const uint32x4_t movemask = { 1, 2, 4, 8 };
static const uint32x4_t highbit = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 };
#endif
uint32x4_t t0 = vreinterpretq_u32_f32(v.m);
uint32x4_t t1 = vtstq_u32(t0, highbit);
uint32x4_t t2 = vandq_u32(t1, movemask);

View File

@@ -72,7 +72,11 @@ namespace
#if defined _M_IX86 || defined _M_X64
#pragma intrinsic(_mm_pause)
inline void Pause() { _mm_pause(); }
#endif
#elif defined(_M_ARM64)
inline void Pause() { __yield(); }
#else
inline void Pause() { /* No ops*/ }
#endif
#elif defined __i386__ || defined __x86_64__
inline void Pause() { __asm__ __volatile__("pause;"); }
#else

0
examples/cuda/README.md Normal file
View File

View File

@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.18)
project(CUDAGraphDemo LANGUAGES CXX CUDA)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CUDA_STANDARD 17)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
set(CMAKE_CUDA_ARCHITECTURES native)
endif()
set(TRACY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../.."
CACHE PATH "Root of the Tracy repository")
set(TRACY_PUBLIC "${TRACY_PATH}/public")
find_package(CUDAToolkit REQUIRED)
find_package(Threads REQUIRED)
# cuda-graph-demo.cu embeds Tracy via #include <TracyClient.cpp> (unity build),
# so no separate TracyClient library is needed — just expose the public headers.
add_executable(cuda-graph-demo cuda-graph-demo.cu)
target_include_directories(cuda-graph-demo PRIVATE ${TRACY_PUBLIC})
target_link_libraries(cuda-graph-demo PRIVATE
CUDA::cupti CUDA::cuda_driver Threads::Threads ${CMAKE_DL_LIBS})
# ctest-related integration below
# to run the binaries via ctest:
# ctest --test-dir <cmake-build-dir> -R <binary-name> -C <build-config>
enable_testing()
add_test(NAME cuda-graph-demo COMMAND cuda-graph-demo)
# On Windows, CUPTI's DLL must be on PATH at runtime.
if(WIN32)
set(_cupti_dir "$<TARGET_FILE_DIR:CUDA::cupti>")
set_target_properties(cuda-graph-demo PROPERTIES
VS_DEBUGGER_ENVIRONMENT "PATH=${_cupti_dir};$ENV{PATH}")
set_tests_properties(cuda-graph-demo PROPERTIES
ENVIRONMENT "PATH=${_cupti_dir};$ENV{PATH}")
endif()

View File

@@ -0,0 +1,11 @@
TRACY_PATH=<path-to-tracy>
CUDA_TOOLKIT_PATH=/usr/local/cuda
CUDA_CUPTI_PATH=${CUDA_TOOLKIT_PATH}/extras/CUPTI
# pass -v to nvcc for verbose build information
nvcc -O2 -std=c++17 cuda-graph-demo.cu \
-o cuda-graph-demo \
-I "${TRACY_PATH}/public" \
-I "${CUDA_CUPTI_PATH}/include" -I "${CUDA_TOOLKIT_PATH}/include" \
-L "${CUDA_CUPTI_PATH}/lib64" -L "${CUDA_TOOLKIT_PATH}/lib64" \
-lcupti -lcuda

View File

@@ -0,0 +1,146 @@
#include <cuda_runtime.h>
// WARN: for simplicity, we enable and "embed" the Tracy client directly into the code
#define TRACY_ENABLE
#include <TracyClient.cpp>
#include <tracy/Tracy.hpp>
#include <tracy/TracyCUDA.hpp>
#include <cstdio>
#include <cstdlib>
#include <vector>
#define CUDA_CHECK(call) \
do { \
cudaError_t err__ = (call); \
if (err__ != cudaSuccess) { \
std::fprintf(stderr, "CUDA error %s at %s:%d: %s\n", \
cudaGetErrorName(err__), __FILE__, __LINE__, \
cudaGetErrorString(err__)); \
std::exit(EXIT_FAILURE); \
} \
} while (0)
__global__ void saxpy(float a, const float* x, float* y, int n)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) y[i] = a * x[i] + y[i];
}
int main()
{
// CUPTI-backed Tracy context. Auto-captures all CUDA activity from the
// point StartProfiling() is called until StopProfiling(). The background
// collector thread flushes activity into Tracy; the explicit Collect()
// calls below just force a flush at known phase boundaries.
auto* cudaCtx = TracyCUDAContext();
{
constexpr char ctxName[] = "CUDA Graph Demo";
TracyCUDAContextName(cudaCtx, ctxName, sizeof(ctxName) - 1);
}
TracyCUDAStartProfiling(cudaCtx);
constexpr int N = 1 << 16; // small N => kernel is short => launch overhead dominates
constexpr int KERNELS_PER_GRAPH = 32; // chain length captured into the graph
constexpr int OUTER_ITERS = 2000; // how many times we replay the chain
// allocate device buffers
float *dX = nullptr, *dY = nullptr;
CUDA_CHECK(cudaMalloc(&dX, N * sizeof(float)));
CUDA_CHECK(cudaMalloc(&dY, N * sizeof(float)));
std::vector<float> hX(N, 1.0f);
CUDA_CHECK(cudaMemcpy(dX, hX.data(), N * sizeof(float), cudaMemcpyHostToDevice));
cudaStream_t stream = nullptr;
CUDA_CHECK(cudaStreamCreate(&stream));
const dim3 block(256);
const dim3 grid((N + block.x - 1) / block.x);
cudaEvent_t evStart, evStop;
CUDA_CHECK(cudaEventCreate(&evStart));
CUDA_CHECK(cudaEventCreate(&evStop));
// warm-up (so first-launch lazy-init and/or JIT doesn't bias the measurement)
saxpy<<<grid, block, 0, stream>>>(0.0f, dX, dY, N);
CUDA_CHECK(cudaStreamSynchronize(stream));
// baseline: launch each kernel directly on the stream
float msStream = 0.0f;
{
ZoneScopedN("stream-launches");
CUDA_CHECK(cudaMemsetAsync(dY, 0, N * sizeof(float), stream));
CUDA_CHECK(cudaEventRecord(evStart, stream));
for (int outer = 0; outer < OUTER_ITERS; ++outer) {
for (int k = 0; k < KERNELS_PER_GRAPH; ++k) {
saxpy<<<grid, block, 0, stream>>>(1.0e-6f, dX, dY, N);
}
}
CUDA_CHECK(cudaEventRecord(evStop, stream));
CUDA_CHECK(cudaEventSynchronize(evStop));
CUDA_CHECK(cudaEventElapsedTime(&msStream, evStart, evStop));
TracyCUDACollect(cudaCtx);
}
// capture: record the same kernel chain into a graph
cudaGraph_t graph = nullptr;
cudaGraphExec_t graphExec = nullptr;
{
ZoneScopedN("graph-capture");
// cudaStreamCaptureModeRelaxed allows the calling thread to perform
// unrelated CUDA work during capture; ThreadLocal is stricter if you need
// isolation. Most short, single-stream captures work fine in either mode.
CUDA_CHECK(cudaStreamBeginCapture(stream, cudaStreamCaptureModeRelaxed));
for (int k = 0; k < KERNELS_PER_GRAPH; ++k) {
saxpy<<<grid, block, 0, stream>>>(1.0e-6f, dX, dY, N);
}
CUDA_CHECK(cudaStreamEndCapture(stream, &graph));
// Instantiate once -> reusable executable graph.
CUDA_CHECK(cudaGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
// The template graph isn't needed once instantiated.
CUDA_CHECK(cudaGraphDestroy(graph));
}
// replay: launch the instantiated graph OUTER_ITERS times
float msGraph = 0.0f;
{
ZoneScopedN("graph-launches");
CUDA_CHECK(cudaMemsetAsync(dY, 0, N * sizeof(float), stream));
CUDA_CHECK(cudaEventRecord(evStart, stream));
for (int outer = 0; outer < OUTER_ITERS; ++outer) {
CUDA_CHECK(cudaGraphLaunch(graphExec, stream));
}
CUDA_CHECK(cudaEventRecord(evStop, stream));
CUDA_CHECK(cudaEventSynchronize(evStop));
CUDA_CHECK(cudaEventElapsedTime(&msGraph, evStart, evStop));
TracyCUDACollect(cudaCtx);
}
// sanity check: y[i] = OUTER_ITERS * KERNELS_PER_GRAPH * 1e-6 * x[i]
std::vector<float> hY(N);
CUDA_CHECK(cudaMemcpy(hY.data(), dY, N * sizeof(float), cudaMemcpyDeviceToHost));
const float expected = float(OUTER_ITERS) * float(KERNELS_PER_GRAPH) * 1.0e-6f;
std::printf("Stream launches: %8.3f ms (%d kernels)\n",
msStream, OUTER_ITERS * KERNELS_PER_GRAPH);
std::printf("Graph launches: %8.3f ms (%d graph launches x %d kernels)\n",
msGraph, OUTER_ITERS, KERNELS_PER_GRAPH);
std::printf("Speedup : %8.2fx\n", msStream / msGraph);
std::printf("hY[0] = %.6e (expected %.6e)\n", hY[0], expected);
// shutdown
CUDA_CHECK(cudaGraphExecDestroy(graphExec));
CUDA_CHECK(cudaEventDestroy(evStart));
CUDA_CHECK(cudaEventDestroy(evStop));
CUDA_CHECK(cudaStreamDestroy(stream));
CUDA_CHECK(cudaFree(dX));
CUDA_CHECK(cudaFree(dY));
TracyCUDAStopProfiling(cudaCtx);
TracyCUDAContextDestroy(cudaCtx);
return 0;
}

View File

@@ -1,4 +1,4 @@
// g++ identify.cpp -lpthread ../public/common/tracy_lz4.cpp ../zstd/common/*.c ../zstd/decompress/*.c ../zstd/decompress/huf_decompress_amd64.S
// g++ identify.cpp -lpthread ../public/common/tracy_lz4.cpp -lzstd
#include <memory>
#include <stdint.h>
@@ -8,7 +8,7 @@
#include "../public/common/TracyVersion.hpp"
static const uint8_t FileHeader[8] { 't', 'r', 'a', 'c', 'y', tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch };
enum { FileHeaderMagic = 5 };
constexpr size_t FileHeaderMagic = 5;
int main( int argc, char** argv )
{

View File

@@ -1,26 +0,0 @@
#!/bin/sh
rm -rf tracy-build
mkdir tracy-build
./update-meson-version.sh
if [ ! -f vswhere.exe ]; then
wget https://github.com/microsoft/vswhere/releases/download/2.8.4/vswhere.exe
fi
MSVC=`./vswhere.exe -property installationPath -version '[17.0,17.999]' | head -n 1`
MSVC=`wslpath "$MSVC" | tr -d '\r'`
MSBUILD=$MSVC/MSBuild/Current/Bin/MSBuild.exe
for i in capture csvexport import-chrome update; do
echo $i...
"$MSBUILD" ../$i/build/win32/$i.sln /t:Clean /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
"$MSBUILD" ../$i/build/win32/$i.sln /t:Build /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
cp ../$i/build/win32/x64/Release/$i.exe tracy-build/
done
echo profiler...
"$MSBUILD" ../profiler/build/win32/Tracy.sln /t:Clean /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
"$MSBUILD" ../profiler/build/win32/Tracy.sln /t:Build /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
cp ../profiler/build/win32/x64/Release/Tracy.exe tracy-build/

3
extra/mcp/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
tracy_mcp.port
tracy_mcp.pid
*.local.sh

72
extra/mcp/eval_guide.md Normal file
View File

@@ -0,0 +1,72 @@
# Tracy MCP eval guide
This document covers the bindings-layer detail that the curated catalog
(`tracy://catalog`) and analysis guidance (`tracy://prompt`) do not.
## ctx
`ctx` is a `TracyServerBindings.Worker` — the same object Tracy Assist's
C++ tools query through `Worker::Get*`. The pybind methods are the canonical
data surface. Common entry points:
- Zones: `get_all_zone_stats()` (every callsite, large), `get_root_zone_stats()`
(top-level zones only, useful for "where is the program spending time"),
`get_zone_stats(srcloc_id)`, `get_child_zone_stats(srcloc_id)` (subtract for
self-time), `get_zone_durations(name)`, `get_zone_count()`,
`get_all_zone_source_locations()`
- GPU zones: `get_all_gpu_zone_stats()`, `get_gpu_zone_durations(...)`,
`get_gpu_contexts()`
- Frames: `get_frame_count()`, `get_frame_times()`, `get_frame_times_named(name)`,
`get_frame_boundaries()`, `get_zones_in_frame(...)`
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
## Units and conventions
- All time values returned by Worker methods are **nanoseconds** (int).
`get_first_time()` / `get_last_time()` bound the capture timeline.
- `ZoneStats` fields: `count`, `total`, `min`, `max`, `avg`, `sum_sq`. `total`
is the inclusive aggregate; use `get_child_zone_stats(srcloc_id)` to subtract
child time when you need self-time.
- `get_all_zone_stats()` returns `dict[str, ZoneStats]` keyed by an opaque label
of the form `'name (addr)[arch] <srcloc_id>'`. The trailing `<id>` is the
source-location ID — the int accepted by `get_zone_stats(int)`,
`get_zone_durations_by_id`, and friends. Parse it with a regex if you need
to join across calls.
- Source-location IDs from `get_all_zone_source_locations()` are the join key
between zone-name lookups and per-callsite queries.
## Translating catalog entries to ctx Python
The catalog (`tracy://catalog`) lists curated queries. Each maps to a small
Python snippet:
```python
# zone_list — top 10 hottest zones by total time
top = sorted(ctx.get_all_zone_stats().items(),
key=lambda kv: kv[1].total, reverse=True)[:10]
for k, v in top:
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
# frame_list — primary frame set timing
times = ctx.get_frame_times() # ns per frame
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
# zone_stats for a named zone — find the srcloc id, then drill in
import re
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))
stats = ctx.get_zone_stats(sid)
```
## Async mode
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=...`).

17
extra/mcp/start_mcp.sh Normal file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
# Start the Tracy MCP server.
#
# 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"
export PYTHONPATH
# Machine-local overrides (not committed). Create start_mcp.local.sh next to
# this file to set TRACY_CAPTURES_DIR, TRACY_MCP_PORT, or any other env var:
# export TRACY_CAPTURES_DIR=/path/to/captures
# export TRACY_MCP_PORT=47380
if [ -f "$(dirname "$0")/start_mcp.local.sh" ]; then
. "$(dirname "$0")/start_mcp.local.sh"
fi
exec python3 "$(dirname "$0")/tracy_mcp.py" "$@"

700
extra/mcp/tracy_mcp.py Normal file
View File

@@ -0,0 +1,700 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import asyncio
import atexit
import builtins
import concurrent.futures
import glob
import io
import os
import logging
import re
import socket
import struct
import sys
import time
import uuid
from contextlib import redirect_stdout
import mcp.server.fastmcp as fastmcp
# 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
# but after the SSE 200 OK headers have already been sent. Global level suppression
# is used because surgical filtering of ASGI exceptions is unreliable in this stack.
logging.getLogger("uvicorn.error").setLevel(logging.CRITICAL)
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")
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
# system prompt; exposing it as an MCP resource keeps analysis guidance in
# sync across both surfaces with no plumbing. eval_guide.md covers
# bindings-layer detail (ctx object model, units, source-location ID joins).
_LLM_DIR = os.path.normpath(os.path.join(_HERE, "..", "..", "profiler", "src", "llm"))
_PROMPT_PATH = os.path.join(_LLM_DIR, "system.prompt.md")
_EVAL_GUIDE_PATH = os.path.join(_HERE, "eval_guide.md")
def _read_text(path: str) -> str:
try:
with open(path, encoding="utf-8") as f:
return f.read()
except Exception as e:
return f"(unavailable: {e})"
# Tracy UDP broadcast packet support. Tracy clients announce themselves on
# port 8086 with a BroadcastMessage (see public/common/TracyProtocol.hpp).
# The dev GUI reads protocolVersion from the broadcast and refuses connection
# on mismatch instead of hitting an opaque TCP timeout. We do the same.
_PROTOCOL_HPP = os.path.normpath(
os.path.join(_HERE, "..", "..", "public", "common", "TracyProtocol.hpp")
)
_BROADCAST_PORT = 8086
_PROGRAM_NAME_SIZE = 64
def _read_bindings_protocol_version() -> int | None:
"""Parse ProtocolVersion from TracyProtocol.hpp at startup so our 'expected'
version stays in sync with the bindings build without extra C++ wiring."""
try:
with open(_PROTOCOL_HPP, encoding="utf-8") as f:
for line in f:
m = re.search(r"constexpr\s+uint32_t\s+ProtocolVersion\s*=\s*(\d+)", line)
if m:
return int(m.group(1))
except Exception:
pass
return None
_OUR_PROTOCOL_VERSION = _read_bindings_protocol_version()
def _parse_broadcast(data: bytes) -> dict | None:
"""Parse a Tracy BroadcastMessage. Handles broadcast versions 0-3.
Fixed-field sizes (from TracyProtocol.hpp, packed):
v3: u16 bv, u16 lp, u32 pv, u64 pid, i32 at, char[<=64] name (>=20 + name)
v2: u16 bv, u16 lp, u32 pv, i32 at, char[<=64] name (>=12 + name)
v1: u32 bv, u32 pv, u32 lp, u32 at, char[<=64] name (>=16 + name)
v0: u32 bv, u32 pv, u32 at, char[<=64] name (>=12 + name)
The programName field is variable-length on the wire — the sender writes
only the actual name plus null terminator, not the full 64-byte buffer.
"""
if len(data) < 4:
return None
def _name(buf: bytes) -> str:
return buf[:_PROGRAM_NAME_SIZE].split(b"\0", 1)[0].decode("utf-8", "replace")
bv16 = struct.unpack_from("<H", data, 0)[0]
if bv16 == 3 and len(data) >= 21:
bv, lp, pv, pid, at = struct.unpack_from("<HHIQi", data, 0)
return {"broadcast_version": bv, "listen_port": lp,
"protocol_version": pv, "pid": pid,
"active_seconds": at, "program": _name(data[20:])}
if bv16 == 2 and len(data) >= 13:
bv, lp, pv, at = struct.unpack_from("<HHIi", data, 0)
return {"broadcast_version": bv, "listen_port": lp,
"protocol_version": pv, "active_seconds": at,
"program": _name(data[12:])}
bv32 = struct.unpack_from("<I", data, 0)[0]
if bv32 == 1 and len(data) >= 17:
bv, pv, lp, at = struct.unpack_from("<IIII", data, 0)
return {"broadcast_version": bv, "listen_port": lp,
"protocol_version": pv, "active_seconds": at,
"program": _name(data[16:])}
if bv32 == 0 and len(data) >= 13:
bv, pv, at = struct.unpack_from("<III", data, 0)
return {"broadcast_version": bv, "listen_port": None,
"protocol_version": pv, "active_seconds": at,
"program": _name(data[12:])}
return None
async def _listen_broadcasts(timeout_s: float = 1.5) -> list[dict]:
"""Listen briefly on UDP 8086 for Tracy client announcements.
Returns a list of parsed broadcasts (deduplicated by listen_port). Empty
list means no broadcast received — the target may use TRACY_ON_DEMAND,
a non-default broadcast port, or simply isn't running.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
s.bind(("", _BROADCAST_PORT))
except OSError:
s.close()
return []
s.setblocking(False)
loop = asyncio.get_running_loop()
seen: dict[int | None, dict] = {}
deadline = loop.time() + timeout_s
try:
while loop.time() < deadline:
remaining = deadline - loop.time()
if remaining <= 0:
break
try:
fut = loop.sock_recvfrom(s, 2048)
data, _addr = await asyncio.wait_for(fut, timeout=remaining)
except (asyncio.TimeoutError, BlockingIOError):
break
parsed = _parse_broadcast(data)
if parsed:
seen.setdefault(parsed.get("listen_port"), parsed)
finally:
s.close()
return list(seen.values())
def _is_our_server_running() -> tuple[bool, int]:
"""
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.
"""
try:
with open(_PID_FILE) as f:
pid = int(f.read().strip())
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
def _find_free_port() -> int:
"""Scan from preferred port upward; fall back to OS-assigned if the range is exhausted."""
for port in range(_PREFERRED_PORT, _PREFERRED_PORT + 16):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
s.bind(("127.0.0.1", port))
s.close()
return port
except OSError:
s.close()
# Let OS assign any free port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
s.close()
return port
def _write_pid_and_port(port: int) -> None:
try:
with open(_PID_FILE, "w") as f:
f.write(str(os.getpid()))
with open(_PORT_FILE, "w") as f:
f.write(str(port))
except Exception:
pass
def _cleanup_pid_files() -> None:
for path in (_PID_FILE, _PORT_FILE):
try:
os.unlink(path)
except Exception:
pass
# Attempt to import Tracy Server bindings
try:
import TracyServerBindings as tracy_server
except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), "../../build/python"))
try:
import TracyServerBindings as tracy_server
except ImportError:
tracy_server = None
mcp_server = fastmcp.FastMCP("Tracy Profiler")
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
class Task:
def __init__(self, task_id: str, code: str):
self.id = task_id
self.code = code
self.status = "pending"
self.result = None
self.error = None
self.start_time = time.time()
self.end_time = None
class TracyInstance:
def __init__(self, name: str, worker: object | None = None):
self.name = name
self.worker = worker
self.path = None
self.mtime = None
instances: dict[str, TracyInstance] = {}
tasks: dict[str, Task] = {}
captures_dir: str | None = os.environ.get("TRACY_CAPTURES_DIR")
@mcp_server.resource("tracy://prompt")
def _prompt_resource() -> str:
"""Tracy Assist's analysis guidance (system.prompt.md). Contains workflows
for optimization, callstack inspection, and privacy rules. %TIME%, %USER%,
and %PROGRAMNAME% are placeholders filled by the in-app chat — ignore them
when reading from MCP."""
return _read_text(_PROMPT_PATH)
@mcp_server.resource("tracy://eval-guide")
def _eval_guide_resource() -> str:
"""Bindings-layer guide for the eval tool: ctx object model, time units,
source-location ID semantics, and worked examples translating catalog
entries into ctx Python."""
return _read_text(_EVAL_GUIDE_PATH)
@mcp_server.tool()
async def list_captures() -> list[str]:
"""List .tracy capture files in the TRACY_CAPTURES_DIR directory (non-recursive)."""
if not captures_dir:
return []
return sorted(glob.glob(os.path.join(captures_dir, "*.tracy")))
@mcp_server.tool()
async def list_instances() -> list[dict]:
"""List all loaded Tracy instances and captures with metadata."""
return [
{
"id": name,
"path": inst.path,
"mtime": inst.mtime,
"live": inst.path is None
}
for name, inst in instances.items()
]
@mcp_server.tool()
async def discover_instances(port_range: str = "8086-8095") -> list[dict]:
"""
Scan for running Tracy-instrumented applications on local ports.
Returns a list of discovered ports that are listening.
"""
start_port, end_port = map(int, port_range.split("-"))
discovered = []
async def check_port(port: int) -> None:
try:
_, writer = await asyncio.wait_for(
asyncio.open_connection("127.0.0.1", port), timeout=0.1
)
writer.close()
await writer.wait_closed()
discovered.append({"port": port, "address": "127.0.0.1"})
except (OSError, asyncio.TimeoutError, ConnectionRefusedError):
pass
await asyncio.gather(*(check_port(p) for p in range(start_port, end_port + 1)))
return discovered
@mcp_server.tool()
async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str | None = None) -> str:
"""
Connect to a live running Tracy-instrumented application.
Wraps Worker(addr, port, memoryLimit=-1). Returns the instance_id.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
# Pre-flight: read Tracy's UDP broadcast on port 8086 and compare protocol
# versions before attempting TCP. Mirrors what the Tracy GUI does so a
# version mismatch produces a precise error instead of an opaque timeout.
# Tracy clients broadcast every ~3s (TracyProfiler.cpp), so we listen a
# little longer to guarantee catching at least one beat.
broadcasts = await _listen_broadcasts(timeout_s=3.5)
match = next((b for b in broadcasts if b.get("listen_port") == port), None)
if match and _OUR_PROTOCOL_VERSION is not None:
if match["protocol_version"] != _OUR_PROTOCOL_VERSION:
return (
f"Protocol mismatch: target program '{match['program']}' "
f"announces Tracy protocol v{match['protocol_version']} on "
f"{address}:{port}, but these server bindings are built "
f"against v{_OUR_PROTOCOL_VERSION}. Rebuild the bindings or "
f"the target against a matching Tracy version."
)
try:
w = tracy_server.Worker(address, port)
except Exception as e:
return f"Failed to connect: {str(e)}"
# Worker construction returns immediately even on protocol failure (the
# bindings expose no error state — is_connected() is the only signal).
# Probe briefly so silent failures (e.g. TRACY_ON_DEMAND with no profiler
# request yet, or a target broadcasting on a non-default port) surface
# cleanly even when broadcast pre-flight didn't catch them.
deadline_s = 2.0
step_s = 0.1
elapsed = 0.0
while elapsed < deadline_s and not w.is_connected():
await asyncio.sleep(step_s)
elapsed += step_s
if not w.is_connected():
try:
w.shutdown()
except Exception:
pass
hint = ""
if broadcasts and not match:
seen = ", ".join(
f"'{b['program']}' on port {b.get('listen_port')} (protocol v{b['protocol_version']})"
for b in broadcasts
)
hint = f" Detected other Tracy broadcasts: {seen}."
elif not broadcasts:
hint = (
" No Tracy broadcasts were received on port 8086 in 3.5s — "
"the target may use TRACY_ON_DEMAND, a non-default broadcast "
"port, or isn't running."
)
return (
f"Reached {address}:{port} but the Tracy handshake did not complete "
f"within {deadline_s:.1f}s.{hint} Common causes: (1) the Tracy "
f"client version embedded in the target program differs from these "
f"server bindings; (2) the target was built with TRACY_ON_DEMAND "
f"and is awaiting a profiler request; (3) another client is "
f"already attached."
)
name = alias or f"live_{address}_{port}"
instances[name] = TracyInstance(name, w)
return (
f"Connected to live instance as '{name}'. "
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)."
)
@mcp_server.tool()
async def load_capture(path: str, alias: str | None = None) -> str:
"""
Load a .tracy capture file by absolute path.
Parameters:
path — absolute path to a .tracy file. On Windows use backslashes
(e.g. 'E:\\\\traces\\\\foo.tracy').
alias — optional instance name; overwrites existing on collision.
If omitted, an ID is derived from filename and mtime.
If you don't already have a path, call `list_captures` first — it lists
.tracy files in the TRACY_CAPTURES_DIR environment directory.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
try:
mtime = os.path.getmtime(path)
if alias:
name = alias
else:
# unique name including mtime to avoid version collision
name = f"{os.path.basename(path)}@{int(mtime):x}"
if name in instances:
inst = instances[name]
if inst.path == path and inst.mtime == mtime:
return f"Instance '{name}' is already loaded and up to date."
f = tracy_server.open_file(path)
w = tracy_server.create_worker_from_file(f)
inst = TracyInstance(name, w)
inst.path = path
inst.mtime = mtime
instances[name] = inst
return (
f"Loaded as '{name}'. "
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)."
)
except Exception as e:
return f"Failed to load: {str(e)}"
@mcp_server.tool()
async def save_trace(
instance_id: str,
path: str,
level: int = 3,
streams: int = 4,
fi_dict: bool = False,
overwrite: bool = False,
async_mode: bool = True,
) -> object:
"""
Snapshot a Tracy instance (live or loaded) to a .tracy file.
Wraps `Worker::Write` under the main-thread data lock — safe for live
instances; the receive thread yields cooperatively for the duration of
the write. Concurrent `eval` calls against the same live instance may
stall until the save completes.
Parameters:
instance_id — name returned by live_connect / load_capture.
path — output file. Absolute paths are used as-is; a bare
filename is resolved under TRACY_CAPTURES_DIR if set.
On Windows use backslashes (e.g. 'E:\\\\traces\\\\a.tracy').
level — Zstd compression level (default 3, matches capture tool).
streams — number of compression streams (default 4).
fi_dict — rebuild frame-image dedup dictionary on save. Only
meaningful for traces containing screenshots; default
False matches the capture tool and GUI default.
overwrite — refuse to clobber an existing file unless True.
async_mode — default True; large traces take seconds-to-minutes.
Returns {task_id, status: "running"}; poll with `task`.
On success returns a dict with path, uncompressed_bytes, compressed_bytes,
ratio, and elapsed_ms — the same numbers the capture tool prints.
"""
if instance_id not in instances:
return f"Error: Instance '{instance_id}' not found. Use list_instances to find valid IDs."
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
if not os.path.isabs(path):
if captures_dir and os.path.basename(path) == path:
path = os.path.join(captures_dir, path)
else:
return (
f"Error: '{path}' is not absolute. Pass a full path, or a bare "
f"filename with TRACY_CAPTURES_DIR set (currently "
f"{captures_dir!r})."
)
if not path.endswith(".tracy"):
path += ".tracy"
if os.path.exists(path) and not overwrite:
return (
f"Error: '{path}' already exists. Pass overwrite=True to clobber, "
f"or choose a different path."
)
if not async_mode:
return await _execute_save(instance.worker, path, level, streams, fi_dict)
task_id = str(uuid.uuid4())
t = Task(task_id, f"save_trace({instance_id} -> {path})")
tasks[task_id] = t
asyncio.get_running_loop().run_in_executor(
executor, _run_save_task_sync, t, instance.worker, path, level, streams, fi_dict
)
return {"task_id": task_id, "status": "running"}
def _save_worker_sync(worker: object, path: str, level: int, streams: int, fi_dict: bool) -> dict:
t0 = time.time()
uncompressed, compressed = tracy_server.save_worker(
worker, path, level, streams, fi_dict
)
elapsed_ms = int((time.time() - t0) * 1000)
ratio = (compressed / uncompressed) if uncompressed else 0.0
return {
"path": path,
"uncompressed_bytes": uncompressed,
"compressed_bytes": compressed,
"ratio": ratio,
"elapsed_ms": elapsed_ms,
}
def _run_save_task_sync(t: Task, worker: object, path: str, level: int, streams: int, fi_dict: bool) -> None:
t.status = "running"
try:
t.result = _save_worker_sync(worker, path, level, streams, fi_dict)
t.status = "completed"
except Exception as e:
t.error = str(e)
t.status = "failed"
finally:
t.end_time = time.time()
async def _execute_save(worker: object, path: str, level: int, streams: int, fi_dict: bool) -> dict:
return await asyncio.get_running_loop().run_in_executor(
executor, _save_worker_sync, worker, path, level, streams, fi_dict
)
@mcp_server.tool()
async def unload_capture(instance_id: str) -> str:
"""Unload a Tracy instance and release its memory."""
if instance_id in instances:
del instances[instance_id]
return f"Instance '{instance_id}' unloaded."
return f"Instance '{instance_id}' not found."
@mcp_server.tool(name="eval")
async def tracy_eval(code: str, instance_id: str, async_mode: bool = False) -> object:
"""
Execute Python code against a specific Tracy Worker bound as `ctx`.
On first use, read the `tracy://prompt` (analysis guidance) and
`tracy://eval-guide` (ctx object model, units, source-location ID joins)
resources. Time values returned by Worker methods are nanoseconds.
If async_mode=True, returns a task_id immediately; poll via the `task` tool.
"""
if instance_id not in instances:
return f"Error: Instance '{instance_id}' not found. Use list_instances to find valid IDs."
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
if not async_mode:
return await _execute_eval(code, instance.worker)
# Async mode: spawn task and return immediately
task_id = str(uuid.uuid4())
task = Task(task_id, code)
tasks[task_id] = task
asyncio.get_running_loop().run_in_executor(
executor, _run_task_sync, task, instance.worker
)
return {"task_id": task_id, "status": "running"}
def _run_task_sync(task: Task, worker: object) -> None:
"""Run a background eval task in the thread pool."""
task.status = "running"
try:
task.result = _execute_eval_sync(task.code, worker)
task.status = "completed"
except Exception as e:
task.error = str(e)
task.status = "failed"
finally:
task.end_time = time.time()
def _execute_eval_sync(code: str, ctx: object) -> str:
"""Execute *code* with `ctx` bound to the Tracy worker. Captures stdout."""
global_vars = {
"__builtins__": builtins,
"ctx": ctx,
"tracy": tracy_server,
"instances": {name: inst.worker for name, inst in instances.items()},
}
buf = io.StringIO()
with redirect_stdout(buf):
try:
result = eval(compile(code, "<eval>", "eval"), global_vars)
except SyntaxError:
exec(compile(code, "<exec>", "exec"), global_vars)
result = None
output = buf.getvalue()
if result is None:
return output or ""
return str(result)
async def _execute_eval(code: str, ctx: object) -> str:
"""Async wrapper: runs `_execute_eval_sync` in the thread-pool executor."""
return await asyncio.get_running_loop().run_in_executor(
executor, _execute_eval_sync, code, ctx
)
@mcp_server.tool()
async def task(action: str, task_id: str | None = None) -> object:
"""
Manage background analysis tasks.
Actions: poll, cancel, list
"""
if action == "list":
return [
{"id": t.id, "status": t.status, "elapsed": time.time() - t.start_time}
for t in tasks.values()
]
if not task_id or task_id not in tasks:
return "Error: Task ID not found."
t = tasks[task_id]
if action == "poll":
res: dict = {"id": t.id, "status": t.status}
if t.status == "completed":
res["result"] = t.result
elif t.status == "failed":
res["error"] = t.error
return res
if action == "cancel":
# Cancellation of thread-pool work is not possible post-submission;
# mark the task so callers know it was abandoned.
if t.status == "running":
t.status = "cancelled"
return f"Task {task_id} marked as cancelled."
return f"Task {task_id} is not running."
return "Error: Unknown action."
@mcp_server.tool()
async def shutdown_server() -> str:
"""
Shut down the Tracy MCP server.
Because the server runs as a singleton (SSE transport, one process shared
across all VS Code windows), this releases the TracyServerBindings.pyd lock
for all clients at once. Restart tracy_mcp.py after rebuilding.
"""
import threading
def _exit() -> None:
time.sleep(0.2)
os._exit(0)
threading.Thread(target=_exit, daemon=True).start()
return "Server shutting down. Restart tracy_mcp.py to reconnect."
if __name__ == "__main__":
atexit.register(_cleanup_pid_files)
running, existing_port = _is_our_server_running()
if running:
print(
f"Tracy MCP already running on port {existing_port}. "
"All VS Code windows share that instance.",
file=sys.stderr,
)
sys.exit(0)
port = _find_free_port()
_write_pid_and_port(port)
print(f"Tracy MCP listening on http://127.0.0.1:{port}/sse", file=sys.stderr)
mcp_server.settings.host = "127.0.0.1"
mcp_server.settings.port = port
try:
mcp_server.run(transport="sse")
except KeyboardInterrupt:
print("\nTracy MCP server stopped.", file=sys.stderr)
sys.exit(0)

View File

@@ -1,7 +1,6 @@
cmake_minimum_required(VERSION 3.16)
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
option(NO_STATISTICS "Disable calculation of statistics" ON)
set(NO_STATISTICS ON)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
@@ -16,15 +15,18 @@ project(
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)
add_executable(tracy-import-chrome
src/import-chrome.cpp
)
add_git_ref(tracy-import-chrome)
target_link_libraries(tracy-import-chrome PRIVATE TracyServer nlohmann_json::nlohmann_json)
add_executable(tracy-import-fuchsia
src/import-fuchsia.cpp
)
add_git_ref(tracy-import-fuchsia)
target_link_libraries(tracy-import-fuchsia PRIVATE TracyServer)
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})

View File

@@ -22,11 +22,14 @@
#include "../../server/TracyFileWrite.hpp"
#include "../../server/TracyMmap.hpp"
#include "../../server/TracyWorker.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
using json = nlohmann::json;
void Usage()
{
printf( "tracy-import-chrome %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
printf( "Usage: import-chrome input.json output.tracy\n\n" );
printf( "The following chrome-tracing phases are supported:\n\n" );
printf( " b/B/e/E - Timeline events such as ZoneNamed\n" );
@@ -89,7 +92,7 @@ int main( int argc, char** argv )
auto zctx = ZSTD_createDStream();
ZSTD_initDStream( zctx );
enum { tmpSize = 64*1024 };
constexpr size_t tmpSize = 64*1024;
auto tmp = new char[tmpSize];
ZSTD_inBuffer_s zin = { zbuf, (size_t)zsz };

View File

@@ -30,8 +30,11 @@
#include "../../server/TracyFileWrite.hpp"
#include "../../server/TracyMmap.hpp"
#include "../../server/TracyWorker.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
void Usage() {
printf("tracy-import-fuchsia %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef);
printf("Usage: import-fuchsia input.json output.tracy\n\n");
printf("See: "
"https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format\n\n");
@@ -133,7 +136,7 @@ std::vector<uint8_t> read_input(const char *input) {
auto zctx = ZSTD_createDStream();
ZSTD_initDStream(zctx);
enum { tmpSize = 64 * 1024 };
constexpr size_t tmpSize = 64 * 1024;
auto tmp = new char[tmpSize];
ZSTD_inBuffer_s zin = {zbuf, (size_t)zsz};

1
manual/README Normal file
View File

@@ -0,0 +1 @@
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.

35
manual/bclogo2quote.awk Normal file
View File

@@ -0,0 +1,35 @@
/\\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 }

64
manual/fa-icons.py Normal file
View File

@@ -0,0 +1,64 @@
#!/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

@@ -3,3 +3,151 @@ function Link(el)
el.attributes['reference'] = nil
return el
end
-- Drop Div wrappers (e.g. table/titlepage containers), keeping their content.
function Div(el)
return el.content
end
-- ---------------------------------------------------------------------------
-- LaTeX math -> plain-text approximation.
--
-- The target Markdown renderer has no math support, so a raw "$\frac{1}{2}$"
-- would show verbatim. We turn each math node into the closest Unicode/ASCII
-- equivalent: fractions become "a/b", \times becomes "x", super/subscripts use
-- Unicode digits, and the one multi-line display equation becomes a fenced
-- code block (Markdown collapses plain newlines, a code block keeps them).
-- ---------------------------------------------------------------------------
local sup = {['0']='',['1']='¹',['2']='²',['3']='³',['4']='',['5']='',
['6']='',['7']='',['8']='',['9']='',['+']='',['-']='',
['=']='',['(']='',[')']=''}
local sub = {['0']='',['1']='',['2']='',['3']='',['4']='',['5']='',
['6']='',['7']='',['8']='',['9']='',['+']='',['-']='',
['=']='',['(']='',[')']=''}
-- Symbol replacements, applied as literal substitutions. Longer commands must
-- precede those that are a prefix of them (e.g. \rightarrow before \right).
local symbols = {
{'\\leftrightarrow',''}, {'\\rightarrow',''}, {'\\leftarrow',''},
{'\\Rightarrow',''}, {'\\Leftarrow',''}, {'\\to',''}, {'\\mapsto',''},
{'\\times','×'}, {'\\cdot','·'}, {'\\div','÷'}, {'\\ast','*'}, {'\\star','*'},
{'\\leq',''}, {'\\geq',''}, {'\\neq',''}, {'\\approx',''}, {'\\equiv',''},
{'\\ll','«'}, {'\\gg','»'}, {'\\le',''}, {'\\ge',''},
{'\\ldots',''}, {'\\cdots',''}, {'\\dots',''}, {'\\infty',''},
{'\\pm','±'}, {'\\mp',''}, {'\\propto',''}, {'\\sum','Σ'}, {'\\prod','Π'},
{'\\alpha','α'}, {'\\beta','β'}, {'\\gamma','γ'}, {'\\delta','δ'}, {'\\Delta','Δ'},
{'\\mu','µ'}, {'\\sigma','σ'}, {'\\pi','π'}, {'\\lambda','λ'}, {'\\theta','θ'},
{'\\left',''}, {'\\right',''},
{'\\qquad',' '}, {'\\quad',' '}, {'\\,',' '}, {'\\;',' '}, {'\\:',' '},
{'\\ ',' '}, {'\\!',''},
{'\\%','%'}, {'\\#','#'}, {'\\&','&'}, {'\\_','_'}, {'\\{','{'}, {'\\}','}'},
{'\\$','$'},
}
-- Literal (non-pattern) string replacement; avoids Lua pattern magic in keys.
local function lit_replace(s, a, b)
local out, i = {}, 1
while true do
local p = s:find(a, i, true)
if not p then out[#out + 1] = s:sub(i); break end
out[#out + 1] = s:sub(i, p - 1)
out[#out + 1] = b
i = p + #a
end
return table.concat(out)
end
-- Strip the outer braces of a "%b{}" capture.
local function grp(b) return b:sub(2, #b - 1) end
-- Map a string to Unicode super/subscript, or nil if any char is unsupported.
local function map_script(txt, map)
local res = {}
for i = 1, #txt do
local c = txt:sub(i, i)
if not map[c] then return nil end
res[#res + 1] = map[c]
end
return table.concat(res)
end
local function convert(s)
-- Text/font wrappers: keep the content, recurse to handle nesting.
for _, cmd in ipairs({'text', 'mathrm', 'mathit', 'mathbf', 'mathbb',
'mathsf', 'mathtt', 'mathcal', 'operatorname',
'textbf', 'textit', 'textrm'}) do
s = s:gsub('\\' .. cmd .. '(%b{})', function(b) return convert(grp(b)) end)
end
-- Fractions -> "num/den" (spaced when either side has spaces).
local function frac(a, b)
local n, d = convert(grp(a)), convert(grp(b))
local sep = (n:find(' ', 1, true) or d:find(' ', 1, true)) and ' / ' or '/'
return n .. sep .. d
end
s = s:gsub('\\frac(%b{})(%b{})', frac)
s = s:gsub('\\dfrac(%b{})(%b{})', frac)
s = s:gsub('\\tfrac(%b{})(%b{})', frac)
s = s:gsub('\\sfrac(%b{})(%b{})', frac)
-- Roots.
s = s:gsub('\\sqrt(%b{})', function(b) return '√(' .. convert(grp(b)) .. ')' end)
-- Single-char scripts first, so the braced fallback (e.g. "_native") below
-- is not re-scanned and mangled into Unicode subscripts.
s = s:gsub('%^([%w])', function(c) return sup[c] or ('^' .. c) end)
s = s:gsub('_([%w])', function(c) return sub[c] or ('_' .. c) end)
-- Braced scripts: Unicode when the content is all digits/signs, else keep
-- a readable "^(...)" / "_..." form.
s = s:gsub('%^(%b{})', function(b)
local inner = convert(grp(b))
return map_script(inner, sup) or ('^(' .. inner .. ')')
end)
s = s:gsub('_(%b{})', function(b)
local inner = convert(grp(b))
return map_script(inner, sub) or ('_' .. inner)
end)
-- Remaining symbols.
for _, pair in ipairs(symbols) do s = lit_replace(s, pair[1], pair[2]) end
return s
end
-- Convert a display equation, preserving its line structure for a code block.
local function convert_display(s)
s = convert(s)
for _, env in ipairs({'cases', 'aligned', 'align', 'array', 'matrix',
'gathered', 'split'}) do
s = lit_replace(s, '\\begin{' .. env .. '}', '')
s = lit_replace(s, '\\end{' .. env .. '}', '')
end
s = lit_replace(s, '\\\\', '\n') -- row break
s = s:gsub('%s*&%s*', ' ') -- column separator -> spacing
local lines = {}
for line in (s .. '\n'):gmatch('(.-)\n') do
line = line:gsub('^%s+', ''):gsub('%s+$', '')
if line ~= '' then lines[#lines + 1] = line end
end
for i = 2, #lines do lines[i] = ' ' .. lines[i] end -- indent continuations
return table.concat(lines, '\n')
end
function Math(el)
if el.mathtype == 'DisplayMath' then
return el -- handled at block level by Para, to emit a code block
end
return pandoc.Str(convert(el.text))
end
-- A paragraph that is solely a display equation becomes a fenced code block.
function Para(el)
local maths, only_math = {}, true
for _, x in ipairs(el.content) do
if x.t == 'Math' and x.mathtype == 'DisplayMath' then
maths[#maths + 1] = x
elseif x.t ~= 'Space' and x.t ~= 'SoftBreak' and x.t ~= 'LineBreak' then
only_math = false
end
end
if #maths == 0 or not only_math then return nil end
local parts = {}
for _, m in ipairs(maths) do parts[#parts + 1] = convert_display(m.text) end
return pandoc.CodeBlock(table.concat(parts, '\n\n'))
end

77
manual/icon-explain.py Normal file
View File

@@ -0,0 +1,77 @@
#!/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()

View File

@@ -7,20 +7,45 @@ 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
sed -i -e 's@\\fa\([a-zA-Z]*\)@(\1~icon)@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
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -s _tmp.tex -o tracy.md
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

16
manual/tablecaption.awk Normal file
View File

@@ -0,0 +1,16 @@
# 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

File diff suppressed because it is too large Load Diff

25
merge/CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.16)
set(NO_STATISTICS OFF) # we need those to get processed source zone locations
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
set(CMAKE_CXX_STANDARD 20)
project(
tracy-merge
LANGUAGES C CXX
VERSION ${TRACY_VERSION_STRING}
)
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)
add_executable(${PROJECT_NAME} src/merge.cpp)
add_git_ref(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

394
merge/src/merge.cpp Normal file
View File

@@ -0,0 +1,394 @@
#include "TracyFileRead.hpp"
#include "TracyFileWrite.hpp"
#include "TracyPrint.hpp"
#include "TracyWorker.hpp"
#include "../../getopt/getopt.h"
#include "../../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
template<typename T1, typename T2>
struct PairHash
{
size_t operator()( std::pair<T1, T2> const& p ) const
{
auto h1 = std::hash<T1>{}( p.first );
auto h2 = std::hash<T2>{}( p.second );
return h1 ^ (h2 << 1);
}
};
using namespace std::chrono_literals;
struct ExportedTrace
{
uint64_t pid = 0;
std::string process;
std::string name;
std::vector<tracy::Worker::ImportEventTimeline> timeline;
std::vector<tracy::Worker::ImportEventMessages> messages;
std::vector<tracy::Worker::ImportEventPlots> plots;
std::unordered_map<uint64_t, std::string> threadNames;
static bool orderTimeline( tracy::Worker::ImportEventTimeline const& a, tracy::Worker::ImportEventTimeline const& b )
{
return a.timestamp < b.timestamp;
}
static bool orderMessages( tracy::Worker::ImportEventMessages const& a, tracy::Worker::ImportEventMessages const& b )
{
return a.timestamp < b.timestamp;
}
static std::optional<ExportedTrace> fromFile( std::string const& filepath, size_t fileIndex )
{
auto sourceFile = std::unique_ptr<tracy::FileRead>( tracy::FileRead::Open( filepath.c_str() ) );
if( !sourceFile )
{
std::cerr << "Could not open file: " << filepath << std::endl;
return std::nullopt;
}
std::cout << "Reading: " << filepath << std::endl;
tracy::Worker worker( *sourceFile, tracy::EventType::All, true, false );
while( !worker.AreSourceLocationZonesReady() )
{
std::this_thread::sleep_for( 1s );
}
ExportedTrace trace;
trace.pid = worker.GetPid();
if( trace.pid == 0 )
{
trace.pid = 0xFFFE0000 | fileIndex;
}
trace.process = worker.GetCaptureProgram();
if( trace.process.empty() )
{
trace.process = "unknown";
}
trace.name = worker.GetCaptureName();
std::cout << " PID: " << trace.pid << ", Process: " << trace.process << std::endl;
std::unordered_set<uint64_t> seenThreads;
auto& sourceLocationZones = worker.GetSourceLocationZones();
std::cout << " Zones: " << sourceLocationZones.size() << std::endl;
for( auto& zone_it : sourceLocationZones )
{
const tracy::SourceLocation& srcLoc = worker.GetSourceLocation( zone_it.first );
std::string zoneFile = worker.GetString( srcLoc.file );
int zoneLine = srcLoc.line;
std::string zoneName = worker.GetZoneName( srcLoc );
for( auto& zoneData : zone_it.second.zones )
{
const auto zone = zoneData.Zone();
const uint64_t threadId = worker.DecompressThread( zoneData.Thread() );
seenThreads.insert( threadId );
auto& startEvent = trace.timeline.emplace_back();
startEvent.locFile = zoneFile;
startEvent.locLine = zoneLine;
startEvent.name = zoneName;
startEvent.tid = threadId;
startEvent.isEnd = false;
startEvent.timestamp = zone->Start();
auto& endEvent = trace.timeline.emplace_back();
endEvent.locFile = zoneFile;
endEvent.locLine = zoneLine;
endEvent.name = zoneName;
endEvent.tid = threadId;
endEvent.isEnd = true;
endEvent.timestamp = zone->End();
}
}
std::sort( trace.timeline.begin(), trace.timeline.end(), orderTimeline );
auto& messages = worker.GetMessages();
std::cout << " Messages: " << messages.size() << std::endl;
for( auto& msg : messages )
{
auto& importMsg = trace.messages.emplace_back();
importMsg.tid = worker.DecompressThread( msg->thread );
importMsg.message = worker.GetString( msg->ref );
importMsg.timestamp = msg->time;
seenThreads.insert( importMsg.tid );
}
std::sort( trace.messages.begin(), trace.messages.end(), orderMessages );
auto& plots = worker.GetPlots();
std::cout << " Plots: " << plots.size() << std::endl;
for( auto& plot : plots )
{
auto& importPlot = trace.plots.emplace_back();
importPlot.name = worker.GetString( plot->name );
importPlot.format = plot->format;
importPlot.data.reserve( plot->data.size() );
for( auto& pt : plot->data )
{
importPlot.data.emplace_back( pt.time.Val(), pt.val );
}
}
for( uint64_t tid : seenThreads )
{
std::string name = worker.GetThreadName( tid );
trace.threadNames[tid] = name.empty() ? std::to_string( tid ) : name;
}
return trace;
}
};
struct MergedTrace
{
std::vector<tracy::Worker::ImportEventTimeline> timeline;
std::vector<tracy::Worker::ImportEventMessages> messages;
std::vector<tracy::Worker::ImportEventPlots> plots;
std::unordered_map<uint64_t, std::string> threadNames;
std::string name;
std::string process;
static MergedTrace merge( std::vector<ExportedTrace> const& traces )
{
MergedTrace out;
if( traces.empty() ) return out;
out.name = traces[0].name;
out.process = traces[0].process + " (merged)";
std::unordered_map<std::pair<std::string, std::string>, size_t, PairHash<std::string, std::string>> nameCounts;
for( auto const& trace : traces )
{
for( auto const& [tid, threadName] : trace.threadNames )
{
auto key = std::make_pair( trace.process, threadName );
nameCounts[key]++;
}
}
std::unordered_map<std::pair<std::string, std::string>, size_t, PairHash<std::string, std::string>> plotNameCounts;
for( auto const& trace : traces )
{
for( auto const& plot : trace.plots )
{
auto key = std::make_pair( trace.process, plot.name );
plotNameCounts[key]++;
}
}
std::unordered_map<std::pair<uint64_t, uint64_t>, uint64_t, PairHash<uint64_t, uint64_t>> tidMapping;
size_t totalTimeline = 0, totalMessages = 0, totalPlots = 0;
for( auto const& trace : traces )
{
totalTimeline += trace.timeline.size();
totalMessages += trace.messages.size();
totalPlots += trace.plots.size();
}
out.timeline.reserve( totalTimeline );
out.messages.reserve( totalMessages );
out.plots.reserve( totalPlots );
for( auto const& trace : traces )
{
for( auto const& [origTid, threadName] : trace.threadNames )
{
uint64_t encodedTid = (origTid & 0xFFFFFFFF) | (trace.pid << 32);
auto [it, inserted] = tidMapping.emplace( std::make_pair( trace.pid, origTid ), encodedTid );
uint64_t finalTid = it->second;
auto key = std::make_pair( trace.process, threadName );
std::string displayName;
if( nameCounts[key] > 1 )
{
displayName = trace.process + "[" + std::to_string( trace.pid ) + "]/" + threadName;
}
else
{
displayName = trace.process + "/" + threadName;
}
out.threadNames[finalTid] = displayName;
}
for( auto const& event : trace.timeline )
{
auto& inserted = out.timeline.emplace_back( event );
auto key = std::make_pair( trace.pid, event.tid );
auto it = tidMapping.find( key );
if( it != tidMapping.end() )
{
inserted.tid = it->second;
}
}
for( auto const& msg : trace.messages )
{
auto& inserted = out.messages.emplace_back( msg );
auto key = std::make_pair( trace.pid, msg.tid );
auto it = tidMapping.find( key );
if( it != tidMapping.end() )
{
inserted.tid = it->second;
}
}
for( auto const& plot : trace.plots )
{
auto renamedPlot = plot;
auto key = std::make_pair( trace.process, plot.name );
if( plotNameCounts[key] > 1 )
{
renamedPlot.name = trace.process + "[" + std::to_string( trace.pid ) + "]/" + plot.name;
}
else
{
renamedPlot.name = trace.process + "/" + plot.name;
}
out.plots.push_back( renamedPlot );
}
}
std::sort( out.timeline.begin(), out.timeline.end(), ExportedTrace::orderTimeline );
std::sort( out.messages.begin(), out.messages.end(), ExportedTrace::orderMessages );
return out;
}
};
[[noreturn]] void Usage()
{
printf( "tracy-merge %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
printf( "Usage: tracy-merge -o output.tracy input1.tracy [input2.tracy ...]\n\n" );
printf( "Options:\n" );
printf( " -o, --output <file> Output file path (required)\n" );
printf( " -f, --force Overwrite output file if it exists\n" );
printf( " -h, --help Show this help message\n" );
printf( " -V, --version Show version information\n" );
exit( 1 );
}
int main( int argc, char** argv )
{
std::string outputFile;
std::vector<std::string> inputFiles;
bool overwrite = false;
static struct option longOptions[] = {
{ "output", required_argument, nullptr, 'o' },
{ "force", no_argument, nullptr, 'f' },
{ "help", no_argument, nullptr, 'h' },
{ "version", no_argument, nullptr, 'V' },
{ nullptr, 0, nullptr, 0 }
};
int c;
while( ( c = getopt_long( argc, argv, "o:fhV", longOptions, nullptr ) ) != -1 )
{
switch( c )
{
case 'o':
outputFile = optarg;
break;
case 'f':
overwrite = true;
break;
case 'h':
Usage();
break;
case 'V':
printf( "tracy-merge %i.%i.%i / %s\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
exit( 0 );
default:
Usage();
break;
}
}
if( outputFile.empty() )
{
std::cerr << "Error: Output file is required (-o)" << std::endl << std::endl;
Usage();
}
while( optind < argc )
{
inputFiles.emplace_back( argv[optind++] );
}
if( inputFiles.empty() )
{
std::cerr << "Error: At least one input file is required" << std::endl << std::endl;
Usage();
}
if( std::filesystem::exists( outputFile ) )
{
if( overwrite )
{
std::filesystem::remove( outputFile );
}
else
{
std::cerr << "Error: Output file already exists: " << outputFile << std::endl;
std::cerr << "Use -f to overwrite" << std::endl;
return 1;
}
}
std::vector<ExportedTrace> traces;
traces.reserve( inputFiles.size() );
for( size_t i = 0; i < inputFiles.size(); i++ )
{
auto trace = ExportedTrace::fromFile( inputFiles[i], i );
if( !trace )
{
std::cerr << "Failed to read: " << inputFiles[i] << std::endl;
return 1;
}
traces.push_back( std::move( *trace ) );
}
std::cout << "\nMerging " << traces.size() << " trace(s)..." << std::endl;
MergedTrace merged = MergedTrace::merge( traces );
std::cout << " Total zones: " << merged.timeline.size() << std::endl;
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 ) );
if( !outFile )
{
std::cerr << "Error: Could not open output file: " << outputFile << std::endl;
return 1;
}
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();
auto stats = outFile->GetCompressionStatistics();
std::cout << "Done. Output size: " << tracy::MemSizeToString( stats.second ) << " (" << (100.0 * stats.second / stats.first) << "% ratio)" << std::endl;
return 0;
}

View File

@@ -21,6 +21,10 @@ if get_option('callstack') > 0
tracy_common_args += ['-DTRACY_CALLSTACK='+get_option('callstack').to_string()]
endif
if get_option('platform_header') != ''
tracy_common_args += ['-DTRACY_PLATFORM_HEADER="'+get_option('platform_header')+'"']
endif
if get_option('no_callstack')
tracy_common_args += ['-DTRACY_NO_CALLSTACK']
endif
@@ -93,6 +97,10 @@ if get_option('timer_fallback')
tracy_common_args += ['-DTRACY_TIMER_FALLBACK']
endif
if get_option('disallow_hw_timer')
tracy_common_args += ['-DTRACY_DISALLOW_HW_TIMER']
endif
if get_option('no_crash_handler')
tracy_common_args += ['-DTRACY_NO_CRASH_HANDLER']
endif
@@ -114,6 +122,10 @@ if get_option('verbose')
tracy_common_args += ['-DTRACY_VERBOSE']
endif
if get_option('no_internal_message')
tracy_common_args += ['-DTRACY_NO_INTERNAL_MESSAGE']
endif
if get_option('debuginfod')
tracy_common_args += ['-DTRACY_DEBUGINFOD']
tracy_public_deps += dependency('libdebuginfod')
@@ -123,6 +135,10 @@ if get_option('ignore_memory_faults')
tracy_common_args += ['-DTRACY_IGNORE_MEMORY_FAULTS']
endif
if get_option('opengl_auto_calibration')
tracy_common_args += ['-DTRACY_OPENGL_AUTO_CALIBRATION']
endif
tracy_shared_libs = get_option('default_library') == 'shared'
if tracy_shared_libs
@@ -130,7 +146,7 @@ if tracy_shared_libs
endif
if host_machine.system() == 'windows'
tracy_compile_args += ['-DWINVER=0x0601', '-D_WIN32_WINNT=0x0601']
tracy_compile_args += ['-DWINVER=0x0A00', '-D_WIN32_WINNT=0x0A00']
endif
includes = [
@@ -157,6 +173,7 @@ client_includes = [
'public/client/TracyFastVector.hpp',
'public/client/TracyKCore.hpp',
'public/client/TracyLock.hpp',
'public/client/TracyMangle.hpp',
'public/client/TracyProfiler.hpp',
'public/client/TracyRingBuffer.hpp',
'public/client/TracyScoped.hpp',
@@ -181,6 +198,7 @@ common_includes = [
'public/common/TracySocket.hpp',
'public/common/TracyStackFrames.hpp',
'public/common/TracySystem.hpp',
'public/common/TracyTaggedUserlandAddress.hpp',
'public/common/TracyWinFamily.hpp',
'public/common/TracyYield.hpp'
]

View File

@@ -1,6 +1,7 @@
option('tracy_enable', type : 'boolean', value : true, description : 'Enable profiling', yield: true)
option('tracy_enable', type : 'boolean', value : false, description : 'Enable profiling', yield: true)
option('on_demand', type : 'boolean', value : false, description : 'On-demand profiling')
option('callstack', type : 'integer', value : 0, description : 'Enforce callstack collection for tracy zones x frames deep')
option('platform_header', type : 'string', value : '', description : 'Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform')
option('no_callstack', type : 'boolean', value : false, description : 'Disable all callstack related functionality')
option('no_callstack_inlines', type : 'boolean', value : false, description : 'Disables the inline functions in callstacks')
option('only_localhost', type : 'boolean', value : false, description : 'Only listen on the localhost interface')
@@ -16,6 +17,7 @@ option('no_frame_image', type : 'boolean', value : false, description : 'Disable
option('no_system_tracing', type : 'boolean', value : false, description : 'Disable systrace sampling')
option('patchable_nopsleds', type : 'boolean', value : false, description : 'Enable nopsleds for efficient patching by system-level tools (e.g. rr)')
option('timer_fallback', type : 'boolean', value : false, description : 'Use lower resolution timers')
option('disallow_hw_timer', type : 'boolean', value : false, description : 'Disallow hardware timer (may be useful on VMs). Requires timer_fallback.')
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')
@@ -24,5 +26,7 @@ option('manual_lifetime', type : 'boolean', value : false, description : 'Enable
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('opengl_auto_calibration', type : 'boolean', value : false, description : 'Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)')

42
monitor/CMakeLists.txt Normal file
View File

@@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.16)
set(TRACY_PUBLIC_DIR ${CMAKE_CURRENT_LIST_DIR}/../public)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
set(CMAKE_CXX_STANDARD 17)
project(
tracy-monitor
LANGUAGES C CXX
VERSION ${TRACY_VERSION_STRING}
)
find_package(Threads REQUIRED)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
set(PROGRAM_FILES
src/monitor.cpp
)
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${TRACY_PUBLIC_DIR}/TracyClient.cpp)
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC
$<BUILD_INTERFACE:${TRACY_PUBLIC_DIR}>
$<INSTALL_INTERFACE:include/tracy>)
target_compile_definitions(${PROJECT_NAME} PRIVATE
TRACY_ENABLE
TRACY_DELAYED_INIT
TRACY_MANUAL_LIFETIME
TRACY_NO_FRAME_IMAGE
)
target_link_libraries(${PROJECT_NAME} PRIVATE
Threads::Threads
${CMAKE_DL_LIBS}
)
add_git_ref(${PROJECT_NAME})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

407
monitor/src/monitor.cpp Normal file
View File

@@ -0,0 +1,407 @@
#include <errno.h>
#include <getopt.h>
#include <linux/perf_event.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../public/tracy/Tracy.hpp"
#include "../public/common/TracyVersion.hpp"
#include "../public/client/TracyCallstack.hpp"
#include "GitRef.hpp"
namespace tracy {
extern uint32_t ___tracy_magic_pid_override;
extern char ___tracy_magic_process_name[64];
}
static volatile sig_atomic_t s_shouldQuit = 0;
static pid_t s_targetPid = 0;
static bool s_isForked = false;
static void SignalHandler( int sig )
{
s_shouldQuit = 1;
if( s_isForked && s_targetPid != 0 )
{
// We launched the target under ptrace, so forward the signal to wake
// a blocking waitpid and let the child exit. kill() is async-signal-safe.
kill( s_targetPid, SIGINT );
}
}
static bool ReadProcessName( pid_t pid, char* buf, size_t bufSize )
{
char path[64];
snprintf( path, sizeof( path ), "/proc/%d/comm", (int)pid );
FILE* f = fopen( path, "r" );
if( !f ) return false;
if( !fgets( buf, bufSize, f ) )
{
fclose( f );
return false;
}
fclose( f );
// Remove trailing newline
size_t len = strlen( buf );
while( len > 0 && ( buf[len-1] == '\n' || buf[len-1] == '\r' ) ) len--;
buf[len] = '\0';
return len > 0;
}
static bool CheckPerfPermissions()
{
FILE* f = fopen( "/proc/sys/kernel/perf_event_paranoid", "r" );
if( !f )
{
fprintf( stderr, "Warning: Cannot read /proc/sys/kernel/perf_event_paranoid\n" );
return true; // Assume OK
}
int paranoid = 2;
if( fscanf( f, "%d", &paranoid ) != 1 ) paranoid = 2;
fclose( f );
if( paranoid > 1 && geteuid() != 0 )
{
fprintf( stderr, "Warning: perf_event_paranoid = %d. Profiling another process may require:\n", paranoid );
fprintf( stderr, " - Running as root, or\n" );
fprintf( stderr, " - Setting /proc/sys/kernel/perf_event_paranoid to -1 or 0, or\n" );
fprintf( stderr, " - Granting CAP_PERFMON + CAP_SYS_PTRACE capabilities\n" );
}
return true;
}
static bool ProcessIsAlive( pid_t pid )
{
return kill( pid, 0 ) == 0;
}
// Try opening one perf event against the target so we fail fast with a clear
// message instead of starting the profiler and silently producing no samples.
static bool PreflightPerfEventOpen( pid_t pid )
{
perf_event_attr pe = {};
pe.type = PERF_TYPE_SOFTWARE;
pe.size = sizeof( perf_event_attr );
pe.config = PERF_COUNT_SW_CPU_CLOCK;
pe.disabled = 1;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
const long fd = syscall( __NR_perf_event_open, &pe, pid, 0, -1, 0 );
if( fd < 0 )
{
const int err = errno;
if( err == EACCES || err == EPERM )
{
fprintf( stderr, "Cannot open perf events for pid %d: %s\n", (int)pid, strerror( err ) );
fprintf( stderr, "Profiling another process requires one of:\n" );
fprintf( stderr, " - running as root, or\n" );
fprintf( stderr, " - /proc/sys/kernel/perf_event_paranoid <= 0, or\n" );
fprintf( stderr, " - CAP_PERFMON + CAP_SYS_PTRACE capabilities\n" );
return false;
}
if( err == ESRCH )
{
fprintf( stderr, "Target process %d no longer exists.\n", (int)pid );
return false;
}
// Other errors may still be recoverable in the real setup path.
fprintf( stderr, "Warning: perf_event_open preflight failed: %s\n", strerror( err ) );
return true;
}
close( (int)fd );
return true;
}
static void PrintUsage( const char* progName )
{
printf( "tracy-monitor %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
printf( "Usage: %s [OPTIONS] program [arguments...]\n", progName );
printf( " %s [OPTIONS] -p PID\n", progName );
printf( "\n" );
printf( "Options:\n" );
printf( " -p PID Attach to existing process (PID)\n" );
printf( " -h Show this help message\n" );
printf( "\n" );
printf( "Examples:\n" );
printf( " %s ./my_program arg1 arg2\n", progName );
printf( " %s -p 1234\n", progName );
printf( "\n" );
printf( "The monitor captures sampling profiling data from an external process\n" );
printf( "and streams it to a Tracy server for visualization.\n" );
printf( "\n" );
printf( "In launch mode, the target program is started under ptrace control to\n" );
printf( "ensure profiling begins before the first instruction executes.\n" );
printf( "\n" );
printf( "In attach mode (-p), the target must already be running.\n" );
}
static int RunAttached( pid_t pid )
{
if( !ProcessIsAlive( pid ) )
{
fprintf( stderr, "Process %d does not exist or is not accessible.\n", (int)pid );
return 1;
}
s_targetPid = pid;
char procName[64] = {};
if( ReadProcessName( pid, procName, sizeof( procName ) ) )
{
memcpy( tracy::___tracy_magic_process_name, procName, sizeof( tracy::___tracy_magic_process_name ) );
}
printf( "Attaching to process %d", (int)pid );
if( tracy::___tracy_magic_process_name[0] ) printf( " (%s)", tracy::___tracy_magic_process_name );
printf( "...\n" );
fflush( stdout );
if( !PreflightPerfEventOpen( pid ) ) return 1;
tracy::InitExternalImageCache( pid );
tracy::___tracy_magic_pid_override = (uint32_t)pid;
tracy::StartupProfiler();
printf( "Profiling started. Waiting for Tracy server connection...\n" );
// Wait for the target process to exit, or for a signal
while( !s_shouldQuit && ProcessIsAlive( pid ) )
{
usleep( 100000 ); // 100ms poll
}
if( s_shouldQuit )
{
printf( "\nShutting down profiler...\n" );
}
else
{
printf( "Target process %d exited.\n", (int)pid );
}
tracy::ShutdownProfiler();
return 0;
}
static int RunForked( int argc, char** argv )
{
pid_t childPid = fork();
if( childPid < 0 )
{
fprintf( stderr, "Unable to fork: %s\n", strerror( errno ) );
return 2;
}
if( childPid == 0 )
{
// Child process: request ptrace stop at exec, then exec the target
if( ptrace( PTRACE_TRACEME, 0, nullptr, nullptr ) < 0 )
{
fprintf( stderr, "ptrace(TRACEME) failed: %s\n", strerror( errno ) );
_exit( 2 );
}
execvp( argv[0], argv );
fprintf( stderr, "Unable to exec '%s': %s\n", argv[0], strerror( errno ) );
_exit( 2 );
}
// Parent: wait for the child to stop at the exec boundary (SIGTRAP)
s_targetPid = childPid;
s_isForked = true;
int status;
for(;;)
{
if( waitpid( childPid, &status, 0 ) >= 0 ) break;
if( errno == EINTR ) continue;
fprintf( stderr, "waitpid failed: %s\n", strerror( errno ) );
kill( childPid, SIGKILL );
waitpid( childPid, nullptr, 0 );
return 2;
}
if( !WIFSTOPPED( status ) )
{
// Child exited or was killed before reaching the post-exec SIGTRAP.
if( s_shouldQuit )
{
fprintf( stderr, "\nInterrupted before target started.\n" );
}
else if( WIFEXITED( status ) )
{
fprintf( stderr, "Target exited before profiling began (status %d) -- exec failed?\n", WEXITSTATUS( status ) );
}
else if( WIFSIGNALED( status ) )
{
fprintf( stderr, "Target killed by signal %d before profiling began.\n", WTERMSIG( status ) );
}
else
{
fprintf( stderr, "Child process did not stop as expected (status=0x%x).\n", status );
}
return 2;
}
// The child has exec'd but is stopped. Its address space is now the target program.
// Read its process name and memory maps.
char procName[64] = {};
if( ReadProcessName( childPid, procName, sizeof( procName ) ) )
{
memcpy( tracy::___tracy_magic_process_name, procName, sizeof( tracy::___tracy_magic_process_name ) );
}
printf( "Profiling '%s' (pid %d)...\n",
tracy::___tracy_magic_process_name[0] ? tracy::___tracy_magic_process_name : argv[0],
(int)childPid );
fflush( stdout );
if( !PreflightPerfEventOpen( childPid ) )
{
kill( childPid, SIGKILL );
waitpid( childPid, nullptr, 0 );
return 1;
}
// Initialize the external image cache (target's /proc/pid/maps)
tracy::InitExternalImageCache( childPid );
// Set up the profiler to target the child
tracy::___tracy_magic_pid_override = (uint32_t)childPid;
tracy::StartupProfiler();
// Detach ptrace and let the child run. If detach fails the child stays
// stopped forever, so this has to be fatal.
if( ptrace( PTRACE_DETACH, childPid, nullptr, nullptr ) < 0 )
{
fprintf( stderr, "ptrace(DETACH) failed: %s -- killing child.\n", strerror( errno ) );
kill( childPid, SIGKILL );
waitpid( childPid, nullptr, 0 );
tracy::ShutdownProfiler();
return 2;
}
printf( "Profiling started. Waiting for Tracy server connection...\n" );
// Wait for child to exit, or for a signal
for(;;)
{
if( s_shouldQuit ) break;
int wstatus;
pid_t ret = waitpid( childPid, &wstatus, WNOHANG );
if( ret > 0 )
{
if( WIFEXITED( wstatus ) )
{
printf( "Target process exited with status %d.\n", WEXITSTATUS( wstatus ) );
}
else if( WIFSIGNALED( wstatus ) )
{
printf( "Target process killed by signal %d.\n", WTERMSIG( wstatus ) );
}
break;
}
else if( ret < 0 && errno != EINTR )
{
// Child already gone
break;
}
usleep( 100000 );
}
if( s_shouldQuit && ProcessIsAlive( childPid ) )
{
printf( "\nForwarding signal to child and shutting down...\n" );
kill( childPid, SIGINT );
// Give it a moment to exit
usleep( 500000 );
if( ProcessIsAlive( childPid ) )
{
kill( childPid, SIGKILL );
waitpid( childPid, nullptr, 0 );
}
}
tracy::ShutdownProfiler();
return 0;
}
int main( int argc, char** argv )
{
auto progName = argv[0];
if( argc < 2 )
{
PrintUsage( progName );
return 1;
}
// Install signal handlers for graceful shutdown
struct sigaction sa = {};
sa.sa_handler = SignalHandler;
sigemptyset( &sa.sa_mask );
sa.sa_flags = 0;
sigaction( SIGINT, &sa, nullptr );
sigaction( SIGTERM, &sa, nullptr );
sigaction( SIGHUP, &sa, nullptr );
sigaction( SIGQUIT, &sa, nullptr );
pid_t attachPid = 0;
bool wantAttach = false;
static struct option longOptions[] = {
{ "pid", required_argument, nullptr, 'p' },
{ "help", no_argument, nullptr, 'h' },
{ nullptr, 0, nullptr, 0 }
};
int c;
while( ( c = getopt_long( argc, argv, "+p:h", longOptions, nullptr ) ) != -1 )
{
switch( c )
{
case 'p':
attachPid = atoi( optarg );
wantAttach = true;
break;
case 'h':
PrintUsage( argv[0] );
return 0;
case '?':
fprintf( stderr, "Unknown option. Use -h for help.\n" );
return 1;
}
}
argv += optind;
argc -= optind;
CheckPerfPermissions();
if( wantAttach )
{
if( attachPid <= 0 )
{
fprintf( stderr, "Invalid PID specified.\n" );
return 1;
}
return RunAttached( attachPid );
}
if( argc < 1 )
{
PrintUsage( progName ); // argv[0] was shifted, use original
return 1;
}
return RunForked( argc, argv );
}

View File

@@ -1,12 +1,14 @@
cmake_minimum_required(VERSION 3.25)
option(NO_FILESELECTOR "Disable the file selector" OFF)
option(GTK_FILESELECTOR "Use the GTK file selector on Linux instead of the xdg-portal one" OFF)
option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
option(NO_STATISTICS "Disable calculation of statistics" OFF)
option(SELF_PROFILE "Enable self-profiling" OFF)
option(SANITIZE "Sanitizer parameters" OFF)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/options.cmake)
set_option(NO_FILESELECTOR "Disable the file selector" OFF)
set_option(GTK_FILESELECTOR "Use the GTK file selector on Linux instead of the xdg-portal one" OFF)
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" "")
set(NO_STATISTICS OFF)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
@@ -31,6 +33,7 @@ endif()
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(ExternalProject)
ExternalProject_Add(embed
@@ -40,16 +43,12 @@ ExternalProject_Add(embed
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
)
add_custom_command(
OUTPUT data
COMMAND ${CMAKE_COMMAND} -E make_directory data
)
function(Embed LIST NAME FILE)
add_custom_command(
OUTPUT data/${NAME}.cpp data/${NAME}.hpp
COMMAND ${CMAKE_COMMAND} -E make_directory data
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
DEPENDS data embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
DEPENDS embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
)
list(APPEND ${LIST} data/${NAME}.cpp)
return(PROPAGATE ${LIST})
@@ -61,6 +60,7 @@ set(SERVER_FILES
TracyBadVersion.cpp
TracyColor.cpp
TracyConfig.cpp
TracyDisassembly.cpp
TracyEmbed.cpp
TracyEventDebug.cpp
TracyFileselector.cpp
@@ -146,15 +146,33 @@ set(PROFILER_FILES
)
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md)
Embed(PROFILER_FILES SystemReminder src/llm/system.reminder.md)
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md)
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md)
Embed(PROFILER_FILES ToolsJson src/llm/tools.json)
Embed(PROFILER_FILES FontFixed src/font/FiraCode-Retina.ttf)
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 6\ Free-Solid-900.otf)
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 7\ Free-Solid-900.otf)
Embed(PROFILER_FILES FontNormal src/font/Roboto-Regular.ttf)
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)
Embed(PROFILER_FILES Manual ../manual/tracy.md)
Embed(PROFILER_FILES Text100Million src/achievements/100Million.md)
Embed(PROFILER_FILES TextConnectToClient src/achievements/ConnectToClient.md)
Embed(PROFILER_FILES TextFindZone src/achievements/FindZone.md)
Embed(PROFILER_FILES TextFrameImages src/achievements/FrameImages.md)
Embed(PROFILER_FILES TextGlobalSettings src/achievements/GlobalSettings.md)
Embed(PROFILER_FILES TextInstrumentationIntro src/achievements/InstrumentationIntro.md)
Embed(PROFILER_FILES TextInstrumentationStatistics src/achievements/InstrumentationStatistics.md)
Embed(PROFILER_FILES TextInstrumentFrames src/achievements/InstrumentFrames.md)
Embed(PROFILER_FILES TextIntro src/achievements/Intro.md)
Embed(PROFILER_FILES TextLoadTrace src/achievements/LoadTrace.md)
Embed(PROFILER_FILES TextSamplingIntro src/achievements/SamplingIntro.md)
Embed(PROFILER_FILES TextSaveTrace src/achievements/SaveTrace.md)
set(INCLUDES "${CMAKE_CURRENT_BINARY_DIR}")
set(LIBS "")
@@ -171,7 +189,7 @@ if(USE_WAYLAND)
CPMAddPackage(
NAME wayland-protocols
GIT_REPOSITORY https://gitlab.freedesktop.org/wayland/wayland-protocols.git
GIT_TAG 1.37
GIT_TAG 1.47
DOWNLOAD_ONLY YES
)
@@ -263,30 +281,7 @@ if(NOT EMSCRIPTEN)
)
endif()
if(NOT DEFINED GIT_REV)
set(GIT_REV "HEAD")
endif()
find_package(Git)
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 ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
VERBATIM
)
add_dependencies(${PROJECT_NAME} git-ref)
else()
message(WARNING "git not found, using 'unknown' as git ref.")
add_custom_command(
OUTPUT GitRef.hpp
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp
COMMAND ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp
VERBATIM
)
target_sources(${PROJECT_NAME} PUBLIC GitRef.hpp)
endif()
add_git_ref(${PROJECT_NAME})
if(NOT EMSCRIPTEN)
if(NOT NO_FILESELECTOR)
@@ -298,7 +293,7 @@ if(NOT EMSCRIPTEN)
endif()
if(EMSCRIPTEN)
target_link_options(${PROJECT_NAME} PRIVATE -pthread -sASSERTIONS=0 -sINITIAL_MEMORY=384mb -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sSTACK_SIZE=1048576 -sWASM_BIGINT=1 -sPTHREAD_POOL_SIZE=8 -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile -sEXPORTED_RUNTIME_METHODS=ccall -sENVIRONMENT=web,worker --preload-file embed.tracy)
target_link_options(${PROJECT_NAME} PRIVATE -pthread -sASSERTIONS=0 -sINITIAL_MEMORY=384mb -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sSTACK_SIZE=1048576 -sWASM_BIGINT=1 -sPTHREAD_POOL_SIZE=8 -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard -sEXPORTED_RUNTIME_METHODS=ccall -sENVIRONMENT=web,worker --preload-file embed.tracy)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -24,6 +24,30 @@ static float s_prevScale = -1;
static int s_width, s_height;
static uint64_t s_time;
static const char* s_prevCursor = nullptr;
static std::string s_clipboard;
extern "C" void tracy_paste_clipboard( const char* text )
{
s_clipboard = text;
ImGui::GetIO().AddInputCharactersUTF8( text );
}
static void SetClipboard( ImGuiContext*, const char* text )
{
s_clipboard = text;
EM_ASM( {
var text = UTF8ToString($0);
if( navigator.clipboard && navigator.clipboard.writeText )
{
navigator.clipboard.writeText( text );
}
}, text );
}
static const char* GetClipboard( ImGuiContext* )
{
return s_clipboard.c_str();
}
static ImGuiKey TranslateKeyCode( const char* code )
{
@@ -183,6 +207,10 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = "wasm (tracy profiler)";
auto& platform = ImGui::GetPlatformIO();
platform.Platform_SetClipboardTextFn = SetClipboard;
platform.Platform_GetClipboardTextFn = GetClipboard;
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;
@@ -218,7 +246,7 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, true );
if( e->key[0] && !e->key[1] ) ImGui::GetIO().AddInputCharacter( *e->key );
if( e->key[0] && !e->key[1] && !e->ctrlKey && !e->metaKey ) ImGui::GetIO().AddInputCharacter( *e->key );
return EM_TRUE;
} );
emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
@@ -227,6 +255,12 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
ImGui::GetIO().AddKeyEvent( code, false );
return EM_TRUE;
} );
EM_ASM( {
document.addEventListener( 'paste', function( e ) {
var text = ( e.clipboardData || window.clipboardData ).getData( 'text' );
if( text ) ccall( 'tracy_paste_clipboard', 'void', ['string'], [text] );
} );
} );
s_time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
}
@@ -265,8 +299,12 @@ void Backend::NewFrame( int& w, int& h )
if( s_width != w || s_height != h )
{
EM_ASM( Module.canvas.style.width = window.innerWidth + 'px'; Module.canvas.style.height = window.innerHeight + 'px' );
EM_ASM( Module.canvas.width = $0; Module.canvas.height = $1, w, h );
EM_ASM( {
Module.canvas.style.width = ($0 / $2) + 'px';
Module.canvas.style.height = ($1 / $2) + 'px';
Module.canvas.width = $0;
Module.canvas.height = $1;
}, w, h, double( scale ) );
s_width = w;
s_height = h;

View File

@@ -134,7 +134,18 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
# endif
#endif
s_window = glfwCreateWindow( m_winPos.w, m_winPos.h, title, NULL, NULL );
if( !s_window ) exit( 1 );
if( !s_window ) {
const char* description;
int code = glfwGetError( &description );
if( description ) {
fprintf( stderr, "ERROR: Tracy (GLFW): %s\n", description );
#ifdef _WIN32
MessageBoxA( NULL, description, "ERROR: Tracy (GLFW)", MB_OK );
OutputDebugStringA( description );
#endif
}
exit( 1 );
}
glfwSetWindowPos( s_window, m_winPos.x, m_winPos.y );
#if GLFW_VERSION_MAJOR > 3 || ( GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 2 )

View File

@@ -119,7 +119,7 @@ constexpr ImGuiKey s_keyTable[] = {
/* 81 */ ImGuiKey_Keypad3,
/* 82 */ ImGuiKey_Keypad0,
/* 83 */ ImGuiKey_KeypadDecimal,
/* 84 */ ImGuiKey_RightAlt,
/* 84 */ ImGuiKey_None,
/* 85 */ ImGuiKey_None,
/* 86 */ ImGuiKey_Backslash,
/* 87 */ ImGuiKey_F11,
@@ -135,7 +135,7 @@ constexpr ImGuiKey s_keyTable[] = {
/* 97 */ ImGuiKey_RightCtrl,
/* 98 */ ImGuiKey_KeypadDivide,
/* 99 */ ImGuiKey_PrintScreen,
/* 100 */ ImGuiKey_RightAlt,
/* 100 */ ImGuiKey_None,
/* 101 */ ImGuiKey_None,
/* 102 */ ImGuiKey_Home,
/* 103 */ ImGuiKey_UpArrow,
@@ -338,8 +338,8 @@ static void PointerFrame( void*, struct wl_pointer* pointer )
if( s_wheel )
{
s_wheel = false;
s_wheelAxisX /= 8;
s_wheelAxisY /= 8;
s_wheelAxisX /= 15;
s_wheelAxisY /= 15;
ImGuiIO& io = ImGui::GetIO();
io.AddMouseWheelEvent( wl_fixed_to_double( s_wheelAxisX ), wl_fixed_to_double( s_wheelAxisY ) );
s_wheelAxisX = s_wheelAxisY = 0;

View File

@@ -0,0 +1,29 @@
#ifndef __EMSCRIPTENSHIM_HPP__
#define __EMSCRIPTENSHIM_HPP__
#if defined __EMSCRIPTEN__ || defined __APPLE__
namespace std {
template<typename T>
struct atomic<shared_ptr<T>> {
shared_ptr<T> value;
bool compare_exchange_weak(shared_ptr<T>& expected, const shared_ptr<T>& desired) noexcept {
return atomic_compare_exchange_weak_explicit(&value, &expected, &desired, std::memory_order_acq_rel, std::memory_order_acquire);
}
shared_ptr<T> load(memory_order order = std::memory_order_seq_cst) const noexcept {
return atomic_load_explicit(&value, order);
}
void store(shared_ptr<T> desired, memory_order order = std::memory_order_seq_cst) noexcept {
atomic_store_explicit(&value, desired, order);
}
};
} // namespace std
#endif
#endif

View File

@@ -4,7 +4,6 @@
#include <misc/freetype/imgui_freetype.h>
#include "Fonts.hpp"
#include "profiler/IconsFontAwesome6.h"
#include "profiler/TracyEmbed.hpp"
#include "data/FontFixed.hpp"
@@ -13,6 +12,7 @@
#include "data/FontBold.hpp"
#include "data/FontBoldItalic.hpp"
#include "data/FontItalic.hpp"
#include "data/FontEmoji.hpp"
FontData g_fonts;
@@ -34,29 +34,35 @@ void LoadFonts( float scale )
configFixed.GlyphExtraAdvanceX = -1;
configFixed.FontDataOwnedByAtlas = false;
auto fontFixed = Unembed( FontFixed );
auto fontIcons = Unembed( FontIcons );
auto fontNormal = Unembed( FontNormal );
auto fontBold = Unembed( FontBold );
auto fontBoldItalic = Unembed( FontBoldItalic );
auto fontItalic = Unembed( FontItalic );
static auto fontFixed = Unembed( FontFixed );
static auto fontIcons = Unembed( FontIcons );
static auto fontNormal = Unembed( FontNormal );
static auto fontBold = Unembed( FontBold );
static auto fontBoldItalic = Unembed( FontBoldItalic );
static auto fontItalic = Unembed( FontItalic );
static auto fontEmoji = Unembed( FontEmoji );
io.Fonts->Clear();
g_fonts.normal = io.Fonts->AddFontFromMemoryTTF( (void*)fontNormal->data(), fontNormal->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
g_fonts.mono = io.Fonts->AddFontFromMemoryTTF( (void*)fontFixed->data(), fontFixed->size(), round( 15.0f * scale ), &configFixed );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
g_fonts.bold = io.Fonts->AddFontFromMemoryTTF( (void*)fontBold->data(), fontBold->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
g_fonts.boldItalic = io.Fonts->AddFontFromMemoryTTF( (void*)fontBoldItalic->data(), fontBoldItalic->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
g_fonts.italic = io.Fonts->AddFontFromMemoryTTF( (void*)fontItalic->data(), fontItalic->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
FontNormal = round( scale * 15.f );
FontSmall = round( scale * 15 * 2.f / 3.f );

View File

@@ -0,0 +1,12 @@
# It's over 100 million!
Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!
Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it.
Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case.
> [!TIP]
> Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory.
>
> To capture 100 million zones, you will need approximately 4 GB of RAM.

View File

@@ -0,0 +1,10 @@
# First profiling session
Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:
1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project.
2. Make sure that `TracyClient.cpp` (or the Tracy library) is included in your build.
3. Define `TRACY_ENABLE` in your build configuration, for the whole application. Do not do it in a single source file because it won't work.
4. Start your application, and * Connect* to it with the profiler.
Please refer to the [user manual](https://github.com/wolfpld/tracy/releases) for more details.

View File

@@ -0,0 +1,11 @@
# Find some zones
You can search for zones in the trace by opening the search window with the * Find zone* button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code.
The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them.
Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone.
When a zone is selected, a number of statistics are displayed to help you understand the performance of your application. In addition, a histogram of the zone execution times is displayed to make it easier for you to determine the performance of the profiled code. Be sure to select a zone with a large number of calls to make the histogram look interesting!
Note that you can draw a range on the histogram to limit the number of entries displayed in the zone list below. This list allows you to examine each zone individually. There are also a number of zone groupings that you can select. Each group can be selected and the time associated with the selected group will be highlighted on the histogram.

View File

@@ -0,0 +1,11 @@
# A picture is worth a thousand words
Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the `FrameImage` macro.
You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way.
The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen.
You can even view a recording of what your application was doing by clicking the * Tools* icon and then selecting the * Playback* option. Try it out!
The `FrameImage` macro is a great way to see what happened in your application at a particular time. Maybe you have a performance problem that only occurs when a certain object is on the screen?

View File

@@ -0,0 +1,5 @@
# Global settings
Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the * Wrench* icon on the welcome screen. This will open the about window, where you can expand the * Global settings* menu.
The settings are saved between sessions, so you only need to set them once.

View File

@@ -0,0 +1,22 @@
# Instrumenting frames
In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application.
To instrument frames, you need to add the `FrameMark` macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame.
```c++
#include "Tracy.hpp"
void Render()
{
// Render the frame
SwapBuffers();
FrameMark;
}
```
When you profile your application, you will see a new frame appear on the timeline each time the `FrameMark` macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second.
The `FrameMark` macro is a great way to see at a glance how your application is performing over time. Maybe there are some performance problems that only appear after a few minutes of running the application? A frame graph is drawn at the top of the profiler window where you can see the timing of all frames.
Note that some applications do not have a frame-based structure, and in such cases, frame instrumentation may not be useful. That's ok.

View File

@@ -0,0 +1,22 @@
# Instrumentating your application
Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up.
To get started, open a source file and include the `Tracy.hpp` header. This will give you access to a variety of macros provided by Tracy. Next, add the `ZoneScoped` macro to the beginning of one of your functions, like this:
```c++
#include "Tracy.hpp"
void SomeFunction()
{
ZoneScoped;
// Your code here
}
```
Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called.
> [!NOTE]
> The `ZoneScoped` macro is just one of the many macros provided by Tracy. See the documentation for more information.
The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information.

View File

@@ -0,0 +1,5 @@
# Show me the stats!
Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called.
To view the statistics, click on the * Statistics* button on the top bar. This will open a new window with a list of all zones in the trace.

View File

@@ -0,0 +1,12 @@
# Click here to discover achievements!
Clicking on the * Achievements* button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done.
As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!
To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements * Achievements* button will glow yellow when there are new things to see.
- New tasks: orange 
- Completed tasks: green 
Good luck!

View File

@@ -0,0 +1,3 @@
# Load a trace
You can open a previously saved trace file (or one received from a friend) with the * Open saved trace* button on the welcome screen.

View File

@@ -0,0 +1,10 @@
# Sampling program execution
Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often.
While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return.
Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work.
> [!WARNING]
> Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information.

View File

@@ -0,0 +1,12 @@
# Save a trace
Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the * Connection* icon in the top left corner of the screen and then clicking on the * Save trace* button.
Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before.
You can also share the trace with your friends or co-workers by sending them the trace file.
> [!WARNING]
> **Warning**
>
> Trace files can contain sensitive information about your application, such as program code, or even the contents of source files. Be careful when sharing them with others.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,111 @@
# Call stacks
A call stack is a trace of program execution at the moment of capture. This capture can happen at any time, but it usually occurs when a program crashes, so the developer can trace the path that led to the failure. The case of call stacks in a profiler is very different. During profiling, the operating system halts execution of the program at a predefined rate-for example, 10 kHz-notes where the program execution is and the function calls that led it there, then resumes the program. As can be seen, the call stacks in a profiler are slices of a normal workflow you can use to explore execution characteristics, not indications of failure.
# Call stack structure
The top call stack frame is numbered 0, and it always is the place *where the program execution is* during the stack capture. Stack frame numbers increase the farther toward the origin function we go, as the usual convention goes. A stack trace is always a snapshot of what a *single thread* is doing.
In Tracy Profiler, each execution frame can have multiple *inline* frames. When a program is compiled, the compiler, as an optimization, may inline function calls into the base function ("symbol"), and the profiler can track that.
To show how it works, let's consider the following source code:
```c++
float Square(float val) { return val*val; }
float Distance(Point p1, Point p2) { return sqrt(Square(p1.x-p2.x)+Square(p1.y-p2.y)); }
bool CanReach(Player p, Item i) { return Distance(p.pos, i.pos)<5; }
```
Now, let's say we capture a call stack inside the `Square` function. This is how the call stack can look:
```callstack
0. Square() [inline 0]
0. Distance() [inline 1]
0. CanReach() [inline 2]
1. ItemsLoop()
2. PlayerLogic()
3. ...
```
There are three frames with index 0, which means that both `Square` and `Distance` have been inlined into the `CanReach` function, forming a symbol named `CanReach`. Following the inline stack frame indices, we can also see that the call order is `CanReach` -> `Distance` -> `Square`, which matches what the source code does.
Note that while the example is at the top level, inline frames can appear at any depth of the call stack.
# Call stacks are return stacks
You need to be very careful when reading call stacks. The usual notion is that call stacks (as the name suggests) show function call stacks, that is, which function called which to get where we are. Unfortunately, this is not true. In reality, call stacks are *function return stacks*. The call stack shows where each function will **return**, not from where it was called.
## Example 1
To fully understand how this works, consider the following source code:
```c++
int main()
{
auto app = std::make_unique<Application>();
app->Run();
app.reset();
}
```
Let's assume the `Application` instance (`app`) is already created and we have entered the `Run` method, where, somewhere inside, we're capturing a call stack. Here's a result we might get:
```callstack
0. Application::Run()
1. std::unique_ptr<Application>::reset()
2. main()
```
At the first glance it may look like `unique_ptr::reset` was the *call site* of the `Application::Run`, which would make no sense, but this is not the case here. When you remember these are the *function return points*, it becomes much more clear what is happening. As an optimization, `Application::Run` is returning directly into `unique_ptr::reset`, skipping the return to `main` and an unnecessary `reset` function call.
## Example 2
Here you will see how a function on the call chain can be entirely absent:
```c++
int ComputeHash(const Buffer& b) {
// expensive hashing
...
}
int Validate(const Buffer& b) {
if(b.empty()) return 0;
return ComputeHash(b);
}
void HandleRequest(Request& r) {
int h = Validate(r.payload);
Store(r, h);
}
```
A sample lands inside ComputeHash. The captured stack looks like this:
```callstack
0. ComputeHash()
1. HandleRequest()
2. RequestLoop()
```
**Naive (call-stack) reading:** `HandleRequest` called `ComputeHash` directly. Conclusion: optimize the call site in `HandleRequest`, or rethink why `HandleRequest` is hashing.
**Correct (return-stack) reading:** Frame 1 is where `ComputeHash` will return to, not who called it. `Validate` ended with return `ComputeHash(b)`, so the compiler turned that into a tail call — `Validate`'s own frame was reused for `ComputeHash`. When `ComputeHash` returns, it skips `Validate` entirely and lands in `HandleRequest`. `Validate` is on the actual call chain (`HandleRequest` -> `Validate` -> `ComputeHash`) but is missing from the stack.
**Why the divergence matters:** The naive reading sends you to fix the wrong code. `HandleRequest` doesn't decide to hash anything — `Validate` does. If you act on the naive interpretation, you investigate a function that has no agency in this hot path while the actual decision-maker (`Validate`) is invisible.
**Lesson the example teaches:** Any frame below a given frame in the stack may not be its caller. It is only guaranteed to be the return target. Functions on the real call chain can be entirely absent — tail-called away or inlined.
# Crash handler
Tracy Profiler can intercept crashes and report them to the user for analysis. To do this, some code machinery is needed, and then the Tracy crash handler needs to run, capture the call stack, and send it over the network. All this only happens after the actual crash occurred; otherwise, there would be no reason to run the crash handler. As a consequence, the retrieved crash trace may include parts of the crash handler stack, which you must ignore.
# Base address and instruction pointer
Each frame in a call stack has an associated instruction pointer, `ip` the return address where the execution will return from the function a frame above. This address is somewhere in the symbol code. The start of the symbol is provided as the `baseAddr` value. This base address identifies the symbol and can be used in various symbol-related tool calls as the symbol address.
# Inspecting call stacks
1. Focus on user's code. Ignore standard library boilerplate.
2. Retrieve source code to verify call stack validity. Frames in call stacks are return locations, and the call site may actually be near the reported source line, or in a different function altogether.
3. Top of the call stack is the most interesting, as it shows what the program is doing *now*. The bottom of the call stack shows what the program did to do what it's doing.
4. If the call stack contains Tracy's crash handler, the profiled program has crashed. In this case, ignore the crash handler and any functions it may be calling. The crash happened *before* the handler intercepted it.

View File

@@ -0,0 +1,70 @@
# Program optimization
The user may ask you to optimize a particular functionality, routine, or code fragment. While doing so, the user may include attachments of various types:
- Source code listings, with or without per-line performance data.
- A machine instructions list (disassembly of binary code), accompanied by per-instruction performance data.
You should try to find where the optimization opportunities are. Note that some code may already be optimized very well, and there may be little or nothing left to gain.
# Symbols
When a source code function is compiled, the compiler may inline multiple auxiliary functions into the produced machine code block. This block is called a *symbol*. The symbol may contain multiple source-level functions (some of which may be repeated multiple times), which may come from multiple source files.
# Assembly listings
The assembly instruction listing of a symbol must be mapped to the source code. The assembly attachment contains the code itself, and an array of source files named `files`. The format of an assembly line is:
fileIdx:line:offset:cost:callCost:assembly
To identify the source file name of any assembly instruction, you must access `files[fileIdx]`. The `fileIdx` value is strictly internal and should never be presented to the user. Always show the source file name and line number in your answers. Since symbols can be constructed from multiple source files, you must specify both the source file name and line number, or user won't know which file you refer to.
The `offset` value represents the byte offset at which the machine instruction lies in the symbol code.
The `cost` value shows how much time the CPU spent executing the given machine instruction. If the cost is not present, the profiler recorded no activity for the given instruction. The `callCost` value shows how much time was spent executing the called external functions. The cost values are percentages relative to the total execution cost of an entire symbol, including external function calls.
The `assembly` value is the actual disassembled machine code. It may also contain a comment with:
- Local jump target, `label`, for example `.L6`.
- Name of the external function call, `destination`.
## Measurement skid
The measurements present in the attachment may be slightly imprecise due to the way the profiler infrastructure or the CPU works, especially considering out-of-order architectures. As a result, some cost value may be wrongly attributed to the instruction in the immediate vicinity of the instruction that produced the cost.
Take the following example:
```asm
5% mov rax, [rbx]
40% inc rax
```
The first instruction that loads the value from memory is the high-latency one, but it can be dispatched for execution fairly quickly. The second instruction, which needs the output of the first instruction, is actually very fast to execute but is blocked by the slow memory access of the first instruction, taking the majority of the cost on itself.
A careful investigation of the cost attribution is thus needed.
# Symbols vs source code
Analyzing a user program can be done in two complementary ways.
1. You can retrieve the function source code and look at what it does. This is enough for simple checks.
2. Alternatively, you can get the disassembly of the binary code of a symbol. This method of analysis contains source line information, which can be used to match the assembly against the source code, as well as CPU usage data, allowing you to see which individual assembly instructions have the most performance cost associated with them. Doing this deep dive is important for thorough analysis of code performance characteristics.
# Which code paths are important
When looking at code, you may find many places that use inefficient algorithms or implementations. While pointing out such cases may sometimes be useful, you must check whether the problematic code is actually on the hot path, as indicated by the profiling data included with the disassembly. The profiling data the user provides are highly targeted at specific workflows, and the primary optimization target should be the code that was actually executing, not something that could run theoretically. Avoid including optimization advice for code paths that might run but did not.
# Context is important
When reasoning about the performance of a symbol, you should look at the environment where it is used. You can do this by:
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?
# 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.
2. Analyze the available data, looking for where the majority of the run time is spent. Always look at the code as a whole. Do not stop after finding a bunch of interesting spots.
3. Consider the external calls the function is making. If appropriate, look at the performance characteristics of the called code.
4. Figure out what algorithms are in use, how the data is structured, how it flows, and reason about trade-offs taken.
5. Determine whether the code can be made to perform better. Note that some code will already be optimal, despite having hot spots.
6. Formulate the optimization opportunities and present them to the user. Tell the user where the problems are, what causes them, and the potential solutions.
7. Do not provide concrete speedup percentages. It is only possible to know how much faster the code is by measuring it after the changes. You can't do that.

View File

@@ -1,203 +1,43 @@
You are a language model, designed to provide precise answers based on available tools and your knowledge. Your operation must strictly adhere to the instructions below.
You're a language model, meant to give exact answers using the tools you have and what you know. The current time is %TIME%. Your operation has to follow these instructions exactly.
# Core Principles:
1. *Never guess or invent information.* If you do not have the necessary data, use the available tools to gather it.
2. Always protect privacy of the user.
3. If the tools return no data or you still lack the required information after using the tools, attempt to answer using your internal knowledge, while clearly informing the user that the response might be incorrect, invalid, or wrong, and that the tools returned no data.
4. *Never ask the user* for permission to use tools or perform further queries. You *MUST* conduct the entire information retrieval process independently, and *ONLY THEN* reply to the user.
5. *Language Consistency:* If the user's query is in a language other than English, you MUST translate *all* tool output and internally generated responses into the user's query language *before* formulating your final response. Your final response to the user must always be in the language they used. Do not output information in any other language.
6. Prioritize information obtained via tools (from `<tool_output>`) over your internal knowledge when constructing your response. Treat tool outputs as the leading source of information, but be aware that they may contain irrelevant details, inconsistencies, or inaccuracies. Critically evaluate all tool outputs: check for relevance to the user's query, cross-reference information across different tool outputs, and assess consistency with your internal knowledge. If multiple tool outputs provide conflicting but equally plausible information, you may state the different findings or, if possible, explain the discrepancy if it leads to a clearer answer. Avoid presenting information as definitively true if its source is uncertain.
# Thinking Process and Tool Usage:
Your operation process will be strictly structured using `<think>` and `<tool>` tags.
1. *Thinking Process (`<think>`):*
- Always start with a `<think>` block.
- This block is for planning, analyzing the user's query, deciding which tools are needed (if any), processing results from `<tool_output>`, and formulating the structure of the response.
- You must analyse the question or any attachments provided by the user to decide which tools you can use.
- The tag name MUST be exactly `think`.
2. *Tool Usage (`<tool>`):*
- If, in the `<think>` block, you decide you need to use a tool, the next block generated *MUST* be a `<tool>` block.
- The tag name MUST be exactly `tool`.
- There can be ONLY ONE tool call in the `<tool>` block.
- Only ONE tool call is permitted PER TURN.
- *After generating a `<tool>` block, you MUST END YOUR RESPONSE FOR THIS TURN.* Do not generate any other text or tags after the `<tool>` block. The system will process this tool call and provide you with the result in the next step.
- The tool name and its parameters (if applicable) must be passed as a json data. For example:
<think>
The user is asking about the weather in San Francisco. I need to use the weather checking tool. The tool name is 'check_weather', the parameter is the city name.
</think>
<tool>
{"tool": "check_weather", "city": "San Francisco"}
</tool>
3. *Tool Output (`<tool_output>`):*
- After the system executes the tool call from the `<tool>` block, you will receive the result in an `<tool_output>` block.
- *You MUST process this result in the subsequent `<think>` block.* Analyze the data received. Based on it, decide if further tool calls are necessary or if you have enough information to answer the user.
- *Never show the user the raw text from `<tool_output>`*. All processing happens internally within the `<think>` block.
# Available Tools:
These are the tools you can use. *You have no access to any other tools or means to search the web outside of these.*
```json
{
"tool": "search_wikipedia",
"description": "Search the Wikipedia with given query. The `key` field in the response is the Wikipedia page name.",
"network": true,
"parameters": [
{
"name": "query",
"description": "The search terms in the language matching the second parameter."
},
{
"name": "language",
"description": "Language code matching the search query. For example, `en` for English or `pl` for Polish."
}
]
},
{
"tool": "get_wikipedia",
"description": "Retrieve the Wikipedia article on given subject. The response may be trimmed.",
"network": true,
"parameters": [
{
"name": "page",
"description": "The `key` field from the search response, specifying the topic you want to retrieve.",
},
{
"name": "language",
"description": "Language code."
}
]
},
{
"tool": "get_dictionary",
"description": "Retrieve description of a word from dictionary.",
"network": true,
"parameters": [
{
"name": "word",
"description": "Word to describe."
},
{
"name": "language",
"description": "Language code."
}
]
},
{
"tool": "search_web",
"description": "Search the web with given query.",
"network": true,
"parameters": [
{
"name": "query",
"description": "Search query."
}
]
},
{
"tool": "get_webpage",
"description": "Download web page at given URL.",
"network": true,
"parameters": [
{
"name": "url",
"description": "Web page to download."
}
]
},
{
"tool": "user_manual",
"description": "Search the Tracy Profiler user manual with given query.",
"local": true,
"parameters": [
{
"name": "query",
"description": "Verbose search query in English language."
}
]
},
{
"tool": "source_file",
"description": "Retrieve the source file contents.",
"local": true,
"parameters": [
{
"name": "file",
"description": "Path to the file."
},
{
"name": "line",
"description": "Line number that should be retrieved (as large files may be not available completely)."
}
]
}
```
Tools marked as `local` operate privately and are always safe to use. Tools marked as `network` send data over the internet and may affect user's privacy.
1. Never guess or make up facts. Your knowledge is outdated. Always use the tools you have to get the real info.
2. It's important to protect the user's privacy and the privacy of their program.
3. Use multiple tool calls to get the info you need. You can try to answer using your own knowledge only after all the relevant tools don't work. If so, be sure to let the user know that the response might be wrong because getting the data failed.
4. Make sure you check all the tool's outputs for relevance to the user's query, then cross-reference the information across different outputs. And finally, see if it's consistent with your internal knowledge.
5. Respond in the language the user is using.
6. Don't go asking the user if you should move forward with getting more info — just go for it.
# Tool Usage and Knowledge Strategy:
1. *Source Priority:*
- For questions related to Tracy Profiler always refer to the `user_manual`. Do not use this tool to research user's program.
- If the user's question explicitly asks about source code in user's program (for example, a callstack provided as an attachment), use the `source_file` tool to retrieve the content of specified files.
- For other factual queries, start by checking Wikipedia. If Wikipedia doesn't provide enough information, or if the topic is new or highly specialized, then perform a `search_web` query.
2. *Internal Knowledge vs. Tools:* Always assume your internal knowledge is incomplete or outdated compared to information from tools. *You MUST use tools* to get the latest and most accurate data on subjects covered by their scope (e.g. facts likely on Wikipedia or the web). Output from previous tool invocations must be always considered.
3. *Efficient Tool Use:* Before using a tool you MUST check if previous tool calls already contain the tool and parameters you want to call. If they do, you are forbidden from calling the tool a second time. You must use the tool output you already have.
4. *Tool Output Completness:* Some tools will return snippets or summaries of the information, which can only be used in limited conditions. You MUST use these summaries to decide which tool to call next to get complete data.
5. *Mandatory Content Retrieval:* Some tool outputs (e.g. `search_wikipedia` or `search_web`) provide only summaries or snippets. These are *never* sufficient for formulating a final answer. Their sole purpose is to identify the most promising page or URL. You MUST always follow a successful search with a corresponding tool call (e.g., `get_wikipedia` or `get_webpage`) to retrieve the full content before attempting to answer the user's query. Do not answer based on search snippets alone. The only exception is if the search returns no relevant results.
1. Keep in mind that your own understanding might be a bit outdated compared to what you can find in the tools.
2. If a tool gives you a preview of information, use it only to determine if the search result is worth pursuing. If it is, use a different tool to retrieve the full contents.
3. If you're not getting the info you need from one tool, try another.
4. Use as many tools as you need to get all the info you need.
5. Keep the internal names of the tools you can use under wraps. Don't mention that you're using tools unless someone asks you about it.
6. Always check if a loadable skill can be used for your task.
7. If you have loaded a skill, you must carefully follow what it says.
# Context of operation
# Final Response to the User:
You are "Tracy Assist" and operate in context of Tracy Profiler, a code performance profiler for games and other applications. You are talking with user named %USER%.
1. Once you have gathered all necessary information using the `<think>`, `<tool>`, and `<tool_output>` processing cycle, *generate the final response FOR THE USER.*
2. This final response *MUST* be *OUTSIDE* of the `<think>` and `<tool>` tags.
3. The user shouldn't know you are "using tools". Use a natural language, such as "the Wikipedia states that..." or "the web search results indicate that...". The user should not be aware of the tool usage process.
4. Provide responses *strictly in the language the user used* in their query.
The profiler uses a bunch of different methods to analyze (profile) user program's behavior and measure its performance characteristics. So, there are many types of questions the user might ask, and you need to correctly categorize each one to give the best answer possible.
- The user might have questions about Tracy Profiler. In this case, you should primarily focus on the `user_manual` tool, which has info about the profiler. When talking about certain terms in the profiler UI, stick with the original English names.
- The user might want to ask about the program they're profiling. Your tools can give you access to that program's source code. The user program is probably private, so you should limit usage of tools using the network, as that may violate the privacy. Try to use more of your own experience and know-how here.
- The user can also ask general questions that aren't related to the profiler or the program they're profiling. In this case, answer however you like and use any tool you think is necessary.
# Summary of Communication Structure:
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
Each of your responses (or part of a response, if it requires a tool call) should start with a `<think>` block, followed by either a `<tool>` block (if further information is needed) or directly the final response to the user (if you have all information).
# User's program
**Example Cycle (Not visible to the user):**
User: "What is the capital of Poland?"
Model:
<think>
The user is asking for the capital of Poland, in English language. This is a standard fact, but instructions say to use tools for facts. I should search Wikipedia. I need search_wikipedia for "capital of Poland" in language "en" to find the key.
</think>
<tool>
{"tool": "search_wikipedia", "query": "capital of Poland", "language": "en"}
</tool>
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%
System returns `<tool_output>` with the key for the article about Warsaw.
Model (New Turn):
<think>
I retrieved the key for the capital of Poland article ("Warsaw"). Now I need to retrieve that article using get_wikipedia. The key is "Warsaw", language is "en".
</think>
<tool>
{"tool": "get_wikipedia", "page": "Warsaw", "language": "en"}
</tool>
Here are instructions you must follow when you are asked to work with program the user is profiling.
System returns `<tool_output>` with the content of the Warsaw article, stating it is the capital.
Model (New Turn):
<think>
I downloaded the content of the Warsaw article. It confirms that Warsaw is the capital of Poland. I have all the necessary information. I can provide the answer in user's language, which is English.
</think>
The capital of Poland is Warsaw.
*The user only sees:* "The capital of Poland is Warsaw."
# Attachments
## Attachments
The user may provide various types of attachments for you to process. These attachments come from the users's program. When you process *attachments* using *tools that access a network*, you must adhere to the following privacy protection rules. The rules *do not* apply in other circumstances, such as in conversation with the user, when using local tools, or when getting data for things unrelated to the user's program.
@@ -205,13 +45,20 @@ The user may provide various types of attachments for you to process. These atta
- Publicly Available Files: This restriction does not apply to files that are in publicly accessible locations.
- Tool Use: The `source_file` tool preserves user privacy and can be used regardless of the source file location.
## Using links
# Context of operation
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.
You operate in context of Tracy Profiler, a C++ profiler for games and other applications. The profiler uses various methods to measure how the user's program behaves and measures the program's run-time performance characteristics. As such, there are various types of questions the user may ask you, and you must properly classify each question in order to give the best possible answer:
To provide a link to user manual section, use the anchor point as the link destination: "[<description>](#anchor)".
- The user may ask you about things related to Tracy Profiler. In this case you should primarily focus on the `user_manual` tool, which provides information about the profiler. When refering to specific terms in the profiler UI, use the original English names.
- The user may attach information from the program they are profiling and ask you about it. Since this would be mostly private data, you should focus on the `source_file` tool, which will give you context about specific source locations referenced in the attachment. You may need to put more emphasis on your internal knowledge when answering these kind of questions. Use of other tools should be limited to cases where it's obvious they will be useful. For example, you may want to search the web about the zlib library if the code uses it, or, you may retrieve a web page referenced in the source code comments.
- The user may also ask general question not related either to the profiler or the program they are profiling. In this case answer freely, and use any tool you feel necessary.
Insert links inline in the text, for example: "Function xyz() is located at [line 123 in source.c](source:/home/user/source.c:123)."
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
Never write a link in an inline code block, like: `[description](link)`. If code markdown is needed, include it in the description: [`description`](link).
## Case specific operation
Specialized instructions and workflows for specific tasks are provided with the `skill` tool. If the task description matches the skill description, you must load the skill in question to gather the required abilities, *before* doing anything else.
Available skills:
%SKILLS%

View File

@@ -1,5 +0,0 @@
Remember your core principles:
1. Protect user's privacy.
2. Always prioritize tools for factual information.
3. Your internal knowledge is strictly secondary and a last resort.
4. Respond strictly in the user's language.

246
profiler/src/llm/tools.json Normal file
View File

@@ -0,0 +1,246 @@
[
{
"type": "function",
"function": {
"name": "skill",
"description": "Learn a specialized skill for the task needing specific knowledge.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the skill, chosen from the list of available skills."
}
},
"required": ["name"]
}
}
},
{
"type": "function",
"function": {
"name": "search_wikipedia",
"description": "Search the Wikipedia with given query. The `key` field in the response is the Wikipedia page name. Uses network.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search terms in the language matching the second parameter."
},
"language": {
"type": "string",
"description": "Language code matching the search query. For example, `en` for English or `pl` for Polish."
}
},
"required": ["query", "language"]
}
}
},
{
"type": "function",
"function": {
"name": "get_wikipedia",
"description": "Retrieve the Wikipedia article on given subject. Uses network.",
"parameters": {
"type": "object",
"properties": {
"page": {
"type": "string",
"description": "The Wikipedia page name."
},
"language": {
"type": "string",
"description": "Language code."
}
},
"required": ["page", "language"]
}
}
},
{
"type": "function",
"function": {
"name": "get_dictionary",
"description": "Retrieve description of a word from dictionary. Uses network.",
"parameters": {
"type": "object",
"properties": {
"word": {
"type": "string",
"description": "Word to describe."
},
"language": {
"type": "string",
"description": "Language code."
}
},
"required": ["word", "language"]
}
}
},
{
"type": "function",
"function": {
"name": "search_web",
"description": "Search the web with given query. Uses network.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query."
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "get_webpage",
"description": "Fetch contents of a web page at given URL. Uses network.",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Web page to download."
}
},
"required": ["url"]
}
}
},
{
"type": "function",
"function": {
"name": "user_manual",
"description": "Search the Tracy Profiler user manual with given query.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Verbose search query in English language."
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "source_file",
"description": "Retrieve the user program source file contents around given line.",
"parameters": {
"type": "object",
"properties": {
"file": {
"type": "string",
"description": "Path to the file."
},
"line": {
"type": "integer",
"description": "Target line number."
},
"context": {
"type": "integer",
"description": "How many lines to get after the target line. Default: 2."
},
"context_back": {
"type": "integer",
"description": "How many lines to get before the target line. Default: 2."
}
},
"required": ["file", "line"]
}
}
},
{
"type": "function",
"function": {
"name": "source_search",
"description": "Search user program source files for given keyword.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "ECMAScript regex to search for."
},
"case_insensitive": {
"type": "boolean",
"description": "Case insensitive search."
},
"path": {
"type": "string",
"description": "File path ECMAScript regex."
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "symbol_disasm",
"description": "Get disassembly and profiling data for a symbol.",
"parameters": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Symbol address, for example `0x12345678`."
}
},
"required": ["address"]
}
}
},
{
"type": "function",
"function": {
"name": "symbol_parents",
"description": "Get the top N entry call stacks for a given symbol address. Shows the call paths through which execution reached this function.",
"parameters": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Symbol address, for example `0x12345678`."
},
"limit": {
"type": "integer",
"description": "Maximum number of call stacks to return. Default: 10."
}
},
"required": ["address"]
}
}
},
{
"type": "function",
"function": {
"name": "sampling_stats",
"description": "Get sampling statistics for functions, sorted by time spent in each.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "ECMAScript regex to search for."
},
"limit": {
"type": "integer",
"description": "Maximum number of entries to return. Default: 30."
}
},
"required": []
}
}
}
]

View File

@@ -39,11 +39,12 @@
#include "profiler/TracyTexture.hpp"
#include "profiler/TracyView.hpp"
#include "profiler/TracyWeb.hpp"
#include "profiler/IconsFontAwesome6.h"
#include "profiler/IconsFontAwesome7.h"
#include "../../server/tracy_pdqsort.h"
#include "../../server/tracy_robin_hood.h"
#include "../../server/TracyFileHeader.hpp"
#include "../../server/TracyFileRead.hpp"
#include "../../server/TracyBroadcast.hpp"
#include "../../server/TracyPrint.hpp"
#include "../../server/TracySysUtil.hpp"
#include "../../server/TracyWorker.hpp"
@@ -58,6 +59,7 @@
#include "Backend.hpp"
#include "ConnectionHistory.hpp"
#include "EmscriptenShim.hpp"
#include "Filters.hpp"
#include "Fonts.hpp"
#include "HttpRequest.hpp"
@@ -83,7 +85,7 @@ struct ClientData
enum class ViewShutdown { False, True, Join };
static tracy::unordered_flat_map<uint64_t, ClientData> clients;
static std::unique_ptr<tracy::View> view;
static std::atomic<std::shared_ptr<tracy::View>> view;
static tracy::BadVersionState badVer;
static uint16_t port = 8086;
static const char* connectTo = nullptr;
@@ -122,6 +124,8 @@ tracy::AchievementsMgr* s_achievements;
static const tracy::data::AchievementItem* s_achievementItem = nullptr;
static bool s_switchAchievementCategory = false;
ImTextureID GetProfilerIconTexture() { return iconTex; }
static float smoothstep( float x )
{
return x * x * ( 3.0f - 2.0f * x );
@@ -160,16 +164,16 @@ static void SetupDPIScale()
{
auto scale = dpiScale * tracy::s_config.userScale;
#ifdef __APPLE__
scale = tracy::s_config.userScale;
#endif
if( !dpiFirstSetup && prevScale == scale ) return;
dpiFirstSetup = false;
dpiChanged = 2;
LoadFonts( scale );
#ifdef __APPLE__
scale = 1.0f;
#endif
auto& style = ImGui::GetStyle();
style = ImGuiStyle();
ImGui::StyleColorsDark();
@@ -199,7 +203,8 @@ static void SetupDPIScale()
static int IsBusy()
{
if( loadThread.joinable() ) return 2;
if( view && !view->IsBackgroundDone() ) return 1;
auto ptr = view.load( std::memory_order_acquire );
if( ptr && !ptr->IsBackgroundDone() ) return 1;
return 0;
}
@@ -231,7 +236,7 @@ int main( int argc, char** argv )
{
if( strcmp( argv[1], "--help" ) == 0 )
{
printf( "%s\n\n", title );
printf( "%s / %s\n\n", title, tracy::GitRef );
printf( "Usage:\n\n" );
printf( " Open trace file stored on disk:\n" );
printf( " %s file.tracy\n\n", argv[0] );
@@ -348,12 +353,12 @@ int main( int argc, char** argv )
if( initFileOpen )
{
view = std::make_unique<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
view.store( std::make_shared<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
initFileOpen.reset();
}
else if( connectTo )
{
view = std::make_unique<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
view.store( std::make_shared<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
tracy::Fileselector::Init();
@@ -365,7 +370,7 @@ int main( int argc, char** argv )
if( loadThread.joinable() ) loadThread.join();
if( updateThread.joinable() ) updateThread.join();
if( updateNotesThread.joinable() ) updateNotesThread.join();
view.reset();
view.store( nullptr, std::memory_order_release );
tracy::FreeTexture( zigzagTex, RunOnMainThread );
tracy::FreeTexture( iconTex, RunOnMainThread );
@@ -378,7 +383,8 @@ int main( int argc, char** argv )
static void UpdateBroadcastClients()
{
if( !view )
auto ptr = view.load( std::memory_order_acquire );
if( !ptr )
{
const auto time = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
if( !broadcastListen )
@@ -397,76 +403,15 @@ static void UpdateBroadcastClients()
{
auto msg = broadcastListen->Read( len, addr, 0 );
if( !msg ) break;
if( len > sizeof( tracy::BroadcastMessage ) ) continue;
uint16_t broadcastVersion;
memcpy( &broadcastVersion, msg, sizeof( uint16_t ) );
if( broadcastVersion <= tracy::BroadcastVersion )
auto parsed = tracy::ParseBroadcastMessage( msg, len );
if( parsed.has_value() )
{
uint32_t protoVer;
char procname[tracy::WelcomeMessageProgramNameSize];
int32_t activeTime;
uint16_t listenPort;
uint64_t pid;
switch( broadcastVersion )
{
case 3:
{
tracy::BroadcastMessage bm;
memcpy( &bm, msg, len );
protoVer = bm.protocolVersion;
strcpy( procname, bm.programName );
activeTime = bm.activeTime;
listenPort = bm.listenPort;
pid = bm.pid;
break;
}
case 2:
{
if( len > sizeof( tracy::BroadcastMessage_v2 ) ) continue;
tracy::BroadcastMessage_v2 bm;
memcpy( &bm, msg, len );
protoVer = bm.protocolVersion;
strcpy( procname, bm.programName );
activeTime = bm.activeTime;
listenPort = bm.listenPort;
pid = 0;
break;
}
case 1:
{
if( len > sizeof( tracy::BroadcastMessage_v1 ) ) continue;
tracy::BroadcastMessage_v1 bm;
memcpy( &bm, msg, len );
protoVer = bm.protocolVersion;
strcpy( procname, bm.programName );
activeTime = bm.activeTime;
listenPort = bm.listenPort;
pid = 0;
break;
}
case 0:
{
if( len > sizeof( tracy::BroadcastMessage_v0 ) ) continue;
tracy::BroadcastMessage_v0 bm;
memcpy( &bm, msg, len );
protoVer = bm.protocolVersion;
strcpy( procname, bm.programName );
activeTime = bm.activeTime;
listenPort = 8086;
pid = 0;
break;
}
default:
assert( false );
break;
}
auto& pm = parsed.value();
auto address = addr.GetText();
const auto ipNumerical = addr.GetNumber();
const auto clientId = uint64_t( ipNumerical ) | ( uint64_t( listenPort ) << 32 );
const auto clientId = tracy::ClientUniqueID( addr, pm.listenPort );
auto it = clients.find( clientId );
if( activeTime >= 0 )
if( pm.activeTime >= 0 )
{
if( it == clients.end() )
{
@@ -480,19 +425,19 @@ static void UpdateBroadcastClients()
auto it = resolvMap.find( ip );
assert( it != resolvMap.end() );
std::swap( it->second, name );
} );
} );
}
resolvLock.unlock();
clients.emplace( clientId, ClientData { time, protoVer, activeTime, listenPort, pid, procname, std::move( ip ) } );
clients.emplace( clientId, ClientData { time, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
}
else
{
it->second.time = time;
it->second.activeTime = activeTime;
it->second.port = listenPort;
it->second.pid = pid;
it->second.protocolVersion = protoVer;
if( strcmp( it->second.procName.c_str(), procname ) != 0 ) it->second.procName = procname;
it->second.activeTime = pm.activeTime;
it->second.port = pm.listenPort;
it->second.pid = pm.pid;
it->second.protocolVersion = pm.protocolVersion;
if( strcmp( it->second.procName.c_str(), pm.programName ) != 0 ) it->second.procName = pm.programName;
}
}
else if( it != clients.end() )
@@ -587,7 +532,8 @@ static void DrawContents()
const bool achievementsAttention = tracy::s_config.achievements ? s_achievements->NeedsAttention() : false;
static int activeFrames = 3;
if( tracy::WasActive() || !clients.empty() || ( view && view->WasActive() ) || achievementsAttention )
auto viewPtr = view.load( std::memory_order_acquire );
if( tracy::WasActive() || !clients.empty() || ( viewPtr && viewPtr->WasActive() ) || achievementsAttention )
{
activeFrames = 3;
}
@@ -626,7 +572,7 @@ static void DrawContents()
setlocale( LC_NUMERIC, "C" );
if( !view )
if( !viewPtr )
{
if( s_customTitle )
{
@@ -863,7 +809,7 @@ static void DrawContents()
ImGui::EndPopup();
}
ImGui::SameLine();
if( ImGui::Button( ICON_FA_COMMENT " Chat" ) )
if( ImGui::Button( ICON_FA_COMMENTS " Chat" ) )
{
tracy::OpenWebpage( "https://discord.gg/pk78auc" );
}
@@ -962,11 +908,11 @@ static void DrawContents()
{
std::string addrPart = std::string( adata, ptr );
uint16_t portPart = (uint16_t)atoi( ptr+1 );
view = std::make_unique<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
view.store( std::make_shared<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
else
{
view = std::make_unique<tracy::View>( RunOnMainThread, address.c_str(), port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
view.store( std::make_shared<tracy::View>( RunOnMainThread, address.c_str(), port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
}
}
@@ -990,7 +936,7 @@ static void DrawContents()
loadThread = std::thread( [f] {
try
{
view = std::make_unique<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
view.store( std::make_shared<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
catch( const tracy::UnsupportedVersion& e )
{
@@ -1123,7 +1069,7 @@ static void DrawContents()
}
if( selected && !loadThread.joinable() )
{
view = std::make_unique<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
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 );
}
ImGui::NextColumn();
const auto acttime = ( v.second.activeTime + ( time - v.second.time ) / 1000 ) * 1000000000ll;
@@ -1169,8 +1115,8 @@ static void DrawContents()
{
static float rnTime = 0;
rnTime += ImGui::GetIO().DeltaTime;
tracy::TextCentered( "Fetching release notes..." );
tracy::DrawWaitingDots( rnTime );
tracy::TextCentered( "Fetching release notes" );
tracy::DrawWaitingDotsCentered( rnTime );
}
else
{
@@ -1191,17 +1137,19 @@ static void DrawContents()
clients.clear();
}
if( loadThread.joinable() ) loadThread.join();
view->NotifyRootWindowSize( display_w, display_h );
if( !view->Draw() )
viewPtr->NotifyRootWindowSize( display_w, display_h );
if( !viewPtr->Draw() )
{
viewShutdown.store( ViewShutdown::True, std::memory_order_relaxed );
reconnect = view->ReconnectRequested();
reconnect = viewPtr->ReconnectRequested();
if( reconnect )
{
reconnectAddr = view->GetAddress();
reconnectPort = view->GetPort();
reconnectAddr = viewPtr->GetAddress();
reconnectPort = viewPtr->GetPort();
}
loadThread = std::thread( [view = std::move( view )] () mutable {
view.store( nullptr, std::memory_order_release );
loadThread = std::thread( [view = std::move( viewPtr )] () mutable {
view.reset();
viewShutdown.store( ViewShutdown::Join, std::memory_order_relaxed );
} );
@@ -1211,9 +1159,9 @@ static void DrawContents()
auto totalProgress = progress.total.load( std::memory_order_relaxed );
if( totalProgress != 0 )
{
ImGui::OpenPopup( "Loading trace..." );
ImGui::OpenPopup( "Loading trace" );
}
if( ImGui::BeginPopupModal( "Loading trace...", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings) )
if( ImGui::BeginPopupModal( "Loading trace", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings) )
{
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
ImGui::Spacing();
@@ -1222,7 +1170,7 @@ static void DrawContents()
ImGui::PopFont();
animTime += ImGui::GetIO().DeltaTime;
tracy::DrawWaitingDots( animTime );
tracy::DrawWaitingDotsCentered( animTime );
auto currProgress = progress.progress.load( std::memory_order_relaxed );
if( totalProgress == 0 )
@@ -1233,37 +1181,37 @@ static void DrawContents()
switch( currProgress )
{
case tracy::LoadProgress::Initialization:
ImGui::TextUnformatted( "Initialization..." );
ImGui::TextUnformatted( "Initialization" );
break;
case tracy::LoadProgress::Locks:
ImGui::TextUnformatted( "Locks..." );
ImGui::TextUnformatted( "Locks" );
break;
case tracy::LoadProgress::Messages:
ImGui::TextUnformatted( "Messages..." );
ImGui::TextUnformatted( "Messages" );
break;
case tracy::LoadProgress::Zones:
ImGui::TextUnformatted( "CPU zones..." );
ImGui::TextUnformatted( "CPU zones" );
break;
case tracy::LoadProgress::GpuZones:
ImGui::TextUnformatted( "GPU zones..." );
ImGui::TextUnformatted( "GPU zones" );
break;
case tracy::LoadProgress::Plots:
ImGui::TextUnformatted( "Plots..." );
ImGui::TextUnformatted( "Plots" );
break;
case tracy::LoadProgress::Memory:
ImGui::TextUnformatted( "Memory..." );
ImGui::TextUnformatted( "Memory" );
break;
case tracy::LoadProgress::CallStacks:
ImGui::TextUnformatted( "Call stacks..." );
ImGui::TextUnformatted( "Call stacks" );
break;
case tracy::LoadProgress::FrameImages:
ImGui::TextUnformatted( "Frame images..." );
ImGui::TextUnformatted( "Frame images" );
break;
case tracy::LoadProgress::ContextSwitches:
ImGui::TextUnformatted( "Context switches..." );
ImGui::TextUnformatted( "Context switches" );
break;
case tracy::LoadProgress::ContextSwitchesPerCpu:
ImGui::TextUnformatted( "CPU context switches..." );
ImGui::TextUnformatted( "CPU context switches" );
break;
default:
assert( false );
@@ -1271,7 +1219,7 @@ static void DrawContents()
}
ImGui::ProgressBar( float( currProgress ) / totalProgress, ImVec2( 200 * dpiScale, 0 ) );
ImGui::TextUnformatted( "Progress..." );
ImGui::TextUnformatted( "Progress" );
auto subTotal = progress.subTotal.load( std::memory_order_relaxed );
auto subProgress = progress.subProgress.load( std::memory_order_relaxed );
if( subTotal == 0 )
@@ -1287,20 +1235,20 @@ static void DrawContents()
switch( viewShutdown.load( std::memory_order_relaxed ) )
{
case ViewShutdown::True:
ImGui::OpenPopup( "Capture cleanup..." );
ImGui::OpenPopup( "Capture cleanup" );
break;
case ViewShutdown::Join:
loadThread.join();
viewShutdown.store( ViewShutdown::False, std::memory_order_relaxed );
if( reconnect )
{
view = std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
view.store( std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
}
break;
default:
break;
}
if( ImGui::BeginPopupModal( "Capture cleanup...", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
if( ImGui::BeginPopupModal( "Capture cleanup", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
if( viewShutdown.load( std::memory_order_relaxed ) != ViewShutdown::True ) ImGui::CloseCurrentPopup();
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
@@ -1309,7 +1257,7 @@ static void DrawContents()
ImGui::Spacing();
ImGui::PopFont();
animTime += ImGui::GetIO().DeltaTime;
tracy::DrawWaitingDots( animTime );
tracy::DrawWaitingDotsCentered( animTime );
ImGui::TextUnformatted( "Please wait, cleanup is in progress" );
ImGui::EndPopup();
}
@@ -1345,7 +1293,7 @@ The *Achievements* system will guide you through the main features and teach you
Would you like to enable achievements?
)";
tracy::Markdown md;
tracy::Markdown md( nullptr, nullptr );
md.Print( text, strlen( text ) );
ImGui::Spacing();
ImGui::PushFont( g_fonts.normal, FontSmall );
@@ -1518,9 +1466,17 @@ Would you like to enable achievements?
{
ImGui::Columns( 2 );
ImGui::SetColumnWidth( 0, 300 * dpiScale );
ImGui::BeginChild( "##achievementtoc", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
DrawAchievements( c->items );
ImGui::EndChild();
ImGui::NextColumn();
if( s_achievementItem ) s_achievementItem->description();
ImGui::BeginChild( "##achievementtext", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
if( s_achievementItem )
{
tracy::Markdown md( nullptr, nullptr );
md.Print( s_achievementItem->text.c_str(), s_achievementItem->text.size() );
}
ImGui::EndChild();
ImGui::EndColumns();
ImGui::EndTabItem();
}

View File

@@ -1,14 +1,17 @@
// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++
// from https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml
// for use with https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf
// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py
// for C and C++
// from codepoints https://github.com/FortAwesome/Font-Awesome/raw/7.x/metadata/icons.yml
// for use with font https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-regular-400.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.), https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-solid-900.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.)
#pragma once
#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf"
#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf"
#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.woff2"
#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.woff2"
#define ICON_MIN_FA 0xe005
#define ICON_MAX_16_FA 0xf8ff
#define ICON_MAX_FA 0xf8ff
#define ICON_FA_0 "0" // U+0030
#define ICON_FA_1 "1" // U+0031
#define ICON_FA_2 "2" // U+0032
@@ -22,6 +25,7 @@
#define ICON_FA_A "A" // U+0041
#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9
#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb
#define ICON_FA_ALARM_CLOCK "\xef\x8d\x8e" // U+f34e
#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037
#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039
#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036
@@ -41,7 +45,9 @@
#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102
#define ICON_FA_ANKH "\xef\x99\x84" // U+f644
#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1
#define ICON_FA_AQUARIUS "\xee\xa1\x85" // U+e845
#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557
#define ICON_FA_ARIES "\xee\xa1\x86" // U+e846
#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063
#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162
#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886
@@ -116,6 +122,7 @@
#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e
#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d
#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462
#define ICON_FA_BANGLADESHI_TAKA_SIGN "\xee\x8b\xa6" // U+e2e6
#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a
#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9
#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828
@@ -214,6 +221,7 @@
#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805
#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc
#define ICON_FA_BUS "\xef\x88\x87" // U+f207
#define ICON_FA_BUS_SIDE "\xee\xa0\x9d" // U+e81d
#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e
#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a
#define ICON_FA_C "C" // U+0043
@@ -232,8 +240,10 @@
#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083
#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8
#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb
#define ICON_FA_CANCER "\xee\xa1\x87" // U+e847
#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786
#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f
#define ICON_FA_CAPRICORN "\xee\xa1\x88" // U+e848
#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b
#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9
#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df
@@ -266,6 +276,7 @@
#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe
#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080
#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3
#define ICON_FA_CHART_DIAGRAM "\xee\x9a\x95" // U+e695
#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4
#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201
#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200
@@ -287,9 +298,9 @@
#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054
#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077
#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae
#define ICON_FA_CHILD_COMBATANT "\xee\x93\xa0" // U+e4e0
#define ICON_FA_CHILD_DRESS "\xee\x96\x9c" // U+e59c
#define ICON_FA_CHILD_REACHING "\xee\x96\x9d" // U+e59d
#define ICON_FA_CHILD_RIFLE "\xee\x93\xa0" // U+e4e0
#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1
#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d
#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111
@@ -334,6 +345,7 @@
#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da
#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d
#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a
#define ICON_FA_CLOSED_CAPTIONING_SLASH "\xee\x84\xb5" // U+e135
#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2
#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed
#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee
@@ -360,6 +372,7 @@
#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651
#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad
#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5
#define ICON_FA_COMMENT_NODES "\xee\x9a\x96" // U+e696
#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3
#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd
#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086
@@ -522,6 +535,8 @@
#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd
#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3
#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e
#define ICON_FA_FILE_FRAGMENT "\xee\x9a\x97" // U+e697
#define ICON_FA_FILE_HALF_DASHED "\xee\x9a\x98" // U+e698
#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5
#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f
#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570
@@ -585,6 +600,7 @@
#define ICON_FA_GEAR "\xef\x80\x93" // U+f013
#define ICON_FA_GEARS "\xef\x82\x85" // U+f085
#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5
#define ICON_FA_GEMINI "\xee\xa1\x89" // U+e849
#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d
#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2
#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b
@@ -642,8 +658,6 @@
#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684
#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5
#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4
#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6
#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f
#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060
#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6
#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0
@@ -657,7 +671,6 @@
#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064
#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc
#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025
#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f
#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590
#define ICON_FA_HEART "\xef\x80\x84" // U+f004
#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc
@@ -672,6 +685,9 @@
#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502
#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807
#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503
#define ICON_FA_HEXAGON "\xef\x8c\x92" // U+f312
#define ICON_FA_HEXAGON_NODES "\xee\x9a\x99" // U+e699
#define ICON_FA_HEXAGON_NODES_BOLT "\xee\x9a\x9a" // U+e69a
#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591
#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507
#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508
@@ -767,8 +783,10 @@
#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a
#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337
#define ICON_FA_LEMON "\xef\x82\x94" // U+f094
#define ICON_FA_LEO "\xee\xa1\x8a" // U+e84a
#define ICON_FA_LESS_THAN "<" // U+003c
#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537
#define ICON_FA_LIBRA "\xee\xa1\x8b" // U+e84b
#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd
#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb
#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e
@@ -842,6 +860,7 @@
#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527
#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf
#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd
#define ICON_FA_MOBILE_VIBRATE "\xee\xa0\x96" // U+e816
#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6
#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1
#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b
@@ -871,6 +890,7 @@
#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff
#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c
#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea
#define ICON_FA_NON_BINARY "\xee\xa0\x87" // U+e807
#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e
#define ICON_FA_NOTDEF "\xee\x87\xbe" // U+e1fe
#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249
@@ -878,6 +898,7 @@
#define ICON_FA_O "O" // U+004f
#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247
#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248
#define ICON_FA_OCTAGON "\xef\x8c\x86" // U+f306
#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613
#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532
#define ICON_FA_OM "\xef\x99\xb9" // U+f679
@@ -906,6 +927,7 @@
#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae
#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044
#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303
#define ICON_FA_PENTAGON "\xee\x9e\x90" // U+e790
#define ICON_FA_PEOPLE_ARROWS "\xee\x81\xa8" // U+e068
#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce
#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533
@@ -968,8 +990,10 @@
#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd
#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0
#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c
#define ICON_FA_PICTURE_IN_PICTURE "\xee\xa0\x8b" // U+e80b
#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3
#define ICON_FA_PILLS "\xef\x92\x84" // U+f484
#define ICON_FA_PISCES "\xee\xa1\x8c" // U+e84c
#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818
#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f
#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072
@@ -1060,6 +1084,7 @@
#define ICON_FA_S "S" // U+0053
#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d
#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a
#define ICON_FA_SAGITTARIUS "\xee\xa1\x8d" // U+e84d
#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445
#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf
#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0
@@ -1073,6 +1098,7 @@
#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e
#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f
#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4
#define ICON_FA_SCORPIO "\xee\xa1\x8e" // U+e84e
#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a
#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9
#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e
@@ -1080,6 +1106,7 @@
#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2
#define ICON_FA_SECTION "\xee\x91\x87" // U+e447
#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8
#define ICON_FA_SEPTAGON "\xee\xa0\xa0" // U+e820
#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233
#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f
#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064
@@ -1108,6 +1135,8 @@
#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7
#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277
#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4
#define ICON_FA_SINGLE_QUOTE_LEFT "\xee\xa0\x9b" // U+e81b
#define ICON_FA_SINGLE_QUOTE_RIGHT "\xee\xa0\x9c" // U+e81c
#define ICON_FA_SINK "\xee\x81\xad" // U+e06d
#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8
#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c
@@ -1131,12 +1160,14 @@
#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891
#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717
#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110
#define ICON_FA_SPIRAL "\xee\xa0\x8a" // U+e80a
#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc
#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5
#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd
#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0
#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8
#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c
#define ICON_FA_SQUARE_BINARY "\xee\x9a\x9b" // U+e69b
#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150
#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191
#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152
@@ -1194,7 +1225,10 @@
#define ICON_FA_T "T" // U+0054
#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce
#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a
#define ICON_FA_TABLE_CELLS_COLUMN_LOCK "\xee\x99\xb8" // U+e678
#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009
#define ICON_FA_TABLE_CELLS_ROW_LOCK "\xee\x99\xba" // U+e67a
#define ICON_FA_TABLE_CELLS_ROW_UNLOCK "\xee\x9a\x91" // U+e691
#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db
#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b
#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d
@@ -1208,6 +1242,7 @@
#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db
#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b
#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c
#define ICON_FA_TAURUS "\xee\xa1\x8f" // U+e84f
#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba
#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e
#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f
@@ -1235,6 +1270,7 @@
#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165
#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164
#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d
#define ICON_FA_THUMBTACK_SLASH "\xee\x9a\x8f" // U+e68f
#define ICON_FA_TICKET "\xef\x85\x85" // U+f145
#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff
#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c
@@ -1310,8 +1346,6 @@
#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501
#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500
#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728
#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406
#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa
#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502
#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503
#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504
@@ -1336,7 +1370,6 @@
#define ICON_FA_V "V" // U+0056
#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6
#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5
#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb
#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221
#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226
#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228
@@ -1349,6 +1382,7 @@
#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d
#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2
#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7
#define ICON_FA_VIRGO "\xee\xa1\x90" // U+e850
#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074
#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8
#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9
@@ -1357,6 +1391,7 @@
#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897
#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770
#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f
#define ICON_FA_VOLUME "\xef\x9a\xa8" // U+f6a8
#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028
#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027
#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026
@@ -1372,6 +1407,7 @@
#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773
#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5
#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e
#define ICON_FA_WEB_AWESOME "\xee\x9a\x82" // U+e682
#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd
#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496
#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd

View File

@@ -1,52 +1,60 @@
#include "IconsFontAwesome6.h"
#include "TracyAchievements.hpp"
#include "TracyImGui.hpp"
#include "TracySourceContents.hpp"
#include "TracyWeb.hpp"
#include "../Fonts.hpp"
#include "TracyEmbed.hpp"
#include "data/Text100Million.hpp"
#include "data/TextConnectToClient.hpp"
#include "data/TextFindZone.hpp"
#include "data/TextFrameImages.hpp"
#include "data/TextGlobalSettings.hpp"
#include "data/TextInstrumentFrames.hpp"
#include "data/TextInstrumentationIntro.hpp"
#include "data/TextInstrumentationStatistics.hpp"
#include "data/TextIntro.hpp"
#include "data/TextLoadTrace.hpp"
#include "data/TextSamplingIntro.hpp"
#include "data/TextSaveTrace.hpp"
namespace tracy::data
{
AchievementItem ai_samplingIntro = { "samplingIntro", "Sampling program execution", [](){
ImGui::TextWrapped( "Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often." );
ImGui::TextWrapped( "While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return." );
ImGui::TextWrapped( "Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work." );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information." );
ImGui::PopStyleColor();
ImGui::PopFont();
} };
static std::string UnpackImpl( size_t size, size_t lz4Size, const uint8_t* data )
{
std::string ret;
const EmbedData unembed( size, lz4Size, data );
ret.assign( unembed.data(), unembed.size() );
return ret;
}
#define Unpack( name ) UnpackImpl( Embed::name##Size, Embed::name##Lz4Size, Embed::name##Data )
AchievementItem ai_samplingIntro = {
.id = "samplingIntro",
.name = "Sampling program execution",
.text = Unpack( TextSamplingIntro ),
};
AchievementItem* ac_samplingItems[] = { &ai_samplingIntro, nullptr };
AchievementCategory ac_sampling = { "sampling", "Sampling", ac_samplingItems };
AchievementItem ai_100million = { "100million", "It's over 100 million!", [](){
ImGui::TextWrapped( "Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!" );
ImGui::TextWrapped( "Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it." );
ImGui::TextWrapped( "Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case." );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory." );
ImGui::TextWrapped( "To capture 100 million zones, you will need approximately 4 GB of RAM." );
ImGui::PopStyleColor();
ImGui::PopFont();
} };
AchievementItem ai_100million = {
.id = "100million",
.name = "It's over 100 million!",
.text = Unpack( Text100Million )
};
AchievementItem ai_instrumentationStatistics = { "instrumentationStatistics", "Show me the stats!", [](){
ImGui::TextWrapped( "Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called." );
ImGui::TextWrapped( "To view the statistics, click on the \"" ICON_FA_ARROW_UP_WIDE_SHORT " Statistics\" button on the top bar. This will open a new window with a list of all zones in the trace." );
} };
AchievementItem ai_instrumentationStatistics = {
.id = "instrumentationStatistics",
.name = "Show me the stats!",
.text = Unpack( TextInstrumentationStatistics )
};
AchievementItem ai_findZone = { "findZone", "Find some zones", [](){
ImGui::TextWrapped( "You can search for zones in the trace by opening the search window with the \"" ICON_FA_MAGNIFYING_GLASS " Find zone\" button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code." );
ImGui::TextWrapped( "The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them." );
ImGui::TextWrapped( "Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone." );
ImGui::TextWrapped( "When a zone is selected, a number of statistics are displayed to help you understand the performance of your application. In addition, a histogram of the zone execution times is displayed to make it easier for you to determine the performance of the profiled code. Be sure to select a zone with a large number of calls to make the histogram look interesting!" );
ImGui::TextWrapped( "Note that you can draw a range on the histogram to limit the number of entries displayed in the zone list below. This list allows you to examine each zone individually. There are also a number of zone groupings that you can select. Each group can be selected and the time associated with the selected group will be highlighted on the histogram." );
} };
AchievementItem ai_findZone = {
.id = "findZone",
.name = "Find some zones",
.text = Unpack( TextFindZone )
};
AchievementItem* ac_instrumentationIntroItems[] = {
&ai_100million,
@@ -55,90 +63,46 @@ AchievementItem* ac_instrumentationIntroItems[] = {
nullptr
};
AchievementItem ai_instrumentationIntro = { "instrumentationIntro", "Instrumentating your application", [](){
constexpr const char* src = R"(#include "Tracy.hpp"
AchievementItem ai_instrumentationIntro = {
.id = "instrumentationIntro",
.name = "Instrumentating your application",
.text = Unpack( TextInstrumentationIntro ),
.items = ac_instrumentationIntroItems
};
void SomeFunction()
{
ZoneScoped;
// Your code here
}
)";
static SourceContents sc;
sc.Parse( src );
ImGui::TextWrapped( "Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up." );
ImGui::TextWrapped( "To get started, open a source file and include the Tracy.hpp header. This will give you access to a variety of macros provided by Tracy. Next, add the ZoneScoped macro to the beginning of one of your functions, like this:" );
ImGui::PushFont( g_fonts.mono, FontNormal );
PrintSource( sc.get() );
ImGui::PopFont();
ImGui::TextWrapped( "Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called." );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Note: The ZoneScoped macro is just one of the many macros provided by Tracy. See the documentation for more information." );
ImGui::TextWrapped( "The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information." );
ImGui::PopStyleColor();
ImGui::PopFont();
}, ac_instrumentationIntroItems };
AchievementItem ai_frameImages = { "frameImages", "A picture is worth a thousand words", [](){
ImGui::TextWrapped( "Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the FrameImage macro." );
ImGui::TextWrapped( "You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way." );
ImGui::TextWrapped( "The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen." );
ImGui::TextWrapped( "You can even view a recording of what your application was doing by clicking the " ICON_FA_SCREWDRIVER_WRENCH " icon and then selecting the \"" ICON_FA_PLAY " Playback\" option. Try it out!" );
ImGui::TextWrapped( "The FrameImage macro is a great way to see what happened in your application at a particular time. Maybe you have a performance problem that only occurs when a certain object is on the screen?" );
} };
AchievementItem ai_frameImages = {
.id = "frameImages",
.name = "A picture is worth a thousand words",
.text = Unpack( TextFrameImages )
};
AchievementItem* ac_instrumentFramesItems[] = {
&ai_frameImages,
nullptr
};
AchievementItem ai_instrumentFrames = { "instrumentFrames", "Instrumenting frames", [](){
constexpr const char* src = R"(#include "Tracy.hpp"
void Render()
{
// Render the frame
SwapBuffers();
FrameMark;
}
)";
static SourceContents sc;
sc.Parse( src );
ImGui::TextWrapped( "In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application." );
ImGui::TextWrapped( "To instrument frames, you need to add the FrameMark macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame." );
ImGui::PushFont( g_fonts.mono, FontNormal );
PrintSource( sc.get() );
ImGui::PopFont();
ImGui::TextWrapped( "When you profile your application, you will see a new frame appear on the timeline each time the FrameMark macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second." );
ImGui::TextWrapped( "The FrameMark macro is a great way to see at a glance how your application is performing over time. Maybe there are some performance problems that only appear after a few minutes of running the application? A frame graph is drawn at the top of the profiler window where you can see the timing of all frames." );
ImGui::TextWrapped( "Note that some applications do not have a frame-based structure, and in such cases, frame instrumentation may not be useful. That's ok." );
}, ac_instrumentFramesItems };
AchievementItem ai_instrumentFrames = {
.id = "instrumentFrames",
.name = "Instrumenting frames",
.text = Unpack( TextInstrumentFrames ),
.items = ac_instrumentFramesItems
};
AchievementItem* ac_instrumentationItems[] = { &ai_instrumentationIntro, &ai_instrumentFrames, nullptr };
AchievementCategory ac_instrumentation = { "instrumentation", "Instrumentation", ac_instrumentationItems };
AchievementItem ai_loadTrace = { "loadTrace", "Load a trace", [](){
ImGui::TextWrapped( "You can open a previously saved trace file (or one received from a friend) with the \"" ICON_FA_FOLDER_OPEN " Open saved trace\" button on the welcome screen." );
} };
AchievementItem ai_loadTrace = {
.id = "loadTrace",
.name = "Load a trace",
.text = Unpack( TextLoadTrace )
};
AchievementItem ai_saveTrace = { "saveTrace", "Save a trace", [](){
ImGui::TextWrapped( "Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the " ICON_FA_WIFI " icon in the top left corner of the screen and then clicking on the \"" ICON_FA_FLOPPY_DISK " Save trace\" button." );
ImGui::TextWrapped( "Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before." );
ImGui::TextWrapped( "You can also share the trace with your friends or co-workers by sending them the trace file." );
ImGui::Spacing();
tracy::TextColoredUnformatted( 0xFF44FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::SameLine();
ImGui::TextUnformatted( "Warning" );
ImGui::SameLine();
tracy::TextColoredUnformatted( 0xFF44FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::TextWrapped( "Trace files can contain sensitive information about your application, such as program code, or even the contents of source files. Be careful when sharing them with others." );
} };
AchievementItem ai_saveTrace = {
.id = "saveTrace",
.name = "Save a trace",
.text = Unpack( TextSaveTrace )
};
AchievementItem* ac_connectToServerItems[] = {
&ai_saveTrace,
@@ -152,23 +116,19 @@ AchievementItem* ac_connectToServerUnlock[] = {
nullptr
};
AchievementItem ai_connectToServer = { "connectToClient", "First profiling session", [](){
ImGui::TextWrapped( "Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:" );
ImGui::TextWrapped( " 1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project." );
ImGui::TextWrapped( " 2. Make sure that TracyClient.cpp (or the Tracy library) is included in your build." );
ImGui::TextWrapped( " 3. Define TRACY_ENABLE in your build configuration, for the whole application. Do not do it in a single source file because it won't work." );
ImGui::TextWrapped( " 4. Start your application, and \"" ICON_FA_WIFI " Connect\" to it with the profiler." );
ImGui::TextWrapped( "Please refer to the user manual for more details." );
if( ImGui::SmallButton( "Download the user manual" ) )
{
tracy::OpenWebpage( "https://github.com/wolfpld/tracy/releases" );
}
}, ac_connectToServerItems, ac_connectToServerUnlock };
AchievementItem ai_connectToServer = {
.id = "connectToClient",
.name = "First profiling session",
.text = Unpack( TextConnectToClient ),
.items = ac_connectToServerItems,
.unlocks = ac_connectToServerUnlock
};
AchievementItem ai_globalSettings = { "globalSettings", "Global settings", [](){
ImGui::TextWrapped( "Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the " ICON_FA_WRENCH " icon on the welcome screen. This will open the about window, where you can expand the \"" ICON_FA_TOOLBOX " Global settings\" menu." );
ImGui::TextWrapped( "The settings are saved between sessions, so you only need to set them once." );
} };
AchievementItem ai_globalSettings = {
.id = "globalSettings",
.name = "Global settings",
.text = Unpack( TextGlobalSettings )
};
AchievementItem* ac_achievementsIntroItems[] = {
&ai_connectToServer,
@@ -176,18 +136,14 @@ AchievementItem* ac_achievementsIntroItems[] = {
nullptr
};
AchievementItem ai_achievementsIntro = { "achievementsIntro", "Click here to discover achievements!", [](){
ImGui::TextWrapped( "Clicking on the " ICON_FA_STAR " button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done." );
ImGui::TextWrapped( "As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!" );
ImGui::TextWrapped( "To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements " ICON_FA_STAR " button will glow yellow when there are new things to see." );
ImGui::TextUnformatted( "New tasks:" );
ImGui::SameLine();
TextColoredUnformatted( 0xFF4488FF, ICON_FA_CIRCLE_EXCLAMATION );
ImGui::TextUnformatted( "Completed tasks:" );
ImGui::SameLine();
TextColoredUnformatted( 0xFF44FF44, ICON_FA_CIRCLE_CHECK );
ImGui::TextWrapped( "Good luck!" );
}, ac_achievementsIntroItems, nullptr, true, 1 };
AchievementItem ai_achievementsIntro = {
.id = "achievementsIntro",
.name = "Click here to discover achievements!",
.text = Unpack( TextIntro ),
.items = ac_achievementsIntroItems,
.keepOpen = true,
.unlockTime = 1
};
AchievementItem* ac_firstStepsItems[] = { &ai_achievementsIntro, nullptr };
AchievementCategory ac_firstSteps = { "firstSteps", "First steps", ac_firstStepsItems, 1 };

View File

@@ -20,7 +20,7 @@ struct AchievementItem
{
const char* id;
const char* name;
void(*description)();
std::string text;
AchievementItem** items;
AchievementItem** unlocks;
bool keepOpen;

View File

@@ -3,7 +3,7 @@
#include "imgui.h"
#include "../Fonts.hpp"
#include "IconsFontAwesome6.h"
#include "IconsFontAwesome7.h"
#include "TracyBadVersion.hpp"
#include "TracyImGui.hpp"
#include "TracyWeb.hpp"

View File

@@ -42,10 +42,18 @@ void LoadConfig()
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;
if( v2 = ini_get( ini, "llm", "fastModel" ); v2 ) s_config.llmFastModel = v2;
if( v2 = ini_get( ini, "llm", "embeddings" ); v2 ) s_config.llmEmbeddingsModel = v2;
if( v2 = ini_get( ini, "llm", "useragent" ); v2 ) s_config.llmUserAgent = v2;
if( v2 = ini_get( ini, "llm", "searchIdentifier" ); v2 ) s_config.llmSearchIdentifier = v2;
if( v2 = ini_get( ini, "llm", "searchApiKey" ); v2 ) s_config.llmSearchApiKey = v2;
if( v2 = ini_get( ini, "llm", "searchBraveApiKey" ); v2 ) s_config.llmSearchBraveApiKey = v2;
if( ini_sget( ini, "llm", "annotateCallstacks", "%d", &v ) ) s_config.llmAnnotateCallstacks = v;
if( ini_sget( ini, "llm", "limitToolReplySize", "%d", &v ) ) s_config.llmLimitToolReplySize = v;
if( ini_sget( ini, "llm", "maxToolReplySizeValue", "%d", &v ) ) s_config.llmMaxToolReplySizeValue = v;
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;
ini_free( ini );
}
@@ -89,10 +97,18 @@ bool SaveConfig()
fprintf( f, "enabled = %i\n", (int)s_config.llm );
fprintf( f, "address = %s\n", s_config.llmAddress.c_str() );
fprintf( f, "model = %s\n", s_config.llmModel.c_str() );
fprintf( f, "fastModel = %s\n", s_config.llmFastModel.c_str() );
fprintf( f, "embeddings = %s\n", s_config.llmEmbeddingsModel.c_str() );
fprintf( f, "useragent = %s\n", s_config.llmUserAgent.c_str() );
fprintf( f, "searchIdentifier = %s\n", s_config.llmSearchIdentifier.c_str() );
fprintf( f, "searchApiKey = %s\n", s_config.llmSearchApiKey.c_str() );
fprintf( f, "searchBraveApiKey = %s\n", s_config.llmSearchBraveApiKey.c_str() );
fprintf( f, "annotateCallstacks = %i\n", (int)s_config.llmAnnotateCallstacks );
fprintf( f, "limitToolReplySize = %i\n", (int)s_config.llmLimitToolReplySize );
fprintf( f, "maxToolReplySizeValue = %i\n", s_config.llmMaxToolReplySizeValue );
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 );
fclose( f );
return true;

View File

@@ -36,12 +36,20 @@ struct Config
#else
bool llm = true;
#endif
std::string llmAddress = "http://localhost:11434";
std::string llmAddress = "http://localhost:8080";
std::string llmModel;
std::string llmFastModel;
std::string llmEmbeddingsModel;
std::string llmUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
std::string llmSearchIdentifier;
std::string llmSearchApiKey;
std::string llmSearchBraveApiKey;
bool llmSeparateFastModel = true;
bool llmAnnotateCallstacks = false;
bool llmLimitToolReplySize = false;
int llmMaxToolReplySizeValue = 48*1024;
bool llmSummary = true;
bool llmSuggestion = true;
};
extern Config s_config;

View File

@@ -0,0 +1,982 @@
#include <capstone.h>
#include <string.h>
#include "TracyDisassembly.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../server/TracyWorker.hpp"
namespace tracy
{
static RegsX86 s_regMapX86[X86_REG_ENDING];
struct InitRegMapX86
{
InitRegMapX86()
{
memset( s_regMapX86, 0, sizeof( s_regMapX86 ) );
s_regMapX86[X86_REG_EFLAGS] = RegsX86::flags;
s_regMapX86[X86_REG_AH] = RegsX86::rax;
s_regMapX86[X86_REG_AL] = RegsX86::rax;
s_regMapX86[X86_REG_AX] = RegsX86::rax;
s_regMapX86[X86_REG_EAX] = RegsX86::rax;
s_regMapX86[X86_REG_RAX] = RegsX86::rax;
s_regMapX86[X86_REG_BH] = RegsX86::rbx;
s_regMapX86[X86_REG_BL] = RegsX86::rbx;
s_regMapX86[X86_REG_BX] = RegsX86::rbx;
s_regMapX86[X86_REG_EBX] = RegsX86::rbx;
s_regMapX86[X86_REG_RBX] = RegsX86::rbx;
s_regMapX86[X86_REG_CH] = RegsX86::rcx;
s_regMapX86[X86_REG_CL] = RegsX86::rcx;
s_regMapX86[X86_REG_CX] = RegsX86::rcx;
s_regMapX86[X86_REG_ECX] = RegsX86::rcx;
s_regMapX86[X86_REG_RCX] = RegsX86::rcx;
s_regMapX86[X86_REG_DH] = RegsX86::rdx;
s_regMapX86[X86_REG_DL] = RegsX86::rdx;
s_regMapX86[X86_REG_DX] = RegsX86::rdx;
s_regMapX86[X86_REG_EDX] = RegsX86::rdx;
s_regMapX86[X86_REG_RDX] = RegsX86::rdx;
s_regMapX86[X86_REG_SIL] = RegsX86::rsi;
s_regMapX86[X86_REG_SI] = RegsX86::rsi;
s_regMapX86[X86_REG_ESI] = RegsX86::rsi;
s_regMapX86[X86_REG_RSI] = RegsX86::rsi;
s_regMapX86[X86_REG_DIL] = RegsX86::rdi;
s_regMapX86[X86_REG_DI] = RegsX86::rdi;
s_regMapX86[X86_REG_EDI] = RegsX86::rdi;
s_regMapX86[X86_REG_RDI] = RegsX86::rdi;
s_regMapX86[X86_REG_BPL] = RegsX86::rbp;
s_regMapX86[X86_REG_BP] = RegsX86::rbp;
s_regMapX86[X86_REG_EBP] = RegsX86::rbp;
s_regMapX86[X86_REG_RBP] = RegsX86::rbp;
s_regMapX86[X86_REG_SPL] = RegsX86::rsp;
s_regMapX86[X86_REG_SP] = RegsX86::rsp;
s_regMapX86[X86_REG_ESP] = RegsX86::rsp;
s_regMapX86[X86_REG_RSP] = RegsX86::rsp;
s_regMapX86[X86_REG_R8B] = RegsX86::r8;
s_regMapX86[X86_REG_R8W] = RegsX86::r8;
s_regMapX86[X86_REG_R8D] = RegsX86::r8;
s_regMapX86[X86_REG_R8] = RegsX86::r8;
s_regMapX86[X86_REG_R9B] = RegsX86::r9;
s_regMapX86[X86_REG_R9W] = RegsX86::r9;
s_regMapX86[X86_REG_R9D] = RegsX86::r9;
s_regMapX86[X86_REG_R9] = RegsX86::r9;
s_regMapX86[X86_REG_R10B] = RegsX86::r10;
s_regMapX86[X86_REG_R10W] = RegsX86::r10;
s_regMapX86[X86_REG_R10D] = RegsX86::r10;
s_regMapX86[X86_REG_R10] = RegsX86::r10;
s_regMapX86[X86_REG_R11B] = RegsX86::r11;
s_regMapX86[X86_REG_R11W] = RegsX86::r11;
s_regMapX86[X86_REG_R11D] = RegsX86::r11;
s_regMapX86[X86_REG_R11] = RegsX86::r11;
s_regMapX86[X86_REG_R12B] = RegsX86::r12;
s_regMapX86[X86_REG_R12W] = RegsX86::r12;
s_regMapX86[X86_REG_R12D] = RegsX86::r12;
s_regMapX86[X86_REG_R12] = RegsX86::r12;
s_regMapX86[X86_REG_R13B] = RegsX86::r13;
s_regMapX86[X86_REG_R13W] = RegsX86::r13;
s_regMapX86[X86_REG_R13D] = RegsX86::r13;
s_regMapX86[X86_REG_R13] = RegsX86::r13;
s_regMapX86[X86_REG_R14B] = RegsX86::r14;
s_regMapX86[X86_REG_R14W] = RegsX86::r14;
s_regMapX86[X86_REG_R14D] = RegsX86::r14;
s_regMapX86[X86_REG_R14] = RegsX86::r14;
s_regMapX86[X86_REG_R15B] = RegsX86::r15;
s_regMapX86[X86_REG_R15W] = RegsX86::r15;
s_regMapX86[X86_REG_R15D] = RegsX86::r15;
s_regMapX86[X86_REG_R15] = RegsX86::r15;
s_regMapX86[X86_REG_MM0] = RegsX86::mm0;
s_regMapX86[X86_REG_MM1] = RegsX86::mm1;
s_regMapX86[X86_REG_MM2] = RegsX86::mm2;
s_regMapX86[X86_REG_MM3] = RegsX86::mm3;
s_regMapX86[X86_REG_MM4] = RegsX86::mm4;
s_regMapX86[X86_REG_MM5] = RegsX86::mm5;
s_regMapX86[X86_REG_MM6] = RegsX86::mm6;
s_regMapX86[X86_REG_MM7] = RegsX86::mm7;
s_regMapX86[X86_REG_ST0] = RegsX86::mm0;
s_regMapX86[X86_REG_ST1] = RegsX86::mm1;
s_regMapX86[X86_REG_ST2] = RegsX86::mm2;
s_regMapX86[X86_REG_ST3] = RegsX86::mm3;
s_regMapX86[X86_REG_ST4] = RegsX86::mm4;
s_regMapX86[X86_REG_ST5] = RegsX86::mm5;
s_regMapX86[X86_REG_ST6] = RegsX86::mm6;
s_regMapX86[X86_REG_ST7] = RegsX86::mm7;
s_regMapX86[X86_REG_XMM0] = RegsX86::xmm0;
s_regMapX86[X86_REG_YMM0] = RegsX86::xmm0;
s_regMapX86[X86_REG_ZMM0] = RegsX86::xmm0;
s_regMapX86[X86_REG_XMM1] = RegsX86::xmm1;
s_regMapX86[X86_REG_YMM1] = RegsX86::xmm1;
s_regMapX86[X86_REG_ZMM1] = RegsX86::xmm1;
s_regMapX86[X86_REG_XMM2] = RegsX86::xmm2;
s_regMapX86[X86_REG_YMM2] = RegsX86::xmm2;
s_regMapX86[X86_REG_ZMM2] = RegsX86::xmm2;
s_regMapX86[X86_REG_XMM3] = RegsX86::xmm3;
s_regMapX86[X86_REG_YMM3] = RegsX86::xmm3;
s_regMapX86[X86_REG_ZMM3] = RegsX86::xmm3;
s_regMapX86[X86_REG_XMM4] = RegsX86::xmm4;
s_regMapX86[X86_REG_YMM4] = RegsX86::xmm4;
s_regMapX86[X86_REG_ZMM4] = RegsX86::xmm4;
s_regMapX86[X86_REG_XMM5] = RegsX86::xmm5;
s_regMapX86[X86_REG_YMM5] = RegsX86::xmm5;
s_regMapX86[X86_REG_ZMM5] = RegsX86::xmm5;
s_regMapX86[X86_REG_XMM6] = RegsX86::xmm6;
s_regMapX86[X86_REG_YMM6] = RegsX86::xmm6;
s_regMapX86[X86_REG_ZMM6] = RegsX86::xmm6;
s_regMapX86[X86_REG_XMM7] = RegsX86::xmm7;
s_regMapX86[X86_REG_YMM7] = RegsX86::xmm7;
s_regMapX86[X86_REG_ZMM7] = RegsX86::xmm7;
s_regMapX86[X86_REG_XMM8] = RegsX86::xmm8;
s_regMapX86[X86_REG_YMM8] = RegsX86::xmm8;
s_regMapX86[X86_REG_ZMM8] = RegsX86::xmm8;
s_regMapX86[X86_REG_XMM9] = RegsX86::xmm9;
s_regMapX86[X86_REG_YMM9] = RegsX86::xmm9;
s_regMapX86[X86_REG_ZMM9] = RegsX86::xmm9;
s_regMapX86[X86_REG_XMM10] = RegsX86::xmm10;
s_regMapX86[X86_REG_YMM10] = RegsX86::xmm10;
s_regMapX86[X86_REG_ZMM10] = RegsX86::xmm10;
s_regMapX86[X86_REG_XMM11] = RegsX86::xmm11;
s_regMapX86[X86_REG_YMM11] = RegsX86::xmm11;
s_regMapX86[X86_REG_ZMM11] = RegsX86::xmm11;
s_regMapX86[X86_REG_XMM12] = RegsX86::xmm12;
s_regMapX86[X86_REG_YMM12] = RegsX86::xmm12;
s_regMapX86[X86_REG_ZMM12] = RegsX86::xmm12;
s_regMapX86[X86_REG_XMM13] = RegsX86::xmm13;
s_regMapX86[X86_REG_YMM13] = RegsX86::xmm13;
s_regMapX86[X86_REG_ZMM13] = RegsX86::xmm13;
s_regMapX86[X86_REG_XMM14] = RegsX86::xmm14;
s_regMapX86[X86_REG_YMM14] = RegsX86::xmm14;
s_regMapX86[X86_REG_ZMM14] = RegsX86::xmm14;
s_regMapX86[X86_REG_XMM15] = RegsX86::xmm15;
s_regMapX86[X86_REG_YMM15] = RegsX86::xmm15;
s_regMapX86[X86_REG_ZMM15] = RegsX86::xmm15;
s_regMapX86[X86_REG_XMM16] = RegsX86::xmm16;
s_regMapX86[X86_REG_YMM16] = RegsX86::xmm16;
s_regMapX86[X86_REG_ZMM16] = RegsX86::xmm16;
s_regMapX86[X86_REG_XMM17] = RegsX86::xmm17;
s_regMapX86[X86_REG_YMM17] = RegsX86::xmm17;
s_regMapX86[X86_REG_ZMM17] = RegsX86::xmm17;
s_regMapX86[X86_REG_XMM18] = RegsX86::xmm18;
s_regMapX86[X86_REG_YMM18] = RegsX86::xmm18;
s_regMapX86[X86_REG_ZMM18] = RegsX86::xmm18;
s_regMapX86[X86_REG_XMM19] = RegsX86::xmm19;
s_regMapX86[X86_REG_YMM19] = RegsX86::xmm19;
s_regMapX86[X86_REG_ZMM19] = RegsX86::xmm19;
s_regMapX86[X86_REG_XMM20] = RegsX86::xmm20;
s_regMapX86[X86_REG_YMM20] = RegsX86::xmm20;
s_regMapX86[X86_REG_ZMM20] = RegsX86::xmm20;
s_regMapX86[X86_REG_XMM21] = RegsX86::xmm21;
s_regMapX86[X86_REG_YMM21] = RegsX86::xmm21;
s_regMapX86[X86_REG_ZMM21] = RegsX86::xmm21;
s_regMapX86[X86_REG_XMM22] = RegsX86::xmm22;
s_regMapX86[X86_REG_YMM22] = RegsX86::xmm22;
s_regMapX86[X86_REG_ZMM22] = RegsX86::xmm22;
s_regMapX86[X86_REG_XMM23] = RegsX86::xmm23;
s_regMapX86[X86_REG_YMM23] = RegsX86::xmm23;
s_regMapX86[X86_REG_ZMM23] = RegsX86::xmm23;
s_regMapX86[X86_REG_XMM24] = RegsX86::xmm24;
s_regMapX86[X86_REG_YMM24] = RegsX86::xmm24;
s_regMapX86[X86_REG_ZMM24] = RegsX86::xmm24;
s_regMapX86[X86_REG_XMM25] = RegsX86::xmm25;
s_regMapX86[X86_REG_YMM25] = RegsX86::xmm25;
s_regMapX86[X86_REG_ZMM25] = RegsX86::xmm25;
s_regMapX86[X86_REG_XMM26] = RegsX86::xmm26;
s_regMapX86[X86_REG_YMM26] = RegsX86::xmm26;
s_regMapX86[X86_REG_ZMM26] = RegsX86::xmm26;
s_regMapX86[X86_REG_XMM27] = RegsX86::xmm27;
s_regMapX86[X86_REG_YMM27] = RegsX86::xmm27;
s_regMapX86[X86_REG_ZMM27] = RegsX86::xmm27;
s_regMapX86[X86_REG_XMM28] = RegsX86::xmm28;
s_regMapX86[X86_REG_YMM28] = RegsX86::xmm28;
s_regMapX86[X86_REG_ZMM28] = RegsX86::xmm28;
s_regMapX86[X86_REG_XMM29] = RegsX86::xmm29;
s_regMapX86[X86_REG_YMM29] = RegsX86::xmm29;
s_regMapX86[X86_REG_ZMM29] = RegsX86::xmm29;
s_regMapX86[X86_REG_XMM30] = RegsX86::xmm30;
s_regMapX86[X86_REG_YMM30] = RegsX86::xmm30;
s_regMapX86[X86_REG_ZMM30] = RegsX86::xmm30;
s_regMapX86[X86_REG_XMM31] = RegsX86::xmm31;
s_regMapX86[X86_REG_YMM31] = RegsX86::xmm31;
s_regMapX86[X86_REG_ZMM31] = RegsX86::xmm31;
s_regMapX86[X86_REG_K0] = RegsX86::k0;
s_regMapX86[X86_REG_K1] = RegsX86::k1;
s_regMapX86[X86_REG_K2] = RegsX86::k2;
s_regMapX86[X86_REG_K3] = RegsX86::k3;
s_regMapX86[X86_REG_K4] = RegsX86::k4;
s_regMapX86[X86_REG_K5] = RegsX86::k5;
s_regMapX86[X86_REG_K6] = RegsX86::k6;
s_regMapX86[X86_REG_K7] = RegsX86::k7;
}
};
static bool IsJumpConditionalX86( const char* op )
{
static constexpr const char* branchX86[] = {
"je", "jne", "jg", "jge", "ja", "jae", "jl", "jle", "jb", "jbe", "jo", "jno", "jz", "jnz", "js", "jns", "jcxz", "jecxz", "jrcxz", "loop", "loope",
"loopne", "loopnz", "loopz", "jnle", "jnl", "jnge", "jng", "jnbe", "jnb", "jnae", "jna", "jc", "jnc", "jp", "jpe", "jnp", "jpo", nullptr
};
auto ptr = branchX86;
while( *ptr ) if( strcmp( *ptr++, op ) == 0 ) return true;
return false;
}
DisasmData Disassemble( uint64_t symAddr, const Worker& worker )
{
DisasmData data = {};
data.disasmFail = -1;
if( symAddr == 0 ) return data;
data.cpuArch = worker.GetCpuArch();
if( data.cpuArch == CpuArchUnknown ) return data;
uint32_t len;
auto code = worker.GetSymbolCode( symAddr, len );
if( !code ) return data;
data.codeLen = len;
static InitRegMapX86 regMapInit;
csh handle;
cs_err rval = CS_ERR_ARCH;
switch( data.cpuArch )
{
case CpuArchX86:
rval = cs_open( CS_ARCH_X86, CS_MODE_32, &handle );
break;
case CpuArchX64:
rval = cs_open( CS_ARCH_X86, CS_MODE_64, &handle );
break;
case CpuArchArm32:
rval = cs_open( CS_ARCH_ARM, CS_MODE_ARM, &handle );
break;
case CpuArchArm64:
rval = cs_open( CS_ARCH_AARCH64, CS_MODE_ARM, &handle );
break;
default:
assert( false );
break;
}
if( rval != CS_ERR_OK ) return data;
Tokenizer tokenizer;
cs_option( handle, CS_OPT_DETAIL, CS_OPT_ON );
cs_option( handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL );
cs_insn* insn;
size_t cnt = cs_disasm( handle, (const uint8_t*)code, len, symAddr, 0, &insn );
if( cnt > 0 )
{
if( insn[cnt-1].address - symAddr + insn[cnt-1].size < len ) data.disasmFail = insn[cnt-1].address - symAddr;
int bytesMax = 0;
int mLenMax = 0;
int oLenMax = 0;
data.lines.reserve( cnt );
for( size_t i=0; i<cnt; i++ )
{
const auto& op = insn[i];
const auto& detail = *op.detail;
bool hasJump = false;
bool jumpConditional = false;
AsmOpType opType = AsmOpType::None;
for( auto j=0; j<detail.groups_count; j++ )
{
if( detail.groups[j] == CS_GRP_JUMP || detail.groups[j] == CS_GRP_CALL || detail.groups[j] == CS_GRP_RET )
{
hasJump = true;
break;
}
}
for( auto j=0; j<detail.groups_count; j++ )
{
if( detail.groups[j] == CS_GRP_JUMP && opType < AsmOpType::Jump ) opType = AsmOpType::Jump;
else if( detail.groups[j] == CS_GRP_BRANCH_RELATIVE && opType < AsmOpType::Branch ) opType = AsmOpType::Branch;
else if( detail.groups[j] == CS_GRP_CALL && opType < AsmOpType::Call ) opType = AsmOpType::Call;
else if( detail.groups[j] == CS_GRP_RET && opType < AsmOpType::Ret ) opType = AsmOpType::Ret;
else if( detail.groups[j] == CS_GRP_PRIVILEGE && opType < AsmOpType::Privileged )
{
opType = AsmOpType::Privileged;
break;
}
}
uint64_t jumpAddr = 0;
if( hasJump )
{
switch( data.cpuArch )
{
case CpuArchX86:
case CpuArchX64:
if( detail.x86.op_count == 1 && detail.x86.operands[0].type == X86_OP_IMM )
{
jumpAddr = (uint64_t)detail.x86.operands[0].imm;
}
jumpConditional = IsJumpConditionalX86( op.mnemonic );
break;
case CpuArchArm32:
if( detail.arm.op_count == 1 && detail.arm.operands[0].type == ARM_OP_IMM )
{
jumpAddr = (uint64_t)detail.arm.operands[0].imm;
}
break;
case CpuArchArm64:
if( detail.aarch64.op_count == 1 && detail.aarch64.operands[0].type == AARCH64_OP_IMM )
{
jumpAddr = (uint64_t)detail.aarch64.operands[0].imm;
}
break;
default:
assert( false );
break;
}
if( jumpAddr >= symAddr && jumpAddr < symAddr + len )
{
auto fit = std::lower_bound( insn, insn+cnt, jumpAddr, []( const auto& l, const auto& r ) { return l.address < r; } );
if( fit != insn+cnt && fit->address == jumpAddr )
{
const auto min = std::min( jumpAddr, op.address );
const auto max = std::max( jumpAddr, op.address );
auto it = data.jumpTable.find( jumpAddr );
if( it == data.jumpTable.end() )
{
data.jumpTable.emplace( jumpAddr, AsmJumpData { min, max, 0, { op.address } } );
}
else
{
if( it->second.min > min ) it->second.min = min;
else if( it->second.max < max ) it->second.max = max;
it->second.source.emplace_back( op.address );
}
}
else
{
jumpAddr = 0;
}
}
else
{
data.jumpOut.emplace( op.address );
}
}
std::vector<AsmOpParams> params;
switch( data.cpuArch )
{
case CpuArchX86:
case CpuArchX64:
for( uint8_t i=0; i<detail.x86.op_count; i++ )
{
uint8_t type = 0;
switch( detail.x86.operands[i].type )
{
case X86_OP_IMM:
type = 0;
break;
case X86_OP_REG:
type = 1;
break;
case X86_OP_MEM:
type = 2;
break;
default:
assert( false );
break;
}
params.emplace_back( AsmOpParams { type, uint16_t( detail.x86.operands[i].size * 8 ) } );
}
break;
case CpuArchArm32:
for( uint8_t i=0; i<detail.arm.op_count; i++ )
{
uint8_t type = 0;
switch( detail.arm.operands[i].type )
{
case ARM_OP_IMM:
type = 0;
break;
case ARM_OP_REG:
type = 1;
break;
case ARM_OP_MEM:
type = 2;
break;
default:
type = 255;
break;
}
params.emplace_back( AsmOpParams { type, 0 } );
}
break;
case CpuArchArm64:
for( uint8_t i=0; i<detail.aarch64.op_count; i++ )
{
uint8_t type = 0;
switch( detail.aarch64.operands[i].type )
{
case AARCH64_OP_IMM:
type = 0;
break;
case AARCH64_OP_REG:
type = 1;
break;
case AARCH64_OP_MEM:
type = 2;
break;
default:
type = 255;
break;
}
params.emplace_back( AsmOpParams { type, 0 } );
}
break;
default:
assert( false );
break;
}
AsmLeaData leaData = AsmLeaData::none;
if( ( data.cpuArch == CpuArchX64 || data.cpuArch == CpuArchX86 ) && op.id == X86_INS_LEA )
{
assert( op.detail->x86.op_count == 2 );
assert( op.detail->x86.operands[1].type == X86_OP_MEM );
auto& mem = op.detail->x86.operands[1].mem;
if( mem.base == X86_REG_INVALID )
{
if( mem.index == X86_REG_INVALID )
{
leaData = AsmLeaData::d;
}
else
{
leaData = mem.disp == 0 ? AsmLeaData::i : AsmLeaData::id;
}
}
else if( mem.base == X86_REG_RIP )
{
leaData = mem.disp == 0 ? AsmLeaData::r : AsmLeaData::rd;
}
else
{
if( mem.index == X86_REG_INVALID )
{
leaData = mem.disp == 0 ? AsmLeaData::b : AsmLeaData::bd;
}
else
{
leaData = mem.disp == 0 ? AsmLeaData::bi : AsmLeaData::bid;
}
}
}
data.lines.emplace_back( AsmLine { op.address, jumpAddr, op.mnemonic, op.op_str, (uint8_t)op.size, leaData, opType, jumpConditional, std::move( params ) } );
const auto& operands = data.lines.back().operands;
data.lines.back().opTokens = tokenizer.TokenizeAsm( operands.c_str(), operands.c_str() + operands.size() );
#if CS_API_MAJOR >= 4
auto& entry = data.lines.back();
cs_regs read, write;
uint8_t rcnt, wcnt;
cs_regs_access( handle, &op, read, &rcnt, write, &wcnt );
int idx;
switch( data.cpuArch )
{
case CpuArchX86:
case CpuArchX64:
assert( rcnt < sizeof( entry.readX86 ) );
assert( wcnt < sizeof( entry.writeX86 ) );
idx = 0;
for( int i=0; i<rcnt; i++ )
{
if( s_regMapX86[read[i]] != RegsX86::invalid ) entry.readX86[idx++] = s_regMapX86[read[i]];
entry.readX86[idx] = RegsX86::invalid;
}
idx = 0;
for( int i=0; i<wcnt; i++ )
{
if( s_regMapX86[write[i]] != RegsX86::invalid ) entry.writeX86[idx++] = s_regMapX86[write[i]];
entry.writeX86[idx] = RegsX86::invalid;
}
break;
default:
break;
}
#endif
const auto mLen = (int)strlen( op.mnemonic );
if( mLen > mLenMax ) mLenMax = mLen;
const auto oLen = (int)strlen( op.op_str );
if( oLen > oLenMax ) oLenMax = oLen;
if( op.size > bytesMax ) bytesMax = op.size;
uint32_t mLineMax = 0;
uint32_t srcline;
const auto srcidx = worker.GetLocationForAddress( op.address, srcline );
if( srcidx.Active() )
{
mLineMax = srcline;
const auto idx = srcidx.Idx();
auto sit = data.sourceFiles.find( idx );
if( sit == data.sourceFiles.end() ) data.sourceFiles.emplace( idx, srcline );
}
char tmp[16];
sprintf( tmp, "%" PRIu32, mLineMax );
data.maxLine = std::max( data.maxLine, strlen( tmp ) + 1 );
}
cs_free( insn, cnt );
data.maxMnemonicLen = mLenMax + 1;
data.maxOperandLen = oLenMax + 1;
data.maxAsmBytes = bytesMax;
if( !data.jumpTable.empty() )
{
struct JumpRange
{
uint64_t target;
uint64_t len;
};
std::vector<JumpRange> jumpRange;
jumpRange.reserve( data.jumpTable.size() );
for( auto& v : data.jumpTable )
{
pdqsort_branchless( v.second.source.begin(), v.second.source.end() );
jumpRange.emplace_back( JumpRange { v.first, v.second.max - v.second.min } );
}
pdqsort_branchless( jumpRange.begin(), jumpRange.end(), []( const auto& l, const auto& r ) { return l.len < r.len; } );
std::vector<std::vector<std::pair<uint64_t, uint64_t>>> levelRanges;
for( auto& v : jumpRange )
{
auto it = data.jumpTable.find( v.target );
assert( it != data.jumpTable.end() );
size_t level = 0;
for(;;)
{
assert( levelRanges.size() >= level );
if( levelRanges.size() == level )
{
it->second.level = level;
levelRanges.push_back( { { it->second.min, it->second.max } } );
break;
}
else
{
bool validFit = true;
auto& lr = levelRanges[level];
for( auto& range : lr )
{
assert( !( it->second.min >= range.first && it->second.max <= range.second ) );
if( it->second.min <= range.second && it->second.max >= range.first )
{
validFit = false;
break;
}
}
if( validFit )
{
it->second.level = level;
lr.emplace_back( it->second.min, it->second.max );
break;
}
level++;
}
}
if( level > data.maxJumpLevel ) data.maxJumpLevel = level;
}
uint32_t locNum = 0;
for( auto& v : data.lines )
{
if( data.jumpTable.contains( v.addr ) )
{
data.locMap.emplace( v.addr, locNum++ );
}
}
}
}
cs_close( &handle );
return data;
}
std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::vector<std::string>& sources, uint64_t symAddr, const AddrStatData& as, const unordered_flat_map<uint64_t, uint32_t>& locMap )
{
std::string line;
uint32_t srcline;
const auto srcidx = worker.GetLocationForAddress( opcode.addr, srcline );
if( srcidx.Active() )
{
size_t idx;
const auto file = worker.GetString( srcidx );
auto it = std::ranges::find( sources, file );
if( it == sources.end() )
{
idx = sources.size();
sources.emplace_back( file );
}
else
{
idx = std::distance( sources.begin(), it );
}
line = std::to_string( idx ) + ":" + std::to_string( srcline ) + ":";
}
else
{
line = "::";
}
line += "+" + std::to_string( opcode.addr - symAddr ) + ":";
const auto totalCost = as.ipTotalAsm.local + as.ipTotalAsm.ext;
if( totalCost != 0 )
{
char buf[32];
auto it = as.ipCountAsm.find( opcode.addr );
if( it != as.ipCountAsm.end() )
{
auto& stat = it->second;
if( stat.local != 0 )
{
snprintf( buf, sizeof(buf), "%.4f%%:", 100.0f * stat.local / totalCost );
line += buf;
}
else
{
line += ":";
}
if( stat.ext != 0 )
{
snprintf( buf, sizeof(buf), "%.4f%%:", 100.0f * stat.ext / totalCost );
line += buf;
}
else
{
line += ":";
}
}
else
{
line += "::";
}
}
else
{
line += "::";
}
line += opcode.mnemonic;
const char* jumpName = nullptr;
bool hasJump = false;
if( opcode.jumpAddr != 0 )
{
auto lit = locMap.find( opcode.jumpAddr );
if( lit != locMap.end() )
{
line += " .L" + std::to_string( lit->second );
hasJump = true;
}
else
{
uint32_t jumpOffset;
uint64_t jumpBase = worker.GetSymbolForAddress( opcode.jumpAddr, jumpOffset );
if( jumpBase && jumpBase != symAddr )
{
auto jumpSym = worker.GetSymbolData( jumpBase );
if( jumpSym )
{
if( worker.HasInlineSymbolAddresses() )
{
const auto jumpAddr = worker.GetInlineSymbolForAddress( opcode.jumpAddr );
if( jumpAddr != 0 )
{
const auto symData = worker.GetSymbolData( jumpAddr );
if( symData ) jumpName = worker.GetString( symData->name );
}
}
if( !jumpName ) jumpName = worker.GetString( jumpSym->name );
}
}
}
}
if( !hasJump && !opcode.operands.empty() ) line += " " + opcode.operands;
std::string label;
auto it = locMap.find( opcode.addr );
if( it != locMap.end() ) label = ".L" + std::to_string( it->second );
if( jumpName || !label.empty() )
{
line += ";";
if( !label.empty() ) line += " label: " + label;
if( jumpName ) line += " destination: " + std::string( jumpName );
}
return line;
}
nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& view )
{
auto sym = worker.GetSymbolData( symAddr );
if( !sym ) return nlohmann::json { { "error", "Symbol not found" } };
if( sym->isInline ) return nlohmann::json { { "error", "Symbol is inline" } };
auto data = Disassemble( symAddr, worker );
if( data.lines.empty() ) return nlohmann::json { { "error", "Disassembly failed" } };
const bool limitView = view.m_statRange.active;
AddrStatData as;
GatherIpStats( symAddr, as, worker, limitView, 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 );
iptr++;
}
}
GatherAdditionalIpStats( symAddr, as, worker, limitView, view, nullptr, false );
char tmp[32];
sprintf( tmp, "0x%" PRIx64, symAddr );
nlohmann::json json = {
{ "address", tmp },
{ "files", nlohmann::json::object() },
{ "hint", "Code lines format is: fileIdx:line:offset:cost:callCost:assembly. To decode file names, access files[fileIdx]." },
{ "symbol", worker.GetString( sym->name ) }
};
std::vector<std::string> sources;
std::string code;
for( auto& v: data.lines ) code += FormatDisassemblyLine( v, worker, sources, symAddr, as, data.locMap ) + "\n";
json["code"] = code;
for( size_t i = 0; i < sources.size(); ++i ) json["files"][std::to_string(i)] = sources[i];
return json;
}
void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
{
if( limitView )
{
auto vec = worker.GetSamplesForSymbol( baseAddr );
if( !vec ) return;
auto it = std::lower_bound( vec->begin(), vec->end(), view.m_statRange.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
if( it == vec->end() ) return;
auto end = std::lower_bound( it, vec->end(), view.m_statRange.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
as.ipTotalAsm.local += end - it;
while( it != end )
{
if( filename )
{
auto frame = worker.GetCallstackFrame( it->ip );
if( frame )
{
const auto end = propagateInlines ? frame->size : 1;
for( uint8_t i=0; i<end; i++ )
{
const auto line = frame->data[i].line;
if( line != 0 )
{
auto ffn = worker.GetString( frame->data[i].file );
if( strcmp( ffn, filename ) == 0 )
{
auto sit = as.ipCountSrc.find( line );
if( sit == as.ipCountSrc.end() )
{
as.ipCountSrc.emplace( line, AddrStat { 1, 0 } );
if( as.ipMaxSrc.local < 1 ) as.ipMaxSrc.local = 1;
}
else
{
const auto sum = sit->second.local + 1;
sit->second.local = sum;
if( as.ipMaxSrc.local < sum ) as.ipMaxSrc.local = sum;
}
as.ipTotalSrc.local++;
}
}
}
}
}
auto addr = worker.GetCanonicalPointer( it->ip );
auto sit = as.ipCountAsm.find( addr );
if( sit == as.ipCountAsm.end() )
{
as.ipCountAsm.emplace( addr, AddrStat{ 1, 0 } );
if( as.ipMaxAsm.local < 1 ) as.ipMaxAsm.local = 1;
}
else
{
const auto sum = sit->second.local + 1;
sit->second.local = sum;
if( as.ipMaxAsm.local < sum ) as.ipMaxAsm.local = sum;
}
++it;
}
}
else
{
auto ipmap = worker.GetSymbolInstructionPointers( baseAddr );
if( !ipmap ) return;
for( auto& ip : *ipmap )
{
auto addr = worker.GetCanonicalPointer( ip.first );
assert( as.ipCountAsm.find( addr ) == as.ipCountAsm.end() );
as.ipCountAsm.emplace( addr, AddrStat { ip.second, 0 } );
as.ipTotalAsm.local += ip.second;
if( as.ipMaxAsm.local < ip.second ) as.ipMaxAsm.local = ip.second;
if( filename )
{
auto frame = worker.GetCallstackFrame( ip.first );
if( frame )
{
const auto end = propagateInlines ? frame->size : 1;
for( uint8_t i=0; i<end; i++ )
{
const auto line = frame->data[i].line;
if( line != 0 )
{
auto ffn = worker.GetString( frame->data[i].file );
if( strcmp( ffn, filename ) == 0 )
{
auto it = as.ipCountSrc.find( line );
if( it == as.ipCountSrc.end() )
{
as.ipCountSrc.emplace( line, AddrStat{ ip.second, 0 } );
if( as.ipMaxSrc.local < ip.second ) as.ipMaxSrc.local = ip.second;
}
else
{
const auto sum = it->second.local + ip.second;
it->second.local = sum;
if( as.ipMaxSrc.local < sum ) as.ipMaxSrc.local = sum;
}
as.ipTotalSrc.local += ip.second;
}
}
}
}
}
}
}
}
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
{
if( !worker.AreSymbolSamplesReady() ) return;
auto sym = worker.GetSymbolData( baseAddr );
if( !sym ) return;
if( limitView )
{
for( uint64_t ip = baseAddr; ip < baseAddr + sym->size.Val(); ip++ )
{
auto cp = worker.GetChildSamples( ip );
if( !cp ) continue;
auto it = std::lower_bound( cp->begin(), cp->end(), view.m_statRange.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
if( it == cp->end() ) continue;
auto end = std::lower_bound( it, cp->end(), view.m_statRange.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
const auto ccnt = uint64_t( end - it );
auto eit = as.ipCountAsm.find( ip );
if( eit == as.ipCountAsm.end() )
{
as.ipCountAsm.emplace( ip, AddrStat { 0, ccnt } );
}
else
{
eit->second.ext += ccnt;
}
as.ipTotalAsm.ext += ccnt;
if( as.ipMaxAsm.ext < ccnt ) as.ipMaxAsm.ext = ccnt;
if( filename )
{
auto frame = worker.GetCallstackFrame( worker.PackPointer( ip ) );
if( frame )
{
const auto end = propagateInlines ? frame->size : 1;
for( uint8_t i=0; i<end; i++ )
{
const auto line = frame->data[i].line;
if( line != 0 )
{
auto ffn = worker.GetString( frame->data[i].file );
if( strcmp( ffn, filename ) == 0 )
{
auto sit = as.ipCountSrc.find( line );
if( sit == as.ipCountSrc.end() )
{
as.ipCountSrc.emplace( line, AddrStat{ 0, ccnt } );
if( as.ipMaxSrc.ext < ccnt ) as.ipMaxSrc.ext = ccnt;
}
else
{
const auto csum = sit->second.ext + ccnt;
sit->second.ext = csum;
if( as.ipMaxSrc.ext < csum ) as.ipMaxSrc.ext = csum;
}
as.ipTotalSrc.ext += ccnt;
}
}
}
}
}
}
}
else
{
for( uint64_t ip = baseAddr; ip < baseAddr + sym->size.Val(); ip++ )
{
auto cp = worker.GetChildSamples( ip );
if( !cp ) continue;
const auto ccnt = cp->size();
auto eit = as.ipCountAsm.find( ip );
if( eit == as.ipCountAsm.end() )
{
as.ipCountAsm.emplace( ip, AddrStat { 0, ccnt } );
}
else
{
eit->second.ext += ccnt;
}
as.ipTotalAsm.ext += ccnt;
if( as.ipMaxAsm.ext < ccnt ) as.ipMaxAsm.ext = ccnt;
if( filename )
{
auto frame = worker.GetCallstackFrame( worker.PackPointer( ip ) );
if( frame )
{
const auto end = propagateInlines ? frame->size : 1;
for( uint8_t i=0; i<end; i++ )
{
const auto line = frame->data[i].line;
if( line != 0 )
{
auto ffn = worker.GetString( frame->data[i].file );
if( strcmp( ffn, filename ) == 0 )
{
auto sit = as.ipCountSrc.find( line );
if( sit == as.ipCountSrc.end() )
{
as.ipCountSrc.emplace( line, AddrStat{ 0, ccnt } );
if( as.ipMaxSrc.ext < ccnt ) as.ipMaxSrc.ext = ccnt;
}
else
{
const auto csum = sit->second.ext + ccnt;
sit->second.ext = csum;
if( as.ipMaxSrc.ext < csum ) as.ipMaxSrc.ext = csum;
}
as.ipTotalSrc.ext += ccnt;
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,145 @@
#ifndef __TRACYDISASSEMBLY_HPP__
#define __TRACYDISASSEMBLY_HPP__
#include <nlohmann/json.hpp>
#include <stdint.h>
#include <string>
#include <vector>
#include "tracy_robin_hood.h"
#include "TracyProtocol.hpp"
#include "TracySourceTokenizer.hpp"
namespace tracy
{
class View;
class Worker;
enum class RegsX86 : uint8_t
{
invalid, flags,
rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15,
mm0, mm1, mm2, mm3, mm4, mm5, mm6, mm7,
xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9,
xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, xmm16, xmm17, xmm18, xmm19,
xmm20, xmm21, xmm22, xmm23, xmm24, xmm25, xmm26, xmm27, xmm28, xmm29,
xmm30, xmm31, k0, k1, k2, k3, k4, k5, k6, k7,
NUMBER_OF_ENTRIES
};
enum class AsmLeaData : uint8_t
{
none,
b,
bd,
bi,
bid,
d,
i,
id,
r,
rd
};
enum class AsmOpType : uint8_t
{
None,
Jump,
Branch,
Call,
Ret,
Privileged
};
struct AsmOpParams
{
uint8_t type;
uint16_t width;
};
struct AsmJumpData
{
uint64_t min;
uint64_t max;
size_t level;
std::vector<uint64_t> source;
};
struct AsmLine
{
uint64_t addr;
uint64_t jumpAddr;
std::string mnemonic;
std::string operands;
uint8_t len;
AsmLeaData leaData;
AsmOpType opType;
bool jumpConditional;
std::vector<AsmOpParams> params;
std::vector<Tokenizer::AsmToken> opTokens;
union
{
RegsX86 readX86[12];
};
union
{
RegsX86 writeX86[20];
};
uint16_t regData[20];
};
struct DisasmData
{
std::vector<AsmLine> lines;
unordered_flat_map<uint64_t, uint32_t> locMap;
unordered_flat_map<uint64_t, AsmJumpData> jumpTable;
unordered_flat_set<uint64_t> jumpOut;
unordered_flat_map<uint32_t, uint32_t> sourceFiles;
size_t maxJumpLevel;
int32_t disasmFail;
uint32_t codeLen;
size_t maxLine;
int maxMnemonicLen;
int maxOperandLen;
uint8_t maxAsmBytes;
CpuArchitecture cpuArch;
};
struct AddrStat
{
uint64_t local;
uint64_t ext;
AddrStat& operator+=( const AddrStat& other )
{
local += other.local;
ext += other.ext;
return *this;
}
};
struct AddrStatData
{
AddrStat ipTotalSrc = {};
AddrStat ipTotalAsm = {};
AddrStat ipMaxSrc = {};
AddrStat ipMaxAsm = {};
AddrStat hwMaxSrc = {};
AddrStat hwMaxAsm = {};
unordered_flat_map<uint64_t, AddrStat> ipCountSrc, ipCountAsm;
unordered_flat_map<uint64_t, AddrStat> hwCountSrc, hwCountAsm;
};
DisasmData Disassemble( uint64_t symAddr, const Worker& worker );
std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::vector<std::string>& sources, uint64_t symAddr, const AddrStatData& as, const unordered_flat_map<uint64_t, uint32_t>& locMap );
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 );
}
#endif

View File

@@ -1,5 +1,6 @@
#include <assert.h>
#include <algorithm>
#include <string>
#include "TracyPrint.hpp"
#include "TracyImGui.hpp"
@@ -127,25 +128,54 @@ void PrintSource( const std::vector<Tokenizer::Line>& lines )
}
}
bool PrintTextWrapped( const char* text, const char* end )
bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bool underline )
{
bool hovered = false;
if( !end ) end = text + strlen( text );
auto firstWord = text;
while( firstWord < end && *firstWord == ' ' ) firstWord++;
while( firstWord < end && *firstWord != ' ' && *firstWord != '\n' ) firstWord++;
auto fontSize = ImGui::GetFontSize();
const auto fontSize = ImGui::GetFontSize();
const auto fontSize05 = round( fontSize * 0.5f );
const auto scale = GetScale();
const auto color = ImGui::ColorConvertFloat4ToU32( ImGui::GetStyle().Colors[ImGuiCol_Text] );
auto left = ImGui::GetContentRegionAvail().x;
auto fwLen = ImGui::CalcTextSize( text, firstWord ).x;
if( fwLen > left )
{
const auto textPrev = text;
while( text < firstWord && *text == ' ' ) text++;
const auto prev = left;
ImGui::NewLine();
left = ImGui::GetContentRegionAvail().x;
if( left == prev )
{
ImGui::SameLine( 0, 0 ); // undo NewLine
text = textPrev;
}
}
auto endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
ImGui::TextUnformatted( text, endLine );
if( strikethrough || underline )
{
auto y1 = ImGui::GetCursorScreenPos().y + fontSize05;
auto y2 = ImGui::GetCursorScreenPos().y + fontSize;
auto x0 = ImGui::GetCursorScreenPos().x - scale;
ImGui::TextUnformatted( text, endLine );
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 );
}
else
{
ImGui::TextUnformatted( text, endLine );
}
if( !hovered ) hovered = ImGui::IsItemHovered();
left = ImGui::GetContentRegionAvail().x;
@@ -155,11 +185,46 @@ bool PrintTextWrapped( const char* text, const char* end )
if( *text == ' ' ) text++;
endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
if( text == endLine ) endLine++;
ImGui::TextUnformatted( text, endLine );
if( strikethrough || underline )
{
auto y1 = ImGui::GetCursorScreenPos().y + fontSize05;
auto y2 = ImGui::GetCursorScreenPos().y + fontSize;
auto x0 = ImGui::GetCursorScreenPos().x - scale;
ImGui::TextUnformatted( text, endLine );
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 );
}
else
{
ImGui::TextUnformatted( text, endLine );
}
if( !hovered ) hovered = ImGui::IsItemHovered();
}
return hovered;
}
bool DragHeightSplitter( const char* id, float& height, float minHeight, float maxHeight, float thickness )
{
ImGui::InvisibleButton( id, ImVec2( -1, thickness * 1.5f ) );
const bool active = ImGui::IsItemActive();
if( active ) height = std::clamp( height + ImGui::GetIO().MouseDelta.y, minHeight, maxHeight );
if( ImGui::IsItemHovered() || active ) ImGui::SetMouseCursor( ImGuiMouseCursor_ResizeNS );
auto color = ImGui::GetColorU32( ImGuiCol_Separator );
if( active ) color = ImGui::GetColorU32( ImGuiCol_SeparatorActive );
else if( ImGui::IsItemHovered() ) color = ImGui::GetColorU32( ImGuiCol_SeparatorHovered );
auto draw = ImGui::GetWindowDrawList();
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 );
return active;
}
}

View File

@@ -13,9 +13,11 @@
#include "imgui_internal.h"
#include "../public/common/TracyForceInline.hpp"
#include "IconsFontAwesome6.h"
#include "IconsFontAwesome7.h"
#include "TracySourceTokenizer.hpp"
ImTextureID GetProfilerIconTexture();
#if !IMGUI_DEFINE_MATH_OPERATORS
static inline ImVec2 operator+( const ImVec2& l, const ImVec2& r ) { return ImVec2( l.x + r.x, l.y + r.y ); }
static inline ImVec2 operator-( const ImVec2& l, const ImVec2& r ) { return ImVec2( l.x - r.x, l.y - r.y ); }
@@ -32,7 +34,8 @@ void DrawZigZag( ImDrawList* draw, const ImVec2& wpos, double start, double end,
void DrawStripedRect( ImDrawList* draw, const ImVec2& wpos, double x0, double y0, double x1, double y1, double sw, uint32_t color, bool fix_stripes_in_screen_space, bool inverted );
void DrawHistogramMinMaxLabel( ImDrawList* draw, int64_t tmin, int64_t tmax, ImVec2 wpos, float w, float ty );
void PrintSource( const std::vector<Tokenizer::Line>& lines );
bool PrintTextWrapped( const char* text, const char* end = nullptr );
bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bool underline );
bool DragHeightSplitter( const char* id, float& height, float minHeight, float maxHeight, float thickness );
static constexpr const uint32_t SyntaxColors[] = {
@@ -119,7 +122,7 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
ImGui::TextUnformatted( value );
}
[[maybe_unused]] static inline void DrawWaitingDots( double time )
[[maybe_unused]] static inline void DrawWaitingDotsCentered( double time )
{
s_wasActive = true;
ImGui::TextUnformatted( "" );
@@ -133,6 +136,19 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
draw->AddCircleFilled( wpos + ImVec2( w * 0.5f + ty, h ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f - 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
}
[[maybe_unused]] static inline void DrawWaitingDots( double time, bool windowPos = true, bool small = false )
{
s_wasActive = true;
const auto pos = ( windowPos ? ImGui::GetWindowPos() : ImVec2( 0, 0 ) ) + ImGui::GetCursorPos();
auto draw = ImGui::GetWindowDrawList();
const auto ty = ImGui::GetTextLineHeight();
const auto yOffset = ty * ( small ? 0.5f : 0.675f );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 0 * ty, yOffset ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f + 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 1 * ty, yOffset ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 2 * ty, yOffset ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f - 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
ImGui::Dummy( ImVec2( ty * 3, ty ) );
}
[[maybe_unused]] static inline bool SmallCheckbox( const char* label, bool* var )
{
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
@@ -263,13 +279,13 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
[[maybe_unused]] static tracy_force_inline void DrawLine( ImDrawList* draw, const ImVec2& v1, const ImVec2& v2, uint32_t col, float thickness = 1.0f )
{
const ImVec2 data[2] = { v1, v2 };
draw->AddPolyline( data, 2, col, 0, thickness );
draw->AddPolyline( data, 2, col, thickness );
}
[[maybe_unused]] static tracy_force_inline void DrawLine( ImDrawList* draw, const ImVec2& v1, const ImVec2& v2, const ImVec2& v3, uint32_t col, float thickness = 1.0f )
{
const ImVec2 data[3] = { v1, v2, v3 };
draw->AddPolyline( data, 3, col, 0, thickness );
draw->AddPolyline( data, 3, col, thickness );
}
[[maybe_unused]] static tracy_force_inline void TooltipIfHovered( const char* text )

File diff suppressed because it is too large Load Diff

View File

@@ -20,14 +20,23 @@ class TracyLlmApi;
class TracyLlmChat;
class TracyLlmTools;
class TracyManualData;
class View;
class Worker;
struct LlmSkill
{
std::string name;
std::string description;
std::string content;
};
class TracyLlm
{
enum class Task
{
Connect,
SendMessage,
FastMessage,
Tokenize
};
@@ -37,64 +46,87 @@ class TracyLlm
std::function<void()> callback;
std::function<void(nlohmann::json)> callback2;
std::string param;
nlohmann::json param2;
bool stop;
};
public:
TracyLlm( Worker& worker, const TracyManualData& manual );
TracyLlm( Worker& worker, View& view, const TracyManualData& manual );
~TracyLlm();
[[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_lock ); return m_busy; }
[[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_jobsLock ); return m_busy; }
void Draw();
void AddAttachment( std::string&& str, const char* role );
void AddMessage( std::string&& str, const char* role );
bool QueueSendMessage();
bool m_show = false;
void AddAttachmentLocking( std::string&& str, const char* role );
void AddMessageLocking( std::string&& str, const char* role );
bool QueueSendMessageLocking();
bool QueueFastMessageLocking( const nlohmann::json& req, std::function<void(nlohmann::json)> callback );
private:
void AddMessage( std::string&& str, const char* role );
bool QueueSendMessage();
bool QueueFastMessage( const nlohmann::json& req, std::function<void(nlohmann::json)> callback );
void WorkerThread();
void UpdateModels();
void ResetChat();
void UpdateSystemPrompt();
void QueueConnect();
void AddMessageBlocking( std::string&& str, const char* role, std::unique_lock<std::mutex>& lock );
// Will block, cannot enter with a taken lock
void AddMessageBlocking( std::string&& str, const char* role );
void AddMessageBlocking( nlohmann::json&& json );
void ManageContext( std::unique_lock<std::mutex>& lock );
void SendMessage( std::unique_lock<std::mutex>& lock );
void ManageContext();
void SendMessage();
void AppendResponse( const char* name, const nlohmann::json& delta );
bool OnResponse( const nlohmann::json& json );
void AddSkill( std::string&& name, std::string&& description, const std::shared_ptr<EmbedData>& content );
std::unique_ptr<TracyLlmApi> m_api;
std::unique_ptr<TracyLlmChat> m_chatUi;
std::unique_ptr<TracyLlmTools> m_tools;
int m_modelIdx;
int m_fastIdx;
int m_embedIdx;
std::atomic<bool> m_exit;
std::condition_variable m_cv;
std::thread m_thread;
mutable std::mutex m_lock;
mutable std::mutex m_jobsLock;
std::vector<std::shared_ptr<WorkItem>> m_jobs;
std::shared_ptr<WorkItem> m_currentJob;
bool m_busy = false;
bool m_focusInput = false;
int m_chatId = 0;
std::atomic<int> m_chatId {0};
int m_usedCtx = 0;
float m_temperature = 1.0f;
bool m_setTemperature = false;
bool m_allThinkingRegions = false;
char* m_input;
char* m_apiInput;
std::mutex m_chatLock;
std::vector<nlohmann::json> m_chat;
std::string m_summary;
std::string m_suggestion;
std::vector<LlmSkill> m_skills;
std::shared_ptr<EmbedData> m_systemPrompt;
std::shared_ptr<EmbedData> m_systemReminder;
nlohmann::json m_toolsJson;
Worker& m_worker;
View& m_view;
};
}

View File

@@ -28,7 +28,8 @@ void TracyLlmApi::SetupCurl( void* curl )
curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1L );
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 300 );
curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, 5 );
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 1200 );
curl_easy_setopt( curl, CURLOPT_USERAGENT, "Tracy Profiler" );
}
@@ -74,20 +75,6 @@ bool TracyLlmApi::Connect( const char* url )
m_models.back().quant = json2["quantization"].get_ref<const std::string&>();
if( json2.contains( "loaded_context_length" ) ) m_models.back().contextSize = json2["loaded_context_length"].get<int>();
}
else if( ( m_type == Type::Unknown || m_type == Type::Ollama ) && PostRequest( m_url + "/api/show", "{\"name\":\"" + id + "\"}", buf2 ) == 200 )
{
m_type = Type::Ollama;
auto json2 = nlohmann::json::parse( buf2 );
m_models.back().quant = json2["details"]["quantization_level"].get_ref<const std::string&>();
for( auto& cap : json2["capabilities"] )
{
if( cap.get_ref<const std::string&>() == "embedding" )
{
m_models.back().embeddings = true;
break;
}
}
}
else if( m_type == Type::Unknown )
{
m_type = Type::Other;
@@ -262,6 +249,32 @@ int TracyLlmApi::Tokenize( const std::string& text, int modelIdx )
return -1;
}
nlohmann::json TracyLlmApi::SendMessage( const nlohmann::json& chat, int modelIdx )
{
assert( m_curl );
nlohmann::json req = {
{ "model", m_models[modelIdx].name },
{ "messages", chat },
{ "chat_template_kwargs", {
{ "enable_thinking", false }
} }
};
auto data = req.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace );
std::string buf;
auto res = PostRequest( m_url + "/v1/chat/completions", data, buf, true );
try
{
return nlohmann::json::parse( buf );
}
catch( const std::exception& )
{
return { { "response", buf } };
}
}
int64_t TracyLlmApi::GetRequest( const std::string& url, std::string& response )
{
assert( m_curl );

View File

@@ -23,7 +23,6 @@ class TracyLlmApi
enum class Type
{
Unknown,
Ollama,
LmStudio,
LlamaSwap,
Other
@@ -36,6 +35,7 @@ public:
bool ChatCompletion( const nlohmann::json& req, const std::function<bool(const nlohmann::json&)>& callback, int modelIdx );
bool Embeddings( const nlohmann::json& req, nlohmann::json& response, bool separateConnection = false );
[[nodiscard]] int Tokenize( const std::string& text, int modelIdx );
[[nodiscard]] nlohmann::json SendMessage( const nlohmann::json& chat, int modelIdx );
[[nodiscard]] bool IsConnected() const { return m_curl != nullptr; }
[[nodiscard]] const std::vector<LlmModel>& GetModels() const { return m_models; }

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