type ProfilerOptions = { sampleInterval: number; maxBufferSize: number; }; type Profiler = { new (options: ProfilerOptions): Profiler; stop(): Promise; }; declare global { const Profiler: Profiler; interface Window { Profiler: Profiler; } } type Sample = { stackId?: number; timestamp: number; }; type Stack = { frameId: number; parentId?: number; }; type Frame = { name: string; line?: number; column?: number; resourceId?: number; }; type ProfilingTrace = { frames: Frame[]; resources: string[]; samples: Sample[]; stacks: Stack[]; }; const NOT_STARTED_PROFILER = { stop: (): Promise => { console.warn('The profiler has not been started'); return Promise.resolve({ frames: [], resources: [], samples: [], stacks: [], }); }, }; let profiler = NOT_STARTED_PROFILER; const SAMPLING_INTERVAL = 10; const MAX_BUFFER_SIZE = 10000; const MICRO_SECONDS_IN_MILLISECONDS = 1000; export function startProfiling(): boolean { if (typeof window.Profiler !== 'function') { console.warn( 'Profiler is not available in this browser. Ignoring this profiler run...' ); return false; } profiler = new Profiler({ sampleInterval: SAMPLING_INTERVAL, maxBufferSize: MAX_BUFFER_SIZE, }); return true; } export async function stopProfiling( saveToDownloads = false, fileName = `trace-${new Date().toISOString()}.cpuprofile.txt` ): Promise { const trace = await profiler.stop(); profiler = NOT_STARTED_PROFILER; const hermesProfiler = convertToHermesProfilerFormat(trace); downloadJsonFile(hermesProfiler, fileName); if (!saveToDownloads) { console.warn( 'Specifying `saveToDownloads=false` is not supported on web, as downloading is the only one possible way to store the trace file' ); } return fileName; } function downloadJsonFile( exportObj: Record, exportName: string ) { const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(exportObj)); const downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute('href', dataStr); downloadAnchorNode.setAttribute('download', exportName); document.body.appendChild(downloadAnchorNode); // required for Firefox downloadAnchorNode.click(); downloadAnchorNode.remove(); } function convertToHermesProfilerFormat(profilingTrace: ProfilingTrace) { const { frames, resources, samples, stacks } = profilingTrace; return { traceEvents: [], samples: samples.map((sample) => ({ cpu: '-1', name: '', ts: sample.timestamp * MICRO_SECONDS_IN_MILLISECONDS, pid: 1, tid: '1', weight: '1', sf: sample.stackId ?? 1, })), stackFrames: stacks.map((stack) => { const resourceId = frames[stack.frameId]?.resourceId; return { parent: stack.parentId, category: 'JavaScript', ...frames[stack.frameId], name: `${frames[stack.frameId]?.name}(${ resourceId ? resources[resourceId] : null })`, }; }), }; }