Skip to content

PromQL

Prometheus 把指标存进 TSDB 之后,PromQL 负责从这些时序数据里筛选、计算、聚合和对比。Prometheus 的 Query 页面、Grafana 面板、告警规则,最终都落到 PromQL 表达式上。

写 PromQL 时最容易卡住的不是语法本身,是指标类型没认清(Counter 不能直接当当前值用)、标签维度没想好(聚合丢掉 instance 后没法定位机器)、时间范围没搞清楚(Instant Vector 和 Range Vector 什么时候用)。

时间序列:指标名 + 标签 = 唯一序列

TSDB 里每一条时间序列由"指标名 + 一组标签"唯一确定:

text
up{job="node", instance="192.168.10.11:9100", hostname="mon01"} 1
up{job="node", instance="192.168.10.12:9100", hostname="db01"} 1
up{job="node", instance="192.168.10.13:9100", hostname="cache01"} 1

指标名都是 up,但 instancehostname 不同,所以是三条不同的序列。给查询加了标签过滤条件后,就只在匹配到的序列里计算。

标签过滤写在 {} 里:

写法含义
up查询所有 up 序列
up{job="node"}只查 jobnode
up{job!="node"}排除 jobnode
up{hostname=~"db.*"}=~ 是正则匹配
up{hostname!~"db.*"}!~ 是正则排除

Instant Vector 和 Range Vector

这两个概念贯穿所有 PromQL 查询。Instant Vector 是一个时间点上的一组序列及其值:

promql
up{job="node"}          # "现在"这个时间点,三台机器的 up 值分别是多少

Range Vector 是一段时间内的一组序列样本:

promql
node_cpu_seconds_total{job="node",mode="idle"}[5m]
# 最近 5 分钟内,每个时间点的 idle CPU 累计秒数

[5m] 把 Instant Vector 变成 Range Vector。rate()increase()avg_over_time() 这些函数只能处理 Range Vector——它们需要一段时间内的多个样本才能计算。

表达式类型说明
upInstant Vector当前查询时间点的值
up[5m]Range Vector最近 5 分钟内的所有样本点
rate(http_requests_total[5m])Instant Vector输入 Range Vector,根据样本增长算出每秒速率,输出还是 Instant Vector

Prometheus 页面的 Graph 模式按时间范围连续计算表达式,图上看到的曲线是很多个 Instant Vector 拼起来的;Table 模式只看当前时间点的值和标签。

Counter:算速率,不直接看当前值

Counter 只增不减(进程重启会归零)。请求总数、网卡累计字节数、CPU 累计运行秒数——都是 Counter。直接画 Counter 只会看到一条不断往上的线,排查流量和 CPU 时更想知道"每秒变化多少":

promql
rate(node_network_receive_bytes_total{job="node",device!~"lo|veth.*"}[5m])

rate() 取 Range Vector 里的样本,算平均每秒增长速率。Counter 归零时(进程重启),rate() 内部会自动处理——看到当前值比上一个值小,就按重置计算增量,不会算出负速率。

rate()increase() 的区别:rate() 是每秒速率,increase() 是一段时间内的总增量。Grafana 里画图常用 rate(),告警规则里有时用 increase() 判断"过去 N 分钟增加了多少"。

Gauge:直接查当前值

Gauge 可增可减,和温度计一样。当前内存、当前连接数、磁盘可用空间——直接查就行:

promql
node_memory_MemAvailable_bytes{job="node"}
mysql_global_status_threads_connected

Gauge 也可以做 avgmaxmin 聚合,看整体情况。

Histogram:从桶里算分位数

Histogram 用多个桶记录数据分布。比如 HTTP 请求耗时,不是只记一个平均值,而是统计 ≤0.1s 几个、≤0.5s 几个、≤1s 几个……从桶的分布里能算出 P95、P99:

promql
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

每个桶本身也是 Counter(只增不减),所以先用 rate() 转成速率,再调 histogram_quantile() 求分位。P95=200ms 和平均值=200ms 含义完全不一样——平均值 200ms 可能是"大部分请求 50ms,偶尔几个 5 秒"的结果,分位数能暴露这个长尾。

四种类型的查询方式总结:

