# 機能設計書 46-Stopwatch

## 概要

本ドキュメントは、Symfony Stopwatchコンポーネントの機能設計を記述する。Stopwatchはコードのプロファイリング機能を提供し、実行時間計測、メモリ使用量の計測、セクション管理によるコード実行のパフォーマンス分析を実現するコンポーネントである。

### 本機能の処理概要

Stopwatchコンポーネントは、コードブロックの実行時間とメモリ使用量を計測するための軽量プロファイリングツールを提供する。イベント（StopwatchEvent）、ピリオド（StopwatchPeriod）、セクション（Section）の3階層構造で計測データを管理する。

**業務上の目的・背景**：アプリケーションのパフォーマンスボトルネックを特定するためには、コードの各部分の実行時間を正確に計測する必要がある。Stopwatchコンポーネントは、HTTPリクエスト処理のタイムライン分析、データベースクエリの実行時間計測、テンプレートレンダリングの性能測定等のプロファイリングニーズに対応する。

**機能の利用シーン**：WebProfilerBundleのパフォーマンスタイムライン表示、HTTPカーネルのイベント処理時間計測、Doctrineクエリの実行時間計測、カスタムコードのパフォーマンス計測、CI/CDパイプラインでの性能回帰テスト等。

**主要な処理内容**：
1. イベントの開始（start）と停止（stop）による時間計測
2. ラップ（lap）によるイベントのピリオド分割計測
3. セクションの開始・停止による計測のグループ化
4. 実行時間（ミリ秒）とメモリ使用量（バイト）の記録
5. 高精度モード（morePrecision）でのマイクロ秒精度対応

**関連システム・外部連携**：HttpKernelコンポーネント（TimeDataCollector）、WebProfilerBundle（パフォーマンスタイムラインパネル）と連携する。

**権限による制御**：Stopwatchコンポーネントには権限制御機構はない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | パフォーマンス（時間）パネル | 主画面 | Stopwatchコンポーネントで計測した実行時間データの表示 |

## 機能種別

パフォーマンス計測 / プロファイリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | イベント名 | - |
| category | string\|null | No | イベントカテゴリ（デフォルト: "default"） | - |
| morePrecision | bool | No | マイクロ秒精度有効化（デフォルト: false） | - |
| id | string | No（セクション用） | セクションID | - |

### 入力データソース

- アプリケーションコードからの直接呼び出し
- HttpKernelイベントリスナーからの自動計測
- DIコンテナ経由のStopwatchサービス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| StopwatchEvent | StopwatchEvent | イベント計測データ（duration、memory、periods） |
| duration | int\|float | 実行時間（ミリ秒） |
| memory | int | メモリ使用量（バイト） |
| StopwatchPeriod[] | StopwatchPeriod[] | ピリオド（ラップ）一覧 |

### 出力先

- アプリケーションコード（計測結果取得）
- WebProfilerBundleパフォーマンスタイムライン
- TimeDataCollector（HTTPカーネル計測データ）

## 処理フロー

### 処理シーケンス

```
1. Stopwatch::start(name, category)
   └─ Section::startEvent() でイベント生成
   └─ StopwatchEvent::start() でピリオド開始
2. [計測対象コードの実行]
3. Stopwatch::stop(name)
   └─ Section::stopEvent() でイベント停止
   └─ StopwatchEvent::stop() でStopwatchPeriod生成
4. 結果取得
   └─ StopwatchEvent::getDuration() で合計実行時間
   └─ StopwatchEvent::getMemory() で最大メモリ使用量
```

### フローチャート

