[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M699A4W2WWBPBGS9JTPVA9ZY":3,"skill-same:01M699A4W2WWBPBGS9JTPVA9ZY":103},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":50,"ownerHtmlUrl":51,"ownerBlogUrl":52,"ownerDescription":53,"updatedAt":54,"install":55,"resources":68},"01M699A4W2WWBPBGS9JTPVA9ZY","cpu-profile-analysis",null,"Analyze V8\u002FChrome 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\u002Fpaint\u002Frendering, investigating user timing marks.","microsoft\u002Fvscode","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M6998NK2WED5M549DX06VBCH",195270,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fcpu-profile-analysis\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"cpu profile analysis","CPU","analysis","profile","sampling","when","profiles","trees","rendering","timing","paths","layout","call","files","performance","analyze","data","analyzing","devtools","code","chrome","2026-11-10 10:12:30",false,"---\nname: cpu-profile-analysis\ndescription: \"Analyze V8\u002FChrome 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\u002Fpaint\u002Frendering, investigating user timing marks.\"\n---\n\n# Analyze Performance Profiles\n\nAnalyze `.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.\n\n## When to Use\n- User provides a `.cpuprofile` or `Trace-*.json` file and wants to understand performance\n- Investigating why one code path is slower than another\n- Finding what functions consume the most time\n- Comparing \"before\u002Fafter\" or \"old\u002Fnew\" implementations in a single profile\n- Investigating layout thrashing, long tasks, or rendering bottlenecks (trace files)\n- Analyzing VS Code user timing marks like `code\u002FdidResolveTextFileEditorModel` (trace files)\n- Understanding multi-process behavior (Browser, Renderer, GPU processes in trace files)\n\n## Detecting File Type\n\n- **`.cpuprofile`**: Top-level JSON with `nodes`, `samples`, `timeDeltas` keys. Created by the VS Code profiler.\n- **`Trace-*.json`**: Top-level JSON with `traceEvents` array (and optional `metadata`). Created by Chrome\u002FElectron DevTools (Performance tab). These are richer than `.cpuprofile` -- they contain CPU samples, layout\u002Fpaint events, user timing marks, GC events, input events, and multi-process data.\n\n## Key Concepts\n\n- **Sampling profiler**: The profiler periodically snapshots the call stack. Not every function appears -- only those on the stack when the profiler sampled. Don't expect exact function names; look for patterns and nearby activity.\n- **Self time**: Time spent in the function itself (the leaf\u002Finnermost frame).\n- **Total time**: Time the function was anywhere on the stack (includes callees).\n- **Idle samples**: Frames labeled `(idle)`, `(program)`, or `(garbage collector)` represent no user code running.\n\n---\n\n## Part 1: `.cpuprofile` Files\n\n### Profile Format\n\nA `.cpuprofile` is JSON with these top-level keys:\n- `nodes`: Array of call frame nodes forming a tree (each has `id`, `callFrame`, `children`)\n- `samples`: Array of node IDs -- one per profiler tick, referencing the leaf (innermost) frame\n- `timeDeltas`: Array of microsecond deltas between consecutive samples\n- `startTime` \u002F `endTime`: Absolute timestamps in microseconds\n- `$vscode`: Optional VS Code metadata\n\n### Procedure\n\n### 1. Check File Size and Parse\n\nProfile and trace files can exceed V8's string limit (~512MB). Always check the file size first and choose the right parsing strategy:\n\n```javascript\nimport { readFileSync, statSync } from 'fs';\n\nconst stat = statSync(profilePath);\nconst sizeMB = stat.size \u002F (1024 * 1024);\nconsole.log(`File size: ${sizeMB.toFixed(0)}MB`);\n\nlet data;\nif (sizeMB \u003C 400) {\n    \u002F\u002F Small enough for JSON.parse\n    data = JSON.parse(readFileSync(profilePath, 'utf8'));\n} else {\n    \u002F\u002F Too large -- use Buffer-based extraction (see \"Handling Huge Files\" section)\n    data = parseProfileFromBuffer(readFileSync(profilePath));\n}\n```\n\nFor files under ~400MB, `JSON.parse(readFileSync(..., 'utf8'))` works fine. For larger files, see the **Handling Huge Files** section below.\n\n### 2. Reformat the File (small files only)\n\nProfiles are often single-line JSON. Reformat for inspection (only if small enough):\n\n```javascript\nif (sizeMB \u003C 400) {\n    const data = JSON.parse(fs.readFileSync(profilePath, 'utf8'));\n    fs.writeFileSync(profilePath, JSON.stringify(data, null, 2));\n}\n```\n\n### 3. Build Data Structures\n\nWrite a Node.js analysis script. Build these structures:\n\n```javascript\n\u002F\u002F Node lookup\nconst nodeMap = new Map();       \u002F\u002F id -> node\nconst parentMap = new Map();     \u002F\u002F id -> parent id\n\n\u002F\u002F Absolute timestamps from deltas\nconst timestamps = [data.startTime];\nfor (let i = 0; i \u003C data.timeDeltas.length; i++) {\n    timestamps.push(timestamps[i] + data.timeDeltas[i]);\n}\n\n\u002F\u002F Stack walker (leaf to root)\nfunction getStack(sampleNodeId) {\n    const stack = [];\n    let id = sampleNodeId;\n    while (id !== undefined) {\n        const node = nodeMap.get(id);\n        if (node) stack.push(node.callFrame.functionName);\n        id = parentMap.get(id);\n    }\n    return stack; \u002F\u002F [leaf, ..., root]\n}\n```\n\n### 4. Identify Activity Regions\n\nSplit 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.\n\n**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.\n\n### 5. Measure Timing Between Milestones\n\nFor questions like \"time from X to Y\":\n1. Find the first non-idle sample containing a marker for X on the stack\n2. Find the first sample containing a marker for Y on the stack\n3. The gap in absolute timestamps is the approximate duration\n4. List all non-idle samples between these points to see what work happens in the gap\n\n### 6. Compare Code Paths\n\nWhen comparing two implementations:\n1. Identify the activity region for each\n2. For each region, compute self-time per function (time attributed to the leaf frame)\n3. Sort by self-time descending to find the top cost centers\n4. Show the first N non-idle stacks in each region to visualize the startup sequence\n\n### 7. Report Findings\n\nPresent results as:\n- **Timeline**: When each activity region occurred relative to profile start\n- **Duration**: How long each region lasted\n- **Top functions by self-time**: Where CPU time was actually spent\n- **Comparison table**: Side-by-side metrics when comparing paths\n- **Stack traces**: Key sample stacks showing the critical path\n\n---\n\n## Part 2: DevTools Trace Files (`Trace-*.json`)\n\nDevTools traces are the future of perf tracing for VS Code. They are created from the built-in Electron\u002FChrome DevTools Performance tab and contain far more information than `.cpuprofile` files.\n\n### Trace Format\n\nA `Trace-*.json` file has these top-level keys:\n- `traceEvents`: Array of trace event objects (hundreds of thousands of entries)\n- `metadata`: Object with `source`, `startTime`, `dataOrigin`, and optional DevTools state (breadcrumbs, annotations)\n\n### Trace Event Structure\n\nEach event in `traceEvents` follows the Chrome Trace Event Format:\n\n```javascript\n{\n  \"pid\": 3406,           \u002F\u002F Process ID\n  \"tid\": 7534980,        \u002F\u002F Thread ID\n  \"ts\": 200420830729,    \u002F\u002F Timestamp in microseconds\n  \"ph\": \"X\",             \u002F\u002F Phase (event type)\n  \"cat\": \"devtools.timeline\",  \u002F\u002F Category\n  \"name\": \"EventDispatch\",     \u002F\u002F Event name\n  \"dur\": 9,              \u002F\u002F Duration in microseconds (for complete events)\n  \"tdur\": 8,             \u002F\u002F Thread duration (excludes time thread was suspended)\n  \"args\": { ... },       \u002F\u002F Event-specific arguments\n  \"tts\": 7078808         \u002F\u002F Thread timestamp\n}\n```\n\n### Phase Types (`ph`)\n\n| Phase | Name | Meaning |\n|-------|------|---------|\n| `X` | Complete | Event with duration (`dur` field). Most common. |\n| `B` | Begin | Start of a duration event (paired with `E`). |\n| `E` | End | End of a duration event (paired with `B`). |\n| `I` | Instant | Point-in-time event (no duration). |\n| `P` | Sample | CPU profiler sample. |\n| `R` | Mark | Navigation timing mark. |\n| `M` | Metadata | Process\u002Fthread name metadata. |\n| `N` | Object Created | Object lifecycle tracking. |\n| `D` | Object Destroyed | Object lifecycle tracking. |\n| `s` | Flow Start | Async flow connection start. |\n| `f` | Flow End | Async flow connection end. |\n| `b` | Async Begin | Async event begin. |\n| `e` | Async End | Async event end. |\n| `n` | Async Instant | Async event instant. |\n\n### Key Categories and What They Contain\n\n| Category | What it captures |\n|----------|-----------------|\n| `disabled-by-default-devtools.timeline` | `RunTask`, `EvaluateScript`, `TracingStartedInBrowser` -- core task scheduling |\n| `devtools.timeline` | `FunctionCall`, `EventDispatch`, `TimerInstall\u002FFire`, `PrePaint`, `Paint` -- main thread activity |\n| `blink.user_timing` | VS Code performance marks (e.g. `code\u002FwillResolveTextFileEditorModel`, `code\u002FdidResolveTextFileEditorModel`) |\n| `blink,devtools.timeline` | `UpdateLayoutTree`, `HitTest`, `IntersectionObserver`, `ParseAuthorStyleSheet` -- layout\u002Frendering |\n| `disabled-by-default-v8.cpu_profiler` | `Profile`, `ProfileChunk` -- embedded CPU profile data (same as `.cpuprofile` but chunked) |\n| `v8` | `v8.callFunction`, `v8.newInstance`, `V8.DeoptimizeCode` -- V8 engine events |\n| `v8,devtools.timeline` | `v8.compile` -- script compilation |\n| `devtools.timeline,v8` | `MinorGC`, `MajorGC` -- garbage collection |\n| `cppgc` | C++ GC events (Blink garbage collection) |\n| `loading` | `LayoutShift`, `URLLoader` -- resource loading and layout shifts |\n| `cc,benchmark,disabled-by-default-devtools.timeline.frame` | Frame pipeline events (`PipelineReporter`, `Commit`, etc.) |\n| `__metadata` | `process_name`, `thread_name` -- process\u002Fthread identification |\n\n### Processes and Threads\n\nTrace files contain events from multiple processes:\n\n| Process | Role | Key Thread |\n|---------|------|------------|\n| **Renderer** (pid varies) | VS Code's renderer process -- where JS runs | `CrRendererMain` (main thread) |\n| **Browser** (pid varies) | Electron's main\u002Fbrowser process | `CrBrowserMain` |\n| **GPU Process** (pid varies) | GPU compositing and rendering | `CrGpuMain`, `VizCompositorThread` |\n\nIdentify processes\u002Fthreads via metadata events:\n```javascript\nconst procNames = events.filter(e => e.name === 'process_name');\n\u002F\u002F => [{args: {name: 'Renderer'}, pid: 3406}, {args: {name: 'Browser'}, pid: 3348}, ...]\n\nconst threadNames = events.filter(e => e.name === 'thread_name');\n\u002F\u002F => [{args: {name: 'CrRendererMain'}, pid: 3406, tid: 7534980}, ...]\n```\n\nFor VS Code perf analysis, focus on the **Renderer process, CrRendererMain thread** -- this is where JavaScript execution, layout, and painting happen.\n\n### Procedure\n\n#### 1. Check File Size and Parse\n\nTrace files are typically 50-200MB but can exceed V8's string limit (~512MB). Always check first:\n\n```javascript\nimport { readFileSync, statSync } from 'fs';\n\nconst stat = statSync(tracePath);\nconst sizeMB = stat.size \u002F (1024 * 1024);\nconsole.log(`File size: ${sizeMB.toFixed(0)}MB`);\n\nlet data;\nif (sizeMB \u003C 400) {\n    data = JSON.parse(readFileSync(tracePath, 'utf8'));\n} else {\n    \u002F\u002F Too large -- use Buffer-based extraction (see \"Handling Huge Files\" section)\n    data = parseTraceFromBuffer(readFileSync(tracePath));\n}\nconst events = data.traceEvents;\n```\n\n#### 2. Reformat the File (small files only)\n\nFor small trace files, reformat for inspection:\n```javascript\nif (sizeMB \u003C 400) {\n    fs.writeFileSync(tracePath, JSON.stringify(data, null, 2));\n}\n```\n\n#### 3. Build Data Structures\n\n```javascript\nconst data = JSON.parse(fs.readFileSync(tracePath, 'utf8'));\nconst events = data.traceEvents;\n\n\u002F\u002F Identify Renderer main thread\nconst rendererPid = events.find(e => e.name === 'process_name' && e.args?.name === 'Renderer')?.pid;\nconst mainTid = events.find(e => e.name === 'thread_name' && e.pid === rendererPid && e.args?.name === 'CrRendererMain')?.tid;\n\n\u002F\u002F Filter to main thread events for most analysis\nconst mainEvents = events.filter(e => e.pid === rendererPid && e.tid === mainTid);\n```\n\n#### 4. Analyze User Timing Marks\n\nVS Code emits `performance.mark()` calls that appear as `blink.user_timing` events. These are the most direct way to measure VS Code-specific milestones:\n\n```javascript\nconst userTimings = events.filter(e => e.cat?.includes('blink.user_timing') && !e.cat.includes('rail'));\n\u002F\u002F Each has: name (e.g. 'code\u002FdidResolveTextFileEditorModel'), ts (microseconds), args.data.startTime (ms from navigation)\n```\n\n#### 5. Analyze Long Tasks\n\nFind expensive tasks on the main thread:\n```javascript\nconst longTasks = mainEvents\n    .filter(e => e.name === 'RunTask' && e.ph === 'X' && e.dur > 50000) \u002F\u002F > 50ms\n    .sort((a, b) => b.dur - a.dur);\n```\n\n#### 6. Analyze Function Calls\n\n`FunctionCall` events include source location info:\n```javascript\nconst funcCalls = mainEvents\n    .filter(e => e.name === 'FunctionCall' && e.dur > 10000) \u002F\u002F > 10ms\n    .sort((a, b) => b.dur - a.dur);\n\u002F\u002F args.data contains: functionName, url, lineNumber, columnNumber, scriptId\n```\n\n#### 7. Analyze Layout and Rendering\n\nFind layout thrashing and expensive paints:\n```javascript\nconst layoutEvents = mainEvents.filter(e =>\n    e.name === 'UpdateLayoutTree' || e.name === 'Layout' ||\n    e.name === 'PrePaint' || e.name === 'Paint'\n);\n\u002F\u002F UpdateLayoutTree.args.elementCount tells you how many elements were restyled\n```\n\n#### 8. Extract Embedded CPU Profile\n\nTrace files contain the full CPU profile as `ProfileChunk` events. Reconstruct it:\n```javascript\nconst profileEvent = events.find(e => e.name === 'Profile' && e.pid === rendererPid);\nconst chunks = events.filter(e => e.name === 'ProfileChunk' && e.pid === rendererPid && e.id === profileEvent.id);\n\n\u002F\u002F Each chunk's args.data.cpuProfile contains: {nodes: [...], samples: [...]}\n\u002F\u002F Each chunk's args.data.timeDeltas contains sample timing\n\u002F\u002F Merge all chunks to reconstruct a full cpuprofile-like structure\nconst allNodes = [];\nconst allSamples = [];\nconst allDeltas = [];\nfor (const chunk of chunks) {\n    const cp = chunk.args.data.cpuProfile;\n    if (cp.nodes) allNodes.push(...cp.nodes);\n    if (cp.samples) allSamples.push(...cp.samples);\n    if (chunk.args.data.timeDeltas) allDeltas.push(...chunk.args.data.timeDeltas);\n}\n\u002F\u002F Now analyze allNodes\u002FallSamples\u002FallDeltas using the same approach as .cpuprofile\n```\n\n#### 9. Analyze GC Pressure\n\n```javascript\nconst gcEvents = mainEvents.filter(e => e.name === 'MinorGC' || e.name === 'MajorGC');\nconst totalGcTime = gcEvents.reduce((sum, e) => sum + (e.dur || 0), 0);\n\u002F\u002F Also check cppgc events for Blink GC\nconst cppgcEvents = events.filter(e => e.cat?.includes('cppgc'));\n```\n\n#### 10. Analyze Input Latency\n\n```javascript\nconst dispatches = mainEvents.filter(e => e.name === 'EventDispatch');\n\u002F\u002F args.data.type tells you the event type: 'click', 'keydown', 'mousedown', etc.\n\u002F\u002F dur tells you how long the handler took\nconst longHandlers = dispatches.filter(e => e.dur > 50000).sort((a, b) => b.dur - a.dur);\n```\n\n#### 11. Report Findings\n\nPresent results as:\n- **Timeline**: When each activity region occurred relative to trace start\n- **User timing marks**: VS Code milestone events and their timestamps\n- **Long tasks**: Tasks > 50ms that block the main thread\n- **Top functions by duration**: Where CPU time was spent, with source locations\n- **Layout\u002Frendering**: Expensive style recalculations and paints\n- **GC pressure**: Total GC time and frequency\n- **Input latency**: Slow event handlers that degrade responsiveness\n- **Process breakdown**: What work happened in Browser vs Renderer vs GPU\n\n---\n\n## Handling Huge Files\n\nWhen 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`).\n\n**Key principle**: Read the file as a Buffer, locate JSON array\u002Fobject boundaries by scanning bytes, extract individual sections as sub-buffers that are small enough for `JSON.parse`, then assemble the result.\n\nAlways run analysis scripts with extra memory: `node --max-old-space-size=16384 script.mjs`\n\n### Buffer-based Parsing for `.cpuprofile`\n\nA `.cpuprofile` has top-level keys `nodes`, `samples`, `timeDeltas`, `startTime`, `endTime`. Extract each section from the buffer:\n\n```javascript\nimport { readFileSync, statSync } from 'fs';\n\nfunction parseProfileFromBuffer(buf) {\n    \u002F\u002F Helper: find the array value for a given key, return parsed array\n    function extractArray(key) {\n        const keyBuf = Buffer.from(`\"${key}\":[`);\n        const pos = buf.indexOf(keyBuf);\n        if (pos === -1) throw new Error(`${key} not found`);\n        const arrayStart = pos + keyBuf.length;\n        \u002F\u002F Find matching ']' -- arrays of numbers have no nested brackets\n        const arrayEnd = buf.indexOf(0x5D, arrayStart); \u002F\u002F 0x5D = ']'\n        return JSON.parse('[' + buf.subarray(arrayStart, arrayEnd).toString('utf8') + ']');\n    }\n\n    \u002F\u002F Helper: find a scalar value for a given key\n    function extractScalar(key) {\n        const keyBuf = Buffer.from(`\"${key}\":`);\n        const pos = buf.indexOf(keyBuf);\n        if (pos === -1) throw new Error(`${key} not found`);\n        const valueStart = pos + keyBuf.length;\n        \u002F\u002F Scan to next comma or closing brace\n        let end = valueStart;\n        while (end \u003C buf.length && buf[end] !== 0x2C && buf[end] !== 0x7D) end++;\n        return JSON.parse(buf.subarray(valueStart, end).toString('utf8'));\n    }\n\n    \u002F\u002F Extract the nodes array -- contains objects, so we need bracket matching\n    function extractNodesArray() {\n        const keyBuf = Buffer.from('\"nodes\":[');\n        const pos = buf.indexOf(keyBuf);\n        if (pos === -1) throw new Error('nodes not found');\n        const start = pos + keyBuf.length - 1; \u002F\u002F include '['\n        let depth = 0, end = -1;\n        for (let i = start; i \u003C buf.length; i++) {\n            if (buf[i] === 0x5B) depth++;\n            else if (buf[i] === 0x5D) { depth--; if (depth === 0) { end = i + 1; break; } }\n            \u002F\u002F Skip strings to avoid counting brackets inside them\n            if (buf[i] === 0x22) { i++; while (i \u003C buf.length) { if (buf[i] === 0x5C) i++; else if (buf[i] === 0x22) break; i++; } }\n        }\n        if (end === -1) throw new Error('nodes array end not found');\n        return JSON.parse(buf.subarray(start, end).toString('utf8'));\n    }\n\n    const nodes = extractNodesArray();\n    const samples = extractArray('samples');\n    const timeDeltas = extractArray('timeDeltas');\n    const startTime = extractScalar('startTime');\n    const endTime = extractScalar('endTime');\n\n    return { nodes, samples, timeDeltas, startTime, endTime };\n}\n```\n\n### Buffer-based Parsing for `Trace-*.json`\n\nTrace 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:\n\n```javascript\nimport { readFileSync } from 'fs';\n\nfunction parseTraceFromBuffer(buf) {\n    \u002F\u002F 1. Extract metadata (small, near the top of the file)\n    let metadata = {};\n    const metaKeyBuf = Buffer.from('\"metadata\":{');\n    const metaPos = buf.indexOf(metaKeyBuf);\n    if (metaPos !== -1) {\n        const metaStart = metaPos + metaKeyBuf.length - 1; \u002F\u002F include '{'\n        let depth = 0, metaEnd = -1;\n        for (let i = metaStart; i \u003C buf.length; i++) {\n            if (buf[i] === 0x7B) depth++;\n            else if (buf[i] === 0x7D) { depth--; if (depth === 0) { metaEnd = i + 1; break; } }\n            if (buf[i] === 0x22) { i++; while (i \u003C buf.length) { if (buf[i] === 0x5C) i++; else if (buf[i] === 0x22) break; i++; } }\n        }\n        if (metaEnd !== -1) {\n            metadata = JSON.parse(buf.subarray(metaStart, metaEnd).toString('utf8'));\n        }\n    }\n\n    \u002F\u002F 2. Extract traceEvents by parsing in chunks\n    \u002F\u002F    Each event is a JSON object {...}. Scan for object boundaries.\n    const eventsKeyBuf = Buffer.from('\"traceEvents\":[');\n    const eventsPos = buf.indexOf(eventsKeyBuf);\n    if (eventsPos === -1) throw new Error('traceEvents not found');\n    const arrayStart = eventsPos + eventsKeyBuf.length;\n\n    const traceEvents = [];\n    let i = arrayStart;\n    while (i \u003C buf.length) {\n        \u002F\u002F Skip whitespace and commas\n        while (i \u003C buf.length && (buf[i] === 0x20 || buf[i] === 0x0A || buf[i] === 0x0D || buf[i] === 0x09 || buf[i] === 0x2C)) i++;\n        if (i >= buf.length || buf[i] === 0x5D) break; \u002F\u002F end of array\n\n        if (buf[i] !== 0x7B) { i++; continue; } \u002F\u002F expect '{'\n\n        \u002F\u002F Find matching '}'\n        let depth = 0, objEnd = -1;\n        for (let j = i; j \u003C buf.length; j++) {\n            if (buf[j] === 0x7B) depth++;\n            else if (buf[j] === 0x7D) { depth--; if (depth === 0) { objEnd = j + 1; break; } }\n            if (buf[j] === 0x22) { j++; while (j \u003C buf.length) { if (buf[j] === 0x5C) j++; else if (buf[j] === 0x22) break; j++; } }\n        }\n        if (objEnd === -1) break;\n\n        \u002F\u002F Parse this individual event object -- each is small enough for JSON.parse\n        traceEvents.push(JSON.parse(buf.subarray(i, objEnd).toString('utf8')));\n        i = objEnd;\n    }\n\n    return { metadata, traceEvents };\n}\n```\n\n### When to Use Buffer Parsing\n\n| File size | Approach |\n|-----------|----------|\n| \u003C 400MB | `JSON.parse(readFileSync(path, 'utf8'))` is fine |\n| 400MB - 1GB | Use Buffer-based extraction functions above |\n| > 1GB | Use Buffer-based extraction + `--max-old-space-size=16384` |\n\n### Tips for Large File Analysis\n\n- Always run with `node --max-old-space-size=16384` to give Node.js enough heap space.\n- For trace files, consider filtering events during parsing (e.g. skip categories you don't need) to reduce memory.\n- The Buffer approach reads the entire file into memory as bytes (which is fine -- Buffer doesn't have the ~512MB string limit). Individual `JSON.parse` calls operate on small sub-buffers.\n- When reformatting would create a file too large to write back, skip reformatting and work directly with the parsed data structures.\n- If you only need specific event types from a huge trace, add a filter callback to the parsing loop to avoid allocating objects you'll discard.\n\n## Tips\n\n- Timestamps in both formats are microseconds. Divide by 1000 for milliseconds.\n- For bundled\u002Fminified code (e.g. `extension.js`), function names may be mangled. Use line numbers from `callFrame.lineNumber` to cross-reference with source maps.\n- Filter out idle\u002Fprogram\u002FGC samples when measuring active CPU work.\n- When the user asks about a gap, check if it's truly idle (event loop waiting) vs. active work in unrelated code.\n- Clean up any analysis scripts you create when done.\n- Trace files are large (50-200MB). Always filter to the relevant process\u002Fthread before analysis to reduce memory and noise.\n- When a trace file contains embedded `ProfileChunk` events, prefer analyzing those over asking for a separate `.cpuprofile` -- the data is equivalent but already correlated with other trace events.\n- Use `args.data.url` in `FunctionCall` and `EvaluateScript` events to map back to VS Code source files (paths like `vscode-file:\u002F\u002Fvscode-app\u002FUsers\u002F...\u002Fout\u002Fvs\u002F...`).\n- The `dur` field is wall-clock duration; `tdur` is thread-time duration. The difference reveals time the thread was suspended (e.g. waiting for I\u002FO or preempted).\n","# Analyze Performance Profiles\n\nAnalyze `.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.\n\n## When to Use\n- User provides a `.cpuprofile` or `Trace-*.json` file and wants to understand performance\n- Investigating why one code path is slower than another\n- Finding what functions consume the most time\n- Comparing \"before\u002Fafter\" or \"old\u002Fnew\" implementations in a single profile\n- Investigating layout thrashing, long tasks, or rendering bottlenecks (trace files)\n- Analyzing VS Code user timing marks like `code\u002FdidResolveTextFileEditorModel` (trace files)\n- Understanding multi-process behavior (Browser, Renderer, GPU processes in trace files)\n\n## Detecting File Type\n\n- **`.cpuprofile`**: Top-level JSON with `nodes`, `samples`, `timeDeltas` keys. Created by the VS Code profiler.\n- **`Trace-*.json`**: Top-level JSON with `traceEvents` array (and optional `metadata`). Created by Chrome\u002FElectron DevTools (Performance tab). These are richer than `.cpuprofile` -- they contain CPU samples, layout\u002Fpaint events, user timing marks, GC events, input events, and multi-process data.\n\n## Key Concepts\n\n- **Sampling profiler**: The profiler periodically snapshots the call stack. Not every function appears -- only those on the stack when the profiler sampled. Don't expect exact function names; look for patterns and nearby activity.\n- **Self time**: Time spent in the function itself (the leaf\u002Finnermost frame).\n- **Total time**: Time the function was anywhere on the stack (includes callees).\n- **Idle samples**: Frames labeled `(idle)`, `(program)`, or `(garbage collector)` represent no user code running.\n\n---\n\n## Part 1: `.cpuprofile` Files\n\n### Profile Format\n\nA `.cpuprofile` is JSON with these top-level keys:\n- `nodes`: Array of call frame nodes forming a tree (each has `id`, `callFrame`, `children`)\n- `samples`: Array of node IDs -- one per profiler tick, referencing the leaf (innermost) frame\n- `timeDeltas`: Array of microsecond deltas between consecutive samples\n- `startTime` \u002F `endTime`: Absolute timestamps in microseconds\n- `$vscode`: Optional VS Code metadata\n\n### Procedure\n\n### 1. Check File Size and Parse\n\nProfile and trace files can exceed V8's string limit (~512MB). Always check the file size first and choose the right parsing strategy:\n\n```javascript\nimport { readFileSync, statSync } from 'fs';\n\nconst stat = statSync(profilePath);\nconst sizeMB = stat.size \u002F (1024 * 1024);\nconsole.log(`File size: ${sizeMB.toFixed(0)}MB`);\n\nlet data;\nif (sizeMB \u003C 400) {\n    \u002F\u002F Small enough for JSON.parse\n    data = JSON.parse(readFileSync(profilePath, 'utf8'));\n} else {\n    \u002F\u002F Too large -- use Buffer-based extraction (see \"Handling Huge Files\" section)\n    data = parseProfileFromBuffer(readFileSync(profilePath));\n}\n```\n\nFor files under ~400MB, `JSON.parse(readFileSync(..., 'utf8'))` works fine. For larger files, see the **Handling Huge Files** section below.\n\n### 2. Reformat the File (small files only)\n\nProfiles are often single-line JSON. Reformat for inspection (only if small enough):\n\n```javascript\nif (sizeMB \u003C 400) {\n    const data = JSON.parse(fs.readFileSync(profilePath, 'utf8'));\n    fs.writeFileSync(profilePath, JSON.stringify(data, null, 2));\n}\n```\n\n### 3. Build Data Structures\n\nWrite a Node.js analysis script. Build these structures:\n\n```javascript\n\u002F\u002F Node lookup\nconst nodeMap = new Map();       \u002F\u002F id -> node\nconst parentMap = new Map();     \u002F\u002F id -> parent id\n\n\u002F\u002F Absolute timestamps from deltas\nconst timestamps = [data.startTime];\nfor (let i = 0; i \u003C data.timeDeltas.length; i++) {\n    timestamps.push(timestamps[i] + data.timeDeltas[i]);\n}\n\n\u002F\u002F Stack walker (leaf to root)\nfunction getStack(sampleNodeId) {\n    const stack = [];\n    let id = sampleNodeId;\n    while (id !== undefined) {\n        const node = nodeMap.get(id);\n        if (node) stack.push(node.callFrame.functionName);\n        id = parentMap.get(id);\n    }\n    return stack; \u002F\u002F [leaf, ..., root]\n}\n```\n\n### 4. Identify Activity Regions\n\nSplit 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.\n\n**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.\n\n### 5. Measure Timing Between Milestones\n\nFor questions like \"time from X to Y\":\n1. Find the first non-idle sample containing a marker for X on the stack\n2. Find the first sample containing a marker for Y on the stack\n3. The gap in absolute timestamps is the approximate duration\n4. List all non-idle samples between these points to see what work happens in the gap\n\n### 6. Compare Code Paths\n\nWhen comparing two implementations:\n1. Identify the activity region for each\n2. For each region, compute self-time per function (time attributed to the leaf frame)\n3. Sort by self-time descending to find the top cost centers\n4. Show the first N non-idle stacks in each region to visualize the startup sequence\n\n### 7. Report Findings\n\nPresent results as:\n- **Timeline**: When each activity region occurred relative to profile start\n- **Duration**: How long each region lasted\n- **Top functions by self-time**: Where CPU time was actually spent\n- **Comparison table**: Side-by-side metrics when comparing paths\n- **Stack traces**: Key sample stacks showing the critical path\n\n---\n\n## Part 2: DevTools Trace Files (`Trace-*.json`)\n\nDevTools traces are the future of perf tracing for VS Code. They are created from the built-in Electron\u002FChrome DevTools Performance tab and contain far more information than `.cpuprofile` files.\n\n### Trace Format\n\nA `Trace-*.json` file has these top-level keys:\n- `traceEvents`: Array of trace event objects (hundreds of thousands of entries)\n- `metadata`: Object with `source`, `startTime`, `dataOrigin`, and optional DevTools state (breadcrumbs, annotations)\n\n### Trace Event Structure\n\nEach event in `traceEvents` follows the Chrome Trace Event Format:\n\n```javascript\n{\n  \"pid\": 3406,           \u002F\u002F Process ID\n  \"tid\": 7534980,        \u002F\u002F Thread ID\n  \"ts\": 200420830729,    \u002F\u002F Timestamp in microseconds\n  \"ph\": \"X\",             \u002F\u002F Phase (event type)\n  \"cat\": \"devtools.timeline\",  \u002F\u002F Category\n  \"name\": \"EventDispatch\",     \u002F\u002F Event name\n  \"dur\": 9,              \u002F\u002F Duration in microseconds (for complete events)\n  \"tdur\": 8,             \u002F\u002F Thread duration (excludes time thread was suspended)\n  \"args\": { ... },       \u002F\u002F Event-specific arguments\n  \"tts\": 7078808         \u002F\u002F Thread timestamp\n}\n```\n\n### Phase Types (`ph`)\n\n| Phase | Name | Meaning |\n|-------|------|---------|\n| `X` | Complete | Event with duration (`dur` field). Most common. |\n| `B` | Begin | Start of a duration event (paired with `E`). |\n| `E` | End | End of a duration event (paired with `B`). |\n| `I` | Instant | Point-in-time event (no duration). |\n| `P` | Sample | CPU profiler sample. |\n| `R` | Mark | Navigation timing mark. |\n| `M` | Metadata | Process\u002Fthread name metadata. |\n| `N` | Object Created | Object lifecycle tracking. |\n| `D` | Object Destroyed | Object lifecycle tracking. |\n| `s` | Flow Start | Async flow connection start. |\n| `f` | Flow End | Async flow connection end. |\n| `b` | Async Begin | Async event begin. |\n| `e` | Async End | Async event end. |\n| `n` | Async Instant | Async event instant. |\n\n### Key Categories and What They Contain\n\n| Category | What it captures |\n|----------|-----------------|\n| `disabled-by-default-devtools.timeline` | `RunTask`, `EvaluateScript`, `TracingStartedInBrowser` -- core task scheduling |\n| `devtools.timeline` | `FunctionCall`, `EventDispatch`, `TimerInstall\u002FFire`, `PrePaint`, `Paint` -- main thread activity |\n| `blink.user_timing` | VS Code performance marks (e.g. `code\u002FwillResolveTextFileEditorModel`, `code\u002FdidResolveTextFileEditorModel`) |\n| `blink,devtools.timeline` | `UpdateLayoutTree`, `HitTest`, `IntersectionObserver`, `ParseAuthorStyleSheet` -- layout\u002Frendering |\n| `disabled-by-default-v8.cpu_profiler` | `Profile`, `ProfileChunk` -- embedded CPU profile data (same as `.cpuprofile` but chunked) |\n| `v8` | `v8.callFunction`, `v8.newInstance`, `V8.DeoptimizeCode` -- V8 engine events |\n| `v8,devtools.timeline` | `v8.compile` -- script compilation |\n| `devtools.timeline,v8` | `MinorGC`, `MajorGC` -- garbage collection |\n| `cppgc` | C++ GC events (Blink garbage collection) |\n| `loading` | `LayoutShift`, `URLLoader` -- resource loading and layout shifts |\n| `cc,benchmark,disabled-by-default-devtools.timeline.frame` | Frame pipeline events (`PipelineReporter`, `Commit`, etc.) |\n| `__metadata` | `process_name`, `thread_name` -- process\u002Fthread identification |\n\n### Processes and Threads\n\nTrace files contain events from multiple processes:\n\n| Process | Role | Key Thread |\n|---------|------|------------|\n| **Renderer** (pid varies) | VS Code's renderer process -- where JS runs | `CrRendererMain` (main thread) |\n| **Browser** (pid varies) | Electron's main\u002Fbrowser process | `CrBrowserMain` |\n| **GPU Process** (pid varies) | GPU compositing and rendering | `CrGpuMain`, `VizCompositorThread` |\n\nIdentify processes\u002Fthreads via metadata events:\n```javascript\nconst procNames = events.filter(e => e.name === 'process_name');\n\u002F\u002F => [{args: {name: 'Renderer'}, pid: 3406}, {args: {name: 'Browser'}, pid: 3348}, ...]\n\nconst threadNames = events.filter(e => e.name === 'thread_name');\n\u002F\u002F => [{args: {name: 'CrRendererMain'}, pid: 3406, tid: 7534980}, ...]\n```\n\nFor VS Code perf analysis, focus on the **Renderer process, CrRendererMain thread** -- this is where JavaScript execution, layout, and painting happen.\n\n### Procedure\n\n#### 1. Check File Size and Parse\n\nTrace files are typically 50-200MB but can exceed V8's string limit (~512MB). Always check first:\n\n```javascript\nimport { readFileSync, statSync } from 'fs';\n\nconst stat = statSync(tracePath);\nconst sizeMB = stat.size \u002F (1024 * 1024);\nconsole.log(`File size: ${sizeMB.toFixed(0)}MB`);\n\nlet data;\nif (sizeMB \u003C 400) {\n    data = JSON.parse(readFileSync(tracePath, 'utf8'));\n} else {\n    \u002F\u002F Too large -- use Buffer-based extraction (see \"Handling Huge Files\" section)\n    data = parseTraceFromBuffer(readFileSync(tracePath));\n}\nconst events = data.traceEvents;\n```\n\n#### 2. Reformat the File (small files only)\n\nFor small trace files, reformat for inspection:\n```javascript\nif (sizeMB \u003C 400) {\n    fs.writeFileSync(tracePath, JSON.stringify(data, null, 2));\n}\n```\n\n#### 3. Build Data Structures\n\n```javascript\nconst data = JSON.parse(fs.readFileSync(tracePath, 'utf8'));\nconst events = data.traceEvents;\n\n\u002F\u002F Identify Renderer main thread\nconst rendererPid = events.find(e => e.name === 'process_name' && e.args?.name === 'Renderer')?.pid;\nconst mainTid = events.find(e => e.name === 'thread_name' && e.pid === rendererPid && e.args?.name === 'CrRendererMain')?.tid;\n\n\u002F\u002F Filter to main thread events for most analysis\nconst mainEvents = events.filter(e => e.pid === rendererPid && e.tid === mainTid);\n```\n\n#### 4. Analyze User Timing Marks\n\nVS Code emits `performance.mark()` calls that appear as `blink.user_timing` events. These are the most direct way to measure VS Code-specific milestones:\n\n```javascript\nconst userTimings = events.filter(e => e.cat?.includes('blink.user_timing') && !e.cat.includes('rail'));\n\u002F\u002F Each has: name (e.g. 'code\u002FdidResolveTextFileEditorModel'), ts (microseconds), args.data.startTime (ms from navigation)\n```\n\n#### 5. Analyze Long Tasks\n\nFind expensive tasks on the main thread:\n```javascript\nconst longTasks = mainEvents\n    .filter(e => e.name === 'RunTask' && e.ph === 'X' && e.dur > 50000) \u002F\u002F > 50ms\n    .sort((a, b) => b.dur - a.dur);\n```\n\n#### 6. Analyze Function Calls\n\n`FunctionCall` events include source location info:\n```javascript\nconst funcCalls = mainEvents\n    .filter(e => e.name === 'FunctionCall' && e.dur > 10000) \u002F\u002F > 10ms\n    .sort((a, b) => b.dur - a.dur);\n\u002F\u002F args.data contains: functionName, url, lineNumber, columnNumber, scriptId\n```\n\n#### 7. Analyze Layout and Rendering\n\nFind layout thrashing and expensive paints:\n```javascript\nconst layoutEvents = mainEvents.filter(e =>\n    e.name === 'UpdateLayoutTree' || e.name === 'Layout' ||\n    e.name === 'PrePaint' || e.name === 'Paint'\n);\n\u002F\u002F UpdateLayoutTree.args.elementCount tells you how many elements were restyled\n```\n\n#### 8. Extract Embedded CPU Profile\n\nTrace files contain the full CPU profile as `ProfileChunk` events. Reconstruct it:\n```javascript\nconst profileEvent = events.find(e => e.name === 'Profile' && e.pid === rendererPid);\nconst chunks = events.filter(e => e.name === 'ProfileChunk' && e.pid === rendererPid && e.id === profileEvent.id);\n\n\u002F\u002F Each chunk's args.data.cpuProfile contains: {nodes: [...], samples: [...]}\n\u002F\u002F Each chunk's args.data.timeDeltas contains sample timing\n\u002F\u002F Merge all chunks to reconstruct a full cpuprofile-like structure\nconst allNodes = [];\nconst allSamples = [];\nconst allDeltas = [];\nfor (const chunk of chunks) {\n    const cp = chunk.args.data.cpuProfile;\n    if (cp.nodes) allNodes.push(...cp.nodes);\n    if (cp.samples) allSamples.push(...cp.samples);\n    if (chunk.args.data.timeDeltas) allDeltas.push(...chunk.args.data.timeDeltas);\n}\n\u002F\u002F Now analyze allNodes\u002FallSamples\u002FallDeltas using the same approach as .cpuprofile\n```\n\n#### 9. Analyze GC Pressure\n\n```javascript\nconst gcEvents = mainEvents.filter(e => e.name === 'MinorGC' || e.name === 'MajorGC');\nconst totalGcTime = gcEvents.reduce((sum, e) => sum + (e.dur || 0), 0);\n\u002F\u002F Also check cppgc events for Blink GC\nconst cppgcEvents = events.filter(e => e.cat?.includes('cppgc'));\n```\n\n#### 10. Analyze Input Latency\n\n```javascript\nconst dispatches = mainEvents.filter(e => e.name === 'EventDispatch');\n\u002F\u002F args.data.type tells you the event type: 'click', 'keydown', 'mousedown', etc.\n\u002F\u002F dur tells you how long the handler took\nconst longHandlers = dispatches.filter(e => e.dur > 50000).sort((a, b) => b.dur - a.dur);\n```\n\n#### 11. Report Findings\n\nPresent results as:\n- **Timeline**: When each activity region occurred relative to trace start\n- **User timing marks**: VS Code milestone events and their timestamps\n- **Long tasks**: Tasks > 50ms that block the main thread\n- **Top functions by duration**: Where CPU time was spent, with source locations\n- **Layout\u002Frendering**: Expensive style recalculations and paints\n- **GC pressure**: Total GC time and frequency\n- **Input latency**: Slow event handlers that degrade responsiveness\n- **Process breakdown**: What work happened in Browser vs Renderer vs GPU\n\n---\n\n## Handling Huge Files\n\nWhen 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`).\n\n**Key principle**: Read the file as a Buffer, locate JSON array\u002Fobject boundaries by scanning bytes, extract individual sections as sub-buffers that are small enough for `JSON.parse`, then assemble the result.\n\nAlways run analysis scripts with extra memory: `node --max-old-space-size=16384 script.mjs`\n\n### Buffer-based Parsing for `.cpuprofile`\n\nA `.cpuprofile` has top-level keys `nodes`, `samples`, `timeDeltas`, `startTime`, `endTime`. Extract each section from the buffer:\n\n```javascript\nimport { readFileSync, statSync } from 'fs';\n\nfunction parseProfileFromBuffer(buf) {\n    \u002F\u002F Helper: find the array value for a given key, return parsed array\n    function extractArray(key) {\n        const keyBuf = Buffer.from(`\"${key}\":[`);\n        const pos = buf.indexOf(keyBuf);\n        if (pos === -1) throw new Error(`${key} not found`);\n        const arrayStart = pos + keyBuf.length;\n        \u002F\u002F Find matching ']' -- arrays of numbers have no nested brackets\n        const arrayEnd = buf.indexOf(0x5D, arrayStart); \u002F\u002F 0x5D = ']'\n        return JSON.parse('[' + buf.subarray(arrayStart, arrayEnd).toString('utf8') + ']');\n    }\n\n    \u002F\u002F Helper: find a scalar value for a given key\n    function extractScalar(key) {\n        const keyBuf = Buffer.from(`\"${key}\":`);\n        const pos = buf.indexOf(keyBuf);\n        if (pos === -1) throw new Error(`${key} not found`);\n        const valueStart = pos + keyBuf.length;\n        \u002F\u002F Scan to next comma or closing brace\n        let end = valueStart;\n        while (end \u003C buf.length && buf[end] !== 0x2C && buf[end] !== 0x7D) end++;\n        return JSON.parse(buf.subarray(valueStart, end).toString('utf8'));\n    }\n\n    \u002F\u002F Extract the nodes array -- contains objects, so we need bracket matching\n    function extractNodesArray() {\n        const keyBuf = Buffer.from('\"nodes\":[');\n        const pos = buf.indexOf(keyBuf);\n        if (pos === -1) throw new Error('nodes not found');\n        const start = pos + keyBuf.length - 1; \u002F\u002F include '['\n        let depth = 0, end = -1;\n        for (let i = start; i \u003C buf.length; i++) {\n            if (buf[i] === 0x5B) depth++;\n            else if (buf[i] === 0x5D) { depth--; if (depth === 0) { end = i + 1; break; } }\n            \u002F\u002F Skip strings to avoid counting brackets inside them\n            if (buf[i] === 0x22) { i++; while (i \u003C buf.length) { if (buf[i] === 0x5C) i++; else if (buf[i] === 0x22) break; i++; } }\n        }\n        if (end === -1) throw new Error('nodes array end not found');\n        return JSON.parse(buf.subarray(start, end).toString('utf8'));\n    }\n\n    const nodes = extractNodesArray();\n    const samples = extractArray('samples');\n    const timeDeltas = extractArray('timeDeltas');\n    const startTime = extractScalar('startTime');\n    const endTime = extractScalar('endTime');\n\n    return { nodes, samples, timeDeltas, startTime, endTime };\n}\n```\n\n### Buffer-based Parsing for `Trace-*.json`\n\nTrace 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:\n\n```javascript\nimport { readFileSync } from 'fs';\n\nfunction parseTraceFromBuffer(buf) {\n    \u002F\u002F 1. Extract metadata (small, near the top of the file)\n    let metadata = {};\n    const metaKeyBuf = Buffer.from('\"metadata\":{');\n    const metaPos = buf.indexOf(metaKeyBuf);\n    if (metaPos !== -1) {\n        const metaStart = metaPos + metaKeyBuf.length - 1; \u002F\u002F include '{'\n        let depth = 0, metaEnd = -1;\n        for (let i = metaStart; i \u003C buf.length; i++) {\n            if (buf[i] === 0x7B) depth++;\n            else if (buf[i] === 0x7D) { depth--; if (depth === 0) { metaEnd = i + 1; break; } }\n            if (buf[i] === 0x22) { i++; while (i \u003C buf.length) { if (buf[i] === 0x5C) i++; else if (buf[i] === 0x22) break; i++; } }\n        }\n        if (metaEnd !== -1) {\n            metadata = JSON.parse(buf.subarray(metaStart, metaEnd).toString('utf8'));\n        }\n    }\n\n    \u002F\u002F 2. Extract traceEvents by parsing in chunks\n    \u002F\u002F    Each event is a JSON object {...}. Scan for object boundaries.\n    const eventsKeyBuf = Buffer.from('\"traceEvents\":[');\n    const eventsPos = buf.indexOf(eventsKeyBuf);\n    if (eventsPos === -1) throw new Error('traceEvents not found');\n    const arrayStart = eventsPos + eventsKeyBuf.length;\n\n    const traceEvents = [];\n    let i = arrayStart;\n    while (i \u003C buf.length) {\n        \u002F\u002F Skip whitespace and commas\n        while (i \u003C buf.length && (buf[i] === 0x20 || buf[i] === 0x0A || buf[i] === 0x0D || buf[i] === 0x09 || buf[i] === 0x2C)) i++;\n        if (i >= buf.length || buf[i] === 0x5D) break; \u002F\u002F end of array\n\n        if (buf[i] !== 0x7B) { i++; continue; } \u002F\u002F expect '{'\n\n        \u002F\u002F Find matching '}'\n        let depth = 0, objEnd = -1;\n        for (let j = i; j \u003C buf.length; j++) {\n            if (buf[j] === 0x7B) depth++;\n            else if (buf[j] === 0x7D) { depth--; if (depth === 0) { objEnd = j + 1; break; } }\n            if (buf[j] === 0x22) { j++; while (j \u003C buf.length) { if (buf[j] === 0x5C) j++; else if (buf[j] === 0x22) break; j++; } }\n        }\n        if (objEnd === -1) break;\n\n        \u002F\u002F Parse this individual event object -- each is small enough for JSON.parse\n        traceEvents.push(JSON.parse(buf.subarray(i, objEnd).toString('utf8')));\n        i = objEnd;\n    }\n\n    return { metadata, traceEvents };\n}\n```\n\n### When to Use Buffer Parsing\n\n| File size | Approach |\n|-----------|----------|\n| \u003C 400MB | `JSON.parse(readFileSync(path, 'utf8'))` is fine |\n| 400MB - 1GB | Use Buffer-based extraction functions above |\n| > 1GB | Use Buffer-based extraction + `--max-old-space-size=16384` |\n\n### Tips for Large File Analysis\n\n- Always run with `node --max-old-space-size=16384` to give Node.js enough heap space.\n- For trace files, consider filtering events during parsing (e.g. skip categories you don't need) to reduce memory.\n- The Buffer approach reads the entire file into memory as bytes (which is fine -- Buffer doesn't have the ~512MB string limit). Individual `JSON.parse` calls operate on small sub-buffers.\n- When reformatting would create a file too large to write back, skip reformatting and work directly with the parsed data structures.\n- If you only need specific event types from a huge trace, add a filter callback to the parsing loop to avoid allocating objects you'll discard.\n\n## Tips\n\n- Timestamps in both formats are microseconds. Divide by 1000 for milliseconds.\n- For bundled\u002Fminified code (e.g. `extension.js`), function names may be mangled. Use line numbers from `callFrame.lineNumber` to cross-reference with source maps.\n- Filter out idle\u002Fprogram\u002FGC samples when measuring active CPU work.\n- When the user asks about a gap, check if it's truly idle (event loop waiting) vs. active work in unrelated code.\n- Clean up any analysis scripts you create when done.\n- Trace files are large (50-200MB). Always filter to the relevant process\u002Fthread before analysis to reduce memory and noise.\n- When a trace file contains embedded `ProfileChunk` events, prefer analyzing those over asking for a separate `.cpuprofile` -- the data is equivalent but already correlated with other trace events.\n- Use `args.data.url` in `FunctionCall` and `EvaluateScript` events to map back to VS Code source files (paths like `vscode-file:\u002F\u002Fvscode-app\u002FUsers\u002F...\u002Fout\u002Fvs\u002F...`).\n- The `dur` field is wall-clock duration; `tdur` is thread-time duration. The difference reveals time the thread was suspended (e.g. waiting for I\u002FO or preempted).\n",{"name":5,"description":7},"main","11ba54a90a0ff8720cf47c086f5a9948eef9cf2c",".github\u002Fskills\u002Fcpu-profile-analysis",43791,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode","https:\u002F\u002Fcode.visualstudio.com","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-12 03:13:10",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add microsoft\u002Fvscode --skill cpu-profile-analysis",{"tool":61,"command":62},"bunx","bunx skills add microsoft\u002Fvscode --skill cpu-profile-analysis",{"tool":64,"command":65},"pnpm","pnpm dlx skills add microsoft\u002Fvscode --skill cpu-profile-analysis","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fcpu-profile-analysis\u002FSKILL.md",true,[69,75,79,83,87,91,95,99],{"path":70,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":73,"sourceHtmlUrl":74},"recipes\u002Fnode-fetch-scrape.md","REFERENCE","参考文档",964,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fnode-fetch-scrape.md",{"path":76,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":77,"sourceHtmlUrl":78},"recipes\u002Fpython-auth-patterns.md",2308,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-auth-patterns.md",{"path":80,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":81,"sourceHtmlUrl":82},"recipes\u002Fpython-csv-parse.md",2042,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-csv-parse.md",{"path":84,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":85,"sourceHtmlUrl":86},"recipes\u002Fpython-html-parse.md",2117,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-html-parse.md",{"path":88,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":89,"sourceHtmlUrl":90},"recipes\u002Fpython-rest-fetch.md",869,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-rest-fetch.md",{"path":92,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":93,"sourceHtmlUrl":94},"recipes\u002Fpython-rest-pagination.md",2287,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-rest-pagination.md",{"path":96,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":97,"sourceHtmlUrl":98},"recipes\u002Fpython-stream-download.md",2504,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-stream-download.md",{"path":100,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":101,"sourceHtmlUrl":102},"recipes\u002Fpython-streaming-write.md",1624,"https:\u002F\u002Fgithub.com\u002Fwallfacers\u002Fdata-talk\u002Fblob\u002Fmaster\u002Fserver\u002Fdata-talk-adapter\u002Fsrc\u002Fmain\u002Fresources\u002Fskills\u002Fdata-collection\u002Frecipes\u002Fpython-streaming-write.md",[104,129,152,166,181,197],{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":40},"01M70H0ANBKZAVEP1NVV4K28M3","sweeper-query","Answer natural-language questions about the VS Code Sweeper's published issue index (index-\u003Cname>.json for microsoft\u002Fvscode and the enrolled family repos) — which issues are agent-ready to implement or plan, what the sweeper proposed for an assignee or area, whether the sweeper knows a report as a duplicate, the highest-value open bugs in a lane — and, for one issue, the review's full record (evidence with file citations, fix brief, proposed comment). Use ONLY when the request explicitly names the sweeper — \"sweeper\", \"vscodesweeper\", \"sweeper-query\", a sweeper verdict, record, brief or index, an agent-ready issue — or asks what \"the sweeper\" found, proposed or knows. Do NOT use for general questions about issues, duplicates, priorities or ranking that do not mention the sweeper. Read-only; discovery only.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fsweeper-query\u002FSKILL.md",[],[111,112,113,114,115,24,116,117,118,119,120,121,122,123,124,125,126,127,128,38],"sweeper query","query","sweeper","review","fix","request","which","natural-language","ranking","duplicates","ready","only","read","plan","open","Citations","brief","issues",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":135,"publishedAt":40},"01M699DRJS4YJCV600Q72SVVWE","update-skills","Create or update repository skills and instructions when major learnings are discovered during a session. Use when the user says \"learn!\", when a significant pattern or pitfall is identified, or when reusable domain knowledge should be captured for future sessions.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002Fsrc\u002Fvs\u002Fsessions\u002Fskills\u002Fupdate-skills\u002FSKILL.md",[],[136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,24],"update","update skills","for","pattern","domain","learnings","reusable","use","session","user","instructions","knowledge","repository","learn","create","sessions",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":158,"publishedAt":40},"01M699DPWHZ72B66P82EW31RNE","update-pr","Update the pull request for the current session. Use when the user wants to push new changes to an existing PR.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002Fsrc\u002Fvs\u002Fsessions\u002Fskills\u002Fupdate-pr\u002FSKILL.md",[],[136,159,160,24,116,161,138,143,144,145,162,163,164,165],"pr","update pr","Existing","push","pull","pull request","changes",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":172,"publishedAt":40},"01M699DJMR6CE1MH5XCJZ0GFQ0","sync","Sync the current session branch with its upstream branch, or publish the current session branch to a remote. Use when the user asks to sync a branch, pull latest changes, rebase onto upstream, push current branch, publish branch, or set upstream.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002Fsrc\u002Fvs\u002Fsessions\u002Fskills\u002Fsync\u002FSKILL.md",[],[168,173,174,162,163,175,24,165,176,177,178,179,143,144,145,180],"SET","publish","rebase","latest","with","remote","upstream","branch",{"skillNo":182,"name":183,"nameZh":6,"description":184,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":185,"categories":186,"keywords":187,"publishedAt":40},"01M699DGXHX6A2A72X57R6GGHJ","sync-upstream","Update a stale session branch by rebasing onto the latest origin. Use when the upstream has moved significantly and the session needs to catch up, resolving conflicts by preserving upstream changes and adapting session work to fit.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002Fsrc\u002Fvs\u002Fsessions\u002Fskills\u002Fsync-upstream\u002FSKILL.md",[],[179,168,188,189,24,190,191,165,176,192,193,194,195,143,144,180,196,136],"sync upstream","fit","catch","catch up","origin","conflicts","stale","up","work",{"skillNo":198,"name":199,"nameZh":6,"description":200,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":201,"categories":202,"keywords":203,"publishedAt":40},"01M699DEXYFWDGW0BAXJ5QQ377","merge","Merge changes from the topic branch to the merge base branch. Use when the user wants to merge their session's work back to the base branch.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode\u002Fblob\u002Fmain\u002Fsrc\u002Fvs\u002Fsessions\u002Fskills\u002Fmerge\u002FSKILL.md",[],[199,144,143,196,180,145,24,204,205,165],"back","base"]