perf: diagnoistics improvements
This commit is contained in:
179
electron/diagnostics/high-memory-snapshot.rules.ts
Normal file
179
electron/diagnostics/high-memory-snapshot.rules.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import type { AppMetricsProcessSnapshot, AppMetricsSnapshot } from '../app-metrics';
|
||||
import type { PerfDiagEntry } from './diagnostics.models';
|
||||
import { formatWorkingSetGb, HIGH_MEMORY_THRESHOLD_KB } from './high-memory-alert.rules';
|
||||
import type { SessionContextSnapshot } from './session-context.collector';
|
||||
|
||||
export interface RankedProcessSnapshot extends AppMetricsProcessSnapshot {
|
||||
sharePercent: number;
|
||||
}
|
||||
|
||||
export interface HighMemorySummary {
|
||||
detectedAt: number;
|
||||
thresholdKb: number;
|
||||
thresholdGb: string;
|
||||
totalWorkingSetKb: number;
|
||||
totalWorkingSetGb: string;
|
||||
topProcesses: RankedProcessSnapshot[];
|
||||
}
|
||||
|
||||
export interface LatestRendererSamples {
|
||||
store: Record<string, unknown> | null;
|
||||
heap: Record<string, unknown> | null;
|
||||
components: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export function rankProcessesByWorkingSet(
|
||||
processes: readonly AppMetricsProcessSnapshot[],
|
||||
totalWorkingSetKb: number | null
|
||||
): RankedProcessSnapshot[] {
|
||||
const total = totalWorkingSetKb ?? 0;
|
||||
|
||||
return [...processes]
|
||||
.filter((process) => process.workingSetKb != null && process.workingSetKb > 0)
|
||||
.sort((left, right) => (right.workingSetKb ?? 0) - (left.workingSetKb ?? 0))
|
||||
.map((process) => ({
|
||||
...process,
|
||||
sharePercent: total > 0
|
||||
? Math.round(((process.workingSetKb ?? 0) / total) * 1000) / 10
|
||||
: 0
|
||||
}));
|
||||
}
|
||||
|
||||
export function extractLatestRendererSamples(entries: readonly PerfDiagEntry[]): LatestRendererSamples {
|
||||
let store: Record<string, unknown> | null = null;
|
||||
let heap: Record<string, unknown> | null = null;
|
||||
let components: Record<string, unknown> | null = null;
|
||||
|
||||
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
||||
const entry = entries[index];
|
||||
|
||||
if (entry.source !== 'renderer') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!store && entry.type === 'store') {
|
||||
store = entry.payload;
|
||||
}
|
||||
|
||||
if (!heap && entry.type === 'heap') {
|
||||
heap = entry.payload;
|
||||
}
|
||||
|
||||
if (!components && entry.type === 'components') {
|
||||
components = entry.payload;
|
||||
}
|
||||
|
||||
if (store && heap && components) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
store,
|
||||
heap,
|
||||
components
|
||||
};
|
||||
}
|
||||
|
||||
export function extractProcessHistory(
|
||||
entries: readonly PerfDiagEntry[],
|
||||
limit = 24
|
||||
): Record<string, unknown>[] {
|
||||
const history: Record<string, unknown>[] = [];
|
||||
|
||||
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
||||
const entry = entries[index];
|
||||
|
||||
if (entry.type !== 'process') {
|
||||
continue;
|
||||
}
|
||||
|
||||
history.unshift({
|
||||
collectedAt: entry.collectedAt,
|
||||
...entry.payload
|
||||
});
|
||||
|
||||
if (history.length >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
export function summarizeRingBuffer(entries: readonly PerfDiagEntry[]): Record<string, number> {
|
||||
const counts: Record<string, number> = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
const key = `${entry.source}:${entry.type}`;
|
||||
|
||||
counts[key] = (counts[key] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
||||
export function buildHighMemorySummary(
|
||||
totalWorkingSetKb: number,
|
||||
processes: readonly AppMetricsProcessSnapshot[],
|
||||
detectedAt: number
|
||||
): HighMemorySummary {
|
||||
return {
|
||||
detectedAt,
|
||||
thresholdKb: HIGH_MEMORY_THRESHOLD_KB,
|
||||
thresholdGb: formatWorkingSetGb(HIGH_MEMORY_THRESHOLD_KB),
|
||||
totalWorkingSetKb,
|
||||
totalWorkingSetGb: formatWorkingSetGb(totalWorkingSetKb),
|
||||
topProcesses: rankProcessesByWorkingSet(processes, totalWorkingSetKb).slice(0, 12)
|
||||
};
|
||||
}
|
||||
|
||||
export function formatMemoryUsageMb(memoryUsage: NodeJS.MemoryUsage): Record<string, number> {
|
||||
return {
|
||||
rssMb: roundMb(memoryUsage.rss),
|
||||
heapTotalMb: roundMb(memoryUsage.heapTotal),
|
||||
heapUsedMb: roundMb(memoryUsage.heapUsed),
|
||||
externalMb: roundMb(memoryUsage.external),
|
||||
arrayBuffersMb: roundMb(memoryUsage.arrayBuffers ?? 0)
|
||||
};
|
||||
}
|
||||
|
||||
export function buildHighMemoryDiagnosticPayload(input: {
|
||||
detectedAt: number;
|
||||
totalWorkingSetKb: number;
|
||||
metrics: AppMetricsSnapshot;
|
||||
environment: SessionContextSnapshot;
|
||||
mainProcessMemory: NodeJS.MemoryUsage;
|
||||
ringEntries: readonly PerfDiagEntry[];
|
||||
immediateRendererEntries: readonly PerfDiagEntry[];
|
||||
sessionId: string;
|
||||
}): Record<string, unknown> {
|
||||
const mergedRingEntries = [...input.ringEntries, ...input.immediateRendererEntries];
|
||||
const recentRendererSamples = extractLatestRendererSamples(mergedRingEntries);
|
||||
|
||||
return {
|
||||
event: 'high-memory-threshold',
|
||||
sessionId: input.sessionId,
|
||||
summary: buildHighMemorySummary(
|
||||
input.totalWorkingSetKb,
|
||||
input.metrics.processes,
|
||||
input.detectedAt
|
||||
),
|
||||
environment: input.environment,
|
||||
metrics: input.metrics,
|
||||
mainProcessMemory: input.mainProcessMemory,
|
||||
mainProcessMemoryMb: formatMemoryUsageMb(input.mainProcessMemory),
|
||||
processHistory: extractProcessHistory(mergedRingEntries),
|
||||
ringSummary: summarizeRingBuffer(mergedRingEntries),
|
||||
recentRendererSamples,
|
||||
immediateRendererSamples: input.immediateRendererEntries.map((entry) => ({
|
||||
collectedAt: entry.collectedAt,
|
||||
type: entry.type,
|
||||
payload: entry.payload
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function roundMb(bytes: number): number {
|
||||
return Math.round((bytes / (1024 * 1024)) * 100) / 100;
|
||||
}
|
||||
Reference in New Issue
Block a user