Skip to content

Commit 7f6211c

Browse files
authored
test(e2e): instrument common spans for wall-clock tracking (#24407)
## Motivation A-1178 (#24281) measures each e2e test's wall-clock as four coarse buckets (`setupFnMs`, `beforeHooksMs`, `bodyMs`, `afterHooksMs`). That tells us a test is slow but not *where* it loses time. Most e2e tests spend their wall-clock in the same handful of operations — standing up the environment, waiting for a tx to be mined/checkpointed/proven, waiting for a committee to form or an offense to be detected, client-side proving of seed txs, and warp scans hunting for a proposer slot. This decomposes that time into **named spans** so a single aggregate query over a full CI run answers *"across the suite, how much total wall-clock goes into proving / spinning up nodes / waiting for checkpoints?"* — a ranked list of where to invest in speedups. It extends A-1178 and builds on the consolidation (#24201/#24310) and helpers (#24404) work that turned each repeated operation into one shared definition, so wrapping it once instruments every test with no per-test edits. This is **instrumentation / data-gathering only** — no test behavior or timing changes. ## Approach - A generic `testSpan(name, fn)` / `testSpanSync(name, fn)` wrapper (`fixtures/timing.ts`) records `{ owner, name, start, end }` into the shared collector installed by the timing environment. When `TEST_TIMING_FILE` is unset there is no collector, so `testSpan()` calls `fn()` directly — exactly zero-cost, with no clock reads. - At flush, the timing environment groups spans by owner then tag and computes four numbers per `(owner, tag)`: - `count` — multiplicity is itself a signal (waited for a checkpoint 14× points at a loop to batch). - `totalMs` — naive sum (correct for serial repeats). - `busyMs` — duration of the **union** of the spans' intervals; concurrency-correct, so a `Promise.all` of 12 concurrent 3s spans reads ~3s instead of ~36s. The `busyMs ≪ totalMs` signature flags work that is run serially where it could be parallel. - `maxMs` — longest single occurrence, to catch one pathological wait hiding in a cheap average. - The aggregates attach as an additive `spans` map on each `type:"test"` / `type:"suite"` JSONL line. `setupFnMs` / `teardownFnMs` are kept for back-compat, now derived from the `setup:env:<mode>` / `teardown:env` tags. - Tags follow a stable `category:label` taxonomy (`setup:`, `wait:`, `tx:`, `warp:`, `wallet:`, `deploy:`, `other:`), tagged **by concept, not by function** — e.g. every checkpoint waiter maps to `wait:checkpoint` — so a label is a forever aggregation key regardless of which helper a test happened to call. - **One clock.** All spans use `performance.now()`. I verified Node's `perf_hooks` clock is process-wide: a `performance.now()` taken inside a fresh `vm` context (the jest sandbox realm) shares the same monotonic origin as the host realm (delta ~0.002ms; a fresh `perf_hooks.performance` inside a separate context anchors to the same `timeOrigin`). Interval-merging for `busyMs` across the sandbox and host realms is therefore valid, so **`busyMs` is included** (no fallback to `totalMs`-only needed). - **Background loop.** `startMempoolFeeder` runs interleaved with arbitrary tests; without care its prove/send spans would smear onto whichever test was current when each round fired. Its production is run inside an `AsyncLocalStorage`-scoped owner override (`other:mempool-feeder`), which pins every span in its async call tree to a fixed owner — isolated from concurrent test-body spans. That owner matches no test/suite record, so the feeder's spans are cleanly excluded from the per-test view. ## Changes by phase - **Phase 0 — collector + `testSpan()` + flush aggregation.** New `fixtures/timing.ts` (`testSpan`/`testSpanSync`/`withTestSpanOwner`). `timing_env.mjs` collector generalized from `fnSpans` to a `spans` array; `finalizeAndFlush` groups by owner/tag, merges intervals for `busyMs`, attaches the `spans` map, and derives the back-compat fields. - **Phase 1 — shared waits + `setup:node`.** Wrapped the waiters in `fixtures/wait_helpers.ts` (`wait:proposed` / `wait:proven`, `wait:checkpoint` / `wait:proven-checkpoint`, `wait:tx-mined`, `wait:l2-to-l1-witness`, `wait:pending-tx`, `wait:sequencer-state`) and the wait methods on `SingleNodeTestContext` / `MultiNodeTestContext` (`wait:epoch`, `wait:slot`, `wait:proof-window`, `wait:node-sync`, `wait:proof-submitted`, `wait:offense`, the multi-node `wait:checkpoint`/`wait:proven-checkpoint`/`wait:block` convergence waiters). Wrapped `createNode`/`createProverNode` with `setup:node`. - **Phase 2 — decompose `setup:env`.** Cracked the opaque setup in `fixtures/setup.ts` into `setup:env:anvil`, `:l1-deploy`, `:sequencer-start`, `:prover-node`, `:pxe`, and `wallet:create`. The top-level `setup:env` is tagged with the prover mode (`setup:env:none` / `:fake` / `:real`) so the three factories are comparable. - **Phase 3 — tx leaf wraps.** `proveInteraction` → `tx:prove`, `ProvenTx.send` → `tx:send`; everything else (`proveTxs`/`proveAndSendTxs`/the submit helpers) aggregates through those leaves. `startMempoolFeeder` handled as above. - **Phase 4 — slashing/governance waiters.** Wrapped `awaitCommitteeExists` (`wait:committee`), `awaitOffenseDetected` (`wait:offense`), `awaitCommitteeKicked` (`wait:committee-kicked`), `awaitProposalExecution` (`wait:slash-execution`), and the `findUpcomingProposerSlot` / `advanceToEpochBeforeProposer` / `findSlotsWithProposers` scans (`warp:find-proposer`). - **Phase 5 — reporting.** The JSONL schema now carries the per-line `spans` map, and `TEST_TIMING_SPANS=1` emits one `type:"span"` line per occurrence (owner, name, ms) for deep dives. The repo now also ships a `track-e2e-times` skill (`yarn-project/.claude/skills/track-e2e-times/`) covering how to **collect and aggregate** the span timings locally: run the suite with `TEST_TIMING_FILE` set, find the per-worker JSONL, and print per-test sums plus the ranked span leaderboard (via the bundled `row.sh`). Only the step of publishing aggregate numbers to a running tracking log remains a separate out-of-band workflow. The leaderboard rollup is a small additive jq over the new `spans` maps: ``` jq -rs '[ .[] | select(.type=="test" or .type=="suite") | (.spans // {}) | to_entries[] ] | group_by(.key) | map({ tag: .[0].key, count: (map(.value.count) | add), busyMs: (map(.value.busyMs) | add), maxMs: (map(.value.maxMs) | max) }) | sort_by(-.busyMs) | .[] | "\(.busyMs)\t\(.count)\t\(.maxMs)\t\(.tag)"' ``` The existing `row.sh` is unaffected — it filters `type=="test"` and reads the unchanged `setupFnMs`/`bodyMs`/etc., ignoring the new `spans` key and `type:"span"` lines. ## Notes - `waitForEpoch`/`waitForSlot` (added to `@aztec/ethereum`'s `rollup_cheat_codes.ts` by #24404) are deliberately **not** instrumented in-package: that package should not depend on the e2e timing collector, and the `wait:epoch`/`wait:slot` concepts they cover are already captured at the e2e-context wrapper layer (`waitUntilEpochStarts`, the slot waiters). They have no e2e callers yet; a future call site picks them up via its context wrapper. - Spans do not partition `bodyMs` — a parent span includes its children, so `sum(spans) ≠ bodyMs`. Tagging is at the leaf wait/setup/tx level where additivity holds; the few intentional nests (e.g. `wait:committee-kicked` contains `wait:slash-execution`) carry distinct tags. Verified: `yarn build`, `yarn lint end-to-end`, `yarn format --check end-to-end` all pass. The flush aggregation (busyMs interval merge, back-compat derivation, feeder-owner exclusion, opt-in raw lines) is covered by unit tests in `end-to-end/src/shared/timing_env.test.ts`, which exercise the extracted `aggregateSpans` / `foldSpansInto` pure functions directly. Fixes A-1179 ## Example timing output Each e2e test run in CI emits one JSONL record per test and per suite, carrying a `spans` map keyed by `category:label` tag, where each value is `{count, totalMs, busyMs, maxMs}` (`busyMs` is the de-overlapped union duration, so concurrent occurrences of a span are counted once). An illustrative `type:"test"` line from CI run 28469687883 (commit de3635e): ```json { "suite": "optimistic.parallel", "type": "test", "name": "single-node/proving/optimistic happy path proves multiple epochs via checkpoint-driven flow", "status": "passed", "setupFnMs": 4707, "beforeHooksMs": 4717, "bodyMs": 327324, "teardownFnMs": 615, "afterHooksMs": 624, "totalMs": 332668, "startedAt": "2026-06-30T19:20:58.279Z", "spans": { "setup:env:anvil": { "count": 1, "totalMs": 82, "busyMs": 82, "maxMs": 82 }, "setup:env:l1-deploy": { "count": 1, "totalMs": 470, "busyMs": 470, "maxMs": 470 }, "setup:env:sequencer-start": { "count": 1, "totalMs": 3141, "busyMs": 3141, "maxMs": 3141 }, "setup:env:prover-node": { "count": 1, "totalMs": 139, "busyMs": 139, "maxMs": 139 }, "setup:env:pxe": { "count": 1, "totalMs": 391, "busyMs": 391, "maxMs": 391 }, "wallet:create": { "count": 1, "totalMs": 79, "busyMs": 79, "maxMs": 79 }, "setup:env:fake": { "count": 1, "totalMs": 4707, "busyMs": 4707, "maxMs": 4707 }, "wait:epoch": { "count": 5, "totalMs": 180210, "busyMs": 180210, "maxMs": 36067 }, "tx:prove": { "count": 4, "totalMs": 2123, "busyMs": 2123, "maxMs": 621 }, "tx:send": { "count": 4, "totalMs": 144305, "busyMs": 144305, "maxMs": 36124 }, "wait:proven-checkpoint": { "count": 4, "totalMs": 0, "busyMs": 0, "maxMs": 0 }, "wait:node-sync": { "count": 4, "totalMs": 401, "busyMs": 401, "maxMs": 101 }, "teardown:env": { "count": 1, "totalMs": 615, "busyMs": 615, "maxMs": 615 } }, "commit": "de3635e74eb89071b52939d1583d072ba2c3852c", "branch": "spl/a1179-track-common-spans", "runId": "28469687883" } ```
1 parent 9dc804b commit 7f6211c

12 files changed

Lines changed: 1137 additions & 471 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
name: track-e2e-times
3+
description: Collect and aggregate e2e test wall-clock timings — run the suite with the timing instrumentation on, find the per-worker JSONL it produces, and print per-test sums plus a ranked span leaderboard. Use when asked to measure, profile, or break down where the e2e suite spends time.
4+
argument-hint: <folder-with-jsonl>
5+
---
6+
7+
# Track e2e times
8+
9+
The e2e suite is instrumented to record, per test, how long it spends in jest before/after hooks and
10+
the test body, plus a set of named **spans** for the repeated operations that dominate e2e wall-clock
11+
(standing up the environment, waiting for blocks/checkpoints/proofs, client-side proving, warp scans).
12+
This skill covers two things and nothing else:
13+
14+
1. **Collect** — run the suite with timing on and locate the JSONL it writes.
15+
2. **Aggregate** — turn that JSONL into per-test sum tables and a ranked span leaderboard locally.
16+
17+
There is a separate, out-of-band workflow for publishing aggregate numbers to a running tracking log;
18+
that is not part of this skill. Everything here is local: run, find the files, print tables.
19+
20+
## How the instrumentation works
21+
22+
The jest `testEnvironment` for `end-to-end` is `src/shared/timing_env.mjs`, wired in
23+
`yarn-project/end-to-end/package.json`. It is always installed but **gated on the `TEST_TIMING_FILE`
24+
env var**: when that var is unset it behaves like the base environment and records nothing (the
25+
`testSpan()` wrapper in `src/fixtures/timing.ts` calls through with zero overhead). Set `TEST_TIMING_FILE`
26+
to a path and the environment writes JSONL there.
27+
28+
Relevant env vars:
29+
30+
- `TEST_TIMING_FILE` — path to the JSONL output file. **Required** to turn timing on. The environment
31+
*appends*, and runs once per jest worker process, so a single file accumulates every suite that
32+
worker ran. Use one file per test command.
33+
- `TEST_TIMING_SPANS=1` — optional. Additionally emit one `type:"span"` line per individual span
34+
occurrence (owner, span tag, ms) for deep dives. Off by default to keep the file one line per test.
35+
36+
## Step 1 — Collect: run the suite with timing on
37+
38+
Run any e2e test (or the whole suite) with `TEST_TIMING_FILE` pointed at a scratch path. From
39+
`yarn-project`:
40+
41+
```bash
42+
TEST_TIMING_FILE=/tmp/e2e-timings.jsonl \
43+
yarn workspace @aztec/end-to-end test:e2e e2e_block_building.test.ts
44+
```
45+
46+
For the per-occurrence span lines as well:
47+
48+
```bash
49+
TEST_TIMING_FILE=/tmp/e2e-timings.jsonl TEST_TIMING_SPANS=1 \
50+
yarn workspace @aztec/end-to-end test:e2e e2e_block_building.test.ts
51+
```
52+
53+
If a run spreads across several jest workers and each worker writes its own file, collect them all
54+
into one folder and point the aggregation at the folder — the recipes below read `*.jsonl`.
55+
56+
To aggregate a CI run instead of a local one, the per-worker JSONL each CI test command produced is
57+
uploaded to S3 keyed by the job's `CI_LOG_ID`. From the repo root:
58+
59+
```bash
60+
./ci.sh test-timings <CI_LOG_ID> <folder>
61+
```
62+
63+
That downloads every `<LOG_ID>.log.gz` for the job and gunzips them to `*.jsonl` in `<folder>`. The
64+
`CI_LOG_ID` is the decimal id in a job's `ci.aztec-labs.com/<id>` dashboard URL.
65+
66+
## The JSONL line schema
67+
68+
One JSON object per line, one line per test and one suite-scoped line per file. Fields:
69+
70+
| Field | Meaning |
71+
|---|---|
72+
| `suite` | The `.test.ts` basename the line came from. |
73+
| `type` | `"test"` (one `it()`), `"suite"` (the file's `beforeAll`/`afterAll`, `name` is `null`), or `"span"` (a raw occurrence, only with `TEST_TIMING_SPANS=1`). |
74+
| `name` | Full test name (describe chain + `it`), or `null` for a suite line. |
75+
| `status` | `"passed"` / `"failed"`. |
76+
| `beforeHooksMs` | Sum of `beforeEach` hooks (test lines) or `beforeAll` (suite line). |
77+
| `afterHooksMs` | Sum of `afterEach` hooks (test lines) or `afterAll` (suite line). |
78+
| `bodyMs` | The `it()` body wall-clock (test lines only). |
79+
| `setupFnMs` / `teardownFnMs` | Back-compat fields, derived from the `setup:env:<mode>` / `teardown:env` spans. |
80+
| `totalMs` | Whole-test wall-clock (test lines), or `beforeHooksMs + afterHooksMs` (suite line). |
81+
| `startedAt` | ISO timestamp of test start (test lines). |
82+
| `commit` / `branch` / `runId` | Run metadata from `COMMIT_HASH` / `TARGET_BRANCH`|`REF_NAME` / `RUN_ID` (may be `null` locally). |
83+
| `spans` | Map of `category:label` tag → `{ count, totalMs, busyMs, maxMs }` (see below). |
84+
85+
All `*Ms` values are integers (rounded at flush).
86+
87+
### The span model
88+
89+
Each test/suite line carries a `spans` map. For every `category:label` tag the test touched it records:
90+
91+
- **`count`** — number of occurrences. Multiplicity is itself a signal (e.g. waited for a checkpoint
92+
14× points at a loop that could batch).
93+
- **`totalMs`** — naive sum of the occurrences' durations. Correct for serial repeats.
94+
- **`busyMs`** — wall-clock of the **union** of the occurrences' `[start, end)` intervals. This is
95+
concurrency-correct: a `Promise.all` of 12 concurrent 3s spans reads ~3s here, not ~36s. A
96+
`busyMs ≪ totalMs` gap flags work run serially that could be parallel.
97+
- **`maxMs`** — longest single occurrence, to catch one pathological wait hiding in a cheap average.
98+
99+
Tags follow a stable `category:label` taxonomy (`setup:`, `wait:`, `tx:`, `warp:`, `wallet:`,
100+
`deploy:`, `other:`), tagged **by concept, not by function** — e.g. every checkpoint waiter maps to
101+
`wait:checkpoint` — so a tag is a stable aggregation key regardless of which helper a test called.
102+
103+
Spans do **not** partition `bodyMs`: a parent span includes its children, so `sum(spans) ≠ bodyMs`.
104+
Tagging is at the leaf wait/spawn/tx level where additivity holds. `busyMs` is the right number to
105+
rank by; sum `busyMs` per tag across the run to see where wall-clock goes.
106+
107+
## Step 2 — Aggregate locally
108+
109+
### Per-test sums
110+
111+
`row.sh` (this directory) prints a one-line summary of the run's aggregate sums (test count, overall,
112+
setup, setup.ts-fn, body, teardown) plus the wall-clock window:
113+
114+
```bash
115+
bash row.sh <folder-with-jsonl>
116+
```
117+
118+
It reads `*.jsonl` from the folder, sums the buckets across all `type:"test"` lines, and prints to
119+
stderr. It warns if the test count is far below a full run (~950) — a partial or cache-served run is
120+
not comparable to a full one.
121+
122+
> Sums, not wall-clock: files run in parallel, so the column sums far exceed the run's actual wall
123+
> time. The `(HH:MM–HH:MM)` window the script prints is the real wall-clock span.
124+
125+
### Span leaderboard
126+
127+
The ranked "where does the suite spend wall-clock" table is a small jq over the `spans` maps. It rolls
128+
every tag up across all test and suite lines, summing `busyMs` and `count`, taking the max `maxMs`, and
129+
sorts by total `busyMs` descending:
130+
131+
```bash
132+
cat <folder>/*.jsonl | jq -rs '[ .[] | select(.type=="test" or .type=="suite") | (.spans // {}) | to_entries[] ]
133+
| group_by(.key)
134+
| map({ tag: .[0].key, count: (map(.value.count) | add),
135+
busyMs: (map(.value.busyMs) | add), maxMs: (map(.value.maxMs) | max) })
136+
| sort_by(-.busyMs) | .[] | "\(.busyMs)\t\(.count)\t\(.maxMs)\t\(.tag)"'
137+
```
138+
139+
Output columns are `busyMs`, `count`, `maxMs`, `tag` — the top rows are where to invest in speedups.
140+
141+
For a single tag's per-test breakdown (which tests contribute the most to one tag):
142+
143+
```bash
144+
cat <folder>/*.jsonl | jq -rs --arg tag wait:checkpoint '
145+
[ .[] | select(.type=="test" or .type=="suite") | select(.spans[$tag])
146+
| { name, busyMs: .spans[$tag].busyMs, count: .spans[$tag].count } ]
147+
| sort_by(-.busyMs) | .[] | "\(.busyMs)\t\(.count)\t\(.name)"'
148+
```
149+
150+
With `TEST_TIMING_SPANS=1`, each `type:"span"` line is a single occurrence (`name` = owning test,
151+
`span` = tag, `ms` = duration), so you can histogram or list the raw occurrences of one tag directly.
152+
153+
## Key points
154+
155+
- **Timing is off unless `TEST_TIMING_FILE` is set** — the instrumentation is zero-cost when unset and
156+
cannot change test behavior or timing.
157+
- **One file per worker, appended** — collect all of a run's files into one folder before aggregating.
158+
- **Rank by `busyMs`** — it is concurrency-correct; `totalMs` double-counts parallel work.
159+
- **`spans` is additive across lines** but not within a test (parents include children); sum per tag
160+
across the run, do not expect spans to add up to `bodyMs`.
161+
- **Full-run vs full-run only** — a partial or cache-served run emits far fewer than ~950 tests and is
162+
not comparable.
163+
164+
## Reference
165+
166+
- `row.sh` (this directory) — aggregate JSONL → summary sums + leaderboard.
167+
- `yarn-project/end-to-end/src/shared/timing_env.mjs` — the jest timing environment that writes the JSONL.
168+
- `yarn-project/end-to-end/src/fixtures/timing.ts` — the `testSpan()` / `testSpanSync()` / `withTestSpanOwner()` wrappers.
169+
- `./ci.sh test-timings <CI_LOG_ID> <folder>` — download a CI job's per-worker JSONL (repo root).
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
# Aggregate downloaded e2e test-timing JSONL into local summary tables: the run's aggregate sums
3+
# (test count, overall / setup / setup.ts-fn / body / teardown), the wall-clock window, and a ranked
4+
# span leaderboard. Reads every *.jsonl in the given folder.
5+
#
6+
# Usage: row.sh <folder-with-jsonl>
7+
# <folder> a directory of *.jsonl produced by a timed run (TEST_TIMING_FILE) or downloaded via
8+
# `./ci.sh test-timings <CI_LOG_ID> <folder>`.
9+
set -euo pipefail
10+
11+
folder=${1:?Usage: row.sh <folder-with-jsonl>}
12+
13+
shopt -s nullglob
14+
files=("$folder"/*.jsonl)
15+
[ ${#files[@]} -gt 0 ] || { echo "row.sh: no *.jsonl files in $folder" >&2; exit 1; }
16+
17+
# Sums across all per-test measurements, plus the run's commit/branch/runId and the wall-clock window
18+
# (first test start -> last test end).
19+
IFS=$'\t' read -r tests overall setup setupfn body teardown commit branch runid startISO endISO < <(
20+
cat "$folder"/*.jsonl | jq -rs '
21+
(map(select(.type=="test"))) as $t
22+
| {
23+
tests: ($t | length),
24+
overall: (map(.totalMs) | add),
25+
setup: (map(.beforeHooksMs) | add),
26+
setupfn: (map(.setupFnMs) | add),
27+
body: ($t | map(.bodyMs) | add),
28+
teardown: (map(.afterHooksMs) | add),
29+
commit: ($t | map(.commit) | first),
30+
branch: ($t | map(.branch) | first),
31+
runid: ($t | map(.runId) | first),
32+
startISO: ($t | map(.startedAt) | min),
33+
endISO: ( $t
34+
| map((.startedAt | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601) + (.totalMs / 1000))
35+
| max | todateiso8601 )
36+
}
37+
| [ .tests, .overall, .setup, .setupfn, .body, .teardown, .commit, .branch, .runid, .startISO, .endISO ]
38+
| @tsv'
39+
)
40+
41+
# Insert thousands separators into an integer.
42+
group() { printf '%s' "$1" | rev | sed 's/[0-9]\{3\}/&,/g' | rev | sed 's/^,//'; }
43+
44+
# Format milliseconds as "Xh Ym Zs (N,NNN ms)", dropping leading zero units.
45+
fmt() {
46+
local ms=$1 s h m sec out=""
47+
s=$(( (ms + 500) / 1000 )); h=$(( s / 3600 )); m=$(( (s % 3600) / 60 )); sec=$(( s % 60 ))
48+
[ "$h" -gt 0 ] && out="${h}h "
49+
{ [ "$h" -gt 0 ] || [ "$m" -gt 0 ]; } && out="${out}${m}m "
50+
out="${out}${sec}s"
51+
printf '%s (%s ms)' "$out" "$(group "$ms")"
52+
}
53+
54+
date_str=$(date -u -d "$startISO" +%Y-%m-%d)
55+
start_hm=$(date -u -d "$startISO" +%H:%M)
56+
end_hm=$(date -u -d "$endISO" +%H:%M)
57+
short=${commit:0:8}
58+
59+
echo "tests=$tests commit=$short branch=$branch runId=$runid"
60+
echo "window=${date_str} ${start_hm}-${end_hm} UTC"
61+
echo "overall=$(fmt "$overall") | setup=$(fmt "$setup") | setup.ts=$(fmt "$setupfn") | body=$(fmt "$body") | teardown=$(fmt "$teardown")"
62+
[ "$tests" -lt 800 ] && echo "WARNING: only $tests tests — likely a partial/cache-hit run, NOT comparable to full runs (~950)." >&2
63+
[ "$runid" = "null" ] && echo "NOTE: runId is null — RUN_ID was not set for this run (expected for local runs)." >&2
64+
65+
# Span leaderboard: roll every category:label tag up across all test/suite lines, summing busyMs (the
66+
# concurrency-correct wall-clock) and count, taking the max maxMs, sorted by busyMs descending.
67+
echo ""
68+
echo "span leaderboard (busyMs | count | maxMs | tag):"
69+
cat "$folder"/*.jsonl | jq -rs '
70+
[ .[] | select(.type=="test" or .type=="suite") | (.spans // {}) | to_entries[] ]
71+
| group_by(.key)
72+
| map({ tag: .[0].key, count: (map(.value.count) | add),
73+
busyMs: (map(.value.busyMs) | add), maxMs: (map(.value.maxMs) | max) })
74+
| sort_by(-.busyMs) | .[] | "\(.busyMs)\t\(.count)\t\(.maxMs)\t\(.tag)"'

0 commit comments

Comments
 (0)