3、PromQL语法学习

Prometheus入门 / 2022-05-11

本文为:朱政科《Prometheus云原生监控:运维与开发实战》的学习笔记,感谢作者的贡献

一、前言

众所周知,想学习编程,得先学习编程语言的语法;想学习使用数据库,得先学习SQL

那么Prometheus也有内置的“SQL”:PromQLprometheus query language,即prometheus查询语言。它可以实时选择和汇聚时序数据,快速查询与检索数据。表达式(SQL)的结果根据数据类型还可以绘制成图形来展示、表格形式展示、第三方应用系统还可以通过API形式调用。需要注意的是,虽然PromQLQL结尾,但它并非与SQL类似,因为SQL缺乏在书序数据上计算的能力。

PromQL除了支持常见的操作符号,还提供大量的内置函数来实现对数据的高级处理。日常数据查询、可视化展示、告警配置,这三大模块都是依赖PromQL实现的。

PromQL是prometheus的基石,任何以prometheus为基础的应用系统都绕不开它,因此我们需要花费足够的精力来学习它。本章节将从时间序列、PromQL数据类型、选择器、指标类型、聚合操作、三元操作符、内置函数、实践、性能优化等方面,介绍PromQL相关的知识。

二、PromQL基础

下面会通过几个简单的例子来学习PromQL,首先得先启动prometheus,然后浏览器打开9090管理后台,因为我是在本机上部署的,所以地址是:http://localhost:9090

2.1、查询可用内存

在打开的窗口里,有一个输入框,当我们输入关键字时,prometheus会自动筛选语句,当敲下回车键后补全完整的语句,且当选中指标时,会显示该指标的注释。

image-20220421152523054

我们选中node_memory_MemFree_bytes后,点击右方的Execute执行查询,会返回结果在下方表格中:

node_memory_MemFree_bytes{instance=“localhost:9100”, job=“prometheus”} 28204482560

可以看到结果是一串数字,证明该指标是一个瞬间向量,即值会变化,它意思是当前主机可用内存大小,单位为字节,这看起来不太友好,我们可以将其单位转换一下。

image-20220421153325053

上面结果显示我的空闲内存为26.3GB,命令行查询结果后证明是准确

sanxi@sanxi-PC:~$ free -h
              total        used        free      shared  buff/cache   available
Mem:           31Gi       1.8Gi        26Gi       1.0Gi       3.0Gi        27Gi
Swap:            0B          0B          0B

2.2、预测磁盘趋势

predict_linear(node_filesystem_free_bytes[1h], 3600*24) == 0

predict_linear函数可以预测一定时间范围内的时序数据在N秒后的值,它基于简单线性回归的方式,对一定时间的样本数据进行统计,从而对该时间段内的时序数据的变化趋势进行判断预测。

以上语句为根据文件系统过去1小时以内的磁盘剩余空间,去预测未来24小时后磁盘剩余空间是否会小于0,也就是一天后磁盘是否会满,下面是执行后得出的结果:

在PromQL中,布尔值1代表是,0代表否。

{device=“/dev/nvme0n1p1”, fstype=“vfat”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/boot/efi”} 0
{device=“/dev/nvme0n1p3”, fstype=“fuseblk”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/media/sanxi/本地磁盘”} 0
{device=“/dev/nvme0n1p4”, fstype=“fuseblk”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/media/sanxi/WinRE”} 0
{device=“/dev/nvme0n1p5”, fstype=“fuseblk”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/media/sanxi/RecoveryImage”} 0
{device=“/dev/nvme0n1p6”, fstype=“ext4”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/”} 0
{device=“/dev/nvme1n1p1”, fstype=“fuseblk”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/media/sanxi/0A9AD66165F337621”} 0
{device=“encrypte_file”, fstype=“fuse.encrypte_file”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/home/sanxi/.config/browser/Default/virtual”} 0
{device=“gvfsd-fuse”, fstype=“fuse.gvfsd-fuse”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/run/user/1000/gvfs”} 0
{device=“tmpfs”, fstype=“tmpfs”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/run”} 0
{device=“tmpfs”, fstype=“tmpfs”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/run/lock”} 0
{device=“tmpfs”, fstype=“tmpfs”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/run/user/1000”} 0

根据上面的结果,可以根据预测结果增加告警功能,alert_rules.yml可以新增如下例子:

- alert: "磁盘满了"
expr: predict_linear(node_filesystem_free_bytes[1h], 3600*24) == 0 
for: 1m
labels:
serveriry: critical

