# 帳票設計書 2-CPUプロファイルレポート（ツリーフォーマット）

## 概要

本ドキュメントは、Julia の `Profile.print(format=:tree)` によって出力される CPU プロファイルレポート（ツリーフォーマット）の設計仕様を記述する。

### 本帳票の処理概要

本帳票は、Julia プログラムの CPU プロファイリング結果をツリー形式（階層形式）で標準出力またはファイルに出力するレポートである。`@profile` マクロでバックトレースを収集し、`Profile.print()` または `Profile.print(format=:tree)` を呼び出すことで、コールスタックの階層構造を反映したツリー形式のレポートが出力される。ツリー形式はデフォルトの出力形式である。

**業務上の目的・背景**：Julia プログラムのパフォーマンスボトルネックを特定するために、関数の呼び出し階層と各階層でのサンプル数を可視化する必要がある。ツリー形式では、どの呼び出しパスが最も多くの CPU 時間を消費しているかをコールスタックの文脈で理解でき、ボトルネックの根本原因を追跡するのに適している。

**帳票の利用シーン**：開発者がアプリケーションのパフォーマンスチューニングを行う際に使用する。特に、ホットスポットがどの呼び出し経路から到達されているかを把握したい場合に利用される。スレッド・タスク別のグループ化もサポートされ、マルチスレッドプログラムの分析にも対応する。

**主要な出力内容**：
1. Overhead（オーバーヘッド）：各フレームが実行中の関数として出現した回数
2. Count（サンプル数）：各フレームがバックトレースに出現した回数
3. File:Line（ファイル名:行番号）：ソースファイルのパスと行番号
4. Function（関数名）：関数の名前
5. インデント：コールスタックの深さを表す階層構造
6. Total snapshots と Utilization

**帳票の出力タイミング**：`@profile` マクロで式を実行した後、`Profile.print()` を呼び出したとき（デフォルト）、または SIGUSR1/SIGINFO シグナルを受信したとき。

**帳票の利用者**：Julia プログラムの開発者、パフォーマンスエンジニア。

## 帳票種別

集計表（CPU プロファイリングサンプルのツリー構造レポート）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Julia REPL | julia> | `Profile.print()` または `Profile.print(format=:tree)` コマンド実行 |
| - | ターミナル | - | SIGUSR1/SIGINFO シグナル送信（デフォルトでツリー形式） |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（標準出力） / テキストファイル |
| 用紙サイズ | N/A（テキスト出力） |
| 向き | N/A |
| ファイル名 | 任意（`Profile.print(path)` で指定）|
| 出力方法 | 標準出力への表示 / ファイルへの書き出し |
| 文字コード | UTF-8 |

### テキスト出力固有設定

| 項目 | 内容 |
|-----|------|
| カラム幅 | ターミナル幅に自動調整（`displaysize(io)[2]`） |
| 色付き出力 | 対応（パッケージ名ごとに色分け、オーバーヘッド行は太字） |
| クリッカブルリンク | 対応（エディタ設定に応じた URI リンク生成） |
| インデント | `"    ╎"` を繰り返して深さを表現 |

## 帳票レイアウト

### レイアウト概要

ツリーフォーマットは、ヘッダー行、区切り行、階層的なデータ行、サマリー行で構成される。

