撰寫客戶端函式庫

本文檔涵蓋 Prometheus 客戶端函式庫應提供的功能和 API,目標是讓函式庫之間保持一致性,使簡單的使用案例變得容易,並避免提供可能導致使用者走錯方向的功能。

在撰寫本文時,已有 10 種語言支援,因此我們現在已經對如何撰寫客戶端有了很好的概念。這些準則旨在幫助新客戶端函式庫的作者製作優良的函式庫。

慣例

MUST/MUST NOT/SHOULD/SHOULD NOT/MAY 的含義如 https://www.ietf.org/rfc/rfc2119.txt 中所述。

此外,ENCOURAGED 表示功能是函式庫所期望的,但即使不存在也沒關係。換句話說,錦上添花。

注意事項

  • 善用每種語言的功能。

  • 常見的使用案例應該容易。

  • 正確的做事方式應該是容易的方式。

  • 更複雜的使用案例應該是可行的。

常見的使用案例是(依序)

  • 沒有標籤的計數器散佈在函式庫/應用程式中。

  • 在摘要/直方圖中計時函式/程式碼區塊。

  • 量表追蹤事物的當前狀態(及其限制)。

  • 監控批次任務。

整體結構

客戶端必須在內部以回呼為基礎撰寫。客戶端通常應該遵循此處描述的結構。

主要類別是收集器。它有一個方法(通常稱為「collect」),該方法會傳回零個或多個度量及其樣本。收集器會向 CollectorRegistry 註冊。透過將 CollectorRegistry 傳遞給類別/方法/函式「橋接器」來顯示資料,該橋接器會以 Prometheus 支援的格式傳回度量。每次抓取 CollectorRegistry 時,都必須回呼每個收集器的 collect 方法。

大多數使用者與之互動的介面是計數器、量表、摘要和直方圖收集器。這些代表單一度量,並且應涵蓋使用者檢測自己程式碼的絕大多數使用案例。

更進階的使用案例(例如從另一個監控/檢測系統代理)需要撰寫自訂收集器。有人可能還想要撰寫一個「橋接器」,該橋接器會採用 CollectorRegistry 並以不同的監控/檢測系統理解的格式產生資料,讓使用者只需考慮一個檢測系統。

CollectorRegistry 應該提供 register()/unregister() 函式,並且應該允許將收集器註冊到多個 CollectorRegistry。

客戶端函式庫必須是執行緒安全的。

對於非物件導向語言(例如 C),客戶端函式庫應盡可能遵循此結構的精神。

命名

客戶端函式庫應該遵循本文檔中提到的函式/方法/類別名稱,同時考慮到它們所使用的語言的命名慣例。例如,set_to_current_time() 對於 Python 中的方法名稱來說是不錯的,但 SetToCurrentTime() 在 Go 中更好,而 setToCurrentTime() 是 Java 中的慣例。如果名稱因技術原因而不同(例如,不允許函式多載),則文件/說明字串應將使用者指向其他名稱。

函式庫不得提供與此處提供的名稱相同或相似的函式/方法/類別,但具有不同的語義。

度量

計數器、量表、摘要和直方圖度量類型是使用者使用的主要介面。

計數器和量表必須是客戶端函式庫的一部分。至少必須提供摘要和直方圖之一。

這些主要應作為檔案靜態變數使用,也就是說,在與它們檢測的程式碼相同的檔案中定義的全域變數。客戶端函式庫應該啟用此功能。常見的使用案例是檢測一段程式碼的整體,而不是在物件的單一實例中檢測一段程式碼。使用者不必擔心在整個程式碼中管道傳輸他們的度量,客戶端函式庫應該為他們執行此操作(如果沒有,使用者會編寫一個包裝器來使函式庫「更容易」 - 這通常很少會順利進行)。

必須有一個預設的 CollectorRegistry,預設情況下,標準度量必須在使用者無需任何特殊工作的情況下隱式註冊到其中。必須有一種方法讓度量不註冊到預設的 CollectorRegistry,以用於批次任務和單元測試。自訂收集器也應遵循此方法。

確切地應如何建立度量取決於語言。對於某些語言(Java、Go),建構器方法是最佳的,而對於其他語言(Python),函式引數就足以在一次呼叫中完成。

例如,在 Java Simpleclient 中,我們有

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

這會將要求註冊到預設的 CollectorRegistry。透過呼叫 build() 而不是 register(),度量將不會註冊(方便用於單元測試),您也可以將 CollectorRegistry 傳遞給 register()(方便用於批次任務)。

計數器

