要深度追踪特定业务下某块磁盘的原子级读写行为,必须先确认内核启用CONFIG_BLK_DEV_IO_TRACE=y/m、挂载debugfs、绑定业务PID精准采集;解析时需用blkparse显式输出CPU ID与纳秒偏移,通过Q/G/I/D/C事件定位IO各阶段耗时,并结合debugfs反查A事件扇区号对应的具体文件路径。

要深度追踪特定业务下某块磁盘的所有原子级读写行为,关键不是“开启就完事”,而是精准控制采集边界、严格对齐上下文、逐阶段解读事件语义。blktrace 本身不识别“业务”,它只忠实记录 block layer 的每一个 Q/G/I/D/C 事件——所以必须靠你把业务行为与这些底层事件锚定起来。
明确目标设备并确认内核支持
blktrace 跑不起来,90% 的问题出在第一步。
- 执行
zcat /proc/config.gz | grep CONFIG_BLK_DEV_IO_TRACE(若无/proc/config.gz,查/boot/config-$(uname -r)) - 必须看到
CONFIG_BLK_DEV_IO_TRACE=y或=m - 若为
=m,需提前运行sudo modprobe blk_trace - 若为
=n或未找到,当前内核不支持,换内核或改用biosnoop(bpftrace 实现,适用于云主机/NVMe) - 检查
/sys/kernel/debug/block/下是否存在目标设备目录(如sdb),不存在说明 debugfs 未挂载或设备不被支持
绑定业务进程 + 精准采集窗口
不能等业务跑起来再开 trace,也不能 trace 太久冲掉关键片段。
- 先用
pidof your_app或pgrep -f "mysql|redis|etcd"锁定主进程 PID - 启动采集前,清空 page cache(减少干扰):
echo 3 | sudo tee /proc/sys/vm/drop_caches - 使用
-a issue过滤只抓下发到 driver 的写请求(或-a queue抓所有入队请求),避免噪音:sudo blktrace -d /dev/sdb -a issue -w 30 -o sdb.issue.30s
- 采集时同步打标真实时间:
date +%s.%N; sudo blktrace -d /dev/sdb -w 30 -o sdb.trace; date +%s.%N
后续可用这两个时间戳校准
blkparse输出中的相对偏移
解析时还原 CPU 局部时钟 + 关联进程上下文
多核下每个 .blktrace.N 文件的时间戳是独立 cycle counter,直接拼接会错乱。
- 合并所有 CPU 文件:
blkparse -i sdb.trace -f "%5T.%9t %5p %2c %5C %5s ] ] %5S %5N %5I %5P\n" > sdb.parsed-
%C显示 CPU ID,%t是纳秒级偏移,必须同时看这两列才能判断是否跨核调度延迟
-
- 过滤指定 PID 的全部事件:
awk '$5 == 12345' sdb.parsed
- 关键看事件类型(第六列):
-
Q:请求入队(可能被合并、限流) -
G:分配 request 结构体(split/merge 已完成) -
I:进入 IO scheduler 队列(cfq/deadline/noop) -
D:下发给驱动(driver 接收) -
C:硬件返回完成(真正耗时在此段) -
A:原始请求抵达 block layer(含 offset、size、R/W 标志,最接近上层语义)
-
定位原子读写并反查文件路径(需 ext4/xfs)
A 事件给出的是物理扇区号(512B 单位),要回溯到具体文件:
- 提取一行
A事件,例如:8,16 0 123 0.002145678 12345 A WS 420143 + 8 <p>表示:向设备 <code>sdb</code>(主次设备号 8,16)的逻辑块 <code>420143</code> 写入 8 个扇区(4KB),原始请求来自 PID 12345 </p>
- 换算为文件系统块(ext4 默认 4KB = 8 个扇区):
420143 ÷ 8 = 52517.875→ 实际起始块为52517(向下取整) - 挂载 debugfs 并查 inode:
sudo debugfs -R "icheck 52517" /dev/sdb # 输出类似:Block Inode number # 52517 123456 sudo debugfs -R "ncheck 123456" /dev/sdb # 输出类似:Inode Pathname # 123456 /var/lib/mysql/ibdata1
这样就把一次原子写,精准对应到 MySQL 的共享表空间文件
基本上就这些。不需要复杂脚本,但每一步都得踩准——否则看到的只是乱序的数字,不是业务的真实 IO 脉搏。