类型特点常见写法
Counter只增不减,重启归零rate()increase()
Gauge可增可减直接查,或 avg()max()
Histogram分桶记录分布histogram_quantile() + rate()
Summary客户端侧算好分位直接查,但跨实例无法重新聚合

常用主机查询

按 job 统计成功抓取数量:

promql
sum by (job) (up)

CPU 使用率——用 100 减去 idle 占比。avg by (instance) 按机器聚合,避免每个 CPU 核心各出一条线:

promql
100 - (
  avg by (instance) (
    rate(node_cpu_seconds_total{job="node",mode="idle"}[5m])
  ) * 100
)

内存使用率——MemAvailableMemFree 更接近"还能分配多少内存"。Linux 会把空闲内存用作 page cache,MemFree 看着低但随时能被回收:

promql
100 * (1 - (
  node_memory_MemAvailable_bytes{job="node"}
  /
  node_memory_MemTotal_bytes{job="node"}
))

根分区使用率——fstype!~"tmpfs|overlay" 过滤虚拟文件系统。tmpfs 是内存文件系统,overlay 常见于容器,和真实磁盘容量无关:

promql
100 * (1 - (
  node_filesystem_avail_bytes{job="node",mountpoint="/",fstype!~"tmpfs|overlay"}
  /
  node_filesystem_size_bytes{job="node",mountpoint="/",fstype!~"tmpfs|overlay"}
))

聚合:by 决定保留什么标签

by 指定保留哪些标签,其余的被聚合掉。without 反过来——指定去掉哪些标签,其余保留:

promql
avg by (instance) (rate(node_cpu_seconds_total{job="node",mode!="idle"}[5m]))
# 结果按 instance 分组,每个 instance 一个值

count by (job) (up)
# 按 job 统计数量,instance/hostname 被聚掉了

up{job="node"} == 0
# 直接过滤出抓取失败的实例——没有聚合,保留完整标签用来定位

聚合维度选择的影响:

聚合方式适合失去什么
sum by (job)看一类服务整体无法定位具体哪台机器
avg by (instance)看每台机器的平均资源丢失 CPU 核心维度
max by (mountpoint)看每台机器每个挂载点告警条数可能翻倍
不聚合排查原始序列面板上可能太乱

告警和看板的聚合取舍不一样。看板可以保留更多维度方便排查;告警规则如果按 instance + mountpoint 出结果,每台机器每个挂载点都可能触一条告警——维度太细时考虑收窄聚合或配合 Alertmanager 做分组。

验证 PromQL 的入口

Prometheus Query 页面适合验证表达式本身:先确认 up{job="node"} 能返回三台主机,再逐步加函数和聚合。Grafana Explore 用同一条 PromQL 交叉确认数据源和查询结果。

平台路径用途
PrometheusQuery(中文:查询)验证 PromQL 是否有结果
PrometheusStatus → Target health看 target 抓取状态
GrafanaExplore(中文:探索)用 Grafana 数据源执行临时查询
GrafanaDashboards → 面板菜单 → Edit检查面板的 PromQL、变量和单位

Grafana 查不到数据、Prometheus Query 能查到,问题在 Grafana 数据源、时间范围或面板变量。两边都查不到,回到 Targets 页面和指标名确认。

常见查询问题

现象常见原因先看什么
查询无结果指标名写错、标签不匹配、时间范围没数据先查指标名,再逐个加标签条件
曲线中间断了某段时间 target 抓取失败、Prometheus 重启up 指标和 Targets 页面
CPU 数值异常高或低Counter 直接使用没加 rate(),或聚合维度错了rate()、明确 by(instance)
磁盘面板出现很多虚拟挂载点没过滤 tmpfs、overlay`fstype!~"tmpfs
多台机器的数据混成一条线聚合丢了 instance检查 bywithout
Counter 曲线突然掉到底再涨进程重启归零rate() 自动处理,但突降痕迹还在

写复杂表达式时,从最简开始一层一层加:原始指标能不能查到 → 加一个标签后还能不能查到 → 套函数后结果对不对 → 聚合后标签维度是不是还够定位。每一步都能确认结果,出了问题也容易知道是哪一步不对。