```mermaid
flowchart TD
    A[start name, category] --> B[Section::startEvent]
    B --> C[StopwatchEvent::start]
    C --> D[計測対象コード実行]
    D --> E{lap?}
    E -->|Yes| F[stop + start = lap]
    F --> D
    E -->|No| G[stop name]
    G --> H[Section::stopEvent]
    H --> I[StopwatchEvent::stop]
    I --> J[StopwatchPeriod生成]
    J --> K[getDuration / getMemory]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-46-01 | 開始前停止不可 | start()なしでstop()を呼ぶとLogicException | StopwatchEvent::stop()時 |
| BR-46-02 | セクション階層 | セクションはネスト可能だが、ルートセクションの停止は不可 | 1つしかactiveSectionsがない場合 |
| BR-46-03 | イベント再利用 | 同名イベントのstart()は既存イベントに新ピリオドを追加する | Section::startEvent()時 |
| BR-46-04 | 時間精度 | morePrecision=falseの場合は小数点1桁に丸められる | formatTime()で処理 |

### 計算ロジック

- 実行時間: `microtime(true) * 1000 - origin`（**StopwatchEvent.php 215行目**）
- メモリ使用量: StopwatchPeriod生成時にmemory_get_usage(true)で取得

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

Stopwatchコンポーネントはデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | LogicException | start()なしでstop()を呼んだ場合 | start()を先に呼ぶ |
| - | LogicException | 未知のイベント名でgetEvent()を呼んだ場合 | 正しいイベント名を使用する |
| - | LogicException | 停止するセクションがない場合 | セクションの開始/停止を正しくネストする |
| - | LogicException | 別レベルで開始されたセクションを開こうとした場合 | セクションの階層を確認する |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- Stopwatch自体のオーバーヘッドは最小限に設計されている
- microtime(true)を使用した高精度計測

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

- プロファイリングデータは本番環境では無効化することを推奨
- パフォーマンスデータの外部公開に注意が必要

## 備考

- ResetInterfaceを実装し、reset()で全計測データをクリア可能
- ROOT定数（'__root__'）がルートセクションの識別子

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | StopwatchPeriod.php | `src/Symfony/Component/Stopwatch/StopwatchPeriod.php` | 最小単位のデータ構造（startTime、endTime、memory） |
| 1-2 | StopwatchEvent.php | `src/Symfony/Component/Stopwatch/StopwatchEvent.php` | イベントデータ構造（periods配列、origin、category） |
| 1-3 | Section.php | `src/Symfony/Component/Stopwatch/Section.php` | セクション構造（events辞書、children配列） |

**読解のコツ**: StopwatchPeriod -> StopwatchEvent -> Section -> Stopwatch の階層構造を理解することが重要。originはセクション開始時のタイムスタンプで、イベントの相対時間計算の基準点となる。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Stopwatch.php | `src/Symfony/Component/Stopwatch/Stopwatch.php` | メインのAPIクラス |

**主要処理フロー**:
1. **41-45行目**: コンストラクタでmorePrecision設定とreset()呼び出し
2. **99-102行目**: start() - activeSectionsの末尾セクションでstartEvent()
3. **115-118行目**: stop() - activeSectionsの末尾セクションでstopEvent()
4. **123-126行目**: lap() - stop()してstart()を連続実行
5. **62-73行目**: openSection() - 子セクションの作成/再開
6. **84-94行目**: stopSection() - セクション停止とID割り当て
7. **159-162行目**: reset() - sections/activeSectionsを初期化

#### Step 3: イベント計測を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | StopwatchEvent.php | `src/Symfony/Component/Stopwatch/StopwatchEvent.php` | イベントの計測ロジック |

**主要処理フロー**:
- **74-79行目**: start() - 現在時刻をstarted配列に追加
- **88-97行目**: stop() - started配列からポップし、StopwatchPeriodを生成
- **112-115行目**: lap() - stop().start()の連鎖
- **178-193行目**: getDuration() - 全ピリオド+未停止ピリオドの合計時間
- **199-208行目**: getMemory() - 全ピリオドの最大メモリ使用量

#### Step 4: セクション管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Section.php | `src/Symfony/Component/Stopwatch/Section.php` | セクションのイベント管理 |

**主要処理フロー**:
- **91-98行目**: startEvent() - 既存イベント再利用または新規作成してstart()
- **113-120行目**: stopEvent() - イベントのstop()呼び出し

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

```
Stopwatch
    |
    ├─ start(name, category)
    |      └─ Section::startEvent(name, category)
    |             └─ StopwatchEvent::start()
    |
    ├─ stop(name)
    |      └─ Section::stopEvent(name)
    |             └─ StopwatchEvent::stop()
    |                    └─ new StopwatchPeriod(start, end)
    |
    ├─ lap(name)
    |      └─ stop() + start()
    |
    ├─ openSection(id)
    |      └─ Section::open(id)
    |
    └─ stopSection(id)
           └─ Section::setId(id)
```

### データフロー図

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

start(name)          Stopwatch                       StopwatchEvent
  → 開始時刻記録     ├─ Section管理                   ├─ duration (ms)
                     ├─ Event管理                     ├─ memory (bytes)
stop(name)           └─ Period生成                    └─ periods[]
  → 終了時刻記録                                          ├─ startTime
                                                          ├─ endTime
microtime(true)      StopwatchPeriod                      └─ memory
memory_get_usage()   └─ 時間差とメモリ記録
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Stopwatch.php | `src/Symfony/Component/Stopwatch/Stopwatch.php` | ソース | メインAPIクラス |
| StopwatchEvent.php | `src/Symfony/Component/Stopwatch/StopwatchEvent.php` | ソース | イベント計測データ |
| StopwatchPeriod.php | `src/Symfony/Component/Stopwatch/StopwatchPeriod.php` | ソース | ピリオドデータ |
| Section.php | `src/Symfony/Component/Stopwatch/Section.php` | ソース | セクション管理 |
