Skip to content

Add cmake_profile backend (configure-step profile, closes BuildDigest) - #17

Merged
onlyxItachi merged 2 commits into
mainfrom
feat/v1.2-cmake-profile
Jul 17, 2026
Merged

Add cmake_profile backend (configure-step profile, closes BuildDigest)#17
onlyxItachi merged 2 commits into
mainfrom
feat/v1.2-cmake-profile

Conversation

@onlyxItachi

Copy link
Copy Markdown
Owner

Summary

New backend: cmake_profile digests cmake --profiling-format=google-trace
output (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 no
reason 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 E side carries only
ph/pid/tid/ts. Naive _grouped reuse would silently digest every cmake
trace to zero units.

Fix: a fold_be_pairs hook in chrome_trace's _complete_events/_grouped
the same surgical-hook pattern tag_override established for
clang_time_trace. B/E pairs are stack-folded per (pid, tid) (Chrome-trace
semantics: 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. Default False
leaves chrome_trace/clang_time_trace behavior byte-for-byte unchanged (full
suite proves it). Honesty: a leftover B from a truncated trace is dropped —
never given a fabricated dur (tested); a stray E likewise.

Grouping — decided from the real data

Every B event is cat: "script" with name = the cmake command/macro/
function that ran; the per-call detail lives in args
(functionArgs, location). Chosen: (cat, name) aggregation — the
sibling backends' rule — which turned 4523 spans into 83 units on the cold
run, with hot units directly actionable (try_compile 99ms and
find_package 55ms of a 223ms cold configure). Rejected: per-call-site
grouping by args.location (unit-count explosion, host paths in unit
names). The call-site detail stays reachable per-unit via expand
(arg:location, arg:functionArgs) — tested.

Usage notes carry the family overlap caveat: frames NEST (configure and
generate are cmake's whole-step frames containing everything else), so
total_time_us overlaps across units and configure ranking first is
expected, not a hotspot.

Fixture provenance (real, not hand-fabricated)

tests/fixtures/cmake_profile_sample/ is the committed project
(CMakeLists.txt with find_package(Threads), an include()d helper defining
a 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:

  • run 1, cold configure: 1.5MB / 9046 events, dominated by compiler-detection
    try_compile — used for design verification, not committed.
  • run 2, warm RE-configure (the everyday "why is my configure slow"
    artifact): 1598 events — committed as
    cmake_profile_sample.cmake-profile.json.

Host-path hygiene, exactly as the task anticipated: 86 args.location values
pointing into the temp source/build dirs were relativized to
proj//build/. Verified programmatically: every field OUTSIDE args
(names, ph, ts, pid, tid) is byte-identical to the raw capture; /usr/share/ cmake-4.3/Modules/... locations are verbatim; timings untouched; zero
leaks (grep for user/home/tmp paths clean).

Test evidence

New file tests/test_cmake_profile.py, 10 tests, all passing:

  • registry dispatch on cmake-profile/cmake-trace (+ case-insensitive)
  • real-trace parse from B/E pairs (50 units incl. configure, generate,
    find_package, the user function add_tagged_executable)
  • name aggregation across call sites (include calls=28, total 6132us,
    max 1091us — exact committed values)
  • ranking (configure 10166us > include > project)
  • nested-overlap proof on real data (sum of units >> configure + generate)
  • expand reaches arg:location/arg:functionArgs (relativized, no host
    paths) and per-call durs_us
  • truncated-trace honesty: unpaired B vanishes, never a fabricated duration
  • inherited loud errors on non-JSON and JSON-without-traceEvents
  • absence honesty + vocabulary hint for an out-of-domain metric
  • cross-backend smoke check that the fold hook (default off) left the torch
    fixture'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, ONE PROFILER_TOOLS entry
("cmake_profile": "cmake"), no new domain constant (reused
DOMAIN_BUILD_PHASE per the task default), plus the fold_be_pairs hook in
adapters/chrome_trace/trace_reader.py following the established
tag_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

onlyxItachi and others added 2 commits July 17, 2026 18:07
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>
@onlyxItachi
onlyxItachi merged commit cb2ec6f into main Jul 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant