92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
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();
|
|
};
|
|
}
|