perf: diagnoistics improvements
This commit is contained in:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user