细节篇(5):拿到CPU总开销

2023年 9月 26日 27.5k 0

问题重现

在容器中运行 top 命令虽然可以看到容器中每个进程的 CPU 使用率,但%Cpu(s)那行显示的数值是宿主机的 CPU 使用率。

例子如下,在一个 12 个 CPU 的宿主机上启动一个容器,在容器里运行 top 命令。

容器里两个进程 threads-cpu总共消耗了 200% 的 CPU,而%Cpu(s)那一行的us cpu是 58.5%。12 * 58.5%=7.02,显示总共消耗了 7 个 CPU。

image.png

该例说明 top 放在容器环境下无法得到容器中总的 CPU 使用率。那么还有什么其他的办法吗?

进程CPU使用率和系统CPU使用率

既然没有现成的工具可以得到容器 CPU 开销,那我们自己开发一个工具来解决问题。

在解决怎样得到单个容器整体的 CPU 使用率之前,先来学习在 Linux 中到底是如何计算单个进程的 CPU 使用率,还有整个系统的 CPU 使用率的。

进程CPU使用率

每个进程在 top 命令输出中都有对应的一行,%CPU那列就是这个进程的实时 CPU 使用率。这个百分比的数值是怎么得到呢?

对于每个进程,top 都会从 proc 文件系统中进程对应的 stat 文件(/proc/[pid]/stat)中读取 2 个数值。

stat 文件实时输出进程的状态信息,比如进程的运行态(Running/Sleeping)、父进程 PID、进程优先级、进程使用的内存等等总共 50 多项。

这里我们只关注这两项数值,stat 文件中的第 14 项 utime 和第 15 项 stime。

image.png

utime 表示进程的用户态部分在 Linux 调度中获得 CPU 的 ticks,stime 表示内核态部分获得 CPU 的 ticks。ticks 是 Linux 中的一个时间单位。Linux 有自己的时钟,它会周期性地产生中断。每次中断都会触发内核去做一次进程调度,一次中断就是一个 tick。比如 1 秒钟 100 次中断,那么一个 tick 也就是 1/100 秒。

假如进程的 utime 是 130ticks,130 * 1/100=1.3 秒, 进程从启动开始在用户态总共运行了 1.3 秒。

utime 和 stime 都是一个累计值,从进程启动开始这两个值就一直累积增长。

那么怎么才能知道某一进程在用户态和内核态中,分别获得了多少 CPU 的 ticks 呢?

我们可以假设这个瞬时是 1 秒钟,这 1 秒是 T1 时刻到 T2 时刻之间的,这样就能获得 T1 时刻的 utime_1 和 stime_1,同时获得 T2 时刻的 utime_2 和 stime_2。

进程 CPU 总开销就是用户态加上内核态,1 秒瞬时进程总的 CPU ticks 等于 (utime_2 – utime_1) + (stime_2 – stime_1)。

进程的 CPU 使用率 =((utime_2 – utime_1) + (stime_2 – stime_1)) * 100.0 / (HZ * et * 1 )

乘以 100.0 的目的是产生百分比数值。

ticks 是按照固定频率发生的,在 Linux 里 1 秒钟 100 次,HZ 就是 1 秒钟里 ticks 的次数,这里值是 100。

et 是“瞬时”时间,也就是得到 utime_1 和 utime_2 这 两个值的时间间隔。

“1”是 1 个 CPU。这三个值相乘就是在这“瞬时”时间里,1 个 CPU 所包含的 ticks 数目。

可以把公式简化: 进程的 CPU 使用率 =(进程的 ticks/ 单个 CPU 总 ticks)*100.0

上手验证一下,启动一个消耗 CPU 的小程序,然后读取一下进程对应的 /proc/[pid]/stat 中的 utime 和 stime,用这个方法计算进程使用率,和 top 的输出对比。

先启动一个消耗 200% 的小程序,它的 PID 是 10021,CPU 使用率是 200%。

image.png

查看这个进程对应的 stat 文件,间隔 1 秒输出第二次,utime_1 = 399,stime_1=0,utime_2=600, stime_2=0。

image.png

((600 – 399) + (0 – 0)) * 100.0 / (100 * 1 * 1) =201,也就是 201%。和我们运行 top 里的值是一样的。验证了这个公式是没问题的。

