如何正确获取容器的CPU利用率?( 三 )

从上述结果中可以看出,我的机器的每秒要打出 1000 次节拍 。也就是每 1 ms 一次 。
每次当时间中断到来的时候,都会调用 update_process_times 来更新系统时间 。更新后的时间都存储在我们前面提到的 percpu 变量 kcpustat_cpu 中 。

如何正确获取容器的CPU利用率?

文章插图
我们来详细看下汇总过程 update_process_times 的源码,它位于 kernel/time/timer.c 文件中 。
//file:kernel/time/timer.cvoid update_process_times(int user_tick){ struct task_struct *p = current; //进行时间累积处理 account_process_tick(p, user_tick); ...}这个函数的参数 user_tick 值得是采样的瞬间是处于内核态还是用户态 。接下来调用 account_process_tick 。
//file:kernel/sched/cputime.cvoid account_process_tick(struct task_struct *p, int user_tick){ cputime = TICK_NSEC; ... if (user_tick)//3.1 统计用户态时间account_user_time(p, cputime); else if ((p != rq->idle) || (irq_count() != HARDIRQ_OFFSET))//3.2 统计内核态时间account_system_time(p, HARDIRQ_OFFSET, cputime); else//3.3 统计空闲时间account_idle_time(cputime);}在这个函数中,首先设置 cputime = TICK_NSEC, 一个 TICK_NSEC 的定义是一个节拍所占的纳秒数 。接下来根据判断结果分别执行 account_user_time、account_system_time 和 account_idle_time 来统计用户态、内核态和空闲时间 。
3.1 用户态时间统计//file:kernel/sched/cputime.cvoid account_user_time(struct task_struct *p, u64 cputime){ //分两种种情况统计用户态 CPU 的使用情况 int index; index = (task_nice(p) > 0) ? CPUTIME_NICE : CPUTIME_USER; //将时间累积到 /proc/stat 中 task_group_account_field(p, index, cputime); ......}account_user_time 函数主要分两种情况统计:
  • 如果进程的 nice 值大于 0,那么将会增加到 CPU 统计结构的 nice 字段中 。
  • 如果进程的 nice 值小于等于 0,那么增加到 CPU 统计结构的 user 字段中 。
看到这里,开篇的问题 2 就有答案了,其实用户态的时间不只是 user 字段,nice 也是 。之所以要把 nice 分出来,是为了让 Linux 用户更一目了然地看到调过 nice 的进程所占的 cpu 周期有多少 。
我们平时如果想要观察系统的用户态消耗的时间的话,应该是将 top 中输出的 user 和 nice 加起来一并考虑,而不是只看 user!
接着调用 task_group_account_field 来把时间加到前面我们用到的 kernel_cpustat 内核变量中 。
//file:kernel/sched/cputime.cstatic inline void task_group_account_field(struct task_struct *p, int index,u64 tmp){ __this_cpu_add(kernel_cpustat.cpustat[index], tmp); ...}3.2 内核态时间统计我们再来看内核态时间是如何统计的,找到 account_system_time 的代码 。
//file:kernel/sched/cputime.cvoid account_system_time(struct task_struct *p, int hardirq_offset, u64 cputime){ if (hardirq_count() - hardirq_offset)index = CPUTIME_IRQ; else if (in_serving_softirq())index = CPUTIME_SOFTIRQ; elseindex = CPUTIME_SYSTEM; account_system_index_time(p, cputime, index);}内核态的时间主要分 3 种情况进行统计 。
  • 如果当前处于硬中断执行上下文, 那么统计到 irq 字段中
  • 如果当前处于软中断执行上下文, 那么统计到 softirq 字段中
  • 否则统计到 system 字段中
判断好要加到哪个统计项中后,依次调用 account_system_index_time、task_group_account_field 来将这段时间加到内核变量 kernel_cpustat 中
//file:kernel/sched/cputime.cstatic inline void task_group_account_field(struct task_struct *p, int index,u64 tmp){__this_cpu_add(kernel_cpustat.cpustat[index], tmp);}3.3 空闲时间的累积没错,在内核变量 kernel_cpustat 中不仅仅是统计了各种用户态、内核态的使用统计,空闲也一并统计起来了 。
如果在采样的瞬间,cpu 既不在内核态也不在用户态的话,就将当前节拍的时间都累加到 idle 中 。
//file:kernel/sched/cputime.cvoid account_idle_time(u64 cputime){ u64 *cpustat = kcpustat_this_cpu->cpustat; struct rq *rq = this_rq(); if (atomic_read(&rq->nr_iowait) > 0)cpustat[CPUTIME_IOWAIT] += cputime; elsecpustat[CPUTIME_IDLE] += cputime;}在 cpu 空闲的情况下,进一步判断当前是不是在等待 IO(例如磁盘 IO),如果是的话这段空闲时间会加到 iowait 中,否则就加到 idle 中 。从这里,我们可以看到 iowait 其实是 cpu 的空闲时间,只不过是在等待 IO 完成而已 。
看到这里,开篇问题 3 也有非常明确的答案了,io wait 其实是 cpu 在空闲状态的一项统计,只不过这种状态和 idle 的区别是 cpu 是因为等待 io 而空闲 。


推荐阅读