More alerts page filtering measurement
Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
787b1b3367
commit
6b67a67d7d
|
@ -20,7 +20,15 @@ import badgeClasses from "../Badge.module.css";
|
||||||
import panelClasses from "../Panel.module.css";
|
import panelClasses from "../Panel.module.css";
|
||||||
import RuleDefinition from "../components/RuleDefinition";
|
import RuleDefinition from "../components/RuleDefinition";
|
||||||
import { humanizeDurationRelative, now } from "../lib/formatTime";
|
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 { StateMultiSelect } from "../components/StateMultiSelect";
|
||||||
import { IconInfoCircle, IconSearch } from "@tabler/icons-react";
|
import { IconInfoCircle, IconSearch } from "@tabler/icons-react";
|
||||||
import { LabelBadges } from "../components/LabelBadges";
|
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>({
|
const kvSearch = new KVSearch<AlertingRule>({
|
||||||
shouldSort: true,
|
shouldSort: true,
|
||||||
indexedKeys: ["name", "labels", ["labels", /.*/]],
|
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.
|
// Measure how long it takes to render the filtered list after debouncedSearch changes.
|
||||||
const searchRenderStartRef = useRef<number | null>(null);
|
const searchRenderStartRef = useRef<number | null>(null);
|
||||||
const prevDebouncedSearchRef = useRef<string>(debouncedSearch);
|
const prevDebouncedSearchRef = useRef<string>(debouncedSearch);
|
||||||
|
const measureIdRef = useRef(0);
|
||||||
|
const measureRef = useRef<SearchMeasure | null>(null);
|
||||||
if (prevDebouncedSearchRef.current !== debouncedSearch) {
|
if (prevDebouncedSearchRef.current !== debouncedSearch) {
|
||||||
searchRenderStartRef.current = performance.now();
|
|
||||||
prevDebouncedSearchRef.current = debouncedSearch;
|
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.
|
// After commit, wait for paint to complete, then log duration.
|
||||||
|
@ -181,11 +208,23 @@ export default function AlertsPage() {
|
||||||
let raf2 = 0;
|
let raf2 = 0;
|
||||||
raf1 = requestAnimationFrame(() => {
|
raf1 = requestAnimationFrame(() => {
|
||||||
raf2 = 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(
|
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;
|
searchRenderStartRef.current = null;
|
||||||
|
measureRef.current = null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -213,6 +252,12 @@ export default function AlertsPage() {
|
||||||
[data, stateFilter, debouncedSearch]
|
[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(
|
const shownGroups = useMemo(
|
||||||
() =>
|
() =>
|
||||||
showEmptyGroups
|
showEmptyGroups
|
||||||
|
@ -429,6 +474,25 @@ export default function AlertsPage() {
|
||||||
[currentPageGroups, showAnnotations, setShowEmptyGroups]
|
[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 (
|
return (
|
||||||
<Stack mt="xs">
|
<Stack mt="xs">
|
||||||
<Group>
|
<Group>
|
||||||
|
@ -485,7 +549,9 @@ export default function AlertsPage() {
|
||||||
onChange={setActivePage}
|
onChange={setActivePage}
|
||||||
hideWithOnePage
|
hideWithOnePage
|
||||||
/>
|
/>
|
||||||
{renderedPageItems}
|
<Profiler id="alerts-list" onRender={onListRender}>
|
||||||
|
{renderedPageItems}
|
||||||
|
</Profiler>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue