Andrey Epifancev 627ea82321 perf: замеры открытия модалок
Перечень модалок (modals.json), measureOpen с main_thread_ms, профиль
перерисовок React (reactProfile), сценарий modals.spec.ts, подкоманда
stand.sh web-profiling (:8081), генератор modals-inventory.md.

Refs #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:53:55 +04:00

72 lines
2.8 KiB
TypeScript

import type { CDPSession, Page } from '@playwright/test';
import { createWriteStream, mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
const sessions = new WeakMap<Page, CDPSession>();
async function cdp(page: Page): Promise<CDPSession> {
let s = sessions.get(page);
if (!s) {
s = await page.context().newCDPSession(page);
sessions.set(page, s);
}
return s;
}
/** CPU throttling: rate=1 — без замедления, 4 — в 4 раза медленнее. */
export async function throttle(page: Page, rate: number): Promise<void> {
await (await cdp(page)).send('Emulation.setCPUThrottlingRate', { rate });
}
export async function startTrace(page: Page): Promise<void> {
await page.context().browser()!.startTracing(page, {
categories: ['devtools.timeline', 'disabled-by-default-devtools.timeline', 'v8.execute', 'blink.user_timing'],
screenshots: false,
});
}
export async function stopTrace(page: Page, file: string): Promise<void> {
mkdirSync(path.dirname(file), { recursive: true });
const buf = await page.context().browser()!.stopTracing();
writeFileSync(file, buf);
}
export async function gc(page: Page): Promise<void> {
const s = await cdp(page);
await s.send('HeapProfiler.enable');
await s.send('HeapProfiler.collectGarbage');
}
export async function domCounters(page: Page): Promise<{ documents: number; nodes: number; jsEventListeners: number }> {
return (await (await cdp(page)).send('Memory.getDOMCounters')) as any;
}
/** Размер JS-кучи после GC, байты. */
export async function heapUsed(page: Page): Promise<number> {
await gc(page);
const s = await cdp(page);
await s.send('Runtime.enable');
const r = (await s.send('Runtime.getHeapUsage')) as { usedSize: number };
return r.usedSize;
}
export async function heapSnapshot(page: Page, file: string): Promise<void> {
mkdirSync(path.dirname(file), { recursive: true });
const s = await cdp(page);
const out = createWriteStream(file);
const onChunk = (e: { chunk: string }) => { out.write(e.chunk); };
s.on('HeapProfiler.addHeapSnapshotChunk', onChunk);
await s.send('HeapProfiler.enable');
await s.send('HeapProfiler.takeHeapSnapshot', { reportProgress: false });
s.off('HeapProfiler.addHeapSnapshotChunk', onChunk);
await new Promise<void>((res) => out.end(res));
}
/** Суммарное время задач main thread (CDP Performance.TaskDuration), мс; монотонный счётчик страницы. */
export async function mainThreadMs(page: Page): Promise<number> {
const s = await cdp(page);
await s.send('Performance.enable');
const r = (await s.send('Performance.getMetrics')) as { metrics: Array<{ name: string; value: number }> };
return (r.metrics.find((m) => m.name === 'TaskDuration')?.value ?? 0) * 1000;
}