Andrey Epifancev efa537e008 perf: каркас замеров фронта
Refs #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:12:36 +04:00

58 lines
1.9 KiB
TypeScript

/**
* Init-script для page.addInitScript(PROBE_SCRIPT).
* window.__perf.start() / stop() -> {long_task_n, long_task_ms, frame_n, heavy_frame_n,
* frame_p50_ms, frame_p95_ms, duration_ms}.
* Long task: > 50 мс (PerformanceObserver 'longtask'). Тяжёлый кадр: интервал между rAF > 50 мс.
*/
export const HEAVY_FRAME_MS = 50;
export interface ProbeMetrics {
long_task_n: number;
long_task_ms: number;
frame_n: number;
heavy_frame_n: number;
frame_p50_ms: number;
frame_p95_ms: number;
duration_ms: number;
}
export const PROBE_SCRIPT = `(() => {
if (window.__perf) return;
const HEAVY = ${HEAVY_FRAME_MS};
let active = false, t0 = 0, tasks = [], frames = [], last = 0, raf = 0;
try {
new PerformanceObserver((list) => {
if (!active) return;
for (const e of list.getEntries()) tasks.push(e.duration);
}).observe({ type: 'longtask', buffered: false });
} catch (e) {}
const tick = (now) => {
if (!active) return;
if (last) frames.push(now - last);
last = now;
raf = requestAnimationFrame(tick);
};
const pct = (s, p) => s.length ? s[Math.max(Math.ceil(p * s.length), 1) - 1] : 0;
window.__perf = {
start() {
tasks = []; frames = []; last = 0; t0 = performance.now(); active = true;
raf = requestAnimationFrame(tick);
},
async stop() {
// дать долгим задачам, завершившимся перед stop, доехать до observer
await new Promise((r) => setTimeout(r, 100));
active = false; cancelAnimationFrame(raf);
const s = frames.slice().sort((a, b) => a - b);
return {
long_task_n: tasks.length,
long_task_ms: tasks.reduce((a, b) => a + b, 0),
frame_n: frames.length,
heavy_frame_n: frames.filter((f) => f > HEAVY).length,
frame_p50_ms: pct(s, 0.5),
frame_p95_ms: pct(s, 0.95),
duration_ms: performance.now() - t0,
};
},
};
})();`;