# 帳票設計書 31-SparkConnectServerSessionPage

## 概要

本ドキュメントは、Apache Spark Connect Server の Web UI において個別セッション詳細情報を HTML 形式で表示する `SparkConnectServerSessionPage` の帳票設計書である。Spark Connect Server に接続した各クライアントセッションについて、セッション基本情報とそのセッション内で実行されたリクエスト（操作）の統計情報を表示する。

### 本帳票の処理概要

本帳票は Spark Connect Server の個別セッションに関する詳細情報を Web UI 上に HTML 形式で動的レンダリングするレポートページである。セッション ID をパラメータとして受け取り、該当セッションの基本情報と、そのセッション内で実行されたすべてのリクエスト（gRPC 操作）の統計テーブルを生成・表示する。

**業務上の目的・背景**：Spark Connect Server は gRPC ベースでリモートクライアントから Spark ジョブを実行するサーバーコンポーネントである。運用時に個別セッションごとの実行状況を詳細に把握するため、セッションレベルのドリルダウンビューが必要である。本帳票は、Spark Connect Server のメインページ（No.30: SparkConnectServerPage）のセッション一覧からリンクでアクセスされ、特定セッションに絞ったリクエスト実行履歴の確認を可能にする。

**帳票の利用シーン**：Spark Connect Server の運用監視において、特定のセッションで実行されたリクエストの状況確認、パフォーマンス分析、障害調査を行う場合に利用される。例えば、あるユーザーのセッションで実行時間が異常に長いリクエストが発生した場合のトラブルシューティングに用いる。

**主要な出力内容**：
1. セッション基本情報（セッション ID、サーバー起動時刻、起動からの経過時間）
2. セッションサマリー（ユーザー名、セッション作成日時、総リクエスト数）
3. リクエスト統計テーブル（ユーザー、ジョブ ID、SQL クエリ ID、開始時刻、終了時刻、クローズ時刻、実行時間、Duration、ステートメント、状態、オペレーション ID、ジョブタグ、Spark Session タグ、詳細）

**帳票の出力タイミング**：Spark Connect Server が稼働中に、Web UI 上でセッション一覧ページからセッション ID のリンクをクリックした際に、HTTP リクエストに応じてリアルタイムにレンダリングされる。

**帳票の利用者**：Spark クラスター管理者、DevOps エンジニア、データプラットフォームチーム、Spark Connect Server の運用監視担当者。

## 帳票種別

Web UI ページ（HTMLレンダリング）/ セッション詳細ビュー

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| 31 | Spark Connect Session | `/connect/session/?id={sessionId}` | SparkConnectServerPage のセッション一覧テーブルからセッション ID リンクをクリック |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML（Scala XML テンプレートによる動的生成） |
| 用紙サイズ | N/A（ブラウザ表示） |
| 向き | N/A |
| ファイル名 | N/A（HTTP レスポンスとして返却） |
| 出力方法 | ブラウザ表示（HTTP GET リクエストへのレスポンス） |
| 文字コード | UTF-8 |

### PDF固有設定

該当なし（HTML 形式のため）

### Excel固有設定

該当なし（HTML 形式のため）

## 帳票レイアウト

### レイアウト概要

ページは Spark UI の共通ヘッダー（ナビゲーションバー）を含む標準レイアウトに、セッション固有のコンテンツを配置する。コンテンツは上から順に「基本統計情報」「セッションサマリー」「リクエスト統計テーブル」の3つのセクションで構成される。

