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 }); }); });