# 通知設計書 29-Node.jsパフォーマンスレポート

## 概要

本ドキュメントは、Tailwind CSSのNode.js環境向けパッケージ（`@tailwindcss/node`）において、`process.hrtime.bigint()`を使用して処理時間を計測し、`process.stderr.write()`を使用してタイミング情報とヒットカウントのパフォーマンスレポートを出力する通知の設計を定義する。

### 本通知の処理概要

Node.js環境でのTailwind CSS実行時に、`Instrumentation`クラスを使用して処理の各フェーズのタイミング情報とヒット回数を収集し、DEBUGモード有効時にstderrにフォーマット済みのパフォーマンスレポートを出力する通知処理である。`Disposable`インターフェースを実装し、`using`構文によるスコープベースの自動レポート出力に対応している。

**業務上の目的・背景**：Tailwind CSSのCSS生成処理はコンパイル、スキャン、ビルド、最適化など複数のフェーズで構成される。開発者やパフォーマンスチューニング担当者がボトルネックを特定するために、各フェーズの処理時間とネスト関係を視覚的にわかりやすいレポートとして出力する機能が必要である。本Instrumentationクラスは、階層的なタイマー管理と自動レポート機能を提供する。

**通知の送信タイミング**：DEBUGモード（`env.DEBUG === true`）が有効な場合に、`Instrumentation`インスタンスのスコープ終了時（`Symbol.dispose`呼び出し時）に`report()`メソッドが自動実行される。または明示的に`report()`を呼び出すことも可能。

**通知の受信者**：CLIまたはビルドツール経由でビルドを実行している開発者。レポートはstderr（`process.stderr.write`）に出力されるため、標準出力とは分離されている。

**通知内容の概要**：ヒットカウント（`Hits:`セクション）とタイマー情報（`Timers:`セクション）の2つのセクションで構成されるレポート。タイマー情報は`[{経過時間}] {ラベル}`の形式で、ネスト関係が矢印（`↳`）で視覚的に表現される。時間はミリ秒単位で表示され、ANSI色コードによる装飾が施される。

**期待されるアクション**：開発者はレポートを確認し、処理時間が長いフェーズを特定してパフォーマンス最適化の対象を決定する。

## 通知種別

stderrテキスト出力（`process.stderr.write`）。DEBUGモード有効時のみ出力される。

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（process.stderr.write） |
| 優先度 | 低（デバッグ・パフォーマンス計測用） |
| リトライ | 無 |

### 送信先決定ロジック

`env.DEBUG === true`の場合にのみ`Symbol.dispose`によるレポート出力が実行される（103行目）。明示的な`report()`呼び出しの場合はDEBUG判定は呼び出し元の責任。

## 通知テンプレート

### メール通知の場合

本通知はメール送信ではなく、stderrテキスト出力である。

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | N/A |
| 送信元名称 | N/A |
| 件名 | N/A |
| 形式 | テキスト（stderr出力、ANSI色コード付き） |

### 本文テンプレート

```
Hits:
  {label} x {count}

Timers:
[{elapsed}ms] {label}
[{elapsed}ms]   ↳ {nested-label} x {count}
```

