diff --git a/packages/engine/src/config.ts b/packages/engine/src/config.ts index ab0871ad2..29296e7da 100644 --- a/packages/engine/src/config.ts +++ b/packages/engine/src/config.ts @@ -6,6 +6,8 @@ * fallbacks for backward compatibility during migration. */ +import { totalmem } from "os"; + /** * Full engine configuration. All fields are wired through the config * object; env vars serve as backward-compatible fallbacks resolved @@ -208,6 +210,24 @@ export const DEFAULT_CONFIG: EngineConfig = { debug: false, }; +function getSystemTotalMb(): number { + return Math.floor(totalmem() / (1024 * 1024)); +} + +function memoryAdaptiveCacheLimit(): number { + const total = getSystemTotalMb(); + if (total < 4096) return 32; + if (total < 8192) return 64; + return DEFAULT_CONFIG.frameDataUriCacheLimit; +} + +function memoryAdaptiveCacheBytesMb(): number { + const total = getSystemTotalMb(); + if (total < 4096) return 128; + if (total < 8192) return 256; + return DEFAULT_CONFIG.frameDataUriCacheBytesLimitMb; +} + /** * Resolve configuration by merging: defaults ← env vars ← explicit overrides. * Env vars provide backward compatibility during migration; explicit config @@ -298,14 +318,11 @@ export function resolveConfig(overrides?: Partial): EngineConfig { audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG.audioGain), frameDataUriCacheLimit: Math.max( 32, - envNum("PRODUCER_FRAME_DATA_URI_CACHE_LIMIT", DEFAULT_CONFIG.frameDataUriCacheLimit), + envNum("PRODUCER_FRAME_DATA_URI_CACHE_LIMIT", memoryAdaptiveCacheLimit()), ), frameDataUriCacheBytesLimitMb: Math.max( 64, - envNum( - "PRODUCER_FRAME_DATA_URI_CACHE_BYTES_MB", - DEFAULT_CONFIG.frameDataUriCacheBytesLimitMb, - ), + envNum("PRODUCER_FRAME_DATA_URI_CACHE_BYTES_MB", memoryAdaptiveCacheBytesMb()), ), playerReadyTimeout: envNum( diff --git a/packages/engine/src/services/browserManager.ts b/packages/engine/src/services/browserManager.ts index 6ea640361..9e60bbe20 100644 --- a/packages/engine/src/services/browserManager.ts +++ b/packages/engine/src/services/browserManager.ts @@ -6,9 +6,10 @@ */ import type { Browser, PuppeteerNode } from "puppeteer-core"; +import { execSync } from "child_process"; import { existsSync, readdirSync } from "fs"; import { join } from "path"; -import { homedir } from "os"; +import { homedir, totalmem } from "os"; import { DEFAULT_CONFIG, type EngineConfig } from "../config.js"; let _puppeteer: PuppeteerNode | undefined; @@ -476,6 +477,49 @@ export function _setPuppeteerForTests(mock: PuppeteerNode | undefined): void { _puppeteer = mock; } +function getTotalMemMb(): number { + return Math.floor(totalmem() / (1024 * 1024)); +} + +let _cachedVramMb: number | null = null; + +function probeNvidiaVramMb(): number | null { + if (_cachedVramMb !== null) return _cachedVramMb; + try { + // Synchronous, runs once per process (cached). ~50ms on typical systems. + const out = execSync("nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits", { + timeout: 3000, + encoding: "utf-8", + stdio: ["pipe", "pipe", "pipe"], + }).trim(); + const mb = parseInt(out.split("\n")[0] ?? "", 10); + if (Number.isFinite(mb) && mb > 0) { + _cachedVramMb = mb; + return mb; + } + } catch { + // nvidia-smi not available or no NVIDIA GPU + } + return null; +} + +function getGpuMemBudgetMb(): number { + const vram = probeNvidiaVramMb(); + if (vram) return Math.min(vram, 16384); + + const total = getTotalMemMb(); + if (total < 4096) return 512; + if (total < 8192) return 1024; + return Math.min(Math.floor(total / 2), 16384); +} + +function getLowMemoryFlags(): string[] { + const total = getTotalMemMb(); + if (total >= 8192) return []; + const heapMb = total < 4096 ? 256 : 512; + return [`--js-flags=--max-old-space-size=${heapMb}`]; +} + export interface BuildChromeArgsOptions { width: number; height: number; @@ -530,9 +574,10 @@ export function buildChromeArgs( "--disable-print-preview", "--no-pings", "--no-zygote", - // Memory - "--force-gpu-mem-available-mb=4096", + // Memory — scale GPU budget to available system RAM + `--force-gpu-mem-available-mb=${getGpuMemBudgetMb()}`, "--disk-cache-size=268435456", + ...getLowMemoryFlags(), // Disable features that add overhead "--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process,Translate,BackForwardCache,IntensiveWakeUpThrottling", ];