PromQL(Prometheus Query Language)是 Prometheus 自己開發的表達式語言,語言表現力很豐富,內置函數也很多。使用它可以對時序數據進行篩選和聚合。
1- PromQL 語法
1.1- 數據類型
PromQL 表達式計算出來的值有以下幾種類型:
- 瞬時向量 (Instant vector): 一組時序,每個時序只有一個采樣值
- 區間向量 (Range vector): 一組時序,每個時序包含一段時間內的多個采樣值
- 標量數據 (Scalar): 一個浮點數
- 字符串 (String): 一個字符串,暫時未用
1.2- 時序選擇器
1.2.1- 瞬時向量選擇器
瞬時向量選擇器用來選擇一組時序在某個采樣點的采樣值。
最簡單的情況就是指定一個度量指標,選擇出所有屬於該度量指標的時序的當前采樣值。比如下面的表達式:
http_requests_total
可以通過在后面添加用大括號包圍起來的一組標簽鍵值對來對時序進行過濾。比如下面的表達式篩選出了 job 為 prometheus,並且 group 為 canary 的時序:
http_requests_total{job="prometheus", group="canary"}
匹配標簽值時可以是等於,也可以使用正則表達式。總共有下面幾種匹配操作符:
- =:完全相等
- !=: 不相等
- =~: 正則表達式匹配
- !~: 正則表達式不匹配
下面的表達式篩選出了 environment 為 staging 或 testing 或 development,並且 method 不是 GET 的時序:
http_requests_total{environment=~"staging|testing|development",method!="GET"}
度量指標名可以使用內部標簽 name 來匹配,表達式 http_requests_total 也可以寫成 {name="http_requests_total"}。表達式 {name=~"job:.*"} 匹配所有度量指標名稱以 job: 打頭的時序。
1.2.2- 區間向量選擇器
區間向量選擇器類似於瞬時向量選擇器,不同的是它選擇的是過去一段時間的采樣值。可以通過在瞬時向量選擇器后面添加包含在 [] 里的時長來得到區間向量選擇器。比如下面的表達式選出了所有度量指標為 http_requests_total 且 job 為 prometheus 的時序在過去 5 分鍾的采樣值。
http_requests_total{job="prometheus"}[5m]
說明:時長的單位可以是下面幾種之一:
- s:seconds
- m:minutes
- h:hours
- d:days
- w:weeks
- y:years
1.2.3- 偏移修飾器
前面介紹的選擇器默認都是以當前時間為基准時間,偏移修飾器用來調整基准時間,使其往前偏移一段時間。偏移修飾器緊跟在選擇器后面,使用 offset 來指定要偏移的量。比如下面的表達式選擇度量名稱為 http_requests_total 的所有時序在 5 分鍾前的采樣值。
http_requests_total offset 5m
下面的表達式選擇 http_requests_total 度量指標在 1 周前的這個時間點過去 5 分鍾的采樣值。
http_requests_total[5m] offset 1w
2- PromQL 操作符
2.1- 二元操作符
PromQL 的二元操作符支持基本的邏輯和算術運算,包含算術類、比較類和邏輯類三大類。
2.1.1- 算術類二元操作符
算術類二元操作符有以下幾種:
- +:加
- -:減
- *:乘
- /:除
- %:求余
- ^:乘方
算術類二元操作符可以使用在標量與標量、向量與標量,以及向量與向量之間。
二元操作符上下文里的向量特指瞬時向量,不包括區間向量。
- 標量與標量之間,結果很明顯,跟通常的算術運算一致。
- 向量與標量之間,相當於把標量跟向量里的每一個標量進行運算,這些計算結果組成了一個新的向量。
- 向量與向量之間,會稍微麻煩一些。運算的時候首先會為左邊向量里的每一個元素在右邊向量里去尋找一個匹配元素(匹配規則后面會講),然后對這兩個匹配元素執行計算,這樣每對匹配元素的計算結果組成了一個新的向量。如果沒有找到匹配元素,則該元素丟棄。
2.1.2- 比較類二元操作符
比較類二元操作符有以下幾種:
- == (equal)
- != (not-equal)
- > (greater-than)
- < (less-than)
- >= (greater-or-equal)
- <= (less-or-equal)
比較類二元操作符同樣可以使用在標量與標量、向量與標量,以及向量與向量之間。默認執行的是過濾,也就是保留值。
也可以通過在運算符后面跟 bool 修飾符來使得返回值 0 和 1,而不是過濾。
- 標量與標量之間,必須跟 bool 修飾符,因此結果只可能是 0(false) 或 1(true)。
- 向量與標量之間,相當於把向量里的每一個標量跟標量進行比較,結果為真則保留,否則丟棄。如果后面跟了 bool 修飾符,則結果分別為 1 和 0。
- 向量與向量之間,運算過程類似於算術類操作符,只不過如果比較結果為真則保留左邊的值(包括度量指標和標簽這些屬性),否則丟棄,沒找到匹配也是丟棄。如果后面跟了 bool 修飾符,則保留和丟棄時結果相應為 1 和 0。
2.1.3- 邏輯類二元操作符
邏輯操作符僅用於向量與向量之間。
-
and:交集
-
or:合集
-
unless:補集
具體運算規則如下: -
vector1 and vector2 的結果由在 vector2 里有匹配(標簽鍵值對組合相同)元素的 2. vector1 里的元素組成。
-
vector1 or vector2 的結果由所有 vector1 里的元素加上在 vector1 里沒有匹配(標簽鍵值對組合相同)元素的 vector2 里的元素組成。
-
vector1 unless vector2 的結果由在 vector2 里沒有匹配(標簽鍵值對組合相同)元素的 vector1 里的元素組成。
2.1.4- 二元操作符優先級
PromQL 的各類二元操作符運算優先級如下:
- ^
- *, /, %
- +, -
- ==, !=, <=, <, >=, >
- and, unless
- or
2.2- 向量匹配
前面算術類和比較類操作符都需要在向量之間進行匹配。共有兩種匹配類型,one-to-one 和 many-to-one / one-to-many。
2.2.1- One-to-one 向量匹配
這種匹配模式下,兩邊向量里的元素如果其標簽鍵值對組合相同則為匹配,並且只會有一個匹配元素。可以使用 ignoring 關鍵詞來忽略不參與匹配的標簽,或者使用 on 關鍵詞來指定要參與匹配的標簽。語法如下:
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>
比如對於下面的輸入:
method_code:http_errors:rate5m{method="get", code="500"} 24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120
執行下面的查詢:
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m
得到的結果為:
{method="get"} 0.04 // 24 / 600
{method="post"} 0.05 // 6 / 120
也就是每一種 method 里 code 為 500 的請求數占總數的百分比。由於 method 為 put 和 del 的沒有匹配元素所以沒有出現在結果里。
2.2.2- Many-to-one / one-to-many 向量匹配
這種匹配模式下,某一邊會有多個元素跟另一邊的元素匹配。這時就需要使用 group_left 或 group_right 組修飾符來指明哪邊匹配元素較多,左邊多則用 group_left,右邊多則用 group_right。其語法如下:
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>
組修飾符只適用於算術類和比較類操作符。
對於前面的輸入,執行下面的查詢:
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m
將得到下面的結果:
{method="get", code="500"} 0.04 // 24 / 600
{method="get", code="404"} 0.05 // 30 / 600
{method="post", code="500"} 0.05 // 6 / 120
{method="post", code="404"} 0.175 // 21 / 120
也就是每種 method 的每種 code 錯誤次數占每種 method 請求數的比例。這里匹配的時候 ignoring 了 code,才使得兩邊可以形成 Many-to-one 形式的匹配。由於左邊多,所以需要使用 group_left 來指明。
Many-to-one / one-to-many 過於高級和復雜,要盡量避免使用。很多時候通過 ignoring 就可以解決問題。
2.3- 聚合操作符
PromQL 的聚合操作符用來將向量里的元素聚合得更少。總共有下面這些聚合操作符:
- sum:求和
- min:最小值
- max:最大值
- avg:平均值
- stddev:標准差
- stdvar:方差
- count:元素個數
- count_values:等於某值的元素個數
- bottomk:最小的 k 個元素
- topk:最大的 k 個元素
- quantile:分位數
聚合操作符語法如下:
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
其中 without 用來指定不需要保留的標簽(也就是這些標簽的多個值會被聚合),而 by 正好相反,用來指定需要保留的標簽(也就是按這些標簽來聚合)。
下面來看幾個示例:
sum(http_requests_total) without (instance)
http_requests_total 度量指標帶有 application、instance 和 group 三個標簽。上面的表達式會得到每個 application 的每個 group 在所有 instance 上的請求總數。效果等同於下面的表達式:
sum(http_requests_total) by (application, group)
下面的表達式可以得到所有 application 的所有 group 的所有 instance 的請求總數。
sum(http_requests_total)
3- 函數
Prometheus 內置了一些函數來輔助計算,下面介紹一些典型的。
- abs():絕對值
- sqrt():平方根
- exp():指數計算
- ln():自然對數
- ceil():向上取整
- floor():向下取整
- round():四舍五入取整
- delta():計算區間向量里每一個時序第一個和最后一個的差值
- sort():排序
