Селектор строк данных свода 2 исключает скелетоны MRT, settle ждёт 0 скелетонов; first_rows_ms через waitForFunction(raf); счётчики загрузки не пишутся в запись фильтра; scale_compare различает view/search; api_split не считает строки для facets; README: порядок до/после и новые метрики. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
/** Чистые помощники замеров свода 2 (без импортов, стираемый TS: проверяются `node --test`). */
|
||
|
||
/** Строка данных: не заглушка окна и без скелетонов MRT (isLoading рисует .MuiSkeleton-root в ячейках даже при непустых данных). */
|
||
export const SVOD2_DATA_ROW_SELECTOR = 'tbody tr:not([data-svod2-placeholder]):not(:has(.MuiSkeleton-root))';
|
||
export const SVOD2_PLACEHOLDER_SELECTOR = 'tbody tr[data-svod2-placeholder]';
|
||
|
||
/** Ответы свода 2: страница `/svod/2/<sheet>/...` и `/svod/2/<sheet>/<year>/facets`. */
|
||
export function svod2RespRe(sheet: string): RegExp {
|
||
return new RegExp(`/api/v1/svod/2/${sheet}/`);
|
||
}
|
||
|
||
/** Запрос свода 2 с параметром search= (серверный поиск). */
|
||
export function isSvod2SearchUrl(url: string): boolean {
|
||
const q = url.indexOf('?');
|
||
if (q < 0 || !/\/api\/v1\/svod\/2\//.test(url.slice(0, q))) return false;
|
||
return url
|
||
.slice(q + 1)
|
||
.split('#')[0]
|
||
.split('&')
|
||
.some((p) => p === 'search' || p.startsWith('search='));
|
||
}
|
||
|
||
/** Число запросов страниц (offset= в query). */
|
||
export function countPagesRequested(urls: string[]): number {
|
||
let n = 0;
|
||
for (const u of urls) {
|
||
const q = u.indexOf('?');
|
||
if (q < 0) continue;
|
||
if (u.slice(q + 1).split('#')[0].split('&').some((p) => p === 'offset' || p.startsWith('offset='))) n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/** Доля кадров, где видна хотя бы одна заглушка. */
|
||
export function placeholderShare(samples: boolean[]): number {
|
||
if (samples.length === 0) return 0;
|
||
let k = 0;
|
||
for (const s of samples) if (s) k++;
|
||
return k / samples.length;
|
||
}
|
||
|
||
/** Байты ответов по requestId: sheet - максимум одного ответа, total - сумма, first - сумма до markFirstRows. */
|
||
export class ByteCounter {
|
||
perRequest: Map<string, number> = new Map();
|
||
totalBytes = 0;
|
||
firstBytes = 0;
|
||
marked = false;
|
||
|
||
add(requestId: string, bytes: number): void {
|
||
this.perRequest.set(requestId, (this.perRequest.get(requestId) ?? 0) + bytes);
|
||
this.totalBytes += bytes;
|
||
if (!this.marked) this.firstBytes += bytes;
|
||
}
|
||
|
||
markFirstRows(): void {
|
||
this.marked = true;
|
||
}
|
||
|
||
get sheet(): number {
|
||
let m = 0;
|
||
for (const v of this.perRequest.values()) if (v > m) m = v;
|
||
return m;
|
||
}
|
||
|
||
get total(): number {
|
||
return this.totalBytes;
|
||
}
|
||
|
||
get first(): number {
|
||
return this.firstBytes;
|
||
}
|
||
}
|
||
|
||
/** filter_result_ms: от fill до стабилизации; ответ поиска (если был) не должен быть позже конца; таймаут ожидания (respAt=null) не учитывается. */
|
||
export function filterResultMs(startAt: number, stableAt: number, respAt: number | null): number {
|
||
return Math.max(stableAt, respAt ?? 0) - startAt;
|
||
}
|
||
|
||
/** Точки замера памяти свода 2: GO «все» -> RF «все» -> (опц. прокрутка RF) -> снова GO. */
|
||
export function memoryPoints(env: Record<string, string | undefined>): string[] {
|
||
return env.PERF_SVOD2_MEM_SCROLL === '1' ? ['go', 'rf', 'rf_scrolled', 'final'] : ['go', 'rf', 'final'];
|
||
}
|