More alerts page filtering measurement

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2025-08-11 18:50:49 +02:00
parent 787b1b3367
commit 6b67a67d7d
1 changed files with 71 additions and 5 deletions

View File

@ -20,7 +20,15 @@ import badgeClasses from "../Badge.module.css";
import panelClasses from "../Panel.module.css";
import RuleDefinition from "../components/RuleDefinition";
import { humanizeDurationRelative, now } from "../lib/formatTime";
import { Fragment, useEffect, useMemo, useRef } from "react";
import {
Profiler,
Fragment,
useEffect,
useMemo,
useRef,
useCallback,
} from "react";
import type { ProfilerOnRenderCallback } from "react";
import { StateMultiSelect } from "../components/StateMultiSelect";
import { IconInfoCircle, IconSearch } from "@tabler/icons-react";
import { LabelBadges } from "../components/LabelBadges";
@ -67,6 +75,16 @@ type AlertsPageData = {
}[];
};
// Track timing for a search render cycle
type SearchMeasure = {
id: number;
search: string;
startedAt: number;
computeEnd?: number;
elementsEnd?: number; // fallback if Profiler isn't available
elementsDuration?: number; // actual render time from React Profiler
};
const kvSearch = new KVSearch<AlertingRule>({
shouldSort: true,
indexedKeys: ["name", "labels", ["labels", /.*/]],
@ -168,9 +186,18 @@ export default function AlertsPage() {
// Measure how long it takes to render the filtered list after debouncedSearch changes.
const searchRenderStartRef = useRef<number | null>(null);
const prevDebouncedSearchRef = useRef<string>(debouncedSearch);
const measureIdRef = useRef(0);
const measureRef = useRef<SearchMeasure | null>(null);
if (prevDebouncedSearchRef.current !== debouncedSearch) {
searchRenderStartRef.current = performance.now();
prevDebouncedSearchRef.current = debouncedSearch;
measureIdRef.current += 1;
const startedAt = performance.now();
measureRef.current = {
id: measureIdRef.current,
search: debouncedSearch,
startedAt,
};
searchRenderStartRef.current = startedAt;
}
// After commit, wait for paint to complete, then log duration.
@ -181,11 +208,23 @@ export default function AlertsPage() {
let raf2 = 0;
raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => {
const duration = performance.now() - start;
const paintEnd = performance.now();
const m = measureRef.current;
const computeMs = m?.computeEnd != null ? m.computeEnd - start : NaN;
const elementsMs =
m?.elementsDuration != null
? m.elementsDuration
: m?.elementsEnd != null && m?.computeEnd != null
? m.elementsEnd - m.computeEnd
: NaN;
const paintMs =
m?.elementsEnd != null ? paintEnd - m.elementsEnd : NaN;
const totalMs = paintEnd - start;
console.log(
`AlertsPage: filtered list render time ${duration.toFixed(2)} ms (search="${debouncedSearch}")`
`AlertsPage timings (search="${m?.search ?? ""}"): compute=${isNaN(computeMs) ? "-" : computeMs.toFixed(2)}ms, elements=${isNaN(elementsMs as number) ? "-" : (elementsMs as number).toFixed(2)}ms, paint=${isNaN(paintMs) ? "-" : paintMs.toFixed(2)}ms, total=${totalMs.toFixed(2)}ms`
);
searchRenderStartRef.current = null;
measureRef.current = null;
});
});
return () => {
@ -213,6 +252,12 @@ export default function AlertsPage() {
[data, stateFilter, debouncedSearch]
);
// Mark compute end when the filtered data is ready for rendering
if (measureRef.current && searchRenderStartRef.current != null) {
// We consider compute finished after alertsPageData has been recomputed
measureRef.current.computeEnd = performance.now();
}
const shownGroups = useMemo(
() =>
showEmptyGroups
@ -429,6 +474,25 @@ export default function AlertsPage() {
[currentPageGroups, showAnnotations, setShowEmptyGroups]
);
// Capture actual render time for the list subtree using React Profiler
const onListRender = useCallback<ProfilerOnRenderCallback>(
(_, __, actualDuration) => {
if (measureRef.current && searchRenderStartRef.current != null) {
measureRef.current.elementsDuration = actualDuration;
}
},
[]
);
// Mark elements end after creating React elements for the current view (fallback when Profiler duration is unavailable)
if (
measureRef.current &&
measureRef.current.computeEnd &&
searchRenderStartRef.current != null
) {
measureRef.current.elementsEnd = performance.now();
}
return (
<Stack mt="xs">
<Group>
@ -485,7 +549,9 @@ export default function AlertsPage() {
onChange={setActivePage}
hideWithOnePage
/>
{renderedPageItems}
<Profiler id="alerts-list" onRender={onListRender}>
{renderedPageItems}
</Profiler>
</Stack>
);
}