# 帳票設計書 17-ProfilingPlugin プロファイルトレース

## 概要

本ドキュメントは、ビルドプロセスのプロファイリング情報をChrome DevTools形式（events.json）で出力する「ProfilingPlugin プロファイルトレース」の設計仕様を定義するものである。

### 本帳票の処理概要

ProfilingPlugin プロファイルトレースは、webpackビルドプロセス全体のプロファイリング情報を Chrome DevTools の Performance タブで読み込み可能な Trace Event Format で出力する帳票である。Compiler、Compilation、NormalModuleFactory、ContextModuleFactory、Parser、Generator、JavascriptModulesPlugin、CssModulesPlugin の全フックの実行時間をインターセプトして記録し、同時にNode.jsのCPUプロファイリング情報も収集する。

**業務上の目的・背景**：webpackビルドのパフォーマンスボトルネックを特定するために、各プラグインのフック実行時間を可視化する必要がある。Chrome DevToolsのPerformanceタブで直接開けるTrace Event Formatで出力することで、既存のプロファイリングツールとの親和性を確保する。

**帳票の利用シーン**：ビルドパフォーマンスの分析・最適化、プラグイン開発時のパフォーマンス測定、CI/CDパイプラインのビルド時間改善など。

**主要な出力内容**：
1. Trace Events - 各プラグインフックの開始/終了イベント（begin/end形式）
2. CPU Profile - Node.js inspectorによるCPUプロファイリング結果
3. メタイベント - TracingStartedInPage / TracingStartedInBrowser
4. EvaluateScript / CpuProfile イベント - DevTools互換のラッパーイベント

**帳票の出力タイミング**：webpackビルド完了後、`compiler.hooks.done` フック（stage: Infinity）で実行される。ウォッチモードでは出力されない。

**帳票の利用者**：ビルドエンジニア、プラグイン開発者、パフォーマンスエンジニア。

## 帳票種別

プロファイリングレポート（ビルドパフォーマンス分析用）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | webpack設定 | `plugins: [new ProfilingPlugin({ outputPath: 'events.json' })]` | ビルド実行時に自動出力 |
| - | Chrome DevTools | Performance タブ → Load profile | プロファイルファイル読み込み |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON（Chrome Trace Event Format） |
| 用紙サイズ | N/A（データ出力） |
| 向き | N/A |
| ファイル名 | デフォルト: `events.json`（options.outputPathで変更可能） |
| 出力方法 | 中間ファイルシステム（intermediateFileSystem）への直接書き込み（ストリーミング） |
| 文字コード | UTF-8 |

## 帳票レイアウト

### レイアウト概要

出力ファイルは Chrome Trace Event Format に準拠したJSON配列として出力される。

