Linux 工具实战:内存、CPU 与磁盘 I/O 监控

看到内存用了 90%、磁盘 %util 达到 100%,很容易立刻想加内存、换 SSD。但这些数字究竟描述谁、哪个时间窗口、什么单位?如果口径没对齐,优化可能只是把钱花在没有证据的猜测上。

本文使用 freevmstatiostatsar 建立从“当前状态”到“资源压力”再到“历史关联”的排障思路。它们用于提出和检验假设,不是四个自动诊断按钮。

系列阅读:学习地图 · 进程与线程排障 · 本篇 · ELF 与链接 · 传输与任务调度。Shell 管道基础可以回看 原有 Shell 笔记

先统一观察口径

读一个监控数字前,先确定四件事:

问题 例子 不核对会发生什么
对象 整机、虚拟机、容器、进程、某个设备 把其他工作负载算到自己头上
时间 即时状态、最近一秒、启动以来平均 将历史均值误判成当前峰值
单位 B、KiB、MB/s、毫秒、百分比 比较结果差几个数量级
边界 CPU 配额、内存上限、设备层级 宿主有余量但容器已经被限制

下面的 GIF 是实际命令输出的教学重排,不是屏幕录像。它们展示工具操作和字段关系,不是这台机器的性能基准,更不能证明某个资源已经构成瓶颈。每项实际验证状态见 validation.json,完整输入输出见 recorded-sessions.json

在完整网站源码仓库根目录执行:

bash docker/linux-tools-lab/run.sh build
bash docker/linux-tools-lab/run.sh test
bash docker/linux-tools-lab/run.sh render
bash docker/linux-tools-lab/run.sh snapshot

环境与产物契约见 实验 README。本篇不要求在宿主机全局安装监控工具,也不要求为了演示制造 OOM 或大量写满磁盘。

freevmstat 由 procps-ng 提供,iostatsar 属于 sysstat。先记录版本,不能因为命令名字相同就默认字段定义永远不变:

free --version
vmstat --version
iostat -V
sar -V

free:内存少了,还是可回收缓存多了

不要只盯 free 那一列

free -h
free -w -m
free -m -s 1 -c 3

-h 便于快速阅读;-m 使用 MiB 量级输出,方便比较相邻样本;-w 把 buffers 与 cache 分列;-s-c 给出重复采样间隔和次数。别在一次计算里混用自动缩放后的 GiB 与 MiB 数值。

free/proc/meminfo 获取数据。free 列是当前未使用页,available 是不发生交换时可供新工作负载使用的估计,考虑了可回收部分,并不等于把所有 cache 原封不动加回去。procps-ng free 手册

free 比较空闲页、可用内存与缓存字段,避免把低 free 直接判断为内存不足

查看原尺寸 GIF · 完整会话

看动画时先比较 freeavailable,再看缓存和 swap。Linux 把暂时不用的内存用于缓存通常是正常策略;内存充足的机器也可能有很低的 free

used 的等式有版本差异

Ubuntu 22.04 所带旧版文档将 used 解释为 total - free - buffers - cache;较新的 procps-ng 手册使用 total - available。因此截图里的 used 不同,未必说明某个进程突然增加了占用。Ubuntu Jammy free 手册当前上游定义

一个纯教学算例:某次相同口径快照中,total 为 16 GiB,free 为 1 GiB,buffers 与 cache 合计 5 GiB,available 为 4 GiB。旧式计算得到 used=10 GiB,较新式计算得到 used=12 GiB;两者表达的概念不同,不能只按名字判定数据错了。

shared 主要涉及 tmpfs 等共享内存统计,不是“所有进程 RSS 重复部分之和”。cache 中也有不同可回收程度的内容,所以把每一列都当互不重叠的集合相加,往往会得到矛盾。

它不能指出谁泄漏了

free 描述全局内存状态,不能直接证明哪个进程在泄漏。进一步回到 ps/top 找候选,再观察该进程 RSS、PSS、映射类型及增长过程。进程内存视角