系统CPU使用率

同样,要计算 CPU 使用率,首先需要拿到数据,数据源同样从 proc 文件系统里得到,对于整个系统的 CPU 使用率,这个文件就是 /proc/stat。

在 /proc/stat 文件的 cpu 这行有 10 列数据,前 8 列数据正好对应 top 输出中%Cpu(s)那行里的 8 项数据,也就是在上一讲中的 user/system/nice/idle/iowait/irq/softirq/steal 这 8 项。

image.png

/proc/stat 里的每一项的数值,就是系统自启动开始的 ticks。要计算出“瞬时”的 CPU 使用率,先要算出这个“瞬时”的 ticks,可以记录开始时刻 T1 的 ticks, 再记录 1 秒钟后 T2 时刻的 ticks,两者相减得到这 1 秒钟的 ticks。

image.png

在这 1 秒钟里每个 CPU 使用率的 ticks:

image.png

比如说计算 idle CPU 的使用率就是:

(1203 / 0 + 0 + 0 + 1203 + 0 + 0 + 0 + 0)=100%

对于单个进程的 CPU 使用率计算,需要读取对应进程的 /proc/[pid]/stat 文件,将进程瞬时用户态和内核态的 ticks 数相加,就能得到进程的总 ticks。

运用公式“(进程的 ticks / 单个 CPU 总 ticks) * 100.0”计算出进程 CPU 使用率的百分比值。

对于系统的 CPU 使用率,需要读取 /proc/stat 文件,得到瞬时各项 CPU 使用率的 ticks 值,相加得到一个总值,单项值除以总值就是各项 CPU 的使用率。

解决问题

再来看最初的问题:为什么在容器中运行 top 命令不能得到容器中 总的 CPU 使用率?

对于系统总的 CPU 使用率,需要读取 /proc/stat 文件,但是这个文件中的各项 CPU ticks 是反映整个节点的,并且 /proc/stat 文件也不包含在任意一个 Namespace 里。

那么怎么得到整个容器的 CPU 使用率呢?

每个容器都有一个 CPU Cgroup 的控制组。目录下面有一个可读项 cpuacct.stat。
包含了两个统计值,分别是这个控制组里所有进程的内核态 ticks 和用户态的 ticks,可以用前面的公式去计算整个容器的 CPU 使用率:

CPU 使用率 =((utime_2 – utime_1) + (stime_2 – stime_1)) * 100.0 / (HZ * et * 1 )

还是以问题重现中的例子说明,也就是最开始启动容器里的那两个容器 threads-cpu 进程。

就像下图显示的这样,整个容器的 CPU 使用率的百分比就是 ( (174021 - 173820) + (4 – 4)) * 100.0 / (100 * 1 * 1) = 201, 也就是 201%。我们从每个容器的 CPU Cgroup 控制组里的 cpuacct.stat 的统计值中,可以比较快地得到整个容器的 CPU 使用率。

image.png

总结

Linux 里获取 CPU 使用率的工具,比如 top,都是读取 proc 文件系统下的 stat 文件来得到 CPU 使用了多少 ticks。ticks是 Linux 里的一个时间单位。

对于每个进程,它的 stat 文件是 /proc/[pid]/stat,里面包含了进程用户态和内核态 的 ticks 数目;对于整个节点,它的 stat 文件是 /proc/stat,里面包含了 user/system/nice/idle/iowait 等不同 CPU 开销类型的 ticks。

由于 /proc/stat 文件是整个节点全局的状态文件,不属于任何一个 Namespace,因此在容器中无法通过读取 /proc/stat 文件来获取单个容器的 CPU 使用率。

所以要得到单个容器的 CPU 使用率,我们可以从 CPU Cgroup 每个控制组里的统计文件 cpuacct.stat 中获取。单个容器 CPU 使用率 =((utime_2 – utime_1) + (stime_2 – stime_1)) * 100.0 / (HZ * et * 1 )。

得到单个容器的 CPU 的使用率,当宿主机上负载变高时,就可以很快知道是哪个容器引起的问题。同时,用户在管理自己成百上千的容器时,也可以很快发现 CPU 使用率异常的容器,这样就能及早地介入去解决问题。

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论