监控

PromQL

·31 分钟阅读·12320 字

梳理 PromQL 数据类型、选择器、聚合、向量匹配、直方图与告警查询方法

📋 目录

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

所以一条时间序列可以抽象为:

TS=(metric name, labels, samples)TS = (metric\ name,\ labels,\ samples)

其中:

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() 根据这段数据计算:

rate≈ΔcounterΔtimerate \approx \frac{\Delta counter}{\Delta time}

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

那么大约:

rate=1600−1000300=2rate = \frac{1600-1000}{300} =2

结果:

2 request/s

注意:

rate() 得到的是:

平均每秒增长速度。

不是过去五分钟增加多少。


10. increase()

如果想知道:

过去五分钟总共增加了多少?

使用:

increase(
  http_requests_total[5m]
)

例如:

1000 → 1600

结果大约:

600

可以简单理解:

increase≈rate×timeincrease \approx rate \times time

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:

0.2+0.3+0.1+0.24=0.2\frac{0.2+0.3+0.1+0.2}{4} =0.2

也就是:

20% idle

最终:

CPUUsage=100CPUUsage=100%-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

对应数学表达式:

MemoryUsage=Total−AvailableTotal×100MemoryUsage = \frac{Total-Available}{Total} \times100

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="..."}
Counterrate()、increase()
时间统计avg_over_time()、max_over_time()、min_over_time()
聚合sum、avg、min、max、count
分组by()、without()
排序筛选topk()、bottomk()
Histogramhistogram_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 基本只是组合。


关联文档

Yanche Blog

记录云原生、Linux、数据库等技术领域的学习心得,以及日常生活的思考与感悟。

© 2026 Yanche Blog. All rights reserved.

Powered by Astro