分配器保留、内存池、文件映射、缓存增长和真正丢失引用的泄漏,可能都表现为 RSS 上升。需要将增长与请求次数、数据量、对象生命周期联系起来,而不是看到一次大数就宣称泄漏。

在更底层,可以对照:

rg '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Shmem|SwapTotal|SwapFree):' \
/proc/meminfo

这些读取发生在不同时间点,系统也在持续变化,所以两次命令的数字不必逐字一致。保留时间、单位和变化趋势,比强求末位相等更合理。Linux /proc 内存统计

最小练习:不用制造内存耗尽

在实验容器中连续采三次,写下每次的 freeavailable 与 swap;解释它们分别回答什么问题。然后查看容器内存上限,确认工具展示的 total 是否等于这个上限。

不要为了让 GIF 更明显而执行 drop_caches。清缓存会改变全局工作集与后续 I/O,属于干预实验,既不是常规释放内存的修复,也不应该在共享系统随意执行。

Swap 已用量不为零,不代表此刻正在频繁换页。是否出现当前交换压力,需要看接下来 vmstat 的速率和业务延迟;“曾经换出过”和“现在正在抖动”不是同一个事实。

vmstat:看资源是否让任务等着

先分清第一行

vmstat -w -t 1 4

第一份报告中,CPU 和许多速率字段反映启动以来的平均;之后才是采样间隔内变化。进程状态和内存字段即使在第一行仍是即时值,不能把整行笼统叫“都是开机平均”。支持 -y 的版本可跳过第一份报告;旧环境先用 --help 核对。procps-ng vmstat 手册

vmstat 连续采样并区分首行启动以来速率与后续间隔统计,联合观察运行队列和交换

查看原尺寸 GIF · 完整会话

阅读时不要孤立截取一个 war。先确定这是不是间隔样本,再把运行队列、交换、块 I/O 和 CPU 分项放在同一行里比较。

用四组字段提出假设

r 是可运行任务数量,含正在运行与等待 CPU;b 提示阻塞等待 I/O 的任务。若 r 持续大于实际可用 CPU 并且 CPU 时间繁忙,才更支持排队竞争;不能用一瞬间的尖峰定性。

swpd 是已使用 swap 的量,siso 是交换读入/写出速率。持续交换并伴随延迟恶化,才是值得追查的组合;单独一个非零 swpd 没有相同含义。

bibo 描述块设备输入/输出速率,incs 是中断与上下文切换速率。更多并发连接、短任务和正常唤醒都可能提高 cs;不要直接把“上下文切换多”翻译成“锁有问题”。

ussyidwast 分别帮助区分用户态、内核态、空闲、I/O 等待计量与虚拟化 steal。各版本可能显示更多字段,先读表头再写脚本,不要永远按固定第 13 列取 CPU 使用率。

iowait 不是某个进程的等待时长

wa 是 CPU 统计口径里的 I/O 等待计量,不是所有任务阻塞时间的总和,也不是磁盘利用率。内核文档还明确提醒,多核归属和计数变化使 iowait 不能作为可靠的单一判据。Linux /proc/stat 的 iowait 说明

例如 CPU 一边做别的计算、一边有任务等待磁盘,wa 未必很高;程序等待网络远端响应也不能简单用 wa 解释。要结合具体设备的延迟、业务调用栈和 I/O 路径。

使用反例校准判断

如果 r 上升、us 上升、磁盘基本无变化,优先调查计算竞争;如果 b、设备延迟和业务读取耗时同时上升,才更支持 I/O 方向。它们是排查分支,不是严格等价关系。

如果 sy 高,可能涉及系统调用、网络栈、内核同步等;需要 strace 或内核/CPU profiling 的进一步证据,不能直接建议“换更快 CPU”。

# 只输出一次累计事件/内存概览,不能当一秒采样
vmstat -s

# 改内存显示单位,不代表所有列都随之换单位
vmstat -S M 1 3

-S 不改变所有块 I/O 字段的单位。分析时应保存实际工具版本和表头,尤其是在比较网上截图与自己的输出时。

