背景

在线上 HPC 服务器上,我曾遇到这样一类问题:

  • 操作系统:CentOS 7.9
  • 物理内存:约 2 TB
  • 未配置 swap
  • 当系统 MemAvailable 下降到较低水位(例如低于 100 GB)后,系统整体变慢,交互卡顿明显
  • 现场观察到:vmstat 1 中运行队列变大、上下文切换显著增多,iostat -xz 1 中根盘对应设备利用率升高,系统出现明显的“卡而不死”状态

从现场映射关系看:

1
2
3
4
5
ls -al /dev/mapper/
/dev/mapper/centos-root -> ../dm-0
/dev/mapper/centos-swap -> ../dm-1
/dev/mapper/tmpvg-tmplv -> ../dm-2
/dev/mapper/centos-var  -> ../dm-3

也就是说:

  • dm-0 对应根文件系统 /
  • dm-2 对应 /tmp
  • dm-3 对应 /var

而当时 iostat -xz 1 里最忙的是 dm-0,这说明在内存压力下,主要 I/O 压力落在根文件系统对应的块设备上。

本文的目标,不是只解释“为什么慢”,而是给出一套可以在实验机上反复复现、观察、验证、缓解的完整方法,让这类问题真正被学透。


先说结论:这类“卡顿”通常不是单纯磁盘坏了,而是内存回收失衡

在“无 swap 或几乎等于无 swap”的场景中,一旦匿名页(anonymous memory,例如进程堆、栈、部分私有映射)占据了大量内存,而系统仍需为文件页缓存(page cache)、共享库、可执行文件映射页、目录项/inode slab 等保留工作集时,内核的回收空间会迅速变窄。

这时如果业务还在持续访问文件、共享库、动态链接器、二进制、配置文件、日志、临时文件,或者有扫描式读取行为,内核就可能进入一种很典型的状态:

  1. 可回收的 page cache 被回收掉;
  2. 很快又因为程序继续访问这些文件页而重新读入;
  3. 刚读入不久又因内存紧张再次被驱逐;
  4. 周而复始,形成 page-cache thrash / workingset refault

用户侧感受到的现象,往往不是立即 OOM,而是:

  • shell 变慢
  • toppsls 甚至登录都开始发飘
  • 应用偶发停顿
  • I/O wait 上升
  • 磁盘忙,但吞吐并不一定夸张
  • CPU 也不一定跑满,系统却就是“很卡”

这类现象,本质上是 内存压力传导成了回收抖动、缺页抖动和文件页反复换入换出


如何理解现场看到的 vmstatiostat

1)dm-0 是什么

iostat -xz 1 看到的 dm-0,通常是 device-mapper 设备。对于使用 LVM 的系统,它经常对应某个逻辑卷。

现场已经确认:

1
/dev/mapper/centos-root -> ../dm-0

所以这里的 dm-0 就是根目录 / 对应的逻辑卷。

2)为什么根盘会忙

因为很多“看起来不像 I/O”的行为,底层其实都可能要回到根文件系统取页:

  • 动态链接库 .so
  • 程序本体 ELF 文件
  • Python 模块、Perl 模块、shell 脚本
  • /etc 下配置文件
  • /usr/bin/usr/lib64 等系统路径内容
  • 用户 home 目录下脚本、工具和缓存
  • 根分区上的应用日志、临时文件、状态文件

当这些文件页因为内存压力被回收,后续再访问就会触发再次读盘,因此 dm-0 忙并不奇怪。

3)为什么 await 不一定特别大,但系统仍然卡

这是排障中最容易误判的地方。

在这类问题里,系统发卡并不要求单次 I/O 延迟特别夸张。即使 await 只是几毫秒,只要:

  • 缺页频率高
  • refault 频率高
  • 任务线程大量阻塞/唤醒
  • 运行队列积压
  • reclaim 路径频繁扫描

整体交互体验一样会很差。

也就是说,问题可能不是“每次 I/O 都慢”,而是“系统不断被迫做本不该这么频繁发生的 I/O”

4)为什么无 swap 会放大问题

没有 swap 时,匿名页几乎没有后备存储可退,内核主要只能从文件页缓存一侧回收。

结果就是:

  • 文件页被更激进地回收
  • 热 page cache 更容易被打掉
  • 应用再次访问文件页时产生更多 major fault/refault
  • 根文件系统上的读 I/O 增多
  • 系统整体进入抖动

