* Pretty-print the snapshot for terminal output. JSON mode (--json) goes * straight through JSON.stringify so the extension footer and any test * harness can consume it programmatically.
(s: MemorySnapshot)
| 35 | * harness can consume it programmatically. |
| 36 | */ |
| 37 | function formatSnapshotText(s: MemorySnapshot): string { |
| 38 | const lines: string[] = []; |
| 39 | lines.push( |
| 40 | `Bun server: RSS: ${formatBytes(s.bunServer.rss)} ` + |
| 41 | `heap: ${formatBytes(s.bunServer.heapUsed)} / ${formatBytes(s.bunServer.heapTotal)} ` + |
| 42 | `external: ${formatBytes(s.bunServer.external)}`, |
| 43 | ); |
| 44 | |
| 45 | if (s.processes && s.processes.length > 0) { |
| 46 | // Group by type so the user sees "renderer: 12" vs listing 12 separate rows. |
| 47 | const byType: Record<string, number> = {}; |
| 48 | for (const p of s.processes) byType[p.type] = (byType[p.type] ?? 0) + 1; |
| 49 | const typeSummary = Object.entries(byType) |
| 50 | .map(([t, n]) => `${t}=${n}`) |
| 51 | .join(' '); |
| 52 | lines.push(`Chromium processes: ${s.processes.length} total (${typeSummary})`); |
| 53 | } else if (s.processes === null) { |
| 54 | lines.push('Chromium processes: (unavailable — see notes)'); |
| 55 | } else { |
| 56 | lines.push('Chromium processes: 0'); |
| 57 | } |
| 58 | |
| 59 | if (s.tabs.length > 0) { |
| 60 | // Sort by JS heap descending; show top 10 plus "...N more" tail. |
| 61 | const sorted = [...s.tabs].sort((a, b) => b.jsHeapUsed - a.jsHeapUsed); |
| 62 | const shown = sorted.slice(0, 10); |
| 63 | lines.push(`Renderers: ${s.tabs.length} tabs (top by JS heap):`); |
| 64 | for (const t of shown) { |
| 65 | const urlShort = t.url.length > 80 ? t.url.slice(0, 77) + '...' : t.url; |
| 66 | lines.push( |
| 67 | ` [${formatBytes(t.jsHeapUsed).padStart(8)} JS, ` + |
| 68 | `${String(t.nodes).padStart(6)} nodes, ` + |
| 69 | `${String(t.listeners).padStart(5)} listeners] ` + |
| 70 | `tab #${t.id} — ${urlShort}`, |
| 71 | ); |
| 72 | } |
| 73 | if (sorted.length > shown.length) { |
| 74 | lines.push(` ...and ${sorted.length - shown.length} more`); |
| 75 | } |
| 76 | } else { |
| 77 | lines.push('Renderers: (no tabs tracked)'); |
| 78 | } |
| 79 | |
| 80 | lines.push('─────────────────────────────────────────────────'); |
| 81 | lines.push('In-memory structures (Bun side):'); |
| 82 | const m = s.structures.modificationHistory; |
| 83 | lines.push( |
| 84 | ` modificationHistory: ${m.current} / ${m.cap} entries` + |
| 85 | (m.evicted > 0 ? ` (${m.evicted} evicted since reset)` : ''), |
| 86 | ); |
| 87 | lines.push(` inspectorSubscribers: ${s.structures.inspectorSubscribers}`); |
| 88 | lines.push(` activitySubscribers: ${s.structures.activitySubscribers}`); |
| 89 | lines.push(` consoleBuffer: ${s.structures.consoleBufferLen} entries`); |
| 90 | lines.push(` networkBuffer: ${s.structures.networkBufferLen} entries`); |
| 91 | lines.push(` dialogBuffer: ${s.structures.dialogBufferLen} entries`); |
| 92 | lines.push(` captureBuffer: ${formatBytes(s.structures.captureBufferBytes)}`); |
| 93 | |
| 94 | if (s.notes.length > 0) { |
no test coverage detected