From dde3afb72e1b4aa8b1b83e0d342b2c636d2d62ad Mon Sep 17 00:00:00 2001 From: vanceingalls Date: Sat, 11 Jul 2026 19:44:24 +0000 Subject: [PATCH 1/2] feat(producer): surface beginframe no-damage reuse counters in perf summary and telemetry --- packages/cli/src/commands/render.ts | 2 ++ packages/cli/src/telemetry/events.test.ts | 21 +++++++++++++ packages/cli/src/telemetry/events.ts | 6 ++++ packages/engine/src/services/frameCapture.ts | 2 ++ packages/engine/src/types.ts | 10 ++++++ .../services/render/perfSummary-dedup.test.ts | 31 +++++++++++++++++++ .../src/services/render/perfSummary.ts | 17 ++++++++++ .../src/services/renderOrchestrator.ts | 13 ++++++++ 8 files changed, 102 insertions(+) diff --git a/packages/cli/src/commands/render.ts b/packages/cli/src/commands/render.ts index e4a7af554..9db9a9606 100644 --- a/packages/cli/src/commands/render.ts +++ b/packages/cli/src/commands/render.ts @@ -1683,6 +1683,8 @@ function trackRenderMetrics( staticDedupSkipReason: perf?.staticDedup?.skipReason, staticDedupPredictedFrames: perf?.staticDedup?.predictedFrames, staticDedupReusedFrames: perf?.staticDedup?.reusedFrames, + beginFrameNoDamageFrames: perf?.beginFrameReuse?.noDamageFrames, + beginFrameHasDamageFrames: perf?.beginFrameReuse?.hasDamageFrames, deCaptureMode: perf?.drawElement?.mode, deCompileGate: perf?.drawElement?.compileGate, deClampReason: perf?.drawElement?.clampReason, diff --git a/packages/cli/src/telemetry/events.test.ts b/packages/cli/src/telemetry/events.test.ts index 68932d58b..a9f6c6a1b 100644 --- a/packages/cli/src/telemetry/events.test.ts +++ b/packages/cli/src/telemetry/events.test.ts @@ -244,6 +244,27 @@ describe("render telemetry events", () => { ); }); + it("sends beginframe no-damage reuse counters on render_complete", () => { + trackRenderComplete({ + durationMs: 6000, + fps: 30, + quality: "standard", + docker: false, + gpu: false, + beginFrameNoDamageFrames: 720, + beginFrameHasDamageFrames: 480, + }); + + expect(trackEvent).toHaveBeenCalledWith( + "render_complete", + expect.objectContaining({ + begin_frame_no_damage_frames: 720, + begin_frame_has_damage_frames: 480, + }), + undefined, + ); + }); + it("redacts render_observation messages and includes renderJobId for correlation", () => { trackRenderObservation({ renderJobId: "render-123", diff --git a/packages/cli/src/telemetry/events.ts b/packages/cli/src/telemetry/events.ts index 9a4353811..5337126bc 100644 --- a/packages/cli/src/telemetry/events.ts +++ b/packages/cli/src/telemetry/events.ts @@ -128,6 +128,10 @@ export function trackRenderComplete( staticDedupSkipReason?: string; staticDedupPredictedFrames?: number; staticDedupReusedFrames?: number; + // BeginFrame no-damage reuse outcome (Linux/Docker lastFrameCache — the BF + // counterpart of static dedup). Undefined outside beginframe capture mode. + beginFrameNoDamageFrames?: number; + beginFrameHasDamageFrames?: number; // drawElement fast-capture outcome (default-on release visibility). // Undefined on render paths with no capture session. deCaptureMode?: string; @@ -210,6 +214,8 @@ export function trackRenderComplete( static_dedup_skip_reason: props.staticDedupSkipReason, static_dedup_predicted_frames: props.staticDedupPredictedFrames, static_dedup_reused_frames: props.staticDedupReusedFrames, + begin_frame_no_damage_frames: props.beginFrameNoDamageFrames, + begin_frame_has_damage_frames: props.beginFrameHasDamageFrames, de_capture_mode: props.deCaptureMode, de_compile_gate: props.deCompileGate, de_clamp_reason: props.deClampReason, diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index 2b0847bc4..297cf0f76 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -3223,6 +3223,8 @@ export function getCapturePerfSummary(session: CaptureSession): CapturePerfSumma staticDedupArmed: (session.staticFrames?.size ?? 0) > 0, staticDedupPredicted: session.staticFrames?.size ?? 0, staticDedupSkipReason: session.staticDedupSkipReason, + beginFrameNoDamage: session.beginFrameNoDamageCount, + beginFrameHasDamage: session.beginFrameHasDamageCount, captureMode: session.captureMode, deGateReason: session.deGateReason, deWorkerEncode: session.workerEncodeEnabled ?? false, diff --git a/packages/engine/src/types.ts b/packages/engine/src/types.ts index fc267821f..e5892fd20 100644 --- a/packages/engine/src/types.ts +++ b/packages/engine/src/types.ts @@ -212,6 +212,16 @@ export interface CapturePerfSummary { * `|`-join distinct reasons when parallel workers diverge.) */ staticDedupSkipReason?: string; + // ── BeginFrame no-damage reuse (Linux/Docker lastFrameCache visibility) ── + /** + * BeginFrame frames where Chrome reported `hasDamage=false` and the previous + * buffer was reused from the per-page lastFrameCache (screenshotService.ts) — + * the BF counterpart of `staticDedupReused` (predictive dedup never arms + * under beginframe). Undefined/0 outside beginframe capture mode. + */ + beginFrameNoDamage?: number; + /** BeginFrame frames where Chrome reported damage (fresh screenshot encoded). */ + beginFrameHasDamage?: number; // ── drawElement fast-capture outcome (default-on release visibility) ── /** Final capture mode this session used: "drawelement" | "screenshot" | "beginframe". */ captureMode: string; diff --git a/packages/producer/src/services/render/perfSummary-dedup.test.ts b/packages/producer/src/services/render/perfSummary-dedup.test.ts index a2b9685d1..51ab7de22 100644 --- a/packages/producer/src/services/render/perfSummary-dedup.test.ts +++ b/packages/producer/src/services/render/perfSummary-dedup.test.ts @@ -162,3 +162,34 @@ describe("buildRenderPerfSummary capture average attribution", () => { expect(summary.captureAvgMs).toBe(43); }); }); + +describe("buildRenderPerfSummary beginframe no-damage reuse aggregation", () => { + it("is undefined when no capture session ran", () => { + expect(buildRenderPerfSummary(baseInput([])).beginFrameReuse).toBeUndefined(); + }); + + it("is undefined when no session captured in beginframe mode (both counters zero)", () => { + const s = buildRenderPerfSummary( + baseInput([perf({ staticDedupEnabled: true, staticDedupReused: 10 })]), + ).beginFrameReuse; + expect(s).toBeUndefined(); + }); + + it("SUMs no-damage and has-damage frames across workers", () => { + const s = buildRenderPerfSummary( + baseInput([ + perf({ beginFrameNoDamage: 240, beginFrameHasDamage: 160 }), + perf({ beginFrameNoDamage: 245, beginFrameHasDamage: 155 }), + perf({ beginFrameNoDamage: 235, beginFrameHasDamage: 165 }), + ]), + ).beginFrameReuse; + expect(s).toEqual({ noDamageFrames: 720, hasDamageFrames: 480 }); + }); + + it("reports an all-damage beginframe render (noDamageFrames 0, not undefined)", () => { + const s = buildRenderPerfSummary( + baseInput([perf({ beginFrameNoDamage: 0, beginFrameHasDamage: 400 })]), + ).beginFrameReuse; + expect(s).toEqual({ noDamageFrames: 0, hasDamageFrames: 400 }); + }); +}); diff --git a/packages/producer/src/services/render/perfSummary.ts b/packages/producer/src/services/render/perfSummary.ts index 0200ae460..915549933 100644 --- a/packages/producer/src/services/render/perfSummary.ts +++ b/packages/producer/src/services/render/perfSummary.ts @@ -141,6 +141,22 @@ function aggregateDedup(perfs: CapturePerfSummary[]): RenderPerfSummary["staticD }; } +/** + * Collapse per-session/per-worker BeginFrame damage counters into one + * render-level reuse outcome (SUM across workers — each worker ticks its own + * frame range). Both zero ⟺ no beginframe session ran (screenshot/drawElement + * sessions never increment these) → undefined, mirroring staticDedup's + * "undefined when it never engaged" contract. + */ +function aggregateBeginFrameReuse( + perfs: CapturePerfSummary[], +): RenderPerfSummary["beginFrameReuse"] { + const noDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameNoDamage ?? 0), 0); + const hasDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameHasDamage ?? 0), 0); + if (noDamageFrames + hasDamageFrames === 0) return undefined; + return { noDamageFrames, hasDamageFrames }; +} + export function buildRenderPerfSummary(input: { job: RenderJob; workerCount: number; @@ -224,6 +240,7 @@ export function buildRenderPerfSummary(input: { peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)), peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)), staticDedup: aggregateDedup(input.dedupPerfs), + beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs), drawElement: aggregateDrawElement( input.dedupPerfs, input.drawElement ?? { selfVerifyFallback: false }, diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index 614ebb75c..e6612a8e3 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -372,6 +372,19 @@ export interface RenderPerfSummary { reusedFrames: number; skipReason?: string; }; + /** + * BeginFrame no-damage reuse outcome for this render (Linux/Docker), + * aggregated across the sequential session or all parallel workers: frames + * Chrome reported unchanged (`hasDamage=false` → previous buffer reused via + * the engine's lastFrameCache) vs frames freshly encoded. The BF counterpart + * of `staticDedup` (predictive dedup never arms under beginframe); the + * static-frame fraction is noDamageFrames / (noDamageFrames + hasDamageFrames). + * Undefined when no session captured in beginframe mode. + */ + beginFrameReuse?: { + noDamageFrames: number; + hasDamageFrames: number; + }; /** * drawElement fast-capture outcome for this render (default-on release * visibility). Undefined when no capture session ran. From 2cd8a750e3b5d9f6ff86f2f99fd16b2699b32a36 Mon Sep 17 00:00:00 2001 From: vanceingalls Date: Sat, 11 Jul 2026 20:32:20 +0000 Subject: [PATCH 2/2] docs(producer): note attempt-scoped beginFrameReuse semantics after partial retries --- packages/producer/src/services/render/perfSummary.ts | 4 +++- packages/producer/src/services/renderOrchestrator.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/producer/src/services/render/perfSummary.ts b/packages/producer/src/services/render/perfSummary.ts index 915549933..5fde2c880 100644 --- a/packages/producer/src/services/render/perfSummary.ts +++ b/packages/producer/src/services/render/perfSummary.ts @@ -146,7 +146,9 @@ function aggregateDedup(perfs: CapturePerfSummary[]): RenderPerfSummary["staticD * render-level reuse outcome (SUM across workers — each worker ticks its own * frame range). Both zero ⟺ no beginframe session ran (screenshot/drawElement * sessions never increment these) → undefined, mirroring staticDedup's - * "undefined when it never engaged" contract. + * "undefined when it never engaged" contract. Also inherits `dedupPerfs`' + * retry semantics: a partial-capture retry resets the sink, so the sums cover + * only the final attempt's recaptured ranges (may be < totalFrames). */ function aggregateBeginFrameReuse( perfs: CapturePerfSummary[], diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index e6612a8e3..d7a49964e 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -380,6 +380,12 @@ export interface RenderPerfSummary { * of `staticDedup` (predictive dedup never arms under beginframe); the * static-frame fraction is noDamageFrames / (noDamageFrames + hasDamageFrames). * Undefined when no session captured in beginframe mode. + * + * Like every metric aggregated from `dedupPerfs` (staticDedup, drawElement, + * subTimelineWait), a partial-capture RETRY replaces the counters with the + * final attempt's set (see the reset in executeDiskCaptureWithAdaptiveRetry) + * — after a missing-range retry the counts cover only the recaptured ranges, + * not the whole render, so noDamage + hasDamage may be < totalFrames. */ beginFrameReuse?: { noDamageFrames: number;