```
+-------------------------------------------------------------+
|              Spark UI 共通ヘッダー / ナビゲーション             |
+-------------------------------------------------------------+
|  基本統計情報部                                                |
|    - Session ID: {sessionId}                                  |
|    - Started at: {startDate}                                  |
|    - Time since start: {duration}                             |
+-------------------------------------------------------------+
|  セッションサマリー部                                          |
|    User {userId},                                             |
|    Session created at {createDate},                           |
|    Total run {count} Request(s)                               |
+-------------------------------------------------------------+
|  リクエスト統計テーブル部                                       |
|  [Request Statistics]                                         |
|  +--+------+--------+--------+-------+-------+---+---+---+   |
|  |User|Job ID|SQL QID|Start  |Finish |Close  |...|...|...|   |
|  +--+------+--------+--------+-------+-------+---+---+---+   |
|  | ... | ... | ...   | ...   | ...   | ...   |...|...|...|   |
|  +--+------+--------+--------+-------+-------+---+---+---+   |
|  [ページネーション]                                            |
+-------------------------------------------------------------+
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Session ID | 対象セッションの識別子 | HTTPリクエストパラメータ `id` | 文字列 |
| 2 | Started at | Spark Connect Server の起動日時 | `SparkConnectServerTab.startTime` | `UIUtils.formatDate()` 形式 |
| 3 | Time since start | サーバー起動からの経過時間 | `System.currentTimeMillis() - startTime.getTime` | `UIUtils.formatDurationVerbose()` 形式（例: "5 hours 30 minutes"） |

### 明細部

セッションサマリー行:

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | User | セッションのユーザー名 | `SessionInfo.userId` | 文字列 |
| 2 | Session created at | セッション作成日時 | `SessionInfo.startTimestamp` | `UIUtils.formatDate()` 形式 |
| 3 | Total run ... Request(s) | セッション内の総リクエスト数 | `SessionInfo.totalExecution` | 整数 |

リクエスト統計テーブル（`SqlStatsPagedTable` による表示、`showSessionLink = false`）:

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | User | 実行ユーザー | `ExecutionInfo.userId` | 文字列 | 自動 |
| 2 | Job ID | Spark ジョブ ID（リンク付き） | `ExecutionInfo.jobId` | `[jobId]` 形式のリンク（`/jobs/job/?id=`） | 自動 |
| 3 | SQL Query ID | SQL実行 ID（リンク付き） | `ExecutionInfo.sqlExecId` | `[sqlExecId]` 形式のリンク（`/SQL/execution/?id=`） | 自動 |
| 4 | Start Time | リクエスト開始時刻 | `ExecutionInfo.startTimestamp` | `UIUtils.formatDate()` 形式 | 自動 |
| 5 | Finish Time | 実行終了時刻 | `ExecutionInfo.finishTimestamp` | `UIUtils.formatDate()` 形式（0の場合は空） | 自動 |
| 6 | Close Time | 操作クローズ時刻 | `ExecutionInfo.closeTimestamp` | `UIUtils.formatDate()` 形式（0の場合は空） | 自動 |
| 7 | Execution Time | 実行時間（開始〜終了） | `ExecutionInfo.totalTime(finishTimestamp)` | `UIUtils.formatDurationVerbose()` | 自動 |
| 8 | Duration | 全体所要時間（開始〜クローズ） | `ExecutionInfo.totalTime(closeTimestamp)` | `UIUtils.formatDurationVerbose()` | 自動 |
| 9 | Statement | 実行されたステートメント | `ExecutionInfo.statement` | 文字列（CSSクラス `description-input` で表示） | 自動 |
| 10 | State | 実行状態 | `ExecutionInfo.state` / `ExecutionInfo.isExecutionActive` | アクティブなら "RUNNING"、それ以外は状態値 | 自動 |
| 11 | Operation ID | 操作識別子 | `ExecutionInfo.operationId` | 文字列 | 自動 |
| 12 | Job Tag | ジョブタグ | `ExecutionInfo.jobTag` | 文字列 | 自動 |
| 13 | Spark Session Tags | Spark セッションタグ | `ExecutionInfo.sparkSessionTags` | カンマ区切り文字列 | 自動 |
| 14 | Detail | エラー詳細 | `ExecutionInfo.detail` | `UIUtils.errorMessageCell()` で表示 | 自動 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | ページネーション | テーブルのページ切り替え | `PagedTable` フレームワーク | ページ番号リンク / ジャンプ入力欄 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| セッションID | HTTPリクエストパラメータ `id` で指定されたセッションIDに一致するセッション情報を取得 | Yes |
| セッションIDフィルタ | リクエスト統計テーブルでは `ExecutionInfo.sessionId == sessionID` でフィルタリング | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | Start Time（デフォルト） | 昇順（ユーザーによるカラムヘッダークリックで変更可能） |

### 改ページ条件

リクエスト統計テーブルは `PagedTable` フレームワークにより、デフォルトのページサイズで自動ページ分割される。ページサイズはクエリパラメータ `sqlsessionstat.pageSize` で制御可能。

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

### 参照テーブル一覧

本帳票はRDBMS上のテーブルではなく、KVStore（インメモリ/ディスクベースの key-value ストア）からデータを取得する。

| テーブル名（KVStore エンティティ） | 用途 | 結合条件 |
|-----------|------|---------|
| `SessionInfo` | セッション基本情報の取得 | `sessionId`（KVIndex主キー）で一意検索 |
| `ExecutionInfo` | リクエスト実行情報の取得 | 全件取得後、`sessionId` でフィルタリング |

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

#### SessionInfo

| 参照項目（フィールド名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| `sessionId` | セッション ID | `store.getSession(sessionId)` で主キー検索 | `@KVIndexParam` アノテーション |
| `userId` | User | 上記と同時取得 | |
| `startTimestamp` | Session created at | 上記と同時取得 | Long型（エポックミリ秒） |
| `totalExecution` | Total run ... Request(s) | 上記と同時取得 | |
| `finishTimestamp` | （直接表示なし） | 上記と同時取得 | セッション終了判定に使用 |

#### ExecutionInfo

| 参照項目（フィールド名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| `jobTag` | Job Tag | `store.getExecutionList` で全件取得後 `sessionId` フィルタ | `@KVIndexParam` 主キー |
| `statement` | Statement | 上記と同時取得 | |
| `sessionId` | （フィルタ条件） | フィルタ条件として使用 | |
| `startTimestamp` | Start Time | 上記と同時取得 | Long型 |
| `userId` | User | 上記と同時取得 | |
| `operationId` | Operation ID | 上記と同時取得 | |
| `sparkSessionTags` | Spark Session Tags | 上記と同時取得 | `Set[String]` |
| `finishTimestamp` | Finish Time / Execution Time | 上記と同時取得 | |
| `closeTimestamp` | Close Time / Duration | 上記と同時取得 | |
| `detail` | Detail | 上記と同時取得 | エラーメッセージ |
| `state` | State | 上記と同時取得 | `ExecutionState.Value` 列挙型 |
| `jobId` | Job ID | 上記と同時取得 | `ArrayBuffer[String]` |
| `sqlExecId` | SQL Query ID | 上記と同時取得 | `mutable.Set[String]` |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| Time since start | `System.currentTimeMillis() - startTime.getTime` | N/A | ミリ秒単位で算出後、`formatDurationVerbose()` で人間可読形式に変換 |
| Execution Time | `ExecutionInfo.totalTime(finishTimestamp)`: `finishTimestamp == 0` の場合は `currentTimeMillis - startTimestamp`、それ以外は `finishTimestamp - startTimestamp` | N/A | ミリ秒単位 |
| Duration | `ExecutionInfo.totalTime(closeTimestamp)`: `closeTimestamp == 0` の場合は `currentTimeMillis - startTimestamp`、それ以外は `closeTimestamp - startTimestamp` | N/A | ミリ秒単位 |
| State 表示値 | `if (isExecutionActive) "RUNNING" else info.state` | N/A | `isExecutionActive` は `state` が `STARTED`、`COMPILED`、`READY` のいずれかで `true` |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[ブラウザからHTTP GET /connect/session/?id=xxx] --> B[SparkConnectServerSessionPage.render]
    B --> C{sessionId パラメータ取得}
    C -->|null or empty| D[IllegalArgumentException]
    C -->|有効| E[store.synchronized ブロック]
    E --> F[store.getSession sessionId]
    F -->|None| G[No information to display メッセージ表示]
    F -->|Some SessionInfo| H[generateBasicStats 基本統計情報生成]
    H --> I[セッションサマリー h4 タグ生成]
    I --> J[generateSQLStatsTable リクエスト統計テーブル生成]
    J --> K[store.getExecutionList で全件取得]
    K --> L[sessionId でフィルタリング]
    L -->|件数 > 0| M[SqlStatsPagedTable 生成]
    L -->|件数 = 0| N[統計なしメッセージ]
    M --> O[UIUtils.headerSparkPage で共通ページラップ]
    N --> O
    G --> O
    O --> P[HTML レスポンス返却]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| パラメータ不正 | `id` パラメータが null または空文字 | `IllegalArgumentException: Missing id parameter` | `require` 文による検証。ブラウザに 500 エラーが返される |
| セッション不存在 | 指定された `sessionId` が KVStore に存在しない | `No information to display for session {sessionId}` | 画面上にメッセージを表示。正常系として処理 |
| テーブルレンダリングエラー | `SqlStatsPagedTable` の生成中に `IllegalArgumentException` または `IndexOutOfBoundsException` が発生 | `Error while rendering job table:` + スタックトレース | 例外を catch して、エラーメッセージを `alert-error` div で表示 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | セッションあたり数百〜数千リクエスト（`CONNECT_UI_STATEMENT_LIMIT` 設定で上限管理） |
| 目標出力時間 | ミリ秒〜数秒（インメモリ KVStore からの読み取りのため高速） |
| 同時出力数上限 | Jetty サーバーのスレッドプール設定に依存 |

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

- 本帳票は Spark UI のアクセス制御機構（ACL）に従う。`spark.ui.view.acls` や `spark.ui.view.acls.groups` で閲覧権限を制御可能
- セッション ID はクエリパラメータとして URL に含まれるため、ブラウザ履歴やログに記録される可能性がある
- ステートメント内容（`statement` フィールド）にセンシティブなクエリ情報が含まれる可能性がある
- KVStore アクセスは `store.synchronized` ブロック内で行われ、データの一貫性を保証する

## 備考

- 本帳票は No.30（SparkConnectServerPage）と密接に連携し、セッション一覧からのドリルダウンビューとして機能する
- `SqlStatsPagedTable` は No.30（SparkConnectServerPage）と共有しており、`showSessionLink = false` の設定でセッション ID カラムのリンクを非表示にしている
- リクエスト統計テーブルのソートはすべてのカラムヘッダーで可能であり、`SqlStatsTableDataSource.ordering()` メソッドで実装されている
- `ExecutionState` は `STARTED`、`COMPILED`、`READY`、`CANCELED`、`FAILED`、`FINISHED`、`CLOSED` の7状態を持つ
- History Server でも本帳票は利用可能（`SparkConnectServerHistoryServerPlugin` 経由）

---

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

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

### 推奨読解順序

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

まず、帳票に表示されるデータの構造を理解することが重要である。セッション情報と実行情報の2つの主要データクラスを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SparkConnectServerAppStatusStore.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerAppStatusStore.scala` | `SessionInfo`（76-91行目）と `ExecutionInfo`（93-126行目）のデータクラス定義。KVStore のインデックス（`@KVIndexParam`）、フィールド構成、`totalTime` 計算ロジックを理解する |
| 1-2 | SparkConnectServerAppStatusStore.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerAppStatusStore.scala` | `SparkConnectServerAppStatusStore` クラス（29-74行目）。`getSession()`、`getExecutionList()` などの KVStore からのデータ取得メソッドを理解する |
| 1-3 | ToolTips.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/ToolTips.scala` | テーブルヘッダーのツールチップ定義（20-39行目）。Finish Time、Close Time、Execution Time、Duration の意味の違いを理解する |

