クライアントライブラリの作成
このドキュメントでは、Prometheusクライアントライブラリが提供すべき機能とAPIについて説明します。これは、ライブラリ間の一貫性を図り、一般的なユースケースを容易にし、ユーザーを誤った方向に導く可能性のある機能を提供しないことを目的としています。
執筆時点ですでに10の言語がサポートされているため、クライアントの作成方法については十分に理解しています。これらのガイドラインは、新しいクライアントライブラリの作成者が優れたライブラリを作成するのに役立つことを目指しています。
規則
MUST/MUST NOT/SHOULD/SHOULD NOT/MAYは、https://www.ietf.org/rfc/rfc2119.txtで与えられた意味を持ちます。
さらにENCOURAGEDとは、ライブラリが備えていることが望ましい機能ですが、存在しなくても問題ないという意味です。言い換えれば、あれば嬉しい機能です。
留意事項
-
各言語の機能を活用する。
-
一般的なユースケースは容易であるべきです。
-
正しい方法は簡単な方法であるべきです。
-
より複雑なユースケースも可能であるべきです。
一般的なユースケースは(順に)
-
ライブラリ/アプリケーション全体に広く分散されたラベルなしカウンタ。
-
Summary/Histogramでの関数/コードブロックのタイミング測定。
-
現在の状態(およびその制限)を追跡するためのGauge。
-
バッチジョブの監視。
全体構造
クライアントは内部的にコールバックベースで記述されなければなりません(MUST)。クライアントは一般的にここで説明されている構造に従うべきです(SHOULD)。
主要なクラスはコレクターです。これには、0個以上のメトリックとそのサンプルを返すメソッド(通常は「collect」と呼ばれる)があります。コレクターはCollectorRegistryに登録されます。データは、CollectorRegistryをクラス/メソッド/関数「ブリッジ」に渡すことによって公開され、Prometheusがサポートする形式でメトリックを返します。CollectorRegistryがスクレイピングされるたびに、各コレクターのcollectメソッドにコールバックする必要があります。
ほとんどのユーザーが対話するインターフェースは、Counter、Gauge、Summary、Histogramコレクターです。これらは単一のメトリックを表し、ユーザーが独自のコードを計装するほとんどのユースケースをカバーするはずです。
より高度なユースケース(別の監視/計装システムからのプロキシなど)では、カスタムコレクターを記述する必要があります。また、CollectorRegistryを受け取り、別の監視/計装システムが理解する形式でデータを生成する「ブリッジ」を作成したい場合もあります。これにより、ユーザーは1つの計装システムについてのみ考えるだけで済みます。
CollectorRegistryは`register()`/`unregister()`関数を提供すべきであり(SHOULD)、コレクターは複数のCollectorRegistryに登録できるべきです(SHOULD)。
クライアントライブラリはスレッドセーフでなければなりません(MUST)。
Cなどの非OO言語の場合、クライアントライブラリは、可能な限りこの構造の精神に従うべきです。
命名
クライアントライブラリは、このドキュメントに記載されている関数/メソッド/クラス名に従うべきであり(SHOULD)、使用する言語の命名規則を考慮に入れるべきです。例えば、Pythonでは`set_to_current_time()`は良いメソッド名ですが、Goでは`SetToCurrentTime()`がより良く、Javaでは`setToCurrentTime()`が慣例です。技術的な理由で名前が異なる場合(例:関数オーバーロードが許可されない場合)、ドキュメント/ヘルプ文字列はユーザーを他の名前へ誘導すべきです(SHOULD)。
ライブラリは、ここで与えられたものと同じまたは類似の名前を持つが、異なるセマンティクスを持つ関数/メソッド/クラスを提供してはなりません(MUST NOT)。
メトリックス
カウンター、ゲージ、サマリー、ヒストグラムのメトリックタイプがユーザーの主要なインターフェースです。
CounterとGaugeはクライアントライブラリの一部でなければなりません(MUST)。SummaryとHistogramの少なくとも一方は提供されなければなりません(MUST)。
これらは主にファイル静的変数、つまり計装するコードと同じファイルで定義されたグローバル変数として使用されるべきです。クライアントライブラリはこれを可能にするべきです(SHOULD)。一般的なユースケースは、オブジェクトの1つのインスタンスのコンテキストではなく、コード全体を計装することです。ユーザーはコード全体にメトリックを配線することについて心配する必要はありません。クライアントライブラリがそれを代行するべきです(そうでない場合、ユーザーはライブラリを「より簡単」にするためにラッパーを作成しますが、これはめったにうまくいきません)。
デフォルトのCollectorRegistryがなければなりません(MUST)。標準メトリックは、ユーザーが特別な作業を必要とせずに、デフォルトで暗黙的にそれに登録されなければなりません(MUST)。バッチジョブや単体テストで使用するために、メトリックがデフォルトのCollectorRegistryに登録されないようにする方法がなければなりません(MUST)。カスタムコレクターもこれに従うべきです(SHOULD)。
メトリックをどのように作成するかは、言語によって異なります。一部の言語(Java、Go)ではビルダーアプローチが最適ですが、他の言語(Python)では関数引数が1回の呼び出しで十分な場合があります。
例えば、Java Simpleclientでは
class YourClass {
static final Counter requests = Counter.build()
.name("requests_total")
.help("Requests.").register();
}
これにより、requestsがデフォルトのCollectorRegistryに登録されます。`register()`ではなく`build()`を呼び出すことで、メトリックは登録されません(単体テストに便利です)。また、`register()`にCollectorRegistryを渡すこともできます(バッチジョブに便利です)。
カウンター
カウンターは単調増加するカウンターです。値が減少することは許可されていません(MUST NOT)が、(サーバーの再起動などにより)0にリセットすることは可能です(MAY)。
カウンターは以下のメソッドを保有しなければなりません(MUST)。
inc()
: カウンターを1増やすinc(double v)
: カウンターを指定された量だけ増やす。v >= 0であることを確認しなければなりません(MUST)。
カウンターは、次の機能を持つことが推奨されます(ENCOURAGED)。
特定のコードブロックで発生した例外、およびオプションで特定の種類の例外をカウントする方法。これはPythonのcount_exceptionsです。
カウンタは0から始まらなければなりません。
ゲージ
ゲージは、増減する可能性のある値を表します。
ゲージは以下のメソッドを保有しなければなりません(MUST)。
inc()
: ゲージを1増やすinc(double v)
: ゲージを指定された量だけ増やすdec()
: ゲージを1減らすdec(double v)
: ゲージを指定された量だけ減らすset(double v)
: ゲージを指定された値に設定する
ゲージは0から始まらなければなりません(MUST)。特定のゲージが異なる数値から始まる方法を提供しても構いません(MAY)。
ゲージは以下のメソッドを保有すべきです(SHOULD)。
set_to_current_time()
: ゲージを現在のユニックス時間(秒単位)に設定する。
ゲージは、以下の機能を持つことが推奨されます(ENCOURAGED)。
コード/関数の進行中のリクエストを追跡する方法。これはPythonの`track_inprogress`です。
コードの実行時間を測定し、その時間を秒単位でゲージに設定する方法。これはバッチジョブに役立ちます。JavaではstartTimer/setDuration、Pythonでは`time()`デコレーター/コンテキストマネージャーです。これはSummary/Histogramのパターンと一致すべきです(ただし、`observe()`ではなく`set()`を使用します)。
要約
サマリーは、スライディングウィンドウ期間にわたる観測値(通常はリクエストの持続時間など)をサンプリングし、それらの分布、頻度、合計に関する即座の洞察を提供します。
サマリーは、内部でサマリーの分位数を指定するために使用されるため、ユーザーが「quantile」をラベル名として設定することを許可してはなりません(MUST NOT)。サマリーは、集計できず遅くなる傾向があるものの、分位数をエクスポートとして提供することが推奨されます(ENCOURAGED)。サマリーは分位数を持たないことを許可しなければなりません(MUST)。なぜなら、`_count`/`_sum`だけでも非常に有用であり、これがデフォルトであるべきだからです(MUST)。
サマリーは以下のメソッドを保有しなければなりません(MUST)。
observe(double v)
: 与えられた量を観測する
サマリーは以下のメソッドを保有すべきです(SHOULD)。
コードの実行時間を秒単位でユーザーに伝える何らかの方法。Pythonでは`time()`デコレーター/コンテキストマネージャーです。JavaではstartTimer/observeDurationです。秒以外の単位は提供してはなりません(MUST NOT)(ユーザーが別の単位を希望する場合は、手動で行うことができます)。これはGauge/Histogramと同じパターンに従うべきです。
Summaryの`_count`/`_sum`は0から始まらなければなりません(MUST)。
ヒストグラム
ヒストグラムは、リクエストの遅延などのイベントの集計可能な分布を可能にします。これは、本質的にバケットごとのカウンターです。
ヒストグラムは、`le`が内部でバケットを指定するために使用されるため、ユーザーが設定するラベルとして`le`を許可してはなりません(MUST NOT)。
ヒストグラムはバケットを手動で選択する方法を提供しなければなりません(MUST)。`linear(start, width, count)`および`exponential(start, factor, count)`方式でバケットを設定する方法を提供すべきです(SHOULD)。Countには`+Inf`バケットを含まなければなりません(MUST)。
ヒストグラムは他のクライアントライブラリと同じデフォルトのバケットを持つべきです(SHOULD)。メトリック作成後はバケットを変更してはなりません(MUST NOT)。
ヒストグラムは以下のメソッドを保有しなければなりません(MUST)。
observe(double v)
: 与えられた量を観測する
ヒストグラムは以下のメソッドを保有すべきです(SHOULD)。
コードの実行時間を秒単位でユーザーに伝える何らかの方法。Pythonでは`time()`デコレーター/コンテキストマネージャーです。Javaでは`startTimer`/`observeDuration`です。秒以外の単位は提供してはなりません(MUST NOT)(ユーザーが別の単位を希望する場合は、手動で行うことができます)。これはGauge/Summaryと同じパターンに従うべきです。
ヒストグラムの`_count`/`_sum`およびバケットは0から始まらなければなりません(MUST)。
メトリックに関するさらなる考慮事項
上記のドキュメントを超えて、特定の言語にとって意味のある追加機能をメトリックに提供することは推奨されます(ENCOURAGED)。
もし、一般的なユースケースをよりシンプルにできるのであれば、不適切な振る舞い(例えば、最適ではないメトリック/ラベルの配置や、クライアントでの計算など)を助長しない限り、そうすべきです。
ラベル
ラベルはPrometheusの最も強力な側面の一つですが、簡単に濫用されます。そのため、クライアントライブラリは、ラベルをユーザーに提供する方法について非常に注意を払う必要があります。
クライアントライブラリは、Gauge/Counter/Summary/Histogram、またはライブラリが提供するその他のCollectorに対して、同じメトリックに異なるラベル名を付けることをユーザーに許可してはなりません(MUST NOT)。
カスタムコレクターからのメトリックは、ほとんどの場合、一貫したラベル名を持つべきです。ただし、まれにそうではない正当なユースケースも存在するため、クライアントライブラリはこれを検証すべきではありません。
ラベルは強力ですが、ほとんどのメトリックにはラベルがありません。したがって、APIはラベルを許可すべきですが、それを支配すべきではありません。
クライアントライブラリは、Gauge/Counter/Summary/Histogramの作成時に、ラベル名のリストをオプションで指定できるようにしなければなりません(MUST)。クライアントライブラリは、任意の数のラベル名をサポートすべきです(SHOULD)。クライアントライブラリは、ラベル名がドキュメントに記載された要件を満たしていることを検証しなければなりません(MUST)。
ラベル付きメトリックディメンションへのアクセスを提供する一般的な方法は、ラベル値のリストまたはラベル名からラベル値へのマップを受け取り、「子」を返す`labels()`メソッドを使用することです。その後、通常どおり`.inc()`/`.dec()`/`.observe()`などのメソッドを子に対して呼び出すことができます。
`labels()`によって返される子(Child)は、ユーザーによってキャッシュ可能であるべきです(SHOULD)。これにより、再度検索する必要がなくなります。これは、遅延にクリティカルなコードでは重要です。
ラベル付きメトリックは、`labels()`と同じシグネチャを持つ`remove()`メソッドをサポートすべきです(SHOULD)。これは、もはやエクスポートされない子をメトリックから削除します。また、メトリックからすべての子を削除する`clear()`メソッドもサポートすべきです(SHOULD)。これらは子のキャッシングを無効にします。
特定のChildをデフォルト値で初期化する方法があるべきです。通常は単に`labels()`を呼び出すだけです。ラベルのないメトリックは、メトリックの欠落による問題を避けるために、常に初期化されなければなりません(MUST)。
メトリック名
メトリック名は仕様に従わなければなりません(MUST)。ラベル名と同様に、Gauge/Counter/Summary/Histogramの使用や、ライブラリで提供されるその他のCollectorについても、これが満たされなければなりません(MUST)。
多くのクライアントライブラリは、名前を3つの部分に設定することを提供します。namespace_subsystem_name
のうち、name
のみが必須です。
カスタムコレクターが他の計装/監視システムからプロキシしている場合を除き、動的/生成されたメトリック名またはメトリック名のサブパートは推奨されません(MUST BE Discouraged)。生成/動的なメトリック名は、代わりにラベルを使用すべきであるという兆候です。
メトリックの説明とヘルプ
Gauge/Counter/Summary/Histogramは、メトリックの説明/ヘルプを提供することを要求しなければなりません(MUST)。
クライアントライブラリに付属するカスタムコレクターは、そのメトリックに関する説明/ヘルプを持たなければなりません(MUST)。
必須引数とすることを推奨しますが、特定の長さであることをチェックする必要はありません。なぜなら、本当にドキュメントを書きたくない人を説得することはないからです。ライブラリに付属する(そしてエコシステム内で可能な限りどこでも)コレクターは、良いメトリック説明を持つべきです(SHOULD)。これは、模範を示すためです。
エクスポジション
クライアントは、エクスポジション形式のドキュメントで概説されているテキストベースのエクスポジション形式を実装しなければなりません(MUST)。
リソースコストが著しく増大しないのであれば、公開されるメトリックの再現可能な順序(特に人間が読める形式の場合)が推奨されます(ENCOURAGED)。
標準コレクタとランタイムコレクタ
クライアントライブラリは、以下のドキュメントにある標準エクスポートのうち、提供できるものをすべて提供すべきです(SHOULD)。
これらはカスタムコレクターとして実装され、デフォルトでデフォルトのCollectorRegistryに登録されるべきです(SHOULD)。これらを無効にする方法があるべきです(SHOULD)。なぜなら、非常に特殊なユースケースではこれらが邪魔になる場合があるからです。
プロセスメトリック
これらのメトリックは`process_`というプレフィックスを持ちます。使用されている言語やランタイムで必要な値を取得するのが問題があるか不可能である場合、クライアントライブラリは、偽の、不正確な、または特殊な値(`NaN`など)をエクスポートするよりも、対応するメトリックを省略することを優先すべきです(SHOULD)。すべてのメモリ値はバイト単位、すべての時間はユニックス時間/秒単位です。
メトリック名 | ヘルプ文字列 | 単位 |
---|---|---|
process_cpu_seconds_total | ユーザーおよびシステムCPUの総使用時間(秒単位)。 | 秒 |
process_open_fds | 開いているファイルディスクリプタの数。 | ファイルディスクリプタ |
process_max_fds | 開いているファイルディスクリプタの最大数。 | ファイルディスクリプタ |
process_virtual_memory_bytes | 仮想メモリサイズ(バイト単位)。 | バイト |
process_virtual_memory_max_bytes | 利用可能な仮想メモリの最大量(バイト単位)。 | バイト |
process_resident_memory_bytes | 常駐メモリサイズ(バイト単位)。 | バイト |
process_heap_bytes | プロセスヒープサイズ(バイト単位)。 | バイト |
process_start_time_seconds | UNIXエポックからのプロセス開始時刻(秒単位)。 | 秒 |
process_threads | プロセス内のOSスレッド数。 | スレッド |
ランタイムメトリック
さらに、クライアントライブラリは、言語のランタイムに関するメトリック(例:ガベージコレクション統計)を、`go_`、`hotspot_`などの適切なプレフィックスを付けて提供することも推奨されます(ENCOURAGED)。
単体テスト
クライアントライブラリは、コア計装ライブラリとエクスポジションをカバーする単体テストを持つべきです(SHOULD)。
クライアントライブラリは、ユーザーが計装コードの使用を単体テストしやすくする方法を提供することが推奨されます(ENCOURAGED)。例えば、Pythonの`CollectorRegistry.get_sample_value`などです。
パッケージングと依存関係
理想的には、クライアントライブラリは、アプリケーションを壊すことなく、いくつかの計装を追加するために任意のアプリケーションに含めることができます。
したがって、クライアントライブラリに依存関係を追加する際には注意が必要です。たとえば、あるライブラリが、ライブラリのバージョンx.yを必要とするPrometheusクライアントを使用しているが、アプリケーションが他の場所でx.zを使用している場合、それはアプリケーションに悪影響を与えるでしょうか?
このような問題が発生する可能性がある場合、コア計装と特定の形式でのメトリックのブリッジ/公開を分離することが推奨されます。たとえば、Java simpleclientの`simpleclient`モジュールには依存関係がなく、`simpleclient_servlet`にはHTTP関連の部分があります。
パフォーマンスに関する考慮事項
クライアントライブラリはスレッドセーフでなければならないため、何らかの並行性制御が必要であり、マルチコアマシンやアプリケーションでのパフォーマンスを考慮する必要があります。
私たちの経験では、最もパフォーマンスが低いのはミューテックスです。
プロセッサのアトミック命令は中程度で、一般的に許容されます。
JavaのsimpleclientにおけるDoubleAdderのように、異なるCPUが同じRAMのビットを変更するのを避けるアプローチが最も効果的です。ただし、メモリコストがかかります。
上記で述べたように、`labels()`の結果はキャッシュ可能であるべきです。ラベル付きメトリックをサポートする並行マップは比較的に遅い傾向があります。ラベルなしメトリックを特別扱いして、`labels()`のようなルックアップを避けることで、大きく改善できます。
メトリックは、インクリメント/デクリメント/設定などが進行中にブロックするのを避けるべきです(SHOULD)。スクレイピング中にアプリケーション全体が停止するのは望ましくないからです。
ラベルを含む主要な計装操作のベンチマークを実施することは推奨されます(ENCOURAGED)。
エクスポジションを実行する際には、リソース消費、特にRAMに留意すべきです。結果をストリーミングしたり、同時スクレイピングの数を制限したりすることで、メモリフットプリントを削減することを検討してください。