クライアントライブラリの作成

このドキュメントでは、Prometheus クライアントライブラリが提供すべき機能と API について説明します。ライブラリ間で一貫性を保ち、簡単なユースケースを簡単にし、ユーザーを誤った方向に導く可能性のある機能の提供を避けることを目的としています。

執筆時点ですでに10 の言語がサポートされているため、クライアントの作成方法について十分に理解できています。これらのガイドラインは、新しいクライアントライブラリの作成者が優れたライブラリを作成するのに役立つことを目的としています。

規約

MUST/MUST NOT/SHOULD/SHOULD NOT/MAY は、https://www.ietf.org/rfc/rfc2119.txt に記載されている意味を持ちます。

さらに、ENCOURAGED とは、ライブラリが持つことが望ましいが、存在しなくても構わない機能であることを意味します。言い換えれば、あると便利です。

留意すべき事項

  • 各言語の機能を活用する。

  • 一般的なユースケースは簡単であるべきです。

  • 何かを行う正しい方法が簡単な方法であるべきです。

  • より複雑なユースケースも可能であるべきです。

一般的なユースケースは (順に)

  • ライブラリ/アプリケーション全体に散在するラベルのないカウンター。

  • サマリー/ヒストグラムでの関数/コードブロックのタイミング測定。

  • モノの現在の状態 (およびその制限) を追跡するゲージ。

  • バッチジョブの監視。

全体構造

クライアントは内部的にコールバックベースとなるように記述する必要があります。クライアントは一般的にここに記述する構造に従う必要があります。

キーとなるクラスはコレクターです。これには、ゼロ以上のメトリクスとそのサンプルを返すメソッド (通常は 'collect' と呼ばれます) があります。コレクターは CollectorRegistry に登録されます。データは、CollectorRegistry をクラス/メソッド/関数 "bridge" に渡すことで公開され、Prometheus がサポートする形式でメトリクスを返します。CollectorRegistry がスクレイプされるたびに、各コレクターの collect メソッドにコールバックする必要があります。

ほとんどのユーザーが対話するインターフェイスは、Counter、Gauge、Summary、および Histogram コレクターです。これらは単一のメトリクスを表し、ユーザーが独自のコードを計測する際のほとんどのユースケースをカバーする必要があります。

より高度なユースケース (別の監視/計測システムからのプロキシなど) では、カスタムコレクターを記述する必要があります。また、CollectorRegistry を受け取り、別の監視/計測システムが理解できる形式でデータを生成する "bridge" を記述して、ユーザーが 1 つの計測システムのみを考慮すれば済むようにすることもできます。

CollectorRegistry は register()/unregister() 関数を提供する必要があり、コレクターは複数の CollectorRegistry に登録できる必要があります。

クライアントライブラリはスレッドセーフである必要があります。

C のような非 OO 言語の場合、クライアントライブラリは、可能な限りこの構造の精神に従う必要があります。

命名

クライアントライブラリは、このドキュメントで言及されている関数/メソッド/クラス名に従う必要があります。作業している言語の命名規則に留意してください。たとえば、set_to_current_time() は Python のメソッド名として適していますが、SetToCurrentTime() は Go でより適しており、setToCurrentTime() は Java の規則です。技術的な理由 (関数オーバーロードを許可しないなど) で名前が異なる場合は、ドキュメント/ヘルプ文字列で他の名前をユーザーに指示する必要があります。

ライブラリは、ここで示すものと同じまたは類似した名前で、異なるセマンティクスを持つ関数/メソッド/クラスを提供してはなりません。

メトリクス

Counter、Gauge、Summary、および Histogram のメトリクスタイプは、ユーザーが使用する主要なインターフェイスです。

Counter と Gauge はクライアントライブラリの一部である必要があります。Summary と Histogram のうち少なくとも 1 つは提供する必要があります。

これらは、主にファイル静的変数、つまり、計測しているコードと同じファイルで定義されたグローバル変数として使用する必要があります。クライアントライブラリはこれを有効にする必要があります。一般的なユースケースは、オブジェクトの 1 つのインスタンスのコンテキストではなく、コード全体を計測することです。ユーザーは、コード全体にメトリクスを配管することを心配する必要はありません。クライアントライブラリがそれを行う必要があります (そうしない場合、ユーザーはライブラリを「簡単にする」ためのラッパーを記述しますが、これはあまりうまくいかない傾向があります)。

