PromQL
PromQL(Prometheus Query Language)是 Prometheus 的查询语言。要真正掌握 PromQL,不能把它当成“几个函数 + 几个聚合语法”来背,而要先理解它查询的对象是什么、时间序列如何组织、不同类型的表达式如何转换,然后再理解 rate()、increase()、sum by()、histogram_quantile() 这些常用操作。
Prometheus 官方把 PromQL 定义为一种用于选择、计算和聚合时间序列数据的函数式查询语言。PromQL 表达式可以返回瞬时向量、区间向量、标量等不同类型;Prometheus UI 的 Table 本质上是 instant query,而 Graph 通常是在一个时间范围内重复执行查询。(Prometheus)
1. 先理解 PromQL 查询的到底是什么
Prometheus 存储的核心对象不是传统数据库中的“表”,而是:
Time Series,时间序列。
例如 node-exporter 可能产生:
node_cpu_seconds_total{
instance="10.0.0.17:9100",
cpu="0",
mode="idle"
}
它实际上代表一条独立时间序列。
随着时间变化:
10:00:00 81231
10:00:15 81243
10:00:30 81254
10:00:45 81267
所以一条时间序列可以抽象为:
其中:
metric name
+
完整 label 集合
共同决定一条时间序列。
例如:
node_cpu_seconds_total{cpu="0",mode="idle"}
和:
node_cpu_seconds_total{cpu="1",mode="idle"}
不是同一条时间序列。
2. PromQL 的核心不是“查询指标”,而是操作向量
这是理解 PromQL 最重要的一步。
很多人刚学时认为:
node_cpu_seconds_total
就是“查一个指标”。
从 PromQL 的角度,更准确地说,它返回的是:
一组符合条件的时间序列,每条序列取当前时刻对应的一个样本。
也就是 Instant Vector。
PromQL 当前主要有四种表达式结果类型:Instant Vector、Range Vector、Scalar 和 String,其中 String 目前基本没有实际查询用途。(Prometheus)
3. Instant Vector:瞬时向量
最简单:
node_memory_MemAvailable_bytes
假设有三台服务器:
instance=A 8.2e9
instance=B 6.1e9
instance=C 12.4e9
返回的不是一个数字,而是:
[
A → 8.2GB
B → 6.1GB
C → 12.4GB
]
也就是:
当前时间点,每条时间序列各取一个值。
这就是 Instant Vector。
4. Label Selector:PromQL 最基础的筛选能力
假设:
node_cpu_seconds_total{
instance="server1",
cpu="0",
mode="idle"
}
可以直接:
node_cpu_seconds_total
查询所有相关时间序列。
如果只要:
mode=idle
可以:
node_cpu_seconds_total{mode="idle"}
只查某台机器:
node_cpu_seconds_total{
instance="10.0.0.17:9100"
}
同时匹配:
node_cpu_seconds_total{
instance="10.0.0.17:9100",
mode="idle"
}
这里条件之间是:
AND
5. Label Matcher
PromQL 支持四种基本 label 匹配方式。
| 操作符 | 含义 |
|---|---|
= | 等于 |
!= | 不等于 |
=~ | 正则匹配 |
!~ | 正则不匹配 |
例如:
node_cpu_seconds_total{mode="idle"}
正则:
node_cpu_seconds_total{
mode=~"idle|iowait"
}
表示:
mode=idle
OR
mode=iowait
排除:
node_cpu_seconds_total{
mode!="idle"
}
对于 Kubernetes,正则特别常用。
例如:
container_cpu_usage_seconds_total{
namespace=~"prod|staging"
}
6. Range Vector:区间向量
如果写:
node_cpu_seconds_total[5m]
意义发生了变化。
之前:
node_cpu_seconds_total
是:
当前一个点
现在:
node_cpu_seconds_total[5m]
是:
过去5分钟所有数据点
例如:
server1:
10:00:00 100
10:00:15 102
10:00:30 104
...
10:05:00 125
因此:
Instant Vector
可以理解成:
每条时间序列一个样本
而:
Range Vector
是:
每条时间序列一组连续样本
Prometheus 官方也明确区分 instant vector 和 range vector;很多时间相关函数要求输入 range vector。(Prometheus)
7. 为什么 rate() 必须使用 [5m]
常见写法为:
rate(
node_cpu_seconds_total[5m]
)
原因就在这里。
node_cpu_seconds_total 是 Counter。
假设:
10:00 100
10:01 110
10:02 120
单看:
120
没有什么意义。
真正需要判断:
最近这段时间增长多快?
因此需要:
过去5分钟数据
即:
node_cpu_seconds_total[5m]
然后 rate() 根据这段数据计算:
8. Counter 和 Gauge 是 PromQL 最重要的指标语义
在使用函数之前一定要知道指标类型。
8.1. Counter
Counter:
只能增长,正常情况下不会下降,除非进程重启/reset。
例如:
http_requests_total
node_cpu_seconds_total
container_network_receive_bytes_total
数据:
100
105
110
120
135
这种指标最常搭配:
rate()
increase()
8.2. Gauge
Gauge:
可以任意升高或降低。
例如:
temperature
memory_used
queue_length
gpu_utilization
可能:
50
70
63
85
40
Gauge 通常直接查询,或者使用:
avg_over_time()
max_over_time()
min_over_time()
Prometheus 当前文档特别提醒:对 Gauge 使用 rate() 通常没有意义,而 Counter 才是 rate() 的主要使用对象。(Prometheus)
9. rate():Prometheus 最重要的函数之一
例如:
rate(
http_requests_total[5m]
)
假设:
10:00 1000
10:05 1600
那么大约:
结果:
2 request/s
注意:
rate() 得到的是:
平均每秒增长速度。
不是过去五分钟增加多少。
10. increase()
如果想知道:
过去五分钟总共增加了多少?
使用:
increase(
http_requests_total[5m]
)
例如:
1000 → 1600
结果大约:
600
可以简单理解:
11. irate() 和 rate() 的区别
还有:
irate(
http_requests_total[5m]
)
rate() 通常根据整个区间的数据估算平均增长速度。
irate() 主要根据最后两个数据点估算瞬时增长速度。
因此:
rate()
更稳定。
irate()
更敏感。
例如请求流量突然暴涨:
100
100
100
100
1000
irate() 会快速反映最后一段变化。
但告警通常更倾向 rate(),因为 irate() 容易受到瞬时抖动影响。
12. Aggregation Operator:聚合是 PromQL 第二个核心能力
假设:
http_requests_total{
instance="server1",
method="GET"
}
http_requests_total{
instance="server1",
method="POST"
}
http_requests_total{
instance="server2",
method="GET"
}
如果:
sum(
rate(http_requests_total[5m])
)
就是:
所有时间序列加起来。
PromQL 支持 sum、avg、min、max、count、topk 等聚合操作。(Prometheus)
13. sum by():生产环境最常用的语法之一
例如:
sum by (instance) (
rate(http_requests_total[5m])
)
表示:
按 instance 分组后求和。
假设:
server1 GET 50 req/s
server1 POST 20 req/s
server2 GET 80 req/s
server2 POST 30 req/s
结果:
server1 70 req/s
server2 110 req/s
这里真正发生的是:
原始 label:
instance
method
status
pod
...
↓
按照 instance 分组
↓
method/status 等维度被聚合掉
14. without():反向聚合
还有一种:
sum without (cpu, mode) (
rate(node_cpu_seconds_total[5m])
)
by() 表示:
保留哪些 label。
without() 表示:
去掉哪些 label。
理解 label 在聚合前后的变化,非常重要,因为 PromQL 最容易出现的问题之一就是:
聚合后 label 被消掉了。
15. 一个完整 CPU 使用率 PromQL
下面拆解一个常用表达式:
100 -
(
avg by (instance) (
rate(
node_cpu_seconds_total{mode="idle"}[5m]
)
) * 100
)
一步一步分析。
首先:
node_cpu_seconds_total{mode="idle"}
得到:
每颗 CPU 的累计 idle 时间。
然后:
rate(
node_cpu_seconds_total{mode="idle"}[5m]
)
得到:
最近 5 分钟每颗 CPU 每秒处于 idle 状态的比例。
例如:
CPU0 = 0.2
CPU1 = 0.3
CPU2 = 0.1
CPU3 = 0.2
然后:
avg by(instance)(...)
得到整个节点平均 idle:
也就是:
20% idle
最终:
得到:
80%
这就是 PromQL 最典型的思想:
原始 Counter → Range Vector → rate → 聚合 → 数学运算 → 业务指标。
16. Binary Operator:PromQL 不只是聚合
PromQL 支持:
+
-
*
/
%
^
以及:
==
!=
>
<
>=
<=
等操作。(Prometheus)
例如内存使用率:
(
node_memory_MemTotal_bytes
-
node_memory_MemAvailable_bytes
)
/
node_memory_MemTotal_bytes
* 100
对应数学表达式:
17. 这里会遇到 PromQL 最难的概念:Vector Matching
如果写:
metric_A / metric_B
Prometheus 不是简单:
第一行÷第一行
第二行÷第二行
而是根据 labels 匹配时间序列。
假设:
A{instance="server1"} = 100
A{instance="server2"} = 200
和:
B{instance="server1"} = 10
B{instance="server2"} = 20
那么:
A / B
得到:
server1 = 10
server2 = 10
因为:
instance="server1"
和:
instance="server1"
匹配。
18. 为什么有时 PromQL 明明有数据却返回空
这是非常常见的问题。
例如:
A{
instance="server1",
pod="pod1"
}
而:
B{
instance="server1"
}
直接:
A / B
可能无法按照预期匹配。
因为 label 集合不同。
这时需要:
A
/
on(instance)
B
意思是:
只根据
instance进行匹配。
19. on() 和 ignoring()
两个重要关键字。
19.1. on()
A
/
on(instance)
B
表示:
只使用指定 label 匹配。
19.2. ignoring()
A
/
ignoring(pod)
B
表示:
匹配时忽略 pod label。
PromQL 的向量匹配还有 group_left、group_right,用于一对多、多对一关系,这部分属于进阶语法。Prometheus 官方在 binary operators 中专门定义了 vector matching 规则。(Prometheus)
20. avg_over_time():分析 Gauge 的时间窗口
例如 GPU utilization:
DCGM_FI_DEV_GPU_UTIL
当前:
95
不代表过去五分钟一直 95%。
如果需要查询:
最近五分钟平均 GPU 利用率。
应该:
avg_over_time(
DCGM_FI_DEV_GPU_UTIL[5m]
)
同样还有:
max_over_time(metric[5m])
min_over_time(metric[5m])
这些函数用于对一个 range vector 内的样本进行时间维度计算。(Prometheus)
21. 空间聚合和时间聚合不要混淆
这是非常重要的区别。
avg(
DCGM_FI_DEV_GPU_UTIL
)
表示:
当前时刻,多张 GPU 的平均值。
这是:
空间维度
而:
avg_over_time(
DCGM_FI_DEV_GPU_UTIL[5m]
)
表示:
每张 GPU 自己过去 5 分钟的平均值。
这是:
时间维度
如果:
avg(
avg_over_time(
DCGM_FI_DEV_GPU_UTIL[5m]
)
)
才是:
先求每张 GPU 的五分钟平均,再求所有 GPU 的平均。
22. PromQL 中非常实用的 topk()
例如找 GPU 利用率最高的 10 张卡:
topk(
10,
DCGM_FI_DEV_GPU_UTIL
)
查最耗 CPU 的 Pod:
topk(
10,
sum by (namespace, pod) (
rate(container_cpu_usage_seconds_total[5m])
)
)
对于 SRE,这种查询非常实用。
23. count() 可以做资源统计
例如:
count(
DCGM_FI_DEV_GPU_UTIL
)
可以粗略得到监控系统当前看到多少条 GPU utilization 时间序列。
如果:
count by(instance)(
DCGM_FI_DEV_GPU_UTIL
)
就是:
每个节点监控到了多少 GPU。
对于 GPU 集群 inventory 很有用。
24. Histogram 是 PromQL 必须掌握的另一大领域
假设 HTTP 请求延迟 exporter 暴露:
http_request_duration_seconds_bucket
http_request_duration_seconds_sum
http_request_duration_seconds_count
classic histogram 会形成类似:
le="0.1"
le="0.5"
le="1"
le="5"
le="+Inf"
不同 bucket。
Prometheus 当前同时支持 classic histogram 和 native histogram;两者在 PromQL 中的内部表示有所不同。(Prometheus)
25. P95 / P99 为什么不是 avg()
例如请求:
99个请求 = 10ms
1个请求 = 10s
平均值可能看起来还不错,但那个 10 秒请求对用户来说已经很严重。
所以通常需要:
P50
P90
P95
P99
例如:
histogram_quantile(
0.99,
sum by (le) (
rate(
http_request_duration_seconds_bucket[5m]
)
)
)
这里:
0.99
就是:
P99。
26. 拆解 P99 查询
先:
http_request_duration_seconds_bucket[5m]
得到五分钟 bucket counter。
然后:
rate(
http_request_duration_seconds_bucket[5m]
)
计算各 bucket 每秒增加速率。
再:
sum by(le)(...)
把不同实例等维度聚合。
最后:
histogram_quantile(
0.99,
...
)
估算:
99% 请求延迟低于多少。
这就是 PromQL 的又一种典型链路:
Histogram Counter
↓
rate()
↓
按 le 聚合
↓
histogram_quantile()
↓
P99
27. Subquery:更高级的时间查询
后续会看到:
avg_over_time(
rate(http_requests_total[5m])[1h:1m]
)
其中:
[1h:1m]
是 subquery。
大意是:
过去一小时,每一分钟计算一次这个
rate(),再对这些结果做进一步计算。
这让 PromQL 可以对“查询结果本身”继续做时间窗口分析。
28. Offset:查看历史对比
例如:
http_requests_total offset 1h
意思是:
查询一小时前的数据。
例如比较:
rate(http_requests_total[5m])
与:
rate(http_requests_total[5m] offset 1h)
可以分析:
当前流量和一小时前相比发生了什么变化。
29. PromQL 在告警中的真正作用
Alertmanager 并不负责查询指标。
真正执行 PromQL 的是:
Prometheus
例如:
expr: |
(
node_memory_MemAvailable_bytes
/
node_memory_MemTotal_bytes
) < 0.1
for: 5m
Prometheus不断执行:
PromQL
如果满足:
可用内存 < 10%
持续 5 分钟
才产生 alert。
所以:
Metrics
↓
PromQL
↓
Alert Rule
↓
Prometheus
↓
Alertmanager
PromQL 实际上就是整个监控系统的数据计算层。
30. 结合 GPU 监控看 PromQL
现在把前面学的 DCGM 指标串起来。
30.1. GPU 五分钟平均利用率
avg_over_time(
DCGM_FI_DEV_GPU_UTIL[5m]
)
30.2. 每节点 GPU 平均利用率
avg by(instance)(
DCGM_FI_DEV_GPU_UTIL
)
30.3. 找利用率最低的 GPU
bottomk(
5,
DCGM_FI_DEV_GPU_UTIL
)
30.4. GPU 显存使用率
例如:
DCGM_FI_DEV_FB_USED
/
(
DCGM_FI_DEV_FB_USED
+
DCGM_FI_DEV_FB_FREE
)
* 100
30.5. 最近十分钟出现新的 ECC 错误
Counter 场景:
increase(
DCGM_FI_DEV_ECC_DBE_VOL_TOTAL[10m]
) > 0
这比:
DCGM_FI_DEV_ECC_DBE_VOL_TOTAL > 0
更合理,因为需要关注的是:
最近有没有新增错误。
31. 真正掌握 PromQL 要形成一个计算模型
以后看到一个需求:
“查询每个节点过去五分钟的 CPU 使用率。”
不要马上背查询语句。
先在脑子里转换成:
需要得到什么结果?
CPU 使用率
原始数据是什么?
node_cpu_seconds_total Counter
需要当前累计值吗?
不需要
需要什么?
增长速度
所以:
rate()
时间窗口?
5m
CPU有很多core怎么办?
聚合
按谁聚合?
instance
idle还是usage?
原始有idle
最终:
1-idle
最后才写:
100 *
(
1 -
avg by(instance)(
rate(
node_cpu_seconds_total{mode="idle"}[5m]
)
)
)
这比背 PromQL 强很多。
32. PromQL 最值得掌握的函数体系
不用一开始记几十个函数。
先掌握这组已经足够处理大部分运维需求:
| 类别 | 核心内容 |
|---|---|
| 筛选 | {label="..."} |
| Counter | rate()、increase() |
| 时间统计 | avg_over_time()、max_over_time()、min_over_time() |
| 聚合 | sum、avg、min、max、count |
| 分组 | by()、without() |
| 排序筛选 | topk()、bottomk() |
| Histogram | histogram_quantile() |
| 向量匹配 | on()、ignoring()、group_left/right |
| 历史 | offset |
| 高级时间计算 | subquery |
PromQL 官方的函数和操作符数量很多,但日常 SRE 查询真正高频使用的就是其中一小部分。(Prometheus)
33. PromQL 最常见的几个错误
第一种是:
rate(gauge_metric[5m])
Gauge 本身会上下变化,通常不应该使用 rate()。
第二种是:
sum(counter)
直接对累计 Counter 求和,很多时候没有实际业务意义。通常应该:
sum(
rate(counter[5m])
)
第三种是搞混:
avg()
和:
avg_over_time()
前者:
多时间序列之间聚合。
后者:
一条时间序列在时间窗口内聚合。
第四种是忽略 labels,导致:
A / B
结果为空。
第五种是过早聚合。对于 Counter,很多情况下最好先:
rate
再:
sum
例如:
sum by(instance)(
rate(http_requests_total[5m])
)
这样 Prometheus 才能正确识别各条 Counter 的 reset。
34. 最后把 PromQL 压缩成一条主线
PromQL 本质上是在完成:
原始时间序列
↓
Label筛选
↓
选择时间窗口
↓
转换
rate / increase / *_over_time
↓
空间聚合
sum / avg / max
↓
向量计算
+ - * /
↓
业务语义
CPU使用率
请求QPS
错误率
P99
GPU利用率
显存使用率
↓
Dashboard / Alert / AIOps
所以 PromQL 真正需要掌握的不是“语法”,而是三件事:
第一,知道指标是什么类型。 Counter、Gauge、Histogram 的处理方法完全不同。
第二,知道自己是在做时间维度计算还是空间维度聚合。 [5m] + rate/avg_over_time 解决时间问题,sum by()/avg by()解决多个时间序列之间的关系。
第三,始终关注 labels。 Prometheus 的数据模型、聚合、向量匹配、Kubernetes Pod 归属,本质上都围绕 labels 展开。
如果这三点建立起来,后面的复杂 PromQL 基本只是组合。
关联文档
- Prometheus 基础原理:理解 Prometheus 时间序列模型与数据采集机制。
- 监控与可观测性:建立指标、日志与链路追踪的整体认知。
- DCGM Exporter 指标级别的工程化:查看 PromQL 在 GPU 监控中的具体应用。