**読解のコツ**: `SessionInfo` と `ExecutionInfo` は KVStore の永続化エンティティであり、`@KVIndexParam` アノテーションが主キーを示す。`@KVIndex` アノテーションはセカンダリインデックスを定義する。`ExecutionState` 列挙型の各状態遷移（STARTED -> COMPILED -> READY -> FINISHED/CANCELED/FAILED -> CLOSED）を把握すると、テーブル表示の `State` カラムの理解が深まる。

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

処理の起点となるページクラスとタブの登録構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SparkConnectServerTab.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerTab.scala` | タブの登録と `SparkConnectServerSessionPage` のアタッチ（43-44行目）。`store` と `startTime` の初期化方法を確認する |
| 2-2 | SparkConnectServerSessionPage.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerSessionPage.scala` | `render()` メソッド（38-63行目）がエントリーポイント。HTTP リクエストからセッション ID を取得し、ページコンテンツを生成する流れを理解する |

**主要処理フロー**:
1. **38行目**: `render(request)` - HTTPリクエストを受け取るエントリーポイント
2. **39行目**: `request.getParameter("id")` - セッションIDの取得
3. **40行目**: `require(sessionId != null && sessionId.nonEmpty, ...)` - パラメータバリデーション
4. **42行目**: `store.synchronized { ... }` - データ一貫性のための同期ブロック開始
5. **44行目**: `store.getSession(sessionId)` - KVStore からセッション情報取得
6. **46行目**: `generateBasicStats(sessionId)` - 基本統計情報セクション生成
7. **59行目**: `generateSQLStatsTable(request, sessionStat.sessionId)` - リクエスト統計テーブル生成
8. **63行目**: `UIUtils.headerSparkPage(...)` - 共通ページヘッダーでラップして返却