具体例：
```
Hits:
  some-event x 5

Timers:
[  45.23ms] [@tailwindcss/postcss] src/styles.css
[ 12.50ms]   ↳ Setup compiler
[  5.30ms]     ↳ PostCSS AST -> Tailwind CSS AST
[  7.20ms]     ↳ Create compiler
[ 20.00ms]   ↳ Scan for candidates
[ 10.00ms]   ↳ Build utilities
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| N/A | N/A | N/A | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| label | 計測対象の識別ラベル | `start(label)`の引数 | Yes |
| elapsed | 経過時間（ミリ秒） | `process.hrtime.bigint()`差分から算出 | Yes |
| count | ヒット回数 | `#hits`マップのカウント値 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| スコープ終了 | `Symbol.dispose`（usingスコープ終了） | `env.DEBUG === true` | DEBUGモード有効時に自動レポート出力 |
| 明示的呼び出し | `report(flush?)`メソッド | 呼び出し元の判断 | 任意のタイミングでのレポート出力 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| DEBUGモード無効 | `env.DEBUG`がfalseの場合、`Symbol.dispose`によるレポート出力は行われない |
| タイマー/ヒットが空 | start/hit/endが一度も呼ばれていない場合、空のレポートが出力される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A["Instrumentationインスタンス生成"] --> B["using I = new Instrumentation()"]
    B --> C["I.start(label)による計測開始"]
    C --> D["I.end(label)による計測終了"]
    D --> E{"スコープ終了（Symbol.dispose）"}
    E --> F{"env.DEBUG === true?"}
    F -->|"Yes"| G["report()呼び出し"]
    F -->|"No"| H["何もしない"]
    G --> I["未完了タイマーの自動end"]
    I --> J["ヒット情報のフォーマット"]
    J --> K["タイマー情報のフォーマット"]
    K --> L["process.stderr.write(output)"]
    L --> M["reset()でカウンタクリア"]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| N/A | N/A | データベースは使用しない |

### テーブル別参照項目詳細

本通知ではデータベースを参照しない。内部の`#hits`（DefaultMap）、`#timers`（DefaultMap）、`#timerStack`（配列）をインメモリで管理する。

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| N/A | N/A | データベース更新なし |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| タイマーラベル不一致 | `end(label)`のlabelが`#timerStack`の最後のエントリと一致しない | `Mismatched timer label`エラーをthrow。start/endのペアリングを修正する |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | N/A |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。DEBUGモード有効時のビルド処理完了後に出力される。

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

レポートにはファイルパスやラベル名が含まれる。stderrに出力されるため、ビルドログの管理に注意が必要。DEBUGモードは本番環境では無効にすべき。

## 備考

- `Instrumentation`クラスは`Disposable`インターフェースを実装しており、TypeScript 5.2の`using`構文でスコープベースの自動レポート出力に対応している（102-104行目）。
- タイマーは階層的に管理され、`//`で区切られた名前空間（例：`[@tailwindcss/postcss] src/styles.css//Setup compiler//Create compiler`）を持つ。
- `report()`メソッドは未完了のタイマーを自動的にend処理してからレポートを生成する（62-64行目）。
- レポート出力後に`reset()`が呼ばれ、内部カウンタがクリアされる（99行目）。
- ANSI色コードを使用した装飾：`dim`（薄い表示）、`blue`（青色）で視覚的な読みやすさを向上させている（107-113行目）。
- コンストラクタの`defaultFlush`引数で出力先を差し替え可能（16行目）。

---

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

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

### 推奨読解順序

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

Instrumentationクラスの内部データ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | instrumentation.ts (node) | `packages/@tailwindcss-node/src/instrumentation.ts` | 11-13行目の内部フィールド。`#hits`（ラベル->カウント）、`#timers`（ラベル->累計時間）、`#timerStack`（ネスト管理用スタック） |

**読解のコツ**: `DefaultMap`は存在しないキーにアクセスした際にデフォルト値を自動生成するMapラッパー。`#hits`と`#timers`はともにDefaultMapで、初期値としてそれぞれ`{ value: 0 }`と`{ value: 0n }`（BigInt）を返す。

#### Step 2: タイマー管理メソッドを理解する

`start`、`end`、`hit`メソッドの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | instrumentation.ts (node) | `packages/@tailwindcss-node/src/instrumentation.ts` | 19-21行目: `hit(label)` — ヒットカウントをインクリメント |
| 2-2 | instrumentation.ts (node) | `packages/@tailwindcss-node/src/instrumentation.ts` | 23-33行目: `start(label)` — 名前空間付きIDを生成し、ヒットカウント、タイマー初期化、スタックpush |
| 2-3 | instrumentation.ts (node) | `packages/@tailwindcss-node/src/instrumentation.ts` | 35-49行目: `end(label)` — スタックトップのラベル検証、経過時間計算、累計時間加算 |

