linux下通过/proc/stat两次采样做差值计算每核cpu使用率,字段含user、nice、system、idle等,需用unsigned long long读取,间隔≥100ms;进程级上下文切换用getrusage()获取累计值,再差分得速率。

Linux下用/proc/stat解析每核CPU使用率
Linux内核通过 /proc/stat 暴露各CPU核心的累计时间统计,这是获取每核详细占用最轻量、最可靠的方式。关键不是“实时采样”,而是两次读取做差值计算——单次读取只有累计值,无法反映瞬时负载。
每行形如 cpu0 123456 789 12345 678901 ...,字段依次为:user、nice、system、idle、iowait、irq、softirq、steal、guest、guest_nice(内核版本 ≥2.6.33)。注意:idle 和 iowait 都计入空闲时间,但 iowait 表示 CPU 等待 I/O 完成,严格来说不算“真正空闲”。
- 必须至少间隔 100ms 以上采样,否则差值太小、浮点精度失真严重
- 所有字段都是无符号 long,需用
unsigned long long读取,否则在高 uptime 机器上会溢出 -
cpu总行(不带数字)是所有核心之和,不能用来反推单核;必须逐行解析cpu0、cpu1等
用getrusage()或/proc/[pid]/status查进程级上下文切换
进程自身的上下文切换次数只能从内核暴露的接口获取,getrusage(RUSAGE_SELF, &ru) 返回的 ru.ru_nvcsw(自愿切换)和 ru.ru_nivcsw(非自愿切换)是最直接的 C++ 标准方式。但要注意:这两个值是**自进程启动以来的累计值**,不是速率。
若需每秒切换次数,必须自己维护上次调用的时间戳和计数值做差分。另外,/proc/[pid]/status 中的 voluntary_ctxt_switches 和 nonvoluntary_ctxt_switches 字段与 getrusage 值一致,可作交叉验证——但不要混用两个来源做同一指标,因读取时机不同会导致微小偏差。
-
ru_nivcsw显著升高通常意味着 CPU 时间片被抢占,可能是系统负载高、或进程被调度器频繁踢出(比如大量锁竞争、高优先级任务抢占) -
ru_nvcsw高常见于频繁阻塞调用(如小包 socket read、短 sleep、mutex contention),不一定是性能问题,但值得结合堆栈分析 - glibc 的
getrusage在容器中仍有效,但若进程被 cgroups 限频,ru_stime/ru_utime可能增长变慢,而上下文切换数不受影响
用perf_event_open()捕获精确的每核调度事件(需root)
如果需要知道“本进程在哪个核上被切换出去/进来”,或者想关联上下文切换与具体函数调用,perf_event_open() 是唯一办法。它能监听 PERF_COUNT_SW_CONTEXT_SWITCHES(已废弃)或更推荐的 PERF_COUNT_SW_CPU_MIGRATIONS + PERF_EVENT_IOC_RESET 控制,但代价是必须有 CAP_SYS_ADMIN 权限,且默认被 seccomp 或容器运行时禁用。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
实际工程中极少直接用它做常规监控——开销大、权限要求高、输出需 mmap 解析。更可行的做法是:用 perf record -e 'sched:sched_switch' -p [pid] 手动抓一次 trace,再用 perf script 分析,用于定位特定卡顿原因,而非持续采集。
- 即使以非 root 运行,
perf_event_paranoid设为 -1 也能启用部分事件,但 per-CPU 切换事件仍受限 - 每个 perf fd 绑定一个 CPU 核心,要监控全部 N 核,需打开 N 个 fd 并分别 mmap,代码复杂度陡增
- 返回的
struct perf_event_mmap_page中data_tail和data_head是 ring buffer 边界,必须用内存屏障(__sync_synchronize())读取,否则可能读到乱序数据
别把 /proc/[pid]/stat 的 stime/utime 当作 CPU 占用率
新手常误以为读取 /proc/[pid]/stat 第 14(utime)和 15(stime)字段,除以采样间隔就能得 CPU 使用率。这是错的:这两个值单位是 CLK_TCK(通常 100Hz),表示**该进程在用户态/内核态消耗的总时钟滴答数**,不是 wall-clock 时间。它受系统负载、调度策略、cgroup 配额等影响,不能直接换算成百分比。
正确做法是:用 clock_gettime(CLOCK_MONOTONIC, &ts) 记录 wall-clock 时间差 Δt,同时用 getrusage() 获取两次的 ru_utime.tv_sec + ru_stime.tv_sec 差值 Δcputime,再算 (Δcputime / Δt) * 100 得到平均 CPU 占用率(注意单位统一)。但这仍是**进程整体**占用,无法拆到每核。
- 若进程启用了 SMT(超线程),
Δcputime可能大于Δt(比如双线程满载,理论最大 200%) -
/proc/[pid]/stat的第 22 字段processor表示上次运行的 CPU 核号,仅快照值,不可用于统计 - 在容器中,
CLK_TCK不变,但 cgroup 的 cpu.cfs_quota_us 会限制Δcputime上限,此时比率可能长期接近 100%,但实际是被 throttled 而非真忙
每核 CPU 占用和上下文切换本质是两类指标:前者看资源消耗分布,后者看调度行为扰动。硬要把它们对齐到同一时间粒度再关联分析,容易陷入采样相位误差——比如你每 200ms 读一次 /proc/stat,但上下文切换可能集中在某 5ms 内爆发,差分后就平滑掉了。真要深挖,得用 perf 抓原始事件流,而不是靠轮询拼凑。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










