目录

promQL

表达式语言数据类型

在Prometheus的表达式中,表达式或子表达式包括以下四种类型:

  • 瞬时向量/及时向量(instant vector) , 一组时间序列,每个时间序列包含单个样本,他们共享相同的时间戳。也就是说,表达式的返回值中只会包含该时间序列中的最新的一个样本值。而相应的这样的表达式称之为瞬时向量表达式
  • 区间向量(Range vector),一组时间序列,每个时间序列包含一段时间范围内的样本数据。
  • 标量(Scalar),一个浮点型的数据值
  • 字符串(String),一个简单的字符串

瞬时向量表达式返回的数据类型是唯一可以直接绘制成图表的数据类型

字面量

字符串

字符串可以用单引号、双引号和反引号指定为文字常量

PromQL 遵循与 Go 相同的转义规则。在单引号或双引号中,用反斜杠来表示转义序列,后面可以跟 a, b, f, n, r, t, v\。特殊字符可以使用八进制(\nnn)或者十六进制(\xnn\unnnn\Unnnnnnnn)。

与 Go 不同,Prometheus 不会对反引号内的换行符进行转义。

标量

标量浮点值可以字面上写成 [-](digits)[.(digits)] 的形式。例如:-2.43

时间序列过滤器

瞬时向量过滤器

瞬时向量过滤器允许在指定的时间戳内选择一组时间序列和每个时间序列的单个样本值。在最简单的形式中,指定指标(metric)名称。这将生成包含此指标名称的所有时间序列的元素的瞬时向量。

例如:选择指标名称为 apiserver_request_total 的所有时间序列:

1
apiserver_request_total

可以通过向花括号({})里附加一组标签来进一步过滤时间序列

例如:选择指标名称为apiserver_request_total,code的标签值为200clientOpenAPI-Generator/11.0.0/python的时间序列:

1
apiserver_request_total{code="200",client="OpenAPI-Generator/11.0.0/python"}

PromQL还支持用户根据时间序列的标签匹配模式来对时间序列进行过滤,目前主要支持两种匹配模式,完全匹配和正则匹配。共有以下几种标签匹配运算符

  • =:选择与提供字符串完全相同的标签。
  • !=:选择与提供字符串不相同的标签
  • =~:选择正则表达式与提供的字符串(或子字符串)相匹配的标签
  • !~:选择正则表达式与提供的字符串(或子字符串)不匹配的标签。

例如:选择指标名称为apiserver_request_totalclientOpenAPI-Generator/11.0.0/pythonPrometheus/2.23.0,verb不等于LIST的时间序列

1
apiserver_request_total{client=~"OpenAPI-Generator/11.0.0/python|Prometheus/2.23.0",verb!="LIST"}

如果没有指定标签的标签过滤器会选择该指标名称的所有时间序列

所有的promQL表达式必须至少包含一个指标名称,或者一个不会匹配到空字符串的标签过滤器

非法表达式

1
{job=~".*"}

合法表达式

1
2
{job=~".+"}
{jon=~".*",method="get"}

除了使用<metric name>{label=value}的形式以外,还可以使用内置的__name__ 标签来指定监控指标名称。例如:表达式apiserver_request_total等效于{__name__="apiserver_request_total"}。也可以使用除=之外的过滤器(!= 、=~ 、~),例如以下表达式选择指标名称以job:开头的所有指标

1
{__name__ =~ "job:.*"}

区间向量过滤器

区间向量与瞬时向量的工作方式类似,唯一的差异在于在区间向量表达式中我们需要定义时间选择的范围,时间范围通过时间范围选择器 [] 进行定义,以指定应为每个返回的区间向量样本值中提取多长的时间范围。

时间范围通过数字来表示,单位可以使用以下其中之一的时间单位:

  • s - 秒
  • m - 分钟
  • h - 小时
  • d - 天
  • w - 周
  • y - 年

例如:选择在过去 5 分钟内指标名称为 apiserver_request_totalclient标签值为 Prometheus/2.23.0 的所有时间序列:

1
apiserver_request_total{client="Prometheus/2.23.0"}[5m]

时间位移操作

在瞬时向量表达式或区间向量表达式中,都是以当前时间为基准:

1
2
apiserver_request_total #瞬时向量表达式,选择当前最新的数据
apiserver_request_total{}[5m] #区间向量表达式,选择以当前时间为基准,5分钟以内的数据

而如果想要查询,5分钟前的瞬时样本数据,或昨天一天的区间内的样本数据?就可以使用位移操作,关键字offset

例如,以下表达式返回相当于过去5分钟的apiserver_request_total的值

1
apiserver_request_total offset 5m

注意:offset 关键字需要紧跟在选择器({})后面,正确写法

1
sum(apiserver_request_total{client="Prometheus/2.23.0"} offset 5m)

错误写法

1
sum(apiserver_request_total{client="Prometheus/2.23.0"}) offset 5m