2.3、http请求总数

2.3.1、查询HTTP请求总数

此指标为所有类型的HTTP请求的总数

prometheus_http_requests_total

返回结果如下表所示:

prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/alerts”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/api/v1/rules”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 4
prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 1
2.3.2、指定查询

指定资源实例(被监控资源,这里是主机)、访问路径

prometheus_http_requests_total{instance="localhost:9090",handler="/api/v1/metadata"}

返回结果如下所示:

prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 2
2.3.3、条件查询

查询状态码为200的请求总数

prometheus_http_requests_total{code="200"}

返回结果如下所示:

prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/alerts”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 9
prometheus_http_requests_total{code=“200”, handler=“/api/v1/rules”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/favicon.ico”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 89
2.3.4、区间查询

区间即一定范围内查询,比如查询2分钟内的请求总数,结果太多就不展示了。

prometheus_http_requests_total[2m]
2.3.5、统计请求总数

统计所有HTTP请求的总数

sum(prometheus_http_requests_total)

返回结果

{} 132
2.3.6、正则表达式

Prometheus中的正则表达式基于RE2语法

查询与特定模式匹配的时序数据

查询访问路径以series结尾的数据,也可以写成"^.*(series)$",波浪号是开启正则匹配

prometheus_http_requests_total{handler=~".*series"}

返回结果

prometheus_http_requests_total{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”} 18

三、PromQL数据类型

暂时理解不了也没关系,随着学习的深入会逐步掌握。

3.1、瞬时向量(instant vector)

一组时间序列,每个时间序列包含单个样本,它们共享一个时间戳,即表达式的返回值中只会包含该时间序列中最新的一个样本值。

假设我们采集了2022-04-22 16:00这个时间点的数据,比如CPU、内存、磁盘等各种指标的数据,它们的时间戳都是2022-04-22 16:00,因为都是同一瞬间采集的,即仅显示最新的一次的数据。

3.2、区间向量(range vector)

一组时间序列,每个时间序列包含target一段时间范围内的样本数据。

很好理解,还是上面的例子,但是我采集的时间范围是从2022-04-22 16:00到2022-04-22 16:05分,频率是1/m,那么就是这组时间序列包含5段时间序列。

3.3、标量(scalar)

浮点型的数据值,没有时序,可以通过内置函数scalar()将单个瞬时向量转换为标量。

3.4、字符串(string)

一串简单的字符串值。可以用单引号(‘’)、双引号(“”)、反引号(``)来指定。

Prometheus基于Go语言研发,因此其与Go语言有着类似的转义规则,比如在单引号(‘’)或双引号(“”)中,可以使用反斜杠()来表示转义序列,如下表所示:

转义符 含义 备注
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表
\v 反斜杠
\ 反斜杠

但不同的是,Prometheus中反引号(``)并不会对换行符进行转义

四、时间序列

4.1、简述

与关系型数据如MySQLpostgrepSQLOracle等不一样的是,时序数据库是按照一定的时间间隔生成一个个数据样本。

以时间轴为横轴、序列为纵轴,由此形成一个矩阵。

每条时间序列(time series)是通过指标名称(metrics name)和一组标签集(label set)来命名的。如果时间戳一致,但指标名称或标签集不一致,那么时间数列也是不一样的,如下表所示:

这个很好理解,打个比方,我咨询各地卫健委的防疫政策,虽然都是今天的最新消息,但是不同的城市政策肯定是有所区别的,这就是不同的样本,即不同的序列。

指标
prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 213
prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 1

4.2、序列构成

每一个序列,即每一个样本数据,主要有三部分组成:

  • 指标(metrics),包括指标名称与标签集名称,如上prometheus_http_requests_total{handler=“/”, instance=“localhost:9090”, job=“prometheus”}
  • 时间戳(timestamp),这个值默认精确到毫秒。
  • 样本值(value),默认使用浮点型(float64)数据展示。

4.3、指标

时间序列的指标可基于bigtable(超级表,Google发布的论文)设计为k/v型存储的方式

还是以上面4.1小节的表格为例子,prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”}作为key,1作为这个key所对应的值。

在这个key中,也有三部分组成:

  • 指标名称(metrics name),即上述的prometheus_http_requests_total部分。
  • 标签集(label set),即上述的code=“302”, handler=“/”, instance=“localhost:9090”, job="prometheus"部分。
  • 时间戳(timestamp),默认是隐藏不展示,如@1651719501234