这也是为什么不少生产系统即使“几乎不希望用到 swap”,也仍然会保留一小部分 swap:不是为了让系统长期跑在 swap 上,而是为了在突发内存压力下给匿名页回收一点弹性,避免 page cache 被打穿。


一个清晰的判断框架:先判断“是回收抖动”,还是“单纯内存不够”

当再遇到类似问题时,可以按下面顺序判断。

现象层

先看三个一线视角:

1
2
3
vmstat 1
iostat -xz 1
sar -B 1

重点看:

  • vmstat
    • r:运行队列是否明显升高
    • free:是否接近底水位
    • si/so:若有 swap,是否开始明显换入换出
    • bi/bo:块设备读写是否持续活跃
    • wa:I/O wait 是否上升
    • cs:上下文切换是否显著放大
  • iostat
    • 哪个设备最忙
    • r/srkB/s 是否持续上升
    • awaitsvctm%util 的组合关系
  • sar -B
    • pgscank/spgsteal/spgmajfault/s 是否显著增长

内存层

1
cat /proc/meminfo | egrep 'MemAvailable|MemFree|Cached|SReclaimable|Slab|AnonPages|Mapped|Shmem|Dirty|Writeback'

重点看:

  • MemAvailable 是否逼近低水位
  • AnonPages 是否很大
  • Cached 是否在快速波动
  • Slab / SReclaimable 是否异常偏大
  • Dirty / Writeback 是否很高(若高,说明写回压力也在叠加)

缺页/回收层

1
grep -E 'pgscan|pgsteal|pgfault|pgmajfault|workingset' /proc/vmstat

连续采样,例如:

1
watch -n 1 "grep -E 'pgscan|pgsteal|pgfault|pgmajfault|workingset' /proc/vmstat"

如果以下计数增长明显:

  • pgscan_*
  • pgsteal_*
  • pgmajfault
  • workingset_refault*
  • workingset_activate*

就非常接近“内存回收抖动 + 工作集被反复驱逐”的判断了。

进程层

1
ps -eo pid,ppid,comm,%mem,%cpu,rss,vsz --sort=-rss | head -30

必要时继续:

1
2
3
4
for p in $(ps -eo pid= --sort=-rss | head -20); do
  echo "===== PID $p ====="
  cat /proc/$p/status | egrep 'Name|VmRSS|VmSize|RssAnon|RssFile|VmSwap'
done

目的不是只找“大进程”,而是判断:

  • 是不是某一两个进程把匿名内存吃满了
  • 还是很多中等进程共同挤压系统
  • 进程 RSS 里匿名页和文件页大概是什么结构

实验目标:在实验机上复现“内存逼近耗尽后系统整体发卡”

实验机是一台 DELL T430 双路服务器,内存为 32GB * 2 = 64GB,配置过 16GB swap,但目前已 swapoff

这台机器非常适合做缩小版复现实验。

实验核心思路

我们不追求把生产环境的 2TB 完全等比复制,而是复现其机制:

  1. 先用匿名内存把系统可用内存压到很低;
  2. 再并发施加文件读取压力,让 page cache 持续建立、被驱逐、再被读取;
  3. 观察系统在 vmstatiostat/proc/vmstat、用户交互上的变化;
  4. 再分别通过“释放压力”“增加 swap/zram”“做资源隔离”“降低文件读放大”等方式验证缓解效果。

实验前准备

1)实验原则

  • 仅在测试机进行,不要在生产环境操作
  • 建议通过带外管理、iDRAC、KVM 或至少第二个 SSH 会话保底
  • 确保有 root 权限
  • 实验前确认没有重要业务在跑

2)关闭 swap(已完成)

1
2
swapoff -a
swapon --show

期望 swapon --show 无输出。

3)准备观测窗口

建议至少开 4 个终端:

终端 A:看总体态势

1
vmstat 1

终端 B:看块设备

1
iostat -xz 1

终端 C:看回收与缺页计数

1
watch -n 1 "grep -E 'pgscan|pgsteal|pgfault|pgmajfault|workingset' /proc/vmstat"

终端 D:看内存结构

1
watch -n 1 "cat /proc/meminfo | egrep 'MemAvailable|MemFree|Cached|SReclaimable|Slab|AnonPages|Mapped|Shmem|Dirty|Writeback'"

