import { invokeIpc } from '@/lib/api-client'; type LongTaskAggregate = { count: number; totalDurationMs: number; maxDurationMs: number; }; type MainPerformanceSnapshot = { capturedAt?: number; eventLoopDelayMs?: number; processes?: Array; windows?: number; webContents?: number; gpu?: Record; background?: { activity?: { visible?: boolean; module?: string | null }; leaseCount?: number; }; }; /** * Installs development-only, aggregate renderer diagnostics. No URLs, page * content, prompts or file names are retained or sent to Main. Production * builds return a no-op cleanup so this cannot become a hidden polling task. */ export function installRendererPerformanceDiagnostics(): () => void { if (typeof window === 'undefined' || window.electron?.isDev !== true) { return () => undefined; } const longTasks: LongTaskAggregate = { count: 0, totalDurationMs: 0, maxDurationMs: 0, }; let firstPaintMs: number | null = null; let disposed = false; let paintFrame = 0; const observer = typeof PerformanceObserver === 'undefined' ? null : new PerformanceObserver((list) => { for (const entry of list.getEntries()) { const duration = Math.round(entry.duration * 100) / 100; longTasks.count += 1; longTasks.totalDurationMs += duration; longTasks.maxDurationMs = Math.max(longTasks.maxDurationMs, duration); } }); try { observer?.observe({ type: 'longtask', buffered: true } as PerformanceObserverInit); } catch { // Chromium versions without the Long Tasks entry type are supported. } paintFrame = window.requestAnimationFrame(() => { firstPaintMs = Math.round(performance.now() * 100) / 100; }); const report = () => { if (disposed) return; const renderer = { firstPaintMs, longTasks: { count: longTasks.count, totalDurationMs: Math.round(longTasks.totalDurationMs * 100) / 100, maxDurationMs: Math.round(longTasks.maxDurationMs * 100) / 100, }, }; void invokeIpc('app:performance') .then((main) => { if (!disposed) { console.debug('[performance]', { renderer, main }); } }) .catch(() => undefined); }; // A low-frequency sample is enough for the development panel and avoids // adding another active request loop to normal renderer operation. const timer = window.setInterval(report, 30_000); return () => { disposed = true; window.cancelAnimationFrame(paintFrame); window.clearInterval(timer); observer?.disconnect(); }; }