diff --git a/packages/producer/src/services/render/perfSummary.test.ts b/packages/producer/src/services/render/perfSummary.test.ts index c56e554b9..29aae01d1 100644 --- a/packages/producer/src/services/render/perfSummary.test.ts +++ b/packages/producer/src/services/render/perfSummary.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; -import { roundDb } from "./perfSummary.js"; +import { buildRenderPerfSummary, roundDb } from "./perfSummary.js"; +import type { RenderJob } from "../renderOrchestrator.js"; describe("roundDb", () => { it("rounds to 1 decimal", () => { @@ -20,3 +21,46 @@ describe("roundDb", () => { expect(roundDb(roundDb(28.4373))).toBe(28.4); }); }); + +// ponytail: minimal stub — only the fields buildRenderPerfSummary reads are real. +const baseInput = { + job: { id: "r1", config: { fps: { num: 30, den: 1 }, quality: "high" } } as unknown as RenderJob, + workerCount: 4, + enableChunkedEncode: false, + chunkedEncodeSize: 0, + compositionDurationSeconds: 5, + totalFrames: 150, + outputWidth: 1920, + outputHeight: 1080, + videoCount: 0, + audioCount: 0, + totalElapsedMs: 1234, + perfStages: {}, + videoExtractBreakdown: undefined, + tmpPeakBytes: 0, + captureAttempts: [], + hdrDiagnostics: { videoExtractionFailures: 0, imageDecodeFailures: 0 }, + peakRssBytes: 0, + peakHeapUsedBytes: 0, + dedupPerfs: [], +}; + +describe("buildRenderPerfSummary host telemetry", () => { + it("captures host facts and threads gpuDisabled through", () => { + const summary = buildRenderPerfSummary({ ...baseInput, gpuDisabled: true }); + expect(summary.host).toBeDefined(); + expect(summary.host?.gpuDisabled).toBe(true); + expect(summary.host?.cpuCount).toBeGreaterThan(0); + expect(summary.host?.totalMemMb).toBeGreaterThan(0); + expect(summary.host?.platform).toBe(process.platform); + expect(summary.host?.arch).toBe(process.arch); + expect(summary.host?.nodeVersion).toBe(process.version); + }); + + it("reflects gpuDisabled=false", () => { + const summary = buildRenderPerfSummary({ ...baseInput, gpuDisabled: false }); + expect(summary.host?.gpuDisabled).toBe(false); + // workers vs cores is the headline correlation — both must be present. + expect(summary.workers).toBe(4); + }); +}); diff --git a/packages/producer/src/services/render/perfSummary.ts b/packages/producer/src/services/render/perfSummary.ts index e4f4db3fc..50ff45578 100644 --- a/packages/producer/src/services/render/perfSummary.ts +++ b/packages/producer/src/services/render/perfSummary.ts @@ -3,6 +3,7 @@ * the `perf-summary.json` debug artifact. */ +import { arch, cpus, platform, totalmem } from "node:os"; import { fpsToNumber } from "@hyperframes/core"; import type { CapturePerfSummary, SubTimelineWaitOutcome, WorkerSizing } from "@hyperframes/engine"; import type { CaptureCalibrationSample, CaptureCostEstimate } from "./captureCost.js"; @@ -221,6 +222,8 @@ export function buildRenderPerfSummary(input: { /** Per-session/per-worker static-dedup perf; aggregated into `staticDedup`. */ dedupPerfs: CapturePerfSummary[]; drawElement?: DrawElementPerfInput; + /** `cfg.disableGpu` — the hard `--disable-gpu` flag, for the `host` GPU picture. */ + gpuDisabled: boolean; }): RenderPerfSummary { return { renderId: input.job.id, @@ -282,5 +285,13 @@ export function buildRenderPerfSummary(input: { input.dedupPerfs, input.drawElement ?? { selfVerifyFallback: false }, ), + host: { + platform: platform(), + arch: arch(), + cpuCount: cpus().length, + totalMemMb: Math.round(totalmem() / (1024 * 1024)), + nodeVersion: process.version, + gpuDisabled: input.gpuDisabled, + }, }; } diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index f94affaf6..235c23ee9 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -546,6 +546,27 @@ export interface RenderPerfSummary { /** Per-frame "No cached paint record" screenshot fallbacks. */ ncprFallbacks: number; }; + /** + * Render-host facts, captured from the orchestrator process. Lets fleet-wide + * telemetry correlate render performance with the machine it ran on — most + * importantly `cpuCount` vs the top-level `workers` (are we under-/over- + * subscribing cores?) and `totalMemMb` (does `lowMemoryMode` / single-worker + * collapse track real memory pressure?). `gpuDisabled` completes the GPU + * picture alongside `observability.browserGpuMode`. + * + * Reflects the ORCHESTRATOR host. For distributed renders the chunk workers + * may run on different machines; this is still the right signal for the + * common single-machine render. Optional for back-compat with serialized + * older summaries. + */ + host?: { + platform: string; + arch: string; + cpuCount: number; + totalMemMb: number; + nodeVersion: string; + gpuDisabled: boolean; + }; } export interface HdrDiagnostics { @@ -3971,6 +3992,7 @@ async function executeRenderPipeline(input: { observability: observabilitySummary, peakRssBytes: memSampler.peakRssBytes(), peakHeapUsedBytes: memSampler.peakHeapUsedBytes(), + gpuDisabled: cfg.disableGpu, }); job.perfSummary = perfSummary; if (job.config.debug) {