此操作同样适用于区间向量。以下表达式返回指标apiserver_request_total一周前的5分钟之内HTTP请求的增长率

1
rate(apiserver_request_total[5m]  offset 1w)

操作符

二元算术符

Prometheus 的查询语言支持基本的逻辑运算和算术运算。对于两个瞬时向量, 匹配行为可以被改变。

算术二元运算符

在 Prometheus 系统中支持下面的二元算术运算符:

  • + 加法
  • - 减法
  • * 乘法
  • / 除法
  • %
  • ^ 幂等

二元运算操作符支持 scalar/scalar(标量/标量)vector/scalar(向量/标量)、和 vector/vector(向量/向量) 之间的操作。

在两个标量之间进行数学运算,得到的结果也是标量。

在向量和标量之间,这个运算符会作用于这个向量的每个样本值上。例如:如果一个时间序列瞬时向量除以 2,操作结果也是一个新的瞬时向量,且度量指标名称不变, 它是原度量指标瞬时向量的每个样本值除以 2。

如果是瞬时向量与瞬时向量之间进行数学运算时,过程会相对复杂一点,运算符会依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。同时新的时间序列将不会包含指标名称。

例如,如果我们想根据 node_disk_written_bytes_total node_disk_read_bytes_total 获取主机磁盘IO的总量,可以使用如下表达式:

1
node_disk_written_bytes_total + node_disk_read_bytes_total

布尔运算符

目前,Prometheus 支持以下布尔运算符:

  • == (相等)
  • != (不相等)
  • > (大于)
  • < (小于)
  • >= (大于等于)
  • <= (小于等于)

布尔运算符被应用于 scalar/scalar(标量/标量)vector/scalar(向量/标量),和vector/vector(向量/向量)。默认情况下布尔运算符只会根据时间序列中样本的值,对时间序列进行过滤。我们可以通过在运算符后面使用 bool 修饰符来改变布尔运算的默认行为。使用 bool 修改符后,布尔运算不会对时间序列进行过滤,而是直接依次瞬时向量中的各个样本数据与标量的比较结果 0 或者 1

在两个标量之间进行布尔运算,必须提供 bool 修饰符,得到的结果也是标量,即 0false)或 1true)。例如:

1
2 > bool 1  #结果为1

瞬时向量和标量之间的布尔运算,这个运算符会应用到某个当前时刻的每个时序数据上,如果一个时序数据的样本值与这个标量比较的结果是 false,则这个时序数据被丢弃掉,如果是 true, 则这个时序数据被保留在结果中。如果提供了 bool 修饰符,那么比较结果是 0 的时序数据被丢弃掉,而比较结果是 1 的时序数据被保留。例如:

1
2
apiserver_request_count > 1000 #瞬时向量 > 1000的保存在结果集中
apiserver_request_count > bool 1000 #所有都保存在结果集中,大于1000的结果显示为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 匹配的元素组成。

匹配模式

向量与向量之间进行运算操作时会基于默认的匹配规则:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。

接下来将介绍在 PromQL 中有两种典型的匹配模式:一对一(one-to-one),多对一(many-to-one)或一对多(one-to-many)。

一对一匹配

一对一匹配模式会从操作符两边表达式获取的瞬时向量依次比较并找到唯一匹配(标签完全一致)的样本值。默认情况下,使用表达式:

1
vector1 <operator> vector2

在操作符两边表达式标签不一致的情况下,可以使用on(label list) 或者 ignoring(label list)来修改便签的匹配行为。使用 ignoreing 可以在匹配时忽略某些便签。而 on 则用于将匹配行为限定在某些便签之内.

1
2
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>

例如当存在样本:

1
2
3
4
5
6
7
8
9
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表达式

1
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m

该表达式会返回在过去 5 分钟内,HTTP 请求状态码为 500 的在所有请求中的比例。如果没有使用 ignoring(code),操作符两边表达式返回的瞬时向量中将找不到任何一个标签完全相同的匹配项。

因此结果如下:

1
2
{method="get"}  0.04            //  24 / 600
{method="post"} 0.05            //   6 / 120

同时由于 method 为 putdel 的样本找不到匹配项,因此不会出现在结果当中。

多对一和一对多

多对一和一对多两种匹配模式指的是“一”侧的每一个向量元素可以与"多"侧的多个元素匹配的情况。在这种情况下,必须使用 group 修饰符:group_left 或者 group_right 来确定哪一个向量具有更高的基数(充当“多”的角色)。

1
2
3
4
<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 修饰符来排除或者限定匹配的标签列表。

例如,使用表达式:

1
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m

该表达式中,左向量 method_code:http_errors:rate5m 包含两个标签 methodcode。而右向量 method:http_requests:rate5m 中只包含一个标签 method,因此匹配时需要使用 ignoring 限定匹配的标签为 code。 在限定匹配标签后,右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一,需要使用 group 修饰符 group_left 指定左向量具有更好的基数。

