# プロファイルデータフォーマット

> Source: https://www.ymotongpoo.com/works/otel-specs-ja/spec/profiles/data-format/


**ステータス**: [アルファ（Alpha）](/works/otel-specs-ja/spec/document-status/)

## 概要

OpenTelemetryのプロファイル用データフォーマットは、集約されたスタックトレースと関連メタデータのエンコードおよび配送のためのプロトコル仕様と、[セマンティック規約](/works/otel-specs-ja/semconv/general/profiles/)から構成されます。

プロトコル仕様は[profiles.proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/profiles/v1development/profiles.proto)というprotobufファイルで定義されており、[pprof](https://github.com/google/pprof/tree/main/proto)フォーマットに基づいています。これはつまり、pprofをこのデータフォーマットへ一意にマッピングできることを意味します。対象のプロファイリングフォーマットが同等の機能を持つ範囲であれば、このデータフォーマットからの無損失な逆マッピングも可能です。

以下の図はメッセージ間の関係を示しています。実線の矢印は埋め込み関係を表し、破線の矢印はディクショナリテーブルへのインデックスによる参照を表します。

```mermaid
graph TD
    ProfilesData -->|"1-n"| ResourceProfiles
    ProfilesData -->|"1"| ProfilesDictionary
    ResourceProfiles -->|"1-n"| ScopeProfiles
    ScopeProfiles -->|"1-n"| Profile
    Profile -->|"1-n"| Sample

    Sample -. "n-1" .-> Stack
    Sample -. "n-n" .-> KeyValueAndUnit
    Sample -. "n-1" .-> Link

    Stack -. "n-n" .-> Location

    Location -->|"1-n"| Line
    Location -. "n-n" .-> KeyValueAndUnit
    Location -. "n-1" .-> Mapping

    Line -. "n-1" .-> Function

    Mapping -. "n-n" .-> KeyValueAndUnit
```

## 他シグナルとの違い

プロファイラーは大量のデータを生成し、ユーザーはプロファイリングが引き起こすオーバーヘッドに対して非常に敏感です。それを踏まえ、プロファイルのデータフォーマットは、[pprof](../pprof/)や他のOpenTelemetryのエコシステムとの互換性を保ちながらペイロードサイズを小さく、処理コストを低く保つために、他のOpenTelemetryシグナルとは異なる設計にしています。

### メッセージの埋め込み

ほとんどのOpenTelemetryシグナルは、直接（「値による」）埋め込みを使います。トレース内のスパンはそのイベントとリンクを埋め込み、ログレコードはその属性をインラインで含みます。

プロファイルは「値による」埋め込みと「参照による」埋め込みの両方の方式を使います。

1. **直接埋め込み**は、外側の階層（`ProfilesData` &#x2192; `ResourceProfiles` &#x2192; `ScopeProfiles` &#x2192; `Profile` &#x2192; `Sample`）と（`Location` &#x2192; `Line`）に使われます。
2. **インデックスによる参照**は、それ以外のすべての関係で、共有される[ディクショナリ](#message-profilesdictionary)に対して使われます。サンプルは、スタック・属性・リンクを直接埋め込むのではなく、インデックスによって参照します。

### ディクショナリ

プロファイルのデータフォーマットは、[`ProfilesData`](#message-profilesdata)メッセージ全体で共有されるデータを重複排除するために、トップレベルのディクショナリメッセージ（[`ProfilesDictionary`](#message-profilesdictionary)）を使います。他のOpenTelemetryシグナルでは各レコードがほぼ自己完結的であるのに対し、プロファイルは重複排除によって利益を得られるほど繰り返しの多いデータを大量に含みます。共有ディクショナリを参照することで、プロデューサーは同じバイト列を繰り返し格納・転送することを避け、プロファイルペイロードの通信量を大幅に削減できます。

トップレベルのディクショナリは、重複排除されるデータの種類ごとに1つずつ、追加のディクショナリテーブルを埋め込みます。埋め込まれた各ディクショナリテーブルは、要素がインデックスで参照される配列としてエンコードされます。

### 属性

このデータフォーマットは、2種類の属性を使います。

1. **標準の`KeyValue`属性**: 他のシグナルで使われているものと同じ[`KeyValue`](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto)のペアです。これらは[`Resource`](/works/otel-specs-ja/spec/resource/)メッセージと[`InstrumentationScope`](/works/otel-specs-ja/spec/common/instrumentation-scope/)メッセージに現れ、通常のOpenTelemetryの属性のセマンティクス（キーの一意性、単位なし）に従いますが、プロファイル固有の拡張として、キーと値の文字列参照フィールドを持ちます（[次の節](#keyvalueにおけるディクショナリの利用)を参照）。

2. **[`KeyValueAndUnit`](#message-keyvalueandunit)属性**: プロファイル固有の属性エンコード方式です。これらは[`ProfilesDictionary.attribute_table`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Aattribute_table&type=code)に格納され、[`Profile`](#message-profile)、[`Sample`](#message-sample)、[`Mapping`](#message-mapping)、[`Location`](#message-location)の各メッセージからインデックスで参照されます。キーと値に加えて、オプションのunitフィールドを持ち、セマンティック規約だけに頼るのではなく、`"allocation_size": 128 By`（単位は[UCUM](https://ucum.org/)）のような属性の単位を明示的に表現できます。

### `KeyValue`におけるディクショナリの利用

ペイロードサイズを最小化するため、このデータフォーマットは標準のOpenTelemetryの[`KeyValue`](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto)メッセージと[`AnyValue`](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto)メッセージを、[`ProfilesDictionary.string_table`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Astring_table&type=code)を指す文字列参照フィールドで拡張しています。

これは、[`Resource`](/works/otel-specs-ja/spec/resource/)属性が、単一の[`ProfilesData`](#message-profilesdata)メッセージ内の多数のプロファイルやサンプルにわたって同じ文字列値（`service.name`や`host.name`など）を頻繁に繰り返すために行われています。

## メッセージの説明

### Message `ProfilesData`

`ProfilesData`はトップレベルのメッセージであり、永続ストレージに格納できるデータ、またはOTLPプロトコルを実装しないがOTLPプロファイルを転送する他のプロトコルによって埋め込まれるデータをカプセル化します。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+ProfilesData+%22&type=code)を参照してください。

### Message `ProfilesDictionary`

`ProfilesDictionary`は、[`ProfilesData`](#message-profilesdata)メッセージ全体で共有されるすべてのディクショナリテーブルを含みます。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+ProfilesDictionary+%22&type=code)を参照してください。

### Message `ResourceProfiles`

[Resource](/works/otel-specs-ja/spec/resource/)から得られる[`ScopeProfiles`](#message-scopeprofiles)の集合です。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+ResourceProfiles+%22&type=code)を参照してください。

### Message `ScopeProfiles`

[InstrumentationScope](/works/otel-specs-ja/spec/common/instrumentation-scope/)によって生成された[`Profile`](#message-profile)メッセージの集合です。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+ScopeProfiles+%22&type=code)を参照してください。

### Message `Profile`

完全なプロファイルを表します。サンプル種別、サンプル、バイナリへのマッピング、スタック、ロケーション、関数、および関連メタデータを含みます。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Profile+%22&type=code)を参照してください。

### Message `Sample`

各`Sample`は、プログラムのコンテキスト（通常はスタックトレース）で観測された値を記録し、スレッドIDやより高レベルなリクエストコンテキストなどの補助情報が付加されることもあります。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Sample+%22&type=code)を参照してください。

### Message `Link`

一意なトレースIDとスパンIDによって識別される、プロファイルの[`Sample`](#message-sample)からトレーススパンへのポインタです。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Link+%22&type=code)を参照してください。

### Message `Stack`

[`Location`](#message-location)（末端が先頭）のリストとしてエンコードされたスタックトレースです。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Stack+%22&type=code)を参照してください。

### Message `Location`

単一のフレームに関する関数情報と行テーブルのデバッグ情報を含みます。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Location+%22&type=code)を参照してください。

### Message `Line`

関数に紐づけられた、ソースコード中の特定の行の詳細を示します。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Line+%22&type=code)を参照してください。

### Message `Mapping`

アドレス範囲、ファイルオフセット、ビルドIDなどのメタデータを含む、メモリ上のバイナリのマッピングを表します。`Mapping`メッセージの必須属性については、[Mappings](/works/otel-specs-ja/spec/profiles/mappings/)を参照してください。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Mapping+%22&type=code)を参照してください。

### Message `Function`

人間が読める名前、システム名、ソースファイル、開始行番号を含む、関数を表します。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+Function+%22&type=code)を参照してください。

### Message `ValueType`

値の型と単位を表します。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+ValueType+%22&type=code)を参照してください。

### Message `KeyValueAndUnit`

キーに[`ProfilesDictionary.string_table`](#message-profilesdictionary)を使う、属性のカスタムなディクショナリネイティブのエンコーディングであり、オプションの単位情報をエンコードできます。

詳細については、protobufの[仕様](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+%22message+KeyValueAndUnit+%22&type=code)を参照してください。

## 他シグナルとの関係

OpenTelemetryプロファイルは、以下の2つの次元にわたって他のシグナルとの双方向リンクをサポートします。

* リソースコンテキストによる相関
* 直接参照による相関

リソースコンテキストによる相関とは、単に同じサービスインスタンスなど、関連するログ・メトリクス・トレースを発生させたのと同じ[Resource](/works/otel-specs-ja/spec/resource/)にプロファイルデータをリンクすることです。

プロファイルと他のシグナルの間には、2種類の直接参照関係があります。

* プロファイルから他シグナルへ
* 他シグナルからプロファイルへ

### プロファイルから他シグナルへ

[`Link`](#message-link)は、[`trace_id`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Atrace_id&type=code)と[`span_id`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Aspan_id&type=code)を介して、プロファイルの[`Sample`](#message-sample)をトレーススパンに接続します。ログやメトリクスなど他のシグナルも同じトレース・スパン識別子を使う場合があるため、プロファイルはこの共有されたトレースコンテキストを通じてそれらのシグナルと相関できます。

### 他シグナルからプロファイルへ

他のシグナルは、[`Profile`](#message-profile)メッセージ上の[`profile_id`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Aprofile_id&type=code)フィールドを使ってプロファイルを参照できます。例えば、ログレコードは、そのログレコードが生成された時点で収集されたプロファイルを参照するために`profile_id`属性を持つことができます。`profile_id`フィールドは現在、発生源ではオプションですが、収集後（例えばOpenTelemetry Collectorの処理パイプライン内）に設定される場合があることに注意してください。

さらに、[`trace_id`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Atrace_id&type=code)と[`span_id`](https://github.com/search?q=repo%3Aopen-telemetry%2Fopentelemetry-proto+path%3Aopentelemetry%2Fproto%2Fprofiles%2Fv1development%2Fprofiles.proto+symbol%3Aspan_id&type=code)は、プロファイル中の[`Sample`](#message-sample)（個別のサンプルではなくグループ単位）を参照するために使うことができます。これは、サンプルが[`Link`](#message-link)メッセージを使ってトレースとリンクされているためです。

## ペイロード例

### シンプルなCPUプロファイル

以下の例は、20Hz（実際のCPU実行時間50msごとに1サンプル）で動作するサンプリングプロファイラーによって収集されたオンCPUプロファイルを示しています。2つの一意なスタックトレースが観測されました。1つ（3回観測）は呼び出しスタック`main -> foo -> bar`を持ち、もう1つ（2回観測）は呼び出しスタック`main -> baz`を持ちます。

わかりやすくするため、文字列とディクショナリのインデックスをインラインで示しています。

```
ProfilesData {
  dictionary: ProfilesDictionary {
    string_table: ["", "samples", "count", "cpu", "nanoseconds",
                   "main", "foo", "bar", "baz",
                   "main.go", "foo.go", "bar.go", "baz.go"]
    function_table: [
      Function {},                                          // index 0: null
      Function { name_strindex: 5, filename_strindex: 9 },  // index 1: main
      Function { name_strindex: 6, filename_strindex: 10 }, // index 2: foo
      Function { name_strindex: 7, filename_strindex: 11 }, // index 3: bar
      Function { name_strindex: 8, filename_strindex: 12 }, // index 4: baz
    ]
    location_table: [
      Location {},                                                // index 0: null
      Location { lines: [Line { function_index: 1, line: 10 }] }, // index 1: main
      Location { lines: [Line { function_index: 2, line: 20 }] }, // index 2: foo
      Location { lines: [Line { function_index: 3, line: 30 }] }, // index 3: bar
      Location { lines: [Line { function_index: 4, line: 40 }] }, // index 4: baz
    ]
    stack_table: [
      Stack {},                              // index 0: null
      Stack { location_indices: [3, 2, 1] }, // index 1: bar <- foo <- main
      Stack { location_indices: [4, 1] },    // index 2: baz <- main
    ]
    mapping_table:   [Mapping {}]
    link_table:      [Link {}]
    attribute_table: [KeyValueAndUnit {}]
  }
  resource_profiles: [ResourceProfiles {
    scope_profiles: [ScopeProfiles {
      profiles: [Profile {
        sample_type: ValueType { type_strindex: 1, unit_strindex: 2 } // "samples", "count"
        samples: [
          Sample { stack_index: 1, values: [3] },
          Sample { stack_index: 2, values: [2] },
        ]
        time_unix_nano: 1234567890000000000
        duration_nano:  1000000000
        period_type: ValueType { type_strindex: 3, unit_strindex: 4 } // "cpu", "nanoseconds"
        period: 50000000 // 50ms = 20Hz
      }]
    }]
  }]
}
```

### リソース属性とスパンリンクを伴うCPUプロファイル

この例は、リソース属性（`service.name`、`process.executable.name`）と、あるサンプルに対するスパンリンクを持つプロファイルを示しており、トレースとの相関を示しています。

このリソース属性（プロファイル固有の[`KeyValueAndUnit`](#message-keyvalueandunit)属性ではなく、標準の[`KeyValue`](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto)属性）は、[`ProfilesDictionary`](#message-profilesdictionary)への文字列参照を使っています。

```
ProfilesData {
  dictionary: ProfilesDictionary {
    string_table: ["", "samples", "count", "cpu", "nanoseconds",
                   "handleRequest", "db.Query", "server.go", "db.go",
                   "service.name", "process.executable.name",
                   "my-service", "my-service.bin"]
    function_table: [
      Function {},                                         // index 0: null
      Function { name_strindex: 5, filename_strindex: 7 }, // index 1: handleRequest
      Function { name_strindex: 6, filename_strindex: 8 }, // index 2: db.Query
    ]
    location_table: [
      Location {},                                                 // index 0: null
      Location { lines: [Line { function_index: 1, line: 45 }] },  // index 1: handleRequest
      Location { lines: [Line { function_index: 2, line: 112 }] }, // index 2: db.Query
    ]
    stack_table: [
      Stack {},                           // index 0: null
      Stack { location_indices: [2, 1] }, // index 1: db.Query <- handleRequest
      Stack { location_indices: [1] },    // index 2: handleRequest
    ]
    link_table: [
      Link {},                                     // index 0: null
      Link {                                       // index 1
        trace_id: 1122aabbccddeeff0000000000000000
        span_id:  ff01020304050607
      },
    ]
    mapping_table:   [Mapping {}]
    attribute_table: [KeyValueAndUnit {}]
  }
  resource_profiles: [ResourceProfiles {
    resource: Resource {
      attributes: [
        { key_strindex: 9, value: { string_value_strindex: 11 } }, // "service.name", "my-service"
        { key_strindex: 10, value: { string_value_strindex: 12 } }, // "process.executable.name", "my-service.bin"
      ]
    }
    scope_profiles: [ScopeProfiles {
      profiles: [Profile {
        sample_type: ValueType { type_strindex: 1, unit_strindex: 2 } // "samples", "count"
        samples: [
          Sample { stack_index: 1, values: [5], link_index: 1 }, // Linked to trace span
          Sample { stack_index: 2, values: [3] },                // No span link
        ]
        time_unix_nano: 2000000000000000000
        duration_nano:  1000000000
        period_type: ValueType { type_strindex: 3, unit_strindex: 4 } // "cpu", "nanoseconds"
        period: 50000000 // 50ms = 20Hz
      }]
    }]
  }]
}
```

## 参考資料

- [Profiles Proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/profiles/v1development/profiles.proto): データフォーマットの現行バージョンを含みます
- [プロファイルセマンティック規約](/works/otel-specs-ja/semconv/general/profiles/)

