/** * Combined benchmark comparison hook * * Composes FPS, Memory, and Render monitoring into a single hook * with baseline comparison capabilities. * * @example * ```tsx * const benchmark = useBenchmarkComparison({ * scenario: 'scroll', * itemCount: 100, * baseline: loadedBaselineData, * }); * * // Start benchmarking * benchmark.start(); * * // After test completes * benchmark.stop(); * const result = benchmark.getResult(); * const comparison = benchmark.compareWithBaseline(); * ``` */ import { useCallback, useRef } from 'react'; import { useFPSMonitor } from './useFPSMonitor'; import { useRenderProfiler } from './useRenderProfiler'; import { useMemoryMonitor } from './useMemoryMonitor'; import { compareWithBaseline, formatComparisonAsMarkdown } from '../utils/compareResults'; import { getBaselineMetrics, getVanillaMetrics } from '../utils/loadBaseline'; import type { BenchmarkResult, ScenarioMetrics, BaselineData, ComparisonResult, BenchmarkScenario, } from '../types'; export interface UseBenchmarkComparisonConfig { /** Scenario being tested */ scenario: BenchmarkScenario | string; /** Number of items in the list */ itemCount: number; /** Baseline data for comparison (optional) */ baseline?: BaselineData | null; /** Profiler ID for render tracking */ profilerId?: string; } export interface UseBenchmarkComparisonResult { /** FPS monitor instance */ fpsMonitor: ReturnType; /** Render profiler instance */ renderProfiler: ReturnType; /** Memory monitor instance */ memoryMonitor: ReturnType; /** Start all monitoring */ start: () => void; /** Stop all monitoring */ stop: () => void; /** Reset all metrics */ reset: () => void; /** Get combined benchmark result */ getResult: () => BenchmarkResult; /** Get metrics in ScenarioMetrics format */ getMetrics: () => ScenarioMetrics; /** Compare with baseline (if provided) */ compareWithBaseline: () => ComparisonResult | null; /** Compare with vanilla FlashList (if baseline contains comparison data) */ compareWithVanilla: () => ComparisonResult | null; /** Get formatted markdown report */ getMarkdownReport: () => string | null; /** Whether benchmark is currently running */ isRunning: boolean; } /** * Hook that combines all performance monitors with baseline comparison */ export function useBenchmarkComparison( config: UseBenchmarkComparisonConfig, ): UseBenchmarkComparisonResult { const { scenario, itemCount, baseline, profilerId = 'benchmark' } = config; // Initialize individual monitors const fpsMonitor = useFPSMonitor(); const renderProfiler = useRenderProfiler(profilerId); const memoryMonitor = useMemoryMonitor(); // Track benchmark timing const startTimeRef = useRef(0); const isRunningRef = useRef(false); // Start all monitors const start = useCallback(() => { if (isRunningRef.current) return; isRunningRef.current = true; startTimeRef.current = Date.now(); fpsMonitor.start(); memoryMonitor.start(); // Render profiler starts automatically when Profiler component mounts }, [fpsMonitor, memoryMonitor]); // Stop all monitors const stop = useCallback(() => { if (!isRunningRef.current) return; isRunningRef.current = false; fpsMonitor.stop(); memoryMonitor.stop(); }, [fpsMonitor, memoryMonitor]); // Reset all monitors const reset = useCallback(() => { fpsMonitor.reset(); renderProfiler.reset(); memoryMonitor.reset(); startTimeRef.current = 0; }, [fpsMonitor, renderProfiler, memoryMonitor]); // Get metrics in ScenarioMetrics format const getMetrics = useCallback((): ScenarioMetrics => { const fps = fpsMonitor.getSnapshot(); const render = renderProfiler.getSnapshot(); const memory = memoryMonitor.getSnapshot(); return { uiFPS: { avg: fps.avg, min: fps.min, max: fps.max, jankPercentage: fps.jankPercentage, }, memory: { peakMB: memory.peakHeapMB, deltaMB: memory.heapDeltaMB, }, render: { avgRenderTime: render.avgRenderTime, maxRenderTime: render.maxRenderTime, slowRenders: render.slowRenders, }, }; }, [fpsMonitor, renderProfiler, memoryMonitor]); // Get combined benchmark result const getResult = useCallback((): BenchmarkResult => { const fps = fpsMonitor.getSnapshot(); const render = renderProfiler.getSnapshot(); const memory = memoryMonitor.getSnapshot(); const durationMs = startTimeRef.current > 0 ? Date.now() - startTimeRef.current : 0; return { scenario, itemCount, durationMs, timestamp: Date.now(), uiFPS: fps, render, memory, }; }, [scenario, itemCount, fpsMonitor, renderProfiler, memoryMonitor]); // Compare with baseline const compareWithBaselineFn = useCallback((): ComparisonResult | null => { if (!baseline) { return null; } const baselineMetrics = getBaselineMetrics(baseline, scenario, itemCount); if (!baselineMetrics) { return null; } const currentMetrics = getMetrics(); return compareWithBaseline(currentMetrics, baselineMetrics); }, [baseline, scenario, itemCount, getMetrics]); // Compare with vanilla FlashList const compareWithVanillaFn = useCallback((): ComparisonResult | null => { if (!baseline) { return null; } const vanillaMetrics = getVanillaMetrics(baseline, scenario, itemCount); if (!vanillaMetrics) { return null; } const currentMetrics = getMetrics(); return compareWithBaseline(currentMetrics, vanillaMetrics); }, [baseline, scenario, itemCount, getMetrics]); // Get formatted markdown report const getMarkdownReport = useCallback((): string | null => { const comparison = compareWithBaselineFn(); if (!comparison) { return null; } return formatComparisonAsMarkdown(comparison, scenario, itemCount); }, [compareWithBaselineFn, scenario, itemCount]); return { fpsMonitor, renderProfiler, memoryMonitor, start, stop, reset, getResult, getMetrics, compareWithBaseline: compareWithBaselineFn, compareWithVanilla: compareWithVanillaFn, getMarkdownReport, isRunning: isRunningRef.current, }; }