最终的运算结果如下:

1
2
3
4
{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 修饰符只能在比较和数学运算符中使用。在逻辑运算 andunlessor 操作中默认与右向量中的所有元素进行匹配。

聚合操作

Prometheus 还提供了下列内置的聚合操作符,这些操作符作用域瞬时向量。可以将瞬时表达式返回的样本数据进行聚合,形成一个具有较少样本值的新的时间序列。

  • sum (求和)
  • min (最小值)
  • max (最大值)
  • avg (平均值)
  • stddev (标准差)
  • stdvar (标准差异)
  • count (计数)
  • count_values (对 value 进行计数)
  • bottomk (样本值最小的 k 个元素)
  • topk (样本值最大的k个元素)
  • quantile (分布统计)

这些操作符被用于聚合所有标签维度,或者通过 without 或者 by 子语句来保留不同的维度。

1
<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 的请求总量:

1
sum(http_requests_total) without (instance)

等价于

1
 sum(http_requests_total) by (application, group)

如果只需要计算整个应用的 HTTP 请求总量,可以直接使用表达式:

1
sum(http_requests_total)

count_values 用于时间序列中每一个样本值出现的次数。count_values 会为每一个唯一的样本值输出一个时间序列,并且每一个时间序列包含一个额外的标签。这个标签的名字由聚合参数指定,同时这个标签值是唯一的样本值。

例如要计算运行每个构建版本的二进制文件的数量:

1
count_values("version", build_version)

返回结果如下:

1
2
3
{count="641"}   1
{count="3226"}  2
{count="644"}   4

topkbottomk 则用于对样本值进行排序,返回当前样本值前 n 位,或者后 n 位的时间序列。

获取 HTTP 请求数前 5 位的时序样本数据,可以使用表达式:

1
topk(5, http_requests_total)

quantile 用于计算当前样本数据值的分布情况 quantile(φ, express) ,其中 0 ≤ φ ≤ 1

例如,当 φ 为 0.5 时,即表示找到当前样本数据中的中位数:

1
quantile(0.5, http_requests_total)

返回结果如下:

1
{}   656

二元运算符优先级

在 Prometheus 系统中,二元运算符优先级从高到低的顺序为:

  1. ^
  2. *, /, %
  3. +, -
  4. ==, !=, <=, <, >=, >
  5. and, unless
  6. or

具有相同优先级的运算符是满足结合律的(左结合)。例如,2 * 3 % 2 等价于 (2 * 3) % 2。运算符 ^ 例外,^ 满足的是右结合,例如,2 ^ 3 ^ 2 等价于 2 ^ (3 ^ 2)

内置函数

Prometheus 提供了其它大量的内置函数,可以对时序数据进行丰富的处理。某些函数有默认的参数,例如:year(v=vector(time()) instant-vector)。其中参数 v 是一个瞬时向量,如果不提供该参数,将使用默认值 vector(time())。instant-vector 表示参数类型。

1. abs()

abs(v instant-vector) 返回输入向量的所有样本的绝对值。

2. absent()

absent(v instant-vector),如果传递给它的向量参数具有样本数据,则返回空向量;如果传递的向量参数没有样本数据,则返回不带度量指标名称且带有标签的时间序列,且样本值为1。

当监控度量指标时,如果获取到的样本数据是空的, 使用 absent 方法对告警是非常有用的。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 这里提供的向量有样本数据
absent(http_requests_total{method="get"})  => no data
absent(sum(http_requests_total{method="get"}))  => no data

# 由于不存在度量指标 nonexistent,所以 返回不带度量指标名称且带有标签的时间序列,且样本值为1
absent(nonexistent{job="myjob"})  => {job="myjob"}  1
# 正则匹配的 instance 不作为返回 labels 中的一部分
absent(nonexistent{job="myjob",instance=~".*"})  => {job="myjob"}  1

# sum 函数返回的时间序列不带有标签,且没有样本数据
absent(sum(nonexistent{job="myjob"}))  => {}  1

3. ceil()

ceil(v instant-vector) 将 v 中所有元素的样本值向上四舍五入到最接近的整数。例如:

1
2
node_load5{instance="192.168.1.75:9100"} # 结果为 2.79
ceil(node_load5{instance="192.168.1.75:9100"}) # 结果为 3

4. changes()

changes(v range-vector) 输入一个区间向量, 返回这个区间向量内每个样本数据值变化的次数(瞬时向量)。例如:

1
2
# 如果样本数据值没有发生变化,则返回结果为 1
changes(node_load5{instance="192.168.1.75:9100"}[1m]) # 结果为 1

5. clamp_max()

clamp_max(v instant-vector, max scalar) 函数,输入一个瞬时向量和最大值,样本数据值若大于 max,则改为 max,否则不变。例如:

1
2
node_load5{instance="192.168.1.75:9100"} # 结果为 2.79
clamp_max(node_load5{instance="192.168.1.75:9100"}, 2) # 结果为 2

6. clamp_min()

clamp_min(v instant-vector, min scalar) 函数,输入一个瞬时向量和最小值,样本数据值若小于 min,则改为 min,否则不变。例如:

1
2
node_load5{instance="192.168.1.75:9100"} # 结果为 2.79
clamp_min(node_load5{instance="192.168.1.75:9100"}, 3) # 结果为 3

7. day_of_month()

day_of_month(v=vector(time()) instant-vector) 函数,返回被给定 UTC 时间所在月的第几天。返回值范围:1~31。

8. day_of_week()

day_of_week(v=vector(time()) instant-vector) 函数,返回被给定 UTC 时间所在周的第几天。返回值范围:0~6,0 表示星期天。

9. days_in_month()

days_in_month(v=vector(time()) instant-vector) 函数,返回当月一共有多少天。返回值范围:28~31。

10. delta()

delta(v range-vector) 的参数是一个区间向量,返回一个瞬时向量。它计算一个区间向量 v 的第一个元素和最后一个元素之间的差值。由于这个值被外推到指定的整个时间范围,所以即使样本值都是整数,你仍然可能会得到一个非整数值。

例如,下面的例子返回过去两小时的 CPU 温度差:

1
delta(cpu_temp_celsius{host="zeus"}[2h])

这个函数一般只用在 Gauge 类型的时间序列上。

11. deriv()

deriv(v range-vector) 的参数是一个区间向量,返回一个瞬时向量。它使用简单的线性回归计算区间向量 v 中各个时间序列的导数。

这个函数一般只用在 Gauge 类型的时间序列上。

12. exp()

exp(v instant-vector) 函数,输入一个瞬时向量,返回各个样本值的 e 的指数值,即 e 的 N 次方。当 N 的值足够大时会返回 +Inf。特殊情况为:

  • Exp(+Inf) = +Inf
  • Exp(NaN) = NaN

13. floor()

floor(v instant-vector) 函数与 ceil() 函数相反,将 v 中所有元素的样本值向下四舍五入到最接近的整数。

14. histogram_quantile()

histogram_quantile(φ float, b instant-vector) 从 bucket 类型的向量 b 中计算 φ (0 ≤ φ ≤ 1) 分位数(百分位数的一般形式)的样本的最大值。(有关 φ 分位数的详细说明以及直方图指标类型的使用,请参阅直方图和摘要)。向量 b 中的样本是每个 bucket 的采样点数量。每个样本的 labels 中必须要有 le 这个 label 来表示每个 bucket 的上边界,没有 le 标签的样本会被忽略。直方图指标类型自动提供带有 _bucket 后缀和相应标签的时间序列。

可以使用 rate() 函数来指定分位数计算的时间窗口。

例如,一个直方图指标名称为 employee_age_bucket_bucket,要计算过去 10 分钟内 第 90 个百分位数,请使用以下表达式:

1
histogram_quantile(0.9, rate(employee_age_bucket_bucket[10m]))

返回:

1
{instance="10.0.86.71:8080",job="prometheus"} 35.714285714285715

这表示最近 10 分钟之内 90% 的样本的最大值为 35.714285714285715。

这个计算结果是每组标签组合成一个时间序列。我们可能不会对所有这些维度(如 jobinstancemethod)感兴趣,并希望将其中的一些维度进行聚合,则可以使用 sum() 函数。例如,以下表达式根据 job 标签来对第 90 个百分位数进行聚合:

1
2
# histogram_quantile() 函数必须包含 le 标签
histogram_quantile(0.9, sum(rate(employee_age_bucket_bucket[10m])) by (job, le))

如果要聚合所有的标签,则使用如下表达式:

1
histogram_quantile(0.9,sum(rate(employee_age_bucket_bucket[10m])) by (le))

注意

histogram_quantile 这个函数是根据假定每个区间内的样本分布是线性分布来计算结果值的(也就是说它的结果未必准确),最高的 bucket 必须是 le="+Inf" (否则就返回 NaN)。

如果分位数位于最高的 bucket(+Inf) 中,则返回第二个最高的 bucket 的上边界。如果该 bucket 的上边界大于 0,则假设最低的 bucket 的的下边界为 0,这种情况下在该 bucket 内使用常规的线性插值。

如果分位数位于最低的 bucket 中,则返回最低 bucket 的上边界。

如果 b 含有少于 2 个 buckets,那么会返回 NaN,如果 φ < 0 会返回 -Inf,如果 φ > 1 会返回 +Inf

15. holt_winters()

holt_winters(v range-vector, sf scalar, tf scalar) 函数基于区间向量 v,生成时间序列数据平滑值。平滑因子 sf 越低, 对旧数据的重视程度越高。趋势因子 tf 越高,对数据的趋势的考虑就越多。其中,0< sf, tf <=1

holt_winters 仅适用于 Gauge 类型的时间序列。

16. hour()

hour(v=vector(time()) instant-vector) 函数返回被给定 UTC 时间的当前第几个小时,时间范围:0~23。

17. idelta()

idelta(v range-vector) 的参数是一个区间向量, 返回一个瞬时向量。它计算最新的 2 个样本值之间的差值。

这个函数一般只用在 Gauge 类型的时间序列上。

18. increase()

increase(v range-vector) 函数获取区间向量中的第一个和最后一个样本并返回其增长量, 它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。由于这个值被外推到指定的整个时间范围,所以即使样本值都是整数,你仍然可能会得到一个非整数值。

例如,以下表达式返回区间向量中每个时间序列过去 5 分钟内 HTTP 请求数的增长数:

1
increase(http_requests_total{job="apiserver"}[5m])

increase 的返回值类型只能是计数器类型,主要作用是增加图表和数据的可读性。使用 rate 函数记录规则的使用率,以便持续跟踪数据样本值的变化。

19. irate()

irate(v range-vector) 函数用于计算区间向量的增长率,但是其反应出的是瞬时增长率。irate 函数是通过区间向量中最后两个两本数据来计算区间向量的增长速率,它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。这种方式可以避免在时间窗口范围内的“长尾问题”,并且体现出更好的灵敏度,通过irate函数绘制的图标能够更好的反应样本数据的瞬时变化状态。

例如,以下表达式返回区间向量中每个时间序列过去 5 分钟内最后两个样本数据的 HTTP 请求数的增长率:

1
irate(http_requests_total{job="api-server"}[5m])

irate 只能用于绘制快速变化的计数器,在长期趋势分析或者告警中更推荐使用 rate 函数。因为使用 irate 函数时,速率的简短变化会重置 FOR 语句,形成的图形有很多波峰,难以阅读。

注意

当将 irate() 函数与聚合运算符(例如 sum())或随时间聚合的函数(任何以 _over_time 结尾的函数)一起使用时,必须先执行 irate 函数,然后再进行聚合操作,否则当采样目标重新启动时 irate() 无法检测到计数器是否被重置。

20. label_join()

label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...) 函数可以将时间序列 v 中多个标签 src_label 的值,通过 separator 作为连接符写入到一个新的标签 dst_label 中。可以有多个 src_label 标签。

