## 編寫導出器
---
當直接檢測你的代碼質量時,通過Prometheus客戶庫檢測你的代碼質量是一個常用手段。當從另一個監控或者檢測系統獲取度量指標時,事情往往不是一成不變的。
當你準備寫度量指標的導出器或者自定義收集器時,這篇文章值得你借鑒和學習。涉及的理論對于做一些檢測的事情是非常有幫助的。
如果你正在寫一個導出器,且有任何不清楚的,可以隨時聯系我們, 詳見IRC和[郵件](https://prometheus.io/community)
### 可維護性和Purity
當你寫一個exporter時,你需要做出的最大決定是你會投入多大的精力,來獲得比較完美的度量指標測量
一方面,如果系統的度量指標很少有變化,則寫導出器是不需要很長的時間和精力(例如:HAProxy exporter); 另一方面,當系統版本每次更新時,若有大量的度量指標發生變化,則你需要投入大量的時間和精力寫一個導出器,[mysql導出器](https://github.com/prometheus/mysqld_exporter)就是一個例子
這個[node exporter](https://github.com/prometheus/node_exporter)是混合態的,不同的模塊的處理方式也不盡相同。對于mdadm,我們必須手動解析文件,并獲取我們想要的度量指標,所以我們可以在需要的時候獲取度量指標。對于meminfo,不同的內核版本命名可能會有變化,所以我們需要創建有效的度量指標,以適應不同內核版本的內存使用情況
### 配置
當導出器和應用程序一起工作時,你的導出器目標是盡量不要讓用戶使用自定義配置,而是盡可能地使用默認配置就ok了。你也可以給你的導出器提供過濾度量指標的支持,因為有些度量指標是非常多且瑣碎,也有些需要花費很大的代價才能獲取度量指標,這些都可以進行過濾(例如:HAProxy exporter允許過濾每個服務器的統計信息)。類似地,默認配置可以直接禁用獲取代價比較的度量指標。
當導出器和監控系統協同工作時,框架和協議相關工作可能會很復雜。
使用監控系統時,框架和協議不是那么簡單的。
最好的情況是其他監控或者檢測系統與Prometheus有非常相似的數據模型,則系統可以自動導出度量指標到Prometheus監控系統中,如:Cloudwatch, SNMP和Collectd。但是大多數情況下,我們都需要用戶自己指定其他監控或者檢測系統需要導出的度量指標,并把這些度量指標樣本數據導入到Prometheus系統中。
但是更多常見的情況是,來自其他監控或者檢測系統的度量指標往往都是非標準的,它們往往依賴于用戶使用它的方法和底層應用程序是什么樣的。這種情況下,用戶必須告訴我們怎么轉換這些度量指標。這個JMX導出器是最糟糕的,graphite和statsd導出器也要求在配置中抽取標簽集。
提供一些導出器的使用方式是值得建議的,例如:生成這個導出器的輸出和實例配置的選擇。當為這些導出器寫一些配置文檔時,這篇文檔應該時刻牢記在心。
YAML是Prometheus的標準配置格式
### 度量指標
#### 命名
遵循[度量指標最佳實踐](https://prometheus.io/docs/practices/naming)
通常度量指標名稱應該允許對Prometheus監控系統比較熟悉的人, 對一個度量名稱的字面含義比較容易理解它的真正含義。一個命名為`http_requests_total`的度量指標名稱不是很有用,因為它是針對所有進入系統的請求計數。如果改為`requests_total`,這度量指標名稱更差勁,它不能表示是什么樣的請求,是tcp,udp還是http?
為了把度量指標推送到檢測系統中,一個給定的度量指標應該在一個指定的文件中存在。根據導出器和收集器,一個度量指標名稱應該應用在一個子系統中。
度量指標名稱永遠不要程序化的生成,除非在編寫自定義收集器或者導出器時。
應用程序的度量指標名稱應該以exporter名稱為前綴,如`haproxy_up`。
度量指標名稱必須使用基本單位(例如:秒,字節),并將其轉換為圖形軟件更易讀的內容。無論你最終使用什么樣的計量單位,指標名稱中的單位都必須與正在使用的單位相匹配。類似地展示比率,而不是百分比(盡管兩個組件的計數器比率更好)。
度量指標名稱不應該包括他們導出的labels(如:`by_type`)如果標簽聚在一起,就沒有意義。
這里有一個特例,當你通過多個度量指標用不同標簽導出相同數據時,通常是區分他們最好的方式。一個例子是,當對單個標簽用所有標簽導出數據時,這個計算是非常復雜的,這種情況下使用這個特例是非常合適的。
Prometheus度量指標和標簽名稱寫在`snake_case`中。把`camelCase`轉換成`snake_case`度量指標是可取的,盡管自動化地做這些事情并不總是產生好的結果,例如:`myTCPExample`或`isNaN`, 所以有時最好將他們保留。
暴露的度量指標不應包含冒號,這些用于聚合時可供用戶使用。度量指標名稱只有符合正則表達式[a-zA-Z0-9:_]是有效的,任何其他字符串都需要被改成"_"下劃線
`_sum`, `_count`, `_bucket`, 和`_total`用于Summaries,Histogram和Counters的后綴。如果你正好要使用這些統計則可以使用這些后綴,否則避免使用這些后綴。
如果你正在使用COUNTER類型,則應該使用`_total`作為度量指標的后綴。
這個`process_`和`scrape_`的前綴被保留。如果它們遵循匹配的語義,可以將這些前綴添加到度量指標上。例如:Prometheus的持續獲取時間為`scrape_duration_seconds`,這是很好的做法。`jmx_scrape_duration_seconds`表示JMX收集器花費多久時間獲取度量指標數據。對于對于進程信息統計,你可以通過PID進程號獲取,Go和Python都會為你提供處理此選項的收集器(請參閱HAProxy exporter示例)
當你統計請求的成功數量和失敗數量時,最佳方法是暴露兩個度量指標,一個是總的請求數,另一個度量指標是失敗的請求度量指標。這使得計算失敗比率變得很容易。不要使用帶有failed/success的標簽。類似于緩存中的`hit/miss`,有一個總的度量指標和另一個hits的度量指標是更好的。
考慮使用監控的人可能會對指標名稱執行代碼或者網絡搜索。如果這些名稱是非常完善的,并且不太可能在用于這些名稱的人的領域之外(例如:SNMP和網絡工程師)使用,那么按原樣離開它們可能是一個好主意。該邏輯不適用于(例如:作為非數據庫管理員的MySQL),可以預期會圍繞這些指標。具有原始名稱的幫助文檔可以提供與使用以前的名稱大致相同的好處。
#### labels
閱讀一個有關labels標簽的建議,詳見[advice](https://prometheus.io/docs/practices/instrumentation/#things-to-watch-out-for)
避免把`type`做為標簽名稱,它太通用化,而且無意義。你也應該試著盡可能地避免與目標標簽沖突,例如:`region`, `zone`, `cluster`, `availability`, `az`, `datacenter`, `dc`, `owner`, `customer`, `stage`, `environment`和`env`, 盡管這是應用程序調用的東西,但是最好不要通過重命名造成混亂。
不要僅僅因為它們共享了同一個前綴,就把一些樣本數據全部集成到一個度量指標下。除非你確定某些指標是有意義的,否則請使用多個度量指標。
標簽`le`對于Histograms有特殊的含義。同樣對于Summaries來說,`quantile`也是如此。避免使用這些標簽。
對于Read/write和send/receive, 不要把它們當做一個標簽使用,而是應該作為獨立的度量指標使用。因為你關心的是某個時刻它們中的一個,度量指標命名并使用它們是容易的。
實踐經驗是,當求和或者求平均時,一個度量指標應該是有意義的。 還有另一種情況導出器的出現,數據基本是用表格的方式呈現,否則將要求用戶對度量標準名稱進行正則表達式可用。 考慮您主板上的電壓傳感器,而對它們進行數學計算是無意義的,將它們置于一個度量標準中而不是每個傳感器有一個度量是有意義的。 度量中的所有值(幾乎)總是具有相同的單位(如果風扇速度與電壓混合,則無法自動分離)。
不要做這些:
```
my_metric{label=a} 1
my_metric{label=b} 6
**my_metric{label=total} 7**
```
或者
```
my_metric{label=a} 1
my_metric{label=b} 6
**my_metric{} 7**
```
前者打破了用戶Prometheus聚合操作`sum()`函數作用, 后者也一樣。某些客戶端庫(如:Go)會積極地嘗試阻止你在自定義收集器中執行后者。依賴Prometheus的`sum`聚合操作,可以輕易地達到這一點。
如果你的監控使用了一個total,請刪除它。如果你必須因為某些原因(例如:這個total并不是統計計數)保留它,請使用其他的度量指標名稱。
#### Target labels, not static scraped labels(目標標簽,非靜態獲取標簽)
如果你發現自己想要對所有度量指標使用相同的標簽,請立即停止。
通常有兩種情況出現。
第一個是有些標簽關聯到度量指標上市非常有用的,例如:軟件的版本號。請使用文檔[地址](https://www.robustperception.io/how-to-have-labels-for-machine-roles/)中所述的方法。
另一種情況是真正的實例標簽。這些標簽是區域,集群名稱等,它們來自目標實例的硬件信息而不是應用程序本身。這類標簽不應該在分類中使用, 這個Prometheus服務加載服務配置,對于相同的應用可以給出不同的度量指標名稱,但是可以有相同的標簽名稱
因此,這些標簽通過你使用的任何服務發現,都屬于Prometheus的獲取配置。還可以在這里應用機器角色的概念,因為至少有一些人獲取它,可能是非常有用的信息。
#### Types類型
你應該嘗試將你的度量指標類型與Prometheus類型相匹配。這通常意味著主要類型是Counter和Gauge。summaries的`_count`和`_sum`也是比較常見的,有時你會看到quantiles。Histogram是罕見的,如果你碰到了,記住那展示格式用來暴露累計值。
通常,衡量度量指標的類型是不明顯的(特別是如果你自動處理一組度量指標),那么在這種情況下使用UNTYPED無類型度量指標。一般來說,UNTYPED類型是一個比較安全的默認值。
Counter遞增,如果你在其他監控或者檢測系統中發現了一個counter類型,但是它能夠減少( 例如:Dropwizard指標),這不是一個Counter類型而是一個Gauge類型, UNTYPED無類型可能是那里使用最好的類型,因為如果它被用作counter,則`GAUGE`將會被誤導。
### 幫助文檔
當你轉換度量指標時,用戶能夠跟蹤原始內容記憶導致該轉換的規則是有用的。以收件人/導出者的身份,應用程序的任何規則ID和原始度量的名稱/詳細信息記錄到幫助文檔,會極大地幫助用戶。
Prometheus不喜歡一個度量指標名稱有不同幫助文檔。如果你正在使用來自多個其他系統的一個度量指標,請選擇其中一個來放置幫助文檔。
例如:SNMP導出器使用OID,JMX導出器放入一個樣例mBean名稱中。HAProxy exporter有手寫字符串。node exporter有大量的例子可以使用。
#### 放棄無用的統計數據
某些檢測系統提供了一些度量指標,包含1m/5m/15m速率、程序運行到現在的平均速率(例如:在dropwizard指標中統計為平均值)、最小值、最大值和標準偏差。
這些檢測系統的統計度量指標數據都應該被丟棄,因為Prometheus提供了同樣的一套統計數據,數據類型為Summary,而且統計的樣本數據更加準確,所以不需要再在寫導出器時統計這類數據。
Quantiles涉及的相關issue,你可以選擇丟棄或者將其放在Summary中。
#### .字符串(Dotted strings)
許多監控系統沒有標簽,而是做成像`my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3`這樣。
graphite和statsd的導出器采用了一種使用配置文件來達到帶有標簽的目的。其他exporters也應該這樣做。它目前僅在Go中實現,并將受益于將其分解為單獨的庫。
### Collectors
當在導出器中實現收集器的功能模塊時,你永遠不要"使用通用且直接的檢測方案,然后在每次抓取后更新度量指標樣本數據"。
我們寧愿在抓取度量指標數據時,創建新的度量指標。在Go中,在Update()方法中調用[MustNewConstMetric](https://godoc.org/github.com/prometheus/client_golang/prometheus#MustNewConstMetric)完成此操作。對于Python,請參與[https://github.com/prometheus/client_python#custom-collectors](https://github.com/prometheus/client_python#custom-collectors), 對于Java,在Collector方法中生成列表\<MetricFamilySamples\>, 請參與StardardExports.java作為示例。
我們在抓取數據時采用創建度量指標的方案,主要有兩個原因: 1. 如果在同一時刻發生兩次抓取,這樣在度量指標數據時會發生資源競爭問題;2. 如果一個度量指標的標簽值消失后,更新度量指標數據,那么空標簽數據還是會被導出。
通過直接的檢測來判斷你寫的導出器是否是ok的。例如:總字節數在所有的數據抓取傳輸或者調用時由導出器執行,例如:blackbox exporters和snmp exporter,他們關聯了多個目標實例,所以這些只能在一個vanilla`/metrics`調用上,而不是在特定目標上。
#### 關于獲取度量指標本身
有時候你想要導出關于一次抓取本身相關的度量指標數據時,例如:這次抓取花費了多長時間,處理了多少記錄。
這些數據的數據類型應該為gauges,數據指標名稱以導出器名稱為前綴,例如:`jmx_scrape_duration_seconds`。通常`_exporter`被排除(如果exporter僅僅作為collector使用,絕對排除它)。
### Machine & Process metrics(硬件,進程度量指標)
許多系統(如:elasticsearch)提供了一些硬件度量指標,如:cpu,內存和文件系統信息。Prometheus生態中的node導出器已經提供了這些度量指標的檢測方案,所以不需要在寫導出器時實現這些硬件度量指標的檢測或者獲取方案。
在Java世界中,許多檢測框架提供了進程級別和JVM級別的統計測量方案,例如:CPU占用率,GC次數等。Java客戶端和JMX導出器已經通過形如[DefaultExports.java](https://github.com/prometheus/client_java/blob/master/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/DefaultExports.java)的機制,可以獲取這些度量指標,所以也不再需要在寫導出器時再實現一套這樣的方案。
與其他語言類似。
### Deployment部署
每個導出器在實例所在的服務器上部署并監控,這意味著如果你想要haproxy,則需要在實例所在的服務器上運行`haproxy_exporter`導出器進程。對于每個有mesos從節點的服務器上,你需要在這個服務器上運行一個mesos導出器進程(如果在這臺服務器上還有mesos主節點,則還需要再啟動一個mesos導出器進程服務)
這背后的理論是,對于在所有實例上的導出器進程,我們需要獲取其上的度量指標樣本數據的話,這意味著在Prometheus監控系統上需要提供服務發現機制,而不是由導出器提供。Prometheus監控系統有目標實例的相關信息的優點,主要在于它允許用戶用blackbox導出器探測你的服務。
有兩個例外:
第一個是被監控的實例所在的服務器位置是完全不關心的。例如:SNMP、blackbox和IPMI。IPMI和SNMP作為設備是有效地黑盒,它是不可能運行代碼的(如果你能夠在其上運行node導出器代替,那會更好),blackbox作為你監控像DNS名稱這樣的,完全不需要運行代碼)。但是Prometheus仍然應該需要做服務發現機制,通過傳輸被抓取的目標實例。
請注意,目前只有使用python和Java客戶端庫編寫這種類型的exporter(在Go中編寫的blackbox exporter手工執行文本格式,請不要這樣做)。
第二個是,你從一個系統拉取一個隨機服務實例的一些統計時,這個服務器的位置是你完全不關心的。考慮一種情況,你想要通過Mysql從節點運行一些商業查詢,然后導出數據。這時候使用一個導出器和Mysql從節點通信是最佳方案。
當你監控一個帶有master選取的系統時,也不能用Prometheus服務發現機制。這種情況下,我們應該監控每個實例服務,并通過Prometheus和目標系統的主節點通信。這里并不總是準確的,改變目標實例也可能會在Prometheus監控系統下造成數據錯亂。
#### 調度
當Prometheus服務抓取度量指標數據時,度量指標才能夠從實例中被抓取。導出器不應該設定自己的時間去抓取目標數據,而是由Prometheus監控系統統一管理和下放。即所有的抓取是同步的。如果你需要時間戳,你可以通過pushgateway代替
如果一個度量指標的數據獲取需要花費大量時間和代價,那么暫時先緩存這些數據也是可以接受的。它應該在`HELP`中說明
在Prometheus中默認的抓取度量指標數據的超時設置為10s。如果你的導出器超過了這個時間長度,你需要在你的導出器用戶文檔中明確指出。
#### 推送
一些應用程序和監控系統僅僅推送幾個度量指標,如:statsd, grphite和collected.
有兩點需要考慮:
第一點, 你的度量指標什么時間過期?Collected和Graphite導出器定時導出度量指標數據,但是當他們停止工作后,我們也想要停止提供這些度量指標。Collected包含我們經常使用的過期時間,而Graphite則不會,在導出器中它是一個標志。
Statsd有很大不同,它處理的是事件,而不是度量指標。最好的數據模型是在服務所在的目標實例上運行一個Statsd導出器,當應用程序重啟后,這個導出器也重啟,并清空度量指標數據。
第二點,這些應用程序和監控系統允許用戶發送delta或者原生計數器。你應該盡可能地依賴原生計數器,這是Prometheus的通用模型。
對于服務級別的度量指標(例如:服務級別的批量任務),你應該用導出器把這些度量指標數據推送到Push gateway中,當推送完成后退出。對于實例級別的度量指標,目前還沒有明確的模式。選項不是濫用node導出器的textfile收集器,而是依賴當前內存狀態。
#### 抓取失敗
當前有兩種模式,當導出器同實例服務通信沒有響應或者其他問題時,稱之為抓取失敗。
第一種,返回響應嗎:5xx錯誤。
第二種,有一個度量指標名稱為`myexporter_up`(例如:`haproxy_up`), 判斷一個導出器是否工作時通過這個度量指標變量的值0/1狀態。0:表示不工作;1:表示工作。
第二種模式是比較好的,當抓取失敗時,我們能夠通過一些有用的度量指標數據判斷導出器服務的當前工作狀態,例如:haproxy導出器提供了進程統計信息,第一種對于用戶來說,更加容易處理,但是`up`是非常通用的處理方式(因為你不能辨別出事導出器服務有問題,還是應用程序有問題)
#### 登錄頁面
如果用戶訪問`http://yourexporter/`時返回一個帶有導出器名稱的簡單頁面, 并且通過`/metrics`鏈接到Prometheus系統中國,這對用戶是非常友好的。
#### 端口
在一臺服務器上用戶可以有多個導出器和Prometheus組件,因此有一個唯一的端口變得非常容易
[https://github.com/prometheus/prometheus/wiki/Default-port-allocations ](https://github.com/prometheus/prometheus/wiki/Default-port-allocations), 這個是我們官方的端口規劃。
### 宣布
一旦你對外準備宣布你寫的導出器時,請發送一封郵件,并且發送一個PR到這個可用導出器的[列表](https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exporters.md)