如系统安装了 sar,再补一个:

1
sar -B 1

实验一:先制造匿名内存压力

这个实验的目的是把系统推到“可回收余地变小”的边缘。

方法 A:使用 stress-ng(若已安装)

RockyLinux 上若可安装:

1
2
dnf install -y epel-release
dnf install -y stress-ng sysstat

然后例如分配约 48GB 匿名内存:

1
stress-ng --vm 6 --vm-bytes 8G --vm-keep --timeout 10m

这表示 6 个 worker,每个大约保持 8GB 内存,总计约 48GB。

也可以根据机器实时状态调整,例如 40G、48G、52G,逐步加压,不要一步顶满。

方法 B:使用 Python 匿名内存占用脚本

如果不想依赖 stress-ng,可用以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# mem_eat.py
import time
buf = []
chunk = 256 * 1024 * 1024   # 256MB
count = 0
try:
    while True:
        b = bytearray(chunk)
        for i in range(0, len(b), 4096):
            b[i] = 1   # 触页,确保真正分配
        buf.append(b)
        count += 1
        print(f"allocated = {count * 256} MB")
        time.sleep(1)
except KeyboardInterrupt:
    print("stopped")
    time.sleep(600)

运行:

1
python3 mem_eat.py

这个脚本会以 256MB 为步长逐渐吃掉匿名内存,并保持不释放。这样更适合边观察边加压。

预期现象

在这一阶段:

  • MemAvailable 持续下降
  • AnonPages 明显增大
  • 系统暂时未必立刻很卡
  • 如果只是单纯匿名内存占满但文件访问不频繁,I/O 可能还不明显

也就是说,光有内存紧张,不一定立刻出现最糟糕的交互卡顿。真正让系统“卡出感觉”的,往往是下一步:叠加文件工作集访问。


实验二:在低可用内存状态下,施加文件读取压力

这一步用于复现“page cache 被反复驱逐再读回”的症状。

准备一个足够大的测试文件

建议放在根文件系统 / 上,模拟 dm-0 压力:

1
2
3
mkdir -p /root/mem-lab
cd /root/mem-lab
fallocate -l 20G bigfile.bin

如果文件系统不支持 fallocate,可改用:

1
dd if=/dev/zero of=bigfile.bin bs=1M count=20480 status=progress

方式 A:顺序反复读

1
2
3
4
while true; do
  dd if=bigfile.bin of=/dev/null bs=4M status=none
  sleep 1
done

方式 B:使用 fio 做更贴近真实的读取模式

若已安装 fio

1
dnf install -y fio

执行:

1
2
3
4
5
6
7
8
9
10
11
fio --name=readtest \
    --filename=/root/mem-lab/bigfile.bin \
    --rw=randread \
    --bs=128k \
    --iodepth=16 \
    --ioengine=libaio \
    --direct=0 \
    --numjobs=4 \
    --size=20G \
    --time_based \
    --runtime=600

这里 direct=0 的目的是让读路径经过 page cache,更容易观察“缓存建立—驱逐—refault”的现象。

预期现象

当匿名内存已经很高、可用内存很低时,再启动文件读取压力,通常会看到:

  • iostat -xz 1 中根盘所在设备读 I/O 上升
  • %util 增高
  • vmstat 1biwacs 抬升
  • /proc/vmstatpgscanpgstealpgmajfaultworkingset_refault_file 增长更快
  • shell 响应开始明显变慢
  • 新启动一个命令(例如 lsbashpython3)的体感延迟变大

这就很接近生产环境里的“整体卡顿”了。


实验三:加入“共享库/程序反复冷启动”观察系统为什么更卡

前面提到一个很重要的问题:

有没有可能 cache 被反复地驱逐、加热,比如 shared libraries?

答案是:完全可能,而且这正是很多系统在内存高压下“看起来哪都没跑重活,但就是操作越来越卡”的关键原因之一。

可以用下面的办法做一个简单验证。

冷启动风格测试

在匿名内存压力 + 文件读取压力都存在时,另开一个终端反复执行:

1
2
3
4
while true; do
  /usr/bin/time -f 'elapsed=%E' bash -lc 'python3 -c "import json,ssl,hashlib,subprocess" >/dev/null'
  sleep 1
done

或者:

