npx skills add ...
npx skills add microsoft/vscode --skill cpu-profile-analysis
Analyze V8/Chrome CPU profiles (.cpuprofile) and DevTools trace files (Trace-*.json). Use when: profiling performance, investigating slow functions, comparing code paths, finding bottlenecks, analyzing timeToRequest, understanding call trees from sampling profiler data, analyzing layout/paint/rendering, investigating user timing marks.
npx skills add microsoft/vscode --skill cpu-profile-analysis
Analyze .cpuprofile files (V8 sampling profiler) and DevTools trace files (Trace-*.json, Chrome Trace Event Format) to find performance bottlenecks, compare code paths, and understand timing.
.cpuprofile or Trace-*.json file and wants to understand performancecode/didResolveTextFileEditorModel (trace files).cpuprofile: Top-level JSON with nodes, samples, timeDeltas keys. Created by the VS Code profiler.Trace-*.json: Top-level JSON with traceEvents array (and optional metadata). Created by Chrome/Electron DevTools (Performance tab). These are richer than .cpuprofile -- they contain CPU samples, layout/paint events, user timing marks, GC events, input events, and multi-process data.(idle), (program), or (garbage collector) represent no user code running..cpuprofile FilesA .cpuprofile is JSON with these top-level keys:
nodes: Array of call frame nodes forming a tree (each has id, callFrame, children)samples: Array of node IDs -- one per profiler tick, referencing the leaf (innermost) frametimeDeltas: Array of microsecond deltas between consecutive samplesstartTime / endTime: Absolute timestamps in microseconds$vscode: Optional VS Code metadataProfile and trace files can exceed V8's string limit (~512MB). Always check the file size first and choose the right parsing strategy:
For files under ~400MB, JSON.parse(readFileSync(..., 'utf8')) works fine. For larger files, see the Handling Huge Files section below.
Profiles are often single-line JSON. Reformat for inspection (only if small enough):
Write a Node.js analysis script. Build these structures:
Split the timeline into buckets (e.g. 500ms) and find which contain relevant function names. Use marker functions related to the user's question to detect activity windows. Allow small gaps (1-2 empty buckets) when merging regions.
Important: Because this is a sampling profiler, don't require exact function names. Use sets of related marker functions and look for the broader flow.
For questions like "time from X to Y":
When comparing two implementations:
Present results as:
Trace-*.json)DevTools traces are the future of perf tracing for VS Code. They are created from the built-in Electron/Chrome DevTools Performance tab and contain far more information than .cpuprofile files.
A Trace-*.json file has these top-level keys:
traceEvents: Array of trace event objects (hundreds of thousands of entries)metadata: Object with source, startTime, dataOrigin, and optional DevTools state (breadcrumbs, annotations)Each event in traceEvents follows the Chrome Trace Event Format:
ph)| Phase | Name | Meaning |
|---|---|---|
X | Complete | Event with duration (dur field). Most common. |
B | Begin | Start of a duration event (paired with E). |
E | End | End of a duration event (paired with B). |
I | Instant | Point-in-time event (no duration). |
P | Sample | CPU profiler sample. |
R | Mark | Navigation timing mark. |
M | Metadata | Process/thread name metadata. |
N | Object Created | Object lifecycle tracking. |
D | Object Destroyed | Object lifecycle tracking. |
s | Flow Start | Async flow connection start. |
f | Flow End | Async flow connection end. |
b | Async Begin | Async event begin. |
e | Async End | Async event end. |
n | Async Instant | Async event instant. |
| Category | What it captures |
|---|---|
disabled-by-default-devtools.timeline | RunTask, EvaluateScript, TracingStartedInBrowser -- core task scheduling |
devtools.timeline | FunctionCall, EventDispatch, TimerInstall/Fire, PrePaint, Paint -- main thread activity |
blink.user_timing | VS Code performance marks (e.g. code/willResolveTextFileEditorModel, code/didResolveTextFileEditorModel) |
blink,devtools.timeline | UpdateLayoutTree, HitTest, IntersectionObserver, ParseAuthorStyleSheet -- layout/rendering |
disabled-by-default-v8.cpu_profiler | Profile, ProfileChunk -- embedded CPU profile data (same as .cpuprofile but chunked) |
v8 | v8.callFunction, v8.newInstance, V8.DeoptimizeCode -- V8 engine events |
v8,devtools.timeline | v8.compile -- script compilation |
devtools.timeline,v8 | MinorGC, MajorGC -- garbage collection |
cppgc | C++ GC events (Blink garbage collection) |
loading | LayoutShift, URLLoader -- resource loading and layout shifts |
cc,benchmark,disabled-by-default-devtools.timeline.frame | Frame pipeline events (PipelineReporter, Commit, etc.) |
__metadata | process_name, thread_name -- process/thread identification |
Trace files contain events from multiple processes:
| Process | Role | Key Thread |
|---|---|---|
| Renderer (pid varies) | VS Code's renderer process -- where JS runs | CrRendererMain (main thread) |
| Browser (pid varies) | Electron's main/browser process | CrBrowserMain |
| GPU Process (pid varies) | GPU compositing and rendering | CrGpuMain, VizCompositorThread |
Identify processes/threads via metadata events:
For VS Code perf analysis, focus on the Renderer process, CrRendererMain thread -- this is where JavaScript execution, layout, and painting happen.
Trace files are typically 50-200MB but can exceed V8's string limit (~512MB). Always check first:
For small trace files, reformat for inspection:
VS Code emits performance.mark() calls that appear as blink.user_timing events. These are the most direct way to measure VS Code-specific milestones:
Find expensive tasks on the main thread:
FunctionCall events include source location info:
Find layout thrashing and expensive paints:
Trace files contain the full CPU profile as ProfileChunk events. Reconstruct it:
Present results as:
When a .cpuprofile or Trace-*.json file exceeds ~400MB, readFileSync(..., 'utf8') may fail because V8 cannot create a string that large. Use Buffer-based extraction instead: read the file as a raw Buffer and extract sections by scanning for known JSON keys. This is the same technique used for heap snapshots (see parseSnapshot.ts).
Key principle: Read the file as a Buffer, locate JSON array/object boundaries by scanning bytes, extract individual sections as sub-buffers that are small enough for JSON.parse, then assemble the result.
Always run analysis scripts with extra memory: node --max-old-space-size=16384 script.mjs
.cpuprofileA .cpuprofile has top-level keys nodes, samples, timeDeltas, startTime, endTime. Extract each section from the buffer:
Trace-*.jsonTrace files have two top-level keys: metadata (small object) and traceEvents (huge array of objects). The strategy is to extract metadata normally and stream-parse traceEvents by scanning for individual event objects:
| File size | Approach |
|---|---|
| < 400MB | JSON.parse(readFileSync(path, 'utf8')) is fine |
| 400MB - 1GB | Use Buffer-based extraction functions above |
| > 1GB | Use Buffer-based extraction + --max-old-space-size=16384 |
node --max-old-space-size=16384 to give Node.js enough heap space.JSON.parse calls operate on small sub-buffers.extension.js), function names may be mangled. Use line numbers from callFrame.lineNumber to cross-reference with source maps.ProfileChunk events, prefer analyzing those over asking for a separate .cpuprofile -- the data is equivalent but already correlated with other trace events.args.data.url in FunctionCall and EvaluateScript events to map back to VS Code source files (paths like vscode-file://vscode-app/Users/.../out/vs/...).dur field is wall-clock duration; tdur is thread-time duration. The difference reveals time the thread was suspended (e.g. waiting for I/O or preempted).