計數器是一個單調遞增的計數器。它絕不能允許值減少,但可以重設為 0(例如,透過伺服器重新啟動)。

計數器必須具有以下方法

  • inc():將計數器加 1
  • inc(double v):將計數器增加指定量。必須檢查 v >= 0。

計數器建議具有

一種計算在給定程式碼中擲出/引發的例外狀況的方法,並且可選擇僅計算特定類型的例外狀況。這是 Python 中的 count_exceptions。

計數器必須從 0 開始。

量表

量表表示可以上升和下降的值。

量表必須具有以下方法

  • inc():將量表加 1
  • inc(double v):將量表增加指定量
  • dec():將量表減 1
  • dec(double v):將量表減少指定量
  • set(double v):將量表設定為指定值

量表必須從 0 開始,您可以提供一種方法讓給定的量表從不同的數字開始。

量表建議具有以下方法

  • set_to_current_time():將量表設定為目前的 Unix 時間(以秒為單位)。

量表建議具有

一種追蹤在某些程式碼/函式中正在進行的要求的方法。這是 Python 中的 track_inprogress

一種計時一段程式碼並將量表設定為其持續時間(以秒為單位)的方法。這對於批次任務很有用。這是 Java 中的 startTimer/setDuration,以及 Python 中的 time() 修飾詞/內容管理器。這應該符合摘要/直方圖中的模式(儘管使用 set() 而不是 observe())。

摘要

摘要會隨著時間的推移對觀測值(通常是要求持續時間之類的事物)進行取樣,並提供對其分佈、頻率和總和的即時洞察。

摘要絕不允許使用者將「quantile」設定為標籤名稱,因為它在內部用於指定摘要分位數。摘要建議將分位數作為匯出項提供,但這些分位數無法聚合,並且往往速度很慢。摘要必須允許不具有分位數,因為僅 _count/_sum 就非常有用,這必須是預設值。

摘要必須具有以下方法

  • observe(double v):觀察指定的量

摘要應該具有以下方法

以秒為單位計時使用者程式碼的某種方法。在 Python 中,這是 time() 修飾詞/內容管理器。在 Java 中,這是 startTimer/observeDuration。不得提供秒以外的單位(如果使用者想要其他單位,他們可以手動執行)。這應該遵循與量表/直方圖相同的模式。

摘要 _count/_sum 必須從 0 開始。

直方圖

直方圖允許事件(例如要求延遲)的可聚合分佈。這主要是每個儲存區的計數器。

直方圖絕不允許使用者設定 le 作為標籤,因為 le 在內部用於指定儲存區。

直方圖必須提供一種手動選擇儲存區的方法。應該提供以 linear(start, width, count)exponential(start, factor, count) 方式設定儲存區的方法。計數必須包含 +Inf 儲存區。

直方圖應該具有與其他客戶端函式庫相同的預設儲存區。一旦建立度量,就不得變更儲存區。

直方圖必須具有以下方法

  • observe(double v):觀察指定的量

直方圖應該具有以下方法

提供以秒為單位的程式碼計時方式給使用者。在 Python 中,這可以使用 time() 修飾器/上下文管理器。在 Java 中,則使用 startTimer/observeDuration。除了秒之外,不應提供其他單位 (如果使用者需要其他單位,他們可以自行轉換)。這應遵循與 Gauge/Summary 相同的模式。

直方圖 (Histogram) 的 _count/_sum 和 bucket 必須從 0 開始。

進一步的指標考量

鼓勵在指標中提供超出上述文件記錄的額外功能,只要對特定語言有意義即可。

如果有一個常見的使用案例可以簡化,那就去做吧,只要它不會鼓勵不良行為 (例如次佳的指標/標籤佈局,或在客戶端執行計算)。

標籤

標籤是 Prometheus 最強大的功能之一,但也容易被濫用。因此,用戶端函式庫必須非常謹慎地向使用者提供標籤。

用戶端函式庫絕對不能允許使用者針對相同的指標,在 Gauge/Counter/Summary/Histogram 或函式庫提供的任何其他收集器 (Collector) 上使用不同的標籤名稱。

來自自訂收集器的指標幾乎都應具有一致的標籤名稱。由於仍然存在少數但有效的情況並非如此,因此用戶端函式庫不應驗證這一點。

雖然標籤功能強大,但大多數指標不會有標籤。因此,API 應允許使用標籤,但不應讓標籤主導 API。

用戶端函式庫必須允許在建立 Gauge/Counter/Summary/Histogram 時選擇性地指定標籤名稱清單。用戶端函式庫應支援任意數量的標籤名稱。用戶端函式庫必須驗證標籤名稱是否符合文件中的要求

