# 帳票設計書 8-PrometheusServlet メトリクスレポート

## 概要

本ドキュメントは、Apache Spark の MetricsSystem における PrometheusServlet が出力する Prometheus テキスト形式メトリクスレポートの設計書である。HTTP エンドポイントとして Jetty サーブレットを登録し、リクエストに応じてメトリクス情報を Prometheus テキストエクスポーション形式で返却する。Kubernetes 環境での Prometheus によるメトリクス収集を主な用途として想定している。

### 本帳票の処理概要

PrometheusServlet は HTTP GET リクエストに対して、Spark の全メトリクス情報を Prometheus テキスト形式で返却するサーブレットベースの帳票機能である。

**業務上の目的・背景**：Kubernetes 環境での Spark 運用が普及する中、Prometheus による統合的なメトリクス収集が標準的なモニタリング手法となっている。PrometheusServlet は Spark のメトリクスを Prometheus が直接スクレイプ可能な形式で公開し、JMX Sink + Prometheus JMX Converter の組み合わせを不要にする。Spark K8s operator からも利用される重要なコンポーネントである。本機能は `@DeveloperApi` および `@Unstable` アノテーションが付与されており、API は変更される可能性がある。

**帳票の利用シーン**：(1) Kubernetes 上の Spark クラスタで Prometheus によるメトリクススクレイピングを行う場合、(2) Prometheus + Grafana ベースのモニタリングダッシュボードを構築する場合、(3) JMX 変換なしで直接 Prometheus 形式のメトリクスを取得したい場合。

**主要な出力内容**：
1. Gauge メトリクス - `metrics_{normalized_key}_Number{type="gauges"}` および `_Value{type="gauges"}` 形式
2. Counter メトリクス - `metrics_{normalized_key}_Count{type="counters"}` 形式
3. Histogram メトリクス - Count, Max, Mean, Min, パーセンタイル等を `{type="histograms"}` ラベル付きで出力
4. Meter メトリクス - Count, MeanRate, OneMinuteRate 等を `{type="counters"}` ラベル付きで出力
5. Timer メトリクス - Count, Max, Mean, Min, パーセンタイル, Rate 等を `{type="timers"}` ラベル付きで出力

**帳票の出力タイミング**：HTTP GET リクエストを受信した時点でオンデマンドに生成・返却される。Prometheus のスクレイプ間隔に従って定期的にリクエストが発生する。

**帳票の利用者**：Prometheus サーバ、Kubernetes 運用チーム、SRE チーム。

## 帳票種別

メトリクスレポート（HTTP オンデマンド型の Prometheus テキストエクスポーション）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | Prometheus メトリクスエンドポイント | `{servletPath}`（設定による、例: /metrics/prometheus） | HTTP GET リクエスト（Prometheus スクレイプ） |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | Prometheus テキストエクスポーション形式 |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（HTTP レスポンスボディ） |
| 出力方法 | HTTP レスポンス |
| 文字コード | UTF-8 |
| Content-Type | text/plain |

### Prometheus 固有設定

| 項目 | 内容 |
|-----|------|
| メトリクス名正規化 | `[^a-zA-Z0-9]` をアンダースコアに置換、`metrics_` プレフィックス付与 |
| ラベル | `{type="gauges\|counters\|histograms\|timers"}` |
| 1行1メトリクス | `{metric_name}{labels} {value}\n` 形式 |

## 帳票レイアウト

### レイアウト概要

Prometheus テキストエクスポーション形式で、1メトリクスにつき1行で出力する。