#### Step 3: 基本統計情報の生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SparkConnectServerSessionPage.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerSessionPage.scala` | `generateBasicStats()` メソッド（67-80行目）。Session ID、起動時刻、経過時間の3項目を `<ul>` リストとして生成 |

**主要処理フロー**:
- **68行目**: `System.currentTimeMillis() - startTime.getTime` - 経過時間のミリ秒算出
- **69-79行目**: Scala XML リテラルで HTML `<ul>` を構築

#### Step 4: リクエスト統計テーブルの生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SparkConnectServerSessionPage.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerSessionPage.scala` | `generateSQLStatsTable()` メソッド（83-130行目）。セッションIDでフィルタリングした実行リストから `SqlStatsPagedTable` を生成 |
| 4-2 | SparkConnectServerPage.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerPage.scala` | `SqlStatsPagedTable` クラス（170-341行目）と `SqlStatsTableDataSource` クラス（431-491行目）。テーブルのカラム定義、ソートロジック、行レンダリングの詳細 |

**主要処理フロー**:
- **84-85行目**: `store.getExecutionList.filter(_.sessionId == sessionID)` - 当該セッションのリクエストをフィルタ
- **87行目**: `numStatement > 0` の条件分岐でテーブル生成有無を判断
- **96-103行目**: `new SqlStatsPagedTable(...)` - `showSessionLink = false` でテーブル生成
- **104-111行目**: `IllegalArgumentException` / `IndexOutOfBoundsException` のエラーハンドリング
- **117-129行目**: 折りたたみ可能な HTML ラッパーの生成

#### Step 5: データ収集リスナーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | SparkConnectServerListener.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerListener.scala` | イベントリスナー全体（35-452行目）。`SparkListenerConnectSessionStarted`/`Closed`、`SparkListenerConnectOperation*` 系イベントをハンドリングし、KVStore にデータを書き込む仕組み |
| 5-2 | SparkConnectServerListener.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerListener.scala` | `LiveSessionData`（433-452行目）と `LiveExecutionData`（390-431行目）。ライブデータから KVStore エンティティへの変換（`doUpdate()` メソッド）を理解する |

**主要処理フロー**:
- **118-131行目**: `onOtherEvent()` - イベントタイプに応じたディスパッチ
- **280-283行目**: `onSessionStarted()` - セッション開始イベント処理
- **285-300行目**: `onSessionClosed()` - セッション終了イベント処理
- **173-193行目**: `onOperationStarted()` - 操作開始イベント処理（セッションの `totalExecution` をインクリメント）

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

```
SparkConnectServerSessionPage.render(request)
    |
    +-- request.getParameter("id")
    |
    +-- store.synchronized { ... }
    |       |
    |       +-- store.getSession(sessionId)
    |       |       +-- KVStore.read(SessionInfo, sessionId)
    |       |
    |       +-- generateBasicStats(sessionId)
    |       |       +-- UIUtils.formatDate(startTime)
    |       |       +-- UIUtils.formatDurationVerbose(timeSinceStart)
    |       |
    |       +-- generateSQLStatsTable(request, sessionId)
    |               |
    |               +-- store.getExecutionList
    |               |       +-- KVUtils.viewToSeq(store.view(ExecutionInfo))
    |               |
    |               +-- executionList.filter(_.sessionId == sessionID)
    |               |
    |               +-- new SqlStatsPagedTable(...)
    |                       |
    |                       +-- SqlStatsTableDataSource(data, pageSize, sortColumn, desc)
    |                       |       +-- sqlStatsTableRow(executionInfo) [各行データ変換]
    |                       |       +-- ordering(sortColumn, desc) [ソート処理]
    |                       |
    |                       +-- table(sqlTablePage)
    |                               +-- headers [テーブルヘッダー生成]
    |                               +-- row(sqlStatsTableRow) [各行レンダリング]
    |                                       +-- jobURL() [ジョブリンク生成]
    |                                       +-- sqlURL() [SQLリンク生成]
    |
    +-- UIUtils.headerSparkPage(request, "Spark Connect Session", content, parent)
