必须手动差分计算才能获得真实走势,因vmstat的cs列仅为滑动窗口平均值,pidstat首行是累计值,且二者均不包含中断上下文切换,需读取/proc/[pid]/status并用steady_clock采样求差。

不能直接“看走势”,必须自己采样计算增量速率——所有现成命令只给瞬时值或累计值,原始数据本身是单调递增的计数器,不差分就没有趋势。
vmstat 1 的 cs 列不是走势,只是滑动窗口平均值
vmstat 1 输出的 cs 是「自上次采样以来」的上下文切换次数除以间隔(默认1秒),它本质是离散速率点,不是连续曲线。连续观察 10 行 cs 值,看起来像走势,但有严重缺陷:
- 首次启动时数值常偏高(systemd、日志刷盘等初始化行为干扰)
- 采样间隔受调度延迟影响,
sleep(1)实际可能延迟 20–50ms,导致速率计算失真 - 无法区分自愿/非自愿,更无法关联到具体进程,纯系统总量掩盖根因
- 若想导出绘图,需手动记录多行输出再做后处理,没法直接喂给 gnuplot 或 Grafana
pidstat -w 1 能定位进程,但首行是累计值
pidstat -w 1 每秒输出一次,但第一行显示的是自进程启动以来的累计切换数,第二行起才是每秒增量。这意味着:
- 直接把第一行
cswch/s当作速率会严重高估(尤其对长生命周期进程) - 输出默认只显示
cswch/s或nvcswch/s非零的进程,静默过滤掉低频切换者,容易漏掉“多个中等切换进程叠加”的场景 - 线程级需加
-t,但老版本 sysstat(如 RHEL 7 自带的 10.1.5)会忽略-w,输出空——务必先跑pidstat -V确认版本 ≥ 11.0.0 - Java 应用必须加
-t,否则看到的进程级cswch/s是所有线程平均值,掩盖单个死锁线程的 5000+/s 切换
真正能画走势的只有 /proc/[pid]/status + 差分计算
要获得精确、可绘图的动态走势,必须自己读 /proc/self/status(或指定 PID),提取 voluntary_ctxt_switches 和 nonvoluntary_ctxt_switches,两次采样求差再除以时间间隔:
- 用
std::chrono::steady_clock记录时间戳,别用sleep()——后者精度差且不可控 - 每次读取后调用
seekg(0)复位文件指针,比反复open/close轻量得多 - 差值为负时说明进程重启或计数器重置(极少见),该点应丢弃或打 warning 日志
- 采样间隔建议 100ms–1s:低于 100ms 噪声大、开销反升;高于 1s 会错过短时脉冲(如定时任务 fork 风暴)
- 解析时用
std::stoull(),别用atoi()或std::stoi()——计数器可能超int范围
别忽略中断上下文切换这个隐藏变量
vmstat 的 cs 和 pidstat -w 的和永远对不上,因为前者含中断上下文切换,后者不含。如果 in(每秒中断数)和 cs 同步飙升,比如 in=4500 且 cs=12000,那真正该查的是网卡软中断(cat /proc/softirqs | grep NET)或磁盘 IO,而不是盯着 Java 进程调优:
-
vmstat的cs是内核全局视角,pidstat是用户态任务视角,二者统计口径不同,强行对齐无意义 - 中断上下文切换不走进程调度路径,不计入
voluntary_ctxt_switches,但消耗 CPU 时间、挤占正常任务 - 高频中断(如 RSS 网卡队列绑定不当)会导致
ksoftirqd占用大量 CPU,此时pidstat -w显示各进程nvcswch/s很低,但系统响应迟钝
真正难的不是拿到数字,而是判断哪个数字该信、哪个该扔——比如 cs 突然跳到 8000,但 r=0、b=0、in=300,那大概率是 systemd 或 logrotate 的一次性抖动,盯 5 秒就回落;而 cswch/s 持续 300 且 %CPU 不高,就得立刻 strace -p PID -e trace=read,write,epoll_wait 看卡在哪类系统调用上。











