zl程序教程

您现在的位置是:首页 >  其它

当前栏目

PromQL 运算

运算
2023-09-14 09:01:47 时间

运算


 Prometheus 的查询语言支持基本的逻辑运算和算术运算。

算术运算符


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

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

最简单的我们可以将一个数字计算当做一个 PromQL 语句,用于标量与标量之间计算,比如:

(2 + 3 / 6) * 2^2

可以得到如下所示的结果:

图形中返回的是一个值为 10 的标量(scalar)类型的数据。

二元运算同样适用于向量和标量之间,例如我们可以将一个字节数除以两次 1024 来转换为 MiB,如下查询语句:

demo_batch_last_run_processed_bytes{job="demo"} / 1024 / 1024

最后计算的结果就是 MiB 单位的了:

另外 PromQL 的一个强大功能就是可以让我们在向量与向量之间进行二元运算。

例如 demo_api_request_duration_seconds_sum 的数据包含了在 pathmethodstatus 等不同维度上花费的总时间,指标 demo_api_request_duration_seconds_count 包含了上面同维度下的请求总次数。则我们可以用下面的语句来查询过去 5 分钟的平均请求持续时间:(平均的时间/平均的数量)

rate(demo_api_request_duration_seconds_sum{job="demo"}[5m])
/
rate(demo_api_request_duration_seconds_count{job="demo"}[5m])


标签集合是一样的,所以可以直接去做运算

 

从上面可以看出,两个指标sum count对应的标签是完全一致的,所以可以做加减乘除运算。

 PromQL 会通过相同的标签集自动匹配操作符左边和右边的元素,并将二元运算应用到它们身上。由于上面两个指标的标签集合都是一致的,所有可以得到相同标签集的平均请求延迟结果 

上面的示例其实就是一对一的向量匹配。

向量匹配


一对一

上面的示例其实就是一对一的向量匹配,但是一对一向量匹配也有两种情况,就是是否按照所有标签匹配进行计算,下图是匹配所有标签的情况:(foo和bar指标里面标签并不是完全对应的)

图中我们两个指标 foo 和 bar,分别生成了 3 个序列:

# TYPE foo gauge
foo{color="red", size="small"} 4
foo{color="green", size="medium"} 8
foo{color="blue", size="large"} 16
# TYPE bar gauge
bar{color="green", size="xlarge"} 2
bar{color="blue", size="large"} 7
bar{color="red", size="small"} 5

当我们执行查询语句 foo{} + bar{} 的时候,对于向量左边的每一个元素,操作符都会尝试在右边里面找到一个匹配的元素匹配是通过比较所有的标签来完成的,没有匹配的元素会被丢弃,我们可以看到其中的 foo{color="green", size="medium"} 与 bar{color="green", size="xlarge"} 两个序列的标签是不匹配的,其余两个序列标签匹配,所以计算结果会抛弃掉不匹配的序列,得到的结果为其余序列的值相加。

上面例子中其中不匹配的标签主要是因为第二个 size 标签不一致造成的,那么如果我们在计算的时候忽略掉这个标签可以吗?如下图所示:

同样针对上面的两个指标,我们在进行计算的时候可以使用 on 或者 ignoring 修饰符来指定用于匹配的标签进行计算,由于示例中两边的标签都具有 color 标签,所以在进行计算的时候我们可以基于该标签(on (color))或者忽略其他的标签(ignoring (size))进行计算,这样得到的结果就是所以匹配的标签序列相加的结果,要注意结果中的标签也是匹配的标签。

一对多 

一对多与多对一(这种场景就不能像上面一样,直接相加,或者加上on ignoring这种关键字就可以处理的了)

上面讲解的一对一的向量计算是最直接的方式,在多数情况下,on 或者 ignoring 修饰符有助于是查询返回合理的结果,但通常情况用于计算的两个向量之间并不是一对一的关系,更多的是一对多或者多对一的关系,对于这种场景我们就不能简单使用上面的方式进行处理了。