```

### データフロー図

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

SparkListener             SparkConnectServerListener          KVStore
  イベント群        -------->  onSessionStarted()     -------->  SessionInfo
  (gRPCオペレー             onOperationStarted()               ExecutionInfo
   ション起動時)             onOperationFinished()
                            onOperationClosed()
                            onSessionClosed()

                                      |
                                      v

HTTP GET                   SparkConnectServerSessionPage       HTML
  /connect/session/  ----->  render(request)             ----->  レスポンス
  ?id={sessionId}            |                                   (セッション詳細
                             +- getSession()                      ページ)
                             +- getExecutionList()
                             +- SqlStatsPagedTable
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SparkConnectServerSessionPage.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerSessionPage.scala` | ソース | 本帳票のメインページクラス。セッション詳細の HTML レンダリング |
| SparkConnectServerPage.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerPage.scala` | ソース | 親ページ（セッション一覧）および `SqlStatsPagedTable`、`SessionStatsPagedTable` 等の共有テーブルコンポーネント定義 |
| SparkConnectServerTab.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerTab.scala` | ソース | UI タブの定義。ページの登録と `store`、`startTime` の管理 |
| SparkConnectServerAppStatusStore.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerAppStatusStore.scala` | ソース | KVStore ラッパー。`SessionInfo`、`ExecutionInfo` のデータアクセス層、およびデータクラス定義 |
| SparkConnectServerListener.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerListener.scala` | ソース | Spark イベントリスナー。gRPC 操作イベントを受信し KVStore にデータを書き込む |
| ToolTips.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/ToolTips.scala` | ソース | テーブルカラムのツールチップ定義 |
| SparkConnectServerHistoryServerPlugin.scala | `sql/connect/server/src/main/scala/org/apache/spark/sql/connect/ui/SparkConnectServerHistoryServerPlugin.scala` | ソース | History Server プラグイン。過去のアプリケーションデータ閲覧のための統合 |
| SparkConnectServerPageSuite.scala | `sql/connect/server/src/test/scala/org/apache/spark/sql/connect/ui/SparkConnectServerPageSuite.scala` | テスト | ページのレンダリングテスト。セッションページのロードテスト（108-133行目）を含む |
