CPU 使用率的计算方法
翻了几篇 Prometheus 的 PromQL 查询 cpu 使用率的文章,说得都不是特别透,结合一篇英文文章终于搞明白了怎么计算这个指标。
cpu 模式
一颗 cpu 要通过分时复用的方式运行于不同的模式中,可以类比为让不同的人使用 cpu,张三使一会儿,李四使一会儿。这些模式可以用 top 命令查看,包括:
- us:用户进程使用cpu的时间
- sy:内核进程使用cpu的时间
- ni:用户进程空间内改变过优先级的进程使用的cpu时间
- id:空闲(没人用)的cpu时间
- wa:等待io的cpu时间
- hi:硬中断的cpu时间
- si:软中断的cpu时间
- st:虚拟机管理程序使用的cpu时间
这些时间加在一起是总的cpu时间,这个时间又是什么意思呢?
cpu 时间
通过 node-exporter 抓取的指标中cpu相关主要是各个 node_cpu_seconds_total
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 230416.36
node_cpu_seconds_total{cpu="0",mode="iowait"} 3.86
node_cpu_seconds_total{cpu="0",mode="irq"} 0
node_cpu_seconds_total{cpu="0",mode="nice"} 1.05
node_cpu_seconds_total{cpu="0",mode="softirq"} 302.24
node_cpu_seconds_total{cpu="0",mode="steal"} 0
node_cpu_seconds_total{cpu="0",mode="system"} 3829.27
node_cpu_seconds_total{cpu="0",mode="user"} 4802.39
node_cpu_seconds_total{cpu="1",mode="idle"} 230389.47
node_cpu_seconds_total{cpu="1",mode="iowait"} 30.73
node_cpu_seconds_total{cpu="1",mode="irq"} 0
node_cpu_seconds_total{cpu="1",mode="nice"} 1.09
node_cpu_seconds_total{cpu="1",mode="softirq"} 486.8
node_cpu_seconds_total{cpu="1",mode="steal"} 0
node_cpu_seconds_total{cpu="1",mode="system"} 3928.86
node_cpu_seconds_total{cpu="1",mode="user"} 4919.87
每个指标就是某一核cpu的某个模式的运行时间,
单位是秒
。把某一核各个模式的 cpu 时间加起来的秒数就是执行 uptime
得到的系统开机以来运行的总秒数了(/proc/uptime 的第一列,第二列是各核的空闲时间和)。这就理解了为什么这些值都是单调递增的。比如
node_cpu_seconds_total{cpu="0",mode="idle"} 230416.36
这个值的意思是自系统开机以来到当前时点,cpu0的空闲时间是230416.36秒。用它除以 uptime 就是开机以来 cpu0 的空闲率。加入现在是5点,那你5点过5分再查一下这个值,肯定会大于等于现在这个值。
计算 cpu 使用率的公式分解
下面一步一步推导cpu使用率的计算公式:
-
cpu0 5分钟内处于空闲状态的时间:
increase(node_cpu_seconds_total{cpu="0",mode="idle"}[5m])
,increase 的意思是表示增量,刚才说了 node_cpu_seconds_total 是单调递增的,这个公式的结果是当前时点的 node_cpu_seconds_total 减去5分钟之前的 node_cpu_seconds_total,也就是这5分钟之内处于idle 状态的 cpu 时间。 -
cpu0 5分钟内处于空闲状态的时间占比:
increase(node_cpu_seconds_total{cpu="0",mode="idle"}[5m]) / increase(node_cpu_seconds_total{cpu="0"}[5m])
,分母其实就是5分钟=300秒。 -
一台主机所有 cpu 5分钟内处于空闲状态的时间占比,用
sum()
函数累加各核数值:
sum (increase(node_cpu_seconds_total{mode="idle"}[5m])) / sum (increase(node_cpu_seconds_total{mode="idle"}[5m]))
-
如果 Prometheus 监控多台主机,要根据每台主机做 sum:
sum by (instance)(increase(node_cpu_seconds_total{mode="idle"}[5m])) / sum by (instance)(increase(node_cpu_seconds_total[5m]))
-
cpu 使用率 = 1 – cpu 空闲率:
100 * (1 - sum by (instance)(increase(node_cpu_seconds_total{mode="idle"}[5m])) / sum by (instance)(increase(node_cpu_seconds_total[5m])))
PromQL 有计算比率的函数:
rate()
和
irate()
,可以简化计算公式为:
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
irate 和 rate
官方文档定义
irate()
是基于最后两个数据点计算一个时序指标在一个范围内的每秒递增率。我半天没想明白最后两个数据点是哪两个,怎么定义的,后来忽然想到它就是你所采集到的最后两个数据,这个两个数据点的间隔就是
scrape_interval
。如果使用
irate()
,只要方括号中查询的时间段长度大于
scrape_interval
,那么在一个
scrape_interval
之内,查询不同的时间长度的结果是一样的。举个栗子:
scrape_interval
设置为30s,那么方括号里面填1m、2m、5m、10m,查询的结果是不变的,只要前后两次查询在30s之内。也就是说查询的时间范围至少要大于等于
scrape_interval
,以保证这个时间范围内至少有两个数据点,大于
scrape_interval
的两倍比较保险。
rate()
是计算一个时序指标在一个范围内的每秒平均递增率,它仅使用第一个和最后一个数据点。如果查询的时间范围内仅有两个数据点,那么它的查询结果就等于
irate()
。它的图像更平滑,更适合用于告警。