perf: optimize app startup and background lifecycles
This commit is contained in:
91
src/lib/performance-diagnostics.ts
Normal file
91
src/lib/performance-diagnostics.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { invokeIpc } from '@/lib/api-client';
|
||||
|
||||
type LongTaskAggregate = {
|
||||
count: number;
|
||||
totalDurationMs: number;
|
||||
maxDurationMs: number;
|
||||
};
|
||||
|
||||
type MainPerformanceSnapshot = {
|
||||
capturedAt?: number;
|
||||
eventLoopDelayMs?: number;
|
||||
processes?: Array<unknown>;
|
||||
windows?: number;
|
||||
webContents?: number;
|
||||
gpu?: Record<string, string>;
|
||||
background?: {
|
||||
activity?: { visible?: boolean; module?: string | null };
|
||||
leaseCount?: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Installs development-only, aggregate renderer diagnostics. No URLs, page
|
||||
* content, prompts or file names are retained or sent to Main. Production
|
||||
* builds return a no-op cleanup so this cannot become a hidden polling task.
|
||||
*/
|
||||
export function installRendererPerformanceDiagnostics(): () => void {
|
||||
if (typeof window === 'undefined' || window.electron?.isDev !== true) {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
const longTasks: LongTaskAggregate = {
|
||||
count: 0,
|
||||
totalDurationMs: 0,
|
||||
maxDurationMs: 0,
|
||||
};
|
||||
let firstPaintMs: number | null = null;
|
||||
let disposed = false;
|
||||
let paintFrame = 0;
|
||||
|
||||
const observer = typeof PerformanceObserver === 'undefined'
|
||||
? null
|
||||
: new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
const duration = Math.round(entry.duration * 100) / 100;
|
||||
longTasks.count += 1;
|
||||
longTasks.totalDurationMs += duration;
|
||||
longTasks.maxDurationMs = Math.max(longTasks.maxDurationMs, duration);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
observer?.observe({ type: 'longtask', buffered: true } as PerformanceObserverInit);
|
||||
} catch {
|
||||
// Chromium versions without the Long Tasks entry type are supported.
|
||||
}
|
||||
|
||||
paintFrame = window.requestAnimationFrame(() => {
|
||||
firstPaintMs = Math.round(performance.now() * 100) / 100;
|
||||
});
|
||||
|
||||
const report = () => {
|
||||
if (disposed) return;
|
||||
const renderer = {
|
||||
firstPaintMs,
|
||||
longTasks: {
|
||||
count: longTasks.count,
|
||||
totalDurationMs: Math.round(longTasks.totalDurationMs * 100) / 100,
|
||||
maxDurationMs: Math.round(longTasks.maxDurationMs * 100) / 100,
|
||||
},
|
||||
};
|
||||
void invokeIpc<MainPerformanceSnapshot>('app:performance')
|
||||
.then((main) => {
|
||||
if (!disposed) {
|
||||
console.debug('[performance]', { renderer, main });
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
};
|
||||
|
||||
// A low-frequency sample is enough for the development panel and avoids
|
||||
// adding another active request loop to normal renderer operation.
|
||||
const timer = window.setInterval(report, 30_000);
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
window.cancelAnimationFrame(paintFrame);
|
||||
window.clearInterval(timer);
|
||||
observer?.disconnect();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user