```
metrics_{normalized_key}_Number{type="gauges"} 123.45
metrics_{normalized_key}_Value{type="gauges"} 123.45
metrics_{normalized_key}_Count{type="counters"} 100
metrics_{normalized_key}_Count{type="histograms"} 50
metrics_{normalized_key}_Max{type="histograms"} 200
metrics_{normalized_key}_Mean{type="histograms"} 100.5
...
metrics_{normalized_key}_Count{type="counters"} 75
metrics_{normalized_key}_MeanRate{type="counters"} 2.5
...
metrics_{normalized_key}_Count{type="timers"} 30
metrics_{normalized_key}_Max{type="timers"} 500
...
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| N/A | ヘッダーなし | Prometheus テキスト形式にヘッダーは存在しない（TYPE/HELP コメント行は本実装では非出力） | N/A | N/A |

### 明細部

**Gauge メトリクス（String 型以外）:**

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Number | ゲージ数値 | Gauge.getValue | `metrics_{key}_Number{type="gauges"} {value}` | 可変 |
| 2 | Value | ゲージ値 | Gauge.getValue | `metrics_{key}_Value{type="gauges"} {value}` | 可変 |

**Counter メトリクス:**

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Count | カウント値 | Counter.getCount | `metrics_{key}_Count{type="counters"} {count}` | 可変 |

**Histogram メトリクス:**

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Count | サンプル数 | Histogram.getCount | histograms ラベル | 可変 |
| 2 | Max | 最大値 | Snapshot.getMax | histograms ラベル | 可変 |
| 3 | Mean | 平均値 | Snapshot.getMean | histograms ラベル | 可変 |
| 4 | Min | 最小値 | Snapshot.getMin | histograms ラベル | 可変 |
| 5 | 50thPercentile | 中央値 | Snapshot.getMedian | histograms ラベル | 可変 |
| 6 | 75thPercentile | 75% | Snapshot.get75thPercentile | histograms ラベル | 可変 |
| 7 | 95thPercentile | 95% | Snapshot.get95thPercentile | histograms ラベル | 可変 |
| 8 | 98thPercentile | 98% | Snapshot.get98thPercentile | histograms ラベル | 可変 |
| 9 | 99thPercentile | 99% | Snapshot.get99thPercentile | histograms ラベル | 可変 |
| 10 | 999thPercentile | 99.9% | Snapshot.get999thPercentile | histograms ラベル | 可変 |
| 11 | StdDev | 標準偏差 | Snapshot.getStdDev | histograms ラベル | 可変 |

**Meter メトリクス:**

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Count | カウント | Meter.getCount | counters ラベル | 可変 |
| 2 | MeanRate | 平均レート | Meter.getMeanRate | counters ラベル | 可変 |
| 3 | OneMinuteRate | 1分レート | Meter.getOneMinuteRate | counters ラベル | 可変 |
| 4 | FiveMinuteRate | 5分レート | Meter.getFiveMinuteRate | counters ラベル | 可変 |
| 5 | FifteenMinuteRate | 15分レート | Meter.getFifteenMinuteRate | counters ラベル | 可変 |

**Timer メトリクス:**

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | Count | カウント | Timer.getCount | timers ラベル | 可変 |
| 2 | Max | 最大値 | Snapshot.getMax | timers ラベル | 可変 |
| 3 | Mean | 平均値 | Snapshot.getMean | timers ラベル | 可変 |
| 4 | Min | 最小値 | Snapshot.getMin | timers ラベル | 可変 |
| 5-11 | パーセンタイル | 50th〜999th | Snapshot | timers ラベル | 可変 |
| 12 | StdDev | 標準偏差 | Snapshot.getStdDev | timers ラベル | 可変 |
| 13 | FifteenMinuteRate | 15分レート | Timer.getFifteenMinuteRate | timers ラベル | 可変 |
| 14 | FiveMinuteRate | 5分レート | Timer.getFiveMinuteRate | timers ラベル | 可変 |
| 15 | OneMinuteRate | 1分レート | Timer.getOneMinuteRate | timers ラベル | 可変 |
| 16 | MeanRate | 平均レート | Timer.getMeanRate | timers ラベル | 可変 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| N/A | フッターなし | N/A | N/A | N/A |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| MetricRegistry 登録 | MetricRegistry に登録されている全メトリクスが対象 | Yes |
| HTTP リクエスト | servletPath への GET リクエスト受信 | Yes |
| String 型 Gauge 除外 | Gauge の値が String 型の場合は出力対象外 | 自動 |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | メトリクス種別 | 固定順（Gauges, Counters, Histograms, Meters, Timers） |
| 2 | メトリクス名 | 昇順（SortedMap/asScala の順） |

### 改ページ条件

テキストレスポンスのため改ページなし。

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

### 参照テーブル一覧

本帳票はデータベースを参照しない。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| N/A（MetricRegistry） | メトリクスデータの取得元 | N/A |

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

#### MetricRegistry（インメモリ）

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| Gauges | Gauge 行群 | registry.getGauges.asScala | String 値は除外 |
| Counters | Counter 行群 | registry.getCounters.asScala | 全 Counter |
| Histograms | Histogram 行群 | registry.getHistograms.asScala | 全 Histogram |
| Meters | Meter 行群 | registry.getMeters.entrySet.iterator.asScala | 全 Meter |
| Timers | Timer 行群 | registry.getTimers.entrySet.iterator.asScala | 全 Timer |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| メトリクス名正規化 | `metrics_` + key.replaceAll("[^a-zA-Z0-9]", "_") + `_` | N/A | normalizeKey() メソッド |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[HTTP GET リクエスト受信] --> B[getMetricsSnapshot]
    B --> C[StringBuilder 初期化]
    C --> D[Gauges 処理 - String 型以外を Number/Value として出力]
    D --> E[Counters 処理 - Count を出力]
    E --> F[Histograms 処理 - 統計値群を出力]
    F --> G[Meters 処理 - Count/Rate 群を出力]
    G --> H[Timers 処理 - 統計値/Rate 群を出力]
    H --> I[StringBuilder.toString で文字列生成]
    I --> J[HTTP レスポンスとして返却 - text/plain]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| パス未設定 | servletPath プロパティが null | NullPointerException（サーブレット登録時） | path 設定を追加 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | メトリクス数に依存 |
| 目標出力時間 | Prometheus スクレイプタイムアウト内（通常10秒以内） |
| 同時出力数上限 | Jetty サーバのスレッド数に依存 |

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

- Spark Web UI のアクセス制御設定に従う
- Kubernetes 環境では、Service/Ingress のネットワークポリシーで Prometheus サーバからのアクセスのみ許可すること
- @Unstable API であるため、バージョンアップ時に互換性が失われる可能性がある

## 備考

- 設定は `metrics.properties` ファイルで行う。設定例：
  ```
  *.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet
  *.sink.prometheusServlet.path=/metrics/prometheus
  ```
- MetricsSystem 内で特別扱いされており、prometheusServlet フィールドに格納される（行212-216）
- start()/stop()/report() はすべて空実装
- `@DeveloperApi` と `@Unstable` のアノテーションが付与されており、API 安定性は保証されない
- Meter のラベルが `{type="counters"}` である点に注意（Prometheus の命名規則との互換性のため）
- getMetricsSnapshot() は引数なしのバージョンも提供されている（@Since("4.0.0")）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Sink.scala | `core/src/main/scala/org/apache/spark/metrics/sink/Sink.scala` | Sink トレイト定義（行20-24） |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | MetricsSystem.scala | `core/src/main/scala/org/apache/spark/metrics/MetricsSystem.scala` | registerSinks() 内の prometheusServlet 特別処理（行212-216）と getServletHandlers()（行89-93）を確認 |

**主要処理フロー**:
1. **行212-216**: registerSinks() で kv._1 == "prometheusServlet" の場合、PrometheusServlet をインスタンス化して prometheusServlet フィールドに格納
2. **行89-93**: getServletHandlers() で prometheusServlet.getHandlers(conf) を呼び出す

#### Step 3: PrometheusServlet の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PrometheusServlet.scala | `core/src/main/scala/org/apache/spark/metrics/sink/PrometheusServlet.scala` | クラス全体（行42-134）を通読。MetricsServlet と異なり Jackson を使わず、StringBuilder で直接 Prometheus テキスト形式を生成している点が特徴 |

**主要処理フロー**:
- **行42-43**: Properties と MetricRegistry をコンストラクタ引数として受け取る
- **行45-47**: servletPath の設定読み込み
- **行49-53**: getHandlers() で Jetty の ServletContextHandler を生成。Content-Type は "text/plain"
- **行56**: getMetricsSnapshot(request) はパラメータなし版に委譲
- **行58-123**: getMetricsSnapshot() のメインロジック
  - **行62-66**: ラベル文字列の定義（gauges, counters, histograms, timers）
  - **行68**: StringBuilder 初期化
  - **行69-73**: Gauge 処理 - String 型以外を Number/Value として出力
  - **行75-77**: Counter 処理 - Count を出力
  - **行78-92**: Histogram 処理 - Count, Max, Mean, Min, パーセンタイル, StdDev を出力
  - **行93-101**: Meter 処理 - Count, MeanRate, OneMinuteRate, FiveMinuteRate, FifteenMinuteRate を出力
  - **行102-121**: Timer 処理 - Count, Max, Mean, Min, パーセンタイル, StdDev, Rate を出力
- **行125-127**: normalizeKey() - メトリクス名の正規化（`metrics_` + 非英数字をアンダースコアに置換 + `_`）
- **行129-133**: start()/stop()/report() は空実装

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

```
MetricsSystem.start()
    |
    +-- registerSinks()
    |       +-- kv._1 == "prometheusServlet"
    |       +-- PrometheusServlet.<init>(properties, registry)
    |       +-- prometheusServlet = Some(servlet)
    |
    +-- getServletHandlers()
            +-- PrometheusServlet.getHandlers(conf)
                    +-- createServletHandler(servletPath, ...)
                            +-- [HTTP GET] getMetricsSnapshot()
                                    +-- registry.getGauges.asScala.foreach
                                    +-- registry.getCounters.asScala.foreach
                                    +-- registry.getHistograms.asScala.foreach
                                    +-- registry.getMeters.entrySet.iterator.asScala.foreach
                                    +-- registry.getTimers.entrySet.iterator.asScala.foreach
                                    +-- normalizeKey(k) で各キーを正規化
                                    +-- StringBuilder.toString
