在之前的文章:《BPF 性能之巅》- 使用 perf tools 做性能分析(1)中,我们使用了 iosnoop、strace 和 opensnoop 来排查磁盘 IO 满的问题,最终可以大致定位哪些进程执行了哪些文件相关的操作,导致磁盘 IOUtil 高。
那有没有工具可以一步到位(即用一条命令)来定位问题呢?
由广成子开发的 iodump 命令就是我们需要的工具(最重要的是 fullpath 字段非常有用)。
地址:https://gitee.com/anolis/iodump
iops dump 工具(简称 iodump)是利用内核 tracepoint 静态探针点技术实现的一个 IO 问题排查工具。通过 iops dump 工具,我们可以获取每一个 IOPS(w/s和r/s)的详细信息,不仅包括 IO 请求的 size 大小,还包括 IO 请求的扇区地址,同时还包含 IO 请求的发生时间、读写的文件全路径、产生 IO 请求的进程、产生 IO 请求的系统调用和扩展 IO 类型等信息。这其中最具有特色的就是读写的文件全路径功能。
当我们要排查操作系统磁盘 IO 问题时,可以使用 iostat 扩展命令进行具体分析。当 iostat 工具显示此时磁盘 IO 并发很高,磁盘使用率接近饱和时,还需要依赖更多的工具进一步查看影响磁盘 IO 使用率高的进程信息和读写文件信息。
常见的工具或者方法有 iotop、blktrace、ftrace和 block_dump 等。实际使用中,它们都有各种不足:
相比其他磁盘 IO 类工具,iodump 有如下几个特色优势 :
iodump 功能虽然强大,但 iodump 本质上采用的是加载内核模块方式实现,可能会引起操作系统crash,请在重要的生产环境使用前,提前进行充分测试。从计算平台的实际使用情况,未发生生产环境宕机。
工具安装
具体请参考官方文档,可以自己构建 rpm 或者使用源码安装。
生产中遇到一个案例,磁盘分区 sda 平时读 IOPS 是 20 多,但有时候读 IOPS会瞬时高出数倍,并将磁盘 io util 打满,此时我们就可以使用如下方式来查看 sda 盘上的 IO 操作:
sudo iodump -p sda -t 15
datetime comm pid iosize sector rw rwsec launcher fullpath
2024-03-19T15:18:55.973146 kworker/u546:0 2683970 4096 527212928 W V ret_from_fork_nospec_begin /var/log/audit/audit.log
2024-03-19T15:18:55.973215 kworker/u546:0 2683970 4096 75002816 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973236 kworker/u546:0 2683970 4096 103445480 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973252 kworker/u546:0 2683970 4096 103458376 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973267 kworker/u546:0 2683970 4096 103461040 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973282 kworker/u546:0 2683970 4096 103480128 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973297 kworker/u546:0 2683970 4096 103482472 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973311 kworker/u546:0 2683970 4096 103487648 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973326 kworker/u546:0 2683970 4096 103490904 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973342 kworker/u546:0 2683970 4096 103490976 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973359 kworker/u546:0 2683970 8192 103500688 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973376 kworker/u546:0 2683970 4096 103501784 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973392 kworker/u546:0 2683970 4096 103616688 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973416 kworker/u546:0 2683970 8192 103620928 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973431 kworker/u546:0 2683970 4096 103623504 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973447 kworker/u546:0 2683970 4096 103627824 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973462 kworker/u546:0 2683970 4096 103630496 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973477 kworker/u546:0 2683970 4096 103633160 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973492 kworker/u546:0 2683970 4096 103635824 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973507 kworker/u546:0 2683970 4096 103638504 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973522 kworker/u546:0 2683970 4096 103641224 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973538 kworker/u546:0 2683970 4096 103643896 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973555 kworker/u546:0 2683970 4096 103646584 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973571 kworker/u546:0 2683970 4096 103803304 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973587 kworker/u546:0 2683970 8192 103803848 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973607 kworker/u546:0 2683970 8192 103807088 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973623 kworker/u546:0 2683970 4096 103807464 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973638 kworker/u546:0 2683970 4096 103810136 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973652 kworker/u546:0 2683970 4096 103810320 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973667 kworker/u546:0 2683970 4096 103810336 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973682 kworker/u546:0 2683970 8192 103810352 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973696 kworker/u546:0 2683970 4096 103813960 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973713 kworker/u546:0 2683970 12288 103817672 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973727 kworker/u546:0 2683970 4096 103817696 W V ret_from_fork_nospec_begin /var/log/journal/139ca3191d0842efa10f74b369c73252/system.journal
2024-03-19T15:18:55.973777 kworker/u546:0 2683970 4096 531137256 W V ret_from_fork_nospec_begin /var/tmp/sclgZX5ki
2024-03-19T15:18:55.973794 kworker/u546:0 2683970 4096 963358840 W V ret_from_fork_nospec_begin /var/log/messages
datetime comm pid iosize sector rw rwsec launcher fullpath
相比 iosnoop,这里最妙的是 fullpath 字段,以及 launcer 字段:
Tracing block I/O for 30 seconds (buffered)...
STARTs ENDs COMM PID TYPE DEV BLOCK BYTES LATms
1276585.460147 1276585.460817 <...> 3559925 W 8,0 592043960 4096 0.67
1276585.460214 1276585.460842 <...> 3559925 W 8,0 526997432 4096 0.63
1276594.259996 1276594.260503 <...> 3710230 WSM 8,0 526514096 8192 0.51
Ending tracing...
我们来看一下 iodump 输出字段的含义:
我们可以写脚本对输出结果做统计,这样就能分析出磁盘 IO 满到底是什么进程在做什么样的文件操作。
内存颠簸是指在计算机系统中内存的分配与回收过程中,频繁地执行内存分页,从而使得系统性能下降的情况。这种情况往往是由于程序内存分配不合理,导致频繁地进行内存分页,同时内存碎片过多,导致内存管理效率变低。
当出现内存颠簸时,系统会需要频繁地将内存数据从硬盘或其他存储设备中读取,再放回内存中进行处理。这样,就会大大降低系统的运行效率,甚至造成系统卡顿或崩溃的情况。
解决内存颠簸问题的方式是尽量避免过度使用大型内存分配,并优化内存回收策略,尽可能减少内存碎片产生。同时,可以采用内存复用技术,将已使用的内存块再次利用,从而避免内存的浪费。
有些情况下,我们会看到 launcher 字段是 page_fault 或 async_page_fault,并且 rw 字段是 R 读。最重要的是,如果观察一个时间区间内(比如60秒)的 数据,我们会发现同一个扇区地址 sector 字段,会重复读取几百,几千或上万次。那么这种情况就是内存颠簸引起的读磁盘 IO。同时可能伴有 iosize 是 4096 等小io,fullpath 的文件为 binary 文件等特征。
datetime comm pid iosize sector rw launcher fullpath
2022-06-01T19:52:44.200717 task 48309 4096 242275424 R page_fault /lib64/libmodx.so
2022-06-01T19:52:44.215145 task 48309 4096 242280736 R page_fault /lib64/libmodx.so
2022-06-01T19:52:44.236601 task 48309 4096 242278216 R page_fault /lib64/libmodx.so
字段 launcher 为 page_fault,说明 io 产生的原因是发生了主缺页中断。一次主缺页中断代表程序执行代码段时,内存中不存在这块代码段,进而引起一次读 IO,从磁盘中读取这部分代码段内容。内存颠簸时就会发生内存中频繁且反复出现代码段不存在的情况。
引起内存颠簸可能是单 cgroup 层面、单 numa 节点或整机层面内存紧张引起。当通过上述特征判断出是内存颠簸引起的读 io 打满时,以单 cgroup 内存紧张为例,pid 是 48309,进一步排查方法如下。
$ cat /proc/48309/cgroup | grep memory
9:memory:/system.slice/sshd.service
$ cat /sys/fs/cgroup/memory/system.slice/task.service/memory.limit_in_bytes
8615100416
$ ps h -p 48309 -o rss
8240736
$ echo $(((8615100416/1024-8240736)/1024))
168
进程的 rss 内存是 8240736kB,此时这个进程的 cgroup 的memory.limit_in_bytes 是 8615100416。说明在 cgroup 的限额内,已经被 rss 占据了绝大部分配额,只留下 168Mb 配额,不足以完全融下进程的代码段,从而引起代码段的不同部分频繁被丢弃和磁盘读取。
说到性能开销,需要先简单介绍下目前行业内各种主流的trace工具的技术实现原理。一般都是在进程上下文环境的tracepoint钩子函数中向一个ring buffer中写入数据,另外一个用户态进程再从ring buffer中读取数据。这样性能开销就需要分两部分计量。
我们构造了一个每秒 1.2 万读 IOPS 和 250 写 IOPS 的测试环境,开启 iodump后,CPU 性能开销如下。
- | CPU | MEM |
---|---|---|
iodump进程 | 单核0.3% | 876KB |
1比100抽样 | 单核1.6% | 1MB/核 |
全量采集 | 单核10% | 1MB/核 |
为了完整的了解使用方法,我们可以查看帮助信息。
$ iodump -h
Usage: iodump [OPTIONS]
Summary: this is a io tools. it can dump the details of struct request or struct bio.
Options:
-h Get the help information.
-H Hiding header information.
-a <G> Set blk tracepoint action which is fully compatible with blktrace, default G, See Actions.
-o <pid,comm> Set the output field, such as datetime,comm,pid, See Formats.
-p <sda2> Set partition parameter, this option is necessary.
-s <filepath> Set saving output to the file. if not set, it will output to standard output.
-t <time> Set tracing last time, default last time is 300 second, default 300 seconds.
-O <ino> Set the extra output field, such as tid,ino, See Formats.
-S <number> Set sample number, Only 1/number output is displayed, default 1.
-c <comm> Just output exact match comm string record.
-C <comm> Just output record which comm contain the comm string.
-P <pid> Just output exact match pid record.
Major Actions:
ACTION TRACEPOINT UNIT
Q block_bio_queue bio
G block_getrq bio
I block_rq_insert request
D block_rq_issue request
C block_rq_complete request
Minor Actions:
B block_bio_bounce bio
F block_bio_frontmerge bio
M block_bio_backmerge bio
S block_sleeprq bio
X block_split bio
R block_rq_requeue request
Formats:
FIELD DESCRIPTION
datetime Such as 2022-03-23T16:42:05.315695, Precision millisecond.
timestamp Such as 1648025082259168, Precision millisecond.
comm Such as iodump, process short name.
pid tgid
tid task id
iosize IO size, the unit is byte.
sector Sector address on the disk.
partition Such as sda5.
rw The value list is R(read) or W(write).
rwsec The value list is A(ahead) E(secure) F(force unit access) M(meta) S(sync) V(vacant)
launcher The bottom function of the call stack.
ino Inode number.
fullpath Read or Write file full path.
下面介绍各主要参数的含义:
下面是一些各种参数组合运行的实例:
$ sudo iodump -p sda
$ sudo iodump -p sda5 -t -1 -H >/tmp/log
$ sudo iodump -p nvme -t 600 -H -s /tmp/log
$ sudo iodump -p nvme0n1p1 -S 10
$ sudo iodump -p sda -a G
$ sudo iodump -p sda -o comm,pid
$ sudo iodump -p sda -O tid,ino
$ sudo iodump -p sda -c kworker/1:0
$ sudo iodump -p sda -C kworker
$ sudo iodump -p sda -P 1234
详细请参考:https://gitee.com/anolis/iodump