最小练习:在四份输出中标记哪一份不能拿来代表当前一秒;再找出瞬时数量与速率各两列。说明为什么 swpd=非零so=0 完全可以同时成立。

iostat:队列、延迟与吞吐一起看

先定位设备,再谈饱和

iostat -dx -y 1 3
iostat -dxm -y 1 3

-d 关注设备,-x 显示扩展字段,-y 跳过启动以来的首份报告,-m 切换吞吐显示单位。设备可能是分区、逻辑卷、虚拟块设备或物理盘,不能把每一行都当独立硬盘。sysstat iostat 手册

iostat 查看间隔设备统计,联合比较请求速率、平均等待时间、队列和活跃比例

查看原尺寸 GIF · 完整会话

容器可能看不到任何有用设备,或者看到虚拟机共享的设备指标。空表、零 I/O 和其他工作负载带来的变化都是真实可能性;这里不人为补造一组“磁盘瓶颈”数值。

三个量分别回答不同问题

r/sw/s 反映合并后完成的请求速率;吞吐列反映字节速率。同样 1000 次请求,4 KiB 小块与 1 MiB 大块不是同样的吞吐和访问特征。

await 或分方向的 r_awaitw_await 是平均请求完成耗时,包含队列等待和服务时间,不是纯硬件服务时间。它是平均数,不显示 P99;少量很慢请求可能被大量快请求稀释。

aqu-sz 是平均队列长度。它与延迟、请求速率一起看,帮助判断并发与积压;现代并行设备允许多个请求同时进行,队列不为零并不自动意味着系统病了。

假设在一段足够稳定、统计范围一致的样本里,完成速率约 1000 请求/秒、平均耗时约 0.004 秒,平均在途请求量的量级可能约为 4。这是用稳定系统关系进行数量级检查,不是强求每一秒截图都严格满足等式,更不是从平均值推导尾延迟。

%util=100% 不等于 SSD 已到极限

%util 近似描述采样窗口中设备有 I/O 活动的时间比例。对串行处理请求的设备,接近 100% 常有参考价值;但对 RAID 和现代并行 SSD,它不是“已使用硬件最大能力百分比”,上游手册明确提醒不能据此判断性能极限。

同样的 100%,可能来自一个很慢的串行请求,也可能来自许多高效并行请求。判断饱和要结合队列、延迟、吞吐、工作负载请求大小与设备能力,不应仅据一个字段决定换盘。

设备计数来自内核块层。父设备与分区、device-mapper 与底层盘可能重复反映同一 I/O 路径,把所有行相加可能重复统计。计数器还可能在重启或设备重置后归零,做差时要识别重置。Linux 内核 I/O 统计

最小练习:比较两种读法

先运行不带 -y 的版本,再运行带 -y 的版本,指出哪一份含启动以来统计。观察设备名,说明你的容器看到的是哪一层,以及你还缺什么证据才能映射到宿主 SSD。

iostat -dx 1 3
iostat -dx -y 1 3

不要为演示执行对裸块设备的写入测试。需要受控 I/O 负载时,只在实验批准的独立文件中使用有限大小,明确缓存、文件系统与持久化语义;普通读命中页缓存时,没有明显设备 I/O 也是合理结果。

sar:把当前截图延伸成时间线

安装工具不等于已经有历史

sar 能实时采样,也能读取之前由 sysstat 收集的数据。如果以前没启用采集,就不能在今天安装后询问“昨天 14 点发生了什么”。默认历史目录和调度方式随发行版配置而异,容器也常没有 systemd 或 cron 常驻采集器。sysstat 项目说明

实时采样可以先只看需要的维度:

sar -u 1 3
sar -r 1 3
sar -q 1 3
sar -d 1 3
sar -n DEV 1 3

CPU、内存、运行队列、设备和网卡分别回答不同问题。-u 的整机平均可能掩盖单核瓶颈,需要时再选 -P ALL-n DEV 的吞吐也不等于丢包、重传和业务错误率。

sar 采集带时间戳的系统活动并查看记录,建立指标变化的时间关系

