iops dump 使用手册
一、工具介绍
iops dump 工具是利用内核 tracepoint 静态探针点技术实现的一个 io 问题排查工具。通过 iops dump 工具,我们可以获取每一个 IOPS(w/s 和 r/s)的详细信息,不仅包括 IO 请求的 size 大小,还包括 IO 请求的扇区地址,同时还包含 IO 请求的发生时间、读写的文件全路径、产生 IO 请求的进程、产生 IO 请求的系统调用和扩展 IO 类型等信息。这其中最具有特色的就是读写的文件全路径功能。为了便于向大家介绍 iops dump 工具,下文将我们简称其为 iodump。
二、工具特点
当我们要排查操作系统磁盘 IO 问题时,可以使用 iostat 扩展命令进行具体分析。当 iostat 工具显示此时磁盘 IO 并发很高,磁盘使用率接近饱和时,还需要依赖更多的工具进一步查看影响磁盘 IO 使用率高的进程信息和读写文件信息。
常见的工具或者方法有 iotop、blktrace、ftrace 和 block_dump 等。实际使用中,它们都有各种不足:
- iotop 工具,可以细化到进程带宽信息,但缺乏进程级的 IOPS 信息,也没有对应的磁盘分区信息。
- blktrace 工具,功能强大,但使用较复杂。获取 sector 信息后,进一步通过 debugfs 等其他方式解析文件路径也比较低效。
- ftrace 工具,当跟踪块设备层静态探针点时,功能和 blktrace 工具类似,也需要通过 debugfs 等工具进一步解析文件路径。当跟踪文件系统层探针点函数时,无法精确对应 IOPS 数量。
- block_dump 工具,也同样存在以上 ftrace 工具的 2 个不足。
相比其他磁盘 io 类工具,iodump 有如下几个特色优势 :
- 支持自定义选择 blk 层探针点函数。
- 支持自定义输出字段信息,包括时间、进程名、进程 ID、IO 大小、扇区地址、磁盘分区、读写类型、扩展 IO 类型、IO 来源、Inode 号,文件全路径。
- 当采集进程异常退出后,支持内核态自动关闭探针。
- 支持从 2.6.32 以上的各内核版本。
- 当 IOPS 高时,支持抽样输出。
iodump 功能虽然强大,但 iodump 本质上采用的是加载内核模块方式实现,可能会引起操作系统 crash,请在重要的生产环境使用前,提前进行充分测试。从计算平台的实际使用情况,未发生生产环境宕机。
三、工具安装
工具的安装,有如下几种方法:
3.1、rpm 包打包方法
rpm 包的打包方法如下,本方法适用于 AnolisOS and CentOS 等操作系统环境。
$ yum install rpm-build rpmdevtools git
$ rpmdev-setuptree
$ cd ~/
$ git clone https://gitee.com/anolis/iodump.git
$ cp iodump/spec/iodump.spec ~/rpmbuild/SPECS/
$ tar -zcvf ~/rpmbuild/SOURCES/iodump-$(cat iodump/spec/iodump.spec |grep Version |awk '{print $2}').tar.gz iodump
$ rpmbuild -bb ~/rpmbuild/SPECS/iodump.spec
$ cd ~/rpmbuild/RPMS/x86_64/
$ sudo rpm -ivh iodump-$(uname -r)-*.an8.x86_64.rpm
$ sudo rpm -e iodump-$(uname -r) # remove package
iodump 工具本质上是内核驱动模块,在一个特定内核版本上生成的 rpm 包在另外一个不同的内核版本上是不能正常工作的。在这里我们将内核版本信息加入到 rpm 包的 name 部分,用以识别不同内核版本的 rpm 包。这里我们推荐使用如下 rpm 的查询命令区分一个 rpm 包的 name、version、rel ease 和 arch 四个部分的内容。我们使用连续的 3 横线来区隔不同的部分,结果一目了然。
$ rpm -qp iodump-4.19.91-24.8.an8.x86_64-1.0.1-1.an8.x86_64.rpm --queryformat="%{name}---%{version}---%{release}---%{arch}\n"
iodump-4.19.91-24.8.an8.x86_64---1.0.1---1.an8---x86_64
3.2、deb 包打包方法
deb 包的打包方法如下,本方法适用于 Ubuntu 等操作系统环境。
$ apt-get update
$ apt install git
$ cd ~/
$ git clone https://gitee.com/anolis/iodump.git
$ cd iodump/debian
$ ./build.sh
$ dpkg -i iodump-4.4.0-87-generic_1.0.1-1_amd64.deb
$ dpkg -r iodump-4.4.0-87-generic # remove package
3.3、源代码编译安装
源代码安装方法。
$ cd ~/
$ git clone https://gitee.com/anolis/iodump.git
$ make
$ sudo make install
$ sudo make uninstall # remove
四、使用说明
4.1、基本使用
基本运命命令如下。并且运行时需要使用 sudo 进行提权。
$ sudo iodump -p sda
结束运行 iodump,可以在运行过程中直接使用键盘的 ctrl+c 的组合,或在另外一个终端运行如下命令。
$ sudo killall iodump
$ sudo killall -9 iodump
当向 iodump 进程发送 SIGHUP、SIGINT 和 SIGTERM 等信号时,用户态的 iodump 进程会在退出前向内核模块发送关闭内核跟踪的消息。但是,当向 iodump 进程发送-9,即 SIGKILL 信号时,用户态进程 iodump 将强制退出,无法向内核模块发送关闭内核跟踪的消息。此时,kiodump 内核模块会在用户态 iodump 进程异常退出后,自动关闭内核跟踪开关。其它的 blktrace 等工具并没有此功能。
4.2、参数说明
为了完整的了解使用方法,我们可以查看帮助信息。
$ 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.
下面介绍各主要参数的含义:
- -p 参数: 设置需要追踪的磁盘或磁盘分区,例如 -p sda 或 -p sda5。这个参数是必选参数,不设置程序运行报错。
- -t 参数: 设置追踪程序运行的时长,例如 -t 60 或-t -1,此时程序运行 60 秒后会自动终止,-1 表示永远运行。不指定-t 参数,程序默认运行 300 秒结束。
- -s 参数: 设置追踪信息存储的文件,例如 -s /tmp/log。不指定-s 参数时,追踪信息会打印到屏幕标准输出,此时也可以通过重定向将追踪信息保存到磁盘文件。
- -S 参数: 设置抽样输出的比例。例如 -S 30,此时 iodump 将每隔 30 次只输出一次。
- -H 参数: 设置屏蔽标题栏信息的输出,例如 -H。
- -a 参数: 设置 iodump 追踪的内核静态探针点,选项值可以是 5 个主要跟踪点 Q、G、I、D、C,以及其他几个辅助跟踪点 B、F、M、R、S 和 X。默认缺省为 G,即 block_getrq,UNIT 为 bio。其他一些探针点 UNIT 也可能是 request。
- -o 参数: 设置输出信息字段,例如 -o pid,comm。字段值可以是 datetime、timestamp、comm、pid、tid、iosize、sector、partition、rw、rwsec、launcher、ino 和 fullpath,多选逗号隔开。
- -O 参数: 设置额外追加的输出信息字段,例如 -O tid,ino。默认输出信息字段组合为 datetime,comm,pid,iosize,rw,rwsec,launcher,fullpath。
- -c 参数: 仅输出和输入的进程名精准匹配的结果,例如,-c kworker/1:0.
- -C 参数: 仅输出和输入的进程名模糊匹配的结果,例如,-C kworker.
- -P 参数: 仅输出和输入的 PID 精准匹配的结果,例如,-P 1234.
下面是一些各种参数组合运行的实例。
$ 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
4.3、长期测试
上面提到 iodump 由内核驱动模块实现,可能引发宕机。因此在重要场景使用前需要进行充分的测试。这里提供一个测试命令。
$ sudo iodump -p sda -t 8640000 > /dev/null 2>/dev/null &
五、输出说明
iodump 的每一行输出信息代表一个 io 请求(request 结构体或 bio 结构体)的详细信息。iodump 的输出信息,如所示。
$ iodump -p sda
datetime comm pid iosize partition rw rwsec fullpath
2022-04-26T22:53:18.272487 kworker/u128:0 19607 131072 sda3 W V /var/log/messages
2022-04-26T22:53:18.272563 jbd2/sda3-8 834 4096 sda3 W FS -
2022-04-26T22:53:23.392466 kworker/u128:2 19494 16384 sda3 W M -
datetime comm pid iosize partition rw rwsec fullpath
这些输出信息现在已经有 9 列数据项,其中每一项的含义说明如下:
- datetime: 日期时间格式,小数点后是微秒信息。
- timestamp:时间戳信息,单位微秒(us)。
- comm: 上下文环境中的进程名。
- pid: 上下文环境中的进程 ID。
- tid: 上下文环境中的线程 ID。
- iosize: 一次发向磁盘的 io 的数据大小,例如 4096、524288 等,单位为字节,数值必须是 4096 的倍数,4096 是一个 page 的大小。
- sector: 一次发向磁盘的 request 的数据在磁盘中的扇区地址。扇区地址是硬盘出厂时,低级格式化时的一个扇区顺序号。一个扇区地址在一块磁盘中是唯一的。
- partition:一次发向磁盘的 request 的数据所在磁盘分区信息,例如 sda5,sda,nvme0n1p3 和 nvme0n1。
- rw: IO 基本类型,R 是 READ,W 是 WRITE,D 是 DISCARD,E 是 SECURE_ERASE 和 DISCARD,F 是 FLUSH,N 是 Other,值单选。
- rwsec: IO 扩展类型,F 是 FUA(forced unit access),A 是 RAHEAD(read ahead),S 是 SYNC,M 是 META,E 是 SECURE。可多选,以上都没有时,显示 V。
- launcher: 对于从用户态发起的 IO 调用栈,这里将显示出系统调用名称信息。
- ino: 一次发向磁盘的 request 的数据在磁盘分区中的 inode 号信息,有些操作元数据信息的 request 请求这个 inode 信息为 0。
- fullpath: 如果 inode 信息不为 0 时,所对应的文件名以及其磁盘路径。这里解析了所在磁盘分区的完整文件路径。当磁盘 IO 打满时,我们可以通过对 FilePath 字段的分析,迅速定位问题。
六、结果有效性
磁盘 io 打满时,io 诊断工具尽可能的跟踪到每一个 iops 信息并且输出,只有这样才能说明这个工具是有效的。如果只能显示其中的 20-30%,那工具的价值将是大打折扣的。
6.1、数据有效性
日常我们判断一个磁盘分区 iops 是否打满主要依赖 iostat 工具,该工具数据源取自/proc/diskstats 伪文件。我们可以使用如下脚本对 iodump 的输出结果和 iostat 的结果进行对比。
$ cat io_compare.sh
#!/bin/bash
if [ -z "$1" ];then
echo -e "disk partition need to input, such as \n ./io_compare.sh sda5"
exit 0
fi
partition=$1
echo "Wait 60s for iops comparison..."
sudo iodump -H -p $partition -t 60 > /tmp/iodump.log &
iostat -x -d $partition 1 60 | grep $partition > /tmp/iostat.log &
wait
iodump_read=$(cat /tmp/iodump.log | grep -w R | wc -l)
iodump_write=$(cat /tmp/iodump.log | grep -w W | wc -l)
iostat_read=$(cat /tmp/iostat.log | awk 'BEGIN{sum=0}{sum=sum+$4}END{print int(sum)}')
iostat_write=$(cat /tmp/iostat.log | awk 'BEGIN{sum=0}{sum=sum+$5}END{print int(sum)}')
iodump_total=$((iodump_read+iodump_write))
iostat_total=$((iostat_read+iostat_write))
read_percent=$(echo "$iodump_read $iostat_read" | awk '{div=100*$1/$2;printf("%.2f%%",div)}')
write_percent=$(echo "$iodump_write $iostat_write" | awk '{div=100*$1/$2;printf("%.2f%%",div)}')
total_percent=$(echo "$iodump_total $iostat_total" | awk '{div=100*$1/$2;printf("%.2f%%",div)}')
echo "partition $partition results:"
result="Item Read Write Total"
result=$result"\niostat $iostat_read $iostat_write $iostat_total"
result=$result"\niodump $iodump_read $iodump_write $iodump_total"
result=$result"\npercent $read_percent $write_percent $total_percent"
echo -e "$result" | column -t
运行 iops 对比脚本,结果如下。
$./io_compare.sh nvme0n1p1
Wait 60s for iops comparison...
partition nvme0n1p1 results:
Item Read Write Total
iostat 5122 35660 40782
iodump 5044 35394 40438
percent 98.48% 99.25% 99.16%
从对比结果可以看到 iodump 采集到的 iops 数量在读和写上,都非常接近 iostat 的统计值。只有这样,当磁盘 io 打满时,我们分析 iodump 的结果,才是有意义的。
6.2、跟踪点的正确选择
iodump 的-a 选项提供了 Q、G、I、D 和 C 几个主要的跟踪点参数,按照顺序,这也是磁盘 io 在内核块设备层的路径顺序,iodump 默认选择使用 G 跟踪点。但也有一些磁盘类型,并不经过 G 跟踪点,此时我们可以改用 Q 跟踪点。这类磁盘有个共同特点,iostat 的 rrqm/s 和 wrqm/s 都永远为 0。
$ sudo iodump -p dfa -a Q
6.3、再谈数据有效性
结合上文介绍,对于普通的磁盘类型,选择 G 跟踪点,iodump 输出结果将在 iops 数量上和 iostat 对齐。如果选择了 Q 跟踪点,iodump 输出结果将较大幅度多余 iostat 的结果,这部分多出来的就是 rrqm/s 和 wrqm/s 的数量。
但是选择 G 跟踪点时,iodump 的输出结果在 bps 指标上会较大幅度小于 iostat 的结果,这是由于 G 跟踪点 block_getrq 只获取了新创建 request 结构体时的 bio 结构体信息,没有包含被 merge 合并的 bio 的 io 数据大小。此时,我们使用 D 和 C 跟踪点可以实现 iodump 和 iostat 在 bps 指标上一致的目的。
各个跟踪点各有优缺点,用如下表格显示。默认跟踪点选择 G 是一个权衡利弊的结果。
跟踪点 | iops | bps | 上下文 |
---|---|---|---|
Q | 多于 | 等于 | 保持 |
G | 等于 | 少于 | 保持 |
I | 少于 | 少于 | 保持 |
D | 等于 | 等于 | 改变 |
C | 等于 | 等于 | 改变 |
实际使用中,iostat 工具毕竟只能记录实时数据,有时候我们还需要依赖 sar 类型工具自动记录 io 活动的历史数据。在这里我们推荐龙蜥社区的 ssar 工具的 tsar2 命令。
七、使用案例
7.1、诊断磁盘 io 打满
生产中遇到一个案例,磁盘分区 sda6 平时读 IOPS 是 20 多,但有时候读 IOPS 会瞬时高出数倍,并将磁盘 io util 打满。通过查看 tsar2 显示历史数据如下。
$ tsar2 --io -i 1 -I sda6
Time -sda6-- -sda6-- -sda6--
Time rs ws util
30/05/22-03:30 25.93 68.33 40.71
30/05/22-03:31 24.82 69.60 40.50
30/05/22-03:32 42.88 53.77 61.69
30/05/22-03:33 327.93 52.13 100.00
30/05/22-03:34 390.03 46.73 100.00
30/05/22-03:35 300.53 56.80 100.00
可以看到随着读 iops(rs 指标)的快速增长,磁盘使用率(util)指标瞬间打满到 100%。磁盘使用率打满时,会严重影响应用进程的 io 请求响应时间。此时,iodump 能详细显示每一个 iops 的详情信息,以便能判断高并发的 iops 是否符合预期。
$ sudo iodump -p sda6 -t 60 -s iodump.log # 使用 iodump 抓取 io 详情
通过 iodump 工具抓取 sda6 分区的 io detail 信息,一目了然,非常明显的即可看到是 task 进程在大量读取 rank.data 这个 archive 的文件。以上案例也是 iodump 使用中最常用的经典场景。
$ cat iodump.log
datetime comm ioize rw launcher fullpath
2022-06-01T14:24:58.163214 task 4096 R read /archive/rank.data
2022-06-01T14:24:58.163234 task 4096 R read /archive/rank.data
2022-06-01T14:24:58.163258 task 12288 R read /archive/rank.data
2022-06-01T14:24:58.163318 task 8192 R read /archive/rank.data
7.2、内存颠簸型磁盘 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 配额,不足以完全融下进程的代码段,从而引起代码段的不同部分频繁被丢弃和磁盘读取。
7.3、诊断元数据 io
块设备层产生的 io,大体上可以分为 2 大类。第一类是由于读写用户数据产生数据 io,又叫 block io。另外一类是读写文件的各种属性信息等的元数据 io,又叫 meta io。对于 block io,iodump 目前可以解析出读写的文件全路径,显示在 fullpath 列,但是对于 meta io,iodump 目前尚没有实现低开销情况下解析完整路径的功能。这种 meta 类型的 io,即使没有完整的 fullpath 文件信息,我们仍然可以通过 iodump 提供的其他字段获取有价值的重要信息,比如进程名字段 comm 和包含系统调用信息的 launcher 字段。
严格意义上说,在 fullpath 字段未能解析的情况下,还有一种文件系统本身记录 journal log 产生的 io。这种类型的 io 典型特征是 comm 字段以 jbd2 开头,后面是磁盘分区的信息。jbd 类型的 io 是由于其他写类型 io 间接引发,本身 iops 数量占比也不高。
datetime comm iosize rw rwsec launcher fullpath
2022-06-02T19:32:28.441106 jbd2/vdc2-8 4096 W S ret_from_fork -
2022-06-02T19:32:28.441465 jbd2/vdc2-8 4096 W S ret_from_fork -
2022-06-02T19:32:28.441482 jbd2/vdc2-8 4096 W S ret_from_fork -
7.4、写类型的元数据 io
还有一类 io,典型特征是 comm 字段以 kworker 开头,fullpath 字段未解析,扩展 io 类型中显示这个 io 扩展类型为 M,即 Meta 类型。kworker 是内核线程,说明这些写操作经过了内核 pagecache。符合以上特征的 io,根据我们的经验主要是由 unlink/unlinkat、rename/renameat、mkdir/mkdirat 和 open/openat(O_CREAT)等系统调用触发。
datetime comm iosize rw rwsec launcher fullpath
2022-06-01T16:47:35.768009 kworker/u200:1 4096 W M ret_from_fork -
2022-06-01T16:47:35.768018 kworker/u200:1 4096 W M ret_from_fork -
2022-06-01T16:47:35.768027 kworker/u200:1 4096 W M ret_from_fork -
7.5、获取元数据 io 的文件信息
元数据写类型 io 的三个关键字段 comm、launcher 和 fullpath 都没有有价值的信息,无法有效定位 io 来源。但是这种类型的 io,我们可以通过系统调用层的跟踪来准确定位 io 来源。这里提供两种有效方案。
下面是一个用于追踪 rename 系统调用的 bcc-tools,及使用它追踪文件改名操作的效果。必要时,也可以根据进程名称或者 pid 进行过滤。
$ sudo /usr/share/bcc/tools/trace 'sys_rename "%s %s", arg1, arg2'
PID TID COMM FUNC -
136355 139335 task sys_unlink /snapshot/status.2.LOG /snapshot/status.3.LOG
136355 139335 task sys_unlink /snapshot/status.1.LOG /snapshot/status.2.LOG
136355 139335 task sys_unlink /snapshot/status.LOG /snapshot/status.1.LOG
对于较低版本内核,可以使用 systemtap 工具。下面是一个用于追踪 rename 系统调用的 systemtap 脚本,及使用追踪脚本追踪文件改名的效果。必要时,可以根据进程名称或者 pid 进行过滤。
$ cat trace.stp
#!/usr/bin/env stap
probe syscall.rename{
printf("%s %s %d %s\n", name, execname(), pid(), argstr);
}
probe timer.s(60){
exit();
}
$ sudo stap trace.stp
rename task 11669 "/snapshot/status.2.LOG /snapshot/status.3.LOG"
rename task 11669 "/snapshot/status.1.LOG /snapshot/status.2.LOG"
rename task 11669 "/snapshot/status.LOG /snapshot/status.1.LOG"
以上这种日志轮转引起的写类型的元数据 io 是一种生产上常见的引起大量磁盘 io 的原因。
7.6、读类型的元数据 io
除去以上谈到的 jbd2 和 kworker 外,其他产生 io 的进程主要是用户态进程。由用户态进程引发的写入类型 io 大部分情况下都会经过 pagecache,因此观察到的用户态进程触发的 io 主要是读类型 io。进一步识别,需要主要关注 launcher 字段。
这类元数据类型的读 io 主要是由 getdents/getdents64、stat、lstat、fstat/fstatat 和 access/faccessat 等系统调用触发。除 getdents/getdents64 外,大多可以通过上文提供的跟踪系统调用方式找到 io 来源。
datetime comm iosize rw rwsec launcher fullpath
2022-06-01T15:36:21.313887 task 4096 R M getdents -
2022-06-01T15:36:21.314465 task 4096 R A newstat -
2022-06-01T15:36:21.314489 task 4096 R A newlstat -
2022-06-01T15:36:21.314507 task 4096 R A newfstat -
7.7、写系统调用触发元数据读 io
在实际跟踪 io 活动时,我们会发现如 unlink、rename 这些写入元数据的系统调用,会触发读 io。这类 io 主要是由于进程执行这类系统调用,需要识别这个文件时,把文件路径名传递给内核。这个过程需要不断检查与每个目录或文件向匹配的目录项数据结构,这就需要触发从磁盘的元数据读操作。
datetime comm iosize rw rwsec launcher fullpath
2022-06-03T18:25:02.768859 task 4096 R M rename -
2022-06-03T18:25:02.944608 task 4096 R M unlink -
2022-06-03T18:25:02.944780 task 4096 R M mkdir -
不论是读系统调用触发的元数据读 io,还是写系统调用触发的元数据读 io,当内存资源相对充裕时,内核 buffer cache 都会将曾经访问过的元数据信息都缓存住。一旦出现内存资源相对不足时,buffer cache 就会被内核回收。这时用户态程序再次访问这部分元数据信息时,就会触发大量读 io。
7.8、存储 io 的双峰模式
内核有个参数 max_sectors_kb,表示设备允许的最大请求大小,设置太大会超过磁盘硬件的允许范围。在这里,我们设置为 512KB。操作系统的块设备层会将较大的数据切分为不大于 512KB 的 request 请求。
$ cat /sys/block/sdc/queue/max_sectors_kb
512
然后我们使用 iodump 抓取 60 秒的数据,并对写数据进行分析。
$ sudo iodump -p sdc1 -t 60 -s iodump.log -H
$ cat iodump.log | grep -w W | awk '{print $4}' | sort | uniq -c | sort -k 2nr
2245 524288
24 520192
6 516096
......
2 16384
19 12288
35 8192
1248 4096
明显可以看到,所有的写 io 的 iosize 大小呈现双峰分布,一部分集中于 4096 大小,一部分集中于 524288 的大小。这和我们前面的提到的场景是吻合的,iosize 是 4096 大小的基本属于元数据 io,iosize 是 524288 大小的基本属于数据块 io。换算成秒级数据,524288 大小的 io 数为 37.4 个/秒,4096 大小的 io 数为 20.8 个/秒。
在这里,我们查看 iodump 运行抓取相同时间的 60 秒的 tsar2 的 io 监控数据。ws 表示写 iops 数为 64.23,warqsz 表示平均写 io 的大小为 321.28Kb。
$ tsar2 --io -I sdc1 -i 1
Time -sdc1-- -sdc1--
Time ws warqsz
04/06/22-09:53 64.23 321.28
基于 iosize 的双峰分布模式,我们可以简单的列一个二元一次方程。x 是 4096 这样的小 io 的数量,y 是 524288 这样的大 io 的数量。
x + y = 64.23
4096 * x + 524288 * y = 64.23 * 321.28 * 1024
解析二元一次方程,x 是 24.11,y 是 40.12。对比 iodump 实际抓取的小 iops 数占比 35.8%,和根据 tsar2 监控数据推算的小 iops 数占比 37.5%,基本相当。这说明,我们平时通过各种监控系统看到的平均 io 大小指标中蕴含重要信息。
数据源 | iodump | tsar2 |
---|---|---|
大 io 数 | 37.4 | 40.12 |
小 io 数 | 20.8 | 24.11 |
小 io 占比 | 35.8% | 37.5% |
八、性能开销
说到性能开销,需要先简单介绍下目前行业内各种主流的 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/核 |
九、许可证
This project is licensed under the MIT License - see the LICENSE file for details, unless explicitly stated otherwise. Some files in the 'kernel' directory are dual licensed under either GPL v2 or MIT. Relevant license is reminded at the top of each source file.
十、技术交流
iops dump 工具还在不断开发和优化过程中,如果大家觉得工具使用有任何疑问、对工具功能有新的建议,或者想贡献代码,请加群交流和反馈信息。钉钉群号:31987277 或 33304007。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: Linux iodump 命令
下一篇: IO 技术栈
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论