Prometheus中所有数值都是64bit,即每条时间序列里记录的时间戳和样本值全是64bit的。

由上可知,这样的k/v型结构,可以很灵活地查询想要的结果,我们完全可以通过指标名称、标签、时间戳来进行联合查询,且标签由上可知可以定义多个,这就是PromQL多条件查询的基础。

注意:

1、不指定标签进行查询时,如node_cpu_seconds_total相当于node_cpu_seconds_total{},表达式会返回指标名称为node_cpu_seconds_total的所有时间序列,花括号中可以有一组或多组标签来更进一步过滤时间序列。

2、PromQL必须至少包含一个指标名称,或一个不会匹配到空值的标签过滤器,如

五、PromQL选择器

因为监控系统是要时刻监控着各种各样的设备,那么我们肯定会有缩小查询范围的需求,比如希望查询某台服务器的某个指标。这时候需要使用标签限制功能,这种功能是通过选择器(selector)来完成的。

node_cpu_seconds_total{cpu=“0”, instance=“localhost:9100”, job=“prometheus”, mode=“idle”} 4727.21

上面表格中的PromQL即为一个瞬时向量选择器,cpu="0"等标签即该选择器的匹配器

5.1、匹配器

匹配器作用于标签,标签匹配器可以对时间序列进行过滤,prometheus支持完全匹配和正则匹配两种模式。

5.1.1、相等匹配(=)

相等匹配器(equality matcher),用于选择给定的字符串完全相等的标签。

如果给定的标签不存在或者为空,那么该标签会被忽略掉。

node_cpu_seconds_total{cpu=“0”, instance=“localhost:9100”, job=“prometheus”, mode=“idle”} 4727.21
5.1.2、不相等匹配器(!=)

与上述的相等匹配器正好相反,指的是选择给定的字符串完全不一样的的标签,比如下面的即为但凡CPU不是第0颗的,都查询展示出来。

node_cpu_seconds_total
5.1.3、正则匹配器(=~)

正则表达式匹配器(regular expression matcher),用于选择给定的字符串进行正则运算后的结果相匹配的标签。Prometheus的正则运算采用RE2引擎,是强指定的,比如表达式a那就只会匹配到字符串a,而不会匹配到ab或者abc。如果不想使用强指定,可以加上".*"之类。

  • “.*”,点星表示任意一个字符,可以为空。
  • “.+”,点加表示至少一个字符,不能为空。

如下所示,表示所有http请求中但凡是访问路径以class开头的,都匹配到:

prometheus_http_requests_total

当然,也可以像相等匹配一样使用反向的正则相反匹配器。

5.2、瞬时向量选择器

瞬时向量选择器用于返回指定时间戳之前采集到的最新样本,因为Prometheus考虑到会有大规模的采集可能会导致服务器压力过大,所以并不是在同一时间采集所有资源的,会错开一点点比如几毫秒去分批次采集数据,因为在监控系统中这并不重要,重要的是能采集到数据监控到设备有无问题并以此做分析就够了。

prometheus_http_requests_total

5.3、区间向量选择器

关于瞬时向量选择器和区间向量选择器可以参照3.1小节、3.2小节的阐述。

区间向量选择器是在选择器(PromQL语句)末尾处添加用中括号包含着的时间范围,通过自定义的时间范围来筛选指定范围的序列。如下所示,代表提取最近3分钟所有http请求,prometheus_http_requests_total本来输出的是瞬时向量,[3m]将其转变为区间变量,因为是范围性的。

prometheus_http_requests_total[3m]

PromQL中时间范围只能是整数,有以下几种表达方式:

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

上面说了,时间范围必须是整数,假如你想取1h30m内的数据,是不能直接写[1h30m]的,只能将其写为[90m][5400s]

5.4、偏移量修改器

偏移量修改器可以令瞬时和区间向量选择器发生一定程度的偏移,允许其 查询时在每个选择器的基础上将其往前推移指定时间。

瞬时向量选择器、区间向量选择器都可以获取当前时间基准下的样本数据,但是我们要获取当前时间往前推五分钟的那一瞬的数据,比如现在是10:32分,我想获取10:30的数据,也就是往前推2分钟,那就需要使用偏移向量了,如下所示:

process_cpu_seconds_total{} offset 2m

偏移向量修改器通过调整时间来看到指定时间的样本数据,但是一般只用于调试,想看历史数据变化趋势可以直接在Grafana中看到。