```

### データフロー図

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

HTTP GET リクエスト      PrometheusServlet              HTTP レスポンス
(servletPath)           +-- getMetricsSnapshot()       Content-Type: text/plain
                            +-- StringBuilder
MetricRegistry              +-- Gauges -> Number/Value  metrics_xxx_Number{type="gauges"} 123
(Gauges,                    +-- Counters -> Count       metrics_xxx_Count{type="counters"} 100
 Counters,                  +-- Histograms -> 統計値    metrics_xxx_Mean{type="histograms"} 50.5
 Histograms,                +-- Meters -> Count/Rate    metrics_xxx_MeanRate{type="counters"} 2.5
 Meters,                    +-- Timers -> 統計値/Rate   metrics_xxx_Count{type="timers"} 30
 Timers)                    +-- normalizeKey()

Properties
(path)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Sink.scala | `core/src/main/scala/org/apache/spark/metrics/sink/Sink.scala` | ソース | Sink トレイト定義 |
| PrometheusServlet.scala | `core/src/main/scala/org/apache/spark/metrics/sink/PrometheusServlet.scala` | ソース | Prometheus テキスト形式メトリクスエンドポイント実装 |
| MetricsSystem.scala | `core/src/main/scala/org/apache/spark/metrics/MetricsSystem.scala` | ソース | PrometheusServlet の特別処理と getServletHandlers() |
| JettyUtils.scala | `core/src/main/scala/org/apache/spark/ui/JettyUtils.scala` | ソース | Jetty サーブレットハンドラ生成ユーティリティ |
