mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(engine): realistic worker memory budget + sizing/feedback telemetry
This commit is contained in:
@@ -16,9 +16,11 @@ import {
|
||||
type CaptureOptions,
|
||||
type CaptureSession,
|
||||
type EngineConfig,
|
||||
type WorkerSizing,
|
||||
calculateOptimalWorkers,
|
||||
captureFrameToBuffer,
|
||||
closeCaptureSession,
|
||||
computeWorkerSizing,
|
||||
createCaptureSession,
|
||||
initializeSession,
|
||||
} from "@hyperframes/engine";
|
||||
@@ -113,6 +115,8 @@ export function resolveRenderWorkerCount(
|
||||
compiled: Pick<CompiledComposition, "hasShaderTransitions" | "renderModeHints">,
|
||||
log: ProducerLogger = defaultLogger,
|
||||
measuredCaptureCost?: CaptureCostEstimate,
|
||||
/** Sink for the sizing provenance so the orchestrator can thread it into telemetry. */
|
||||
onSizing?: (sizing: WorkerSizing) => void,
|
||||
): number {
|
||||
// TODO(htmlInCanvas): workaround — Chrome's experimental drawElementImage
|
||||
// API (CanvasDrawElement) is non-deterministic across concurrent browser
|
||||
@@ -144,10 +148,27 @@ export function resolveRenderWorkerCount(
|
||||
estimateCaptureCostMultiplier(compiled),
|
||||
measuredCaptureCost,
|
||||
);
|
||||
const workerCount = calculateOptimalWorkers(totalFrames, requestedWorkers, {
|
||||
const sizing = computeWorkerSizing(totalFrames, requestedWorkers, {
|
||||
...cfg,
|
||||
captureCostMultiplier: captureCost.multiplier,
|
||||
});
|
||||
onSizing?.(sizing);
|
||||
const workerCount = sizing.workers;
|
||||
|
||||
// Advisory heap check (field OOM, 0.7.66 on 24GB: auto-picked 6 workers
|
||||
// blew Node's default ~4GB heap). Not enforced yet — the budget constant is
|
||||
// derived from one field report; the workers_heap_* telemetry emitted with
|
||||
// this sizing decides whether to enforce. The warning gives the operator
|
||||
// the actionable knobs today.
|
||||
if (sizing.exceedsHeapAdvisory) {
|
||||
log.warn(
|
||||
`[Render] ${workerCount} capture workers may exceed this process's V8 heap ` +
|
||||
`(limit ${sizing.heapLimitMb}MB supports ~${sizing.heapBasedWorkers}). If the render ` +
|
||||
`dies with "JavaScript heap out of memory", raise the heap ` +
|
||||
`(NODE_OPTIONS=--max-old-space-size=8192) or pass --workers ${sizing.heapBasedWorkers}.`,
|
||||
{ heapLimitMb: sizing.heapLimitMb, heapBasedWorkers: sizing.heapBasedWorkers },
|
||||
);
|
||||
}
|
||||
|
||||
if (requestedWorkers !== undefined || captureCost.multiplier <= 1) {
|
||||
return workerCount;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { fpsToNumber } from "@hyperframes/core";
|
||||
import type { CapturePerfSummary, SubTimelineWaitOutcome } from "@hyperframes/engine";
|
||||
import type { CapturePerfSummary, SubTimelineWaitOutcome, WorkerSizing } from "@hyperframes/engine";
|
||||
import type { CaptureCalibrationSample, CaptureCostEstimate } from "./captureCost.js";
|
||||
import type {
|
||||
CaptureAttemptSummary,
|
||||
@@ -181,6 +181,8 @@ function aggregateBeginFrameReuse(
|
||||
export function buildRenderPerfSummary(input: {
|
||||
job: RenderJob;
|
||||
workerCount: number;
|
||||
/** Auto-sizing provenance; undefined when a pin (htmlInCanvas / low-memory) short-circuited sizing. */
|
||||
workerSizing?: WorkerSizing;
|
||||
enableChunkedEncode: boolean;
|
||||
chunkedEncodeSize: number;
|
||||
compositionDurationSeconds: number;
|
||||
@@ -217,6 +219,7 @@ export function buildRenderPerfSummary(input: {
|
||||
fps: fpsToNumber(input.job.config.fps),
|
||||
quality: input.job.config.quality,
|
||||
workers: input.workerCount,
|
||||
workerSizing: input.workerSizing,
|
||||
chunkedEncode: input.enableChunkedEncode,
|
||||
chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
|
||||
compositionDurationSeconds: input.compositionDurationSeconds,
|
||||
|
||||
@@ -76,6 +76,7 @@ import {
|
||||
type CapturePerfSummary,
|
||||
type CaptureWarning,
|
||||
type SubTimelineWaitOutcome,
|
||||
type WorkerSizing,
|
||||
resolveBrowserGpuMode,
|
||||
resolveHeadlessShellPath,
|
||||
applyConcreteGpuScreenshotClamp,
|
||||
@@ -373,6 +374,14 @@ export interface RenderPerfSummary {
|
||||
fps: number;
|
||||
quality: string;
|
||||
workers: number;
|
||||
/**
|
||||
* Provenance of the auto worker-sizing decision (undefined when the
|
||||
* htmlInCanvas / low-memory pins short-circuited sizing). `boundBy` names
|
||||
* the binding constraint; the heap fields are the advisory budget being
|
||||
* validated by fleet telemetry before enforcement — see
|
||||
* `computeWorkerSizing` in @hyperframes/engine.
|
||||
*/
|
||||
workerSizing?: WorkerSizing;
|
||||
chunkedEncode: boolean;
|
||||
chunkSizeFrames: number | null;
|
||||
compositionDurationSeconds: number;
|
||||
@@ -1703,6 +1712,7 @@ async function executeRenderPipeline(input: {
|
||||
// "routed" = the parallel router fired and held; "reverted" = fired but
|
||||
// the self-verify retry rolled back; undefined = never fired.
|
||||
let deParallelRouter: "routed" | "reverted" | undefined;
|
||||
let workerSizing: WorkerSizing | undefined;
|
||||
|
||||
execution.defer("rollback staged artifact", () => artifactTransaction.rollback());
|
||||
execution.defer("close file server", () => {
|
||||
@@ -2475,6 +2485,9 @@ async function executeRenderPipeline(input: {
|
||||
compiled,
|
||||
log,
|
||||
captureCalibration?.estimate,
|
||||
(sizing) => {
|
||||
workerSizing = sizing;
|
||||
},
|
||||
);
|
||||
// DE priority inversion — see shouldPreferSingleWorkerDrawElement for the
|
||||
// policy and benchmark rationale (eligibility resolved above, before
|
||||
@@ -3281,6 +3294,7 @@ async function executeRenderPipeline(input: {
|
||||
const perfSummary = buildRenderPerfSummary({
|
||||
job,
|
||||
workerCount,
|
||||
workerSizing,
|
||||
enableChunkedEncode,
|
||||
chunkedEncodeSize,
|
||||
compositionDurationSeconds: composition.duration,
|
||||
|
||||
Reference in New Issue
Block a user