```
[
  { "name": "TracingStartedInPage", "ph": "I", ... },
  { "name": "TracingStartedInBrowser", "ph": "I", ... },
  { "name": "PluginName", "ph": "B", "cat": ["blink.user_timing"], ... },
  { "name": "PluginName", "ph": "E", "cat": ["blink.user_timing"], ... },
  ...
  { "name": "TaskQueueManager::ProcessTaskFromWorkQueue", "ph": "X", ... },
  { "name": "EvaluateScript", "ph": "X", ... },
  { "name": "CpuProfile", "ph": "I", ... }
]
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | TracingStartedInPage | ページトレース開始イベント | 静的データ（sessionId: "-1", page: "0xfff"） | instantEvent |
| 2 | TracingStartedInBrowser | ブラウザトレース開始イベント | 静的データ（sessionId: "-1"） | instantEvent |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | name | イベント名（プラグイン名） | tapInfo.name | 文字列 | - |
| 2 | ph | フェーズ（B=開始, E=終了, X=完了, I=瞬間） | イベント種別による | 文字 | - |
| 3 | cat | カテゴリ | "blink.user_timing" / "toplevel" / "devtools.timeline" | 文字列配列 | - |
| 4 | id | イベントID | tracer.counter（インクリメント） | 数値 | - |
| 5 | ts | タイムスタンプ（マイクロ秒） | process.hrtime() ベース | 数値 | - |
| 6 | dur | 持続時間（マイクロ秒） | cpuEndTime - cpuStartTime | 数値 | - |
| 7 | args | イベント引数 | イベント固有データ | オブジェクト | - |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | TaskQueueManager | CPU プロファイル開始マーカー | cpuStartTime | completeEvent |
| 2 | EvaluateScript | スクリプト評価イベント | cpuStartTime, cpuEndTime | completeEvent |
| 3 | CpuProfile | CPUプロファイル結果 | parsedResults.profile | instantEvent |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| ProfilingPlugin設定 | webpack設定のpluginsにProfilingPluginが含まれていること | Yes |
| 非ウォッチモード | compiler.watchMode が false であること | Yes |
| inspectorモジュール | Node.js inspector モジュールが利用可能であること（CPU Profile取得に必要） | No（なくても基本トレースは出力される） |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | イベント発生時刻順 | 時系列順 |

### 改ページ条件

N/A

## データベース参照仕様

### 参照テーブル一覧

本帳票はデータベースを参照せず、webpackのフックシステムを直接インターセプトする。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| compiler.hooks | Compilerフックのインターセプト | Object.keys(compiler.hooks) |
| compilation.hooks | Compilationフックのインターセプト | Reflect.has(instance, "hooks") |
| normalModuleFactory.hooks | NormalModuleFactoryフックのインターセプト | hooks対象 |
| Node.js inspector | CPUプロファイル情報 | Profiler.startProfiling / stopProfiling |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| _startTime | hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000) | 四捨五入（ナノ秒→マイクロ秒） | プロファイリング開始時刻 |
| endTime | hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000) | 四捨五入 | プロファイリング終了時刻 |
| タイムスタンプ調整 | duration = endTime - startTime, untracked = max(0, ownDuration - duration) | なし | inspector タイムスタンプのずれ補正 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[ProfilingPlugin.apply] --> B[createTrace で Tracer/Profiler 初期化]
    B --> C[profiler.startProfiling]
    C --> D[Compiler フック全体をインターセプト]
    D --> E[ResolverFactory フックをインターセプト]
    E --> F[compilation フックで追加インターセプト]
    F --> G[Compilation / NormalModuleFactory / ContextModuleFactory]
    G --> H[Parser / Generator / JS/CSS Modules Plugin]
    H --> I[ビルド実行中: 各フックの begin/end イベント記録]
    I --> J[compiler.hooks.done フック]
    J --> K{watchMode?}
    K -->|Yes| L[何もせず callback]
    K -->|No| M[profiler.stopProfiling]
    M --> N[CPU Profile 結果取得]
    N --> O[TaskQueueManager / EvaluateScript / CpuProfile イベント追加]
    O --> P[profiler.destroy]
    P --> Q[tracer.end → ストリーム終了]
    Q --> R[fsStream close → callback]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| inspector未対応 | Node.js 8.0未満の場合 | "Unable to CPU profile in < node 8.0" (console.log) | Node.js 8.0以上を使用 |
| inspectorセッション作成失敗 | inspector.Session() が例外をスローした場合 | なし（session = undefined に設定） | エラーは無視され、CPUプロファイルなしで動作 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | フック呼び出し回数に依存（数百〜数万イベント） |
| 目標出力時間 | ビルド完了後に非同期で出力 |
| 同時出力数上限 | N/A |

## セキュリティ考慮事項

プロファイリング結果にはプラグイン名、フック名、ファイルパス等のビルド環境情報が含まれる。外部公開すべきではない。

## 備考

- ProfilingPlugin 自身のフック（name === PLUGIN_NAME）はインターセプト対象外。ストリームのクリーンクローズを保証するため。
- Trace Event Format は Chrome DevTools の Performance タブで直接読み込み可能。
- CPU プロファイルの開始/終了タイムスタンプは process.hrtime() ベースだが、inspector のタイムスタンプとずれる場合があるため、補正処理が実装されている（Profiler.stopProfiling 148-158行目）。
- sync / async / promise の3種類のフックタイプに対応したプロファイリングラッパーを生成する。

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Trace Event Format とプロファイラーの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | Trace 型定義（165-171行目）: trace, counter, profiler, end |
| 1-2 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | Profiler クラス（68-162行目）: セッション管理、CPUプロファイリング |

**読解のコツ**: Profiler は Node.js inspector モジュールのラッパーで、CPU プロファイリングの開始・停止を管理する。Trace は chrome-trace-event ライブラリの Tracer インスタンスをラップする。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | ProfilingPlugin.apply()（254-371行目）。createTrace() でトレーサー初期化後、各フックをインターセプト |

**主要処理フロー**:
1. **255-259行目**: `createTrace()` で Tracer, Profiler, ストリームを初期化
2. **260行目**: `profiler.startProfiling()` でCPUプロファイリング開始
3. **263-269行目**: Compiler フック全体をインターセプト
4. **271-283行目**: ResolverFactory フックをインターセプト
5. **285-303行目**: compilation フックで Compilation, NormalModuleFactory, ContextModuleFactory, Parser, Generator, JS/CSS Plugin のフックをインターセプト
6. **307-369行目**: done フックで CPU プロファイル停止、結果出力、ストリーム終了

#### Step 3: フックインターセプトの仕組み

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | interceptAllHooksFor 関数（380-390行目）: 全フックのインターセプト |
| 3-2 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | makeInterceptorFor 関数（473-491行目）: インターセプター生成 |
| 3-3 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | makeNewProfiledTapFn 関数（502-586行目）: sync/async/promise の3種対応ラッパー |

**主要処理フロー**:
- **478-490行目**: register インターセプターで各タップのfnをプロファイル版にラップ
- **506-525行目**: promise型: begin → fn() → promise.then → end
- **526-549行目**: async型: begin → fn(args, callback(end))
- **550-582行目**: sync型: begin → fn() → end（ProfilingPlugin自身はスキップ）

#### Step 4: トレースファイル生成

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | createTrace 関数（178-236行目）: Tracer初期化、メタイベント出力、ストリーム接続 |

**主要処理フロー**:
- **179行目**: `new Tracer()` でChrome Trace Event形式のトレーサー生成
- **185行目**: `fs.createWriteStream(outputPath)` で出力ストリーム作成
- **189行目**: `trace.pipe(fsStream)` でトレーサーをストリームに接続
- **192-220行目**: TracingStartedInPage / TracingStartedInBrowser メタイベント出力

### プログラム呼び出し階層図

```
ProfilingPlugin.apply(compiler)
    │
    ├─ createTrace(fs, outputPath)
    │      ├─ new Tracer()
    │      ├─ new Profiler(inspector)
    │      ├─ fs.createWriteStream()
    │      └─ trace.pipe(fsStream)
    │
    ├─ profiler.startProfiling()
    │      ├─ new Session().connect()
    │      └─ Profiler.enable / Profiler.start
    │
    ├─ Compiler hooks インターセプト
    │      └─ makeInterceptorFor("Compiler", tracer)
    │             └─ makeNewProfiledTapFn(hookName, tracer, tapInfo)
    │
    ├─ compilation フック
    │      ├─ interceptAllHooksFor(compilation, tracer, "Compilation")
    │      ├─ interceptAllHooksFor(normalModuleFactory, tracer, "Normal Module Factory")
    │      ├─ interceptAllHooksFor(contextModuleFactory, tracer, "Context Module Factory")
    │      ├─ interceptAllParserHooks(normalModuleFactory, tracer)
    │      ├─ interceptAllGeneratorHooks(normalModuleFactory, tracer)
    │      ├─ interceptAllJavascriptModulesPluginHooks(compilation, tracer)
    │      └─ interceptAllCssModulesPluginHooks(compilation, tracer)
    │
    └─ compiler.hooks.done.tapAsync (stage: Infinity)
           ├─ profiler.stopProfiling()
           ├─ TaskQueueManager / EvaluateScript / CpuProfile イベント出力
           ├─ profiler.destroy()
           └─ tracer.end() → fsStream.close()
```

### データフロー図

```
[入力]                          [処理]                            [出力]

webpack Hook System          ProfilingPlugin                   events.json
(Compiler/Compilation/       フックインターセプト              (Chrome Trace Event Format)
 Factory/Parser/Generator)   ────────────────────▶             ├── メタイベント
       │                     begin/end イベント記録              ├── プラグインフックイベント
       │                                                       └── CpuProfile
       │
Node.js inspector ──────▶ CPU プロファイリング ──────▶ CpuProfile イベント
       │
intermediateFileSystem ◀─ createWriteStream + trace.pipe
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | ソース | プロファイリングのメインロジック |
| ModuleTypeConstants.js | `lib/ModuleTypeConstants.js` | ソース | パーサー/ジェネレーター対象のモジュールタイプ定数 |
| JavascriptModulesPlugin.js | `lib/javascript/JavascriptModulesPlugin.js` | ソース | JSモジュールフック提供（getCompilationHooks） |
| CssModulesPlugin.js | `lib/css/CssModulesPlugin.js` | ソース | CSSモジュールフック提供（getCompilationHooks） |
| ProfilingPlugin.json | `schemas/plugins/debug/ProfilingPlugin.json` | スキーマ | オプションバリデーション用スキーマ |