查看原尺寸 GIF · 完整会话

观察重点是采样时间、间隔与字段,而不是一份稳定“答案”。演示采几个样本只证明采集流程,不代表已经开启长期历史监控。

自己保存一段受控记录

# 在实验可写目录保存本次记录,不覆盖已有事故文件
sar -o /work/linux-tools-sample.sa 1 3

# 同一份记录,按不同维度读取已保存的活动
sar -u -f /work/linux-tools-sample.sa
sar -r -f /work/linux-tools-sample.sa

-o 保存的是二进制活动文件,-f 用于读取;它不是把表格重定向到文本文件的同一件事。某类活动是否可读,取决于采集阶段是否保存了对应数据。迁移时还要关注 sysstat 文件格式兼容性和版本。sar 上游手册

历史分析时可以按时间范围裁出事故附近:

# 时间只是示意;应替换为实际记录覆盖的窗口
sar -u -f /work/linux-tools-sample.sa \
-s 14:00:00 -e 14:10:00

如果本次样本不在这个窗口,空输出是正常结果。分享记录时应同时记录日期、时区、主机身份和是否重启;只写“14:05 高了一下”无法对齐应用日志。

从相关性走到因果假设

假设应用延迟在 14:05 上升,同时 sar 显示 swap-out 和设备延迟增加,这支持去检查内存压力与换页链路,但不能直接证明是应用泄漏造成的。还要找哪个进程增长、是否有并发任务、容器限额是否变化。

平均一分钟一次的历史记录,可能完全抹平 200 毫秒的控制循环抖动。排查短暂问题时,采样频率必须能回答该时间尺度的问题;但盲目提高频率又会增加开销和数据量。

最小练习:生成一份只含数个样本的二进制记录,换一个新终端读取 CPU 与内存字段。再选择不覆盖该记录的时间窗口,解释为什么“查不到”不等于系统在那段时间没有工作。

容器内的数字属于谁

/proc 全局视图与 cgroup 限额分开

容器里的 freevmstatiostat 可能读取虚拟机或主机级内核统计,而进程实际受 cgroup 限制。Docker Desktop 还有一层 Linux 虚拟机:你看到的 Linux MemTotal,不必等于 macOS 物理内存,更不必等于单容器限额。

先识别当前 cgroup 版本和路径:

cat /proc/self/cgroup
cat /proc/self/mountinfo

在已经确认挂载根目录就是当前 cgroup 的 v2 容器中,可以只读查看:

cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.events
cat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpu.stat

其他挂载方式需要根据实际 cgroup 路径定位,不能把这些路径当成所有 Linux 的固定承诺。v1 接口不同,文件不存在不表示资源没有限制。

memory.current 是该组及其子组的使用计量,memory.max 是限制或 maxmemory.events 可提供达到限制、OOM 等事件线索。cpu.max 的配额/周期与 cpu.stat 的节流计数,有助于解释“整机很空但本容器排队”。这不是把 free 的 total 简单替换成限额就完成所有计算。Linux cgroup v2 官方文档

配额也可以做一个小计算

假设某个 cgroup 的 cpu.max 是下面这个教学数值,而不是本次实验的固定输出:

200000 100000

它表示每 100000 微秒周期允许合计使用 200000 微秒 CPU 时间,平均相当于 2 个 CPU 的时间预算。进程可以在多个 CPU 上并行消耗预算,然后在周期剩余时间受到节流;它不表示已经被固定绑到两个特定核心。

因此,宿主显示有 16 个逻辑 CPU,并不能让这个 cgroup 自动获得 16 核预算。还需检查祖先 cgroup 的限制与 cpuset 等约束,不能只读一个叶子文件就宣布最终可用能力。

练习时比较一段采样前后的节流计数增量,再对齐请求延迟。历史累计节流次数非零,只能说明以前发生过,不能证明当前这次延迟就是节流造成的。

PSI 补上“是否真的等资源”

若内核支持,可以查看压力指标:

cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io