1
2
3
4
while true; do
  /usr/bin/time -f 'elapsed=%E' bash -lc 'ls /usr/bin >/dev/null'
  sleep 1
done

会看到什么

在内存充足时,这些命令通常很快。

但在内存逼近耗尽且 page cache 工作集被打散后:

  • 这些本应“秒开”的命令启动时间会抖动
  • 偶尔会突然慢很多
  • 越到后面,抖动越明显

原因就在于:

  • 动态链接器、共享库、解释器模块本身也是文件页
  • 一旦它们不在内存里,就要重新从磁盘读入
  • 如果刚读入又因回收被打掉,就会不断重复这个过程

这就是“shared libraries 被反复驱逐、再加热”的一个直观表现。


实验四:人为制造 slab / dentry / inode 压力(可选进阶)

实际生产环境里,除了匿名页与普通 page cache,目录项、inode、文件系统元数据 slab 也可能参与竞争。

可用一个大目录树做简单实验:

1
2
3
4
5
6
7
mkdir -p /root/mem-lab/tree
for d in $(seq 1 200); do
  mkdir -p /root/mem-lab/tree/dir_$d
  for f in $(seq 1 2000); do
    touch /root/mem-lab/tree/dir_$d/file_$f
  done
done

然后反复遍历:

1
2
3
4
while true; do
  find /root/mem-lab/tree -type f | wc -l
  sleep 1
done

观察:

1
2
slabtop
cat /proc/meminfo | egrep 'Slab|SReclaimable|SUnreclaim'

这个实验有助于理解:系统在高压下不只是“数据页”会争内存,很多元数据缓存也会参与回收与重建。


如何记录实验结果:建议建立一份“同屏观测表”

每做一次实验,都记录下面这些项目:

维度 关注点 典型异常
内存 MemAvailableAnonPagesCachedSlab available 降到低水位,anon 高,cached 波动
回收 pgscanpgstealpgmajfaultworkingset_refault_file 连续快速增长
I/O r/srkB/sawait%util 根盘读活跃、util 升高
CPU wasy、上下文切换 wacs 上升,sy 也可能偏高
交互 登录、执行 ls、启动 Python 响应明显变慢
业务 应用延迟、批处理耗时 抖动、停顿、吞吐下降

把“系统指标”和“人能感受到的卡顿”放在同一张表里,学习效果会比只盯着一两个命令强很多。


如何证明“问题不是单纯磁盘慢,而是内存压力诱发的 I/O 抖动”

可以做一个对照实验。

对照组 A:不吃匿名内存,只跑文件读取

只跑:

1
dd if=bigfile.bin of=/dev/null bs=4M status=none

或者 fio randread

此时会看到有 I/O,但系统未必明显卡顿。

对照组 B:先把匿名内存压低,再跑同样的读取

在匿名内存占到 70%~85% 之后,再跑同样 I/O。

这时通常会看到:

  • 交互明显变差
  • pgmajfaultworkingset_refault_file 更明显
  • 同样的磁盘吞吐下,用户体验显著更差

这就说明:根因不是“磁盘天然就慢”,而是内存工作集失衡让磁盘被迫承接了本不该承担的频繁冷读。


缓解与优化建议

下面按“短期止血、中期优化、长期治理”三层来讲。

一、短期止血

1)保留少量 swap,不要完全裸奔

很多人看到 swap 就本能排斥,但对大内存 Linux 服务器来说,少量 swap 往往是稳定性缓冲器,而不是性能灾难本身

对于测试机或生产机,可以考虑:

  • 至少保留一个小规模 swap 分区或 swapfile
  • 例如物理内存的 1%~5%,或者按业务特征给一个更保守但非零的值
  • 配合较低 vm.swappiness,避免系统过早积极换出

例如:

1
2
sysctl -w vm.swappiness=10
sysctl -w vm.watermark_scale_factor=150

说明:

  • vm.swappiness=10 代表更保守地使用 swap,但不是禁用 swap
  • watermark_scale_factor 可影响回收启动水位,适合实验验证,但正式上线前应结合业务压测审慎评估

2)给关键服务预留内存余量

如果一台机器承担了大量 EDA/HPC 任务,不要让任务把系统吃到几乎无余量。

建议在调度器或作业侧做控制:

  • 单节点总申请内存设置上限
  • 为 OS 和基础服务预留固定内存 buffer
  • 对“容易膨胀”的进程设置更严格的资源申请与审计

