2b658cb29039048ff6ab3dac31f10519b3349bcc
braney
  Tue Apr 28 09:06:06 2026 -0700
quickLiftBench: add --phases for per-phase timing breakdown, refs #37445

Add --phases flag that writes phases.tsv alongside results.tsv and
summary.tsv. Long-form rows of every <span class='timing'> marker
emitted by hgTracks (chromAliasSetup, trackDbLoad, parallel data fetch,
image generation, cart write, etc.), one row per (case, variant,
iteration, phase). A per-(case, variant, phase) median+p90 summary is
appended.

Useful for localizing where total_ms gaps come from. The first run on
bench1_hgwdev surfaced the missing parallel-fetch span on the lifted
variant (filed as #37470).

Drop the now-unused parse_overall_total helper; total_ms is read
directly from parse_phase_timings using the OVERALL_TIMING_LABEL key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

diff --git src/utils/qa/quickLiftBench/README.md src/utils/qa/quickLiftBench/README.md
index 5c6e8fadb92..a0230bc9a7b 100644
--- src/utils/qa/quickLiftBench/README.md
+++ src/utils/qa/quickLiftBench/README.md
@@ -23,31 +23,31 @@
   `<span class='timing'>Overall total time: NNN millis</span>` footer span.
 - **Per-track load and draw times** — summed across all visible tracks from
   the `printTrackTiming()` table emitted into a `<span class='trackTiming'>`
   block.
 - **HTTP wall time** — measured around the request itself.
 
 Each variant cell does `warmup` discarded requests followed by `iterations`
 recorded requests. Min / median / p90 are reported.
 
 ## Usage
 
 ```
 ./quickLiftBench.py [--config FILE] [--cases ID,ID]
                     [--server-override NAME]
                     [--iterations N] [--warmup N]
-                    [--out DIR] [--verbose]
+                    [--out DIR] [--verbose] [--phases]
 ```
 
 Defaults: read `cases.yaml` next to the script, no server override, all
 cases, iterations and warmup from `defaults`, output to
 `./results/<timestamp>/`.
 
 Examples:
 
 ```
 # Run everything against the server in each case stanza:
 ./quickLiftBench.py
 
 # One case, against the sandbox, 10 iterations:
 ./quickLiftBench.py --cases bench1_hgwdev \
                     --server-override sandbox --iterations 10
@@ -114,30 +114,36 @@
    want it benchmarked at.
 2. Add a stanza to `cases.yaml` following the schema above.
 3. Smoke-test with `--cases <new_id> --iterations 1 --warmup 0 -v` to verify
    sessions load and timings parse out.
 
 ## Output
 
 Two TSVs are written to `results/<YYYYMMDD-HHMMSS>/`:
 
 - `results.tsv` — one row per (case, variant, iteration) with
   http_ms, load_ms_sum, draw_ms_sum, n_tracks, total_ms, status_code, error.
 - `summary.tsv` — two sections:
   1. per (case, variant): n, n_ok, http/load_sum/draw_sum/total median and p90.
   2. per (case, compare-pair): left vs right total medians and the
      `right/left` ratio for each metric.
+- `phases.tsv` (only with `--phases`) — long-form rows of every
+  `<span class='timing'>label: NNN millis</span>` marker emitted by
+  hgTracks (chromAliasSetup, trackDbLoad, parallel data fetch, image
+  generation, cart write, etc.), one row per (case, variant, iteration,
+  phase). A per-(case, variant, phase) median+p90 summary is appended.
+  Useful for localizing where time is going when total medians differ.
 
 A short pairwise table is also printed to stderr at the end of a run.
 
 ## Dependencies
 
 ```
 pip install requests pyyaml
 ```
 
 ## Notes
 
 - The script does not parallelize requests against a single server.
   quickLift renders are single-threaded per request; parallel requests would
   measure contention rather than work.
 - If hgTracks returns the bot-block page or an `errAbort`, the row is