注意,偏移向量选择器必须紧跟在选择器{}的后面。

六、Prometheus指标类型

Prometheus有4大指标类型(metrics type):计数器(counter)、仪表盘(gauge)、直方图(histogram)、摘要(summary)。

6.1、计数器(Counter)

注意:PromQL要先执行rate()再执行sum(),否则结果有误。

长尾效应,即大部分都是某个范围内,只有少部分不在此范围,而这少部分因为数值较大而影响计算结果。比如99%的http请求响应时间都是1ms,只有极个别请求响应时长10s,那么无论统计何种结果都是被这极个别例子大大拖了后腿。

为此Prometheus提供了irate函数,其相比rate,是通过区间向量中最后两个样本数据来计算区间向量的增长速率,可以有效避免“长尾效应”。但更高的灵敏度在分析长期趋势或告警规则时,反而容易造成干扰。因此,在长期趋势分析和告警中还是推荐使用rate函数。

计数器代表样本数据中递增的指标,在未重置(服务器重启、应用重启)的情况下只增不减。比如http请求总数、错误次数、已完成任务数等,这些指标数据都是不断增加的。

下面通过几个例子来展示样本数据的变化情况:

6.1.1、统计增长速率

以下为统计1分钟内访问metrics路径的http请求的增长速率,值我仅截取了两位小数点。

rate(prometheus_http_requests_total{handler=“/metrics”, instance=“localhost:9090”}[1m])814 0.06
6.1.2、统计增长量

以下为统计1分钟内访问metrics路径的http请求的增长总数

increase(prometheus_http_requests_total{handler=“/metrics”, instance=“localhost:9090”}[1m]) 4

以下为统计5分钟内访问metrics路径的http请求的每分钟平均增长数

increase(prometheus_http_requests_total{code=“200”, handler=“/metrics”}[5m]) / 5 4
6.1.3、统计排行榜

以下为统计当前http请求总访问量排在前10http地址

topk(10, prometheus_http_requests_total)
6.1.4、统计总访问量

以下为统计当前所有http请求总数

sum(prometheus_http_requests_total) 814

6.2、仪表盘(Gauge)

计数器顾名思义是递增,而仪表盘呢?想想你开车的方向盘、时速表、手表表盘等仪表盘,是不是时刻在变化着?仪表盘指标类型就代表着样本数据中可以任意变更的指标,即可增可减。

仪表盘通常用于温度、CPU空闲率、可用内存、并发数等时刻变化的指标数据,我们往往使用它们来进行求和、取平均值、最小值、最大值等。

6.2.1、统计磁盘大小

如下所示为对每一台机器上的总磁盘大小进行求和统计

该指标为node_exporter提供,without可以让sum指令根据相同的标签进行求和,但是排除without指定的标签。

sum without(device, fstype, mountpoint)(node_filesystem_size_bytes / (102410241024)) 1151
6.2.2、统计最大磁盘(分区)

以下为统计每台机器上最大的磁盘,只需要将6.2.1的sum换成max即可。

max without(device, fstype, mountpoint)(node_filesystem_size_bytes / (102410241024)) 476
6.2.3、预测磁盘空间

predict_linear函数能基于一段时间内的样本数据,预测未来一段时间内的趋势。

Prometheus中布尔值0代表否,1代表是。

以下为:根据1小时内的磁盘剩余空间的样本数据,预测未来1天内磁盘空间是否会满:

predict_linear(node_filesystem_free_bytes[1h], 24*3600) < bool 0
6.2.4、统计磁盘变化

delta函数能够获取样本数据在一段时间内的变化情况,即增减的情况。

以下为:统计磁盘空间在过去2小时的变化情况:

delta(node_filesystem_free_bytes[1h])

6.3、直方图(Histogram)

直方图常常用作于比较,它能够很好地展示几种事物的差别。

Prometheus中,histogram可以在一段时间范围内的对样本数据进行采样,并将其计入可配置的存储桶(Bucket)中,后续可通过指定区间进行筛选样本,也可以统计样本总数,最后一般将其展示为直方图。因此,直方图多用于性能分析等领域的分析观测。

