iostat不够用是因为它只提供聚合指标(如%util、await),无法定位具体进程、文件及io模式(read/write、sync/async);而ebpf可无侵入捕获每个系统调用级io事件,结合go用户态聚合,实现精准、低开销的实时io性能分析。

为什么 iostat 不够用,得自己写 Go 工具
因为 iostat 只给聚合指标(如 %util、await),看不出是哪个进程、哪个文件、哪种 IO 模式(read vs write, sync vs async)在拖慢系统。线上服务偶发卡顿,top 看 CPU 低、内存足,但请求延迟飙升——大概率是某个 goroutine 被阻塞在 Read() 或 Write() 上,而标准工具抓不到这一层。
Go 自身的 runtime/pprof 能抓到阻塞调用栈,但默认不区分文件句柄;/proc/[pid]/fd/ 和 /proc/[pid]/io 又太分散,没法实时关联到具体读写行为。所以得自己搭一个轻量级钩子:不侵入业务代码,只监控目标进程打开的文件描述符 + 实时统计每次系统调用耗时。
用 ebpf 抓 sys_enter_read 和 sys_exit_read 是最准的路
用户态轮询 /proc/[pid]/fdinfo/ 或反复 lsof 效率低、精度差(采样间隔内可能漏掉短 IO)。eBPF 是唯一能在内核态无侵入捕获每个文件 IO 的方案,且开销可控(tracepoint 类型比 kprobe 更稳定)。
- 必须监听
sys_enter_read、sys_exit_read、sys_enter_write、sys_exit_write四个 tracepoint,缺一不可——否则无法计算耗时,也分不清是读还是写 -
bpf_get_current_pid_tgid()获取进程 ID,过滤掉非目标进程(比如你只关心myserver,别被rsyslog带偏) - 从
struct pt_regs *提取fd参数(PT_REGS_PARM1(ctx)),再通过bpf_probe_read_user读取struct file *中的f_path.dentry和f_path.mnt,拼出真实路径(注意:仅对常规文件有效,/dev/zero或 pipe 会失败) - eBPF 程序里不能直接打印路径,需用
bpf_perf_event_output推到用户态,Go 侧用github.com/cilium/ebpf/perf消费事件流
Go 用户态聚合要避免锁竞争和 GC 压力
eBPF 每秒可能推送上万条事件,如果每条都 fmt.Printf 或新建 map[string]interface{},GC 会卡住主循环,反而掩盖真实 IO 延迟。
- 用预分配的 ring buffer(
github.com/tidwall/gjson不适合,改用sync.Pool管理eventRecord结构体) - 按
pid + fd做 key 聚合,字段只存必要信息:filename(截断到 64 字节)、op("read"/"write")、latency_ns、size - 每 2 秒 flush 一次聚合结果到终端或 Prometheus:
prometheus.NewGaugeVec比CounterVec更合适,因为你要看“当前最慢的 3 个文件读”,不是累计次数 - 别用
log.Printf打印明细——改用os.Stderr.Write([]byte{...})避免锁,或者干脆关掉日志,只输出 JSON 到管道供下游处理
绕不开的兼容性坑:openat 和 preadv2 怎么办
现代 Go 程序大量用 openat(2)(尤其 os.OpenFile 在相对路径下)、preadv2(2)(io.ReadAtLeast 底层),但很多 eBPF 示例只 hook read/write,漏掉这些就等于没监控。
- 必须额外监听
sys_enter_openat,记录fd → path映射关系,缓存 5 分钟(map[int32]string存在用户态,eBPF 里只传 fd) -
preadv2和pwritev2的 tracepoint 名是sys_enter_preadv2,参数位置和read不同(offset 在PT_REGS_PARM4),不修正会导致fd解析错乱 - 容器环境要注意:eBPF 程序加载需要
CAP_SYS_ADMIN,但 Kubernetes Pod 默认不给。要么用hostPID: true+ 特权容器,要么提前把 eBPF 字节码编译好,用tc或bpftool加载后由 Go 只负责消费
真正难的不是写完,而是确认你看到的“慢 IO”确实是瓶颈——比如某次 read 耗时 800ms,但其实是磁盘队列深度已满,这时候光优化 Go 代码没用,得查 cat /sys/block/nvme0n1/stat 的 aveq 字段。工具只是把问题暴露出来,决策还得靠人。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











