feat(cli): emit Chrome memory, capture path and segment fields on render events

Both events carry the live observability values, so a render_error that
never built a perf summary still reports the last known Chrome memory.
render_complete prefers the aggregate from RenderPerfSummary.chromeMemory,
which covers every worker rather than whichever session sampled last.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-09-18 20:52:25 -07:00
co-authored by Claude Opus 5
parent 2cf8024a7c
commit ddbc9a8a98
4 changed files with 129 additions and 0 deletions
+5
View File
@@ -1751,6 +1751,11 @@ function trackRenderMetrics(
catalogUsage: options.catalogUsage,
...renderOutputShapeTelemetryPayload(options),
...renderEnvironmentTelemetryPayload(options),
chromeBrowserRssPeakMb: perf?.chromeMemory?.browserRssPeakMb,
chromeRendererRssPeakMb: perf?.chromeMemory?.rendererRssPeakMb,
chromeRssLastMb: perf?.chromeMemory?.rssLastMb,
chromeGpuProcessSeenLastSample: perf?.chromeMemory?.gpuProcessSeenLastSample,
chromeMemorySamples: perf?.chromeMemory?.samples,
staticDedupEnabled: perf?.staticDedup?.enabled,
staticDedupArmed: perf?.staticDedup?.armed,
staticDedupSkipReason: perf?.staticDedup?.skipReason,
+83
View File
@@ -680,6 +680,89 @@ describe("render telemetry events", () => {
);
});
it("maps Chrome memory and capture path observability onto render_error", () => {
trackRenderError({
fps: 30,
quality: "draft",
docker: false,
captureChromeBrowserRssPeakMb: 210,
captureChromeRendererRssPeakMb: 1900,
captureChromeRssLastMb: 2400,
captureChromeGpuProcessSeenLastSample: true,
captureChromeMemorySamples: 42,
captureCapturePath: "streaming",
captureSegmentIndex: 3,
captureSegmentRetries: 1,
});
expect(trackEvent).toHaveBeenCalledWith(
"render_error",
expect.objectContaining({
chrome_browser_rss_peak_mb: 210,
chrome_renderer_rss_peak_mb: 1900,
chrome_rss_last_mb: 2400,
gpu_process_seen_last_sample: true,
chrome_memory_samples: 42,
capture_path: "streaming",
segment_index: 3,
segment_retries: 1,
}),
undefined,
);
});
it("prefers the aggregate Chrome memory over the live sample on render_complete", () => {
// The live observability values are the last session's; the perf summary
// aggregates every worker. On success both are present and the aggregate
// must win, or a multi-worker render reports one worker's peak as the
// fleet's.
trackRenderComplete({
durationMs: 1000,
fps: 30,
quality: "draft",
docker: false,
gpu: false,
captureChromeBrowserRssPeakMb: 100,
captureChromeRendererRssPeakMb: 800,
captureChromeRssLastMb: 900,
captureChromeMemorySamples: 5,
chromeBrowserRssPeakMb: 210,
chromeRendererRssPeakMb: 1900,
chromeRssLastMb: 2400,
chromeGpuProcessSeenLastSample: true,
chromeMemorySamples: 42,
});
const props = trackEvent.mock.calls[0]?.[1] as Record<string, unknown>;
expect(props).toMatchObject({
chrome_browser_rss_peak_mb: 210,
chrome_renderer_rss_peak_mb: 1900,
chrome_rss_last_mb: 2400,
gpu_process_seen_last_sample: true,
chrome_memory_samples: 42,
});
});
it("falls back to the live Chrome memory sample when no aggregate exists", () => {
trackRenderComplete({
durationMs: 1000,
fps: 30,
quality: "draft",
docker: false,
gpu: false,
captureChromeBrowserRssPeakMb: 100,
captureChromeMemorySamples: 5,
captureCapturePath: "disk",
});
const props = trackEvent.mock.calls[0]?.[1] as Record<string, unknown>;
expect(props).toMatchObject({
chrome_browser_rss_peak_mb: 100,
chrome_memory_samples: 5,
capture_path: "disk",
});
});
it("carries the DE parallel-router/inversion cohort on render_error (hard failure, not just self-verify revert)", () => {
trackRenderError({
fps: 30,
+33
View File
@@ -99,6 +99,15 @@ export interface RenderObservabilityTelemetryPayload {
/** Non-DE parallel-streaming router outcome ("screenshot" | "beginframe" —
* routed; "eligible_off" — would route but the kill switch is off). */
captureParallelStream?: string;
/** Chrome memory from the engine sampler (Phase −1, long-form render plan). */
captureChromeBrowserRssPeakMb?: number;
captureChromeRendererRssPeakMb?: number;
captureChromeRssLastMb?: number;
captureChromeGpuProcessSeenLastSample?: boolean;
captureChromeMemorySamples?: number;
captureCapturePath?: string;
captureSegmentIndex?: number;
captureSegmentRetries?: number;
observabilityExtractVideoCount?: number;
observabilityExtractedVideoCount?: number;
observabilityExtractTotalFrames?: number;
@@ -171,6 +180,14 @@ function renderObservabilityEventProperties(props: RenderObservabilityTelemetryP
de_fallback_frame_index: props.captureDeFallbackFrameIndex,
de_fallback_threshold_db: props.captureDeFallbackThresholdDb,
capture_parallel_stream: props.captureParallelStream,
chrome_browser_rss_peak_mb: props.captureChromeBrowserRssPeakMb,
chrome_renderer_rss_peak_mb: props.captureChromeRendererRssPeakMb,
chrome_rss_last_mb: props.captureChromeRssLastMb,
gpu_process_seen_last_sample: props.captureChromeGpuProcessSeenLastSample,
chrome_memory_samples: props.captureChromeMemorySamples,
capture_path: props.captureCapturePath,
segment_index: props.captureSegmentIndex,
segment_retries: props.captureSegmentRetries,
observability_extract_video_count: props.observabilityExtractVideoCount,
observability_extracted_video_count: props.observabilityExtractedVideoCount,
observability_extract_total_frames: props.observabilityExtractTotalFrames,
@@ -401,6 +418,14 @@ export function trackRenderComplete(
capturePeakMs?: number;
// Resource usage
peakMemoryMb?: number;
// Aggregate Chrome memory (RenderPerfSummary.chromeMemory); overrides the
// live observability values when both are present, because the live ones
// are only the last session's and the aggregate covers every worker.
chromeBrowserRssPeakMb?: number;
chromeRendererRssPeakMb?: number;
chromeRssLastMb?: number;
chromeGpuProcessSeenLastSample?: boolean;
chromeMemorySamples?: number;
memoryFreeMb?: number;
tmpPeakBytes?: number;
// Per-stage timings (subset of RenderPerfSummary.stages)
@@ -438,6 +463,14 @@ export function trackRenderComplete(
// studioRenderTelemetry.ts never populates drawElement, so without the
// fallback the explicit key still wins the spread with an undefined.
...renderObservabilityEventProperties(props),
chrome_browser_rss_peak_mb:
props.chromeBrowserRssPeakMb ?? props.captureChromeBrowserRssPeakMb,
chrome_renderer_rss_peak_mb:
props.chromeRendererRssPeakMb ?? props.captureChromeRendererRssPeakMb,
chrome_rss_last_mb: props.chromeRssLastMb ?? props.captureChromeRssLastMb,
gpu_process_seen_last_sample:
props.chromeGpuProcessSeenLastSample ?? props.captureChromeGpuProcessSeenLastSample,
chrome_memory_samples: props.chromeMemorySamples ?? props.captureChromeMemorySamples,
duration_ms: props.durationMs,
fps: props.fps,
quality: props.quality,
@@ -66,6 +66,14 @@ export function renderObservabilityTelemetryPayload(
captureDeFallbackFrameIndex: capture.deFallbackFrameIndex,
captureDeFallbackThresholdDb: capture.deFallbackThresholdDb,
captureParallelStream: capture.captureParallelStream,
captureChromeBrowserRssPeakMb: capture.chromeBrowserRssPeakMb,
captureChromeRendererRssPeakMb: capture.chromeRendererRssPeakMb,
captureChromeRssLastMb: capture.chromeRssLastMb,
captureChromeGpuProcessSeenLastSample: capture.chromeGpuProcessSeenLastSample,
captureChromeMemorySamples: capture.chromeMemorySamples,
captureCapturePath: capture.capturePath,
captureSegmentIndex: capture.segmentIndex,
captureSegmentRetries: capture.segmentRetries,
observabilityExtractVideoCount: extraction?.videoCount,
observabilityExtractedVideoCount: extraction?.extractedVideoCount,
observabilityExtractTotalFrames: extraction?.totalFramesExtracted,