デフォルトの CollectorRegistry が存在する必要があり、標準メトリクスはデフォルトでユーザーによる特別な作業を必要とせずに暗黙的に登録される必要があります。バッチジョブやユニットテストで使用するために、メトリクスをデフォルトの CollectorRegistry に登録しない方法が必要です。カスタムコレクターもこれに従う必要があります。

メトリクスをどのように作成するかは、言語によって異なります。一部 (Java、Go) では、ビルダーアプローチが最適ですが、他の言語 (Python) では、関数の引数が 1 回の呼び出しで十分に行うことができます。

たとえば、Java Simpleclient では次のようになります。

class YourClass {
  static final Counter requests = Counter.build()
      .name("requests_total")
      .help("Requests.").register();
}

これにより、デフォルトの CollectorRegistry にリクエストが登録されます。register() ではなく build() を呼び出すと、メトリクスは登録されません (ユニットテストに便利)。また、register() に CollectorRegistry を渡すこともできます (バッチジョブに便利)。

カウンター

カウンターは、単調増加するカウンターです。値が減少することは許可されてはなりませんが、0 にリセットされることはあります (サーバーの再起動など)。

カウンターには、次のメソッドが必要です

  • inc(): カウンターを 1 増やす
  • inc(double v): カウンターを指定された量だけ増やす。v >= 0 であることを確認する必要があります。

カウンターは ENCOURAGED です。

特定のコードでスロー/発生した例外、およびオプションで特定の種類の例外をカウントする方法。これは Python の count_exceptions です。

カウンターは 0 から開始する必要があります。

ゲージ

ゲージは、上下に変動できる値を表します。

ゲージには、次のメソッドが必要です

  • inc(): ゲージを 1 増やす
  • inc(double v): ゲージを指定された量だけ増やす
  • dec(): ゲージを 1 減らす
  • dec(double v): ゲージを指定された量だけ減らす
  • set(double v): ゲージを指定された値に設定する

ゲージは 0 から開始する必要があり、特定のゲージが異なる数値で開始する方法を提供しても構いません。

ゲージには、次のメソッドが必要です。

  • set_to_current_time(): ゲージを現在の Unix 時間 (秒単位) に設定します。

ゲージは ENCOURAGED です。

一部のコード/関数で進行中のリクエストを追跡する方法。これは Python の track_inprogress です。

コードを測定し、ゲージをその継続時間 (秒単位) に設定する方法。これはバッチジョブに役立ちます。これは Java の startTimer/setDuration と、Python の time() デコレーター/コンテキストマネージャーです。これは、Summary/Histogram のパターンと一致する必要があります (observe() ではなく set() ですが)。

サマリー

サマリーは、時間のスライディングウィンドウで観測 (通常はリクエスト時間など) をサンプリングし、その分布、頻度、および合計に関する即時的な洞察を提供します。

サマリーは、内部的にサマリー分位数を指定するために使用されるため、ユーザーが「分位数」をラベル名として設定することを許可してはなりません。サマリーは、エクスポートとして分位数を提供することが ENCOURAGED です。ただし、これらは集計できず、遅くなる傾向があります。サマリーは、分位数を持たないことを許可する必要があります。_count/_sum だけでも非常に便利であり、これがデフォルトである必要があります。

サマリーには、次のメソッドが必要です

  • observe(double v): 指定された量を観測する

サマリーには、次のメソッドが必要です

ユーザーがコードを秒単位で計測するための何らかの方法。Python では、これは time() デコレーター/コンテキストマネージャーです。Java では、これは startTimer/observeDuration です。秒以外の単位を提供してはなりません (ユーザーが他のものを必要とする場合は、手動で行うことができます)。これは、Gauge/Histogram と同じパターンに従う必要があります。

サマリー _count/_sum は 0 から開始する必要があります。

ヒストグラム

ヒストグラムを使用すると、リクエストレイテンシなどのイベントの集計可能な分布が可能になります。これは、本質的にバケットごとのカウンターです。

ヒストグラムは、le がバケットを指定するために内部的に使用されるため、ユーザー設定ラベルとして le を許可してはなりません。

