mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
fix(perf): reduce idle Rust CPU and stabilize Performance Probe overlay (#939)
* fix(perf): skip Performance Probe CPU snapshot poll on Windows Windows has no Rust CPU/RSS sampler, but the probe still invoked performance_cpu_snapshot every 2s when the modal or overlay pins were active. Skip the IPC on unsupported platforms and only poll JS-side metrics; do not start overlay polling for CPU/memory pins alone. * fix(analysis): park backfill coordinator until Advanced is configured #881 started run_coordinator_forever at app init with a 2s sleep even when disabled, waking tokio on every platform for no work. Park on Notify instead; wake on configure (enable/disable) and library sync-idle. Long sleeps use select with wake so sync-idle can interrupt COMPLETED_RECHECK waits. Investigation branch — not for merge until periodic CPU root cause is confirmed. * fix(perf): stop probe overlay flicker on live poll updates Publish CPU samples only after a valid jiffies baseline, skip no-op snapshot emits, and sync sparkline history atomically in the store. Overlay uses wall-clock sparkline time and auto-scales low CPU values. * fix(perf): cut idle Rust CPU from probe scan, cover prefetch, and storage poll Move performance_cpu_snapshot /proc work to spawn_blocking so tokio workers are not charged with probe sampling. Stop lazy cover strategy from running route prefetch disk stats every 1.5s, and slow hot-cache size refresh on Settings → Storage to 15s. * fix(perf): stabilize probe sparkline clock between live poll ticks Track sampleAt separately from updatedAt so CPU rate history and overlay sparklines only advance on real % changes, not FPS re-renders or RSS-only poll ticks. Hold CPU sparkline Y scale with a peak ref to avoid scale jumps. * fix(cover): restore lazy route prefetch without idle disk stats poll Re-enable lazy cover registry warm-up so cached WebP paths reach diskSrcCache before cells mount. Skip cover_cache_stats on every 1.5s tick — drain batches via ensure only, poll full disk usage every 30s when the registry is idle. * docs: CHANGELOG and credits for PR #939 idle CPU perf fix * fix(cover): peek before route prefetch ensure to match main responsiveness Route prefetch moved batch drain ahead of cover_cache_stats for idle CPU, which removed the accidental throttle and flooded ensure invoke slots. Use warmCoverDiskSrcBatch first (cached hits skip ensure), ensure misses only, and yield while high-priority viewport work is queued.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
|
||||
import { useEffect, useMemo, useState, type CSSProperties } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { analysisGetPipelineQueueStats, type AnalysisPipelineQueueStatsDto } from '../api/analysis';
|
||||
import { coverGetPipelineQueueStats, type CoverPipelineQueueStatsDto } from '../api/coverCache';
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
type LiveOverlayItem,
|
||||
} from '../utils/perf/formatLiveOverlayItems';
|
||||
import {
|
||||
getPerfLiveHistoryClock,
|
||||
syncPerfLiveHistoryFromPoll,
|
||||
usePerfLiveHistorySamples,
|
||||
getPerfLiveHistorySamples,
|
||||
} from '../utils/perf/perfLiveHistory';
|
||||
import { acquirePerfLivePoll, usePerfLiveSnapshot } from '../utils/perf/perfLiveStore';
|
||||
import { hasAnyLiveMetricPollNeed, usePerfLiveOverlayPins } from '../utils/perf/perfOverlayPins';
|
||||
import { usePerfLiveSnapshot } from '../utils/perf/perfLiveStore';
|
||||
import { usePerfLiveOverlayPins } from '../utils/perf/perfOverlayPins';
|
||||
import {
|
||||
perfOverlayCornerClass,
|
||||
usePerfOverlayAppearance,
|
||||
@@ -41,11 +39,12 @@ const QUEUE_STATS_MS = 750;
|
||||
function LiveOverlayPinnedMetric({
|
||||
item,
|
||||
now,
|
||||
history,
|
||||
}: {
|
||||
item: LiveOverlayItem;
|
||||
now: number;
|
||||
history: ReturnType<typeof getPerfLiveHistorySamples>;
|
||||
}) {
|
||||
const history = usePerfLiveHistorySamples(item.id);
|
||||
const sparklineKind = item.kind === 'memory' ? 'memory' : 'cpu';
|
||||
|
||||
return (
|
||||
@@ -70,7 +69,6 @@ export default function FpsOverlay() {
|
||||
const [queueStats, setQueueStats] = useState<AnalysisPipelineQueueStatsDto | null>(null);
|
||||
const [coverQueueLines, setCoverQueueLines] = useState<string[]>([]);
|
||||
const last = useAnalysisPerfLast();
|
||||
const lastHistoryAt = useRef(0);
|
||||
|
||||
const liveOverlayItems = useMemo(
|
||||
() => buildLiveOverlayItems(livePins, live),
|
||||
@@ -89,24 +87,13 @@ export default function FpsOverlay() {
|
||||
showLive,
|
||||
} = visibility;
|
||||
|
||||
lastHistoryAt.current = overlayMode === 'pinned'
|
||||
? syncPerfLiveHistoryFromPoll(livePins, live, lastHistoryAt.current)
|
||||
: lastHistoryAt.current;
|
||||
|
||||
const sparklineNow = useMemo(() => {
|
||||
const clock = getPerfLiveHistoryClock(
|
||||
liveOverlayItems.filter(item => item.sparkline).map(item => item.id),
|
||||
);
|
||||
return clock > 0 ? clock : Date.now();
|
||||
}, [liveOverlayItems, live.updatedAt]);
|
||||
const sparklineNow = useMemo(
|
||||
() => (live.sampleAt > 0 ? live.sampleAt : Date.now()),
|
||||
[live.sampleAt],
|
||||
);
|
||||
|
||||
useAnalysisPerfListener(showAnalysisPerfOverlay || livePins.has('analysis:tpm') || livePins.has('analysis:last'));
|
||||
|
||||
useEffect(() => {
|
||||
if (overlayMode !== 'pinned' || !hasAnyLiveMetricPollNeed()) return;
|
||||
return acquirePerfLivePoll('overlay-pins');
|
||||
}, [overlayMode, livePins.size]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showAnalysisPerfOverlay) {
|
||||
setTpm(0);
|
||||
@@ -219,7 +206,12 @@ export default function FpsOverlay() {
|
||||
<div className="fps-overlay__block">
|
||||
<div className="fps-overlay__block-title">Live</div>
|
||||
{liveOverlayItems.map(item => (
|
||||
<LiveOverlayPinnedMetric key={item.id} item={item} now={sparklineNow} />
|
||||
<LiveOverlayPinnedMetric
|
||||
key={item.id}
|
||||
item={item}
|
||||
now={sparklineNow}
|
||||
history={item.sparkline ? getPerfLiveHistorySamples(item.id) : []}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -62,7 +62,7 @@ function scale(samples: readonly PerfLiveSample[], kind: SparklineKind): { min:
|
||||
const minVal = Math.min(...values);
|
||||
const maxVal = Math.max(...values);
|
||||
if (kind === 'cpu') {
|
||||
return { min: 0, max: Math.max(100, maxVal * 1.1) };
|
||||
return { min: 0, max: Math.max(5, maxVal * 1.25) };
|
||||
}
|
||||
const pad = Math.max((maxVal - minVal) * 0.08, maxVal * 0.02, 1);
|
||||
return { min: Math.max(0, minVal - pad), max: maxVal + pad };
|
||||
@@ -74,8 +74,10 @@ function stableScale(
|
||||
peakRef: { current: number },
|
||||
): { min: number; max: number } {
|
||||
const raw = scale(samples, kind);
|
||||
if (kind === 'cpu') return raw;
|
||||
if (raw.max > peakRef.current) peakRef.current = raw.max;
|
||||
if (kind === 'cpu') {
|
||||
return { min: 0, max: Math.max(5, Math.max(peakRef.current, raw.max)) };
|
||||
}
|
||||
return { min: 0, max: Math.max(peakRef.current, raw.max) };
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export function StorageTab() {
|
||||
};
|
||||
refresh();
|
||||
if (!auth.hotCacheEnabled) return;
|
||||
const interval = window.setInterval(refresh, 2000);
|
||||
const interval = window.setInterval(refresh, 15_000);
|
||||
return () => window.clearInterval(interval);
|
||||
}, [auth.hotCacheEnabled, auth.hotCacheDownloadDir]);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { perfLiveCpuSnapshotSupported } from '../../../utils/perf/perfLiveCpuSnapshot';
|
||||
import {
|
||||
PERF_LIVE_POLL_MS_MAX,
|
||||
PERF_LIVE_POLL_MS_MIN,
|
||||
@@ -11,6 +12,8 @@ import {
|
||||
export default function PerfLivePollControls() {
|
||||
const pollMs = usePerfLivePollIntervalMs();
|
||||
const includeThreadGroups = usePerfLiveIncludeThreadGroups();
|
||||
if (!perfLiveCpuSnapshotSupported()) return null;
|
||||
|
||||
const pollSec = (pollMs / 1000).toFixed(1);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { usePerfLiveSnapshot } from '../../../utils/perf/perfLiveStore';
|
||||
import { isPerfLivePollWaitingForCpu, usePerfLiveSnapshot } from '../../../utils/perf/perfLiveStore';
|
||||
import { usePerfLiveIncludeThreadGroups } from '../../../utils/perf/perfLivePollSettings';
|
||||
import {
|
||||
togglePerfLiveOverlayPin,
|
||||
@@ -26,7 +26,7 @@ export default function SidebarPerfProbeMonitorTab() {
|
||||
const coverPinned = usePipelineOverlayPinned('pipeline:cover');
|
||||
const cpu = live.cpu;
|
||||
const cpuSupported = cpu?.supported === true;
|
||||
const collecting = live.collecting && cpu == null;
|
||||
const collecting = isPerfLivePollWaitingForCpu();
|
||||
const includeThreadGroups = usePerfLiveIncludeThreadGroups();
|
||||
const peakMemoryKbRef = useRef(1);
|
||||
const peakThreadCpuRef = useRef(1);
|
||||
|
||||
Reference in New Issue
Block a user