例如,以下表达式返回的时间序列多了一个 foo 标签,标签值为 etcd,etcd-k8s

1
2
3
4
5
up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}
=> up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}  1

label_join(up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}, "foo", ",", "job", "service")
=> up{endpoint="api",foo="etcd,etcd-k8s",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}  1

21. label_replace()

为了能够让客户端的图标更具有可读性,可以通过 label_replace 函数为时间序列添加额外的标签。label_replace 的具体参数如下:

1
label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)

该函数会依次对 v 中的每一条时间序列进行处理,通过 regex 匹配 src_label 的值,并将匹配部分 relacement 写入到 dst_label 标签中。如下所示:

1
label_replace(up, "host", "$1", "instance",  "(.*):.*")

函数处理后,时间序列将包含一个 host 标签,host 标签的值为 Exporter 实例的 IP 地址:

1
2
3
up{host="localhost",instance="localhost:8080",job="cadvisor"}   1
up{host="localhost",instance="localhost:9090",job="prometheus"}   1
up{host="localhost",instance="localhost:9100",job="node"}   1

22. ln()

ln(v instant-vector) 计算瞬时向量 v 中所有样本数据的自然对数。特殊情况:

  • ln(+Inf) = +Inf
  • ln(0) = -Inf
  • ln(x < 0) = NaN
  • ln(NaN) = NaN

