Skip to content

Commit 5325724

Browse files
authored
fix(insights): debounce useInsightChart refetch to prevent rapid DB polling while scrubbing (#1030)
1 parent 25f37a8 commit 5325724

File tree

1 file changed

+90
-32
lines changed

1 file changed

+90
-32
lines changed

src/features/hardware/insights/hooks/useInsightChart.ts

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useMemo, useState } from "react";
1+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import {
33
type archivePeriods,
44
chartConfig,
@@ -98,24 +98,27 @@ export const useInsightChart = (
9898

9999
const [data, setData] = useState<Array<SingleDataArchive>>([]);
100100

101+
const prevOffsetRef = useRef(offset);
102+
const activeRequestIdRef = useRef(0);
103+
const scheduledTimeoutIdRef = useRef<number | null>(null);
104+
105+
const stepMultiplierMap: Record<(typeof archivePeriods)[number], number> = {
106+
10: 1,
107+
30: 1,
108+
60: 1,
109+
180: 1,
110+
720: 10,
111+
1440: 30,
112+
10080: 60,
113+
20160: 180,
114+
43200: 720,
115+
};
116+
101117
const step =
102-
{
103-
10: 1,
104-
30: 1,
105-
60: 1,
106-
180: 1,
107-
720: 10,
108-
1440: 30,
109-
10080: 60,
110-
20160: 180,
111-
43200: 720,
112-
}[period] * chartConfig.archiveUpdateIntervalMilSec;
113-
114-
const endAt = useMemo(() => {
115-
return new Date(Date.now() - offset * step);
116-
}, [offset, step]);
118+
(stepMultiplierMap[period] ?? 1) * chartConfig.archiveUpdateIntervalMilSec;
117119

118120
const getData = useCallback(async (): Promise<SingleDataArchive[]> => {
121+
const endAt = new Date(Date.now() - offset * step);
119122
const adjustedEndAt = new Date(
120123
endAt.getTime() - chartConfig.archiveUpdateIntervalMilSec,
121124
);
@@ -141,7 +144,7 @@ export const useInsightChart = (
141144
})();
142145

143146
return await (await sqlitePromise).load(sql);
144-
}, [endAt, hardwareType, period, dataStats, gpuName, dataType]);
147+
}, [hardwareType, period, dataStats, gpuName, dataType, offset, step]);
145148

146149
const formatValue = useCallback(
147150
(value: number | null) => {
@@ -164,24 +167,75 @@ export const useInsightChart = (
164167
);
165168

166169
useEffect(() => {
167-
const updateData = () => {
168-
getData().then((data) =>
169-
setData(data.map((V) => ({ ...V, value: formatValue(V.value) }))),
170-
);
170+
const isOffsetChanged = prevOffsetRef.current !== offset;
171+
prevOffsetRef.current = offset;
172+
173+
// Offset is updated every 100ms while pressing arrows in Insights.
174+
// Debounce DB reads to avoid spamming when scrubbing.
175+
const debounceMs = isOffsetChanged ? 250 : 0;
176+
177+
const requestId = activeRequestIdRef.current + 1;
178+
activeRequestIdRef.current = requestId;
179+
180+
const run = async () => {
181+
try {
182+
const rows = await getData();
183+
if (activeRequestIdRef.current !== requestId) {
184+
return;
185+
}
186+
setData(rows.map((v) => ({ ...v, value: formatValue(v.value) })));
187+
} catch (e) {
188+
console.error(e);
189+
}
171190
};
172191

173-
updateData();
192+
if (scheduledTimeoutIdRef.current != null) {
193+
clearTimeout(scheduledTimeoutIdRef.current);
194+
}
195+
196+
scheduledTimeoutIdRef.current = window.setTimeout(run, debounceMs);
197+
198+
return () => {
199+
if (scheduledTimeoutIdRef.current != null) {
200+
clearTimeout(scheduledTimeoutIdRef.current);
201+
scheduledTimeoutIdRef.current = null;
202+
}
203+
};
204+
}, [getData, formatValue, offset]);
205+
206+
useEffect(() => {
207+
// Only auto-refresh the "current" window.
208+
if (offset !== 0) {
209+
return;
210+
}
211+
212+
const intervalId = window.setInterval(() => {
213+
const requestId = activeRequestIdRef.current + 1;
214+
activeRequestIdRef.current = requestId;
215+
216+
void getData()
217+
.then((rows) => {
218+
if (activeRequestIdRef.current !== requestId) {
219+
return;
220+
}
221+
setData(rows.map((v) => ({ ...v, value: formatValue(v.value) })));
222+
})
223+
.catch((e) => {
224+
console.error(e);
225+
});
226+
}, chartConfig.archiveUpdateIntervalMilSec);
174227

175-
const intervalId = setInterval(
176-
updateData,
177-
chartConfig.archiveUpdateIntervalMilSec,
178-
);
179228
return () => clearInterval(intervalId);
180-
}, [getData, formatValue]);
229+
}, [getData, formatValue, offset]);
230+
231+
const endAtForBucket = useMemo(
232+
() => new Date(Date.now() - offset * step),
233+
[offset, step],
234+
);
181235

182236
const startTime = useMemo(
183-
() => new Date(endAt.getTime() - period * 60 * 1000),
184-
[endAt, period],
237+
() => new Date(endAtForBucket.getTime() - period * 60 * 1000),
238+
[endAtForBucket, period],
185239
);
186240

187241
const startBucket =
@@ -190,7 +244,8 @@ export const useInsightChart = (
190244
) * step;
191245
const endBucket =
192246
Math.ceil(
193-
(endAt.getTime() - chartConfig.archiveUpdateIntervalMilSec) / step,
247+
(endAtForBucket.getTime() - chartConfig.archiveUpdateIntervalMilSec) /
248+
step,
194249
) * step;
195250

196251
const bucketedData = useMemo(() => {
@@ -246,7 +301,10 @@ export const useInsightChart = (
246301
for (let t = startBucket; t <= endBucket; t += step) {
247302
const bucketData = bucketedData[t];
248303
if (!bucketData || bucketData.length <= 0) {
249-
if (t <= endAt.getTime() - chartConfig.archiveUpdateIntervalMilSec) {
304+
if (
305+
t <=
306+
endAtForBucket.getTime() - chartConfig.archiveUpdateIntervalMilSec
307+
) {
250308
filledChartData.push(null);
251309
filledLabels.push(dateFormatter.format(new Date(t)));
252310
}
@@ -264,7 +322,7 @@ export const useInsightChart = (
264322
aggregateFn,
265323
bucketedData,
266324
endBucket,
267-
endAt,
325+
endAtForBucket,
268326
startBucket,
269327
step,
270328
dateFormatter,

0 commit comments

Comments
 (0)