@@ -19,6 +19,21 @@ import type { ServerMetricsContext } from './server-metrics-adapters';
1919
2020type Sql = ReturnType < typeof postgres > ;
2121
22+ /**
23+ * Keep each postgres.js Bind message comfortably below the large payload that
24+ * can stall through Neon's proxy. The final row is assembled server-side in a
25+ * transaction, so callers still get all-or-nothing trace persistence.
26+ */
27+ export const TRACE_REPLAY_UPLOAD_CHUNK_BYTES = 4 * 1024 * 1024 ;
28+
29+ type TraceReplayUploadField =
30+ | 'profile_export_jsonl_gz'
31+ | 'server_metrics_csv'
32+ | 'server_metrics_json_gz'
33+ | 'aggregate_stats'
34+ | 'chart_series'
35+ | 'request_timeline' ;
36+
2237export interface TraceReplayIngestOptions {
2338 metricsContext ?: ServerMetricsContext ;
2439 progressLabel ?: string ;
@@ -38,6 +53,34 @@ function elapsed(startMs: number): string {
3853 return `${ ( ( Date . now ( ) - startMs ) / 1000 ) . toFixed ( 1 ) } s` ;
3954}
4055
56+ function jsonBuffer ( value : unknown | null ) : Buffer | null {
57+ if ( value === null ) return null ;
58+ return Buffer . from ( JSON . stringify ( structuredClone ( value ) ) , 'utf8' ) ;
59+ }
60+
61+ /**
62+ * Upload one trace-replay value as bounded binary parameters. The caller must
63+ * create `pg_temp.trace_replay_upload_parts` in the same transaction first.
64+ */
65+ export async function uploadTraceReplayPayloadChunks (
66+ sql : postgres . TransactionSql ,
67+ field : TraceReplayUploadField ,
68+ payload : Buffer | null ,
69+ ) : Promise < number > {
70+ if ( ! payload ) return 0 ;
71+
72+ let part = 0 ;
73+ for ( let offset = 0 ; offset < payload . length ; offset += TRACE_REPLAY_UPLOAD_CHUNK_BYTES ) {
74+ const chunk = payload . subarray ( offset , offset + TRACE_REPLAY_UPLOAD_CHUNK_BYTES ) ;
75+ await sql `
76+ insert into pg_temp.trace_replay_upload_parts (field, part, data)
77+ values (${ field } , ${ part } , ${ chunk } )
78+ ` ;
79+ part += 1 ;
80+ }
81+ return part ;
82+ }
83+
4184/**
4285 * Persist the per-point trace files and link them to `benchmarkResultIds`.
4386 *
@@ -119,86 +162,144 @@ export async function insertTraceReplay(
119162 `timeline_requests=${ requestTimeline ?. requests . length ?? 0 } (${ elapsed ( computeStart ) } )` ,
120163 ) ;
121164
165+ const aggregateStatsJson = jsonBuffer ( aggregateStats ) ;
166+ const chartSeriesJson = jsonBuffer ( chartSeries ) ;
167+ const requestTimelineJson = jsonBuffer ( requestTimeline ) ;
168+
122169 const insertStart = Date . now ( ) ;
123- log ( 'inserting trace_replay blob row' ) ;
124- const [ { id : traceReplayId } ] = await sql < { id : number } [ ] > `
125- insert into agentic_trace_replay (
126- profile_export_jsonl_gz,
127- profile_export_uncompressed_size,
128- server_metrics_csv,
129- server_metrics_csv_size,
130- server_metrics_json_gz,
131- server_metrics_json_uncompressed_size,
132- aggregate_stats,
133- chart_series,
134- request_timeline
135- )
136- values (
137- ${ profileGz } ,
138- ${ profileSize } ,
139- ${ serverMetricsCsv } ,
140- ${ csvSize } ,
141- ${ metricsJsonGz } ,
142- ${ metricsJsonSize } ,
143- ${ sql . json ( structuredClone ( aggregateStats ) as unknown as Parameters < typeof sql . json > [ 0 ] ) } ,
144- ${ chartSeries === null ? null : sql . json ( structuredClone ( chartSeries ) as unknown as Parameters < typeof sql . json > [ 0 ] ) } ,
145- ${ requestTimeline === null ? null : sql . json ( structuredClone ( requestTimeline ) as unknown as Parameters < typeof sql . json > [ 0 ] ) }
146- )
147- returning id
148- ` ;
149- log ( `inserted trace_replay_id=${ traceReplayId } (${ elapsed ( insertStart ) } )` ) ;
150-
151- const updateStart = Date . now ( ) ;
152- log ( `linking trace_replay_id=${ traceReplayId } to ${ unlinked . length } benchmark row(s)` ) ;
153- await sql `
154- update benchmark_results
155- set trace_replay_id = ${ traceReplayId }
156- where id = any(${ sql . array ( unlinked . map ( ( r ) => r . id ) ) } ::bigint[])
157- ` ;
158- log ( `linked benchmark rows (${ elapsed ( updateStart ) } )` ) ;
159-
160- // Derive lifetime GPU + CPU cache hit rates from chart_series. SGLang
161- // runs don't populate these in the harness JSON; vLLM runs do but only
162- // for GPU. We always recompute to keep the derivation consistent with
163- // what the detail-page charts plot — overwriting any pre-existing value.
164- //
165- // Source label naming differs by framework / cache topology:
166- // SGLang hicache: 'cache hit (HBM)' + 'cache hit (CPU offload)'
167- // SGLang older: 'cache hit' (no tier breakdown)
168- // vLLM LMCache: 'local_cache_hit' + 'external_kv_transfer' (+ 'local_compute' for miss)
169- // vLLM single: falls back to prefixCacheHitsTps total (= local cache only)
170- if ( chartSeries && chartSeries . prefillTps . length > 0 ) {
171- const sumPrompts = chartSeries . prefillTps . reduce ( ( s , p ) => s + p . value , 0 ) ;
172- if ( sumPrompts > 0 ) {
173- const sumOf = ( name : string ) : number =>
174- ( chartSeries . promptTokensBySource [ name ] ?? [ ] ) . reduce ( ( s , p ) => s + p . value , 0 ) ;
175- // CPU-offload hits: SGLang hicache + vLLM LMCache external transfer.
176- const cpuHits = sumOf ( 'cache hit (CPU offload)' ) + sumOf ( 'external_kv_transfer' ) ;
177- // GPU/HBM hits from source breakdown, summed across known aliases.
178- const hbmFromBreakdown =
179- sumOf ( 'cache hit (HBM)' ) + sumOf ( 'cache hit' ) + sumOf ( 'local_cache_hit' ) ;
180- // If the source breakdown has any GPU entry, use it. Otherwise fall back
181- // to total prefixCacheHitsTps sum (single-source vLLM path with no
182- // by_source metric — equals the lone cache counter's lifetime).
183- const gpuHits =
184- hbmFromBreakdown > 0
185- ? hbmFromBreakdown
186- : chartSeries . prefixCacheHitsTps . reduce ( ( s , p ) => s + p . value , 0 ) ;
187- const gpuRate = gpuHits / sumPrompts ;
188- const cpuRate = cpuHits > 0 ? cpuHits / sumPrompts : null ;
189- await sql `
190- update benchmark_results
191- set metrics = jsonb_set(
192- case when ${ cpuRate } ::numeric is not null
193- then jsonb_set(metrics, '{server_cpu_cache_hit_rate}', to_jsonb(${ cpuRate } ::numeric))
194- else metrics
195- end,
196- '{server_gpu_cache_hit_rate}',
197- to_jsonb(${ gpuRate } ::numeric)
170+ log ( `uploading trace_replay payloads in ${ formatBytes ( TRACE_REPLAY_UPLOAD_CHUNK_BYTES ) } chunks` ) ;
171+ await sql . begin ( async ( tx ) => {
172+ await tx `
173+ create temporary table trace_replay_upload_parts (
174+ field text not null,
175+ part integer not null,
176+ data bytea not null,
177+ primary key (field, part)
178+ ) on commit drop
179+ ` ;
180+
181+ const payloads : [ TraceReplayUploadField , Buffer | null ] [ ] = [
182+ [ 'profile_export_jsonl_gz' , profileGz ] ,
183+ [ 'server_metrics_csv' , serverMetricsCsv ] ,
184+ [ 'server_metrics_json_gz' , metricsJsonGz ] ,
185+ [ 'aggregate_stats' , aggregateStatsJson ] ,
186+ [ 'chart_series' , chartSeriesJson ] ,
187+ [ 'request_timeline' , requestTimelineJson ] ,
188+ ] ;
189+ for ( const [ field , payload ] of payloads ) {
190+ const uploadStart = Date . now ( ) ;
191+ const parts = await uploadTraceReplayPayloadChunks ( tx , field , payload ) ;
192+ log (
193+ `uploaded ${ field } =${ formatBytes ( payload ?. length ) } in ${ parts } part(s) ` +
194+ `(${ elapsed ( uploadStart ) } )` ,
195+ ) ;
196+ }
197+
198+ log ( 'assembling trace_replay blob row' ) ;
199+ const [ { id : traceReplayId } ] = await tx < { id : number } [ ] > `
200+ insert into agentic_trace_replay (
201+ profile_export_jsonl_gz,
202+ profile_export_uncompressed_size,
203+ server_metrics_csv,
204+ server_metrics_csv_size,
205+ server_metrics_json_gz,
206+ server_metrics_json_uncompressed_size,
207+ aggregate_stats,
208+ chart_series,
209+ request_timeline
210+ )
211+ values (
212+ (
213+ select string_agg(data, ''::bytea order by part)
214+ from pg_temp.trace_replay_upload_parts
215+ where field = 'profile_export_jsonl_gz'
216+ ),
217+ ${ profileSize } ,
218+ (
219+ select string_agg(data, ''::bytea order by part)
220+ from pg_temp.trace_replay_upload_parts
221+ where field = 'server_metrics_csv'
222+ ),
223+ ${ csvSize } ,
224+ (
225+ select string_agg(data, ''::bytea order by part)
226+ from pg_temp.trace_replay_upload_parts
227+ where field = 'server_metrics_json_gz'
228+ ),
229+ ${ metricsJsonSize } ,
230+ (
231+ select convert_from(string_agg(data, ''::bytea order by part), 'UTF8')::jsonb
232+ from pg_temp.trace_replay_upload_parts
233+ where field = 'aggregate_stats'
234+ ),
235+ (
236+ select convert_from(string_agg(data, ''::bytea order by part), 'UTF8')::jsonb
237+ from pg_temp.trace_replay_upload_parts
238+ where field = 'chart_series'
239+ ),
240+ (
241+ select convert_from(string_agg(data, ''::bytea order by part), 'UTF8')::jsonb
242+ from pg_temp.trace_replay_upload_parts
243+ where field = 'request_timeline'
198244 )
199- where id = any(${ sql . array ( unlinked . map ( ( r ) => r . id ) ) } ::bigint[])
200- ` ;
201- log ( 'updated cache-hit metrics from chart series' ) ;
245+ )
246+ returning id
247+ ` ;
248+ log ( `assembled trace_replay_id=${ traceReplayId } ` ) ;
249+
250+ const updateStart = Date . now ( ) ;
251+ log ( `linking trace_replay_id=${ traceReplayId } to ${ unlinked . length } benchmark row(s)` ) ;
252+ await tx `
253+ update benchmark_results
254+ set trace_replay_id = ${ traceReplayId }
255+ where id = any(${ tx . array ( unlinked . map ( ( r ) => r . id ) ) } ::bigint[])
256+ ` ;
257+ log ( `linked benchmark rows (${ elapsed ( updateStart ) } )` ) ;
258+
259+ // Derive lifetime GPU + CPU cache hit rates from chart_series. SGLang
260+ // runs don't populate these in the harness JSON; vLLM runs do but only
261+ // for GPU. We always recompute to keep the derivation consistent with
262+ // what the detail-page charts plot — overwriting any pre-existing value.
263+ //
264+ // Source label naming differs by framework / cache topology:
265+ // SGLang hicache: 'cache hit (HBM)' + 'cache hit (CPU offload)'
266+ // SGLang older: 'cache hit' (no tier breakdown)
267+ // vLLM LMCache: 'local_cache_hit' + 'external_kv_transfer' (+ 'local_compute' for miss)
268+ // vLLM single: falls back to prefixCacheHitsTps total (= local cache only)
269+ if ( chartSeries && chartSeries . prefillTps . length > 0 ) {
270+ const sumPrompts = chartSeries . prefillTps . reduce ( ( s , p ) => s + p . value , 0 ) ;
271+ if ( sumPrompts > 0 ) {
272+ const sumOf = ( name : string ) : number =>
273+ ( chartSeries . promptTokensBySource [ name ] ?? [ ] ) . reduce ( ( s , p ) => s + p . value , 0 ) ;
274+ // CPU-offload hits: SGLang hicache + vLLM LMCache external transfer.
275+ const cpuHits = sumOf ( 'cache hit (CPU offload)' ) + sumOf ( 'external_kv_transfer' ) ;
276+ // GPU/HBM hits from source breakdown, summed across known aliases.
277+ const hbmFromBreakdown =
278+ sumOf ( 'cache hit (HBM)' ) + sumOf ( 'cache hit' ) + sumOf ( 'local_cache_hit' ) ;
279+ // If the source breakdown has any GPU entry, use it. Otherwise fall back
280+ // to total prefixCacheHitsTps sum (single-source vLLM path with no
281+ // by_source metric — equals the lone cache counter's lifetime).
282+ const gpuHits =
283+ hbmFromBreakdown > 0
284+ ? hbmFromBreakdown
285+ : chartSeries . prefixCacheHitsTps . reduce ( ( s , p ) => s + p . value , 0 ) ;
286+ const gpuRate = gpuHits / sumPrompts ;
287+ const cpuRate = cpuHits > 0 ? cpuHits / sumPrompts : null ;
288+ await tx `
289+ update benchmark_results
290+ set metrics = jsonb_set(
291+ case when ${ cpuRate } ::numeric is not null
292+ then jsonb_set(metrics, '{server_cpu_cache_hit_rate}', to_jsonb(${ cpuRate } ::numeric))
293+ else metrics
294+ end,
295+ '{server_gpu_cache_hit_rate}',
296+ to_jsonb(${ gpuRate } ::numeric)
297+ )
298+ where id = any(${ tx . array ( unlinked . map ( ( r ) => r . id ) ) } ::bigint[])
299+ ` ;
300+ log ( 'updated cache-hit metrics from chart series' ) ;
301+ }
202302 }
203- }
303+ } ) ;
304+ log ( `inserted trace_replay payload (${ elapsed ( insertStart ) } )` ) ;
204305}
0 commit comments