23. log2()

log2(v instant-vector) 函数计算瞬时向量 v 中所有样本数据的二进制对数。特殊情况同上。

24. log10()

log10(v instant-vector) 计算瞬时向量 v 中所有样本数据的十进制对数。特殊情况同上。

25. minute()

minute(v=vector(time()) instant-vector) 函数返回给定 UTC 时间当前小时的第多少分钟。结果范围:0~59。

26. month()

month(v=vector(time()) instant-vector) 函数返回给定 UTC 时间当前属于第几个月,结果范围:0~12。

27. predict_linear()

predict_linear(v range-vector, t scalar) 函数可以预测时间序列 v 在 t 秒后的值。它基于简单线性回归的方式,对时间窗口内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测。该函数的返回结果不带有度量指标,只有标签列表。

例如,基于 2 小时的样本数据,来预测主机可用磁盘空间的是否在 4 个小时候被占满,可以使用如下表达式:

1
predict_linear(node_filesystem_free{job="node"}[2h], 4 * 3600) < 0

通过下面的例子来观察返回值:

1
2
3
4
5
6
predict_linear(http_requests_total{code="200",instance="120.77.65.193:9090",job="prometheus",method="get"}[5m], 5)
结果:
{code="200",handler="query_range",instance="120.77.65.193:9090",job="prometheus",method="get"}  1
{code="200",handler="prometheus",instance="120.77.65.193:9090",job="prometheus",method="get"}   4283.449995397104
{code="200",handler="static",instance="120.77.65.193:9090",job="prometheus",method="get"}   22.99999999999999
...

