Linux 内核事件追踪 eBPF 实战

2次阅读

用bpftrace通过tracepoint:syscalls:sys_enter_read与sys_exit_read配对测read延迟,加/comm==”java”/过滤,@start[tid]记时、exit分支清理,耗时转微秒并直方图分析;tracepoint比kprobe更稳定低开销,遇错先查/sys/kernel/debug/tracing/events/和tracing_on。

Linux 内核事件追踪 eBPF 实战

怎么用 bpftrace 快速抓到 read 系统调用延迟

直接上手就能定位 Java 服务响应变慢的问题,不用改代码、不重启进程。核心是利用 tracepoint:syscalls:sys_enter_readtracepoint:syscalls:sys_exit_read 配对打点,靠 @start[tid] 记录起始时间,再用 nsecs - @start[tid] 算出耗时。

  • 必须加 /comm == "java"/ 过滤,否则所有进程的 read 都会进来,直奔内存爆炸
  • delete(@start[tid]) 要写在 exit 分支里,漏掉会导致 @start 表持续膨胀,几秒后 bpftrace 自己 abort
  • 除以 1000 是转成微秒,hist 默认按 2 的幂分桶,@usecs = hist(...) 才能看清延迟分布是否毛刺严重
  • 如果目标进程是容器内 Java,comm 可能显示为 java,也可能被截断成 jav(取决于内核版本),可临时放宽为 /comm ~ "java"/

为什么 tracepoint 比 kprobe 更适合系统调用追踪

因为 sys_enter_read 这类 tracepoint 是内核预埋的稳定接口,位置固定、语义清晰;而用 kprobe 挂在 sys_read 函数上,不同内核版本函数名可能变(比如加了 __x64_sys_read 前缀),脚本一升级就失效。

  • tracepoint 不依赖符号表,/proc/kallsyms 权限受限时照样工作;kprobe 依赖 kallsyms 解析地址,无权限就挂不上
  • tracepoint 参数结构明确:args->fdargs->count 直接可用;kprobe 只能靠寄存器或偏移硬猜,稍有不慎就读错值
  • tracepoint 开销更低——它本质是条件跳转+数据拷贝,kprobe 触发时要切内核栈+保存上下文,高频率下可观测性本身就成了性能瓶颈

遇到 “invalid probe” 或 “no such file or Directory” 错误怎么办

这类报错基本都卡在 probe 名称拼写或内核配置上,不是语法问题。

  • 确认 tracepoint 是否存在:ls /sys/kernel/debug/tracing/events/syscalls/,没有 sys_enter_read 说明内核没开 CONFIG_TRACEPOINTS=y(常见于某些定制版 android 内核或旧 LTS 版本)
  • bpftrace 报 invalid probe 但路径存在?检查内核是否禁用了 ftrace:cat /sys/kernel/debug/tracing/tracing_on,是 0echo 1 > /sys/kernel/debug/tracing/tracing_on
  • 使用 sudo bpftrace -l 'tracepoint:syscalls:*' 列出所有可用 tracepoint,别凭记忆敲——sys_enter_read 在 5.10+ 内核才统一命名,老版本可能是 sys_enter_readv 单独存在
  • 某些云厂商内核会 patch 掉部分 tracepoint(如阿里云部分 ECS 镜像),此时只能退到 kprobe:SyS_read,但得查对应内核源码确认符号名

想捕获用户态 Java GC 事件,USDT 是唯一靠谱路子

系统调用层看不到 GC,jvm 内部行为必须靠 USDT(User Statically Defined Tracing)。OpenJDK 从 10 起默认编译进 USDT 探针,无需额外参数启动,但得确保 JVM 是带 --enable-dtrace 编译的版本(主流发行版基本都满足)。

  • 先用 bpftrace -l 'usdt:/usr/lib/jvm/*/jre/lib/*/libjvm.so:*' 查探针列表,常见的是 gc:::begingc:::end,不是所有 JVM 都暴露完整生命周期
  • USDT 探针需指定完整 so 路径,/usr/lib/jvm/java-17-openjdk-amd64/lib/server/libjvm.so 这种路径写错一个字符就加载失败
  • Java 进程启动时若加了 -XX:+UseShenandoahGC 等新 GC,USDT 探针可能未覆盖,bpftrace -l 列不出来就等于不可观测
  • 别指望用 uprobe 替代 USDT——uprobe 挂函数名太脆弱,JVM JIT 后函数地址乱跳,且 GC 是线程协作,单点 uprobe 极易漏事件

实际跑起来才发现,@start[tid] 表键值用 tid 而不是 pid,是因为线程级系统调用才真正对应一次 read,fork 出的子进程 tid 变了但 pid 没变,这里差一个字母就全错。

text=ZqhQzanResources