对于 HPC/EDA 场景,这一点比单纯调内核参数更重要。

3)优先识别匿名内存大户

如果系统一旦逼近低水位就卡,第一件事常常不是“调内核”,而是先查:

1
ps -eo pid,comm,%mem,rss,vsz --sort=-rss | head -20

看是不是:

  • 某类 job 的内存申请失控
  • 某个进程泄漏
  • 大量并发任务叠加占用

如果是业务负载把机器吃干,再漂亮的回收策略也只是延缓症状。


二、中期优化

1)考虑 zswap 或 zram

如果担心传统 swap 直接落盘影响 SSD/HDD,也可以评估:

  • zswap:压缩后仍以 swap 为后备
  • zram:在内存中做压缩块设备,常用于提高内存利用效率

它们不能替代容量规划,但在“突发内存高压、又不想让磁盘成为第一落点”的场景下,往往比“完全关闭 swap”更稳。

2)把高 churn 的临时 I/O 与根分区解耦

如果某些应用会在 / 下制造大量临时文件、缓存或中间结果,建议梳理:

  • 临时目录是否落到了独立文件系统
  • 应用 cache 是否能迁到独立盘或独立 LV
  • 日志与热点数据是否与根分区混布

虽然这不能解决匿名内存高压本身,但能减轻 dm-0 既承载系统文件又承载业务热文件的冲突。

3)减少“冷启动频繁”的链路

如果系统处于高压状态,而业务又频繁:

  • 启动短命令
  • 大量 fork/exec
  • 动态加载大量模块
  • 重复调用脚本型工具

那么 shared libraries 与解释器模块会不断被重新取页。

在可行时,可以考虑:

  • 长驻 worker 替代高频短进程
  • 合并脚本调用
  • 避免无意义的反复冷启动
  • 评估是否存在批处理框架层面的过度调度

4)用 cgroup / systemd 做资源隔离

对 HPC 节点、登录节点或混合负载机器,建议把不同业务放进不同控制组,做最基本的内存约束和保护。

例如:

  • 给关键基础服务设置 MemoryLow= / MemoryMin= 保护
  • 给高风险批处理任务设置 MemoryMax=
  • 避免单类业务把系统直接拖进全局 reclaim 风暴

如果已经是 systemd 体系,很多事情并不需要很复杂的容器化才能做到。


三、长期治理

1)把“MemAvailable 低于某阈值”纳入预警,但不要只看 free

很多团队只盯 MemFree,这不够。

更合理的是结合:

  • MemAvailable
  • pgmajfault/s
  • pgscan/s
  • workingset_refault_file
  • 根盘 %util / r/s
  • 关键应用延迟

做联动判断。

2)在监控中显式加入“workingset refault”指标

如果内核与采集链路支持,把下列指标纳入监控非常有价值:

  • /proc/vmstat 中的 workingset_refault*
  • pgmajfault
  • pgscan_*
  • pgsteal_*

这些指标比单纯看 CPU 或磁盘,更能说明“系统是不是在做无效内存回收”。

3)为调度平台建立“节点保底余量”策略

对 LSF、Slurm、K8s 这类平台,建议不要把节点可调度内存设到 100%。

应该显式保留:

  • OS 基础开销
  • page cache 工作集余量
  • 文件系统元数据缓存余量
  • 监控、代理、登录会话等系统服务余量

从平台治理角度看,这比事后救火更有效。

4)容量规划时把“热工作集”算进去,而不是只算 RSS

很多作业申报内存时只看进程 RSS,但系统稳定运行还需要:

  • 文件页缓存
  • 共享库页
  • 文件系统元数据缓存
  • 短时峰值 buffer

对于 EDA、仿真、编译、数据处理等场景,这些往往不能忽略。


一个推荐的完整实验流程

如果希望做一遍比较系统的学习,建议按下面节奏走。

Phase 1:建立基线

  1. 系统空闲时记录:
    • vmstat 1
    • iostat -xz 1
    • sar -B 1
    • /proc/meminfo
    • /proc/vmstat
  2. 执行几次常见命令,体会“正常交互速度”

Phase 2:只施加匿名内存压力

  1. 运行 stress-ng --vm ...mem_eat.py
  2. MemAvailable 压到 20GB、10GB、5GB 分阶段观察
  3. 记录系统是否已经出现明显卡顿

