/**
 * Parallel Coordinator Service
 *
 * Coordinates parallel frame capture across multiple Puppeteer sessions.
 * Auto-detects optimal worker count based on CPU/memory.
 */
import { type CaptureSession, type CaptureOptions, type CapturePerfSummary, type BeforeCaptureHook } from "./frameCapture.js";
import { type EngineConfig } from "../config.js";
import { CaptureFailure } from "./captureFailure.js";
export interface WorkerTask {
    workerId: number;
    startFrame: number;
    endFrame: number;
    outputDir: string;
    /**
     * Offset subtracted from the absolute frame index when naming the captured
     * file (`frame_<i - outputFrameOffset>.{ext}`). Default 0. Distributed
     * chunks set this to the chunk's absolute startFrame so file names land
     * 0-indexed within the chunk's range — the encoder reads frames
     * sequentially without an `-start_number` override. The per-frame TIME
     * calculation still uses the absolute frame index.
     */
    outputFrameOffset?: number;
    /**
     * Frame stride for interleaved distribution (HF_DE_PARALLEL_STREAM spike):
     * the worker captures startFrame, startFrame+stride, … < endFrame. Default 1
     * (contiguous range). Interleaving keeps the ordered streaming writer's
     * reorder window at O(workerCount) frames instead of O(totalFrames/N).
     */
    frameStride?: number;
}
export interface WorkerResult {
    workerId: number;
    framesCaptured: number;
    startFrame: number;
    endFrame: number;
    /**
     * Mirrors the originating `WorkerTask.frameStride` (default 1). Required by
     * `expectedFramesForTask` — without it, every interleaved (stride > 1)
     * worker's expected count is computed as the full contiguous range instead
     * of range/stride, so a fully-successful worker looks like it under-captured
     * and gets misclassified as a silent death (see `synthesizeSilentWorkerExitError`).
     */
    frameStride?: number;
    durationMs: number;
    perf?: CapturePerfSummary;
    error?: string;
    diagnostics?: string[];
    failure?: CaptureFailure;
}
export interface ParallelProgress {
    totalFrames: number;
    capturedFrames: number;
    activeWorkers: number;
    workerProgress: Map<number, number>;
}
export interface WorkerSizingConfig extends Partial<Pick<EngineConfig, "concurrency" | "coresPerWorker" | "minParallelFrames" | "largeRenderThreshold">> {
    /**
     * Relative per-frame capture cost for auto worker sizing. Values above 1
     * represent compositions that put more CPU pressure on each Chrome worker
     * than a plain DOM screenshot. Explicit --workers requests ignore this hint.
     */
    captureCostMultiplier?: number;
}
type WorkerBrowserPoolDecision = {
    parallel?: boolean;
    platform: NodeJS.Platform;
    forceScreenshot?: boolean;
    deviceScaleFactor?: number;
    headlessShellPath?: string;
};
export declare function shouldDisableBrowserPoolForParallelWorker({ parallel, platform, deviceScaleFactor, headlessShellPath, }: WorkerBrowserPoolDecision): boolean;
export declare function selectWorkerDiagnostics(lines: readonly string[], maxLines?: number): string[];
/**
 * Expected frame count for a worker task, honoring its stride. Contiguous
 * tasks (stride 1) expect `endFrame - startFrame`; interleaved tasks
 * (stride > 1) expect `ceil((endFrame - startFrame) / stride)`, matching
 * the loop shape in `captureFrameRange`.
 */
export declare function expectedFramesForTask(task: {
    startFrame: number;
    endFrame: number;
    frameStride?: number;
}): number;
/**
 * Synthetic terminal-error message for a worker whose exit didn't produce
 * an explicit error string but under-captured its expected frame range.
 * Field signal ts=1784042064: a 1292s Windows render hard-exited during
 * capture with no final error string, leaving the operator with no
 * actionable trace. This message surfaces the shortfall + reruns hint
 * so downstream telemetry (and operators grepping logs) can classify the
 * failure instead of it disappearing silently.
 */
export declare function synthesizeSilentWorkerExitError(result: Pick<WorkerResult, "workerId" | "framesCaptured" | "startFrame" | "endFrame">, expectedFrames: number): string;
/**
 * A worker may return without an error string yet with `framesCaptured`
 * below its task's expected count — the silent-exit shape field signal
 * ts=1784042064 called out. Synthesize a terminal error string in-place so
 * the caller's failure filter treats it as a failure (and so the caller's
 * failure message actually names what went wrong). Requires each result to
 * carry `frameStride` (see `WorkerResult`) — without it, an interleaved
 * worker's true `framesCaptured` (range/stride) is compared against the full
 * contiguous range and every successful interleaved worker false-positives.
 */
