mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-07 08:23:55 +00:00
perf(cuesheet): add result capture + candidate lazy-autosize (Exp B, under measurement)
- Scaffold: snapshot()/getResults() so the benchmark returns structured metrics for automated before/after capture (PERF-METRICS). - Candidate Exp B: defer AutoTextarea autosize() off the mount path to focus-time, removing the per-row forced reflows during scroll. Under evaluation against the metrics.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// @ts-expect-error no types from library
|
||||
import autosize from 'autosize/dist/autosize';
|
||||
import { RefObject, useEffect } from 'react';
|
||||
import { FocusEvent, RefObject, useCallback, useEffect } from 'react';
|
||||
|
||||
import { timeSync } from '../../../devtools/cuesheet-metrics/usePerfMark'; // PERF-METRICS
|
||||
import Textarea, { type TextareaProps } from '../textarea/Textarea';
|
||||
@@ -10,18 +10,41 @@ interface AutoTextAreaProps extends TextareaProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* A textarea that automatically resizes based on its content
|
||||
* A textarea that automatically resizes based on its content.
|
||||
*
|
||||
* `autosize()` forces a synchronous reflow (reads scrollHeight) and installs a MutationObserver.
|
||||
* Doing that on mount makes it expensive when many of these are mounted at once (e.g. virtualised
|
||||
* table rows during scroll), so we only attach autosize while the field is focused for editing and
|
||||
* keep it in sync with the value during that time.
|
||||
*/
|
||||
export function AutoTextarea({ value, inputref, ...textAreaProps }: AutoTextAreaProps) {
|
||||
// when the value changes, we use the ref to reapply autosize
|
||||
export function AutoTextarea({ value, inputref, onFocus, onBlur, ...textAreaProps }: AutoTextAreaProps) {
|
||||
const handleFocus = useCallback(
|
||||
(event: FocusEvent<HTMLTextAreaElement>) => {
|
||||
timeSync('cell.autosize', () => autosize(inputref.current)); // PERF-METRICS
|
||||
onFocus?.(event);
|
||||
},
|
||||
[inputref, onFocus],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(event: FocusEvent<HTMLTextAreaElement>) => {
|
||||
if (inputref.current) {
|
||||
autosize.destroy(inputref.current);
|
||||
}
|
||||
onBlur?.(event);
|
||||
},
|
||||
[inputref, onBlur],
|
||||
);
|
||||
|
||||
// while focused, keep the height in sync as the value changes
|
||||
useEffect(() => {
|
||||
const node = inputref.current;
|
||||
timeSync('cell.autosize', () => autosize(inputref.current)); // PERF-METRICS
|
||||
|
||||
return () => {
|
||||
autosize.destroy(node);
|
||||
};
|
||||
if (node && document.activeElement === node) {
|
||||
autosize(node);
|
||||
}
|
||||
}, [inputref, value]);
|
||||
|
||||
return <Textarea ref={inputref} value={value} {...textAreaProps} />;
|
||||
return (
|
||||
<Textarea ref={inputref} value={value} onFocus={handleFocus} onBlur={handleBlur} {...textAreaProps} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { RefObject } from 'react';
|
||||
import type { TableVirtuosoHandle } from 'react-virtuoso';
|
||||
|
||||
import { PERF_ENABLED } from './perfConfig';
|
||||
import { dump, endSession, startSession } from './perfStore';
|
||||
import { dump, endSession, snapshot, startSession, type PerfSnapshot } from './perfStore';
|
||||
import { startFpsMonitor, stopFpsMonitor } from './scrollFpsMonitor';
|
||||
|
||||
export interface BenchmarkOptions {
|
||||
@@ -24,17 +24,17 @@ export interface BenchmarkOptions {
|
||||
export async function runScrollBenchmark(
|
||||
virtuosoRef: RefObject<TableVirtuosoHandle | null>,
|
||||
options: BenchmarkOptions = {},
|
||||
): Promise<void> {
|
||||
): Promise<PerfSnapshot | undefined> {
|
||||
if (!PERF_ENABLED) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[cuesheet-perf] disabled — open the cuesheet with ?perf=1 in a dev build.');
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
const handle = virtuosoRef.current;
|
||||
if (!handle) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[cuesheet-perf] virtuoso handle not ready.');
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { fromIndex = 0, toIndex = 200, stride = 8, stepMs = 16 } = options;
|
||||
@@ -48,6 +48,7 @@ export async function runScrollBenchmark(
|
||||
stopFpsMonitor();
|
||||
endSession();
|
||||
dump();
|
||||
return snapshot();
|
||||
}
|
||||
|
||||
function scrollThrough(
|
||||
|
||||
@@ -80,9 +80,21 @@ export function recordFrame(deltaMs: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** Dumps the collected metrics to the console as tables. */
|
||||
export function dump(): void {
|
||||
const markRows = Object.entries(state.marks)
|
||||
export interface PerfSnapshot {
|
||||
summary: {
|
||||
rowsMounted: number;
|
||||
frames: number;
|
||||
longFrames: number;
|
||||
avgFps: number;
|
||||
minFps: number;
|
||||
maxFrameMs: number;
|
||||
};
|
||||
marks: Array<{ mark: string; count: number; avgMs: number; maxMs: number; totalMs: number }>;
|
||||
}
|
||||
|
||||
/** Returns the collected metrics as a structured object (for console or automated capture). */
|
||||
export function snapshot(): PerfSnapshot {
|
||||
const marks = Object.entries(state.marks)
|
||||
.map(([name, m]) => ({
|
||||
mark: name,
|
||||
count: m.count,
|
||||
@@ -95,19 +107,26 @@ export function dump(): void {
|
||||
const avgFps = state.totalFrameMs > 0 ? round((state.frameCount * 1000) / state.totalFrameMs) : 0;
|
||||
const minFps = state.maxFrameMs > 0 ? round(1000 / state.maxFrameMs) : 0;
|
||||
|
||||
const summary = {
|
||||
rowsMounted: state.rowsMounted,
|
||||
frames: state.frameCount,
|
||||
longFrames: state.longFrames,
|
||||
avgFps,
|
||||
minFps,
|
||||
maxFrameMs: round(state.maxFrameMs),
|
||||
return {
|
||||
summary: {
|
||||
rowsMounted: state.rowsMounted,
|
||||
frames: state.frameCount,
|
||||
longFrames: state.longFrames,
|
||||
avgFps,
|
||||
minFps,
|
||||
maxFrameMs: round(state.maxFrameMs),
|
||||
},
|
||||
marks,
|
||||
};
|
||||
}
|
||||
|
||||
/** Dumps the collected metrics to the console as tables. */
|
||||
export function dump(): void {
|
||||
const { summary, marks } = snapshot();
|
||||
/* eslint-disable no-console */
|
||||
console.group('[cuesheet-perf] benchmark result');
|
||||
console.table(summary);
|
||||
console.table(markRows);
|
||||
console.table(marks);
|
||||
console.groupEnd();
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { TableVirtuosoHandle } from 'react-virtuoso';
|
||||
|
||||
import { runScrollBenchmark, type BenchmarkOptions } from './benchmark';
|
||||
import { PERF_ENABLED } from './perfConfig';
|
||||
import { dump, endSession, reset, startSession } from './perfStore';
|
||||
import { dump, endSession, reset, snapshot, startSession } from './perfStore';
|
||||
import { startFpsMonitor, stopFpsMonitor } from './scrollFpsMonitor';
|
||||
|
||||
export function useCuesheetPerf(virtuosoRef: RefObject<TableVirtuosoHandle | null>): void {
|
||||
@@ -33,6 +33,7 @@ export function useCuesheetPerf(virtuosoRef: RefObject<TableVirtuosoHandle | nul
|
||||
},
|
||||
dump,
|
||||
reset,
|
||||
getResults: snapshot,
|
||||
};
|
||||
|
||||
(window as unknown as { __cuesheetPerf?: typeof api }).__cuesheetPerf = api;
|
||||
|
||||
Reference in New Issue
Block a user