23790efe33021ccd1691bf5f4262c1396a1f6e9d
braney
  Fri May 1 12:49:13 2026 -0700
quickLiftBench: phase_asserts mechanism + parallel-fetch regression case

phase_asserts is a per-case schema in cases.yaml that declares regex
matches against the per-iteration timing spans, with optional
required/min_median_ms/max_median_ms bounds. When a case declares
phase_asserts, the runner captures phase data automatically and runs
the asserts after iterations complete; any failure prints to stderr
and exits non-zero.

The new regress_quickLift_parallel case uses this to assert that the
"Waiting for parallel..." span fires for the lifted multi-track session
Brianraney/benchQuickPara with a median between 500 and 15000 ms --
discriminating the working sandbox build from a pre-fix hgwdev where
only non-quickLift tracks parallelize at ~50 ms.

refs #37488, #37470

diff --git src/utils/qa/quickLiftBench/cases.yaml src/utils/qa/quickLiftBench/cases.yaml
index b3dadb4f194..d0fb7ae939c 100644
--- src/utils/qa/quickLiftBench/cases.yaml
+++ src/utils/qa/quickLiftBench/cases.yaml
@@ -1,63 +1,88 @@
 # quickLiftBench cases.yaml
 #
 # Each case names two (or more) variants, where each variant is a saved-session
 # reference of the form `user/sessionName`. Both variants render on the same
 # server (set `server:` per case). The runner does (warmup + iterations)
 # requests per variant and writes per-iteration rows to results.tsv plus a
 # per-case summary with median/p90 and pairwise ratios.
 #
 # Each session renders at the position it was saved with -- the runner does
 # NOT override `position`. A native session and its quickLifted counterpart
 # live on different assemblies, so identical chr:start-end ranges would not
 # be biologically equivalent. To benchmark multiple regions, save additional
 # session pairs and add them as separate cases.
 #
 # The headline metric is `total_ms`, taken from the "Overall total time" timing
 # span emitted by hgTracks. `load_ms_sum` and `draw_ms_sum` are summed across
 # all visible tracks from the printTrackTiming() table; use them when you want
 # to attribute time to per-track work vs. CGI startup / cart processing.
 
 defaults:
   iterations: 25
   warmup: 1
   timeout: 60
   servers:
     hgwdev: https://hgwdev.gi.ucsc.edu
     sandbox: https://hgwdev-braney.gi.ucsc.edu
     beta: https://hgwbeta.soe.ucsc.edu
     rr: https://genome.ucsc.edu
 
 cases:
 
   # -------- Smoke test: one session, no comparison --------
   - id: smoke_session
     description: |
       Smoke: load one saved session on hgwdev. Confirms the runner loads
       sessions and extracts Overall total time + per-track timings from a
       live response.
     server: hgwdev
     variants:
       base: Braney/hg38
 
   # -------- Bench pair 1: native vs quickLifted (hgwdev) --------
   - id: bench1_hgwdev
     description: |
       First benchmark pair on hgwdev: native vs quickLifted view of an
       equivalent track set. Each session renders at its own saved region.
     server: hgwdev
     variants:
       native: Brianraney/benchQuickNative1
       lifted: Brianraney/benchQuickList1
     compare:
       - [native, lifted]
 
   # -------- Bench pair 1: same sessions on the public RR --------
   - id: bench1_rr
     description: |
       Same session pair as bench1_hgwdev, run against genome.ucsc.edu.
     server: rr
     variants:
       native: Brianraney/benchQuickNative1
       lifted: Brianraney/benchQuickList1
     compare:
       - [native, lifted]
+
+  # -------- Regression: parallel-fetch tripwire (refs #37488, #37470) --------
+  # phase_asserts make this case fail-fast if the parallel pool stops
+  # firing for quickLift tracks (e.g. a regression in isTrackForParallelLoad
+  # or customFactoryParallelLoad). Runs against the sandbox until the fix
+  # lands on hgwdev.
+  - id: regress_quickLift_parallel
+    description: |
+      Regression tripwire: a quickLifted multi-track session must
+      spawn the parallel-fetch worker pool. Asserts the
+      "Waiting for parallel..." span is emitted in every iteration.
+    server: sandbox
+    variants:
+      base: Brianraney/benchQuickPara
+    phase_asserts:
+      # Required + min_median together: catch a regression that drops the
+      # quickLift tracks back to serial. With the fix in place the median
+      # parallel wait is several seconds; without it (only non-quickLift
+      # tracks parallelize) the wait is ~50 ms, so 500 ms cleanly separates
+      # the working and broken cases.
+      - variant: base
+        phase: 'Waiting for parallel \(\d+ threads for \d+ tracks\) remote data fetch'
+        required: true
+        min_median_ms: 500
+        max_median_ms: 15000