05. Prometheus - PromQL
PromQL
PromQL 是 Prometheus 内置的数据查询语言,其提供对时间序列数据丰富的查询,聚合以及逻辑运算能力的支持。并被广泛应用在 Prometheus 的日常数据查询、可视化、告警处理当中。
可以这么说,PromQL 是 Prometheus 所有应用场景的基础,理解和掌握 PromQL 是 Prometheus 入门的第一课。
如果把 Prometheus 和常见的关系型数据库 MySQL 对比着看。每个指标就相当于 MySQL 中的表,PromQL 就类似于 MySQL 中的 Select 语句,标签筛选就类似于 Select 中加入了 Where 条件限制。
标签筛选
用户可以直接通过指标名称获取到 exporter 采集到的样本数据:
prometheus_http_requests_total
如图所示:
这样采集的数据可能会包含多个拥有不同标签的指标,为了更精确的获取到想要的指标数据,就需要进行标签筛选:
prometheus_http_requests_total{handler=~"/api.*|/metrics", code="200"}
如图所示:
PromQL 支持两种对于标签的匹配模式:
- 完全匹配:= 和 !=
- 正则匹配:=~ 和 !~
PromQL 合法性
所有的 PromQL 表达式都必须至少包含一个指标名称,或一个不会匹配到空字符串的标签过滤器。
合法的表达式
# 只有指标名称
prometheus_http_requests_total
# 标签为空为指标
prometheus_http_requests_total{}
# 普通的标签筛选
prometheus_http_requests_total{code="200"}
# 正则的标签筛选
prometheus_http_requests_total{handler=~"/api.*|/metrics", code="200"}
# 只有标签的筛选
{code="200"}
# 使用名称的筛选
{__name__="prometheus_http_requests_total"}
不合法的表达式
# 都是能匹配到空,所以不合法
{job=~".*"}
{job=""}
瞬时向量和区间向量
通过 PromQL 查询到的数据可以分为两类:
- 瞬时向量:最新的值,常用于监控告警。
- 区间向量:某一时间段范围的值,常用于数据分析。
瞬时向量
如果 PromQL 表达式中如果没有使用 时间范围选择器 []
的,那么这个向量就是瞬时向量。
node_memory_MemFree_bytes
如图所示:
区间向量
node_memory_MemFree_bytes[1m]
如图所示:
显示画图:
特别说明:
- 由于采集间隔为 15s,所以获取 1 分钟的数据会返回 4 个值。
- 绘图只能使用瞬时向量,因为一般的绘图都会自带一个时间选择器,如果使用区间向量则时间范围重复,出现报错。
- 时间范围选择器支持 s(秒),m(分钟),h(小时),d(天),w(周),y(年)等单位,但是不能使用 1.5h,只能是 1h30m。
时间偏移
前面查询到的数据都是从当前时间作为截止时间获取的,有时候也需要获取历史某个时间的值,此时就需要使用到时间偏移。
# 获取 5 分钟前 1 分钟内的数据
node_memory_MemFree_bytes[1m] offset 5m
如图所示:
数学运算符 / 算术运算符
某些监控指标获取到的值有些时候是不好直接拿来使用的,需要进行一定的计算,于是便需要使用到算术运算符。
常见的数学运算符包含以下:
+
:加法-
:减法*
:乘法/
:除法%
:求余^
:幂运算
使用示例:
# 获取可用内存为多少 M
(node_memory_MemFree_bytes + node_memory_Cached_bytes + node_memory_Buffers_bytes) / 1024 / 1024
如图所示:
布尔运算符 / 比较运算符
在监控告警的时候,需要对采集到的值进行判断,此时就需要使用到布尔运算符。
常见的布尔运算符包含以下:
==
:相等!=
:不相等>
:大于<
:小于>=
:大于等于<=
:小于等于
使用实例:
# 获取内存使用率大于 10% 的数据
(1 - ((node_memory_MemFree_bytes + node_memory_Cached_bytes + node_memory_Buffers_bytes) / node_memory_MemTotal_bytes)) * 100 > 10
如图所示:
在某些场景下并不是为了筛选,而是想要获取布尔值(true 为 1,false 为 0
):
# 获取 bool 值
(1 - ((node_memory_MemFree_bytes + node_memory_Cached_bytes + node_memory_Buffers_bytes) / node_memory_MemTotal_bytes)) * 100 > bool 10
如图所示:
集合运算符
集合运算符更多的还是用于数据的筛选。
常见的集合运算符包含以下:
and
:并且or
:或者unless
:排除
使用示例:
# 多条件筛选,并列条件,取交集
prometheus_http_requests_total{code!="400"} and prometheus_http_requests_total{handler!~"/api.*"}
# 多条件筛选,并列条件,取并集
prometheus_http_requests_total{code="400"} or prometheus_http_requests_total{handler!~"/api.*"}
# 多条件筛选,从结果中排除
prometheus_http_requests_total unless prometheus_http_requests_total{code="200"}
如图所示:
操作符优先级
对于复杂的 PromQL 表达式一般都会包含多种运算符,同种运算符,不同运算符之间都是由执行优先级的,具体先后顺序如下:
^
*, /, %
+, -
==, !=, <=, <, >=, >
and, unless
or
内置函数 / absent(取布尔值)
以瞬时向量做参数,判断该瞬时向量是否有值:
- 如果有值,则返回空向量。
- 如果没有值,则返回不带标签的名称时间序列,值为 1。
使用示例:
# 有值返回空
absent(prometheus_http_requests_total)
# 没值返回 1
absent(prometheus_http_requests_totalxxx)
有效的:
无效的:
该方法可以用于判断给定的 PromQL 不存在。
内置函数 / abs(绝对值)
用于返回绝对值,将负数转换成正数。
abs(node_memory_MemFree_bytes - node_memory_MemTotal_bytes)
如图所示:
内置函数 / round(四舍五入取整)
返回结果四舍五入取整:
round((node_memory_MemFree_bytes / node_memory_MemTotal_bytes) * 100)
如图所示:
内置函数 / clamp_max(对比取值)
clamp_max 用于比较瞬时向量和传入的值,谁小返回谁。clamp_min 反之。
clamp_max(node_memory_MemFree_bytes / node_memory_MemTotal_bytes, 1)
如图所示:
内置函数 / label_join(新增标签)
新增标签,但值来源于原来的标签的值的组合。
label_join(prometheus_http_requests_total{handler="/metrics"}, "url", "==", "instance", "handler")
结果如图:
新标签名称第一个参数为分隔符,后面的就是各种需要组合的标签,每个组合的值中间都有一个分隔带。
内置函数 / label_replace(标签替换)
将筛选的标签值进行替换:
label_replace(prometheus_http_requests_total{code="200"}, "code", "$1", "instance", "(.*):.*")
如图所示:
内置函数 / predict_linear(预测)
该方法常用于监控告警中,用于预测未来发生故障的时间。
# 根据 10 分钟的磁盘空闲情况推测 12 小时后磁盘空闲情况
predict_linear(node_filesystem_free_bytes{mountpoint="/"}[10m], 12*3600) / 1024 / 1024 / 1024
如图所示:
内置函数 / rate(区间增长率)
通过区间向量计算平均增长速率,该函数返回结果不带度量指标。
# 以 5 分钟为基准计算请求的每秒增长率
rate(prometheus_http_requests_total{handler="/metrics"}[1m])
如图所示:
内置函数 / irate(瞬时增长率)
通 rate 类似,用于计算区间向量的增长率,但与 rate 不同的是他是用于计算瞬时向量的增长率,通过区间向量中最后两个样本数据来计算区间向量的增长速率。
irate(prometheus_http_requests_total{handler="/metrics"}[1m])
如图所示:
特别说明:
irate 只能用于绘制快速变化的计数器,在长期趋势分析或者告警中更推荐使用 rate 函数。因为使用 irate 函数时,速率的简短变化会重置 FOR 语句,形成的图形有很多波峰,难以阅读。
内置函数 / sort(升序排序)
将多组数据按照升序排序,相反则使用 sort_desc。
sort(prometheus_http_requests_total{handler=~"/api/.*"})
如图所示:
内置函数 / delta(计算差值)
用于计算一段时间的变化值。
# 计算过去 1 小时磁盘使用了多少 M
abs(delta(node_filesystem_free_bytes{mountpoint="/"}[1h]) / 1024 / 1024)
如图所示:
聚合查询
通过获取一个即时向量并聚合它的元素,从而得到一个新的瞬时向量。
常用的聚合函数包含以下:
sum
:求和min
:最小值max
:最大值avg
:平均值count
:元素个数count_values
:等于某值的元素个数bottomk
:最小的 k 个元素topk
:最大的 k 个元素
语法格式如下:
<聚合函数>([parameter,] <指标查询语句>) [without|by (<label list>)]
其中参数 without
⽤于从计算结果中移除列举的标签,by
则相反,结果向量中只保留列出的标签。
只有 count_values , quantile , topk , bottomk ⽀持参数 parameter。
聚合函数 / sum(求和)
使用实例:
# 计算所有请求数量
sum(prometheus_http_requests_total)
如图所示:
结合 by 使用:
# by 通过指定的标签分组
sum(prometheus_http_requests_total) by (code)
如图所示:
结合 without 使用:
# without 剔除指定的标签后分组
sum(prometheus_http_requests_total) without (handler)
如图所示:
聚合函数 / max、min(最大最小值)
使用示例:
# 最大值
max(prometheus_http_requests_total)
# 最小值
min(prometheus_http_requests_total)
如图所示:
聚合函数 / avg(平均值)
使用示例:
# 计算 CPU 平均信息
avg(node_cpu_seconds_total{instance="192.168.200.101:9100"}) by (mode)
如图所示:
聚合函数 / count(数量统计)
使用实例:
# 统计指标下时间序列的数量
count(prometheus_http_requests_total)
如图所示:
聚合函数 / count_values(值出现次数)
# 根据值进行分类统计并自定义标签,标签的值为指标的值
count_values("request_times", prometheus_http_requests_total)
如图所示:
聚合函数 / topk、bottomk(前后 k 个)
使用示例:
# 请求前 3
topk(3, prometheus_http_requests_total)
# 请求后 3
bottomk(3, prometheus_http_requests_total)
如图所示:
子查询
常用的子查询有以下四个:
avg_over_time
:指定间隔内所有样本数据的平均值。min_over_time
:指定间隔内所有样本数据的最小值。max_over_time
:指定间隔内所有样本数据的最大值。sum_over_time
:指定间隔内所有样本数据的总和。
使用示例:
avg_over_time(node_memory_MemFree_bytes[1d]) / 1024 / 1024 / 1024
min_over_time(node_memory_MemFree_bytes[1d]) / 1024 / 1024 / 1024
max_over_time(node_memory_MemFree_bytes[1d]) / 1024 / 1024 / 1024
sum_over_time(node_memory_MemFree_bytes[1d]) / 1024 / 1024 / 1024
综合查询示例
查看 CPU 平均使用率
# CPU 平均使用率
(1 - avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)) * 100
查看 CPU 负载
# 1 分钟
node_load1
# 5 分钟
node_load5
# 15 分钟
node_load15
计算 CPU 数量
count(node_cpu_seconds_total{mode="idle"}) by (instance)
查询负载是 CPU 值的两倍的数据用于告警
node_load1 > on (instance) count(node_cpu_seconds_total{mode="idle"}) by (instance) * 2
内存使用率
(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100
磁盘使用率
(1 - node_filesystem_free_bytes{mountpoint!~"/boot|/run.*"} / node_filesystem_size_bytes{mountpoint!~"/boot|/run.*"}) * 100
特别说明:df 看到的值比监控的值一般会大 1%,原因在于 Linux 本身会预留一部分给 root,避免磁盘写满 root 无法操作。而 exporter 获取到的不包含那部分。
磁盘空间预测
# 预测 12 小时后磁盘使用率
(1 - (predict_linear(node_filesystem_free_bytes{mountpoint="/"}[1h], 3600 * 12) / node_filesystem_size_bytes)) * 100
节点状态查询
up{job="node-exporter"}