ヒストグラムは、バケットを手動で選択する方法を必ず提供する必要があります。linear(start, width, count) および exponential(start, factor, count) 形式でバケットを設定する方法が提供されるべきです。カウントには、+Inf バケットが必ず含まれている必要があります。

ヒストグラムは、他のクライアントライブラリと同じデフォルトのバケットを持つべきです。メトリクスが作成されたら、バケットは変更できないようにする必要があります。

ヒストグラムは、以下のメソッドを必ず持つ必要があります。

  • observe(double v): 指定された量を観測する

ヒストグラムは、以下のメソッドを持つべきです。

ユーザーがコードの実行時間を秒単位で計測する方法が必要です。Pythonでは、これはtime()デコレーター/コンテキストマネージャーです。Javaでは、startTimer/observeDurationです。秒以外の単位は提供すべきではありません(別の単位が必要な場合は、ユーザーが手動で行うことができます)。これは、ゲージ/サマリーと同じパターンに従う必要があります。

ヒストグラムの_count/_sumとバケットは、0から開始する必要があります。

メトリクスに関するさらなる考慮事項

特定の言語に適した、上記で文書化されている以上の追加機能をメトリクスで提供することは推奨されます。

一般的なユースケースをより簡単にする方法があれば、それを使用してください。ただし、望ましくない動作(サブ最適なメトリクス/ラベルレイアウトや、クライアントでの計算など)を助長しない場合に限ります。

ラベル

ラベルはPrometheusの最も強力な側面の1つですが、簡単に乱用される可能性もあります。したがって、クライアントライブラリは、ユーザーにラベルを提供する方法を非常に慎重に検討する必要があります。

クライアントライブラリは、ゲージ/カウンター/サマリー/ヒストグラム、またはライブラリによって提供されるその他のコレクターに対して、同じメトリクスで異なるラベル名を持つことをユーザーに許可してはなりません。

カスタムコレクターからのメトリクスは、ほとんどの場合、一貫したラベル名を持つ必要があります。まれではあるものの、これが当てはまらない有効なユースケースも存在するため、クライアントライブラリはこれを検証すべきではありません。

ラベルは強力ですが、ほとんどのメトリクスにはラベルがありません。したがって、APIはラベルを許可する必要がありますが、APIの中心になってはいけません。

クライアントライブラリは、ゲージ/カウンター/サマリー/ヒストグラムの作成時にラベル名のリストをオプションで指定できるようにする必要があります。クライアントライブラリは、任意の数のラベル名をサポートする必要があります。クライアントライブラリは、ラベル名が文書化された要件を満たしていることを検証する必要があります。

メトリクスのラベル付きディメンションへのアクセスを提供する一般的な方法は、ラベル値のリストまたはラベル名からラベル値へのマップのいずれかを受け取り、「子」を返すlabels()メソッドを使用することです。その後、通常どおり、.inc()/.dec()/.observe()などのメソッドを子で呼び出すことができます。

labels()によって返される子は、ユーザーによってキャッシュ可能であるべきです。これは、再度ルックアップする必要がないようにするためで、レイテンシーが重要なコードでは重要です。

ラベル付きのメトリクスは、メトリクスから子を削除してエクスポートしないlabels()と同じシグネチャを持つremove()メソッドと、メトリクスからすべての子を削除するclear()メソッドをサポートする必要があります。これらは、子のキャッシュを無効にします。

指定された子をデフォルト値で初期化する方法があるべきです。通常はlabels()を呼び出すだけです。ラベルのないメトリクスは、メトリクスの欠落の問題を回避するために、常に初期化される必要があります。

メトリクス名

メトリクス名は、仕様に従う必要があります。ラベル名と同様に、これはゲージ/カウンター/サマリー/ヒストグラムの使用、およびライブラリで提供されるその他のコレクターで満たされている必要があります。

多くのクライアントライブラリは、名前をnamespace_subsystem_nameの3つの部分で設定できるようにしています。このうち、必須なのはnameのみです。

カスタムコレクターが他の計測/監視システムからプロキシしている場合を除き、動的/生成されたメトリクス名またはメトリクス名のサブパーツは推奨されません。生成/動的なメトリクス名は、代わりにラベルを使用する必要がある兆候です。

メトリクスの説明とヘルプ

