11/**
2- * Collect per-tenant enqueue->start latency for one END-TO-END arm.
2+ * Collect per-tenant enqueue->start latency for one END-TO-END arm, from a batch
3+ * manifest (retrieve by id; runs.list is unreliable on this instance).
34 *
4- * Lists the runs for a batch by tag, reads each run's createdAt (enqueue) and
5- * startedAt (execution start), and reports per-tenant p50/p95/p99 of
6- * (startedAt - createdAt). Run once per arm; point --arm/BATCH at the tags the
7- * loadgen used.
5+ * Metric per run = startedAt - createdAt (run-start latency: queue wait + dequeue
6+ * + worker pickup). Headline is tenant B (few keys): bounded under vtime, grows
7+ * with A's backlog under baseline.
88 *
9- * The headline metric is tenant B's start latency: under the baseline it should
10- * grow with tenant A's backlog (B waits behind the flood); under vtime it should
11- * stay bounded (B takes its fair turn). Tenant A's latency is reported for
12- * context and is expected to be similar or slightly higher under vtime.
13- *
14- * NOTE ON THE TIMESTAMP: startedAt - createdAt is the honest run-start latency a
15- * reviewer cares about (queue wait + dequeue + worker pickup). For a tighter
16- * dequeue-only number, use the Postgres/TRQL alternative in the benchmark doc.
17- *
18- * Auth + config (env):
19- * TRIGGER_API_URL, TRIGGER_SECRET_KEY (prod env secret key)
20- * ARM=off|on BATCH=<id> OUT=./e2e-results
9+ * Env: TRIGGER_API_URL, TRIGGER_SECRET_KEY. Args (env): BATCH ARM OUT POLL_CONCURRENCY
2110 */
2211import { configure , runs } from "@trigger.dev/sdk" ;
23- import { appendFileSync , mkdirSync , writeFileSync } from "node:fs" ;
12+ import { appendFileSync , mkdirSync , readFileSync , writeFileSync } from "node:fs" ;
2413
2514function pct ( sorted : number [ ] , p : number ) : number {
2615 if ( sorted . length === 0 ) return NaN ;
@@ -34,65 +23,70 @@ function summarize(xs: number[]) {
3423}
3524
3625async function main ( ) {
37- const apiURL = process . env . TRIGGER_API_URL ;
38- const accessToken = process . env . TRIGGER_SECRET_KEY ;
39- if ( ! apiURL || ! accessToken ) throw new Error ( "Set TRIGGER_API_URL and TRIGGER_SECRET_KEY." ) ;
40- configure ( { baseURL : apiURL , accessToken } ) ;
41-
26+ configure ( {
27+ baseURL : process . env . TRIGGER_API_URL ! ,
28+ accessToken : process . env . TRIGGER_SECRET_KEY ! ,
29+ } ) ;
30+ const batch = process . env . BATCH ! ;
4231 const arm = ( process . env . ARM ?? "off" ) as "off" | "on" ;
43- const batch = process . env . BATCH ;
4432 const outDir = process . env . OUT ?? "./e2e-results" ;
45- if ( ! batch ) throw new Error ( "Set BATCH=<id> to the loadgen batch id." ) ;
33+ const conc = Number ( process . env . POLL_CONCURRENCY ?? "20" ) ;
34+ const manifest = JSON . parse ( readFileSync ( `${ outDir } /manifest-${ batch } .json` , "utf8" ) ) ;
35+ const entries : { id : string ; tenant : string } [ ] = manifest . runs ;
4636
4737 const waitsByTenant = new Map < string , number [ ] > ( ) ;
48- let total = 0 ;
4938 let missingStart = 0 ;
50-
51- // Page through every run carrying this batch tag. BATCH is unique per arm
52- // (e.g. off-1 / on-1), so the single batch tag identifies the arm; filtering
53- // on one tag avoids any multi-tag AND/OR ambiguity in the runs filter.
54- for await ( const run of runs . list ( { tag : `batch:${ batch } ` , limit : 100 } ) ) {
55- total ++ ;
56- const detail = await runs . retrieve ( run . id ) ;
57- const createdAt = detail . createdAt ?. getTime ( ) ;
58- const startedAt = detail . startedAt ?. getTime ( ) ;
59- if ( createdAt === undefined || startedAt === undefined ) {
60- missingStart ++ ;
61- continue ;
39+ let i = 0 ;
40+ async function w ( ) {
41+ while ( i < entries . length ) {
42+ const e = entries [ i ++ ] ! ;
43+ try {
44+ const r = await runs . retrieve ( e . id ) ;
45+ const c = r . createdAt ?. getTime ( ) ;
46+ const s = r . startedAt ?. getTime ( ) ;
47+ if ( c === undefined || s === undefined ) {
48+ missingStart ++ ;
49+ continue ;
50+ }
51+ const arr = waitsByTenant . get ( e . tenant ) ?? waitsByTenant . set ( e . tenant , [ ] ) . get ( e . tenant ) ! ;
52+ arr . push ( s - c ) ;
53+ } catch {
54+ missingStart ++ ;
55+ }
6256 }
63- const tenantTag = ( detail . tags ?? [ ] ) . find ( ( t ) => t . startsWith ( "tenant:" ) ) ?? "tenant:?" ;
64- const tenant = tenantTag . slice ( "tenant:" . length ) ;
65- const wait = startedAt - createdAt ;
66- ( waitsByTenant . get ( tenant ) ?? waitsByTenant . set ( tenant , [ ] ) . get ( tenant ) ! ) . push ( wait ) ;
6757 }
58+ await Promise . all ( Array . from ( { length : Math . min ( conc , entries . length ) } , w ) ) ;
6859
6960 const perTenant : Record < string , ReturnType < typeof summarize > > = { } ;
70- for ( const [ tenant , xs ] of waitsByTenant ) perTenant [ tenant ] = summarize ( xs ) ;
71-
72- const report = { arm, batch, total, missingStart, unit : "ms (startedAt - createdAt)" , perTenant } ;
61+ for ( const [ t , xs ] of waitsByTenant ) perTenant [ t ] = summarize ( xs ) ;
62+ const report = {
63+ arm,
64+ batch,
65+ total : entries . length ,
66+ missingStart,
67+ unit : "ms (startedAt - createdAt)" ,
68+ perTenant,
69+ } ;
7370
7471 mkdirSync ( outDir , { recursive : true } ) ;
7572 writeFileSync ( `${ outDir } /e2e-${ batch } -${ arm } .json` , JSON . stringify ( report , null , 2 ) ) ;
7673
77- // Append a human row per tenant to a shared markdown file so OFF and ON land
78- // in one table you can eyeball before running the joiner.
79- const mdPath = `${ outDir } /e2e-summary.md` ;
8074 const rows = Object . entries ( perTenant )
8175 . map (
82- ( [ tenant , s ] ) =>
83- `| ${ batch } | ${ arm } | ${ tenant } | ${ s . count } | ${ s . mean . toFixed ( 0 ) } | ${ s . p50 . toFixed ( 0 ) } | ${ s . p95 . toFixed ( 0 ) } | ${ s . p99 . toFixed ( 0 ) } |`
76+ ( [ t , s ] ) =>
77+ `| ${ batch } | ${ arm } | ${ t } | ${ s . count } | ${ s . mean . toFixed ( 0 ) } | ${ s . p50 . toFixed ( 0 ) } | ${ s . p95 . toFixed ( 0 ) } | ${ s . p99 . toFixed ( 0 ) } |`
8478 )
8579 . join ( "\n" ) ;
8680 appendFileSync (
87- mdPath ,
81+ ` ${ outDir } /e2e-summary.md` ,
8882 `\n<!-- batch ${ batch } arm ${ arm } -->\n| batch | arm | tenant | runs | mean ms | p50 | p95 | p99 |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n${ rows } \n`
8983 ) ;
90-
91- console . log ( `[collect] arm=${ arm } batch=${ batch } runs=${ total } missingStart=${ missingStart } ` ) ;
84+ console . log (
85+ `[collect] arm=${ arm } batch=${ batch } runs=${ entries . length } missingStart=${ missingStart } `
86+ ) ;
9287 console . log ( JSON . stringify ( perTenant , null , 2 ) ) ;
9388}
94-
95- main ( ) . catch ( ( err ) => {
96- console . error ( err ) ;
89+ main ( ) . catch ( ( e ) => {
90+ console . error ( e ) ;
9791 process . exit ( 1 ) ;
9892} ) ;
0 commit comments