perf: diagnoistics improvements

This commit is contained in:
2026-06-12 01:22:01 +02:00
parent 29032b5a36
commit dac5cb42a5
29 changed files with 1168 additions and 28 deletions

View File

@@ -1,11 +1,23 @@
import {
app,
BrowserWindow,
ipcMain
ipcMain,
shell
} from 'electron';
import { collectAppMetricsSnapshot } from '../app-metrics';
import { collectAppMetricsSnapshot, type AppMetricsSnapshot } from '../app-metrics';
import { getMainWindow } from '../window/create-window';
import { resolveReadablePath } from '../path-jail';
import { sumWorkingSetKb } from './process-metrics.rules';
import { isPerfDiagEnabled } from './diagnostics.flags';
import { exceedsHighMemoryThreshold } from './high-memory-alert.rules';
import { buildHighMemoryDiagnosticPayload } from './high-memory-snapshot.rules';
import { collectImmediateRendererSamples } from './immediate-renderer-samples.collector';
import { collectSessionContext } from './session-context.collector';
import {
clearHighMemoryAlert,
readHighMemoryAlert,
writeHighMemoryAlert
} from './high-memory-alert.store';
import type { PerfDiagEntry } from './diagnostics.models';
import { PerfDiagWriter } from './diagnostics.writer';
@@ -15,6 +27,8 @@ let activeWriter: PerfDiagWriter | null = null;
let processPollTimer: NodeJS.Timeout | null = null;
let diagnosticsEnabled = false;
let ipcRegistered = false;
let highMemoryAlertTriggeredThisSession = false;
let sessionStartedAt = 0;
export function isPerfDiagActive(): boolean {
return diagnosticsEnabled;
@@ -43,6 +57,37 @@ export function ensurePerfDiagIpcRegistered(): void {
return false;
}
});
ipcMain.handle('get-pending-high-memory-alert', async () => {
return readHighMemoryAlert(app.getPath('userData'));
});
ipcMain.handle('acknowledge-high-memory-alert', async () => {
await clearHighMemoryAlert(app.getPath('userData'));
return true;
});
ipcMain.handle('show-log-file-in-folder', async (_event, filePath: string) => {
if (typeof filePath !== 'string' || !filePath.trim()) {
return {
shown: false,
reason: 'missing-path'
};
}
const scopedPath = await resolveReadablePath(filePath);
if (!scopedPath) {
return {
shown: false,
reason: 'outside-app-data'
};
}
shell.showItemInFolder(scopedPath);
return { shown: true };
});
}
export function getActivePerfDiagWriter(): PerfDiagWriter | null {
@@ -64,9 +109,13 @@ export function startPerfDiagnostics(): PerfDiagWriter | null {
});
activeWriter = writer;
highMemoryAlertTriggeredThisSession = false;
sessionStartedAt = Date.now();
registerProcessCrashHandlers(writer);
startProcessMetricsPolling(writer);
const userDataPath = app.getPath('userData');
writer.append({
collectedAt: Date.now(),
source: 'main',
@@ -78,6 +127,18 @@ export function startPerfDiagnostics(): PerfDiagWriter | null {
}
});
writer.append({
collectedAt: Date.now(),
source: 'main',
type: 'environment',
payload: {
...collectSessionContext({
sessionStartedAt,
userDataPath
})
}
});
return writer;
}
@@ -195,6 +256,8 @@ function startProcessMetricsPolling(writer: PerfDiagWriter): void {
processes: metrics.processes
}
});
void maybeTriggerHighMemoryAlert(writer, metrics, totalKb);
} catch {
// Collector failures must never affect the app.
}
@@ -204,6 +267,64 @@ function startProcessMetricsPolling(writer: PerfDiagWriter): void {
processPollTimer = setInterval(sample, PROCESS_POLL_INTERVAL_MS);
}
async function maybeTriggerHighMemoryAlert(
writer: PerfDiagWriter,
metrics: AppMetricsSnapshot,
totalWorkingSetKb: number | null
): Promise<void> {
if (highMemoryAlertTriggeredThisSession || !exceedsHighMemoryThreshold(totalWorkingSetKb)) {
return;
}
highMemoryAlertTriggeredThisSession = true;
const detectedAt = Date.now();
const userDataPath = app.getPath('userData');
const immediateRendererEntries = await collectImmediateRendererSamples(getMainWindow());
const environment = collectSessionContext({
sessionStartedAt,
userDataPath
});
for (const entry of immediateRendererEntries) {
writer.append(entry);
}
writer.append({
collectedAt: detectedAt,
source: 'main',
type: 'environment',
payload: {
...environment
}
});
writer.append({
collectedAt: detectedAt,
source: 'main',
type: 'high-memory',
payload: buildHighMemoryDiagnosticPayload({
detectedAt,
totalWorkingSetKb: totalWorkingSetKb ?? 0,
metrics,
environment,
mainProcessMemory: process.memoryUsage(),
ringEntries: writer.bufferedEntries,
immediateRendererEntries,
sessionId: writer.sessionId
})
});
await writer.flushSnapshot('high-memory-threshold');
await writeHighMemoryAlert(userDataPath, {
logFilePath: writer.snapshotFilePath,
detectedAt,
peakWorkingSetKb: totalWorkingSetKb ?? 0,
sessionId: writer.sessionId
});
}
function normalizeRendererEntry(entry: PerfDiagEntry): PerfDiagEntry {
return {
collectedAt: Number(entry.collectedAt) || Date.now(),