# 機能設計書 69-プロファイリング

## 概要

本ドキュメントは、webpackの開発ツールである `ProfilingPlugin` の機能設計を記述する。`ProfilingPlugin` はコンパイルの各フェーズの実行時間を計測し、Chrome DevToolsで解析可能なトレースファイルを生成する。

### 本機能の処理概要

ProfilingPluginは、webpackのコンパイルプロセス全体にわたって、各プラグインの実行時間をChrome Trace Event形式で記録し、CPUプロファイルと組み合わせた解析用ファイルを生成する機能を提供する。chrome-trace-eventライブラリとNode.jsのinspectorモジュールを使用する。

**業務上の目的・背景**：webpackのビルドパフォーマンスを最適化するためには、どのプラグインやどのフェーズに時間がかかっているかを正確に把握する必要がある。ProfilingPluginは、Chrome DevToolsのPerformanceタブで読み込み可能なトレースファイルを生成し、ビルドプロセスの詳細なパフォーマンス分析を可能にする。

**機能の利用シーン**：ビルドパフォーマンスのボトルネック特定、プラグイン実行時間の計測、カスタムプラグインのパフォーマンスチューニング、CI/CDでのビルド時間の回帰テストに利用される。

**主要な処理内容**：
1. Tracerインスタンスを生成し、出力ファイルへのストリームを設定する
2. Node.jsのinspectorモジュールを使用してCPUプロファイリングを開始する
3. Compiler、Compilation、NormalModuleFactory、ContextModuleFactory等の全フックにインターセプトを設定する
4. 各プラグインの実行をsync/async/promiseの種別に応じてラップし、begin/endイベントを記録する
5. Parser、Generator、JavascriptModulesPlugin、CssModulesPluginのフックにもインターセプトを設定する
6. コンパイル完了時にCPUプロファイルを停止し、トレースファイルに統合して出力する
7. watchモード時はプロファイル出力をスキップする

**関連システム・外部連携**：chrome-trace-eventライブラリ、Node.js inspectorモジュール、IntermediateFileSystemと連携する。

**権限による制御**：権限による制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能に直接関連する画面はない（出力ファイルはChrome DevToolsで閲覧） |

## 機能種別

計算処理 / データ連携（パフォーマンス計測・トレースファイル出力）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| outputPath | `string` | No | トレースファイルの出力パス | デフォルト: "events.json" |

### 入力データソース

- webpack設定ファイルのpluginsセクション
- コンパイラおよびコンパイレーションの各種フック
- Node.js inspectorによるCPUプロファイルデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| events.json | `JSON` | Chrome Trace Event形式のトレースファイル。各プラグインのbegin/endイベント、CPUプロファイルデータを含む |

### 出力先

IntermediateFileSystem経由でファイルシステムに出力。デフォルトは `events.json`。

## 処理フロー

### 処理シーケンス

```
1. createTrace()でTracer、Profiler、出力ストリームを初期化
   └─ chrome-trace-event Tracerインスタンスの生成
   └─ Node.js inspector Sessionの生成
   └─ ファイルストリームの設定
2. CPUプロファイリング開始
   └─ Profiler.setSamplingInterval(100)
   └─ Profiler.enable → Profiler.start
3. Compilerフックのインターセプト設定
   └─ compiler.hooksの全フックにregisterインターセプトを設定
4. ResolverFactory フックのインターセプト設定
5. compilationフックで追加のインターセプト設定
   └─ Compilationの全フック
   └─ NormalModuleFactory/ContextModuleFactoryの全フック
   └─ 各モジュールタイプのParser/Generatorフック
   └─ JavascriptModulesPlugin/CssModulesPluginフック
6. 各プラグイン実行時にbegin/endイベントを記録
   └─ sync: try/finally でbegin/end
   └─ async: コールバックラップでbegin/end
   └─ promise: then()でbegin/end
7. doneフックでCPUプロファイルを停止し出力
   └─ Profiler.stop → cpuProfile取得
   └─ トレースにCpuProfile/EvaluateScriptイベントを追加
   └─ ストリームを閉じてファイル出力完了
```

### フローチャート

