cpu-profile-analysis について
V8 の `.cpuprofile` と DevTools の `Trace-*.json` を分析するスキルです。開発者が性能上のボトルネックを特定し、コードパスを比較して時間軸を把握できます。
主な機能
- 01CPU プロファイルの解析
- 02DevTools トレースイベントの分析
- 03高コストな関数やタスクの特定
- 04コードパスの所要時間比較
- 05レイアウト、描画、GC の調査
活用シーン
- 01遅いコードパスの原因調査
- 02新旧実装の CPU コスト比較
- 03長時間タスクや描画ボトルネックの分析
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ vscode --skill cpu-profile-analysisインストール後、Claude Code で/cpu-profile-analysisを入力して読み込まれたか確認します。
詳細
パフォーマンスプロファイルを分析する
.cpuprofile ファイル(V8 サンプリングプロファイラー)と DevTools トレースファイル(Trace-*.json、Chrome Trace Event Format)を分析し、パフォーマンスのボトルネックを見つけ、コードパスを比較し、タイミングを把握します。
使用する場面
- ユーザーから
.cpuprofileまたはTrace-*.jsonファイルが提供され、パフォーマンスの把握を求められた場合 - あるコードパスが別のコードパスより遅い理由を調査する場合
- 最も多くの時間を消費している関数を見つける場合
- 1 つのプロファイル内で「変更前/変更後」や「旧/新」の実装を比較する場合
- レイアウトスラッシング、長時間タスク、レンダリングのボトルネックを調査する場合(トレースファイル)
code/didResolveTextFileEditorModelなどの VS Code ユーザータイミングマークを分析する場合(トレースファイル)- マルチプロセスの動作を把握する場合(トレースファイル内の Browser、Renderer、GPU プロセス)
ファイル形式の判別
.cpuprofile:トップレベル JSON にnodes、samples、timeDeltasキーがあります。VS Code プロファイラーによって作成されます。Trace-*.json:トップレベル JSON にtraceEvents配列(および任意のmetadata)があります。Chrome/Electron DevTools の Performance タブによって作成されます。.cpuprofileより情報が豊富で、CPU サンプル、レイアウト/描画イベント、ユーザータイミングマーク、GC イベント、入力イベント、マルチプロセスデータが含まれます。
主な概念
- サンプリングプロファイラー:プロファイラーは定期的にコールスタックのスナップショットを取得します。すべての関数が現れるわけではなく、プロファイラーがサンプリングした時点でスタック上にあった関数だけが現れます。関数名が正確に特定できるとは限らないため、パターンや周辺の処理に注目してください。
- セルフタイム:関数自体(リーフ/最内層のフレーム)で費やされた時間です。
- 合計時間:関数がスタック上のどこかにあった時間です(呼び出し先の関数も含みます)。
- アイドルサンプル:
(idle)、(program)、または(garbage collector)とラベル付けされたフレームは、ユーザーコードが実行されていないことを表します。
パート 1:.cpuprofile ファイル
プロファイルの形式
.cpuprofile は、次のトップレベルキーを持つ JSON です。
nodes:コールフレームノードのツリーを形成する配列(各ノードにid、callFrame、childrenがあります)samples:ノード ID の配列。プロファイラーの各ティックにつき 1 つで、リーフ(最内層)フレームを参照しますtimeDeltas:連続するサンプル間のマイクロ秒単位の差分の配列startTime/endTime:マイクロ秒単位の絶対タイムスタンプ$vscode:任意の VS Code メタデータ
手順
1. ファイルサイズを確認して解析する
プロファイルファイルとトレースファイルは、V8 の文字列サイズ上限(約 512 MB)を超えることがあります。必ず最初にファイルサイズを確認し、適切な解析方法を選んでください。
import { readFileSync, statSync } from 'fs';
const stat = statSync(profilePath);
const sizeMB = stat.size / (1024 * 1024);
console.log(`File size: ${sizeMB.toFixed(0)}MB`);
let data;
if (sizeMB < 400) {
// Small enough for JSON.parse
data = JSON.parse(readFileSync(profilePath, 'utf8'));
} else {
// Too large -- use Buffer-based extraction (see "Handling Huge Files" section)
data = parseProfileFromBuffer(readFileSync(profilePath));
}
約 400 MB 未満のファイルであれば、JSON.parse(readFileSync(..., 'utf8')) を問題なく使用できます。それより大きなファイルについては、以下の 巨大ファイルの処理 セクションを参照してください。
2. ファイルを整形する(小さなファイルのみ)
プロファイルは 1 行の JSON であることがよくあります。確認しやすいように整形してください(十分に小さい場合のみ)。
if (sizeMB < 400) {
const data = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
fs.writeFileSync(profilePath, JSON.stringify(data, null, 2));
}
3. データ構造を構築する
Node.js の分析スクリプトを作成し、次のデータ構造を構築してください。
// Node lookup
const nodeMap = new Map(); // id -> node
const parentMap = new Map(); // id -> parent id
// Absolute timestamps from deltas
const timestamps = [data.startTime];
for (let i = 0; i < data.timeDeltas.length; i++) {
timestamps.push(timestamps[i] + data.timeDeltas[i]);
}
// Stack walker (leaf to root)
function getStack(sampleNodeId) {
const stack = [];
let id = sampleNodeId;
while (id !== undefined) {
const node = nodeMap.get(id);
if (node) stack.push(node.callFrame.functionName);
id = parentMap.get(id);
}
return stack; // [leaf, ..., root]
}
4. アクティビティ領域を特定する
タイムラインをバケット(例:500 ミリ秒)に分割し、関連する関数名が含まれるバケットを見つけます。ユーザーの質問に関係するマーカー関数を使って、アクティビティの時間範囲を特定してください。領域をまとめるときは、短い空白(1~2 個の空のバケット)を許容します。
重要:サンプリングプロファイラーであるため、関数名を正確に指定する必要はありません。関連するマーカー関数のセットを使い、より大きな処理の流れを探してください。
5. マイルストーン間のタイミングを測定する
「X から Y までの時間」のような質問の場合:
- スタック上に X のマーカーを含む、最初のアイドルでないサンプルを見つけます
- スタック上に Y のマーカーを含む、最初のサンプルを見つけます
- 絶対タイムスタンプの差を、おおよその所要時間とします
- この間に何が処理されているか確認するため、両地点の間にあるアイドルでないサンプルをすべて列挙します
6. コードパスを比較する
2 つの実装を比較する場合:
- それぞれのアクティビティ領域を特定します
- 各領域について、関数ごとのセルフタイム(リーフフレームに割り当てられた時間)を算出します
- セルフタイムの降順で並べ、コストが最も高い処理を見つけます
- 各領域の最初の N 個のアイドルでないスタックを表示し、起動時の処理順を可視化します
7. 調査結果を報告する
結果は次の形式で提示します。
- タイムライン:プロファイル開始時点を基準とした各アクティビティ領域の発生時刻
- 期間:各領域の継続時間
- 自己時間が長い上位関数:実際に CPU 時間が費やされた箇所
- 比較表:パスを比較する場合の指標を並べた表
- スタックトレース:クリティカルパスを示す主要なサンプルスタック
パート 2:DevTools トレースファイル(Trace-*.json)
DevTools トレースは、VS Code のパフォーマンストレースの今後の標準です。組み込みの Electron/Chrome DevTools のパフォーマンスタブから作成され、.cpuprofile ファイルよりもはるかに多くの情報が含まれています。
トレース形式
Trace-*.json ファイルには、次のトップレベルキーがあります。
traceEvents:トレースイベントオブジェクトの配列(数十万件のエントリ)metadata:source、startTime、dataOriginと、オプションの DevTools 状態(ブレッドクラム、注釈)を含むオブジェクト
トレースイベントの構造
traceEvents 内の各イベントは、Chrome Trace Event Format に従います。
{
"pid": 3406, // Process ID
"tid": 7534980, // Thread ID
"ts": 200420830729, // Timestamp in microseconds
"ph": "X", // Phase (event type)
"cat": "devtools.timeline", // Category
"name": "EventDispatch", // Event name
"dur": 9, // Duration in microseconds (for complete events)
"tdur": 8, // Thread duration (excludes time thread was suspended)
"args": { ... }, // Event-specific arguments
"tts": 7078808 // Thread timestamp
}
フェーズの種類(ph)
| フェーズ | 名前 | 意味 |
|---|---|---|
X | 完了 | 期間(dur フィールド)を持つイベントです。最も一般的です。 |
B | 開始 | 期間イベントの開始です(E と対になります)。 |
E | 終了 | 期間イベントの終了です(B と対になります)。 |
I | インスタント | 特定時点のイベントです(期間なし)。 |
P | サンプル | CPU プロファイラーのサンプルです。 |
R | マーク | ナビゲーションタイミングのマークです。 |
M | メタデータ | プロセス/スレッド名のメタデータです。 |
N | オブジェクト作成 | オブジェクトのライフサイクルを追跡します。 |
D | オブジェクト破棄 | オブジェクトのライフサイクルを追跡します。 |
s | フロー開始 | 非同期フロー接続の開始です。 |
f | フロー終了 | 非同期フロー接続の終了です。 |
b | 非同期開始 | 非同期イベントの開始です。 |
e | 非同期終了 | 非同期イベントの終了です。 |
n | 非同期インスタント | 非同期イベントの特定時点です。 |
主なカテゴリとその内容
| カテゴリ | 記録される内容 |
|---|---|
disabled-by-default-devtools.timeline | RunTask、EvaluateScript、TracingStartedInBrowser -- コアのタスクスケジューリング |
devtools.timeline | FunctionCall、EventDispatch、TimerInstall/Fire、PrePaint、Paint -- メインスレッドのアクティビティ |
blink.user_timing | VS Code のパフォーマンスマーク(例:code/willResolveTextFileEditorModel、code/didResolveTextFileEditorModel) |
blink,devtools.timeline | UpdateLayoutTree、HitTest、IntersectionObserver、ParseAuthorStyleSheet -- レイアウト/レンダリング |
disabled-by-default-v8.cpu_profiler | Profile、ProfileChunk -- 埋め込み CPU プロファイルデータ(.cpuprofile と同じですが、チャンクに分割されています) |
v8 | v8.callFunction、v8.newInstance、V8.DeoptimizeCode -- V8 エンジンのイベント |
v8,devtools.timeline | v8.compile -- スクリプトのコンパイル |
devtools.timeline,v8 | MinorGC、MajorGC -- ガベージコレクション |
cppgc | C++ GC イベント(Blink のガベージコレクション) |
loading | LayoutShift、URLLoader -- リソースの読み込みとレイアウトシフト |
cc,benchmark,disabled-by-default-devtools.timeline.frame | フレームパイプラインのイベント(PipelineReporter、Commit など) |
__metadata | process_name、thread_name -- プロセス/スレッドの識別 |
プロセスとスレッド
トレースファイルには、複数のプロセスのイベントが含まれています。
| プロセス | 役割 | 主なスレッド |
|---|---|---|
| Renderer(pid は変動) | VS Code のレンダラープロセス -- JS が実行される場所 | CrRendererMain(メインスレッド) |
| Browser(pid は変動) | Electron のメイン/ブラウザープロセス | CrBrowserMain |
| GPU Process(pid は変動) | GPU の合成とレンダリング | CrGpuMain、VizCompositorThread |
メタデータイベントでプロセス/スレッドを特定します。
const procNames = events.filter(e => e.name === 'process_name');
// => [{args: {name: 'Renderer'}, pid: 3406}, {args: {name: 'Browser'}, pid: 3348}, ...]
const threadNames = events.filter(e => e.name === 'thread_name');
// => [{args: {name: 'CrRendererMain'}, pid: 3406, tid: 7534980}, ...]
VS Code のパフォーマンス分析では、Renderer プロセスの CrRendererMain スレッドに注目してください。ここで JavaScript の実行、レイアウト、描画が行われます。
手順
1. ファイルサイズを確認して解析する
トレースファイルは通常 50~200MB ですが、V8 の文字列制限(約 512MB)を超える場合があります。必ず最初に確認してください。
import { readFileSync, statSync } from 'fs';
const stat = statSync(tracePath);
const sizeMB = stat.size / (1024 * 1024);
console.log(`File size: ${sizeMB.toFixed(0)}MB`);
let data;
if (sizeMB < 400) {
data = JSON.parse(readFileSync(tracePath, 'utf8'));
} else {
// Too large -- use Buffer-based extraction (see "Handling Huge Files" section)
data = parseTraceFromBuffer(readFileSync(tracePath));
}
const events = data.traceEvents;
2. ファイルを再フォーマットする(小さいファイルのみ)
小さいトレースファイルの場合は、検査用に再フォーマットします:
if (sizeMB < 400) {
fs.writeFileSync(tracePath, JSON.stringify(data, null, 2));
}
3. データ構造を構築する
const data = JSON.parse(fs.readFileSync(tracePath, 'utf8'));
const events = data.traceEvents;
// Identify Renderer main thread
const rendererPid = events.find(e => e.name === 'process_name' && e.args?.name === 'Renderer')?.pid;
const mainTid = events.find(e => e.name === 'thread_name' && e.pid === rendererPid && e.args?.name === 'CrRendererMain')?.tid;
// Filter to main thread events for most analysis
const mainEvents = events.filter(e => e.pid === rendererPid && e.tid === mainTid);
4. ユーザータイミングマークを分析する
VS Code は performance.mark() 呼び出しを発行し、それらは blink.user_timing イベントとして現れます。これらは VS Code 固有のマイルストーンを測定する最も直接的な方法です:
const userTimings = events.filter(e => e.cat?.includes('blink.user_timing') && !e.cat.includes('rail'));
// Each has: name (e.g. 'code/didResolveTextFileEditorModel'), ts (microseconds), args.data.startTime (ms from navigation)
5. 長いタスクを分析する
メインスレッド上の高コストなタスクを見つけます:
const longTasks = mainEvents
.filter(e => e.name === 'RunTask' && e.ph === 'X' && e.dur > 50000) // > 50ms
.sort((a, b) => b.dur - a.dur);
6. 関数呼び出しを分析する
FunctionCall イベントにはソース位置情報が含まれます:
const funcCalls = mainEvents
.filter(e => e.name === 'FunctionCall' && e.dur > 10000) // > 10ms
.sort((a, b) => b.dur - a.dur);
// args.data contains: functionName, url, lineNumber, columnNumber, scriptId
7. レイアウトとレンダリングを分析する
レイアウトスラッシングと高コストなペイントを見つけます:
const layoutEvents = mainEvents.filter(e =>
e.name === 'UpdateLayoutTree' || e.name === 'Layout' ||
e.name === 'PrePaint' || e.name === 'Paint'
);
// UpdateLayoutTree.args.elementCount tells you how many elements were restyled
8. 埋め込まれた CPU プロファイルを抽出する
トレースファイルには、完全な CPU プロファイルが ProfileChunk イベントとして含まれています。それを再構築します:
const profileEvent = events.find(e => e.name === 'Profile' && e.pid === rendererPid);
const chunks = events.filter(e => e.name === 'ProfileChunk' && e.pid === rendererPid && e.id === profileEvent.id);
// Each chunk's args.data.cpuProfile contains: {nodes: [...], samples: [...]}
// Each chunk's args.data.timeDeltas contains sample timing
// Merge all chunks to reconstruct a full cpuprofile-like structure
const allNodes = [];
const allSamples = [];
const allDeltas = [];
for (const chunk of chunks) {
const cp = chunk.args.data.cpuProfile;
if (cp.nodes) allNodes.push(...cp.nodes);
if (cp.samples) allSamples.push(...cp.samples);
if (chunk.args.data.timeDeltas) allDeltas.push(...chunk.args.data.timeDeltas);
}
// Now analyze allNodes/allSamples/allDeltas using the same approach as .cpuprofile
9. GC プレッシャーを分析する
const gcEvents = mainEvents.filter(e => e.name === 'MinorGC' || e.name === 'MajorGC');
const totalGcTime = gcEvents.reduce((sum, e) => sum + (e.dur || 0), 0);
// Also check cppgc events for Blink GC
const cppgcEvents = events.filter(e => e.cat?.includes('cppgc'));
10. 入力レイテンシを分析する
const dispatches = mainEvents.filter(e => e.name === 'EventDispatch');
// args.data.type tells you the event type: 'click', 'keydown', 'mousedown', etc.
// dur tells you how long the handler took
const longHandlers = dispatches.filter(e => e.dur > 50000).sort((a, b) => b.dur - a.dur);
11. 調査結果を報告する
結果を次のように提示します:
- タイムライン: 各アクティビティ領域がトレース開始時点に対してどの時点で発生したか
- ユーザータイミングマーク: VS Code のマイルストーンイベントとそのタイムスタンプ
- 長いタスク: メインスレッドをブロックする 50ms 超のタスク
- 所要時間順の上位関数: CPU 時間がどこで消費されたか、ソース位置付き
- レイアウト/レンダリング: 高コストなスタイル再計算とペイント
- GC プレッシャー: GC の合計時間と頻度
- 入力レイテンシ: 応答性を低下させる低速なイベントハンドラー
- プロセスの内訳: Browser、Renderer、GPU のそれぞれでどのような処理が行われたか
巨大なファイルの扱い
.cpuprofile または Trace-*.json ファイルが約 400MB を超えると、V8 がそれほど大きな文字列を作成できないため、readFileSync(..., 'utf8') が失敗する可能性があります。代わりに Buffer ベースの抽出を使用してください。ファイルを生の Buffer として読み込み、既知の JSON キーをスキャンしてセクションを抽出します。これはヒープスナップショットで使用されているのと同じ手法です(parseSnapshot.ts を参照)。
重要な原則: ファイルを Buffer として読み込み、バイトをスキャンして JSON 配列/オブジェクトの境界を特定し、JSON.parse で扱える程度に小さい個々のセクションをサブバッファとして抽出し、その結果を組み立てます。
分析スクリプトは常に追加メモリを指定して実行してください:node --max-old-space-size=16384 script.mjs
.cpuprofile の Buffer ベース解析
.cpuprofile にはトップレベルのキーとして nodes、samples、timeDeltas、startTime、endTime があります。各セクションをバッファから抽出します:
import { readFileSync, statSync } from 'fs';
function parseProfileFromBuffer(buf) {
// Helper: find the array value for a given key, return parsed array
function extractArray(key) {
const keyBuf = Buffer.from(`"${key}":[`);
const pos = buf.indexOf(keyBuf);
if (pos === -1) throw new Error(`${key} not found`);
const arrayStart = pos + keyBuf.length;
// Find matching ']' -- arrays of numbers have no nested brackets
const arrayEnd = buf.indexOf(0x5D, arrayStart); // 0x5D = ']'
return JSON.parse('[' + buf.subarray(arrayStart, arrayEnd).toString('utf8') + ']');
}
// Helper: find a scalar value for a given key
function extractScalar(key) {
const keyBuf = Buffer.from(`"${key}":`);
const pos = buf.indexOf(keyBuf);
if (pos === -1) throw new Error(`${key} not found`);
const valueStart = pos + keyBuf.length;
// Scan to next comma or closing brace
let end = valueStart;
while (end < buf.length && buf[end] !== 0x2C && buf[end] !== 0x7D) end++;
return JSON.parse(buf.subarray(valueStart, end).toString('utf8'));
}
// Extract the nodes array -- contains objects, so we need bracket matching
function extractNodesArray() {
const keyBuf = Buffer.from('"nodes":[');
const pos = buf.indexOf(keyBuf);
if (pos === -1) throw new Error('nodes not found');
const start = pos + keyBuf.length - 1; // include '['
let depth = 0, end = -1;
for (let i = start; i < buf.length; i++) {
if (buf[i] === 0x5B) depth++;
else if (buf[i] === 0x5D) { depth--; if (depth === 0) { end = i + 1; break; } }
// Skip strings to avoid counting brackets inside them
if (buf[i] === 0x22) { i++; while (i < buf.length) { if (buf[i] === 0x5C) i++; else if (buf[i] === 0x22) break; i++; } }
}
if (end === -1) throw new Error('nodes array end not found');
return JSON.parse(buf.subarray(start, end).toString('utf8'));
}
const nodes = extractNodesArray();
const samples = extractArray('samples');
const timeDeltas = extractArray('timeDeltas');
const startTime = extractScalar('startTime');
const endTime = extractScalar('endTime');
return { nodes, samples, timeDeltas, startTime, endTime };
}
Trace-*.json の Buffer ベース解析
トレースファイルには 2 つのトップレベルキーがあります:metadata(小さいオブジェクト)と traceEvents(オブジェクトの巨大な配列)です。戦略は、metadata を通常どおり抽出し、traceEvents は個々のイベントオブジェクトをスキャンしてストリーム解析することです:
import { readFileSync } from 'fs';
function parseTraceFromBuffer(buf) {
// 1. Extract metadata (small, near the top of the file)
let metadata = {};
const metaKeyBuf = Buffer.from('"metadata":{');
const metaPos = buf.indexOf(metaKeyBuf);
if (metaPos !== -1) {
const metaStart = metaPos + metaKeyBuf.length - 1; // include '{'
let depth = 0, metaEnd = -1;
for (let i = metaStart; i < buf.length; i++) {
if (buf[i] === 0x7B) depth++;
else if (buf[i] === 0x7D) { depth--; if (depth === 0) { metaEnd = i + 1; break; } }
if (buf[i] === 0x22) { i++; while (i < buf.length) { if (buf[i] === 0x5C) i++; else if (buf[i] === 0x22) break; i++; } }
}
if (metaEnd !== -1) {
metadata = JSON.parse(buf.subarray(metaStart, metaEnd).toString('utf8'));
}
}
// 2. Extract traceEvents by parsing in chunks
// Each event is a JSON object {...}. Scan for object boundaries.
const eventsKeyBuf = Buffer.from('"traceEvents":[');
const eventsPos = buf.indexOf(eventsKeyBuf);
if (eventsPos === -1) throw new Error('traceEvents not found');
const arrayStart = eventsPos + eventsKeyBuf.length;
const traceEvents = [];
let i = arrayStart;
while (i < buf.length) {
// Skip whitespace and commas
while (i < buf.length && (buf[i] === 0x20 || buf[i] === 0x0A || buf[i] === 0x0D || buf[i] === 0x09 || buf[i] === 0x2C)) i++;
if (i >= buf.length || buf[i] === 0x5D) break; // end of array
if (buf[i] !== 0x7B) { i++; continue; } // expect '{'
// Find matching '}'
let depth = 0, objEnd = -1;
for (let j = i; j < buf.length; j++) {
if (buf[j] === 0x7B) depth++;
else if (buf[j] === 0x7D) { depth--; if (depth === 0) { objEnd = j + 1; break; } }
if (buf[j] === 0x22) { j++; while (j < buf.length) { if (buf[j] === 0x5C) j++; else if (buf[j] === 0x22) break; j++; } }
}
if (objEnd === -1) break;
// Parse this individual event object -- each is small enough for JSON.parse
traceEvents.push(JSON.parse(buf.subarray(i, objEnd).toString('utf8')));
i = objEnd;
}
return { metadata, traceEvents };
}
Buffer 解析をいつ使用するか
| ファイルサイズ | アプローチ |
|---|---|
| < 400MB | JSON.parse(readFileSync(path, 'utf8')) で問題ありません |
| 400MB - 1GB | 上記の Buffer ベースの抽出関数を使用してください |
| > 1GB | Buffer ベースの抽出 + --max-old-space-size=16384 を使用してください |
大きいファイルの分析のためのヒント
- Node.js に十分なヒープ領域を与えるため、常に
node --max-old-space-size=16384を指定して実行してください。 - トレースファイルの場合、メモリを削減するために解析中にイベントをフィルタリングすることを検討してください(例:不要なカテゴリをスキップする)。
- Buffer アプローチはファイル全体をバイトとしてメモリに読み込みます(これは問題ありません -- Buffer には約 512MB の文字列制限がありません)。個々の
JSON.parse呼び出しは小さいサブバッファに対して動作します。 - 再フォーマットすると書き戻すには大きすぎるファイルが作成される場合は、再フォーマットをスキップし、解析済みのデータ構造を直接扱ってください。
- 巨大なトレースから特定のイベントタイプだけが必要な場合は、解析ループにフィルターコールバックを追加して、破棄するオブジェクトの割り当てを避けてください。
ヒント
- どちらの形式のタイムスタンプもマイクロ秒です。ミリ秒に変換するには、1000 で割ってください。
- バンドル済み/ミニファイ済みのコード(例:
extension.js)では、関数名が難読化されていることがあります。callFrame.lineNumberの行番号を使って、ソースマップと照合してください。 - アクティブな CPU 処理を測定するときは、アイドル/プログラム/GC のサンプルを除外してください。
- ギャップについて質問された場合は、それが本当にアイドル状態(イベントループが待機中)なのか、それとも無関係なコードで処理が行われているのかを確認してください。
- 作成した分析スクリプトは、作業が終わったら削除してください。
- トレースファイルは大容量(50〜200MB)です。メモリ使用量とノイズを抑えるため、分析前に必ず関連するプロセス/スレッドに絞り込んでください。
- トレースファイルに埋め込みの
ProfileChunkイベントが含まれている場合、別途.cpuprofileを求めるよりも、そちらを分析してください。データは同等で、他のトレースイベントとの関連付けも済んでいます。 FunctionCallイベントとEvaluateScriptイベントのargs.data.urlを使って、VS Code のソースファイルに対応付けてください(vscode-file://vscode-app/Users/.../out/vs/...のようなパスです)。durフィールドは実時間での継続時間で、tdurフィールドはスレッド時間での継続時間です。この差から、スレッドが中断されていた時間(I/O 待ちやプリエンプションなど)がわかります。