Add cmake_profile backend (configure-step profile, closes BuildDigest) - #17
Merged
Conversation
Reads cmake --profiling-format=google-trace output (cmake >= 3.18) into NormalizedUnit, domain build_phase (a configure frame is a build phase — no new constant). Closes the BuildDigest family: ninja_log = build edges, clang_time_trace = compiler phases, cmake_profile = the configure step itself (find_package probes, try_compile, include chains, user macros/functions). Real-report finding that shaped the design: cmake does NOT emit ph=='X' complete events — a real cmake 4.3.2 capture is a bare JSON array of ph=='B'/'E' begin/end PAIRS (the E side carries only ph/pid/tid/ts). Naive chrome_trace reuse would digest every cmake trace to zero units. Added fold_be_pairs to chrome_trace's _grouped / _complete_events (default False — chrome_trace and clang_time_trace behavior byte-for-byte unchanged), the same surgical-hook pattern tag_override set for clang_time_trace: B/E pairs are stack-folded per (pid, tid) into complete events; a leftover B from a truncated trace is dropped, never given a fabricated dur. Grouping decided from the real data: every B event is cat 'script' with name = the command/macro/function that ran; (cat, name) aggregation (the sibling rule) turns 4523 spans into 83 units with the hot ones directly actionable. Per-call-site grouping was rejected (unit-count explosion + host paths in unit names); file:line and argument detail stays reachable per-unit via expand (arg:location, arg:functionArgs). Metrics are the shared 4-term shape (calls/total_time_us/avg_time_us/max_time_us). Usage notes document nested-frame overlap and that 'configure'/ 'generate' are whole-step frames expected to rank first. Fixture is real: a small committed CMake project (two targets, find_package(Threads), include()d helper with a user function) profiled with the installed cmake 4.3.2 in a neutral temp dir. The committed trace is the warm RE-configure run (1598 events; the cold first configure produced 1.5MB of compiler-detection noise). 86 args.location values were relativized to strip temp-dir prefixes — verified programmatically that all fields outside args (names, ph, ts, pid, tid) are byte-identical to the raw capture; timings untouched. Registers formats cmake-profile/cmake-trace, suffix .json, platforms linux/darwin/win32, probe = cmake on PATH (>= 3.18 noted). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cmake-profile Union-resolve the PROFILER_TOOLS conflict in platform/detect.py. Full suite green with all nine v1.2 backends together. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New backend:
cmake_profiledigestscmake --profiling-format=google-traceoutput (cmake >= 3.18) — where the CONFIGURE step spends time (find_package
probes, try_compile, include chains, user macros/functions). Domain reuses
build_phase(a configure frame is a build phase; the real data gave noreason for a new constant). This closes the BuildDigest family:
ninja_log= build edges,clang_time_trace= compiler phases,cmake_profile= the configure step itself.The real-report finding that shaped the design
The task assumed straightforward chrome-trace reuse. Inspecting the real
capture first (installed cmake 4.3.2) showed cmake emits zero
ph=='X'complete events — the trace is a bare JSON array of
ph=='B'/'E'begin/end PAIRS (4523 each on the cold run), and the
Eside carries onlyph/pid/tid/ts. Naive_groupedreuse would silently digest every cmaketrace to zero units.
Fix: a
fold_be_pairshook in chrome_trace's_complete_events/_grouped—the same surgical-hook pattern
tag_overrideestablished forclang_time_trace. B/E pairs are stack-folded per
(pid, tid)(Chrome-tracesemantics: E closes the most recent open B) into synthetic complete events
taking name/cat/args/ts from the B side,
dur = E.ts - B.ts. DefaultFalseleaves chrome_trace/clang_time_trace behavior byte-for-byte unchanged (full
suite proves it). Honesty: a leftover
Bfrom a truncated trace is dropped —never given a fabricated
dur(tested); a strayElikewise.Grouping — decided from the real data
Every B event is
cat: "script"withname= the cmake command/macro/function that ran; the per-call detail lives in
args(
functionArgs,location). Chosen: (cat, name) aggregation — thesibling backends' rule — which turned 4523 spans into 83 units on the cold
run, with hot units directly actionable (
try_compile99ms andfind_package55ms of a 223ms cold configure). Rejected: per-call-sitegrouping by
args.location(unit-count explosion, host paths in unitnames). The call-site detail stays reachable per-unit via
expand(
arg:location,arg:functionArgs) — tested.Usage notes carry the family overlap caveat: frames NEST (
configureandgenerateare cmake's whole-step frames containing everything else), sototal_time_usoverlaps across units andconfigureranking first isexpected, not a hotspot.
Fixture provenance (real, not hand-fabricated)
tests/fixtures/cmake_profile_sample/is the committed project(CMakeLists.txt with
find_package(Threads), aninclude()d helper defininga user
function(), a static lib + executable, a foreach of messages),profiled with the installed cmake 4.3.2 in a neutral temp dir. Two runs:
try_compile — used for design verification, not committed.
artifact): 1598 events — committed as
cmake_profile_sample.cmake-profile.json.Host-path hygiene, exactly as the task anticipated: 86
args.locationvaluespointing into the temp source/build dirs were relativized to
proj//build/. Verified programmatically: every field OUTSIDEargs(names, ph, ts, pid, tid) is byte-identical to the raw capture;
/usr/share/ cmake-4.3/Modules/...locations are verbatim; timings untouched; zeroleaks (
grepfor user/home/tmp paths clean).Test evidence
New file
tests/test_cmake_profile.py, 10 tests, all passing:cmake-profile/cmake-trace(+ case-insensitive)configure,generate,find_package, the user functionadd_tagged_executable)includecalls=28, total 6132us,max 1091us — exact committed values)
configure10166us >include>project)expandreachesarg:location/arg:functionArgs(relativized, no hostpaths) and per-call
durs_usfixture's digestion intact
Full suite:
uv sync --extra dev && uv run --extra dev pytest -q->178 passed, 16 skipped (baseline on main was 168 + 16; +10 new tests,
zero regressions — chrome_trace/clang_time_trace suites all green after the
shared-reader hook).
Scope discipline
ONE import line in
server/app.py, ONEPROFILER_TOOLSentry(
"cmake_profile": "cmake"), no new domain constant (reusedDOMAIN_BUILD_PHASEper the task default), plus thefold_be_pairshook inadapters/chrome_trace/trace_reader.pyfollowing the establishedtag_override precedent of extending the shared trace machinery for a sibling.
No version bump, no README/CLAUDE.md edits. Probe stays exec-free like every
sibling (the >= 3.18 requirement is in the probe notes; an older cmake
rejects the flag loudly, so nothing can go silently missing).
🤖 Generated with Claude Code