- CMake 4.0+
- Python 3
- C++20 compatible compiler (MSVC, GCC, Clang)
All builds go through the helper script scripts/cmake_build.py:
# Configure only
python scripts/cmake_build.py
# Configure + build
python scripts/cmake_build.py --build
# Configure + build using presets
python scripts/cmake_build.py --preset macos --build
# Build With Tracy
python scripts/cmake_build.py --preset macos-tracy --build
# Clean build (wipe build directory, then configure + build)
python scripts/cmake_build.py --clean --buildThe script uses CMakePresets.json to select the right preset for each platform automatically.
Build output is placed in a platform-specific directory at the project root:
| Platform | Directory | Preset |
|---|---|---|
| Windows | _Build_wnd/ |
windows |
| macOS | _Build_mac/ |
macos |
| Linux | _Build_lnx/ |
linux |
You can also invoke CMake directly with a preset: cmake --preset macos.
The pre-commit hooks required code to be formatted using Clang format, to format code please use command:
python scripts/code_format.py --format --staged# Run regression tests (macOS example; substitute _Build_wnd or _Build_lnx on other platforms)
cd _Build_mac && ctest && cd ..
# Run performance tests (disabled by default, must be requested explicitly)
cd _Build_mac && ./tests/regression/P8_RegressionTests --gtest_filter="*c_log_perf*" --gtest_also_run_disabled_tests && cd ..Logs: _Build_mac/Testing/Temporary/LastTest.log
Performance tests are disabled in the normal test suite and must be run explicitly.
The build must use Release configuration (CMAKE_BUILD_TYPE=Release, the default for macOS/Linux presets).
cd _Build_mac && ./tests/regression/P8_RegressionTests \
--gtest_filter="*c_log_perf*" \
--gtest_also_run_disabled_testsReference numbers (Apple M4 Max, macOS, Release).
Single-thread emit latency (c_log_perf_test, 32 batches x 1 000 000 iterations, median ns/call):
| Test | Per call | Iterations |
|---|---|---|
send_hello_d_no_attrs |
26.8 ns | 1 000 000 |
send_hello_d_3_attrs (str + i64 + f64) |
39.3 ns | 1 000 000 |
full_cycle throughput (init + emit + release) through the in-memory null sink,
1 000 000 iterations per thread. Numbers are aggregated over 25 runs of the test
(ns/call = full-cycle wall time / total calls):
| Threads | Total calls | Median ns/call | p95 ns/call | Stdev | Throughput (median) |
|---|---|---|---|---|---|
| 1 | 1 000 000 | 26.6 ns | 32.8 ns | 3.16 ns | 37.6 M calls/s |
| 2 | 2 000 000 | 13.7 ns | 16.1 ns | 1.85 ns | 72.8 M calls/s |
| 4 | 4 000 000 | 7.9 ns | 11.0 ns | 1.29 ns | 125.9 M calls/s |
| 8 | 8 000 000 | 4.5 ns | 6.5 ns | 0.94 ns | 221.1 M calls/s |
Relative spread (stdev/mean) grows with thread count — from ~12 % at 1 thread to ~19 % at 8 threads — so multi-threaded runs are less stable and p95 sits noticeably above the median.
Tracy is an optional, sampling + instrumentation
profiler. It is OFF by default (P8_ENABLE_TRACY=OFF): when disabled Tracy is not
fetched, compiled or linked, and the P8_PROF_* macros in engine/p8_profiler.hpp
expand to no-ops — zero build-time and run-time cost. p8 pins Tracy v0.13.1.
Profiling has two halves:
- The client — your instrumented p8 binary. Built by enabling
P8_ENABLE_TRACY. - The viewer (GUI) — the standalone Tracy Profiler app that displays the timeline.
The client and viewer must be the same Tracy version (v0.13.1); the wire protocol
is not compatible across versions.
Use a *-tracy preset. Release presets exist for every platform; Debug presets for
macOS and Linux:
| Platform | Release preset | Debug preset |
|---|---|---|
| Windows | windows-tracy |
(use Release) |
| macOS | macos-tracy |
macos-debug-tracy |
| Linux | linux-tracy |
linux-debug-tracy |
# Windows
python scripts/cmake_build.py --preset windows-tracy --build
# macOS
python scripts/cmake_build.py --preset macos-tracy --build
# Linux
python scripts/cmake_build.py --preset linux-tracy --buildOr without the helper script: cmake --preset macos-tracy && cmake --build _Build_mac.
To add Tracy to any other preset, pass -DP8_ENABLE_TRACY=ON.
At configure time the log confirms the state:
p8: Tracy profiler ENABLED (v0.13.1) # instrumentation active
p8: Tracy profiler disabled (default) # macros are no-ops
The viewer is a separate application. Building it from the sources p8 already fetched guarantees the version matches the client. The sources live under the build directory:
- Windows:
_Build_wnd/_deps/tracy-src/ - macOS:
_Build_mac/_deps/tracy-src/ - Linux:
_Build_lnx/_deps/tracy-src/
Build the viewer with CMake (substitute the tracy-src path for your platform):
# macOS example (swap _Build_mac for _Build_wnd / _Build_lnx)
cmake -S _Build_mac/_deps/tracy-src/profiler -B _Build_tracy_gui -DCMAKE_BUILD_TYPE=Release
cmake --build _Build_tracy_gui --config Release -jThe resulting tracy-profiler (tracy-profiler.exe on Windows) binary is in
_Build_tracy_gui.
Platform notes for the viewer build:
- Windows — build with the Visual Studio generator; the profiler pulls its dependencies via vcpkg automatically. Alternatively grab a prebuilt release from the Tracy releases page.
- macOS — requires
glfwandfreetype(brew install glfw freetype). A prebuilttracycask also exists via Homebrew, but only use it if its version isv0.13.1. - Linux — install the dev packages the profiler needs, e.g. on Debian/Ubuntu:
sudo apt install libglfw3-dev libfreetype-dev libcapstone-dev libtbb-dev libdbus-1-dev.
- Launch the viewer and press Connect (it listens on
localhost:8086by default). - Run your Tracy-enabled p8 binary, for example:
./_Build_mac/examples/logs_file_sink/p8_example_multithread_file_sink /tmp/p8outThe client connects to the viewer and streams the timeline live. p8 configures Tracy
with TRACY_ON_DEMAND=OFF, so the client buffers data from process start — nothing is
lost for short-lived tools even if you connect the viewer slightly late.
Include the shim, make sure the target links p8::profiler (the p8 library and the
examples already do), and drop in the macros:
#include "p8_profiler.hpp"
void p8_flush_buffer()
{
P8_PROF_ZONE(); // zone name = function name
// ... hot code ...
}Available macros (all no-ops unless built with P8_ENABLE_TRACY):
| Macro | Purpose |
|---|---|
P8_PROF_ZONE() |
Scoped CPU zone, named after the function |
P8_PROF_ZONE_NAMED("x") |
Scoped zone with an explicit name |
P8_PROF_FRAME_MARK() |
Frame boundary marker |
P8_PROF_FRAME_MARK_NAMED("x") |
Named continuous frame set |
P8_PROF_THREAD_NAME("x") |
Label the current OS thread |
P8_PROF_PLOT("x", value) |
Numeric plot over time |
P8_PROF_MESSAGE(ptr, size) |
Free-form timeline message |
P8_PROF_ALLOC(ptr, size) / P8_PROF_FREE(ptr) |
Allocation tracking |
A zone lives until the end of its lexical scope, so wrap non-scope regions in { } to
bound them (see examples/logs_file_sink/multithread_file_sink.cpp).
To override settings without changing tracked files, create CMakeUserPresets.json (git-ignored) in the project root. A preset defined there can inherit from any base preset in CMakePresets.json.
Example — use a local kit checkout instead of fetching from GitHub:
{
"version": 6,
"configurePresets": [
{
"name": "macos-local-kit",
"inherits": "macos",
"cacheVariables": {
"FETCHCONTENT_SOURCE_DIR_KIT": "${sourceDir}/../kit"
}
}
]
}Then build with:
python scripts/cmake_build.py --preset macos-local-kit --build