Files
Toju/electron/diagnostics/high-memory-snapshot.rules.spec.ts
2026-06-12 01:22:01 +02:00

202 lines
5.4 KiB
TypeScript

import {
describe,
expect,
it
} from 'vitest';
import type { PerfDiagEntry } from './diagnostics.models';
import {
buildHighMemoryDiagnosticPayload,
buildHighMemorySummary,
extractLatestRendererSamples,
extractProcessHistory,
formatMemoryUsageMb,
rankProcessesByWorkingSet,
summarizeRingBuffer
} from './high-memory-snapshot.rules';
function createProcess(overrides: Partial<{
pid: number;
type: string;
workingSetKb: number | null;
peakWorkingSetKb: number | null;
privateBytesKb: number | null;
creationTime: number | null;
cpuPercent: number | null;
}> = {}) {
return {
pid: 1,
type: 'Tab',
workingSetKb: 1024,
peakWorkingSetKb: null,
privateBytesKb: null,
creationTime: null,
cpuPercent: null,
...overrides
};
}
describe('high-memory-snapshot.rules', () => {
it('ranks processes by working set and computes share percentages', () => {
const tabProcess = createProcess({ pid: 1, type: 'Tab', workingSetKb: 512_000 });
const gpuProcess = createProcess({ pid: 2, type: 'GPU', workingSetKb: 1_536_000 });
const ranked = rankProcessesByWorkingSet([tabProcess, gpuProcess], 2_048_000);
expect(ranked[0]?.type).toBe('GPU');
expect(ranked[0]?.sharePercent).toBe(75);
expect(ranked[1]?.sharePercent).toBe(25);
});
it('extracts the latest renderer store, heap, and component samples', () => {
const entries: PerfDiagEntry[] = [
{
collectedAt: 1,
source: 'renderer',
type: 'store',
payload: { domains: { chat: 100 } }
},
{
collectedAt: 2,
source: 'renderer',
type: 'heap',
payload: { usedJsHeapMb: 120 }
},
{
collectedAt: 3,
source: 'renderer',
type: 'components',
payload: { suspectedLeaks: [{ name: 'ChatMessageItem', count: 40, expected: 20 }] }
},
{
collectedAt: 4,
source: 'renderer',
type: 'store',
payload: { domains: { chat: 500 } }
}
];
expect(extractLatestRendererSamples(entries)).toEqual({
store: { domains: { chat: 500 } },
heap: { usedJsHeapMb: 120 },
components: { suspectedLeaks: [{ name: 'ChatMessageItem', count: 40, expected: 20 }] }
});
});
it('extracts recent process history from the ring buffer', () => {
const entries: PerfDiagEntry[] = [
{
collectedAt: 1,
source: 'main',
type: 'process',
payload: { totalWorkingSetKb: 1000 }
},
{
collectedAt: 2,
source: 'main',
type: 'session',
payload: { event: 'noop' }
},
{
collectedAt: 3,
source: 'main',
type: 'process',
payload: { totalWorkingSetKb: 2000 }
}
];
expect(extractProcessHistory(entries)).toEqual([{ collectedAt: 1, totalWorkingSetKb: 1000 }, { collectedAt: 3, totalWorkingSetKb: 2000 }]);
});
it('summarizes ring buffer entry counts', () => {
expect(summarizeRingBuffer([
{ collectedAt: 1, source: 'main', type: 'process', payload: {} },
{ collectedAt: 2, source: 'renderer', type: 'heap', payload: {} },
{ collectedAt: 3, source: 'main', type: 'process', payload: {} }
])).toEqual({
'main:process': 2,
'renderer:heap': 1
});
});
it('builds a high-memory summary with threshold context', () => {
const summary = buildHighMemorySummary(
2_200_000,
[createProcess({ workingSetKb: 2_200_000 })],
1_700_000_000_000
);
expect(summary.totalWorkingSetGb).toBe('2.10');
expect(summary.thresholdGb).toBe('2.00');
expect(summary.topProcesses).toHaveLength(1);
});
it('builds a comprehensive high-memory diagnostic payload', () => {
const payload = buildHighMemoryDiagnosticPayload({
detectedAt: 1_700_000_000_000,
totalWorkingSetKb: 2_200_000,
metrics: {
collectedAt: 1_700_000_000_000,
processes: [
createProcess({
workingSetKb: 2_200_000,
peakWorkingSetKb: 2_300_000,
privateBytesKb: 1_800_000,
creationTime: 1,
cpuPercent: 12
})
]
},
environment: { appVersion: '1.0.0' },
mainProcessMemory: {
rss: 64 * 1024 * 1024,
heapTotal: 32 * 1024 * 1024,
heapUsed: 16 * 1024 * 1024,
external: 8 * 1024 * 1024,
arrayBuffers: 1024
},
ringEntries: [
{
collectedAt: 1,
source: 'main',
type: 'process',
payload: { totalWorkingSetKb: 2_000_000 }
}
],
immediateRendererEntries: [
{
collectedAt: 2,
source: 'renderer',
type: 'heap',
payload: { usedJsHeapMb: 300, route: '/room/abc' }
}
],
sessionId: 'session-1'
});
expect(payload.event).toBe('high-memory-threshold');
expect(payload.summary).toMatchObject({
totalWorkingSetKb: 2_200_000
});
expect(payload.processHistory).toHaveLength(1);
expect(payload.recentRendererSamples).toEqual({
store: null,
heap: { usedJsHeapMb: 300, route: '/room/abc' },
components: null
});
expect(formatMemoryUsageMb({
rss: 64 * 1024 * 1024,
heapTotal: 32 * 1024 * 1024,
heapUsed: 16 * 1024 * 1024,
external: 8 * 1024 * 1024,
arrayBuffers: 1024
})).toEqual({
rssMb: 64,
heapTotalMb: 32,
heapUsedMb: 16,
externalMb: 8,
arrayBuffersMb: 0
});
});
});