提供存取指標標籤維度的常見方式是透過 labels() 方法,該方法接受標籤值的清單或從標籤名稱到標籤值的映射,並返回一個 "Child"。然後可以在 Child 上呼叫常用的 .inc()/.dec()/.observe() 等方法。

labels() 返回的 Child 應該可由使用者快取,以避免必須再次查詢 - 這在對延遲敏感的程式碼中很重要。

帶有標籤的指標應支援與 labels() 相同簽名的 remove() 方法,該方法將從指標中刪除不再匯出的 Child,以及一個 clear() 方法,該方法將從指標中刪除所有 Child。這些方法會使 Child 的快取失效。

應該有一種方法可以使用預設值初始化給定的 Child,通常只需呼叫 labels() 即可。沒有標籤的指標必須始終初始化,以避免遺失指標的問題

指標名稱

指標名稱必須符合規範。與標籤名稱一樣,這對於 Gauge/Counter/Summary/Histogram 的使用以及函式庫提供的任何其他收集器都必須滿足。

許多用戶端函式庫都提供以三個部分設定名稱的方式:namespace_subsystem_name,其中只有 name 是強制性的。

必須不鼓勵動態/產生的指標名稱或指標名稱的子部分,除非自訂收集器正在代理來自其他儀表/監控系統的資料。產生的/動態的指標名稱表示您應該改用標籤。

指標描述和說明

Gauge/Counter/Summary/Histogram 必須要求提供指標描述/說明。

用戶端函式庫提供的任何自訂收集器都必須在其指標上提供描述/說明。

建議將其設為強制性參數,但不檢查其是否達到特定長度,因為如果有人真的不想撰寫文件,我們無法說服他們。函式庫提供的收集器 (以及我們在生態系統中可以實現的任何地方) 應該具有良好的指標描述,以起帶頭作用。

展示

用戶端必須實作展示格式文件中概述的基於文字的展示格式。

如果可以在不產生重大資源成本的情況下實作,則鼓勵以可重現的順序展示指標 (特別是對於人類可讀的格式)。

標準和運行時收集器

用戶端函式庫應盡可能提供以下文件中記錄的標準匯出。

這些應實作為自訂收集器,並預設在預設的 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 epoch 以來處理程序的啟動時間,以秒為單位。
process_threads 處理程序中的作業系統執行緒數量。 執行緒

運行時指標

此外,鼓勵用戶端函式庫也針對其語言的運行時提供任何有意義的指標 (例如,垃圾收集統計資料),並使用適當的前綴,例如 go_hotspot_ 等。

單元測試

用戶端函式庫應具有涵蓋核心儀表函式庫和展示的單元測試。

鼓勵用戶端函式庫提供使使用者可以輕鬆單元測試其儀表程式碼使用情況的方法。例如,Python 中的 CollectorRegistry.get_sample_value

封裝和依賴關係

理想情況下,可以在任何應用程式中包含用戶端函式庫,以新增一些儀表,而不會破壞應用程式。

因此,在向用戶端函式庫新增依賴關係時,建議謹慎。例如,如果您新增一個使用 Prometheus 用戶端的函式庫,該用戶端需要函式庫的 x.y 版本,但應用程式在其他地方使用 x.z 版本,這會對應用程式產生不利影響嗎?

建議在可能出現這種情況時,將核心儀表與給定格式的指標橋接/展示分開。例如,Java simpleclient simpleclient 模組沒有依賴關係,而 simpleclient_servlet 則具有 HTTP 位元。

效能考量

由於用戶端函式庫必須是執行緒安全的,因此需要某種形式的並行控制,並且必須考慮多核心機器和應用程式上的效能。

根據我們的經驗,效能最差的是互斥鎖。

處理器原子指令的效能往往處於中間位置,並且通常可以接受。

避免不同 CPU 修改同一塊 RAM 的方法效果最佳,例如 Java simpleclient 中的 DoubleAdder。但這會產生記憶體成本。

如上所述,labels() 的結果應該可以快取。傾向於支援帶有標籤的指標的並行映射往往相對較慢。特殊情況下,沒有標籤的指標可以避免類似 labels() 的查詢,這會很有幫助。

指標在遞增/遞減/設定等時應該避免阻塞,因為在抓取正在進行時,整個應用程式被暫停是不利的。

鼓勵對主要儀表操作 (包括標籤) 進行基準測試。

在執行展示時,應記住資源消耗 (尤其是 RAM)。考慮透過串流結果來減少記憶體佔用量,並可能限制同時抓取的數量。

本文件是開源的。請提交問題或提取請求,以協助改進它。