auto-perf-optimize について
VS Code のメモリ増加やリークを調べる開発者向けに、性能・メモリテストを再現可能にするスキルです。シナリオを実行・検証し、後続分析用のヒープスナップショットを収集します。
主な機能
- 01VS Code シナリオの自動実行
- 02レンダラーヒープサンプルの収集
- 03スクリーンショットと summary.json の生成
- 04ラベル付きヒープスナップショットの取得
- 05分析前のシナリオ状態の検証
活用シーン
- 01チャット反復によるメモリ増加の確認
- 02VS Code ワークフローのヒープスナップショット取得
- 03性能調査結果の検証と記録
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ vscode --skill auto-perf-optimizeインストール後、Claude Code で/auto-perf-optimizeを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
scratchpad/.gitignoreREADME.mdscripts/chat-memory-smoke.mtschat-session-switch-smoke.mtsuserDataProfile.mtsworkbench-css-performance.mts詳細
VS Code パフォーマンスワークフロー
再現可能な VS Code のシナリオを実行し、メモリ/パフォーマンスの成果物を収集して、シナリオが実際に実行されたことを確認します。そのうえで、オブジェクトレベルの調査が必要な場合は、生成したヒープスナップショットを汎用の heap-snapshot-analysis スキルに渡します。
使用するタイミング
- ユーザーが VS Code のワークフローについて説明し、メモリリークやメモリ増加の有無を尋ねている
- ユーザーがエージェントに VS Code の起動、シナリオの実行、ヒープスナップショットの取得を依頼している
- ユーザーがこのスキルに含まれる Chat メモリのスモークランナーの実行を依頼している
- ユーザーが、あるシナリオのスクリーンショット、
summary.json、レンダラーのヒープサンプル、対象を絞った.heapsnapshotファイルを求めている - ユーザーが、Chat 以外の VS Code シナリオ向けに新しい自動化ランナーを求めている
スナップショットがすでに存在し、ユーザーがヒープオブジェクトやリテイナーの分析のみを求めている場合は、このスキルを使わないでください。heap-snapshot-analysis を直接使用してください。
手順
- シナリオを定義します。 ウォームアップ操作を1つ、繰り返し可能な反復操作を1つ、GC を強制してメモリをサンプリングしても問題のない静止状態のポイントを1つ書き出します。
- 自動化を開発します。 まずスナップショットを取得しない小規模な実行から始めます。失敗した場合や UI の状態が不確かな場合は、Code ウィンドウを開いたままにし、同じ CDP ポートに
@playwright/cliを接続して、ワークスペース内にスクリーンショットを保存し、スナップショットを調べてから、ランナーのセレクターや待機処理を更新します。 - 短時間のスモークテストを実行します。 まずヒープスナップショットの取得を無効にします。シナリオが完了することと、成果物の概要に想定どおりの内容が記載されていることを確認します。
- 対象を絞ってスナップショットを取得します。 ウォームアップ後のベースラインと、その後の反復時にスナップショットを取得します。必要でない限り、サンプルごとにスナップショットを取得しないでください。スナップショットは非常に大きく、時間もかかります。
- 実行を確認します。
summary.jsonとスクリーンショットを調べます。ログイン失敗、信頼確認プロンプト、進行状況行の停止、または誤った UI 状態を分析しないでください。 - スナップショットを分析します。 比較スクリプト、オブジェクトのグループ化、リテイナーパスには heap-snapshot-analysis を使用します。
- 修正して確認します。 リークを特定したら、製品コードを修正します。その後、同じシナリオを同じスナップショットラベルで再実行し、条件をそろえて比較します。分析だけで終わらせないでください。目標はレポートを作成することではなく、修正をリリースすることです。
- 記録します。 調査結果、修正内容、修正前後の測定値の概要をセッションメモリに保存し、作業内容を残します。
チェックイン済みのランナー
scripts/ フォルダーには、安定した汎用ランナーが含まれています。直接使うか、スクラッチパッドスクリプトのテンプレートとして使用してください。
- chat-memory-smoke.mts — 複数ターンのチャット用スモークランナーです。プロンプトを送信し、応答を待ち、ヒープをサンプリングして、必要に応じてスナップショットを取得します。最も汎用性の高いランナーです。
- chat-session-switch-smoke.mts — コンテンツの種類が異なる複数のチャットセッションを作成し、セッションのサイドバーからそれらを繰り返し切り替えます。
- userDataProfile.mts — スモークテスト実行時のユーザーデータプロファイル管理用ユーティリティです。
チャットのワークフロー:Chat Memory Smoke Runner
シナリオがChat固有の場合、またはChatプロンプトの繰り返しで表現できる場合は、同梱のChat memory smoke runnerを使用してください。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/...フォルダー内に保管し、課題やPRに添付しないでください。
安全に関する注意:Chatの実行は実際のマシン上で行われます。 これらのランナーが起動するCode OSSインスタンスは、ユーザーの実際のコンピューター上でCopilot認証が設定された完全なVS Codeであり、サンドボックスではありません。作成したChatプロンプトは実際のLLMに送信され、エージェントが呼び出すツール(ターミナルコマンド、ファイル編集など)はすべて実際に実行されます。責任を持って使用してください。
- 実際のリポジトリではなく、使い捨てのワークスペースを使用してください。 一時ディレクトリまたはGitの無視対象ディレクトリ(ランナーのスクラッチパッドサブフォルダーや
.build/内のフォルダーなど)を指す--workspace <scratch-folder>を渡してください。チェックイン済みのランナーでは利便性のためデフォルトのワークスペースがリポジトリのルートになっていますが、Chatシナリオ用のスクラッチパッドランナーでは、ソースツリーが誤って変更されないよう、必ず別のワークスペースを指定してください。 - ターミナルツールを起動するプロンプトには、安全で読み取り専用のコマンドを使用してください(例:
touch /tmp/foo、git log --oneline、ls)。エージェントにファイルの削除、破壊的なコマンドの実行、ユーザーのワークスペースの変更を指示しないでください。 - テストにツール呼び出しが必要な場合は、無害な操作を使い、終了後に一時ファイルを削除してください。
- ターミナルコマンドの実行をためらう必要はありません。依頼する内容を慎重に検討してください。
- ユーザーがログインする必要がある場合や、ウィンドウを確認する必要がある場合は
--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は、ランナーによって削除されることはありません。
ランナーの開発と監視
自動化ランナーの最初のバージョンが正しく動くことは、ほとんどありません。ランナーを開発中のテストとして扱ってください。負荷の低いシナリオを実行し、作業中のワークベンチを観察して、セレクターまたは待機条件を1つ調整し、繰り返します。ランナーが十分に安定するまでは、ヒープスナップショットを収集しないでください。
新しいランナーはスクラッチパッドフォルダーに配置します(Git の追跡対象外)。scripts/ にチェックイン済みのスクリプトは、安定した汎用ランナーです。一時的な調査のために変更しないでください。代わりに、そこからパターンをコピーしてスクラッチパッドのスクリプトに使用します。
スクラッチパッドでの作業は、YYYY-MM-DD-short-description/ という名前の日付付きサブフォルダーに整理します(例:2026-04-09-chat-scroll-leak/)。各サブフォルダーには、次の内容を含めます。
- 調査用スクリプト(
.mts、.mjsなど) - 調査全体を記録した
findings.mdファイル。検討したすべてのアイデア、変更につながった案と却下した案(その理由を含む)、変更前後の測定値、結果の要約を記載します。これにより、ユーザーはエージェントの推論を確認し、採用する変更を決め、保留したアイデアを後から検討できます。
毎回、新しく始めてください。 以前の調査で作成されたスクラッチパッドのサブフォルダーはすべて無視します。それらは以前のセッションのものであり、現在のタスクにはそのコンテキスト、スクリプト、調査結果は関係ありません。調査を始めるたびに、新しい日付付きサブフォルダーを作成してください。
インポートパスの階層数: 日付付きサブフォルダー内のスクリプトは、リポジトリのルートから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またはウェブビューの場合は、スナップショットを信頼する前にターゲットを切り替えます。 snapshotを使用して、ボタン、テキストボックス、リストの行、ウェブビュー、現在のアクセシブル名を再確認します。古いセレクターより、発見した状態を優先してください。- スクリーンショットは、ランナーの出力フォルダー、またはワークスペース内の別の
.build/...フォルダーに保存します。ユーザーに確認してもらうスクリーンショットには/tmpを使用しないでください。出力ディレクトリは事前に作成しておく必要があります。存在しない場合、screenshot --filenameはENOENTで失敗します。最初にmkdir -p <dir>で作成してください。 - スクリプトが停止している場合は、ウィンドウを終了する前にスクリーンショットを取得し、更新途中の
summary.jsonを確認します。通常、最後に送信されたターンと最後のスクリーンショットから、不足している待機条件を特定できます。 - 認証が必要な場合は、
--keep-openを使用し、永続化されるデフォルトプロファイルでユーザーに一度サインインしてもらってからウィンドウを閉じ、短時間のスモークテストを再実行します。
シナリオランナーを編集するときは、次の点に注意します。
- 出力形式を安定させます。
summary.json、チェックポイントのスクリーンショット、ヒープサンプル、任意のheap/*.heapsnapshotファイルを出力し、失敗時にはerrorフィールドを含めます。 - 長時間待機する前に、診断に使えるよう、サマリーとスクリーンショットの成果物を書き込みます。
- 根拠のない時間ではなく、ユーザーが確認できるシナリオの完了を待ちます。応答の確認、進行状況表示の消失、行数の変化、エディター内容の変化、コマンドの結果などを優先してください。
- 最初に
--no-heap-snapshotsを指定して検証します。壊れたランナーで2GBのヒープスナップショットを作成すると、時間を浪費し、本当の失敗を見えにくくします。 - コマンドで意図的に
--keep-openまたは--reuseを使用する場合を除き、実行の間に、ランナーが起動した Code ウィンドウを閉じます。
分析前の確認
ヒープスナップショットを開く前に、実行の 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 以外のシナリオを説明した場合は、不足している必須情報だけを尋ねてください。シナリオを開始する操作、繰り返し可能な 1 回の反復の定義、UI が安定したことを示すもの、プロファイルを永続化するか一時的なものにするかを確認します。
新しいシナリオランナーは scratchpad フォルダーに作成してください。 このフォルダーは gitignore の対象です。単発の調査スクリプトに自由に使用してください。ランナーが幅広く役立つと分かった場合は、ドキュメントと検証を追加して scripts/ に移してください。
各調査は日付付きのサブフォルダーに配置してください(命名規則については「ランナーの開発と監視」を参照)。
スクラッチパッドのワークフロー例:
# 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を渡す- Chat ツールの呼び出しによって実際のソースファイルが変更されないように、リポジトリのルートではなく使い捨てのワークスペース(
--workspace <scratch-folder>)を使用する chromium.connectOverCDPで Playwright に接続するglobalThis.driver?.whenWorkbenchRestored?.()を待機する- CDP の
PerformanceとHeapProfilerを有効にする - メモリサンプルの前にガベージコレクションを実行する
- 重要なチェックポイントでスクリーンショットを保存する
- 特に長い待機の前に、機械可読な
summary.jsonを逐次書き込む - 検証を高速に保つため、
--no-heap-snapshotsと対象を絞ったスナップショットラベルをサポートする - クリーンアップを明示する。CDP ブラウザーを閉じ、所有する Code プロセスを終了し、ユーザー提供のプロファイルは保持する
シナリオ固有の UI セレクターと待機ロジックは、シナリオランナーに保持してください。実証済みの複数のシナリオでライフサイクルが完全に共通している場合を除き、Chat ランナーを汎用的な抽象化にしないでください。
ヒープスナップショット分析への引き継ぎ
次の作業が必要な場合は、heap-snapshot-analysis を使用してください。
- 2 つの
.heapsnapshotファイルをコンストラクター/オブジェクトグループごとに比較する - 直接のリテイナーや GC ルートへのパスを見つける
- 特定のクラス、モデル、ウィジェット、エディター入力、DOM ツリーが保持された理由を調べる
- 解析済みスナップショットに対して、調査固有のスクラッチパッド分析を作成する
このワークフローで得られる出力は証拠です。実行サマリー、スクリーンショット、ヒープサンプル、対象を絞ったスナップショット、比較結果、リテイナーへのパスが含まれます。この証拠をもとに具体的なリーク仮説を立て、製品コードを修正して、再度実行し修正を検証してください。
症状ではなく根本原因に対処する
表面的な観察(「この Map が大きくなっている」)は診断ではありません。修正を書く前に、コードがそのような構造になっている理由を理解してください。
- 漏れが発生しているコードで
git blameとgit logを使います。コミットメッセージ、PR の説明、関連する issue を読みます。if (this._isDisposed) returnのようなガードは、以前これを削除したことでクラッシュが発生したために存在する可能性があります。変更する前に、元の意図を理解してください。 - 漏れが発生している箇所だけでなく、ライフサイクル全体を追跡します。
disposeContext()が黙って無視される場合、なぜ親が子より先に破棄されるのかを確認します。破棄の順序が間違っているのか、それともガードが間違っているのかを見極めます。その答えによって、ガードを修正するのか、破棄の順序を修正するのか、別のクリーンアップ経路を追加するのかが決まります。 - キャッシュとメモリリークを区別します。増加していても、
UriIdentityService._canonicalUrisの 2^16 件の上限のように、トリミングや追い出しの仕組みがある Map はキャッシュであり、メモリリークではありません。追い出しポリシーがない場合を除き、キャッシュを「修正」しないでください。 - 設計レベルの問題を探します。一時オブジェクトがグローバルなシングルトンに登録され、シングルトンがそれらの登録を解除しない場合、修正は単に
delete呼び出しを追加することではありません。一時オブジェクトをそもそも登録すべきなのか、あるいは中間のスコープ付きレジストリを設けるべきなのかを検討してください。 - 既存の類似例を確認します。同じライフサイクルを正しく処理しているパターンをコードベース内で検索してください。同種のプールやサービスが、クリア時に使用中のアイテムをすでに破棄しているなら、そのパターンに従います。他に例がない場合は、新しい契約を導入する前に、その理由を理解してください。
目標は原因を修正することであり、結果を取り繕うことではありません。クリーンアップが欠けていた理由を理解せずにクリーンアップコードを追加すると、新たなバグを招いたり、以前の修正を再び損なったりすることがよくあります。
報告だけでなく、修正する
このワークフローの目標は、報告書を作成することではなく、修正をリリースすることです。メモリリークを特定したら、次の手順に従います。
-
根本原因に対処する製品コードの変更を行います。よくあるパターンは次のとおりです。
- プールがアイドル状態のアイテムを
clear()しても、孤立した_inUseを残す場合は、clear時に_inUseも破棄します。 - グローバルサービスの Map(
ContextKeyService._contexts、HoverService._managedHovers、UriIdentityService._canonicalUris)で、一時オブジェクトが登録されるものの、登録解除されないために増加するもの _register(service.createScoped(...))は正しいものの、親のdispose()が一度も呼び出されない Disposable チェーン- 古いモデル参照を保持する Observable サブスクリプション(
autorunIterableDeltaのlastValues)
- プールがアイドル状態のアイテムを
-
同じスナップショットラベルを使って同じシナリオを再実行し、修正を検証します。
postFirst*UsedBytesの推移とスナップショットの差分を比較します。修正が成功していれば、反復フェーズでメモリが横ばい、または減少するはずです。 -
すべてのテストを実行します。 作業を終える前に、変更したファイルに関するユニットテストと統合テストをすべて実行します。このリポジトリのユニットテストは安定していることが期待されるため、ユニットテストが失敗した場合、原因は変更にある可能性が非常に高く、修正しなければなりません。統合テストはやや不安定になりやすいものの、失敗した場合は調査してください。
-
完了を宣言する前に、スクラッチパッドの
findings.mdとセッションメモリに結果を記録します。何が漏れていたのか、何を修正したのか、修正前後の測定値を記載してください。
分析だけで終わらせないでください。メモリリークの証拠がある場合は、修正を試みてください。修正が不明確、またはリスクが高い場合は、その理由を説明し、代替案を提案してください。