mcpField
A
auto-perf-optimize

auto-perf-optimize

github.com/microsoft测试与质量194.4k154 次安装今天更新
代码仓库

关于 auto-perf-optimize

为 VS Code 工作流提供可重复的性能与内存测试技能,面向调查内存增长或泄漏的开发者。它驱动场景、验证运行结果并采集堆快照,供后续分析。

主要功能

  1. 01自动化 VS Code 场景
  2. 02采集渲染器堆样本
  3. 03生成截图与 summary.json
  4. 04按标签采集堆快照
  5. 05运行前验证场景状态

适用场景

  1. 01检查重复聊天是否导致内存增长
  2. 02为 VS Code 工作流采集堆快照
  3. 03验证并记录性能调查结果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/vscode --skill auto-perf-optimize

安装后在 Claude Code 中输入/auto-perf-optimize确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

scratchpad/
.gitignore
README.md
scripts/
chat-memory-smoke.mts
chat-session-switch-smoke.mts
userDataProfile.mts
workbench-css-performance.mts

详细介绍

VS Code 性能工作流

驱动可重复的 VS Code 场景,收集内存和性能产物,验证场景确实发生,然后在需要进行对象级调查时,将生成的堆快照交给通用的 heap-snapshot-analysis 技能。

适用场景

  • 用户描述了 VS Code 工作流,并询问是否存在内存泄漏或内存增长
  • 用户要求 Agent 启动 VS Code、驱动场景并捕获堆快照
  • 用户要求运行此技能附带的 Chat 内存冒烟测试运行器
  • 用户希望为某个场景获取截图、summary.json、渲染进程堆样本和指定的 .heapsnapshot 文件
  • 用户希望为非 Chat VS Code 场景创建新的自动化运行器

如果快照已存在,且用户只想分析堆对象或保留者,请勿使用此技能。直接使用 heap-snapshot-analysis。

工作步骤

  1. 定义场景。 记录一个预热操作、一个可重复的迭代,以及一个适合强制执行 GC 和采样内存的静止点。
  2. 开发自动化。 从一个不生成快照的小型运行开始。如果失败或 UI 状态不确定,请保持 Code 窗口打开,将 @playwright/cli 连接到同一个 CDP 端口,截取保存到工作区的截图、检查快照,并更新运行器的选择器和等待逻辑。
  3. 快速冒烟测试。 先禁用堆快照。确认场景能够完成,并且产物摘要准确反映实际情况。
  4. 捕获指定快照。 对预热后的基线和后续迭代进行快照。除非有必要,否则不要对每个样本都创建快照;快照体积很大,生成速度也很慢。
  5. 验证运行结果。 检查 summary.json 和截图。不要分析登录失败、信任提示、卡住的进度行或错误的 UI 状态。
  6. 分析快照。 切换到 heap-snapshot-analysis,以使用对比脚本、对象分组和保留者路径。
  7. 修复并验证。 找到泄漏后,修复产品代码。然后使用相同的快照标签重新运行同一场景,并进行同类对比。不要止步于分析——目标是交付修复,而不只是生成报告。
  8. 记录。 将发现、修复和前后测量结果汇总并保存到会话记忆中,以保留工作成果。

已纳入版本控制的运行器

scripts/ 文件夹包含稳定、通用的运行器。可直接使用,也可将其用作临时脚本的模板:

  • chat-memory-smoke.mts — 多轮聊天冒烟测试运行器。发送提示词、等待响应、采样堆,并可选择是否创建快照。用途最广泛的运行器。
  • chat-session-switch-smoke.mts — 创建多个内容类型不同的聊天会话,然后通过会话侧边栏反复切换。
  • userDataProfile.mts — 用于管理冒烟测试运行中用户数据配置文件的工具。
聊天工作流:聊天记忆冒烟测试运行器

如果场景专用于 Chat,或可以表示为重复发送 Chat 提示词,请使用随附的聊天记忆冒烟测试运行器。它会启动 Code OSS、打开 Chat、发送提示词、等待响应、写入屏幕截图和 summary.json、采样渲染器堆,并可获取指定的堆快照。