这个函数一般只用在 Gauge 类型的时间序列上。

28. rate()

rate(v range-vector) 函数可以直接计算区间向量 v 在时间窗口内平均增长速率,它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。该函数的返回结果不带有度量指标,只有标签列表。

例如,以下表达式返回区间向量中每个时间序列过去 5 分钟内 HTTP 请求数的每秒增长率:

1
2
3
4
5
6
rate(http_requests_total[5m])
结果:
{code="200",handler="label_values",instance="120.77.65.193:9090",job="prometheus",method="get"} 0
{code="200",handler="query_range",instance="120.77.65.193:9090",job="prometheus",method="get"}  0
{code="200",handler="prometheus",instance="120.77.65.193:9090",job="prometheus",method="get"}   0.2
...

rate() 函数返回值类型只能用计数器,在长期趋势分析或者告警中推荐使用这个函数。

注意

当将 rate() 函数与聚合运算符(例如 sum())或随时间聚合的函数(任何以 _over_time 结尾的函数)一起使用时,必须先执行 rate 函数,然后再进行聚合操作,否则当采样目标重新启动时 rate() 无法检测到计数器是否被重置。

29. resets()

resets(v range-vector) 的参数是一个区间向量。对于每个时间序列,它都返回一个计数器重置的次数。两个连续样本之间的值的减少被认为是一次计数器重置。

这个函数一般只用在计数器类型的时间序列上。

30. round()

round(v instant-vector, to_nearest=1 scalar) 函数与 ceilfloor 函数类似,返回向量中所有样本值的最接近的整数。to_nearest 参数是可选的,默认为 1,表示样本返回的是最接近 1 的整数倍的值。你也可以将该参数指定为任意值(也可以是小数),表示样本返回的是最接近它的整数倍的值。

31. scalar()

scalar(v instant-vector) 函数的参数是一个单元素的瞬时向量,它返回其唯一的时间序列的值作为一个标量。如果度量指标的样本数量大于 1 或者等于 0, 则返回 NaN

32. sort()

sort(v instant-vector) 函数对向量按元素的值进行升序排序,返回结果:key: value = 度量指标:样本值[升序排列]。

33. sort_desc()

sort(v instant-vector) 函数对向量按元素的值进行降序排序,返回结果:key: value = 度量指标:样本值[降序排列]。

34. sqrt()

sqrt(v instant-vector) 函数计算向量 v 中所有元素的平方根。

35. time()

time() 函数返回从 1970-01-01 到现在的秒数。注意:它不是直接返回当前时间,而是时间戳

36. timestamp()

timestamp(v instant-vector) 函数返回向量 v 中的每个样本的时间戳(从 1970-01-01 到现在的秒数)。

该函数从 Prometheus 2.0 版本开始引入。

37. vector()

vector(s scalar) 函数将标量 s 作为没有标签的向量返回,即返回结果为:key: value= {}, s。

38. year()

year(v=vector(time()) instant-vector) 函数返回被给定 UTC 时间的当前年份。

39. <aggregation>_over_time()

下面的函数列表允许传入一个区间向量,它们会聚合每个时间序列的范围,并返回一个瞬时向量:

  • avg_over_time(range-vector) : 区间向量内每个度量指标的平均值。
  • min_over_time(range-vector) : 区间向量内每个度量指标的最小值。
  • max_over_time(range-vector) : 区间向量内每个度量指标的最大值。
  • sum_over_time(range-vector) : 区间向量内每个度量指标的求和。
  • count_over_time(range-vector) : 区间向量内每个度量指标的样本数据个数。
  • quantile_over_time(scalar, range-vector) : 区间向量内每个度量指标的样本数据值分位数,φ-quantile (0 ≤ φ ≤ 1)。
  • stddev_over_time(range-vector) : 区间向量内每个度量指标的总体标准差。
  • stdvar_over_time(range-vector) : 区间向量内每个度量指标的总体标准方差。

注意

即使区间向量内的值分布不均匀,它们在聚合时的权重也是相同的。

陷阱

失效

执行查询操作时,独立于当前时刻被选中的时间序列数据所对应的时间戳,这个时间戳主要用来进行聚合操作,包括 sum, avg 等,大多数聚合的时间序列数据所对应的时间戳没有对齐。由于它们的独立性,我们需要在这些时间戳中选择一个时间戳,并已这个时间戳为基准,获取小于且最接近这个时间戳的时间序列数据。

如果采样目标或告警规则不再返回之前存在的时间序列的样本,则该时间序列将被标记为失效。如果删除了采样目标,则之前返回的时间序列也会很快被标记为失效。