多对一和一对多两种匹配模式指的是侧的每一个向量元素可以与侧的多个元素匹配的情况,在这种情况下,必须使用 group 修饰符group_left 或者 group_right 来确定哪一个向量具有更高的基数(充当的角色)(也就是说那个元素是多的那一边,左侧元素比较多需要添加group left,右侧元素多就是group right,让其明确指定一下,注意是基于标签维度的多少)。

多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况因此同样需要使用 ignoring 和 on 修饰符来排除或者限定匹配的标签列表。

例如 demo_num_cpus 指标告诉我们每个实例的 CPU 核心数量,只有 instance 和 job 这两个标签维度。

而 demo_cpu_usage_seconds_total 指标则多了一个 mode 标签的维度,将每个 mode 模式(idlesystemuser)的 CPU 使用情况分开进行了统计。

如果要计算每个模式的 CPU 使用量除以核心数,我们需要告诉除法运算符按照 demo_cpu_usage_seconds_total 指标上额外的 mode 标签维度对结果进行分组,我们可以使用 group_left表示左边的向量具有更高的基数)修饰符来实现。

同时,我们还需要通过 on() 修饰符明确将所考虑的标签集减少到需要匹配的标签列表:(由于左右两边的标签维度不一样,所以需要明确告诉在instance job两个标签维度上面去做计算。左边是counter类型的,所以需要使用rate去做计算)

rate(demo_cpu_usage_seconds_total{job="demo"}[5m])
/ on(job, instance) group_left
demo_num_cpus{job="demo"}

上面的表达式可以正常得到结果:

除了 on() 之外,还可以使用相反的 ignoring() 修饰符,可以用来将一些标签维度从二元运算操作匹配中忽略掉,如果在操作符的右侧有额外的维度,则应该使用 group_right(表示右边的向量具有更高的基数)修饰符。

比如上面的查询语句同样可以用 ignoring 关键字来完成:

rate(demo_cpu_usage_seconds_total{job="demo"}[5m])
/ ignoring(mode) group_left
demo_num_cpus{job="demo"}

得到的结果和前面用 on() 查询的结果是一致的。

到这里我们就知道了如何在 PromQL 中进行标量和向量之间的运算了。

不过我们在使用 PromQL 查询数据的时候还行要避免使用关联查询group right  group left,先想想能不能通过 Relabel(后续会详细介绍)的方式给原始数据多加个 Label,一条语句能查出来的何必用 Join 呢?时序数据库不是关系数据库。(也就是变为一对一,如果能够变为一对一,那么直接计算就行了)

  • 无论一对多还是多对一都要去想哪边是多的一边,哪边是少的一边,这个很重要。去做计算的时候,多的一边在左侧要加上group left,少的一边在左侧,也就是右侧是多的一侧,那么使用group right。!!!!!
  • 由于这里是一对多或者多对一,两个向量去做计算的时候,标签不太一致,需要指定按照哪几个标签去进行计算,这就要使用到on ignoring去忽略一些标签去做一个计算。按照上面这些操作才没有问题!!!!

练习:

1.计算过去 5 分钟所有 POST 请求平均数的总和相对于所有请求平均数总和的百分比。

sum(rate(demo_api_request_duration_seconds_count{method="POST"}[5m]))
/
sum(rate(demo_api_request_duration_seconds_count[5m])) * 100

2.计算过去 5 分钟内每个实例的 user 和 system 的模式(demo_cpu_usage_seconds_total 指标)下 CPU 使用量平均值总和。(这里统计每个实例就需要使用by

sum by(instance, job) (rate(demo_cpu_usage_seconds_total{mode=~"user|system"}[5m]))

或者

sum without(mode) (rate(demo_cpu_usage_seconds_total{mode=~"user|system"}[5m]))

或者

rate(demo_cpu_usage_seconds_total{mode="user"}[5m]) + ignoring(mode)
rate(demo_cpu_usage_seconds_total{mode="system"}[5m])