# 帳票設計書 4-ヒープスナップショット

## 概要

本ドキュメントは、Julia の `Profile.take_heap_snapshot()` によって出力されるヒープスナップショットの設計仕様を記述する。

### 本帳票の処理概要

本帳票は、Julia プロセスのヒープメモリの状態を `.heapsnapshot` 形式（Chrome DevTools 互換 JSON）でファイルに出力するレポートである。ヒープ上の全オブジェクトのサイズ、型、参照関係を記録し、メモリリークや予期しないオブジェクト保持の分析に利用できる。

**業務上の目的・背景**：Julia プログラムのメモリリークや予期しないメモリ保持を診断するために、ある時点でのヒープメモリの全容をスナップショットとして記録する必要がある。Chrome DevTools のヒープスナップショットビューアと互換性のある形式で出力することで、既存のブラウザベースのツールを使って視覚的にメモリ分析が可能になる。

**帳票の利用シーン**：メモリ使用量が予想以上に増加している場合や、GC で回収されないオブジェクトが存在する疑いがある場合に使用する。長時間稼働するサーバープロセスのメモリ監視や、メモリリークのデバッグに特に有用である。

**主要な出力内容**：
1. ノード情報：ヒープ上の各オブジェクトのメタデータ（型、サイズ、エッジ数等）
2. エッジ情報：オブジェクト間の参照関係
3. 文字列テーブル：ノード・エッジで使用される文字列
4. メタデータ：スナップショットのスキーマ情報（JSON）

**帳票の出力タイミング**：`Profile.take_heap_snapshot()` を明示的に呼び出したとき、または `JULIA_PROFILE_PEEK_HEAP_SNAPSHOT=true` 設定時に SIGUSR1/SIGINFO シグナルを受信したとき。

**帳票の利用者**：Julia プログラムの開発者、メモリデバッグエンジニア、運用監視担当者。

## 帳票種別

スナップショット（ヒープメモリの状態ダンプ）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Julia REPL | julia> | `Profile.take_heap_snapshot()` コマンド実行 |
| - | Chrome DevTools | chrome://devtools | Memory タブでファイルを読み込み |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON（.heapsnapshot）/ バイナリストリーミング |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | `{pid}_{timestamp}.heapsnapshot`（デフォルト）または指定パス |
| 出力方法 | ファイル書き出し |
| 文字コード | UTF-8 |

### ストリーミング出力設定

| 項目 | 内容 |
|-----|------|
| streaming=false（デフォルト） | 一括出力（メモリ内で組み立て後に書き出し） |
| streaming=true（推奨） | 4ファイルに分割ストリーミング出力 |
| ストリーミングファイル | `{prefix}.nodes`, `{prefix}.edges`, `{prefix}.strings`, `{prefix}.metadata.json` |

## 帳票レイアウト

### レイアウト概要

ヒープスナップショットは Chrome DevTools Heap Snapshot フォーマットに準拠した JSON 構造である。

```
┌─────────────────────────────────────────┐
│  .heapsnapshot ファイル                   │
├─────────────────────────────────────────┤
│  {                                       │
│    "snapshot": {                          │
│      "meta": { ... schema ... },         │
│      "node_count": N,                    │
│      "edge_count": M                     │
│    },                                    │
│    "nodes": [ ... ],                     │
│    "edges": [ ... ],                     │
│    "strings": [ ... ]                    │
│  }                                       │
└─────────────────────────────────────────┘
```

### ストリーミング出力時の分割

| No | ファイル | 説明 | 形式 |
|----|---------|------|------|
| 1 | `{prefix}.nodes` | ノード情報 | バイナリ |
| 2 | `{prefix}.edges` | エッジ情報 | バイナリ |
| 3 | `{prefix}.strings` | 文字列テーブル | バイナリ |
| 4 | `{prefix}.metadata.json` | メタデータ・スキーマ | JSON |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| filepath | 出力先ファイルパス | No（デフォルトはカレントディレクトリ） |
| all_one | 全オブジェクトサイズを1として報告 | No（デフォルト: false） |
| redact_data | オブジェクトの内容を含めない | No（デフォルト: true） |
| streaming | ストリーミング出力モード | No（デフォルト: false） |
| dir | 出力先ディレクトリ | No |

### 改ページ条件

N/A（バイナリ/JSON ファイル出力）

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| GCヒープ（jl_gc_take_heap_snapshot） | ヒープ上の全オブジェクト | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| オブジェクトサイズ | 実サイズまたは1（all_one=true時） | なし | all_one はカウント分析用 |
| ファイル名 | `"$(getpid())_$(time_ns()).heapsnapshot"` | なし | PIDとナノ秒タイムスタンプで一意性確保 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A["take_heap_snapshot() 呼び出し"] --> B{streaming?}
    B -->|true| C["_stream_heap_snapshot(prefix)"]
    B -->|false| D["_stream_heap_snapshot(prefix)"]
    D --> E["HeapSnapshot.assemble_snapshot(prefix, filepath)"]
    E --> F["HeapSnapshot.cleanup_streamed_files(prefix)"]
    C --> G["終了（4分割ファイル）"]
    F --> H["終了（単一.heapsnapshotファイル）"]
