
Heap Snapshot Analysis
Compare before/after V8 heap snapshots to find memory regressions when building or maintaining VS Code–style Electron apps.
Overview
Heap Snapshot Analysis is an agent skill for the Ship phase that compares V8 heap snapshots and reports top memory and count regressions plus VS Code-specific class changes.
Install
npx skills add https://github.com/microsoft/vscode --skill heap-snapshot-analysisWhat is this skill?
- Compares two V8 heap snapshots (before/after) with summary node and size deltas
- Reports top object groups by size increase and by count increase
- Surfaces new object groups that appear only in the after snapshot
- Includes VS Code–specific class change tracking in comparison output
- Implemented as TypeScript comparison over parsed snapshot metadata
- Reports top groups by size increase, top groups by count increase, and new object groups only in after
Adoption & trust: 80 installs on skills.sh; 186k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You suspect a memory leak or regression after a change but staring at raw heap dumps does not show which constructors grew.
Who is it for?
Extension authors and indie teams debugging VS Code or V8-based desktop apps with reproducible before/after profiles.
Skip if: Business-only bugs, network latency tuning, or production monitoring without local snapshot files.
When should I use this skill?
When you have before/after V8 heap snapshots and need ranked constructor/group deltas for VS Code or Chromium debugging.
What do I get? / Deliverables
You get ranked comparison groups and deltas so you can target the constructors and object groups to fix before release.
- ComparisonResult with summary deltas and ranked ComparisonGroup lists
Recommended Skills
Journey fit
How it compares
Local V8 snapshot differ—not a hosted APM or continuous cloud memory monitor.
Common Questions / FAQ
Who is heap-snapshot-analysis for?
Developers maintaining VS Code extensions or Electron apps who already capture V8 heap snapshots and need structured diffs.
When should I use heap-snapshot-analysis?
In Ship perf work when comparing baseline vs branch snapshots after stress tests or long editing sessions.
Is heap-snapshot-analysis safe to install?
Review the Security Audits panel on this Prism page; processing heap dumps implies reading large local snapshot files—avoid sharing snapshots that contain secrets.
SKILL.md
READMESKILL.md - Heap Snapshot Analysis
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /** * Heap Snapshot Comparison * * Compares two V8 heap snapshots (before/after) and reports: * - Top object groups by size increase * - Top object groups by count increase * - New object groups (only in "after") * - VS Code-specific class changes */ import { parseSnapshot, collectNodeIds, type SnapshotData } from './parseSnapshot.ts'; export interface ComparisonGroup { key: string; type: string; name: string; beforeCount: number; afterCount: number; countDiff: number; beforeSize: number; afterSize: number; sizeDiff: number; } export interface ComparisonResult { summary: { beforeNodes: number; afterNodes: number; beforeSize: number; afterSize: number; nodeDelta: number; sizeDelta: number; }; /** Top groups by size increase */ topBySize: ComparisonGroup[]; /** Top groups by count increase */ topByCount: ComparisonGroup[]; /** Groups that only exist in the "after" snapshot */ newObjectGroups: ComparisonGroup[]; } function groupByConstructor(data: SnapshotData, filterIds?: Set<number>) { const { meta, nodes, strings } = data; const nfc = meta.node_fields.length; const nodeTypes = meta.node_types[0]; const typeIdx = meta.node_fields.indexOf('type'); const nameIdx = meta.node_fields.indexOf('name'); const idIdx = meta.node_fields.indexOf('id'); const selfSizeIdx = meta.node_fields.indexOf('self_size'); const groups = new Map<string, { type: string; name: string; count: number; totalSize: number }>(); let totalSize = 0; for (let i = 0; i < nodes.length; i += nfc) { const id = nodes[i + idIdx]; if (filterIds && !filterIds.has(id)) { continue; } const typeName = nodeTypes[nodes[i + typeIdx]]; const name = strings[nodes[i + nameIdx]]; const selfSize = nodes[i + selfSizeIdx]; totalSize += selfSize; const key = `${typeName}::${name}`; let g = groups.get(key); if (!g) { g = { type: typeName, name, count: 0, totalSize: 0 }; groups.set(key, g); } g.count++; g.totalSize += selfSize; } return { groups, totalSize, nodeCount: nodes.length / nfc }; } /** * Compare two heap snapshots and return the differences. */ export function compareSnapshots(beforePath: string, afterPath: string, topN = 50): ComparisonResult { const beforeData = parseSnapshot(beforePath); const beforeResult = groupByConstructor(beforeData); const beforeIds = collectNodeIds(beforeData); // Free memory beforeData.nodes = null!; beforeData.edges = null!; const afterData = parseSnapshot(afterPath); const afterResult = groupByConstructor(afterData); const newIds = new Set<number>(); { const nfc = afterData.meta.node_fields.length; const idIdx = afterData.meta.node_fields.indexOf('id'); for (let i = 0; i < afterData.nodes.length; i += nfc) { const id = afterData.nodes[i + idIdx]; if (!beforeIds.has(id)) { newIds.add(id); } } } const newObjectResult = groupByConstructor(afterData, newIds); afterData.nodes = null!; afterData.edges = null!; // Compute diffs const diffs: ComparisonGroup[] = []; for (const [key, afterGroup] of afterResult.groups) { const beforeGroup = beforeResult.groups.get(key); const beforeCount = beforeGroup?.count ?? 0; const beforeSize = beforeGroup?.totalSize ?? 0; const countDiff = afterGroup.count - beforeCount; const sizeDiff = afterGroup.totalSize - beforeSize; if (countDiff > 0 || sizeDiff > 1024) { diffs.push({ key, type: afterGroup.type, name: afterGroup.name, beforeCount, afterCount: afterGroup.count, countDiff, beforeSize, afterSize: afterGroup.totalSize, sizeDiff, }); } } const topBySize = [...diffs].sort((a, b) => b.sizeDiff