0x00 二元運算符
Prometheus 的查詢語言支持基本的邏輯運算和算術運算。對於兩個瞬時向量, 匹配行為可以被改變。
算術二元運算符
在 Prometheus 系統中支持下面的二元算術運算符:
-
+
加法 -
-
減法 -
*
乘法 -
/
除法 -
%
模 -
^
冪等
二元運算操作符支持 scalar/scalar(標量/標量)
、vector/scalar(向量/標量)
、和 vector/vector(向量/向量)
之間的操作。
在兩個標量之間進行數學運算,得到的結果也是標量。
在向量和標量之間,這個運算符會作用於這個向量的每個樣本值上。例如:如果一個時間序列瞬時向量除以 2,操作結果也是一個新的瞬時向量,且度量指標名稱不變, 它是原度量指標瞬時向量的每個樣本值除以 2。
如果是瞬時向量與瞬時向量之間進行數學運算時,過程會相對復雜一點,運算符會依次找到與左邊向量元素匹配(標簽完全一致)的右邊向量元素進行運算,如果沒找到匹配元素,則直接丟棄。同時新的時間序列將不會包含指標名稱。
例如,如果我們想根據 node_disk_bytes_written
和 node_disk_bytes_read
獲取主機磁盤IO的總量,可以使用如下表達式:
node_disk_bytes_written + node_disk_bytes_read
該表達式返回結果的示例如下所示:
{device="sda",instance="localhost:9100",job="node_exporter"}=>1634967552@1518146427.807 + 864551424@1518146427.807 {device="sdb",instance="localhost:9100",job="node_exporter"}=>0@1518146427.807 + 1744384@1518146427.807
布爾運算符
目前,Prometheus 支持以下布爾運算符:
-
==
(相等) -
!=
(不相等) -
>
(大於) -
<
(小於) -
>=
(大於等於) -
<=
(小於等於)
布爾運算符被應用於 scalar/scalar(標量/標量)
、vector/scalar(向量/標量)
,和vector/vector(向量/向量)
。默認情況下布爾運算符只會根據時間序列中樣本的值,對時間序列進行過濾。我們可以通過在運算符后面使用 bool
修飾符來改變布爾運算的默認行為。使用 bool 修改符后,布爾運算不會對時間序列進行過濾,而是直接依次瞬時向量中的各個樣本數據與標量的比較結果 0
或者 1
。
在兩個標量之間進行布爾運算,必須提供 bool 修飾符,得到的結果也是標量,即 0
(false
)或 1
(true
)。例如:
2 > bool 1 # 結果為 1
瞬時向量和標量之間的布爾運算,這個運算符會應用到某個當前時刻的每個時序數據上,如果一個時序數據的樣本值與這個標量比較的結果是 false
,則這個時序數據被丟棄掉,如果是 true
, 則這個時序數據被保留在結果中。如果提供了 bool 修飾符,那么比較結果是 0
的時序數據被丟棄掉,而比較結果是 1
的時序數據被保留。例如:
http_requests_total > 100 # 結果為 true 或 false http_requests_total > bool 100 # 結果為 1 或 0
瞬時向量與瞬時向量直接進行布爾運算時,同樣遵循默認的匹配模式:依次找到與左邊向量元素匹配(標簽完全一致)的右邊向量元素進行相應的操作,如果沒找到匹配元素,或者計算結果為 false,則直接丟棄。如果匹配上了,則將左邊向量的度量指標和標簽的樣本數據寫入瞬時向量。如果提供了 bool 修飾符,那么比較結果是 0
的時序數據被丟棄掉,而比較結果是 1
的時序數據(只保留左邊向量)被保留。
集合運算符
使用瞬時向量表達式能夠獲取到一個包含多個時間序列的集合,我們稱為瞬時向量。 通過集合運算,可以在兩個瞬時向量與瞬時向量之間進行相應的集合操作。目前,Prometheus 支持以下集合運算符:
-
and
(並且) -
or
(或者) -
unless
(排除)
vector1 and vector2 會產生一個由 vector1
的元素組成的新的向量。該向量包含 vector1 中完全匹配 vector2
中的元素組成。
vector1 or vector2 會產生一個新的向量,該向量包含 vector1
中所有的樣本數據,以及 vector2
中沒有與 vector1
匹配到的樣本數據。
vector1 unless vector2 會產生一個新的向量,新向量中的元素由 vector1
中沒有與 vector2
匹配的元素組成。
0x01 匹配模式
向量與向量之間進行運算操作時會基於默認的匹配規則:依次找到與左邊向量元素匹配(標簽完全一致)的右邊向量元素進行運算,如果沒找到匹配元素,則直接丟棄。
接下來將介紹在 PromQL 中有兩種典型的匹配模式:一對一(one-to-one),多對一(many-to-one)或一對多(one-to-many)。
一對一匹配
一對一匹配模式會從操作符兩邊表達式獲取的瞬時向量依次比較並找到唯一匹配(標簽完全一致)的樣本值。默認情況下,使用表達式:
vector1 <operator> vector2
在操作符兩邊表達式標簽不一致的情況下,可以使用 on(label list)
或者 ignoring(label list)
來修改便簽的匹配行為。使用 ignoreing
可以在匹配時忽略某些便簽。而 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
使用 PromQL 表達式:
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m
該表達式會返回在過去 5 分鍾內,HTTP 請求狀態碼為 500 的在所有請求中的比例。如果沒有使用 ignoring(code)
,操作符兩邊表達式返回的瞬時向量中將找不到任何一個標簽完全相同的匹配項。
因此結果如下:
{method="get"} 0.04 // 24 / 600 {method="post"} 0.05 // 6 / 120
同時由於 method 為 put
和 del
的樣本找不到匹配項,因此不會出現在結果當中。
多對一和一對多
多對一和一對多兩種匹配模式指的是“一”側的每一個向量元素可以與"多"側的多個元素匹配的情況。在這種情況下,必須使用 group 修飾符: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>
多對一和一對多兩種模式一定是出現在操作符兩側表達式返回的向量標簽不一致的情況。因此需要使用 ignoring 和 on 修飾符來排除或者限定匹配的標簽列表。
例如,使用表達式:
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m
該表達式中,左向量 method_code:http_errors:rate5m
包含兩個標簽 method
和 code
。而右向量 method:http_requests:rate5m
中只包含一個標簽 method
,因此匹配時需要使用 ignoring
限定匹配的標簽為 code
。 在限定匹配標簽后,右向量中的元素可能匹配到多個左向量中的元素 因此該表達式的匹配模式為多對一,需要使用 group 修飾符 group_left
指定左向量具有更好的基數。
最終的運算結果如下:
{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
提醒:group
修飾符只能在比較和數學運算符中使用。在邏輯運算 and
,unless
和 or
操作中默認與右向量中的所有元素進行匹配。
0x02 聚合操作
Prometheus 還提供了下列內置的聚合操作符,這些操作符作用域瞬時向量。可以將瞬時表達式返回的樣本數據進行聚合,形成一個具有較少樣本值的新的時間序列。
-
sum
(求和) -
min
(最小值) -
max
(最大值) -
avg
(平均值) -
stddev
(標准差) -
stdvar
(標准差異) -
count
(計數) -
count_values
(對 value 進行計數) -
bottomk
(樣本值最小的 k 個元素) -
topk
(樣本值最大的k個元素) -
quantile
(分布統計)
這些操作符被用於聚合所有標簽維度,或者通過 without
或者 by
子語句來保留不同的維度。
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
其中只有 count_values
, quantile
, topk
, bottomk
支持參數(parameter)。
without
用於從計算結果中移除列舉的標簽,而保留其它標簽。by
則正好相反,結果向量中只保留列出的標簽,其余標簽則移除。通過 without 和 by 可以按照樣本的問題對數據進行聚合。
例如:
如果指標 http_requests_total
的時間序列的標簽集為 application
, instance
, 和 group
,我們可以通過以下方式計算所有 instance 中每個 application 和 group 的請求總量:
sum(http_requests_total) without (instance)
等價於
sum(http_requests_total) by (application, group)
如果只需要計算整個應用的 HTTP 請求總量,可以直接使用表達式:
sum(http_requests_total)
count_values
用於時間序列中每一個樣本值出現的次數。count_values 會為每一個唯一的樣本值輸出一個時間序列,並且每一個時間序列包含一個額外的標簽。這個標簽的名字由聚合參數指定,同時這個標簽值是唯一的樣本值。
例如要計算運行每個構建版本的二進制文件的數量:
count_values("version", build_version)
返回結果如下:
{count="641"} 1 {count="3226"} 2 {count="644"} 4
topk
和 bottomk
則用於對樣本值進行排序,返回當前樣本值前 n 位,或者后 n 位的時間序列。
獲取 HTTP 請求數前 5 位的時序樣本數據,可以使用表達式:
topk(5, http_requests_total)
quantile
用於計算當前樣本數據值的分布情況 quantile(φ, express) ,其中 0 ≤ φ ≤ 1
。
例如,當 φ 為 0.5 時,即表示找到當前樣本數據中的中位數:
quantile(0.5, http_requests_total)
返回結果如下:
{} 656
0x03 二元運算符優先級
在 Prometheus 系統中,二元運算符優先級從高到低的順序為:
-
^
-
*
,/
,%
-
+
,-
-
==
,!=
,<=
,<
,>=
,>
-
and
,unless
-
or
具有相同優先級的運算符是滿足結合律的(左結合)。例如,2 * 3 % 2
等價於 (2 * 3) % 2
。運算符 ^
例外,^
滿足的是右結合,例如,2 ^ 3 ^ 2
等價於 2 ^ (3 ^ 2)
。