概述
IO 延迟问题几乎是每个生产系统都会或多或少遇到的问题。虽然现在 NVMe + SSDs 已经可以到达 10Gbytes/s 的呑吐量,价格也非常亲民。但 IO 延迟问题不会消失。因为:
- 一些基于网络的的存储方案,如 Ceph,天然地有不稳定性
- SSD / RAIN Controller 本身的不稳定性
在 Linux 下,传统地,我们有 iostat / sar 等等工具可以看系统级、存储设备级的问题。但均不能告诉你以下几点:
- 发生 IO 延迟时,进程/线程实际上的挂起时长
- 进程/线程因 IO 延迟挂起过几次
这些问题,在 Linux 下由于写操作的异步落盘 pdflush 线程,问题变得更加难回答。当然,你可以用新技术 BPF,但不一定需要这个牛刀。
有同学会问,知道 IO 延迟对线程的实际影响有什么用?答案可见我另外的两篇文章:
基本原理
Linux 有很多隐藏特性,其中 Per-task statistics interface 与其上层的 Delay accounting 可以为每个线程增加一些 delay 的统计指标。 内核源码中,也有读取相关指标的 demo 应用源码。以下是一个操作示例:
sudo sysctl kernel.task_delayacct=1
# run your app
sudo ./getdelays -t 608452 -d -i
print delayacct stats ON
printing IO accounting
TGID 608452
CPU count real total virtual total delay total delay average
60043 4332000000 4670325102 49110971 0.001ms
IO count delay total delay average
199 200090856599 1005ms
SWAP count delay total delay average
0 0 0ms
RECLAIM count delay total delay average
0 0 0ms
THRASHING count delay total delay average
0 0 0ms
COMPACT count delay total delay average
0 0 0ms
WPCOPY count delay total delay average
201 313245 0ms
: read=0, write=0, cancelled_write=0
Ubuntu 好像没有这个 getdelays 工具的 apt 包,需要自己编译。我是个懒人,所以我选择了另一个查看这些指标的方法。
我们知道 linux 的 /proc/pid/stat
其实已经有这个 IO Delay 的指标了:
https://man7.org/linux/man-pages/man5/proc.5.html
/proc/pid/stat Status information about the process. This is used by ps(1). It is defined in the kernel source file fs/proc/array.c. ... (42) delayacct_blkio_ticks %llu (since Linux 2.6.18) Aggregated block I/O delays, measured in clock ticks (centiseconds).
所以只需要找个其它现成的程序,会使用到这个指标就行。正好,htop 与 pidstat 都会。我们看看常用点的 pidstat。
https://manpages.ubuntu.com/manpages/focal/en/man1/pidstat.1.html
-d Report I/O statistics (kernels 2.6.20 and later only). The following values may be displayed: ... iodelay Block I/O delay of the task being monitored, measured in clock ticks. This metric includes the delays spent waiting for sync block I/O completion and for swapin block I/O completion.
如果你好奇,可以查看它的源码: https://github.dev/sysstat/sysstat/blob/master/pidstat.c
这是我执行的一个结果:
sudo sysctl kernel.task_delayacct=1
# run your app
sudo pidstat -d -r -p 202484 -u 205:08:35 PM UID PID %usr %system %guest %wait %CPU CPU Command
05:08:37 PM 0 202484 9.50 66.00 0.00 0.00 75.50 1 dd
05:08:35 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
05:08:37 PM 0 202484 1347648.00 0.00 0.00 49 dd
05:08:37 PM UID PID %usr %system %guest %wait %CPU CPU Command
05:08:39 PM 0 202484 10.00 82.00 0.00 0.00 92.00 1 dd
05:08:37 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
05:08:39 PM 0 202484 1598144.00 0.00 0.00 15 dd
如果你好奇 Per-task statistics interface 与其上层的 Delay accounting 与 /proc/pid/stat
之间在内核设计与源码之间的关系,可以见我整理的图:
sysctl kernel.task_delayacct
iotop
documentation:
https://kaisenlinux.org/manpages/iotop.html
iotop watches I/O usage information output by the Linux kernel (requires 2.6.20 or later) and displays a table of current I/O usage by processes or threads on the system. At least the CONFIG_TASK_DELAY_ACCT, CONFIG_TASK_IO_ACCOUNTING, CONFIG_TASKSTATS and CONFIG_VM_EVENT_COUNTERS options need to be enabled in your Linux kernel build configuration and since Linux kernel 5.14, the
kernel.task_delayacct
sysctl enabled.从 Linux 内核 5.14.x 开始,
kernel.task_delayacct
可在运行时配置并默认设置为关闭。
Ubuntu 下编译 getdelays 的方法
cd kernel_src/tools/accounting/
gcc -I/usr/src/linux-headers-5.19.0-50-generic/include/uapi -I/usr/src/linux-headers-5.19.0-50-generic/include/generated/uapi -I/usr/src/linux-headers-5.19.0-50-generic/include getdelays.c -o getdelays
结语
后面,我会写另外两编相关文章:《eBPF 求证坊间传闻:Java GC 日志可导致整个 JVM 服务卡顿?》、eBPF 求证坊间传闻:mmap + Java Safepoint 可导致整个 JVM 服务卡顿?