🔥 卷① 性能诊断与 eBPF
20 条。适用场景:系统慢了,但不知道慢在哪。
每条的「要点」「我的补充」都是我写的背景与踩坑,不是原文摘录;原文的具体数据与结论请点链接看原作者的表述。
一、排查起步:先把家伙备齐
1. Linux Crisis Tools
来源:Brendan Gregg · brendangregg.com 读它解决什么:故障已经发生、你 SSH 进去却发现 iostat 没装——这一条是让你在故障之前就把工具装好。
要点
- 危机工具应该预装进基础镜像,而不是等出事再
yum install:故障中往往正好网络不通或磁盘写满 - 最小集合大致是
procps、sysstat、iproute2、util-linux、bcc-tools、bpftrace、perf、trace-cmd - 光有工具不够,还要有历史数据。
sar的价值在于事后能回看崩溃前十分钟,top只能看现在
我的补充:容器里排查最容易翻车——很多镜像连 ps 都没有。别往业务镜像里塞工具,用 kubectl debug 起临时容器共享目标的 namespace 更干净。宿主机上记得 systemctl enable --now sysstat 并把 /etc/cron.d/sysstat 的采样间隔从默认 10 分钟改到 1 分钟,否则事后回看粒度粗到没用。
2. Systems Performance: Enterprise and the Cloud, 2nd Edition
来源:Brendan Gregg · brendangregg.com 读它解决什么:你想要的不是又一份命令清单,而是一套遇到没见过的系统也能用的方法论。
要点
- 核心是 USE 方法:对每个资源逐个问三件事——利用率(Utilization)、饱和度(Saturation)、错误(Errors)
- 这套方法的好处是可穷举:列出 CPU、内存、磁盘、网络、控制器、总线,逐格填,不靠灵感
- 另一条主线是延迟分析:把一次请求的耗时逐层拆开,而不是盯着资源指标猜
我的补充:落地时最容易漏掉的是饱和度——绝大多数监控只画了利用率。CPU 利用率 60% 配上运行队列常年堆积,体验已经很差了。看饱和度用 vmstat 1 的 r 列(可运行进程数,超过核数就是在排队),更现代的做法是读 PSI:cat /proc/pressure/cpu,some avg10 才是"有任务在等 CPU"的直接证据。别把这本书当小说顺读,先读方法论那章和你当前要排查的那个子系统。
3. LISA2019 Linux Systems Performance
来源:Brendan Gregg · brendangregg.com 读它解决什么:想在一小时内建立起 Linux 观测工具的整体地图,而不是零散记命令。
要点
- 工具按观测层次分层:静态配置检查、计数器(counters)、追踪(tracing)、剖析(profiling)
- 值得背下来的是"上手 60 秒"清单:
uptime、dmesg | tail、vmstat 1、mpstat -P ALL 1、pidstat 1、iostat -xz 1、free -m、sar -n DEV 1、top - 这个顺序不是随意的:先确认负载趋势和内核报错,再逐个资源缩小范围
我的补充:vmstat 1 的第一行要丢掉——它是自开机以来的平均值,不是当前值,这个坑很多人踩过。dmesg 优先看 OOM killer 和网卡 reset;dmesg -T 能把时间戳变成人类可读,排查时省事很多。
4. Computing Performance: What's on the Horizon
来源:Brendan Gregg · brendangregg.com(USENIX SREcon APAC 2022 讲座) 读它解决什么:判断未来几年性能工程的重心会往哪里挪,用来决定自己学什么。
要点
- 摩尔定律放缓之后,性能收益越来越依赖专用硬件(GPU、DPU、加速器)和软件侧的针对性优化,而不是等 CPU 变快
- 观测能力本身成为瓶颈:异构硬件越多,"看不见"的部分越多
- 云环境让性能问题多了一层——你调不动的部分越来越多,识别边界比调参更重要
我的补充:对普通运维最直接的推论是:先确认瓶颈在不在你能改的那一层。云主机上遇到磁盘延迟抖动,先查是不是踩了云盘的 IOPS/吞吐配额(突发额度耗尽)再去调内核参数,顺序反了会浪费很多天。
二、eBPF 工具链:从 bcc 到 CO-RE
5. A thorough introduction to bpftrace
来源:Brendan Gregg · brendangregg.com 读它解决什么:想用一行命令回答"哪些进程在读这个文件""这个函数延迟分布如何",而不是写 C 程序。
要点
- bpftrace 借了 awk 的形状:
probe /filter/ { action } - probe 类型要分清:
tracepoint(内核静态埋点,稳定)、kprobe(任意内核函数,随版本变)、uprobe(用户态函数)、usdt(应用静态埋点)、profile(定时采样)、interval(定时输出) - 聚合靠 map:
@[comm] = count()、@ = hist(latency),直方图比平均值有用得多——平均值会把偶发长尾抹平
我的补充:生产环境优先用 tracepoint,kprobe 挂的是内核符号,升个内核就可能失效,而且函数被内联后即使符号还在也抓不到调用。用 bpftrace -l 'tracepoint:*' 先列出可用埋点再动手。另外 bpftrace -l 支持通配,bpftrace -lv tracepoint:xxx 能看到参数结构,这比翻源码快。
6. BPF Performance Tools(书)
来源:Brendan Gregg · brendangregg.com 读它解决什么:需要一份按子系统组织的、拿来就能跑的 BPF 工具清单。
要点
- 按 CPU、内存、文件系统、磁盘、网络、安全、语言运行时分卷,每类给出对应工具和该问的问题
- 工具本体在 bcc 与 bpftrace 两个仓库里,多数发行版打包成
bcc-tools - 真正的价值是"该量什么":比如磁盘慢要看
biolatency的分布而不是平均延迟
我的补充:装好后用 ls /usr/share/bcc/tools/ 看本机实际有哪些(不同发行版打包差异不小)。书里的脚本别直接照抄——内核 5.x 之后部分结构体和 tracepoint 变了,跑不通先用 bpftrace -l 确认埋点是否还在。
7. BPF binaries: BTF, CO-RE, and the future of BPF perf tools
来源:Brendan Gregg · brendangregg.com 读它解决什么:搞清楚为什么该从 bcc 迁到 libbpf,以及 BTF、CO-RE 到底解决了什么。
要点
- bcc 是运行时编译:每次启动都要带着 LLVM 和内核头文件现场编译,内存和启动延迟都很可观
- CO-RE(Compile Once – Run Everywhere)靠 BTF 记录内核结构体布局,编译产物在不同内核上自行重定位字段偏移
- 结果是单个小二进制,启动快、无 LLVM 依赖,适合随 agent 分发
我的补充:前提是内核开了 CONFIG_DEBUG_INFO_BTF,检查方式:ls /sys/kernel/btf/vmlinux 存在即可用。RHEL 8.2+ / Ubuntu 20.10+ 默认开启,老内核(尤其 CentOS 7)用不了,只能继续 bcc 或换 SystemTap。生产环境部署优先找 bcc 仓库里的 libbpf-tools/ 版本,而不是同名的 Python 版。
8. BPF Internals(eBPF)
来源:Brendan Gregg · brendangregg.com(USENIX LISA2021 讲座) 读它解决什么:verifier 报了一句看不懂的错,你需要知道它到底在管什么。
要点
- verifier 的职责是保证程序有界终止且内存访问安全:不允许无界循环、指令数有上限、helper 调用必须在白名单内
- JIT 把 BPF 字节码编译成本机指令,所以运行开销远低于解释执行
- map 是 BPF 程序与用户态之间唯一的正式通道,类型(hash / array / perf event / ringbuf)选错会直接影响性能
我的补充:最常见的 verifier 报错是 invalid mem access,九成是指针没判空就解引用——从内核结构体里读出来的指针必须先 if (ptr == 0) return;。想看编译结果用 bpftool prog dump jited id <ID>。另外指令上限在新内核放宽到一百万,但复杂循环仍容易被拒,写不过就拆成多个 probe。
9. BPF: A New Type of Software
来源:Brendan Gregg · brendangregg.com 读它解决什么:理解 eBPF 为什么不只是"追踪工具",而是一类新的内核内可编程执行环境。
要点
- 把 eBPF 看作"内核里的安全沙箱",才能解释 XDP(网卡驱动层收包处理)、Cilium(网络策略)、Katran(四层负载均衡)为什么可能存在
- 与内核模块的关键差别是受校验:模块出错直接 panic,BPF 出错最多加载失败
- 由此带来的是交付方式变化:能力可以随用户态程序热加载,不必等内核发版
我的补充:这个视角对选型很有用。要在数据面做包处理,先问该用 XDP(驱动层,最快但能做的事有限)、tc(灵活些)还是用户态(DPDK/AF_XDP)。别一上来就写 XDP——多数场景 tc-bpf 就够,而 XDP 在很多虚拟网卡上根本不支持原生模式。
10. How To Add eBPF Observability To Your Product
来源:Brendan Gregg · brendangregg.com 读它解决什么:要给自家产品/agent 加 eBPF 采集能力,需要先知道工程代价在哪。
要点
- 关键决策是自研 probe 还是复用 bcc/libbpf 工具,以及是否接受 BTF 依赖
- 跨内核版本兼容是主要成本来源,不是写 probe 本身
- 采集开销必须先量:高频 probe(比如挂在每次收包上)能把系统拖慢,得先做基线
我的补充:跨版本兼容成本通常被低估一个数量级。如果客户环境包含 CentOS 7,基本要准备两套实现。上生产前务必压一遍开销:先测无 probe 基线 QPS,再挂上对比。经验值是常规追踪应控制在 1% 以内,超过 5% 就该换更粗的采样粒度。
三、边界与风险:别把观测工具当安全工具
11. eBPF Observability Tools Are Not Security Tools
来源:Brendan Gregg · brendangregg.com 读它解决什么:有人打算拿 execsnoop 的输出当安全审计证据——这条说明为什么不行。
要点
- 根因是 TOCTOU(检查与使用之间的时间窗):观测工具从用户态内存读参数,恶意进程可以在检查之后改掉内容
- 观测工具的设计目标是"低开销看清常态",不是"对抗主动规避"
- 想做安全审计要用为此设计的机制:BPF LSM hook 或 audit 子系统
我的补充:具体表现是 execsnoop 抓到的命令行可以被伪造,opensnoop 看到的路径也可能与内核最终解析的路径不一致(符号链接竞态)。做入侵检测请走 BPF LSM(内核 5.7+,需 CONFIG_BPF_LSM 并在 lsm= 启动参数里启用),它在内核态拿到的是已解析的结果。
12. No More Blue Fridays
来源:Brendan Gregg · brendangregg.com 读它解决什么:CrowdStrike 蓝屏事件之后,评估"端点安全该不该用内核模块"。
要点
- 论点是端点安全应从内核模块转向 eBPF:模块里的一个越界写就能让整机 panic,而 BPF 程序过不了 verifier 只是加载失败
- 这不是把风险降到零,而是把"崩溃整机"降级为"功能不可用"
- 前提是平台支持度足够
我的补充:两点要现实一些。一是 eBPF 也能出事——内核 helper 自身的 bug、map 内存耗尽(尤其没设 max_entries 上限时)仍会影响系统稳定性。二是 Windows 上的 eBPF 成熟度远不及 Linux,跨平台端点产品短期内难以只靠 eBPF。真正该做的是灰度发布策略:那次事件的直接放大器是配置全量瞬时下发。
13. The Return of the Frame Pointers
来源:Brendan Gregg · brendangregg.com 读它解决什么:profile 出来一堆断掉的调用栈,只有栈顶没有调用者——这条解释为什么。
要点
-fomit-frame-pointer省下一个寄存器换取少量性能,代价是丢失可靠的栈回溯基础- 替代方案(DWARF unwind、ORC)要么慢要么吃内存,采样时压力大
- Fedora 38、Ubuntu 24.04 等发行版重新默认启用帧指针,正是因为可观测性收益大于那点性能损失
我的补充:自己编译的服务遇到断栈,加 -fno-omit-frame-pointer 重编即可,开销通常在 1% 左右,比丢失栈信息划算得多。JIT 语言另说:Java 要装 perf-map-agent 或用 -XX:+PreserveFramePointer,Node 用 --perf-basic-prof,Python 3.12+ 可用 perf 原生支持。
四、火焰图:把采样变成可读的图
14. AI Flame Graphs
来源:Brendan Gregg · brendangregg.com 读它解决什么:AI 训练/推理慢,但 CPU 火焰图上看不出任何热点。
要点
- 传统 CPU 火焰图对 AI 负载解释力有限:时间花在 GPU kernel 执行和主机—设备数据搬运上,CPU 侧只看到在等
- 要把 GPU 侧的执行纳入同一张图,才能看出到底是算子慢还是传输慢
- 这类观测依赖硬件厂商的栈支持,不是纯内核能力
我的补充:日常排查可以先用便宜手段定性:nvidia-smi dmon 看 GPU 利用率与显存带宽,利用率长期偏低基本说明瓶颈在数据供给(dataloader、拷贝、同步点)而不是算力。确认是数据侧再深入。PyTorch 用户先开 torch.profiler 看 CUDA 时间轴,比直接上火焰图门槛低。
15. Doom GPU Flame Graphs
来源:Brendan Gregg · brendangregg.com 读它解决什么:想直观理解 GPU 火焰图怎么读——拿一个人人熟悉的程序当样本。
要点
- 用 Doom 这种行为可预测的程序做演示,能把可视化本身的含义与被测程序的复杂度分开
- GPU 火焰图的横轴仍是"占用时间比例",不是时间顺序,这一点和 CPU 火焰图一致,最容易被误读
- 拿熟悉的负载校准新工具,是验证工具是否可信的通用手段
我的补充:这个方法值得借用——上新监控工具时,先在一个你已知答案的负载上跑一遍。比如用 stress-ng --cpu 4 或 dd 制造已知的 CPU/IO 压力,确认工具画出来的图和你的预期一致,再拿它去看生产。跳过这步很容易把工具的 artifact 当成业务问题查半天。
16. Two kernel mysteries and the most technical talk I've ever seen
来源:Brendan Gregg · brendangregg.com 读它解决什么:想了解 ftrace 的实现机制,以及内核级疑难问题是怎么被啃下来的。
要点
- ftrace 的基础是编译期在函数入口留 nop,运行时按需打补丁成 call,因此未启用时几乎零开销
function_graphtracer 能给出函数调用的进入/退出与耗时,适合回答"这个内核函数为什么慢"- 这类问题的解法通常是"缩小到可复现的最小场景",而不是更强的工具
我的补充:用 trace-cmd 而不是手写 /sys/kernel/debug/tracing——后者状态残留很难清理。常用起手式:trace-cmd record -p function_graph -g <函数名> -P <pid>。务必限定函数或进程:全函数追踪能让机器慢一个数量级,我在生产上试过一次,差点被当成故障。
五、真实案例:照着别人的排查路径走一遍
17. Poor Disk Performance
来源:Brendan Gregg · brendangregg.com 读它解决什么:iostat 显示 %util 100%,你正准备下结论"磁盘满了"——先等等。
要点
%util的语义是"设备至少有一个请求在处理的时间占比",在支持并发的设备上不等于饱和- SSD/NVMe 有多队列,能同时处理几十上百个请求,
%util100% 可能还远没到能力上限 - 该看的是
await(平均等待)、队列深度,以及最终的应用侧延迟
我的补充:NVMe 上判断饱和用 iostat -x 的 aqu-sz(平均队列长度)配合 biolatency 看延迟分布。真正的判据永远是应用侧延迟——设备再忙但应用不慢,就不是问题。云盘还要单独查是否触发了配额限速,这类限速在 iostat 上表现为 await 突然阶梯式上涨而 IOPS 卡在一个整数上。
18. Analyzing a High Rate of Paging
来源:Brendan Gregg · brendangregg.com 读它解决什么:看到大量换页就以为内存不够——这条帮你先分清换的是什么页。
要点
- 要区分两件不同的事:swap(匿名页换出到交换区,通常是真的内存紧张)与 page cache 换入换出(文件页,往往是正常行为)
vmstat的si/so是 swap;pgpgin/pgpgout是块设备换页,量大不一定有问题- 内存回收的压力大小要看扫描与回收的比例,而不是绝对页数
我的补充:容器里这套指标会骗人——cgroup memory limit 触发的回收在宿主机 vmstat 上几乎看不出来。要看 /sys/fs/cgroup/<path>/memory.stat 里的 pgscan/pgsteal,以及 memory.pressure(PSI)。pgscan 远大于 pgsteal 说明在反复扫描却回收不到,这是内存真不够的强信号。K8s 里 Pod 被 OOMKill 前通常先有这段特征。
19. ZFS Is Mysteriously Eating My CPU
来源:Brendan Gregg · brendangregg.com 读它解决什么:用了 ZFS 之后 CPU 占用异常,常规内存/CPU 指标却解释不了。
要点
- ZFS 有自己的缓存层 ARC,内存与 CPU 行为都不同于内核 page cache,用 Linux 常规内存指标推断会得出错误结论
- 校验和、压缩、去重都是 ZFS 侧的 CPU 消耗来源,默认配置下未必明显
- 排查这类问题必须用文件系统自己的观测接口
我的补充:ZFS on Linux 看 ARC 状态用 arcstat 或 cat /proc/spl/kstat/zfs/arcstats。两个高频坑:一是 ARC 默认可以吃掉大量内存,与业务进程争抢,需要显式设 zfs_arc_max;二是开了去重(dedup)基本必然出问题,去重表常驻内存且随数据量线性增长,除非你非常确定,否则别开。压缩用 lz4 通常是净收益。
20. The Speed of Time
来源:Brendan Gregg · brendangregg.com 读它解决什么:程序里频繁取时间戳,怀疑这件事本身成了瓶颈。
要点
- 取时间的开销取决于底层时钟源:
tsc最快,hpet、acpi_pm慢得多 - vDSO 让
clock_gettime多数情况下不必陷入内核,这是性能的关键 - 虚拟机与云主机上的时钟源选择与物理机不同,不能照搬结论
我的补充:查当前时钟源:cat /sys/devices/system/clocksource/clocksource0/current_clocksource。云主机上正常应该是 tsc 或 kvm-clock;如果看到 hpet 或 acpi_pm,高频取时间的程序会明显变慢,我遇到过一次日志组件因此吃掉 20% CPU。可用列表在同目录的 available_clocksource。另外别在热路径里用 date 之类的外部命令取时间,那是几个数量级的差距。
本卷小结
如果你只想记住三件事:
- 先量再改。USE 方法逐格填表,比凭直觉猜快得多;饱和度用 PSI(
/proc/pressure/*)看,别只看利用率。 - 分布优于平均值。
biolatency这类直方图能暴露长尾,平均延迟会把偶发抖动抹平。 - 工具会骗人。
%util在 NVMe 上不代表饱和,容器里的内存指标要读 cgroup 而不是宿主机——先确认指标语义,再下结论。