```
┌──────────────────────────────────────────────────────────────────┐
│  Overhead ╎ [+additional indent] Count File:Line  Function       │  ← ヘッダー行
│  =========================================================       │  ← 区切り行
├──────────────────────────────────────────────────────────────────┤
│       150╎    500 @Base/array.jl:123  getindex(...)              │  ← ルートレベル
│          ╎     ╎    300 @MyPkg/src/main.jl:45  compute(...)      │  ← 深さ1
│          ╎     ╎     ╎    150 @MyPkg/src/utils.jl:10  helper()   │  ← 深さ2
│          ╎    200 @Base/boot.jl:1  eval(...)                     │  ← 別の枝
├──────────────────────────────────────────────────────────────────┤
│  Total snapshots: 500. Utilization: 85%                          │  ← サマリー行
└──────────────────────────────────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Overhead | 実行中フレームとしての出現回数 | `StackFrameTree.overhead` | 右寄せ整数（0の場合は空白） |
| 2 | インデント | コールスタックの深さ | 処理のネストレベル | `"    ╎"` の繰り返し |
| 3 | Count | バックトレースにおける出現回数 | `StackFrameTree.count` | 右寄せ整数 |
| 4 | File:Line | ソースファイルパスと行番号 | `short_path()` + `StackFrame.line` | パッケージ色付きパス:行番号（クリッカブル） |
| 5 | Function | 関数名 | `StackFrame.func` / `show_spec_linfo` | 文字列（スリープ中はグレー表示） |

### 明細部

明細部はツリー構造で出力され、各ノードが1行を構成する。深さに応じてインデントが増加する。

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Overhead | ノードのオーバーヘッド値 | `frame.overhead` | 右寄せ整数、0なら空白 | ndigits(maxes.overhead) |
| 2 | インデント | ネスト深さ | `level` | `"    ╎"` 繰り返し | min(cols>>1, level) |
| 3 | Count | ノードのカウント値 | `frame.count` | 右寄せ整数 | ndigits(maxes.count) |
| 4 | File:Line | ファイル名:行番号 | `short_path()` + `li.line` | パッケージ色付き | 2*ntext/5 |
| 5 | Function | 関数名 | `fname` | 文字列 | 残り幅 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Total snapshots | 合計サンプル数 | `root.count` | 整数 |
| 2 | Utilization | CPU利用率 | `(1 - nsleeping / root.count) * 100` | パーセント（整数） |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| format | `:tree`（デフォルト） | No |
| C | C/Fortran フレームを含めるか（デフォルト: false） | No |
| combine | 同一行のIPをマージするか（デフォルト: true） | No |
| maxdepth | 表示する最大深度（デフォルト: typemax(Int)） | No |
| mincount | 最小サンプル数フィルタ（デフォルト: 0） | No |
| noisefloor | ノイズフロアフィルタ（デフォルト: 0） | No |
| recur | 再帰処理モード（`:off`, `:flat`, `:flatc`） | No |
| groupby | グループ化（`:none`, `:thread`, `:task`, `[:thread, :task]`, `[:task, :thread]`） | No |
| threads | 対象スレッドID | No |
| tasks | 対象タスクID | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | sortedby=:filefuncline（デフォルト） | ファイル名→関数名→行番号 |
| 2 | sortedby=:count | サンプル数順 |
| 3 | sortedby=:overhead | オーバーヘッド順 |
| 4 | sortedby=:flat_count | フラットカウント順 |

### 改ページ条件

テキスト出力のため改ページは発生しない。深いネストは `+N` 表記で圧縮される。

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

### 参照テーブル一覧

本帳票はデータベースを参照しない。プロファイルデータは C ランタイムの内部バッファから取得される。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| C内部バッファ（jl_profile_get_data） | バックトレースデータの取得 | - |
| LineInfoDict | IP→スタックフレーム変換 | IP アドレスによるルックアップ |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| count | ツリーノードの通過回数（再帰制御付き） | なし（整数） | `tree!` 関数内で集計 |
| overhead | バックトレースのリーフでの出現回数 | なし（整数） | `parent.overhead += 1` |
| flat_count | ツリー内でのフラット出現回数 | なし（整数） | ルートまで遡って加算 |
| max_recur | 再帰の最大深度 | なし（整数） | recur モード使用時 |
| Utilization | `(1 - nsleeping / root.count) * 100` | `round(Int, ...)` | パーセント表示 |
| noisefloor_down | `floor(Int, noisefloor * sqrt(count))` | 切り捨て | ノイズフィルタ閾値 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A["Profile.print(format=:tree) 呼び出し"] --> B["fetch() でバックトレースデータ取得"]
    B --> C["getdict() で LineInfoDict 構築"]
    C --> D["print_group() で format 判定"]
    D --> E["tree() 関数呼び出し"]
    E --> F["tree!() で StackFrameTree 構築"]
    F --> G["print_tree() でツリー出力"]
    G --> H["tree_format() で各行フォーマット"]
    H --> I["サマリー行出力"]
    I --> J["終了"]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| バッファ未初期化 | プロファイルが一度も実行されていない | "The profiling data buffer is not initialized." | `@profile` マクロでプロファイルを実行する |
| サンプルなし | 収集されたサンプルが0件 | "There were no samples collected." | プログラムをより長く実行する |
| バッファフル | バッファが満杯 | "The profile data buffer is full" | `Profile.init()` でバッファサイズを拡大 |
| メタデータ欠落 | データにメタデータが含まれていない | "Profile data is missing required metadata" | 正常なプロファイルデータを使用 |
| 不正な recur 指定 | `:off`, `:flat`, `:flatc` 以外 | "recur value not recognized" | 正しい recur 値を指定 |
| 不正な groupby 指定 | サポート外の groupby 値 | "Unrecognized groupby option" | サポートされる値を指定 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 最大 10,000,000 サンプル |
| 目標出力時間 | データ量に依存（ツリー構築とフォーマットが律速） |
| 同時出力数上限 | 1 |

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

プロファイルレポートにはソースコードのファイルパス、関数名、行番号が含まれるため、外部に共有する際は機密情報の有無を確認すること。Windows ではメインスレッドのみのプロファイルに制限される旨の警告が出力される。

## 備考

- ツリー形式はデフォルトの出力形式である（`format=:tree`）。
- `recur=:flat` オプションで再帰呼び出しを圧縮し、イテレータに変換した場合の近似効果を確認できる。
- `noisefloor` パラメータ（推奨値: 2.0）でノイズを除去し、有意なサンプルのみを表示できる。
- スリープ中のフレームはグレー表示される。
- overhead > 0 のフレームは太字表示される。

---

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

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

### 推奨読解順序

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

まず、ツリー表現の中核である `StackFrameTree` 構造体を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `StackFrameTree{T}` 構造体（行974-990）で、count, overhead, flat_count, max_recur, sleeping, down（子ノード辞書）等のフィールドを理解する |
| 1-2 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `nmeta`（行44）と `META_OFFSET_*`（行215-218）でメタデータ構造を理解する |
| 1-3 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `ProfileFormat`（行194-212）でフォーマット設定パラメータを理解する |

**読解のコツ**: `StackFrameTree` は Prefix Trie（前方一致木）として設計されており、`down` フィールドが子ノードへの辞書、`up` が親ノードへの参照となっている。`builder_key`/`builder_value` は構築時の最適化用キャッシュである。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `print(io::IO, ...)` 関数（行271-361）がメインのエントリーポイント。`format=:tree` がデフォルト |

**主要処理フロー**:
1. **行275**: `format = :tree` がデフォルト値
2. **行287**: `ProfileFormat` 構造体生成
3. **行288-289**: `groupby=:none` の場合は `print_group` を直接呼び出し
4. **行297-300**: ツリーフォーマットの場合、ヘッダー行を出力

#### Step 3: ツリー構築処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `tree()` 関数（行1282-1313）でツリー出力の全体制御を理解する |
| 3-2 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `tree!()` 関数（行1080-1217）でバックトレースデータからツリーを構築するロジックを理解する |

**主要処理フロー**:
- **行1084-1087**: ワークリストとビルダーの初期化
- **行1091-1203**: バックトレースデータを後方から走査してツリーを構築
- **行1094-1136**: ブロック末尾検出とメタデータ読み取り
- **行1138-1158**: 再帰圧縮処理（recur モード）
- **行1160-1201**: ツリーノードの追加・更新

#### Step 4: ツリー出力処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `print_tree()` 関数（行1238-1280）でツリーの出力ロジックを理解する |
| 4-2 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `tree_format()` 関数（行1007-1077）で各行のフォーマット処理を理解する |

**主要処理フロー**:
- **行1241**: `maxstats` で最大値を取得（列幅計算用）
- **行1242-1244**: ヘッダー出力
- **行1246-1278**: worklist ベースの幅優先走査でツリーを出力
- **行1269-1277**: ソート順に従って子ノードを走査順に追加

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

```
Profile.print(io, data, lidict; format=:tree)
    |
    +-- ProfileFormat(...)                    # フォーマット設定構築
    |
    +-- print_group(io, data, lidict, ...)    # グループ出力制御
            |
            +-- tree(io, data, lidict, ...)   # ツリー形式出力
                    |
                    +-- tree!(root, all, ...)  # StackFrameTree 構築
                    |       |
                    |       +-- is_block_end() # ブロック末尾判定
                    |       +-- has_meta()     # メタデータ有無確認
                    |       +-- cleanup!()     # ビルダーキャッシュ清掃
                    |
                    +-- print_tree(io, bt, ...)# ツリー出力
                            |
                            +-- maxstats()     # 最大値統計
                            +-- tree_format()  # 行フォーマット
                            |       |
                            |       +-- short_path()   # パス短縮
                            |       +-- editor_link()  # エディタリンク
                            |
                            +-- indent()       # インデント生成
```

### データフロー図

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

C内部バッファ ──fetch()──> Vector{UInt64}
                               |
                        getdict() / lookup()
                               |
                        LineInfoDict
                               |
                         tree!(root, all, lidict)
                               |
                        StackFrameTree (Prefix Trie)
                               |
                         print_tree() + tree_format()
                               |
                        stdout / ファイル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Profile.jl | `stdlib/Profile/src/Profile.jl` | ソース | ツリープロファイルレポートの全ロジック（StackFrameTree, tree!, print_tree, tree_format） |
| timing.jl | `base/timing.jl` | ソース | `format_bytes` 等のユーティリティ関数 |
| stacktraces.jl | `base/stacktraces.jl` | ソース | `StackFrame` 型定義、`lookup` 関数 |
