暗无天日

=============>DarkSun的个人博客

读:sysstat 诊断链——从 sar 到 pidstat 的排查路径

引子

系统变慢的时候,大多数人的第一反应是敲 top 。这没问题。 top 给你一个即时快照:谁在用 CPU、内存还剩多少、load average 大概什么水平。

top 有两个盲区。第一,它只看当下。凌晨 3 点发生的性能问题,你早上 9 点坐到工位时 top 已经什么都看不到了。第二, top 能看到每个进程用了多少 CPU,但很难同时看清三件事:哪个核在忙、忙的是哪个线程、当时有多少进程在排队等 CPU。这三个信息分散在不同的视图里,手工拼凑太慢。

sysstat 套件补上了这两个盲区。它包含四个核心工具: mpstatpidstatsariostat 。TecMint 上有篇 文章 系统介绍了它们的用法。本文不逐条翻译那 20 个示例,改用一条真实的排查链路把它们串起来,从发现异常到定位根因。

安装和启用

sysstat 大部分发行版默认不装:

# Debian/Ubuntu
sudo apt install sysstat
# RHEL/Fedora
sudo dnf install sysstat
# Arch Linux
sudo pacman -S sysstat

装完确认版本:

mpstat -V
sysstat 版本 12.7.9
(C) Sebastien Godard (sysstat <at> orange.fr)

Ubuntu 用户特别注意 :安装后数据收集默认是关闭的。需要编辑 /etc/default/sysstat ,把 ENABLED="false" 改成 ENABLED="true" 。否则 sar 不会自动记录历史数据,而这是整篇文章最有价值的功能,漏掉等于白装。

诊断第一环:sar -u 回看历史

场景:早上到工位,同事说昨晚系统很卡。你可以通过 sar 回顾当时的系统指标。

sar 通过 cron 定时采集系统指标,存成二进制日志。你可以指定任意时间段回放。

配好 cron 自动收集(这一步是整篇文章最重要的事):

# 每 10 分钟采集一次
*/10 * * * * /usr/lib/sysstat/sa1 1 1   # Debian/Ubuntu
# 每天 23:53 生成可读报告
53 23 * * * /usr/lib/sysstat/sa2 -A

sa1 / sa2 的路径因发行版不同:

  • Debian/Ubuntu: /usr/lib/sysstat/sa1
  • RHEL/Rocky: /usr/lib64/sa/sa1
  • Arch Linux: /usr/lib/sa/sa1

Debian/Ubuntu 下 which sa1 能找到,Arch 下 sa1/usr/lib/sa/ 而不在 PATH 里,用 ls /usr/lib/sa/sa1 确认。确认路径后替换 cron 条目中的对应位置。

确认数据在积累:

ls -lh /var/log/sysstat/

回放历史 CPU 数据:

sar -u -f /var/log/sysstat/sa$(date +%d)

sar -u 的输出项跟 mpstat 类似( %=user%=system%=iowait 等),关键在于它能告诉你问题发生的具体时段。知道时间点之后,后面的工具才有用武之地。

如果你不需要回放历史、只想看当前的连续采样,让 sar 交互式跑几轮:

sar -u 2 5

2 5 的意思是每 2 秒采一次,采 5 次。最后会有一行 Average: 汇总。

诊断第二环:mpstat -P ALL 定位热核

sar 告诉你"昨晚 10 点到 11 点 CPU 偏高",接下来要回答"是所有核都忙,还是单核被打满"。

mpstat 报告逐核 CPU 使用率。不加参数时只给一个全局汇总:

mpstat

加了 -P ALL 之后,每个 CPU 核心一行:

mpstat -P ALL 2 5

这时候你可能看到类似这样的输出:

CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
all   19.87    0.01    3.02    0.88    0.00    0.08    0.00    0.00    0.00   76.14
  0   72.14    0.00    5.22    0.00    0.00    0.00    0.00    0.00    0.00   22.64
  1   14.32    0.03    2.88    0.52    0.00    0.08    0.00    0.00    0.00   82.17
  2   10.44    0.02    2.71    1.98    0.00    0.12    0.00    0.00    0.00   84.73
  3    8.11    0.01    1.75    3.52    0.00    0.09    0.00    0.00    0.00   86.52

CPU 0 的用户态使用率 72%,其他三个核几乎空闲。这是经典的单线程进程打满单核的特征。

几个值得注意的列:

  • %iowait :持续超过 10-15% 意味着系统在等磁盘读写。这时候加 CPU 没用,问题在磁盘。
  • %soft :软件中断占比。网络数据包到达后,内核需要软中断来处理协议栈(TCP/IP 拆包、路由等)。 %=soft 高通常意味着网络流量打满了,CPU 在忙于收发包而非跑你的应用程序。

如果怀疑中断不均衡(比如网卡中断全压在 CPU 0 上),用 -I ALL 看中断分布:

mpstat -I ALL

NET_RX/s 集中在某个核上、同时该核 %soft 偏高,说明网卡中断亲和性(网卡硬中断固定发给某个 CPU 处理)需要调整。要么用 irqbalance 服务自动分配中断到多个核,要么手动写 /proc/irq/<编号>/smp_affinity 把特定中断号绑到其他 CPU 上。

诊断第三环:pidstat 找到凶手进程

mpstat 告诉你"CPU 0 被打满了",下一个问题自然是"谁打的"。

pidstat 报告进程级别的 CPU、内存、I/O 使用率。不加参数列出所有活跃进程:

pidstat
UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
1000     2841   18.44    0.88    0.00    0.00   19.32     0  python3
1000     3102    0.12    0.04    0.00    0.00    0.16     2  nginx

CPU 列显示该进程最后跑在哪个核上,和 mpstat 的热核直接对应。

%=wait 是一个容易被忽略但非常重要的列(sysstat 11.5 引入):它表示进程就绪后在运行队列里等待 CPU 调度的时间。这项持续非零,意味着 CPU 不够用,就绪队列里堆了太多工作,不是进程本身慢。

有了可疑的 PID,用 -t 展开线程看细节:

pidstat -t -p 2841 2 3
TGID       TID    %usr %system  %guest   %wait    %CPU   CPU  Command
2841         -   18.00    0.88    0.00    0.00   18.88     0  python3
   -      2841   17.50    0.50    0.00    0.00   18.00     0  |__python3
   -      2844    0.50    0.38    0.00    0.00    0.88     1  |__ThreadPool-1

输出里主线程( python3 )占 18.00% CPU,线程池( ThreadPool-1 )仅 0.88%。而且两行都是 %wait 为零、 %=usr 占大头,说明线程没有在等 CPU,也没有在等 I/O,就是在做纯用户态计算。这样可以定位:CPU 消耗集中在这个进程的主线程上,检查主线程的执行路径就能找到耗 CPU 的代码。

如果进程名已知,用 -G 直接过滤,不用在进程列表里找:

pidstat -G nginx 2

诊断第四环(条件):追 I/O 和内存

如果 mpstat 发现 %iowait 偏高

pidstat -d 报告每个进程的磁盘读写量:

pidstat -d 2
PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
2841    412.00     88.00      4.00      12  python3

iodelay 列是非零值时,该进程就是在等 I/O。和 mpstat 里高 %iowait 互相印证,凶手就确认了。

sar -d 则从块设备层面看延迟:

sar -d 1 3
  • await :请求从排队到完成的总时间
  • aqu-sz :平均队列深度

await 持续高但 aqu-sz 接近零,说明磁盘本身就慢(比如机械盘随机读写)。 await 高且 aqu-sz 也在 1 以上,则是排队已形成,磁盘处理不过来。

注意:旧文档常提的 svctm 字段在新内核中已废弃,sar 输出里该列不再可靠,不要依赖它做判断。

如果怀疑内存压力

sar -r 报告内存使用情况。重点看 %=commit 列:

sar -r 1 3

%=commit 是内核承诺给进程的虚拟内存总量相对于物理内存 + swap 的比例。超过 100%,意味着所有进程同时要求兑现承诺的话,OOM killer 就会出动。

pidstat -r 则从进程视角看内存:

pidstat -r -p 2841 2 3

VSZ 是虚拟内存, RSS 是物理内存。重点看 majflt/s (主缺页)列:主缺页意味着内核必须从磁盘捞回被换出的页面,持续非零说明系统在颠簸(thrashing),进程响应速度会明显下降。次缺页( minflt/s )不贵,只是内核在物理内存里映射页面而已。

贯穿始终:sar 历史 + sadf 导出

sar 真正的价值不是交互式采样(这个 mpstat 和 pidstat 也能做),而是自动收集的历史数据。配好 cron 后,任何发生过的性能问题你都可以事后回放。

回放历史数据时,用 sadf -d 导出为 CSV 格式,可以在表格里画图、和同事分享:

sadf -d /var/log/sysstat/sa$(date +%d) -- -n DEV | grep -v lo

排查链总结

整个过程是一条线:

  1. sar -u 回看历史,确定问题时段
  2. mpstat -P ALL 定位热核,看是均匀分布还是单核瓶颈
  3. pidstat 找到具体进程, -t 展开线程定位
  4. 如果 %iowait 高, pidstat -d + sar -d 追磁盘;如果怀疑内存, sar -r + pidstat -r 查换页

最值得现在做的事:把 sar 的 cron 采集配好,攒一周基线数据。有问题发生时,有历史数据和没有历史数据,排查时间是分钟级和小时级的差别。

Linux : sysstat : 性能诊断 : mpstat : pidstat : sar : iostat