```mermaid
flowchart TD
    A[ProfilingPlugin.apply] --> B[createTrace - Tracer+Profiler初期化]
    B --> C[CPUプロファイリング開始]
    C --> D[Compilerフック全体にインターセプト設定]
    D --> E[compilationフックでサブシステムにインターセプト設定]
    E --> F[ビルド実行中: 各プラグインのbegin/endイベント記録]
    F --> G{watchモード?}
    G -->|Yes| H[プロファイル出力スキップ]
    G -->|No| I[CPUプロファイル停止]
    I --> J[トレースにCPUプロファイルイベント追加]
    J --> K[ストリーム閉じ + ファイル出力]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 自己インターセプト除外 | ProfilingPlugin自身のフック呼び出しはプロファイル対象外 | tapInfo.name === "ProfilingPlugin"の場合 |
| BR-02 | watchモード除外 | watchモード時はプロファイル出力をスキップする | compiler.watchModeがtrueの場合 |
| BR-03 | タイムスタンプ調整 | process.hrtimeとinspectorのタイムスタンプが異なる基準時刻を持つ場合、調整を行う | プロファイルのstartTime/endTimeがずれている場合 |
| BR-04 | サンプリング間隔 | CPUプロファイルのサンプリング間隔は100マイクロ秒 | 常時 |
| BR-05 | fakeHook除外 | _fakeHookプロパティを持つフックはインターセプト対象外 | interceptAllHooksForで |
| BR-06 | 3種のプラグインタイプ対応 | sync, async, promiseの3種のプラグインタイプに対応したラッパーを生成 | makeNewProfiledTapFnで |

### 計算ロジック

タイムスタンプ（マイクロ秒単位）:
```
hrtime = process.hrtime()
timestamp = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000)
```

タイムスタンプ調整:
```
duration = profile.endTime - profile.startTime
ownDuration = endTime - _startTime
untracked = max(0, ownDuration - duration)
adjustedStartTime = _startTime + untracked / 2
adjustedEndTime = endTime - untracked / 2
```

## データベース操作仕様

データベース操作は存在しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ランタイムエラー | Node.js 8未満でinspectorモジュールが利用不可 | Node.js 8以上を使用する |
| - | セッションエラー | inspector Sessionの接続に失敗した場合 | sessionをundefinedに設定し、CPUプロファイルなしで続行 |

### リトライ仕様

リトライ仕様は存在しない。inspector Session接続失敗時はCPUプロファイルなしで続行する。

## トランザクション仕様

トランザクション仕様は存在しない。

## パフォーマンス要件

- プロファイリング自体がビルドパフォーマンスにオーバーヘッドを与えるため、本番ビルドでの使用は推奨されない
- CPUサンプリング間隔100マイクロ秒は適度な精度とオーバーヘッドのバランスを提供する

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

- 出力されるトレースファイルにはプラグイン名やモジュールパスが含まれるため、機密情報が含まれる可能性がある
- トレースファイルのアクセス制御を適切に設定すること

## 備考

- 出力ファイルはChrome DevToolsの「Performance」タブまたは `chrome://tracing` で読み込み可能
- chrome-trace-eventライブラリのTracer API（completeEvent, instantEvent, begin, end）を使用する
- Node.jsのinspector APIが利用できない環境では、CPUプロファイルなしでトレースイベントのみ記録される
- doneフックのstageをInfinityに設定し、最後に実行されるようにしている

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | `Trace` 型（164-171行目）: trace, counter, profiler, end関数 |
| 1-2 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | `Profiler` クラス（68-162行目）: inspector Sessionのラッパー |

**読解のコツ**: ProfilerクラスはNode.jsのinspector APIを抽象化し、CPUプロファイルの開始・停止・コマンド送信を管理する。Traceオブジェクトはchrome-trace-event TracerとProfilerを統合する。

#### Step 2: トレース初期化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | `createTrace()` 関数（178-236行目）- Tracer/Profiler/ストリームの初期化 |

**主要処理フロー**:
1. **179行目**: new Tracer()
2. **180行目**: new Profiler(inspector)
3. **185行目**: fs.createWriteStream(outputPath)
4. **189行目**: trace.pipe(fsStream)
5. **192-220行目**: Chrome DevTools互換のメタイベント出力

#### Step 3: インターセプト設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | `apply()` メソッド（254-371行目）- フックインターセプト設定 |
| 3-2 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | `makeInterceptorFor()` 関数（473-491行目）- インターセプタ生成 |
| 3-3 | ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | `makeNewProfiledTapFn()` 関数（502-586行目）- プロファイル付きtap関数 |

**主要処理フロー**:
- **263-269行目**: Compilerの全フックにインターセプト
- **285-303行目**: compilation内のサブシステムにインターセプト
- **307-369行目**: doneフックでCPUプロファイル停止・出力

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

```
ProfilingPlugin.apply(compiler)
    |
    +-- createTrace(fs, outputPath)
    |       |
    |       +-- new Tracer()
    |       +-- new Profiler(inspector).startProfiling()
    |       +-- fs.createWriteStream(outputPath)
    |
    +-- compiler.hooks[*].intercept(makeInterceptorFor("Compiler", tracer))
    |
    +-- compiler.resolverFactory.hooks[*].intercept()
    |
    +-- compiler.hooks.compilation.tap()
    |       |
    |       +-- 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()
            +-- trace.completeEvent("EvaluateScript")
            +-- trace.instantEvent("CpuProfile")
            +-- profiler.destroy()
            +-- tracer.end(callback)
```

### データフロー図

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

各フック実行 ────────────▶ makeNewProfiledTapFn ──▶ tracer.trace.begin/end ──▶ events.json
(sync/async/promise)     (ラップ＆計測)

inspector API ──────────▶ Profiler ──────────────▶ CpuProfile ──────────────▶ events.json
(CPUサンプリング)          (start/stop)              (Chrome互換形式)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ProfilingPlugin.js | `lib/debug/ProfilingPlugin.js` | ソース | 本機能のメインプラグイン（590行） |
| ModuleTypeConstants.js | `lib/ModuleTypeConstants.js` | ソース | モジュールタイプ定数（Parser/Generatorインターセプト対象） |
| ProfilingPlugin.json | `schemas/plugins/debug/ProfilingPlugin.json` | 設定 | バリデーションスキーマ |