```

### ストリーミング処理の詳細

```mermaid
flowchart TD
    A["_stream_heap_snapshot(prefix)"] --> B["nodes ファイルオープン"]
    B --> C["edges ファイルオープン"]
    C --> D["strings ファイルオープン"]
    D --> E["metadata.json ファイルオープン"]
    E --> F["ccall: jl_gc_take_heap_snapshot"]
    F --> G["全ファイルクローズ"]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 書き込み権限なし | カレントディレクトリに書き込めない | "Cannot write to current directory" | 書き込み可能なディレクトリを `dir` で指定、または tempdir にフォールバック |
| ディスク容量不足 | ファイル書き込み中に容量不足 | OS レベルのエラー | ディスク容量を確保する |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | ヒープ上の全オブジェクト（数十万〜数百万） |
| 目標出力時間 | ヒープサイズに依存 |
| 同時出力数上限 | 1（GC のロック取得が必要） |

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

- `redact_data=true`（デフォルト）を使用することで、オブジェクトの内容データは出力されない。
- `redact_data=false` を使用すると、ヒープ上の全オブジェクトの内容が出力されるため、機密データ（パスワード、トークン等）が含まれる可能性がある。外部共有時は特に注意が必要。
- ファイルにはオブジェクトの型情報と参照関係が含まれるため、プログラムの内部構造が推測される可能性がある。

## 備考

- `streaming=true` の使用を強く推奨する。非ストリーミングモードではスナップショット全体をメモリに保持する必要があり、大きなヒープではプロセスがメモリ不足で終了する可能性がある。
- 非ストリーミングモードでプロセスが強制終了した場合でも、ストリーミングパートファイルは保存されているため、`assemble_snapshot()` で事後復元が可能。
- Chrome DevTools の Memory タブで `.heapsnapshot` ファイルを読み込んで視覚的に分析可能。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `take_heap_snapshot(filepath, all_one; ...)` 関数（行1407-1419）で、streaming モードの分岐を理解する |
| 1-2 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `take_heap_snapshot(all_one; dir, ...)` 関数（行1453-1469）で、デフォルトファイル名生成を理解する |

**主要処理フロー**:
1. **行1453-1469**: デフォルトファイル名 `{pid}_{timestamp}.heapsnapshot` の生成
2. **行1408-1419**: streaming=true なら直接 `_stream_heap_snapshot`、false なら後で `assemble_snapshot` + `cleanup_streamed_files`

#### Step 2: ストリーミング出力を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Profile.jl | `stdlib/Profile/src/Profile.jl` | `_stream_heap_snapshot()` 関数（行1428-1452）で、4ファイルのオープンと ccall を理解する |

**主要処理フロー**:
- **行1430-1448**: `.nodes`, `.edges`, `.strings`, `.metadata.json` の4ファイルをオープンし、`jl_gc_take_heap_snapshot` を ccall
- **行1439-1443**: ccall の引数は4つのファイルハンドル + `all_one` + `redact_data` フラグ

#### Step 3: スナップショットの組み立てを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | heapsnapshot_reassemble.jl | `stdlib/Profile/src/heapsnapshot_reassemble.jl` | `assemble_snapshot()` と `cleanup_streamed_files()` で分割ファイルの組み立てを理解する |

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

```
Profile.take_heap_snapshot(; dir, streaming, ...)
    |
    +-- ファイル名生成: "{pid}_{timestamp}.heapsnapshot"
    |
    +-- take_heap_snapshot(filepath, all_one; ...)
            |
            +-- _stream_heap_snapshot(prefix, all_one, redact_data)
            |       |
            |       +-- open("{prefix}.nodes", "w")
            |       +-- open("{prefix}.edges", "w")
            |       +-- open("{prefix}.strings", "w")
            |       +-- open("{prefix}.metadata.json", "w")
            |       +-- ccall(:jl_gc_take_heap_snapshot, ...)
            |
            +-- (streaming=false のみ)
                    +-- HeapSnapshot.assemble_snapshot(prefix, filepath)
                    +-- HeapSnapshot.cleanup_streamed_files(prefix)
```

### データフロー図

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

GCヒープ ──ccall──> .nodes (バイナリ)
                    .edges (バイナリ)           assemble_snapshot()
                    .strings (バイナリ)  ──────────────────> .heapsnapshot (JSON)
                    .metadata.json (JSON)

                    (streaming=true の場合は4ファイルがそのまま最終出力)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Profile.jl | `stdlib/Profile/src/Profile.jl` | ソース | take_heap_snapshot 関数群、_stream_heap_snapshot |
| heapsnapshot_reassemble.jl | `stdlib/Profile/src/heapsnapshot_reassemble.jl` | ソース | ストリーミングファイルの組み立て（assemble_snapshot, cleanup_streamed_files） |
