perf: reduce background overhead and improve UI responsiveness

- Move blocking usage queries and core lifecycle operations to background
  workers, limit concurrency, and prevent overlapping core operations
- Coalesce statistics refreshes and consolidate category aggregations,
  reducing unfiltered analysis queries from 11 to 4
- Cache bundled model prices, reuse management API connections, and reduce
  redundant checks in the usage collector
- Throttle download progress and guide positioning updates, and avoid
  redundant renders when core status is unchanged
- Bound and coalesce configuration watcher events, and reuse a single
  model catalog refresh worker
- Stop scheduling remaining health checks when the dialog closes and
  limit backend health-check concurrency
- Replace overlapping OAuth polling with sequential requests, retry
  backoff, timeouts, and stale callback protection
- Add regression tests and a statistics benchmark script, and move
  performance verification documentation to agent_docs
This commit is contained in:
lzt404 committed 2026-09-06 01:28:58 +08:00
1 parent 45ecd37ce5
commit 9cf3878b28
19 files changed
+1050 -300

No files matched your search

+20 -12
View File
@@ -1,4 +1,4 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
@@ -26,24 +26,32 @@ const CoreRuntimeContext = createContext<CoreRuntimeContextValue | null>(null);
export function CoreRuntimeProvider({ children }: { children: ReactNode }) {
const [status, setStatus] = useState<CoreStatus | null>(null);
const [statusError, setStatusError] = useState('');
const refreshRequest = useRef<Promise<void> | null>(null);
const statusRevision = useRef(0);
const publishStatus = useCallback((nextStatus: CoreStatus | null) => {
setStatus(nextStatus);
++statusRevision.current;
setStatus((previous) => JSON.stringify(previous) === JSON.stringify(nextStatus) ? previous : nextStatus);
if (nextStatus) {
setStatusError('');
}
}, []);
const refreshStatus = useCallback(async () => {
try {
const nextStatus = await invoke<CoreStatus>('get_core_status');
setStatus(nextStatus);
setStatusError('');
} catch (error) {
setStatus(null);
setStatusError(String(error));
}
}, []);
const refreshStatus = useCallback(() => {
if (refreshRequest.current) return refreshRequest.current;
const revision = statusRevision.current;
refreshRequest.current = invoke<CoreStatus>('get_core_status')
.then((nextStatus) => {
if (revision === statusRevision.current) publishStatus(nextStatus);
})
.catch((error) => {
if (revision !== statusRevision.current) return;
setStatus(null);
setStatusError(String(error));
})
.finally(() => { refreshRequest.current = null; });
return refreshRequest.current;
}, [publishStatus]);
useEffect(() => {
let disposed = false;
+15 -4
View File
@@ -1338,6 +1338,13 @@ function ProviderHealthDialog({ row, onClose }: ProviderHealthDialogProps) {
const [search, setSearch] = useState('');
const [results, setResults] = useState<Record<string, ProviderModelHealthState>>({});
const [checkingAll, setCheckingAll] = useState(false);
const healthControllerRef = useRef<AbortController | null>(null);
useEffect(() => {
const controller = new AbortController();
healthControllerRef.current = controller;
return () => controller.abort();
}, []);
const healthOptions = useMemo<ProviderHealthCheckOptions>(() => ({
provider: providerModelType(row.section),
@@ -1393,22 +1400,26 @@ function ProviderHealthDialog({ row, onClose }: ProviderHealthDialogProps) {
};
const checkOneModel = async (model: ModelOption) => {
const signal = healthControllerRef.current?.signal;
if (!signal || signal.aborted) return;
const key = model.name.toLowerCase();
setResults((current) => ({ ...current, [key]: { status: 'checking' } }));
saveResult(await checkProviderModelHealth(healthOptions, model.name));
const result = await checkProviderModelHealth(healthOptions, model.name);
if (!signal.aborted) saveResult(result);
};
const checkAllModels = async () => {
if (models.length === 0) return;
const signal = healthControllerRef.current?.signal;
if (models.length === 0 || checkingAll || !signal || signal.aborted) return;
setCheckingAll(true);
setResults(Object.fromEntries(models.map((model) => [
model.name.toLowerCase(),
{ status: 'checking' } satisfies ProviderModelHealthState,
])));
try {
await checkProviderModelsHealth(healthOptions, models, saveResult);
await checkProviderModelsHealth(healthOptions, models, saveResult, undefined, signal);
} finally {
setCheckingAll(false);
if (!signal.aborted) setCheckingAll(false);
}
};
+57 -16
View File
@@ -125,6 +125,7 @@ export function EasyModePage({
message: string;
} | null>(null);
const oauthPollTimer = useRef<number | null>(null);
const oauthGeneration = useRef(0);
const [selectedApiSection, setSelectedApiSection] = useState<ApiSection>("openai-compatibility");
const [apiBaseUrl, setApiBaseUrl] = useState("");
@@ -200,7 +201,8 @@ export function EasyModePage({
useEffect(() => {
void refreshSourceStatus();
return () => {
if (oauthPollTimer.current) window.clearInterval(oauthPollTimer.current);
++oauthGeneration.current;
if (oauthPollTimer.current !== null) window.clearTimeout(oauthPollTimer.current);
};
}, [refreshSourceStatus]);
@@ -248,7 +250,9 @@ export function EasyModePage({
setGuideOAuthProvider(provider);
setGuideOAuthCompleted(false);
}
if (oauthPollTimer.current) window.clearInterval(oauthPollTimer.current);
const generation = ++oauthGeneration.current;
if (oauthPollTimer.current !== null) window.clearTimeout(oauthPollTimer.current);
oauthPollTimer.current = null;
setOauthLoggingIn(provider);
setOauthNotice(null);
@@ -263,6 +267,8 @@ export function EasyModePage({
browser: "default",
});
if (generation !== oauthGeneration.current) return;
if (!result.state) {
setOauthNotice({
tone: "error",
@@ -273,15 +279,24 @@ export function EasyModePage({
}
const stateKey = result.state;
oauthPollTimer.current = window.setInterval(async () => {
const deadline = Date.now() + 10 * 60_000;
let failures = 0;
const poll = async () => {
if (generation !== oauthGeneration.current) return;
if (Date.now() >= deadline) {
setOauthLoggingIn(null);
setOauthNotice({ tone: "error", message: "授权等待超时,请重新发起登录" });
return;
}
try {
const pollRes = await invoke<{ status: string; error?: string }>(
"get_oauth_status",
{ state: stateKey },
);
if (generation !== oauthGeneration.current) return;
failures = 0;
const status = (pollRes.status || "").toLowerCase();
if (status === "ok") {
if (oauthPollTimer.current) window.clearInterval(oauthPollTimer.current);
setOauthLoggingIn(null);
setOauthNotice({
tone: "success",
@@ -290,16 +305,29 @@ export function EasyModePage({
setGuideOAuthCompleted(true);
void refreshSourceStatus();
} else if (status === "error") {
if (oauthPollTimer.current) window.clearInterval(oauthPollTimer.current);
setOauthLoggingIn(null);
setOauthNotice({
tone: "error",
message: pollRes.error ? `授权失败: ${pollRes.error}` : "授权失败,请重试",
});
}
} catch {}
}, 1500);
if (status === "ok" || status === "error") return;
} catch (error) {
if (generation !== oauthGeneration.current) return;
failures += 1;
if (failures >= 3) {
setOauthLoggingIn(null);
setOauthNotice({ tone: "error", message: String(error) });
return;
}
}
if (generation === oauthGeneration.current) {
oauthPollTimer.current = window.setTimeout(poll, Math.min(1500 * 2 ** failures, 10_000));
}
};
oauthPollTimer.current = window.setTimeout(poll, 1500);
} catch (err) {
if (generation !== oauthGeneration.current) return;
setOauthLoggingIn(null);
setOauthNotice({
tone: "error",
@@ -451,28 +479,41 @@ export function EasyModePage({
const el = document.getElementById(currentTargetId);
if (el) {
const rect = el.getBoundingClientRect();
setSpotlightRect({
const next = {
top: Math.max(0, rect.top),
left: Math.max(0, rect.left),
width: rect.width,
height: rect.height,
});
el.scrollIntoView({ behavior: "smooth", block: "nearest" });
};
setSpotlightRect((previous) => previous && previous.top === next.top && previous.left === next.left
&& previous.width === next.width && previous.height === next.height ? previous : next);
} else {
setSpotlightRect(null);
}
}, [guideActive, currentTargetId]);
useEffect(() => {
const timer = setTimeout(updateSpotlightPosition, 100);
window.addEventListener("resize", updateSpotlightPosition);
window.addEventListener("scroll", updateSpotlightPosition, true);
let frame: number | null = null;
const schedulePosition = () => {
if (frame !== null) return;
frame = window.requestAnimationFrame(() => {
frame = null;
updateSpotlightPosition();
});
};
const timer = setTimeout(() => {
if (currentTargetId) document.getElementById(currentTargetId)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
schedulePosition();
}, 100);
window.addEventListener("resize", schedulePosition);
window.addEventListener("scroll", schedulePosition, { capture: true, passive: true });
return () => {
clearTimeout(timer);
window.removeEventListener("resize", updateSpotlightPosition);
window.removeEventListener("scroll", updateSpotlightPosition, true);
if (frame !== null) window.cancelAnimationFrame(frame);
window.removeEventListener("resize", schedulePosition);
window.removeEventListener("scroll", schedulePosition, true);
};
}, [updateSpotlightPosition]);
}, [currentTargetId, updateSpotlightPosition]);
useEffect(() => {
setGuideCardPosition(null);
+20 -3
View File
@@ -29,6 +29,7 @@ import { getCurrentLocale, useI18n } from '../i18n';
import type { MessageKey } from '../i18n/resources';
import { formatCacheReadRate, formatGenerationSpeed } from '../services/usageMetrics';
import { formatUsageNumber } from '../services/usageNumber';
import { createRefreshScheduler } from '../services/refreshScheduler';
type UsageTab = 'overview' | 'analysis' | 'events' | 'pricing' | 'data-management';
type UsageRange = '4h' | '24h' | 'today' | '7d' | '30d' | 'all' | 'custom';
@@ -292,6 +293,8 @@ export function UsageRecordsPage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const requestIdRef = useRef(0);
const schedulerRef = useRef<ReturnType<typeof createRefreshScheduler> | null>(null);
if (!schedulerRef.current) schedulerRef.current = createRefreshScheduler();
useEffect(() => {
try {
@@ -325,7 +328,7 @@ export function UsageRecordsPage() {
};
}, [apiKeyHash, customEnd, customStart, model, provider, range, result, source]);
const loadData = useCallback(
const executeLoadData = useCallback(
async (quiet = false) => {
const requestId = ++requestIdRef.current;
const { timeQuery, query } = buildQueries();
@@ -348,7 +351,9 @@ export function UsageRecordsPage() {
statusRequest,
optionsRequest,
invoke<UsageOverview>('get_usage_overview', { query }),
invoke<UsageAnalysis>('get_usage_analysis', { query }),
model || provider || source || apiKeyHash || result !== 'all'
? invoke<UsageAnalysis>('get_usage_analysis', { query })
: optionsRequest,
]);
if (requestId !== requestIdRef.current) return;
setStatus(nextStatus);
@@ -390,11 +395,23 @@ export function UsageRecordsPage() {
if (requestId === requestIdRef.current) setLoading(false);
}
},
[activeTab, buildQueries, page, pageSize]
[activeTab, buildQueries, page, pageSize, model, provider, source, apiKeyHash, result]
);
const loadData = useCallback(
(quiet = false) => {
if (!quiet) setLoading(true);
return schedulerRef.current!.schedule(() => executeLoadData(quiet), !quiet);
},
[executeLoadData],
);
useEffect(() => {
void loadData();
return () => {
++requestIdRef.current;
schedulerRef.current?.cancelPending();
};
}, [loadData]);
useEffect(() => {
+6 -2
View File
@@ -273,25 +273,27 @@ export async function runProviderModelHealthChecks(
checkModel: (model: ModelOption, index: number) => Promise<ProviderModelHealthResult>,
onModelChecked?: (result: ProviderModelHealthResult, index: number) => void,
concurrency = PROVIDER_HEALTH_CONCURRENCY,
signal?: AbortSignal,
): Promise<ProviderModelHealthResult[]> {
const results: ProviderModelHealthResult[] = new Array(models.length);
let nextIndex = 0;
const workerCount = Math.min(models.length, Math.max(1, Math.floor(concurrency)));
const runWorker = async () => {
while (nextIndex < models.length) {
while (nextIndex < models.length && !signal?.aborted) {
const index = nextIndex;
nextIndex += 1;
const model = models[index];
if (!model) continue;
const result = await checkModel(model, index);
if (signal?.aborted) break;
results[index] = result;
onModelChecked?.(result, index);
}
};
await Promise.all(Array.from({ length: workerCount }, runWorker));
return results;
return results.filter((result) => result !== undefined);
}
export async function checkProviderModelsHealth(
@@ -299,11 +301,13 @@ export async function checkProviderModelsHealth(
models: ModelOption[],
onModelChecked?: (result: ProviderModelHealthResult, index: number) => void,
concurrency = PROVIDER_HEALTH_CONCURRENCY,
signal?: AbortSignal,
): Promise<ProviderModelHealthResult[]> {
return runProviderModelHealthChecks(
models,
(model) => checkProviderModelHealth(options, model.name),
onModelChecked,
concurrency,
signal,
);
}
+66
View File
@@ -0,0 +1,66 @@
export function createRefreshScheduler(minIntervalMs = 1_000) {
type Task = () => Promise<void>;
type Completion = { promise: Promise<void>; resolve: () => void; reject: (error: unknown) => void };
let running = false;
let completedAt = -Infinity;
let pending: Task | null = null;
let urgent = false;
let completion: Completion | null = null;
let timer: ReturnType<typeof setTimeout> | null = null;
const pump = () => {
if (running || !pending) return;
if (timer !== null) clearTimeout(timer);
timer = null;
const delay = urgent ? 0 : Math.max(0, minIntervalMs - (performance.now() - completedAt));
if (delay > 0) {
timer = setTimeout(pump, delay);
return;
}
const task = pending;
const currentCompletion = completion!;
pending = null;
completion = null;
urgent = false;
running = true;
void (async () => {
try {
await task();
currentCompletion.resolve();
} catch (error) {
currentCompletion.reject(error);
} finally {
running = false;
completedAt = performance.now();
pump();
}
})();
};
return {
schedule(task: Task, immediate = false): Promise<void> {
pending = task;
urgent ||= immediate;
if (!completion) {
let resolve!: () => void;
let reject!: (error: unknown) => void;
const promise = new Promise<void>((onResolved, onRejected) => {
resolve = onResolved;
reject = onRejected;
});
completion = { promise, resolve, reject };
}
const result = completion.promise;
pump();
return result;
},
cancelPending() {
if (timer !== null) clearTimeout(timer);
timer = null;
pending = null;
urgent = false;
completion?.resolve();
completion = null;
},
};
}