ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

内核CPU时间统计揭秘:从time命令到cputime.c的记账链路

内核CPU时间统计揭秘:从time命令到cputime.c的记账链路 用 time 命令跑一个程序real 0.02s、user 0.01s、sys 0.01s这三个数字看起来平平无奇可我一直觉得这里藏着一个很有意思的问题内核是怎么知道这段代码花在用户态多少、花在内核态多少的尤其当一个进程在几毫秒内频繁切换用户态和内核态统计还能跟得上吗答案最终都指向同一个地方调度器里的 CPU 时间统计模块也就是 kernel/sched/cputime.c。这个文件不负责选进程调度公平性由 CFS 的 vruntime 管它只做一件事——记账。每一次时钟中断落下这一个 tick 的 CPU 时间该记给谁、记成用户态还是内核态、要不要单列成 irq 或 softirq、虚拟机的 guest 时间怎么处理、被 hypervisor 偷走的时间记到哪里全部由这一套机制完成。这篇文章我想把 cputime.c 的记账链路完整拆开讲一遍适合正在读内核源码的人、做性能分析被 CPU 时间绕晕的人以及单纯好奇 time 输出到底怎么来的同学。1. 一个 time 命令引出的问题real、user、sys 到底谁在记账1.1 先分清三种时间real 是墙钟user/sys 才是 CPU 时间随便在 shell 里跑一个命令$ time sleep 0.1 real 0m0.104s user 0m0.001s sys 0m0.001sreal 是墙钟时间从命令开始到结束一共过了 104ms其中 100ms 是 sleep 主动挂起不占 CPU。user 是进程在用户态耗费的 CPU 时间sys 是在内核态耗费的 CPU 时间。两者相加才是这个进程真正吃掉的 CPU 周期。这里有个容易混淆的点sleep 调用本身会进入内核态执行 nanosleep 系统调用sys 为什么只有 1ms因为系统调用的大部分时间在等待定时器唤醒进程处于睡眠状态不消耗 CPU。真正在内核态跑指令的时间可能只有几微秒。所以 user sys 衡量的是CPU 为你工作的时间不是你花了多少时间。要回答内核怎么算出来 0.001s 这种精度就得看统计的两个落点task_struct 里跟进程走的时间字段以及 per-CPU 的全局统计数组。1.2 从 task_struct 的字段看记账的落点每个进程的 task_struct 里记录着它这辈子消耗的 CPU 时间。比较关键的有这几个utime用户态累计时间stime内核态累计时间gtime在虚拟机 guest 模式下运行的时间后面会单讲utimescaled / stimescaled按 CPU 频率做过归一化处理的时间戳主要用于内核内部的负载估算在内核新版本中这些字段的类型都是 u64单位纳秒。但在很多内核文档和旧教程里你看到的说法是单位是 tick那是因为老的 cputime_t 类型在某些配置下直接就是 jiffies。现在统一成纳秒之后语义简单很多但对外输出 /proc 时仍然会换算成传统的 clock tick 单位。这里要特别强调一下task_struct 里记录的这些字段是进程私有的账本不管这个进程跑到哪个 CPU 上时间都记在自己名下。而后面要讲的 kcpustat 是每个 CPU 自己的账本记录这个 CPU 总共经历了多少用户态、系统态、空闲、中断等时间段。两套账本互相独立但更新路径常常交织在一起。1.3 全局 CPU 统计 /proc/stat 和任务级统计的关系如果只记进程不记 CPU就会出现一个问题某个 CPU 上跑了很多短命进程谁来汇总这个 CPU 的使用率所以内核维护了 per-CPU 的 struct kernel_cpustat里面一个 cpustat 数组按 CPUTIME_USER、CPUTIME_NICE、CPUTIME_SYSTEM、CPUTIME_IRQ、CPUTIME_SOFTIRQ、CPUTIME_STEAL、CPUTIME_GUEST 等索引累加。/proc/stat 里的 cpu 行就是把这几个数组直接读出来展示。top 命令显示 CPU 使用率时读的也是这个数据源。所以整个统计体系是双写的一个 tick 落下来既会尝试更新当前进程的 utime/stime也会更新对应 CPU 的 cpustat 数组。明白了这个框架再去看 cputime.c 里的函数脉络就清晰了。2. cputime.c 在调度器里的生态位一个 tick 中断的完整旅程2.1 时钟中断到 account_process_tick 的调用链调度器本身是被动的它靠周期性时钟中断tick驱动。每次时钟中断到来经过架构相关的中断处理代码最终会走到update_process_times()这个函数会告诉你一件事刚才这个 tick 是发生在用户态还是内核态然后调用account_process_tick()去记账。简化后的调用链大致是时钟中断比如 x86 的 local APIC timer - tick_periodic() 或 tick_sched_handle() - update_process_times(user_mode(regs)) - account_process_tick(current, user_tick) - account_user_time() / account_system_time() / account_idle_time()注意user_mode(regs)是判断被中断打断的那一刻CPU 正运行在用户态还是内核态。这个判断在中断入口的 pt_regs 里就有不需要额外采样。如果内核编译时启用了 CONFIG_VIRT_CPU_ACCOUNTING虚拟 CPU 时间记账情况会复杂一些account_process_tick 可能直接返回改由 vtime 机制在用户态/内核态切换的边界处精确计算。这部分留到第四节讲。2.2 函数全览cputime.c 里都有哪些记账员打开 cputime.c核心函数不算多我整理了一个速查表函数职责account_user_time把一段 CPU 时间计入当前进程用户态并更新 user/nice 统计account_system_time把一段 CPU 时间计入内核态同时区分硬中断/软中断/普通系统态account_process_tick调度器周期性 tick 的入口根据上下文决定走哪条记账分支account_guest_time处理 guest 模式下运行虚拟 CPU 的时间account_steal_time记录被 hypervisor 偷走的 CPU 时间account_idle_time记录 CPU 空闲时间顺带处理 iowait 的判定thread_cputime_adjusted / thread_group_cputime_adjusted对外输出前做时间对齐和边界修正cputime_adjustgetrusage 等接口的最终调整逻辑每次 tick 进来核心决策都集中在 account_process_tick 里其他函数都是它调用的分账员。2.3 这里不处理 vruntimecputime.c 不管调度公平性很多刚开始读调度器代码的人容易把两件事混在一起CFS 调度器里有个 vruntime调度时靠它挑选欠债最多的进程而 cputime.c 里统计的 utime/stime是给人看的累计 CPU 时间。两者有本质区别。vruntime 是调度实体按权重归一化之后的虚拟运行时间它的粒度更细和物理时间不成一一对应关系因为它要考虑 nice 值的权重。而 cputime.c 统计的是实实在在的纳秒不管 nice 值高低一纳秒就是一纳秒。看代码时如果发现 update_curr 不走 cputime.c不用奇怪那是另一条独立的记账链路。3. account_process_tick 的决策逻辑当一个 tick 落到当前进程头上3.1 user_tick 怎么来的被打断时寄存器的用户态标志直接看核心函数我贴一段较新内核中 account_process_tick 的典型实现void account_process_tick(struct task_struct *p, int user_tick) { u64 cputime TICK_NSEC; if (vtime_accounting_enabled_this_cpu()) return; if (sched_clock_irqtime) { irqtime_account_process_tick(p, user_tick, TICK_NSEC); return; } if (user_tick) account_user_time(p, cputime); else if (p ! this_rq()-idle) account_system_time(p, HARDIRQ_OFFSET, cputime); else account_idle_time(cputime); }这段代码的关键是 user_tick 这个参数。它在时钟中断处理的早期由 user_mode(regs) 得到如果被打断时的 CS 段寄存器指向用户态就是 1如果指向内核态就是 0。这个判断非常便宜因为 regs 已经在中断栈上了。另一个容易被忽略的点是 TICK_NSEC。传统 tick 记账模式下默认一个 tick 就是纳秒表示的 1/HZ 秒。如果 HZ1000TICK_NSEC 就是 1,000,000 纳秒。也就是说不管这个进程在过去 1ms 里实际只跑了 0.3ms 还是 0.9ms在 tick 记账模式下都按整 tick 记。这是精度的最大来源。3.2 account_user_time / account_system_time 的内部分配先看用户态记账void account_user_time(struct task_struct *p, u64 cputime) { int index; p-utime cputime; account_group_user_time(p, cputime); index (task_nice(p) 0) ? CPUTIME_NICE : CPUTIME_USER; cpustat[CPUTIME_CPU(index)] cputime; acct_account_cputime(p); }前两行更新进程私有时间第三行更新 per-CPU 全局统计index 根据 nice 值区分这个 tick 算 user 还是 nice。这里也调了 account_group_user_time它会把时间累加到线程组signal_struct 里的 cputime 字段这样 getrusage 读取整个进程的 CPU 时间时不需要遍历所有线程。系统态记账会复杂一点因为它要判断这次内核态时间到底属于普通系统调用、硬中断还是软中断void account_system_time(struct task_struct *p, int hardirq_offset, u64 cputime) { int index CPUTIME_SYSTEM; if ((p-flags PF_VCPU) (irq_count() - hardirq_offset 0)) { account_guest_time(p, cputime); return; } p-stime cputime; account_group_system_time(p, cputime); if (hardirq_count() - hardirq_offset) index CPUTIME_IRQ; else if (in_serving_softirq()) index CPUTIME_SOFTIRQ; cpustat[CPUTIME_CPU(index)] cputime; acct_account_cputime(p); }发现一个很有意思的细节p-stime 无论 irq 还是 softirq 都会累加也就是说硬中断和软中断的时间最终也会记到当前正在运行的进程的内核态时间上。这个设计经常引起误解我单独展开讲。3.3 中断时间为什么记到当前进程名下很多人第一次看统计时会疑惑我写了个纯计算程序没有系统调用为什么 sys 时间不是 0因为在它运行期间网络包到了、磁盘中断来了中断处理程序跑在当前进程的上下文里这段时间被算进了当前进程的 stime同时计入全局的 irq/softirq 统计。这是内核的局部归因策略中断处理程序本质上打断了谁就把开销算到谁头上。它不反映谁请求了这个中断只反映这个 CPU 上当时是谁在跑。所以做性能分析时看到一个进程 sys 时间高不能马上断定是它系统调用太多还要看全局 irq 和 softirq 时间是不是同样很高。我后面实战部分会再提这个坑。3.4 idle 和 iowait 的特殊处理回到 account_process_tick第三种情况是当前任务是 idle 进程p this_rq()-idle说明 CPU 没有可运行的任务这段 tick 记到 idle 或者 iowait。这里又有一个判断如果当前 CPU 上有进程在等待 IOnr_iowait_cpu 计数大于 0这段空闲时间会被记成 iowait否则记成 idle。注意 iowait 并不是IO 设备工作的时长而是CPU 因为等待 IO 完成而空闲的时长。两者在语义上经常被混淆。一个进程在做大量磁盘读时CPU 确实没事干所以 iowait 高往往伴随磁盘瓶颈但它本质上是 CPU 的空闲分类。4. 记账精度之战从固定 tick 到纳秒级 vtime4.1 TICK_CPU_ACCOUNTING一切按 1/HZ 量子化传统配置 CONFIG_TICK_CPU_ACCOUNTING 下CPU 时间就像用一把 1/HZ 秒的刻度尺去量。HZ250 时最小精度 4msHZ1000 时最小精度 1ms。这把尺子的问题很明显一个只跑了 0.1ms 的短命进程可能完全落不到任何 tick 边界上最终统计为 0反过来一个进程可能实际只跑了 0.2ms但恰好 tick 落在它身上就被记了整整 1ms。对长时间运行的任务来说误差不大但对频繁短促运行的进程统计会显得很毛糙。早期内核用 jiffies 做单位时更粗糙很多地方还存在字节对齐和进位丢失的问题。后来引入高精度定时器tick 本身可以做到很准但统计量子仍然是 1/HZ除非开启虚拟 CPU 时间记账。4.2 VIRT_CPU_ACCOUNTING_GEN切换边界打时间戳为了避免刻度尺误差内核提供了更精确的记账方式典型代表是 CONFIG_VIRT_CPU_ACCOUNTING_GEN通用虚拟 CPU 时间记账。它的核心思路是不再等 tick 到来才累计一个固定值而是在用户态和内核态切换的瞬间打时间戳用高精度时钟算出这一段实际跑了多久然后精确累计。想象你要统计一个人每天在办公室和家里各待多久tick 模式是每小时看一眼他在哪vtime 模式是每次他跨门槛的瞬间记录时间。显然 vtime 准确得多。代价是代码复杂度暴涨用户态进入内核时要 vtime_user_exit从内核返回用户态时要 vtime_user_enter中间还要处理 NMI、guest 模式、idle 等无数边界情况。cputime.c 里长长一串 vtime_ 开头的辅助函数都是为这些边界服务的。在 vtime 模式下account_process_tick 第一行就会直接 return因为它知道 tick 记账是粗活自己这边已经用更细的颗粒度把账记完了。这也是为什么阅读 cputime.c 时经常看到 vtime_accounting_enabled_this_cpu 这个分支判断。4.3 为什么 4.7 内核把 cputime_t 统一成了纳秒这里要提一个影响深远的历史变更。早期 cputime_t 在 CONFIG_VIRT_CPU_ACCOUNTING_NATIVE 下可能是 jiffies在另一个配置下又可能是 clock_t或者在 s390 这种特殊架构上还有自己的表示。所有记账函数都要调用各种转换宏比如 cputime_to_nsecs、jiffies_to_cputime代码里到处都是类型转换的地雷。大约在 4.7 时代内核社区把 cputime_t 彻底废除统一用 u64 纳秒。这一步看似小实际影响很大所有 account_* 函数参数从一个不确定的 cputime_t 类型变成了明确的纳秒数。TICK_NSEC 就是纳秒采样到的 delta 也是纳秒转换只在输出到用户空间时才做。代码可读性提升一个档次也减少了不少舍入错误。4.4 精度提升之后依然存在的统计误差即便内核内部已经精确到纳秒对外输出时还是可能看起来不准。因为 /proc/pid/stat 和很多系统调用仍以 clock tick通常 100Hz为单位返回纳秒需要除以 10,000,000 做一次量化。所以一个 15ms 的进程在 /proc 里可能显示 utime1、stime0与真实值有出入。内核对这个问题的处理很有意思cputime_adjust 会在输出前做边界修正确保同一线程组内所有线程的 utimestime 之和不超过实际消耗的 CPU 时间。简单说宁可输出值四舍五入得规整些也不能出现子线程时间加总比进程实际 CPU 时间还大的荒谬结果。这个调整逻辑本身就是个单独的函数有兴趣的可以去看 cputime_adjust 的注释。5. 虚拟化场景下的特殊账本guest time 与 steal time5.1 account_guest_time客户机的时间怎么处理云计算普及之后虚拟化场景下的 CPU 时间统计变得尤为重要。当宿主机上的 KVM 进程运行一个 vCPU而这个 vCPU 正在执行客户机里的用户态代码时对宿主机来说这段 CPU 时间依然是当前 KVM 进程在内核态还是用户态运行答案是KVM 通过 VCPU_RUN 进入客户机时当前进程会被打上 PF_VCPU 标志。此时如果 tick 中断发生account_system_time 会看到 irq_count 为 0 且 PF_VCPU 置位于是不按普通系统时间记账而是转交给 account_guest_timevoid account_guest_time(struct task_struct *p, u64 cputime) { p-gtime cputime; account_group_guest_time(p, cputime); if (task_nice(p) 0) { cpustat[CPUTIME_GUEST_NICE] cputime; cpustat[CPUTIME_NICE] cputime; } else { cpustat[CPUTIME_GUEST] cputime; cpustat[CPUTIME_USER] cputime; } }看这段代码有个细节guest 的时间除了记到 CPUTIME_GUEST还会同时加到 CPUTIME_USER。也就是说在全局统计里 guest 时间被算作了用户态时间的一个子集。为什么会这样马上讲。5.2 /proc/stat 中 guest 被算进 user 的兼容性 trick在 /proc/stat 的 cpu 行里第 9、10 列分别是 guest 和 guest_nice。man proc 文档里明确写着guest 时间是 user 时间的子集计算总 CPU 使用率时把它加进 user 会对齐老工具的预期。为什么需要这个 trick因为很多历史悠久的监控脚本只读取前 4 列user、nice、system、idle并不知道 guest 字段的存在。如果 guest 时间不归纳到 user 里这些脚本计算出的 CPU 使用率会明显偏低。内核为了兼容旧工具宁可让 guest 时间在两个字段里出现两次也不破坏这些脚本的统计口径。这也导致一个新现象如果你在宿主机上看到 user 使用率很高但实际跑的都是客户机负载别急着怀疑宿主机上有挖矿进程先看 guest 列是不是同样很高。5.3 steal time被 hypervisor 偷走的时间如果说 guest time 是客户机怎么算贡献steal time 就是客户机怎么算委屈。在虚拟化环境下物理 CPU 是被多个 vCPU 共享的当你的 vCPU 想跑但 hypervisor 把它调度到别处这段时间你的虚拟机里的 CPU 就白白等着什么活都没干。x86 的半虚拟化时钟机制会在一个 steal time page 里持续更新最近被偷走的时间。客户机内核在 tick 处理时会去读取这个页把差值累计到 CPUTIME_STEAL。偷走的时间不记给任何进程也不会算进 idle因为 CPU 既没在跑自己的代码也没在闲着等待自己的任务它是被别人抢走了。5.4 云上排障steal 高该找谁我有个习惯云服务器 CPU 使用率异常但不知道瓶颈在哪时第一件事就是看 mpstat$ mpstat -P ALL 1 Linux ... (hostname) ... # 输出省略 10:20:01 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 10:20:02 all 12.5 0.00 3.00 0.25 0.00 0.50 20.00 0.00 0.00 63.75如果 %steal 长期高于 5% 甚至 10%说明这个实例正和其他邻居激烈争抢物理核。这时候你调代码、加并发都没太大意义更合理的做法是换大规格实例、换平台保证 QoS或者在业务低峰期调度重任务。一个常见的误判是把高 steal 当成程序问题把 user 时间很低但 real 时间很长当成程序卡了其实是被邻居偷走了 CPU。6. 从内核统计到命令行工具读数链路与换算陷阱6.1 /proc/pid/stat 的 tick 单位与 USER_HZ 换算前面说过内核内部记账单位是纳秒但输出到 /proc/pid/stat 时utime 和 stime 字段用的仍然是 clock tick。这个 tick 单位由内核的 USER_HZ 决定通常就是 100。也就是说一个字段值 100 代表这个进程累计消耗了 1 秒 CPU 时间。在用户态可以用 getconf CLK_TCK 查到这个值$ getconf CLK_TCK 100手动读取当前进程的 utime/stime 很简单$ awk {print utime $14, stime $15} /proc/self/stat utime0 stime0把这两个值除以 CLK_TCK就是秒数。如果你的监控脚本直接把 $14 当毫秒用那就会差出一个 10 倍的量级这是我见过最多人踩的坑。6.2 /proc/stat 十列数字的排布/proc/stat 的 cpu 行是系统整体 CPU 时间的最权威来源各列含义有严格顺序列字段含义1user用户态时间2nice低优先级用户态时间3system内核态时间4idle空闲时间5iowait等待 IO 的空闲时间6irq硬中断处理时间7softirq软中断处理时间8steal被 hypervisor 偷走的时间9guest运行客户机的时间并计入 user10guest_nicenice 客户机时间并计入 nice有经验的性能工程师会连续两次读取 /proc/stat用差值除以时间间隔算出各状态占比这就是 top 和 mpstat 的底层逻辑。注意单位也是 USER_HZ tick读出来如果是 10000代表 100 秒。6.3 top、pidstat、time 各是怎么算的top 显示进程 CPU% 时读取 /proc/pid/stat 得到 utimestime 的累计值两次采样做差再除以采样间隔。由于它统计的是进程在所有 CPU 上的总执行时间多线程进程完全可能显示 200%、300%。看 user 和 sys 的拆分应该用 pidstat$ pidstat -u -p 12345 1 10:15:01 UID PID %usr %system %guest %wait %CPU CPU Command 10:15:02 0 12345 12.5 3.00 0.00 2.00 15.50 3 myserver这里 %usr 和 %system 分别来自 utime 和 stime 的差值%guest 来自 gtime%wait 是在等待运行时被抢占的时间%CPU 是全部 CPU 时间占比的总和。注意 %CPU 不一定等于 %usr %system %guest因为它还包含其他细项最好以实际输出为准。time 命令则走的是 wait4/getrusage 这条链路直接拿进程退出时的 ru_utime 和 ru_stime。shell 内建 time 和 /usr/bin/time 输出粒度也有差异但数据源都在内核的 cputime 统计体系里。6.4 getrusage 与 clock_gettime用户 API 的最终出口用户态要拿自己进程的 CPU 时间最常用的是 getrusage(RUSAGE_SELF)它返回 struct rusage 里的 ru_utime 和 ru_stime。这两个值的来源是前面提过的 thread_group_cputime_adjusted也就是说它会汇总整个线程组所有线程的时间并且经过 cputime_adjust 的边界修正。如果要更精确、更高频地度量用 clock_gettime(CLOCK_PROCESS_CPUTIME_ID) 或 CLOCK_THREAD_CPUTIME_ID它们拿到的就是内核纳秒级累计值没有 USER_HZ 量化损失。性能分析工具 perf stat 内部也是走类似的高精度路径。注意 CLOCK_PROCESS_CPUTIME_ID 是所有线程消耗的 CPU 时间总和在多核并行时这个值会大于墙钟时间。如果你的测试脚本用这个值和 wall time 比较看到超过 100% 不必惊讶。7. 实战复盘几个把我绕进去过的统计怪现象7.1 短任务 user/sys 双双为 0不是错觉有次我排查一个响应极快的 HTTP 服务单个请求只要 2ms用 time 命令测 curl 的输出经常是 user 0.00s sys 0.00s。当时差点以为服务没在工作后来才反应过来是统计精度的问题2ms 的进程在 100Hz 的 USER_HZ 刻度下四舍五入就是 0 个 tick。真正想测量这种短任务用 perf stat 会稳很多$ perf stat -e task-clock,cycles,instructions sleep 0.01它基于高精度事件不受 tick 量子化影响。这也是一个经验法则任务越短越不能只听 time 和 top 的。7.2 多线程进程 CPU% 超过 100% 是合法的top 里看到一个 Java 进程 CPU% 显示 380%很多运维新手以为显示 bug。实际上 top 的 %CPU 是按所有线程的 CPU 时间总和除以采样间隔算的。一个 4 线程程序如果满负荷跑满 4 个核CPU% 就能到 400%。类似的clock_gettime(CLOCK_PROCESS_CPUTIME_ID) 读出的时间也可能超过墙钟时间。我在做并发压测脚本时看到耗时 10 秒的压测进程CPU 时间是 35 秒就是因为它在 8 个核上并行跑。区分CPU 时间和墙钟时间在做量化分析时是第一课。7.3 real 比 usersys 大很多或不匹配时的排查方向time 输出的三个数存在一个简单关系如果程序是单线程计算密集real 应该约等于 user sys如果 real 明显大于这两者之和说明程序在等——等 IO、等锁、等网络或者在虚拟机上等物理核steal。比如 real 5s、usersys 1.5s差出来的 3.5s 一般是下面几种情况之一程序大部分时间在 sleep 或等待 IO 完成程序在等待互斥锁或条件变量虚拟机被 hypervisor 抢占%steal 高程序发生大量缺页等待磁盘换页我常用的一个快速分流命令是同时看 usersys 和 steal$ mpstat -P ALL 1 | grep -E CPU|all如果 %steal 接近 0再去看等待链路的系统调用基本就能锁定方向。7.4 高 %sys 的定位思路从统计看内核路径纯用户态计算程序 %sys 不应该高一旦出现高 %sys我会按下面的思路排查先确认是不是中断/软中断被打到了当前进程上。看 /proc/softirqs 和 /proc/interrupts 的增量高位数的软中断往往来自网络收包。如果网络流量不大再考虑系统调用是不是太频繁。用 strace -c 统计一次运行中的系统调用分布。最后用 perf top 看内核热点比如是不是大量时间花在锁、内存分配或者协议栈处理上。有一次我遇到一个服务 %sys 居高不下用 perf top 看到一个函数占了很大比例native_queued_spin_lock_slowpath这说明有严重的自旋锁竞争。顺着调用栈往下查发现是多线程同时往一个全局链表写数据改成无锁队列后 %sys 立刻降下来了。如果中断时间占比高但当前进程时间看起来并不高那就要小心 3.3 节说的局部归因策略——这些中断时间确实是记到了某个进程头上但可能分散在多个短命进程里全局看更清楚。最后分享一个我平时用的查账小习惯通过对 /proc/pid/stat 写一个简单的观察循环可以看到进程在某个阶段 user/sys 的实时变化。用这个办法可以快速判断一个程序的负载特征——是计算密集user 增、sys 低还是系统调用密集sys 增、user 低还是主要卡在 IO 等待两者都不怎么涨但 real 时间在走。这套基于 cputime.c 的统计体系虽然精度有上限但把它的记账规则摸透之后绝大部分性能问题的第一轮判断就够用了。
返回列表