Phase 3:在低水位下叠加文件读取压力

  1. / 上创建大文件
  2. 循环读或用 fio
  3. 观察 dm-0pgmajfaultworkingset_refault_file
  4. 记录 shell / 新进程启动的体感变化

Phase 4:加入冷启动命令测试

  1. 反复启动 Python / bash / ls 等常用命令
  2. 记录耗时抖动
  3. 理解 shared libraries、解释器模块为何会形成额外负担

Phase 5:做对照实验

分别测试:

  • 无 swap
  • 有小 swap + swappiness=10
  • 启用 zram/zswap
  • 降低匿名内存占用阈值
  • 对实验进程加 cgroup 内存上限

最后比较:

  • 系统是否仍然会“整体发卡”
  • workingset_refault_file 是否下降
  • 根盘读压力是否下降
  • 交互响应是否改善

通过这一步,我们不只是“知道原因”,而是能定量地知道哪种治理手段更有效。


实战排障命令清单

下面这组命令值得收藏。

1)块设备与挂载关系

1
2
3
4
lsblk
ls -al /dev/mapper/
df -hT
findmnt

2)总体压力观察

1
2
3
4
vmstat 1
iostat -xz 1
sar -B 1
sar -W 1

3)内存结构

1
2
3
4
cat /proc/meminfo
cat /proc/zoneinfo
numastat -m
slabtop

4)回收与缺页

1
2
grep -E 'pgscan|pgsteal|pgfault|pgmajfault|workingset' /proc/vmstat
watch -n 1 "grep -E 'pgscan|pgsteal|pgfault|pgmajfault|workingset' /proc/vmstat"

5)找大户进程

1
ps -eo pid,ppid,comm,%mem,%cpu,rss,vsz --sort=-rss | head -30

6)看哪些文件被频繁读(进阶)

如果内核/权限允许,可用:

1
2
3
4
perf trace
bcc-tools / bpftrace
pidstat -d 1
iotop -oPa

不过要注意:这些工具更适合在实验环境里用来“定位谁在读”,而不是在最卡的时候再临时重装、临时启用。


对生产环境的建议:不要把“未配置 swap”当成默认最佳实践

对一些延迟敏感业务,担心 swap 影响性能是可以理解的。但把 swap 完全去掉,等于把匿名页回收这条路几乎堵死,一旦内存逼近上限,page cache 更容易被打穿,最终表现出来的可能不是更快,而是更差、更抖、更难预测。

更稳健的做法通常是:

  • 保留小规模 swap 或评估 zram/zswap
  • 控制节点总内存占用上限
  • 为 OS 与关键服务保留余量
  • 监控 MemAvailable + pgmajfault + workingset_refault + 根盘读压力
  • 对异常内存增长的作业做治理

对于 HPC / EDA 平台,这通常比简单地“禁用 swap 追求纯净”更实用。


结语

这类问题最容易被误判成“磁盘慢了”或者“CPU 不够了”。

但真正的根因,往往是:

  • 匿名内存把可用空间压得太低;
  • 无 swap 让回收策略失去弹性;
  • 文件页缓存、共享库、解释器模块、元数据缓存被反复驱逐;
  • 系统持续产生 refault、major fault 与 reclaim 扫描;
  • 最终,所有用户都感受到一种说不清楚但非常明显的“系统发卡”。

当在实验机上亲手把这个过程一步步复现出来,再结合 vmstatiostat/proc/meminfo/proc/vmstat 去看,就会真正理解:

很多“卡顿”并不是单个指标异常,而是整个内存—缓存—I/O 链路开始失衡。

理解这一点,后续不管是做 HPC 节点治理、EDA 服务器稳定性优化,还是设计容量与监控策略,都会更稳。


参考资料

  1. Linux kernel documentation: cgroup v2 memory statistics and workingset counters
    https://docs.kernel.org/admin-guide/cgroup-v2.html
  2. Oracle Linux Blog: Linux Swapping FAQ
    https://blogs.oracle.com/linux/linux-swapping-faq
  3. Red Hat Performance Tuning Guide: vmstat
    https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-tool_reference-vmstat
  4. Red Hat Performance Tuning Guide: iostat
    https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-performance_monitoring_tools-iostat
  5. proc(5) / vmstat(8) / iostat(1) / sar(1) / slabtop(1) / numastat(8) man pages