PSI 的 some 描述至少部分任务因资源不足而停顿的时间比例;full 关注更严重的同时停顿情形,具体资源和系统层级的定义有差异。avg10avg60avg300 是相应时间窗口,total 为累计停顿时间。内核 PSI 文档

如果关心单个容器,应查看其 cgroup 对应的 pressure 文件,而不是拿整机 PSI 给某个容器直接定罪。文件或字段不可用时保留限制说明,不用补零伪装“没有压力”。

联合排障与练习

场景一:内存看起来很多,却发生 OOM

先保存 free,再看 cgroup 内存使用、上限与 OOM 事件变化;核对进程退出原因及系统日志。若 cgroup 限额先触发,宿主还有大量 available 完全可能同时成立。

下一步检查进程内存增长、映射类型与输入规模。只有确定生命周期问题,才进入泄漏检测;若是合法工作集超过配置,则应分析容量、批量大小或限额,不要一律称为内存泄漏。

场景二:CPU 不忙,控制节点却很慢

vmstat 看间隔状态,用 iostat 看具体设备,用 sar 对齐历史;再回到 进程排障 的调用栈与系统调用,检查它是在等待数据、锁、设备还是外部服务。

若只有 cgroup 节流计数持续增加,要优先分析 CPU 配额和工作负载;若设备延迟稳定、线程在条件变量里等待,则不应该因为曾看到一次 wa 就选择磁盘方向。

交付一份真正有用的监控记录

  • 时间:日期、时区、采样起止、间隔、是否包含启动平均。
  • 对象:主机/虚拟机/容器、设备层级、cgroup 路径和限额。
  • 版本:工具版本、内核、字段表头及单位。
  • 业务:同时刻延迟、吞吐、错误率及输入规模。
  • 证据:哪些字段一起变化,哪些关键字段没有变化。
  • 推理:当前假设、反例、下一步最小验证以及未测边界。

最终练习是拿同一段记录回答三个问题:资源用了多少?资源是否限制了任务推进?具体哪个工作负载导致了问题?四个工具提供的是不同层次的证据,不能用第一个问题的答案替代后两个。

3d打印 actor-critic adaptive sampling ai辅助设计 algorithm algorithms anymal apriltag ardupilot atlas attention automation axis-angle bang-bang belief encoder blender bode c++ cadquery calibration camera calibration camera-intrinsics chrome cmake cmakelists cnn colcon computer-vision conan control controller_manager cpp cpu d435i dagger data_struct db depth camera depth-camera design-pattern diagnostics direct collocation dots dtof economics eigen elevation map elf executor factory-pattern fcpx fiducial marker figure finance forge fourier fov freecad gae gazebo gdb geometry git gnu gru guitar hardware humanoid ibus imu interest isaac gym isaac lab isaaclab kdl laplace latent variable latex launch learning-notes legged locomotion legged robotics legged-robot legged_gym life linux linux-kernel linux-tools mac math matlab matrix memory mixture of experts mlp money motion imitation motion-control motor moveit mpc mujoco music-theory network neural mapping ocs2 ode onnx openscad operator optimal algorithm optimal-control perceptive locomotion perf performance personal-finance piano pinhole-camera pinocchio pixhawk pixhawk 6c point-cloud policy distillation ppo privileged learning profiling px4 python qgroundcontrol qos quadrotor realsense reinforcement learning representation learning reward tuning rnn robogauge robot robot parkour robotics ros ros2 ros2_control rsl_rl rtb security sensor-fusion shell signal-processing sim-to-real simulation socket soft dynamics constraints spot ssh stairs stl stm32 tcp-ip teacher policy teacher student teacher-student temporal convolution terrain reconstruction thread tools tron1 twist ubuntu uml uncertainty unitree unitree g1 urdf vae valgrind vcxsrv velocity vim web wifi wiring work workflow wsl z-transform zero-shot transfer 中文输入 交叉编译 人形机器人 依赖管理 分支管理 动力学 四旋翼 四足机器人 实验诊断 强化学习 接触动力学 数值计算 机器人
知识共享许可协议