问题重现
在容器中运行 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。
该例说明 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。
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%。
查看这个进程对应的 stat 文件,间隔 1 秒输出第二次,utime_1 = 399,stime_1=0,utime_2=600, stime_2=0。
((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 项。
/proc/stat 里的每一项的数值,就是系统自启动开始的 ticks。要计算出“瞬时”的 CPU 使用率,先要算出这个“瞬时”的 ticks,可以记录开始时刻 T1 的 ticks, 再记录 1 秒钟后 T2 时刻的 ticks,两者相减得到这 1 秒钟的 ticks。
在这 1 秒钟里每个 CPU 使用率的 ticks:
比如说计算 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 使用率。
总结
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 使用率异常的容器,这样就能及早地介入去解决问题。