如果在某个时间序列被标记为失效后在该时间戳处执行查询操作,则不会为该时间序列返回任何值。如果随后在该时间序列中插入了新的样本,则照常返回时间序列数据。

如果在采样时间戳前 5 分钟(默认情况)未找到任何样本,则该时间戳不会返回任何任何该时间序列的值。这实际上意味着你在图表中看到的数据都是在当前时刻 5 分钟前的数据。

对于在采样点中包含时间戳的时间序列,不会被标记为失效。在这种情况下,仅使用 5 分钟阈值检测的规则。

避免慢查询和高负载

如果一个查询需要操作非常大的数据量,图表绘制很可能会超时,或者服务器负载过高。因此,在对未知数据构建查询时,始终需要在 Prometheus 表达式浏览器的表格视图中构建查询,直到结果是看起来合理的(最多为数百个,而不是数千个)。只有当你已经充分过滤或者聚合数据时,才切换到图表模式。如果表达式的查询结果仍然需要很长时间才能绘制出来,则需要通过记录规则重新清洗数据。

api_http_requests_total 这样简单的度量指标名称选择器,可以扩展到具有不同标签的数千个时间序列中,这对于 Prometheus 的查询语言是非常重要的。还要记住,对于聚合操作来说,即使输出的时间序列集非常少,它也会在服务器上产生负载。这类似于在关系型数据库中查询一个字段的总和,总是非常缓慢。

在HTTP API中使用PromQL

Prometheus 当前稳定的 HTTP API 可以通过 /api/v1 访问。

API 响应格式

Prometheus API 使用了 JSON 格式的响应内容。 当 API 调用成功后将会返回 2xx 的 HTTP 状态码。

反之,当 API 调用失败时可能返回以下几种不同的 HTTP 状态码:

  • 404 Bad Request :当参数错误或者缺失时。
  • 422 Unprocessable Entity : 当表达式无法执行时。
  • 503 Service Unavailable : 当请求超时或者被中断时。

所有的 API 请求返回的格式均使用以下的 JSON 格式:

1
2
3
4
5
6
7
8
9
{
  "status": "success" | "error",
  "data": <data>,

  // Only set if status is "error". The data field may still hold
  // additional data.
  "errorType": "<string>",
  "error": "<string>"
}

输入时间戳可以由 RFC3339 格式或者 Unix 时间戳提供,后面可选的小数位可以精确到亚秒级别。输出时间戳以 Unix 时间戳的方式呈现。

查询参数名称可以用中括号 [] 重复次数。<series_selector> 占位符提供像 http_requests_total 或者 http_requests_total{method=~"(GET|POST)"} 的 Prometheus 时间序列选择器,并需要在 URL 中编码传输。

<duration> 占位符指的是 [0-9]+[smhdwy] 形式的 Prometheus 持续时间字符串。例如:5m 表示 5 分钟的持续时间。

<bool> 提供布尔值(字符串 true 和 false)。

表达式查询

通过 HTTP API 我们可以分别通过 /api/v1/query/api/v1/query_range 查询 PromQL 表达式当前或者一定时间范围内的计算结果。

瞬时数据查询

通过使用 QUERY API 我们可以查询 PromQL 在特定时间点下的计算结果。

1
GET /api/v1/query

URL 请求参数:

  • query=<string> : PromQL 表达式。
  • time=<rfc3339 | unix_timestamp> : 用于指定用于计算 PromQL 的时间戳。可选参数,默认情况下使用当前系统时间。
  • timeout=<duration> : 超时设置。可选参数,默认情况下使用全局设置的参数 -query.timeout

如果 time 参数缺省,则使用当前服务器时间。

当 API 调用成功后,Prometheus 会返回 JSON 格式的响应内容,格式如上小节所示。并且在 data 部分返回查询结果。data 部分格式如下:

1
2
3
4
{
  "resultType": "matrix" | "vector" | "scalar" | "string",
  "result": <value>
}

<value> 指的是查询结果数据,具体的格式取决于 resultType,不同的结果类型,会有不同的结果数据格式。参考 响应数据格式

例如使用以下表达式查询表达式 up 在时间点 2015-07-01T20:10:51.781Z 的计算结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ curl 'http://localhost:9090/api/v1/query?query=up&time=2015-07-01T20:10:51.781Z'
{
   "status" : "success",
   "data" : {
      "resultType" : "vector",
      "result" : [
         {
            "metric" : {
               "__name__" : "up",
               "job" : "prometheus",
               "instance" : "localhost:9090"
            },
            "value": [ 1435781451.781, "1" ]
         },
         {
            "metric" : {
               "__name__" : "up",
               "job" : "node",
               "instance" : "localhost:9100"
            },
            "value" : [ 1435781451.781, "0" ]
         }
      ]
   }
}

区间数据查询

使用 QUERY_RANGE API 我们则可以直接查询 PromQL 表达式在一段时间返回内的计算结果。