**主要処理フロー**:
1. **24行目**: `namespace`をtimerStackから構築（`//`区切り）
2. **25行目**: `id`を`namespace//label`形式で生成
3. **27行目**: ヒットカウントをインクリメント
4. **30行目**: タイマーを作成（未存在の場合）
5. **32行目**: スタックに`{ id, label, namespace, value: process.hrtime.bigint() }`をpush
6. **36行目**: `end(label)`で`process.hrtime.bigint()`取得
7. **38行目**: スタックトップのラベルと一致するか検証
8. **46-48行目**: スタックpop、経過時間計算、累計時間加算

#### Step 3: レポート生成処理を理解する

`report()`メソッドのフォーマットロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | instrumentation.ts (node) | `packages/@tailwindcss-node/src/instrumentation.ts` | 57-99行目: `report(flush)` — 未完了タイマーの自動end、ヒットセクション、タイマーセクションのフォーマット |

**主要処理フロー**:
- **62-64行目**: 未完了タイマーの自動end（逆順でスタック処理）
- **66-75行目**: ヒットセクション出力（タイマーに含まれないラベルのみ）
- **81-87行目**: タイマーの経過時間をミリ秒に変換し、最大桁数を計算
- **89-96行目**: タイマーセクション出力（経過時間+ネスト表示+ヒット回数）
- **98行目**: `flush`関数でstderrに出力
- **99行目**: `reset()`でカウンタクリア

#### Step 4: DEBUGモード判定を理解する

DEBUGモードの判定ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | env.ts | `packages/@tailwindcss-node/src/env.ts` | 1-44行目: `DEBUG`変数の解決ロジック。`process.env.DEBUG`の値に応じてtrue/falseを返す |

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

```
Instrumentation (node版) (instrumentation.ts:10)
    |
    +-- constructor(defaultFlush) (instrumentation.ts:15-17)
    |
    +-- hit(label) (instrumentation.ts:19-21)
    |      +-- #hits.get(label).value++
    |
    +-- start(label) (instrumentation.ts:23-33)
    |      +-- namespace構築 (24-25行目)
    |      +-- #hits.get(id).value++ (27行目)
    |      +-- #timers.get(id) (30行目)
    |      +-- #timerStack.push({id, label, namespace, value}) (32行目)
    |
    +-- end(label) (instrumentation.ts:35-49)
    |      +-- ラベル検証 (38行目)
    |      +-- #timerStack.pop() (46行目)
    |      +-- #timers.get(id).value += elapsed (48行目)
    |
    +-- report(flush?) (instrumentation.ts:57-99)
    |      +-- 未完了タイマー自動end (62-64行目)
    |      +-- ヒットセクション構築 (66-75行目)
    |      +-- タイマーセクション構築 (81-96行目)
    |      +-- flush(output) (98行目)
    |      +-- reset() (99行目)
    |
    +-- [Symbol.dispose]() (instrumentation.ts:102-104)
           +-- env.DEBUG && this.report()
```

### データフロー図

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

I.start("label")          process.hrtime.bigint()        process.stderr.write()
I.end("label")    ───>    経過時間計算            ───>    "[  45.23ms] label x 3"
I.hit("label")            ヒットカウント                  ↓
                          タイマー累積                  stderr
                          レポートフォーマット          (DEBUGモード有効時のみ)

env.DEBUG ───>            Symbol.dispose判定
                          report()トリガー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| instrumentation.ts (node) | `packages/@tailwindcss-node/src/instrumentation.ts` | ソース | Node.js版Instrumentationクラスの実装（全113行） |
| env.ts | `packages/@tailwindcss-node/src/env.ts` | ソース | DEBUG環境変数の解決ロジック |
| default-map.ts | `packages/tailwindcss/src/utils/default-map.ts` | ソース | DefaultMapユーティリティ（#hitsと#timersで使用） |
| index.ts (postcss) | `packages/@tailwindcss-postcss/src/index.ts` | ソース | Instrumentationの使用例（using I = new Instrumentation()） |
