0%

Linux CPU使用率计算方式

Linux CPU使用率计算

前置 - CPU主频

主频即CPU的时钟频率,计算机的操作在时钟信号的控制下分步执行,每个时钟信号周期完成一部操作,时钟频率的高低一定程度上反应了CPU速度的快慢,影响因素还有很多其它性能指标(缓存、指令集、CPU位数等)

cat /proc/cpuinfo可看到CPU相关信息,其中包括CPU型号

1
model name      : Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz

如上,2.10GHz,代表此CPU的主时钟脉冲信号的频率为2.10GHz

赫兹(Hertz,符号Hz),是频率的国际单位制单位,表示每一秒周期性事件发生的次数

$kHz = 10^3Hz$

$MHz = 10^6Hz$

$GHz = 10^9Hz$

$THz = 10^{12}Hz$

CPU使用率计算原理

Linux作为一个多任务操作系统,将每个CPU的时间划分为很短的时间片,通过调度器分配各个任务使用,造成多任务同时运行的错觉。

其CPU使用率是通过采样形式统计得到的,采样周期依赖的是Linux时间子系统中的定时器,即Linux内核周期性(像乐谱中的节拍)的会发出timer interrupt,Linux随之响应并处理一些具体事项。这个节拍的长度,可通过如下方式获得

1
2
localhost ~ # grep CONFIG_HZ= /boot/config-$(uname -r)
CONFIG_HZ=1000

1000代表了每秒1000次中断,即1ms一次中断

Linux会将瞬时采样的值记录到伪文件系统的文件中/proc/stat,根据采样情况将相关字段值进行累加,也就是说“瞬时采样时CPU被哪个字段占用,则对应的字段值加1”。

CPU使用率计算方法

计算CPU使用率用到的信息如下:其中cpu代表的总体情况,cpuN代表的是单核心情况

1
2
3
4
5
6
localhost ~ # grep ^cpu /proc/stat
cpu 181524 20738 156893 310071904 31470 0 1225 1171 0 0
cpu0 32827 5717 22826 77552345 10298 0 525 198 0 0
cpu1 50699 4965 43867 77507112 8213 0 383 315 0 0
cpu2 48299 5099 47722 77500100 5821 0 207 345 0 0
cpu3 49698 4955 42476 77512345 7137 0 110 310 0 0

具体的字段值解读,可通过man proc中搜索/proc/stat章节找到,例如

1
2
3
4
5
user   (1) Time spent in user mode.

nice (2) Time spent in user mode with low priority (nice).

system (3) Time spent in system mode.

另外其中还有非常重要的一段话The amount of time, measured in units of USER_HZ (1/100ths of a second on most architectures, use sysconf(_SC_CLK_TCK) to obtain the right value), that the system ("cpu" line) or the specific CPU ("cpuN" line) spent in various states:,提示这里输出的信息用到的单位是USER_HZ,其值
1/100秒,可通过getconf CLK_TCK命令获得,即1秒100次中断(节拍)。

进一步得出:

  • 单位时间内(每秒)CPU花费在user、nice、system...的次数总值为100(包括空闲idle)
  • 用户空间跟内核空间的值不同。

假如user1记录的是第1秒记录的值,user2记录的是第2秒记录的值…则(user2-user1)+(nice2-nice1)+(system2-system1)… = 100


CPU使用率 = $1 - \frac{CPU空闲时间(值)}{CPU总时间(值)}$

$1 - \frac{IDLE2 - IDLE1}{取样时间间隔(秒)* 100}$


注意: 实际生产环境(工程实现)中,除关注整体CPU使用率外,还会关注一系列更细粒度的参数:单CPU情况、用户态、内核态、IOWAIT等,条件允许的情况下,最好的处理方式就是将/proc/stat中的值进行格式化保存到时序数据库

扩展

参考一

man 7 time

参考二

include/asm-generic/param.h

1
2
3
4
5
6
7
8
9
10
11
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_GENERIC_PARAM_H
#define __ASM_GENERIC_PARAM_H

#include <uapi/asm-generic/param.h>

# undef HZ
# define HZ CONFIG_HZ /* Internal kernel timer frequency */
# define USER_HZ 100 /* some user interfaces are */
# define CLOCKS_PER_SEC (USER_HZ) /* in "ticks" like times() */
#endif /* __ASM_GENERIC_PARAM_H */

CONFIG_HZ

内核编译时确定的值,定义了Linux内核的时钟频率。

当然基于传统配置(或默认开关),如果有定制要求,可有更多编译选项

1
2
3
4
CONFIG_HZ_PERIODIC
CONFIG_NO_HZ_IDLE
CONFIG_HIGH_RES_TIMERS
...

USER_HZ

用户空间的应用程序使用的时钟频率,固定值100,通过sysconf(_SC_CLK_TCK)获取,当然Bash中getconf CLK_TCK也可获得。

1
2
localhost ~ # getconf CLK_TCK
100

由此可以看到USER_HZ != CONFIG_HZ,从源码中可以看到系统输出时作了相关转换

fs/proc/stat.c

1
2
3
4
5
6
7
8
9
10
11
static int show_stat(struct seq_file *p, void *v)
{
...
for_each_online_cpu(i) {
...
seq_put_decimal_ull(p, " ", nsec_to_clock_t(user));
seq_put_decimal_ull(p, " ", nsec_to_clock_t(nice));
...
}
...
}

jiffy/jiffies

  • Linux内核中的时间精度,jiffy值等于CONFIG_HZ,也就是说系统调用能识别到的最小值
  • jiffies记录系统启动以来经历了多少tick(滴答声,也就是节拍)
1
2
3
4
5
6
7
8
9
10
11
12
localhost ~ # grep -E "^cpu|^jiff" /proc/timer_list
cpu: 0
jiffies: 5084275003
cpu: 1
jiffies: 5084275003
cpu: 2
jiffies: 5084275003
cpu: 3
jiffies: 5084275003

localhost ~ # uptime
18:20:31 up 9 days, 3:20, 3 users, load average: 1.00, 1.00, 1.00

将jiffies换算后发现跟uptime显示的值不同?!

include/linux/jiffies.h

1
2
3
4
5
/*
* Have the 32 bit jiffies value wrap 6 minutes after boot
* so jiffies wrap bugs show up earlier.
*/
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))

可以看到INITIAL_JIFFIES的初始值不为0而是0xfffb6c20

file1.c

1
2
3
4
5
6
7
8
#include "stdio.h"

#define HZ 1000
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
int main(int args, char const *argv[])
{
printf("INITIAL_JIFFIES=%lu\n", INITIAL_JIFFIES);
}
1
2
3
test@localhost $ gcc file1.c -o file1
test@localhost $ ./file1
INITIAL_JIFFIES=4294667296 #0xfffb6c20

实际计算方式

1
(jiffies - 4294667296)/HZ 即(5084275003-0xfffb6c20)/1000=789607.707