快速健康检查:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --iterations 3 --no-heap-snapshots

预热后获取指定快照:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --iterations 8 --heap-snapshot-label 03-iteration-01 --heap-snapshot-label 03-iteration-08

用户描述的 Chat 场景:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --iterations 8 --message 'For memory investigation iteration {iteration}, summarize the active workspace in one paragraph.' --heap-snapshot-label 03-iteration-01 --heap-snapshot-label 03-iteration-08

运行器的重要行为:

  • 默认配置文件会持久保存在 .build/auto-perf-optimize/user-data,以便此技能中的所有运行器复用身份验证信息。
  • 仅当场景需要干净配置文件时,才传入 --temporary-user-data。
  • 传入 --seed-user-data-dir <path>,可在启动前将已登录的配置文件复制到新的目标配置文件。目标配置文件可能包含身份验证密钥;请将其保存在被忽略的本地 .build/... 文件夹中,切勿将其附加到 issue 或 PR 中。

安全提示:聊天运行会在真实机器上执行。 这些运行器启动的 Code OSS 实例是完整的 VS Code,并使用用户真实计算机上的 Copilot 身份验证——它并非沙盒。你编写的 Chat 提示词会发送给真实的大模型,而 Agent 执行的任何工具调用(终端命令、文件编辑等)都会实际运行。请谨慎操作:

  • 使用一次性工作区,不要使用真实仓库。传入 --workspace <scratch-folder>,指向临时目录或被 Git 忽略的目录(例如运行器的 scratchpad 子文件夹,或 .build/ 下的文件夹)。已检入的运行器默认使用仓库根目录,以方便使用;但用于 Chat 场景的 scratchpad 运行器应始终覆盖此设置,以避免意外修改源代码树中的文件。
  • 对于会触发终端工具的提示词,请使用安全的只读命令(例如 touch /tmp/foo、git log --oneline、ls)。切勿指示 Agent 删除文件、运行破坏性命令或修改用户的工作区。
  • 如果测试需要工具调用,请使用无害操作,并在之后清理所有临时文件。
  • 不必害怕运行终端命令——只需谨慎斟酌请求内容。
  • 如果用户需要登录或查看窗口,请传入 --keep-open;然后在下一次自动运行前关闭窗口,除非有意复用它。
  • 仅当连接到使用 --enable-smoke-test-driver 启动且端口与所选远程调试端口相同的 Code 窗口时,才传入 --reuse。

配置文件与身份验证

日常运行时,优先使用共享的持久性能配置文件:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --keep-open --iterations 1 --no-heap-snapshots

如果 Chat 要求进行身份验证,请让用户登录一次,然后关闭 Code 窗口,再次运行快速冒烟测试,但不要传入 --keep-open。随附的 Chat 运行器以及遵循此技能配置文件约定的其他运行器都会复用同一配置文件。

若要从较早的已登录自动化配置文件初始化共享性能配置文件,请将其复制到默认目标位置一次:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --seed-user-data-dir .build/chat-memory-smoke/user-data --keep-open --iterations 1 --no-heap-snapshots

若要运行已登录种子配置文件的新一次性副本:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --temporary-user-data --seed-user-data-dir .build/auto-perf-optimize/user-data --iterations 3 --no-heap-snapshots

种子副本规则:

  • 种子配置对应的 Code 窗口必须关闭。切勿在 Code 进程正在使用配置文件时复制它。
  • 目标 user-data-dir 必须不存在或为空。如果脚本拒绝复制,请选择新的 --user-data-dir、使用 --temporary-user-data,或主动删除本地目标目录。
  • 复制时会跳过根目录级别的缓存、日志、崩溃转储、单例锁定/套接字文件和会话存储,但会保留可能包含身份验证信息或扩展状态的用户/全局存储。
  • 如果希望在运行后保留复制的配置文件,请明确指定 --user-data-dir <fresh-path> --seed-user-data-dir <seed-path>。运行器绝不会删除用户提供的 --user-data-dir。

开发并观察运行器