export declare function flagSilentWorkerExits(results: WorkerResult[]): void;
export declare function formatWorkerFailure(result: WorkerResult): string;
/** Which constraint produced the final auto-sized worker count. */
export type WorkerSizingBound = "explicit" | "too_few_frames" | "cpu" | "memory" | "frames" | "max_workers" | "min_parallel_floor" | "contention";
/**
 * Full provenance of a worker-sizing decision. Threaded into render
 * observability/telemetry so fleet data can answer "why N workers?" and
 * "would the heap budget have prevented this OOM?" without a repro.
 */
export interface WorkerSizing {
    workers: number;
    boundBy: WorkerSizingBound;
    cpuBasedWorkers: number;
    memoryBasedWorkers: number;
    frameBasedWorkers: number;
    effectiveMaxWorkers: number;
    /**
     * ADVISORY, not enforced (see HEAP_PER_WORKER_MB): how many workers the
     * parent process's V8 heap could feed. Compare against `workers` in
     * telemetry to validate the budget before enforcement.
     */
    heapBasedWorkers: number;
    /** V8 `heap_size_limit` for the parent process, MB. */
    heapLimitMb: number;
    totalMemoryMb: number;
    cpuCount: number;
    captureCostMultiplier: number;
    /** true when the chosen count exceeds the advisory heap budget. */
    exceedsHeapAdvisory: boolean;
}
/**
 * Compute the auto worker count together with the full sizing provenance.
 * `calculateOptimalWorkers` is the thin legacy wrapper returning `.workers`.
 */
export declare function computeWorkerSizing(totalFrames: number, requested?: number, config?: WorkerSizingConfig): WorkerSizing;
export declare function calculateOptimalWorkers(totalFrames: number, requested?: number, config?: WorkerSizingConfig): number;
export declare function distributeFrames(totalFrames: number, workerCount: number, workDir: string, rangeStart?: number): WorkerTask[];
/**
 * Interleaved (round-robin) distribution: worker i captures frames
 * i, i+N, i+2N, …. Seek-based capture makes stride access free (every frame
 * is an absolute seek), and the streaming reorder window shrinks from
 * totalFrames/N to N — contiguous chunks serialize workers behind the
 * ordered writer (worker 1's first frame waits for ALL of worker 0's).
 * HF_DE_PARALLEL_STREAM spike; disk-path capture keeps contiguous chunks.
 */
export declare function distributeFramesInterleaved(totalFrames: number, workerCount: number, workDir: string, rangeStart?: number): WorkerTask[];
/**
 * Decide whether a parallel worker should run the per-worker SwiftShader
 * assertion. Gated to worker 0 only: workers within a chunk share the same
 * Chrome binary, flags, and OS/driver state, so one verification per chunk
 * is sufficient. See `heygen-com/hyperframes#955`.
 */
export declare function shouldVerifyWorkerGpu(workerId: number, config?: Partial<EngineConfig>): boolean;
/**
 * The armed self-verify sample indices this task actually captured: inside
 * `[startFrame, endFrame)` and on the task's stride lattice. Mirrors the
 * capture loop in `captureFrameRange` (`i += stride` from `startFrame`).
 */
export declare function selectVerifySampleIndicesForTask(sampleIndices: Iterable<number>, task: Pick<WorkerTask, "startFrame" | "endFrame" | "frameStride">): number[];
export declare function verifyDiskDrawElementSamples(session: CaptureSession, task: WorkerTask, streaming: boolean): Promise<void>;
/**
 * drawElement self-verify sample count for multi-worker capture. Each worker
 * arms the same shared sample grid but drains only ~1/N of it, and N
 * concurrent hardware-GPU browsers are exactly where compositor-tile damage
 * shows up (wild 0.7.52 black-slab report) — so density rises with worker
 * count: 4 base + 2 per extra worker, clamped to the verify path's max of 8.
 * A caller-set value passes through untouched, and explicit HF_DE_VERIFY
 * still overrides inside the session.
 */
export declare function resolveParallelDeVerifySamples(callerValue: number | undefined, workerCount: number): number | undefined;
export declare function executeParallelCapture(serverUrl: string, workDir: string, tasks: WorkerTask[], captureOptions: CaptureOptions, createBeforeCaptureHook: () => BeforeCaptureHook | null, signal?: AbortSignal, onProgress?: (progress: ParallelProgress) => void, onFrameBuffer?: (frameIndex: number, buffer: Buffer, session: CaptureSession) => Promise<void>, config?: Partial<EngineConfig>): Promise<WorkerResult[]>;
export declare function mergeWorkerFrames(workDir: string, tasks: WorkerTask[], outputDir: string): Promise<number>;
export declare function getSystemResources(): {
    cpuCores: number;
    totalMemoryMB: number;
    freeMemoryMB: number;
    recommendedWorkers: number;
};
export {};
//# sourceMappingURL=parallelCoordinator.d.ts.map