在被监控设备的度量指标中(比如本机的:http://localhost:9090/metrics),可以看到Prometheus自带的一些histogram信息。

6.3.1、样本分布数量

样本的值分布在bucket中的数量,命名规则为指标名称_bucket{le=“上边界”} value

value的值表示该指标的值小于或等于上边界所有样本数量

以下为:当前为止http请求中关于访问根目录响应时间的histogram信息

# le表示响应时间,由上往下依次递增排序,花括号外面的数字为请求总数
# 如第1条表示:访问根目录的请求中响应时间未超过0.1s的请求有1次。
# 这段histogram信息表示所有访问根目录的http请求总共为1次,且响应时间都没有超过0.1秒。
prometheus_http_request_duration_seconds_bucket{handler="/",le="0.1"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="0.2"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="0.4"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="1"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="3"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="8"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="20"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="60"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="120"} 1
prometheus_http_request_duration_seconds_bucket{handler="/",le="+Inf"} 1
prometheus_http_request_duration_seconds_sum{handler="/"} 0.000509394
prometheus_http_request_duration_seconds_count{handler="/"} 1
6.3.2、样本值总和

命名为:标签名_sum

上述的案例中,表示当前为止访问根目录的总共1次的http请求总响应时间为0.000509394秒。

prometheus_http_request_duration_seconds_sum{handler="/"} 0.000509394
6.3.3、样本总数

命名为:标签名_count,其值与le="+Inf"相同

6.3.1的案例中,表示当前为止访问根目录的http请求总数为1次:

prometheus_http_request_duration_seconds_count{handler="/"} 1

直方图还有分位数的概念,但本人不甚理解,因此未曾描述,有兴趣可自行查阅。

6.4、摘要(Summary)

与直方图(histogram)类似,摘要用于表示一段时间内的数据采样的结果(通常是请求持续时间或响应大小等),但它直接存储了分位数,而非通过区间来计算(histogram的分位数通过histogram_quantile(& float, b instant-vector)函数计算得到)。因此,对于分位数的计算,摘要在通过PromQL进行 查询时有更好的性能表现,而histogram则会消耗更多资源;反之,对于客户端而言,histogram消耗的资源更少。在选择两种方式时,应根据实际场景选择。

metrics页面可以看到Prometheus自带的一些摘要信息,如下所示:

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.7933e-05
go_gc_duration_seconds{quantile="0.25"} 6.7765e-05
go_gc_duration_seconds{quantile="0.5"} 8.3686e-05
go_gc_duration_seconds{quantile="0.75"} 0.000111896
go_gc_duration_seconds{quantile="1"} 0.000829147
go_gc_duration_seconds_sum 0.003694654
go_gc_duration_seconds_count 25

在上述的例子中,可以看到基于Go语言编写的Prometheus的编译总数是25次,耗时约3毫秒,其中中位数{quantile="0.5"}的计算耗时为8.3686e-05秒,代表25次计算中有50%的次数是小于8.3686e-05秒的。

类似直方图,摘要类型的样本数据也会提供三种指标:

6.4.1、样本值分数情况

样本值的分位数分布情况:指标名称{quantile=“分位数”},其属于计数器类型。

go_gc_duration_seconds{quantile="0.25"} 3.7933e-05
6.4.2、样本值总和

所有样本值的大小总和:指标名称_sum,属于计数器类型。

go_gc_duration_seconds_sum 0.003694654
6.4.3、样本总数

样本总数:指标名称_count,属于计算器类型。

go_gc_duration_seconds_count 25

七、聚合操作

因为Prometheus经常被用作监控系统,这往往需要监测大量设备与应用,逐个筛选各资源的指标是不太现实的。聚合操作(aggregation operator)允许用户在一个或多个资源之间对指标进行聚合计算,它可以对瞬时向量的样本数据进行聚合,形成新的较少样本数据的时间序列。因此,它仅对瞬时向量起作用,输出的也是瞬时向量,如下所示:

# 统计所有http请求的总数
sum(prometheus_http_requests_total)
# 按模式统计CPU平均使用情况
avg(node_cpu_seconds_total) by (mode)
# 查询各资源实例的CPU使用率
(sum(irate(node_cpu_seconds_total{mode!="idle"}[5m])) / sum(irate(node_cpu_seconds_total[5m]))) * 100

以下开始介绍聚合操作的十几种都有哪些

7.1、without

without用于从计算结果中移除某些标签,保留其它标签,在6.2.1小节有使用过。

without还可以删除指定的指标名称,它是基于原始指标聚合计算后得到的新指标数据,并非原始指标。其实就跟很多文本编辑器原理类似:当用户更改内容时,实际上更改的是内存上的缓存数据,并未直接更改到文件本身。如下所示:

sum without()(prometheus_http_requests_total)

返回结果如下所示,可以很明显看到前面的prometheus_http_requests_total被删除了

{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 1
{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 2
{code=“200”, handler=“/api/v1/labels”, instance=“localhost:9090”, job=“prometheus”} 3
{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 9
{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 16
{code=“200”, handler=“/api/v1/query_exemplars”, instance=“localhost:9090”, job=“prometheus”} 1
{code=“200”, handler=“/api/v1/query_range”, instance=“localhost:9090”, job=“prometheus”} 26
{code=“200”, handler=“/api/v1/rules”, instance=“localhost:9090”, job=“prometheus”} 1
{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”} 4
{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 1
{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 243
{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 1

7.2、by

bywithout正好相反,by仅在结果中保留指定的标签。因此,二者不可同时使用。

sum by(instance)(prometheus_http_requests_total)

7.3、sum

sum是将分组中的所有值相加得出结果并返回,之前已经使用过很多次了。一定要记得在执行sum前执行rate

sum(prometheus_http_requests_total)

7.4、min

min,计算并返回分组内的最小值。但默认仅返回最小值,并不附带其所属标签和序列,因此需要自行使用withoutby进行过滤,以此得以展示最小值所属的标签和序列。

以下为:查询各个资源实例中磁盘空间最小的有多小

min by(instance)(node_filesystem_size_bytes)

7.5、max

min相反,max返回的是分组内的最大值

max by(instance)(node_filesystem_size_bytes)

7.6、avg

不要对平均值再取平均值

avg,平均值,即返回分组内的时间序列的平均值。

以下为:计算最近5分钟内各主机的CPU平均使用情况

avg without(cpu)(rate(node_cpu_seconds_total[5m]))

7.7、stdvar

stdvar (standard variance)在数学中称为方差,具有统计学意义。方差用于衡量随机变量或一组数据的离散程度,也就是各阶段等级的数据分布情况。在样本容量相同的情况下,数据分布得越分散(即数据在平均数附近波动大),各个数据与平均值的差的平方和也越大,此时方差就越大;反之,数据分布越集中,即各数据与平均值的差的平方和越小,此时方差就越小。方差越大,代表数据波动就越大;越差越小,代表数据的波动越小。

概率论中,方差用来度量随机变量和平均值之间的偏离程度。统计学中的方差(样本方差)是每个样本值与全体样本值的平均数之间的平方值的平均数。

SN2=1Ni=1N(χiχ)2S^2_N=\sqrt{\frac{1}{N}\sum_{i=1}^{N}{(\chi_i-\frac{}{\chi})}^2}

7.8、stddev

其实这些数学理论我真的看不懂,先记下来吧,希望看到这段话的你能懂。

stddev(standard deviation)在数学中称为标准差,又称均方差,是离均差平方的算术平均数的平方根。

在概率统计中,常常使用标准差来统计分布程度。标准差是方差的算术平方根,它能反映一个数据集的离散程度。平均数相同的两组数据,标准差未必相同。

σ=1Ni=1N(χiχ)2\sigma=\sqrt{\frac{1}{N}\sum_{i=1}^{N}{(\chi_i-\frac{}{\chi})}^2}

基于上述公式,可得到如下结论:

  1. 由于方差是数据的平方,与监测值本身相差较大,人们难以形成直观的判断,所以常常对方差进行开方,以此换算得到标准差。
  2. 标准差与方差不同的是,标准差与变量的计算单位相同,比方差更直观,因此很多时候我们使用的是标准差。
  3. 在样本数据大致符合正态分布的情况下,比标准差具有方便估算的特性;66.7%的数据点落在平均值前后1个标准差的范围内,95%的数据点括在平均值前后2个标准差范围内,而99%的数据点将会落在平均值前后3个标准差的范围内。

7.9、count

sum是对分组的值进行求和,count是对分组的时间序列条数进行求和。打个比方,sum就是求中国14亿人口的生产总值加起来有多少,而count是计算中国有多少亿人口。

count聚合操作是对分组中的时间序列的数目来进行求和,如下所示:

count without(handler)(prometheus_http_requests_total) 8

以下表格可以看出,时间序列确实是8条:

prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/labels”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 436
prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”}

count还可以用在一些场景,比如获取唯一的标签值:

count without(mode)(node_cpu_seconds_total)

因为CPU核心数是不会变的,剔除状态后,返回结果就是如下所示:

{cpu=“0”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“1”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“10”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“11”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“2”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“3”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“4”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“5”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“6”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“7”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“8”, instance=“localhost:9100”, job=“prometheus”} 8
{cpu=“9”, instance=“localhost:9100”, job=“prometheus”} 8

7.10、count_values

7.10.1、简述

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

count_values和后面要介绍的topk一样,它接受一个参数后返回的是分组内的多个时间序列。它构建一个频率的直方图(frequency histogram),返回的结果会将参数里的值作为一个新的标签。看不懂?看下面的例子就懂了!

以下为:统计时间序列http_request_total出现的次数

// count是自定义的标签,最好是见名知意,比如我是统计各值的出现的频率,用count就可以。
count_values("count",prometheus_http_requests_total)

返回结果如下所示:

{count=“1”} 4
{count=“3”} 1
{count=“5”} 1
{count=“14”} 1
{count=“610”} 1

它统计的是什么东西出现的次数呢?我们还原一下就知道了!执行一下上面的例子中的源PromQL:

prometheus_http_requests_total

如下所示,我们执行后可以看到访问次数结果是1的时间序列有4条,值为3的有1条,51条,13609因为和上面执行有时间间隔所以结果累加了1,累加后其实就是上面的14610。这样就跟上面的例子完全对上了!

prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/labels”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 5
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 13
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 609
prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 1
7.10.2、使用场景

实践中,count_values常用于统计版本号,比如计算运行每个构建版本的文件的数量。还可以统计机器有多少个磁盘,如下所示:

// 统计每台机器有多少块硬盘
count_values by(instance)("disk",count without(device)(node_disk_io_now))

7.11、bottomk

同样需要注意避免带有NaN值的序列

bottomk用于对样本值进行排序,然后返回排在后N位的样本值。比如,要获取HTTP请求数排序后3位的时序样本数据,可如下使用:

bottomk(3, prometheus_http_requests_total)

7.12、topk

topkbottmk正好相反,是返回排在前N位的序列。

案例1

获取HTTP请求数中排前三位的请求

topk(3, prometheus_http_requests_total)
案例2

统计空闲率最高的是哪三颗CPU

topk(3, sum by(cpu)(rate(node_cpu_seconds_total{mode="idle"}[5m])))

返回结果:

{cpu=“2”} 0.988985926266407
{cpu=“4”} 0.9887403113695136
{cpu=“9”} 0.9884245207877917
案例3

统计所有主机的内存空闲比例最高的主机前两位:

topk(3, (node_memory_MemFree_bytes / node_memory_MemTotal_bytes) * 100)

返回结果

{instance=“localhost:9100”, job=“prometheus”} 84.83566652352617

7.13、quantity

分位数(quantile)用于计算当前样本值的分布情况,函数公式如下所示:

quantile(φ,express)0φ1quantile(\varphi, express) \\ 0\leq\varphi\leq1

统计最近5分钟内,每台机器上90%的CPU在系统模式下每秒至少花费多少资源

quantile without(cpu)(0.9, rate(node_cpu_seconds_total{mode="system"}[5m]))

八、二元操作符

二元操作符(binary operator)表示接受两个操作数的运算符,可以对瞬时向量进行更为复杂的运算,这也是Prometheus比其它基于指标的监控系统更加强大的地方。

Prometheus的二元操作符分为3种类型,分别是算术运算符、集合/逻辑运算符、比较运算符。

8.1、算术运算符

在使用运算符时,尽量用括号将同一个公式括起来,避免因优先级而误计算。

Prometheus系统支持:

  • 二元算术符:加(+)、减(-)、乘(*)、除(/)、模(%)、幂(^)
  • 二元运算操作符:标量/标量(scalar/scalar)、向量/标量(vector/scalar)、向量/向量(vector/vector
8.1.1、减(-)除(/)乘(*)

计算当前所有主机节点的内存使用率

((node_memory_MemTotal_bytes - node_memory_MemFree_bytes) / node_memory_MemTotal_bytes) * 100
8.1.2、内存常用指标

主机上内存总大小

node_memory_MemTotal_bytes

主机上空闲内存大小

node_memory_MemFree_bytes

主机上内存缓冲区占用大小

node_memory_Buffers_bytes

主机上内存缓存区占用大小

node_memory_Cached_bytes

8.2、集合/逻辑运算符

集合/逻辑运算符(logical/set binary operator),仅用于瞬时向量之间。目前Prometheus支持以下集合运算符(无not运算符,内置absent等于not):andorunless

8.2.1、and

vector1 and vector2,会产生一个由vector1的元素组成的新的向量,该集合中的元素是vector1vector2中同时存在的。

简而言之,vector1中有ABCvector2中有BCD,那么vector1 and vector2的结果就是BC

# 这个表达式看似很长,实际展示出来的只有and左边的结果。
((1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}))*100 >= 40) and (predict_linear(node_filesystem_avail_bytes[1h], 3600*24) < bool 0)
8,2,2、or

vector1 or vector2,会产生一个由vector1vector2组成的新向量,该集合中的元素包含vector1vector2所有元素。

比如,vector1中有ABCvector2中有B,那么vector1 and vector2的结果就是ABC

# 把8.2.1的and换成or后,结果就差别很大了。
((1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}))*100 >= 40) and (predict_linear(node_filesystem_avail_bytes[1h], 3600*24) < bool 0)
8.2.3、unless

vector1 unless vector2,会产生一个新的集合,该集合会先取vector1中所有元素,然后排除掉所有在vector2中存在的元素。

比如,vector1中有ABCvector2中有AB,那么新集合就只有C,如下所示:

先算一下进程的平均CPU使用量

rate(process_cpu_seconds_total[5m])

结果如下所示

{instance=“localhost:9090”, job=“prometheus”} 0.0008771929824561403
{instance=“localhost:9100”, job=“prometheus”} 0.008736842105263164

统计常驻内存的且大于100MB的进程

process_resident_memory_bytes > 100*1024*1024
process_resident_memory_bytes{instance=“localhost:9100”, job=“prometheus”} 18501632

unless计算

rate(process_cpu_seconds_total[5m]) unless (process_resident_memory_bytes > 100*1024*1024)

结果如下

{instance=“localhost:9100”, job=“prometheus”} 0.00884210526315788

8.3、比较运算符

Prometheus支持的比较运算符主要包括:==!=><>=<=

统计当前打开文件描述符的作业进程的数量

sum without(instance)(process_open_fds) > 10

结果如下所示:

{job=“prometheus”} 45

通过布尔修饰符(bool)改变运算行为后,不再像上面那样返回时间序列的值,而是直接返回比较的结果0(false,否)或者1(true,是),如下所示:

45大于10,所以比较结果是1。

sum without(instance)(process_open_fds) > bool 10

结果:

{job=“prometheus”} 1

8.4、运算符优先级

二元运算符优先级从高到低依次为:

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

具有同等优先级的运算符从左开始计算,比如5*6/8相当于(5*6)/8,也就是先计算5乘以6,再将结果除以8。但是幂(^)是个例外,它是从右边开始计算。比如3^3^3相当于3^(3^3)。因此,为避免混淆,建议使用运算符时用括号加以区分。

九、向量匹配

在标量和瞬时向量之间使用运算符可以满足很多需求,但是瞬时向量之间使用运算符计算才是王道。向量之间进行运算操作时基于默认的匹配规则:依次找到与左边向量元素匹配(标签完全一致)右边向量元素,如果没找到匹配的元素,直接丢弃。Prometheus中的向量匹配主要分为一对一、多对一、一对多、多对多这四种。

9.1、一对一匹配

一对一匹配会从操作符两边的表达式获取的瞬时向量依次比较,并找到唯一匹配的样本值。

在操作符两边的表达式标签不一致的情况下,可以使用on(label list)或者ignoring(label list)来修改标签匹配行为、ignoring可以在匹配时忽略指定标签,on相反,匹配时仅匹配某些标签。

9.2、一对多与多对一

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

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

在限定匹配标签后,向量中的元素可能匹配到多条另个方向中的向量的元素。因此,需要使用group修饰符group left指定哪个左向量具有更好的基数。

group left的特性可以用在很多场景,比如比较集群上主、备等副本之间的差异,它还可以将target上的指标信息添加到其它target上。

group leftgroup right工作方式一样,但PromQL内部很多表达式执行的是最左原则,因此也建议尽量使用group left保持向左的风格。

9.3、多对多

8.2小节中出现的3种逻辑运算符:andorunless是唯有的可以进行多对多的运算符。它们和算术运算符、比较运算符不同的是,这3种逻辑运算符为代表的多对多匹配,并没有数学计算,只有多对多的分组和样本。

世间微尘里 独爱茶酒中