自动化运行器的首个版本很少能一次写对。把运行器视为正在开发的测试:运行一个成本低的场景,观察实时工作台,调整一个选择器或等待条件,然后重复。只有运行器稳定可靠后,才收集堆快照。

新运行器应放在 scratchpad 文件夹(已加入 gitignore)。scripts/ 中已提交的脚本是稳定、通用的运行器——不要为了某次临时调查而修改它们。请改为将其中的模式复制到 scratchpad 脚本中。

将 scratchpad 中的工作整理到按日期命名的子文件夹中,名称格式为 YYYY-MM-DD-short-description/(例如:2026-04-09-chat-scroll-leak/)。每个子文件夹应包含:

  • 调查脚本(.mts、.mjs 等)
  • 用于记录完整调查过程的 findings.md 文件:记录考虑过的所有想法、哪些想法促成了修改、哪些被否决(及原因)、修改前后的测量结果,以及结果总结。这样用户就能审查 Agent 的推理,决定保留哪些修改,并跟进尚未处理的想法。

**从头开始。**忽略之前调查留下的所有 scratchpad 子文件夹。它们属于先前的会话,其中的上下文、脚本和调查结果与你当前的任务无关。请务必为本次调查新建一个带日期的子文件夹。

**导入路径深度:**带日期的子文件夹中的脚本位于仓库根目录下 6 层(.github/skills/auto-perf-optimize/scratchpad/YYYY-MM-DD-name/script.mts),而不是已提交的 scripts/*.mts 运行器所在的 4 层。请相应调整相对导入路径——从带日期的子文件夹使用 5 个 .. 片段到达仓库根目录(例如:'../../../../../src/vs/base/common/stopwatch.ts'),使用 '../../scripts/userDataProfile.mts' 到达同级的已提交脚本。

捆绑式 Chat 运行器的建议观察循环:

node .github/skills/auto-perf-optimize/scripts/chat-memory-smoke.mts --keep-open --iterations 1 --no-heap-snapshots --port 9224 --output .build/chat-memory-smoke/watch-chat

Code 窗口打开期间,从仓库根目录使用 @playwright/cli 检查它:

npx @playwright/cli attach --cdp=http://127.0.0.1:9224
npx @playwright/cli tab-list
npx @playwright/cli snapshot
npx @playwright/cli screenshot --filename=.build/chat-memory-smoke/watch-chat/observation.png

@playwright/cli 检查要点:

  • 首先运行 tab-list。如果选中的目标是 about:blank 或 webview,而不是工作台,请先切换目标,再依赖快照进行判断。
  • 使用 snapshot 重新发现按钮、文本框、列表行、webview 和当前的可访问名称。优先依据发现的状态,而不是过时的选择器。
  • 将屏幕截图保存在运行器输出文件夹或其他工作区本地的 .build/... 文件夹中。不要将你希望用户审查的截图放在 /tmp 中。输出目录必须已存在——如果目录不存在,screenshot --filename 会因 ENOENT 而失败。请先使用 mkdir -p <dir> 创建目录。
  • 如果脚本卡住,请先截屏并读取增量更新的 summary.json,再关闭窗口。最后提交的轮次和最后一张截图通常能指出缺少哪个等待条件。
  • 如果需要身份验证,请使用 --keep-open,让用户在持久化的默认配置文件中登录一次,关闭窗口,然后重新运行快速冒烟测试。

编辑场景运行器时:

  • 保持输出契约稳定:包含 summary.json、检查点截图、堆样本、可选的 heap/*.heapsnapshot 文件,以及失败时的 error 字段。
  • 在长时间等待之前写入摘要和截图等产物,以便诊断失败的运行。
  • 等待用户可见的场景完成,而不是任意等待一段时间。优先等待观察到的响应、进度提示消失、行数变化、编辑器内容变化或命令结果。
  • 首先使用 --no-heap-snapshots 进行验证。运行器出错时再生成 2GB 堆快照只会浪费时间,还会掩盖真正的问题。
  • 每次运行之间关闭自己启动的 Code 窗口,除非命令有意使用 --keep-open 或 --reuse。

分析前先验证

打开堆快照前,先阅读本次运行的 summary.json。检查:

  • 不存在 error
  • chatTurns 的数量符合预期
  • 每轮都有响应开始原因和最终响应文本,除非本次运行有意使用了 --skip-send
  • 对于多轮内存探测,存在 analysis.postFirstTurnUsedBytes 和 analysis.postFirstTurnUsedBytesPerTurn
  • heap/ 下存在请求的快照标签
  • 屏幕截图显示了请求的工作流和已稳定的 UI

优先使用已预热的基线,例如 03-iteration-01.heapsnapshot,而不是启动快照。启动、打开 Chat、登录、激活扩展和首次加载模型都属于预期的内存分配。

比较 Chat Runner 的结果

完成捕获后,使用 heap-snapshot-analysis。最简临时对比脚本如下:

import path from 'node:path';
import { compareSnapshots, printComparison } from '../helpers/compareSnapshots.ts';

const runDir = process.env.RUN;
if (!runDir) {
	throw new Error('Set RUN to a chat-memory-smoke output directory');
}

const before = path.join(runDir, 'heap', '03-iteration-01.heapsnapshot');
const after = path.join(runDir, 'heap', '03-iteration-08.heapsnapshot');
printComparison(compareSnapshots(before, after));

在 heap-snapshot-analysis 技能文件夹中运行:

cd .github/skills/heap-snapshot-analysis
RUN=../../../.build/chat-memory-smoke/<run-folder> node --max-old-space-size=16384 scratchpad/compare-chat-run.mjs

非 Chat 的 VS Code 场景

当用户描述非 Chat 场景时,只询问缺少的必要信息:什么操作会启动场景、一次可重复迭代如何定义、UI 稳定的判断依据是什么,以及配置文件应为持久型还是临时型。

在 scratchpad 文件夹中编写新的场景 Runner。 此文件夹已被 gitignore,可随意用于一次性调查脚本。如果某个 Runner 证明具有普遍用途,可将其移至 scripts/,并补充文档和验证。

将每项调查放在一个带日期的子文件夹中(命名约定见“开发并监视 Runner”)。

临时工作流示例:

# Create a dated investigation folder
mkdir -p .github/skills/auto-perf-optimize/scratchpad/2026-04-09-editor-tab-leak

# Write a runner inside it
cat > .github/skills/auto-perf-optimize/scratchpad/2026-04-09-editor-tab-leak/scenario.mts << 'EOF'
// ... your scenario using patterns from the checked-in scripts
EOF

# Validate without snapshots first
node .github/skills/auto-perf-optimize/scratchpad/2026-04-09-editor-tab-leak/scenario.mts \
  --iterations 3 --no-heap-snapshots --skip-prelaunch \
  --user-data-dir .build/chat-memory-smoke/user-data

# Then capture targeted snapshots
node .github/skills/auto-perf-optimize/scratchpad/2026-04-09-editor-tab-leak/scenario.mts \
  --iterations 10 --heap-snapshot-label baseline --heap-snapshot-label final \
  --skip-prelaunch --user-data-dir .build/chat-memory-smoke/user-data

# Write findings.md when the investigation concludes

复用已纳入版本控制的脚本中的这些模式(chat-memory-smoke.mts、chat-session-switch-smoke.mts):

  • 启动 scripts/code.sh 或 scripts/code.bat
  • 传入 --enable-smoke-test-driver、--disable-workspace-trust、已知的 --remote-debugging-port、显式指定的 --user-data-dir、显式指定的 --extensions-dir、--skip-welcome 和 --skip-release-notes
  • 使用临时工作区(--workspace <scratch-folder>),而不是仓库根目录,以防 Chat 工具调用修改真实源文件
  • 使用 chromium.connectOverCDP 连接 Playwright
  • 等待 globalThis.driver?.whenWorkbenchRestored?.()
  • 启用 CDP Performance 和 HeapProfiler
  • 在采集内存样本前执行垃圾回收
  • 在重要检查点保存屏幕截图
  • 增量写入机器可读的 summary.json,尤其是在长时间等待之前
  • 支持 --no-heap-snapshots 和指定快照标签,以加快验证
  • 明确执行清理:关闭 CDP 浏览器、终止自有的 Code 进程,并保留用户提供的配置文件

将特定场景的 UI 选择器和等待逻辑放在场景 Runner 中。除非多个已验证场景共用完全相同的生命周期,否则不要将 Chat Runner 抽象为通用工具。

移交给堆快照分析

在以下情况下使用 heap-snapshot-analysis:

  • 按构造函数/对象组比较两个 .heapsnapshot 文件
  • 查找直接保留者或指向 GC 根的路径
  • 检查特定类、模型、微件、编辑器输入或 DOM 树为何仍被保留
  • 针对已解析的快照编写特定调查用的临时分析脚本

此工作流的产出是证据:运行摘要、屏幕截图、堆样本、指定快照、比较结果和保留者路径。利用这些证据提出具体的内存泄漏假设,然后修复产品代码,并通过再次运行验证修复。

从根因着手,而不是只处理表象

表面观察(“这个 Map 在不断增长”)并不等于诊断。在编写修复方案之前,先弄清楚代码为何采用当前结构:

  1. 对存在泄漏的代码使用 git blame 和 git log。阅读提交说明、PR 描述以及任何关联的问题。像 if (this._isDisposed) return 这样的保护逻辑可能是因为之前移除后导致崩溃才加上的——修改前先了解其初衷。
  2. 追踪完整生命周期,而不只是泄漏发生的位置。如果 disposeContext() 被静默跳过,要问:为什么父对象会先于子对象释放?释放顺序有问题,还是保护逻辑有问题?答案将决定你是修复保护逻辑、调整释放顺序,还是添加其他清理路径。
  3. 区分缓存与泄漏。Map 虽然在增长,但如果有裁剪/淘汰机制(例如有 2^16 上限的 UriIdentityService._canonicalUris),它就是缓存,而不是泄漏。除非缓存完全没有淘汰策略,否则不要“修复”缓存。
  4. 查找设计层面的问题。如果临时对象注册到全局单例中,而单例从不注销它们,修复方法不只是添加一个 delete 调用——还要问临时对象是否根本不该注册,或者是否应该有一个中间的作用域注册表。
  5. 检查已有做法。在代码库中搜索能正确处理相同生命周期的类似模式。如果同类池/服务已能在清空时释放正在使用的条目,就遵循这种模式。如果其他地方都没有这样做,新增契约前先弄清原因。

目标是修复原因,而不是掩盖结果。不清楚清理为何缺失就直接添加清理代码,往往会引入新错误,或导致以前的修复再次失效。

修复问题,而不只是报告问题

此工作流的目标是交付修复,而不是生成报告。识别泄漏后:

  1. 修改产品代码,解决根本原因。常见模式:

    • 池在 clear() 时清理空闲条目,却留下孤立的 _inUse 条目——清空时也释放 _inUse
    • 全局服务映射(ContextKeyService._contexts、HoverService._managedHovers、UriIdentityService._canonicalUris)不断增长,因为临时对象注册后从未注销
    • 可释放对象链中,_register(service.createScoped(...)) 是正确的,但父对象的 dispose() 从未被调用
    • 可观察对象订阅(autorunIterableDelta 的 lastValues)保留过期的模型引用
  2. 通过使用相同快照标签重新运行相同场景来验证修复。比较 postFirst*UsedBytes 的趋势和快照差异。成功的修复应当在迭代阶段显示内存持平或下降。

  3. 运行所有测试。完成前,运行你修改的所有文件对应的单元测试和集成测试。本代码库中的单元测试预期应当稳定——任何单元测试失败都很可能由你的修改导致,必须修复。集成测试更容易出现不稳定情况,但仍应调查失败原因。

  4. 在宣布完成前,在临时工作区中的 findings.md 和会话记忆中记录结果:泄漏了什么、修复了什么,以及修复前后的测量结果。

不要止步于分析。如果有泄漏证据,就尝试修复。如果修复方案不明确或风险较高,请说明原因并提出替代方案。

评论

支持 Markdown