1
GET /api/v1/query_range

URL 请求参数:

  • query=<string> : PromQL 表达式。
  • start=<rfc3339 | unix_timestamp> : 起始时间戳。
  • end=<rfc3339 | unix_timestamp> : 结束时间戳。
  • step=<duration | float> : 查询时间步长,时间区间内每 step 秒执行一次。
  • timeout=<duration> : 超时设置。可选参数,默认情况下使用全局设置的参数 -query.timeout

当使用 QUERY_RANGE API 查询 PromQL 表达式时,返回结果一定是一个区间向量:

1
2
3
4
{
  "resultType": "matrix",
  "result": <value>
}

注意

在 QUERY_RANGE API 中 PromQL 只能使用瞬时向量选择器类型的表达式。

对于 <value> 占位符的格式,详见 区间向量查询结果格式

例如使用以下表达式查询表达式 up 在 30 秒范围内以 15 秒为间隔计算 PromQL 表达式的结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ curl 'http://localhost:9090/api/v1/query_range?query=up&start=2015-07-01T20:10:30.781Z&end=2015-07-01T20:11:00.781Z&step=15s'
{
   "status" : "success",
   "data" : {
      "resultType" : "matrix",
      "result" : [
         {
            "metric" : {
               "__name__" : "up",
               "job" : "prometheus",
               "instance" : "localhost:9090"
            },
            "values" : [
               [ 1435781430.781, "1" ],
               [ 1435781445.781, "1" ],
               [ 1435781460.781, "1" ]
            ]
         },
         {
            "metric" : {
               "__name__" : "up",
               "job" : "node",
               "instance" : "localhost:9091"
            },
            "values" : [
               [ 1435781430.781, "0" ],
               [ 1435781445.781, "0" ],
               [ 1435781460.781, "1" ]
            ]
         }
      ]
   }
}

查询元数据

通过标签选择器查找时间序列

以下表达式返回与特定标签集匹配的时间序列列表:

1
GET /api/v1/series

URL 请求参数:

  • match[]=<series_selector> : 表示标签选择器是 series_selector。必须至少提供一个 match[] 参数。
  • start=<rfc3339 | unix_timestamp> : 起始时间戳。
  • end=<rfc3339 | unix_timestamp> : 结束时间戳。

返回结果的 data 部分,是由 key-value 键值对的对象列表组成的。

例如使用以下表达式查询表达式 upprocess_start_time_seconds{job="prometheus"} 的计算结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl -g 'http://localhost:9090/api/v1/series?match[]=up&match[]=process_start_time_seconds{job="prometheus"}'
{
   "status" : "success",
   "data" : [
      {
         "__name__" : "up",
         "job" : "prometheus",
         "instance" : "localhost:9090"
      },
      {
         "__name__" : "up",
         "job" : "node",
         "instance" : "localhost:9091"
      },
      {
         "__name__" : "process_start_time_seconds",
         "job" : "prometheus",
         "instance" : "localhost:9090"
      }
   ]
}

查询标签值

下面这个例子返回了带有指定标签的的时间序列列表:

1
GET /api/v1/label/<label_name>/values

返回结果的 data 部分是一个标签值列表。例如,以下表达式返回结果的 data 部分是标签名称为 job 的所有标签值:

1
2
3
4
5
6
7
8
$ curl http://localhost:9090/api/v1/label/job/values
{
   "status" : "success",
   "data" : [
      "node",
      "prometheus"
   ]
}

响应数据格式

表达式查询结果可能会在 data 部分的 result 字段中返回以下的响应值。其中 <sample_value> 占位符是数值样本值。由于 json 不支持特殊浮点值,例如:NaN, Inf, 和 -Inf,所以样本值将会作为字符串(而不是原始数值)来进行传输。

区间向量

当返回数据类型 resultType 为 matrix 时,result 响应格式如下:

1
2
3
4
5
6
7
[
  {
    "metric": { "<label_name>": "<label_value>", ... },
    "values": [ [ <unix_time>, "<sample_value>" ], ... ]
  },
  ...
]

其中 metrics 表示当前时间序列的特征维度,values 包含当前事件序列的一组样本。

瞬时向量

当返回数据类型 resultType 为 vector 时,result 响应格式如下:

1
2
3
4
5
6
7
[
  {
    "metric": { "<label_name>": "<label_value>", ... },
    "value": [ <unix_time>, "<sample_value>" ]
  },
  ...
]

其中 metrics 表示当前时间序列的特征维度,values 包含当前事件序列的一组样本。

标量

当返回数据类型 resultType 为 scalar 时,result 响应格式如下:

1
[ <unix_time>, "<scalar_value>" ]

由于标量不存在时间序列一说,因此 result 表示为当前系统时间一个标量的值。

字符串

当返回数据类型 resultType 为 string 时,result 响应格式如下:

1
[ <unix_time>, "<string_value>" ]

字符串类型的响应内容格式和标量相同。