ゲージ/カウンター/サマリー/ヒストグラムでは、メトリクスの説明/ヘルプを提供する必要があります。

クライアントライブラリで提供されるカスタムコレクターには、メトリクスの説明/ヘルプが必要です。

必須引数にすることをお勧めしますが、特定の長さをチェックすることは避けてください。誰かがドキュメントを書きたくない場合、そうするように説得することはできません。ライブラリで提供されるコレクター(およびエコシステム内で可能なすべての場所で)は、模範を示すために、優れたメトリクスの説明を持つべきです。

公開

クライアントは、公開形式のドキュメントで概説されているテキストベースの公開形式を実装する必要があります。

公開されたメトリクスの再現可能な順序は、特に人間が判読できる形式では、リソースコストが大幅に増加することなく実装できる場合は推奨されます。

標準およびランタイムコレクター

クライアントライブラリは、以下に文書化されている標準のエクスポートを可能な限り提供する必要があります。

これらはカスタムコレクターとして実装し、デフォルトのCollectorRegistryにデフォルトで登録する必要があります。一部の非常にニッチなユースケースでは、これらのエクスポートが邪魔になることがあるため、これらを無効にする方法が必要です。

プロセスメトリクス

これらのメトリクスには、プレフィックスprocess_が付いています。使用されている言語またはランタイムで必要な値を取得するのが困難または不可能な場合、クライアントライブラリは、誤った値、不正確な値、または特別な値(NaNなど)をエクスポートするのではなく、対応するメトリクスを省略することを優先する必要があります。すべてのメモリ値はバイト単位、すべての時間はUnix時間/秒単位です。

メトリクス名 ヘルプ文字列 単位
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_などの適切なプレフィックスを使用して、意味のあるものを提供するように推奨されています。

単体テスト

クライアントライブラリは、コア計測ライブラリと公開をカバーする単体テストを持つ必要があります。

クライアントライブラリは、ユーザーが計測コードの使用を簡単に単体テストできるようにする方法を提供することが推奨されます。たとえば、PythonのCollectorRegistry.get_sample_valueなどです。

パッケージングと依存関係

理想的には、クライアントライブラリは、アプリケーションを破損させることなく、計測機能を追加するために任意のアプリケーションに含めることができます。

したがって、クライアントライブラリに依存関係を追加する際には注意が必要です。たとえば、ライブラリのバージョンx.yを必要とするPrometheusクライアントを使用するライブラリを追加した場合、アプリケーションが他の場所でx.zを使用していると、アプリケーションに悪影響がありますか。

これが生じる可能性がある場合は、コア計測を特定の形式のメトリクスのブリッジ/公開から分離することが推奨されます。たとえば、Javaのsimpleclientsimpleclientモジュールには依存関係がなく、simpleclient_servletにはHTTPビットがあります。

パフォーマンスに関する考慮事項

クライアントライブラリはスレッドセーフである必要があるため、何らかの形式の並行性制御が必要であり、マルチコアマシンおよびアプリケーションでのパフォーマンスを考慮する必要があります。

私たちの経験では、最もパフォーマンスが低いのはミューテックスです。

プロセッサのアトミック命令は中間であり、一般的に許容範囲内です。

JavaのsimpleclientのDoubleAdderのように、異なるCPUが同じRAMビットを書き換えるのを避けるアプローチが最も効果的です。ただし、メモリコストはかかります。

上記のように、labels()の結果はキャッシュ可能である必要があります。ラベル付きのメトリクスをバックアップする傾向がある同時実行マップは、比較的遅くなる傾向があります。labels()のようなルックアップを避けるためにラベルなしのメトリクスを特別に処理すると、非常に役立つ可能性があります。

メトリクスは、インクリメント/デクリメント/設定などを行っているときにブロックを避ける必要があります。スクレイピングが進行中にアプリケーション全体が停止するのは望ましくないためです。

ラベルを含む主要な計測操作のベンチマークを持つことは推奨されます。

リソース消費、特にRAMは、公開を実行する際に考慮に入れる必要があります。結果をストリーミングし、場合によっては同時スクレイピングの数を制限することで、メモリフットプリントを削減することを検討してください。

このドキュメントはオープンソースです。問題またはプルリクエストを提出して改善にご協力ください。