第 25 章 工具箱——下次故障,你该抡哪把扳手
前 24 章我们建立了微观认知、钉死了参数、走完了真实事故迭代环、提炼了优化哲学。这一章给你一件能直接上手的东西:工具箱。 前面零碎出现过
vmstat/iostat/ss等,这里系统讲清:每个工具解决哪一类问题、看什么输出、什么时候上、典型命令怎么敲。它是你”真遇到故障,手里该抡哪把扳手”的速查手册。 我把工具按”观察层级”分组:应用层→系统调用层→内核/子系统层→硬件层,并附一张”症状→工具”对照总表。
一、先建”工具分层地图”
墨:老哥,工具不能瞎抡。我先给你一张分层地图,你以后”按故障落在哪层,去哪层找工具”:
| 层级 | 回答的问题 | 代表工具 |
|---|---|---|
| 应用层 | 我的代码哪慢?哪个函数吃 CPU/内存? | pprof(Go)、perf、valgrind、gdb |
| 系统调用层 | 进程在反复调哪些 syscall?卡在哪个? | strace、perf trace、bpftrace |
| 内核/子系统层 | 内核在忙什么?谁在等?内存怎么流动? | vmstat、/proc/vmstat、slabtop、numastat、ftrace、bpftrace |
| 块设备层 | 盘堵不堵?队列多长? | iostat、blktrace、biotop |
| 网络层 | 连接什么状态?重传多少? | ss、nstat、tcpdump、ethtool |
| 内核日志层 | 内核报过什么错?OOM/lockup? | dmesg、journalctl -k |
你:这么多,我记不住先学哪个?
墨:先精通 vmstat/iostat/ss/sar 这四把”瑞士军刀”——它们零依赖、随时能跑、覆盖 80% 现场。再把 perf/bpftrace 当”进阶核武器”在前面四把指不出方向时才上。 下面按这个优先级讲。
二、四把瑞士军刀(必精通)
1) vmstat 1——内存/进程/IO 全景逐秒流
墨:vmstat 1 是第一把上手工具(第 18 章用它锁定了 swap thrashing 和 compaction)。它每行的列:
procs --memory---------- ---swap-- -io--- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
2 0 1024 4200 ... ... 0 0 .. .. .. .. 30 5 60 5
r:可运行进程数(含正在跑+等 CPU)。r持续 > CPU 核数 → CPU 饱和(第 05 章)。b:不可中断睡眠(D 状态)进程数。b飙 → 有进程卡在等 I/O 或内核同步动作(第 18 章 compact/direct reclaim 时飙)。si/so:swap in/out。同时跳 = swap 颠簸,即使 free 还有(第 18 章铁证)。bi/bo:块设备读/写(KB/s)。突增 → 写回或大量物理读。cs:上下文切换。异常高 → 抢锁/频繁 woke(第 04 章 futex 竞争会飙)。us/sy/id/wa:用户/系统/空闲/IO 等待 CPU 占比。sy高 → 内核忙(回收/换页);wa高 → 卡在等盘。
什么时候上:任何”卡/慢/怪”的现场,先开一个 vmstat 1 落盘,事后对齐时间轴。它是你的”第一现场记录仪”。
2) iostat -x 1——磁盘堵不堵,看队列不看利用率
墨:第 20 章的主角。关键看 aqu-sz+await+%util 三件套(详见第 20 章),%util 低不能证明没瓶颈:
iostat -x 1 # 看设备级扩展统计
iostat -x 1 sda # 只看 sdaaqu-sz高 +await高 → 排队(治本:调度器/nr_requests/缓存命中)。r_awaitvsw_await分开看,定位读还是写堵。rkB/s/wkB/s看吞吐,rrqm/s/wrqm/s看合并率。
3) ss -tin / ss -tan -——连接状态与 TCP 内部
墨:第 21 章用它看 TIME_WAIT 和 retrans。
ss -tan | awk '{print $1}' | sort | uniq -c # 按状态统计连接
ss -tin # 看每个 TCP 连接的内部:rtt,rttvar,cwnd,retrans,...
ss -tin | grep -i retrans # 揪出重传率高的连接TIME_WAIT几万 → 短连接端口耗尽(第 21 章案 A)。retrans高 → 重传(第 21 章案 B MTU 黑洞)。ss -pl看哪个进程占端口,ss -u看 UDP。
4) sar——历史回放,事后对齐
墨:sar(来自 sysstat 包)是历史记录仪——它默认每 10 分钟采一次,存 /var/log/sa/。故障时你没在场,sar 能回放”故障前那段时间到底发生了什么”:
sar -u 1 3 # CPU
sar -r 1 3 # 内存(含 kbmemfree/kbavail,比 free 准)
sar -B 1 3 # 页换入换出(pgpgIn/Out,pgfree/pgscank)
sar -W 1 3 # swap 进出(pswpin/pswpout)
sar -b 1 3 # 块设备 tps/读写
sar -n DEV 1 3 # 网卡 rxpck/txpck,rxkB/txkB
sar -n TCP,ETCP 1 3 # TCP 主动/被动建连、重传(active/passive opens, retrans)
sar -q 1 3 # 运行队列 + 平均负载什么时候上:故障已经过去、你不在场时,用 sar 回放;平时也应常驻采集,它是”事后第一手证据”。
三、进阶核武器(前四把指不出方向时才上)
5) perf——CPU 火焰图与调用栈
墨:perf 是采样 CPU 的瑞士军刀,能告诉你”CPU 时间花在哪个函数”:
perf top # 实时看哪个函数吃 CPU
perf record -F 99 -a -g -- sleep 30 # 全系统采样 30s,带调用栈
perf report # 交互看
# 生成火焰图(需 FlameGraph 工具):
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > out.svg- on-CPU 火焰图:找”热点函数”(CPU 瓶颈)。
- off-CPU 火焰图(
perf sched或 bpftrace):找”卡在哪等”(锁/IO 等待,这个往往更致命,因为 CPU 看着闲)。 perf stat -a sleep 10看 CPU 计数器:cache-misses(缓存未命中率高→NUMA/大页问题,第 23 章)、branch-misses、instructions per cycle(IPC)。
墨:perf 的价值:第 23 章 NUMA 案例里,我们就是用 perf 的 cache-miss/remote-access 发现”大量跨 node 访问”的。它能直接戳穿”CPU 不忙却卡”——因为你能看到线程卡在等内存(off-CPU) 而非算数。
6) ftrace——内核函数级跟踪(无需重启)
墨:ftrace 是内核自带的跟踪框架,能看”某个内核函数被调了多少次、每次多久”,适合确认”是不是某某内核路径在卡”:
cd /sys/kernel/debug/tracing
echo 1 > events/sched/sched_switch/enable # 跟踪调度切换
echo 1 > events/writeback/enable # 跟踪写回
cat trace # 看输出
# 函数级:
echo function > current_tracer
echo vfs_read > set_ftrace_filter
cat trace注意:ftrace 有开销,生产低峰期用;它是内核自带、无需装 bcc。适合”我想确认 direct reclaim/writeback 是不是被频繁调”这类问题(第 06/07 章机制验证)。
7) bpftrace / bcc——动态追踪神器(现代首选)
墨:bpftrace(基于 eBPF)是当代最强的”不用重启、不改代码、动态插桩”工具。它能挂到几乎任何内核函数/系统调用/用户函数上,实时统计:
# 谁在分配内存最多(按进程)
bpftrace -e 'tracepoint:kmem:kmalloc { @[comm] = sum(args->bytes); }'
# 哪些进程在等 direct reclaim(allocstall)
bpftrace -e 'kprobe:try_to_free_pages { @[comm, pid] = count(); }'
# 块设备 IO 延迟分布
biolatency.bt # bcc 自带工具,画 IO 延迟直方图
# 系统调用计数
execsnoop, opensnoop, syscount # bcc 工具
biotop, hardirqs, softirqs # bcc 工具,看中断/软中断分布(第 09 章)墨:bpftrace 的优势:零侵入、开销可控、能问几乎任何问题。第 09 章的”32 核丢包”若用 softirqs/hardirqs 工具,能直接看到 softirq 集中在某几个核;biolatency 能画出 IO 延迟直方图戳穿”平均掩盖分布”。它是你武器库里最终极的”提问权”。
四、专项工具(按子系统)
8) numastat / numactl——NUMA 诊断与绑定
墨:第 23 章主角。
numastat # 各 node 的内存分配 + 跨 node 访问数
numastat -p <pid> # 某进程的 NUMA 分布
numactl --hardware # 看 node 拓扑
numactl --membind=0 --cpubind=0 ./app # 绑 node
numactl --interleave=all ./app # 内存轮询铺所有 node看 numastat 里 interleave/foreign(跨 node 访问) 高 → NUMA 本地性破坏(第 23 章案 A)。
9) slabtop / /proc/slabinfo——内核小对象
墨:第 06 章 slab 机制的观测口。
slabtop -o # 实时看哪些 slab 占内存最多(dentry/inode 常居前)
cat /proc/slabinfo用途:发现 dentry/inode 缓存占用异常大 → 调 vm.vfs_cache_pressure(第 12/22 章);或确认 slab 泄漏(某对象只涨不降)。
10) dmesg / journalctl -k——内核日志(OOM/lockup 铁证)
墨:第 19 章 OOM 连环杀、第 18 章 lockup,全靠它。
dmesg -T | grep -i "oom\|killed\|kill\|softlockup\|hardlockup\|BUG\|taint"
dmesg -T | grep -i "out of memory" # 看 OOM 杀谁、为什么
journalctl -k -p err # 只看内核 error 级关键时刻看:Out of memory: Killed process X (...)(整机 OOM) vs Memory cgroup out of memory(cgroup OOM,第 19 章分水岭);softlockup: CPU#N stuck for 23s(第 05 章看门狗)。
11) /proc 文件系统——细粒度真相
墨:很多前面提到的计数器都在 /proc:
/proc/vmstat:allocstall(direct reclaim 卡)、compact_stall(compaction 卡)、pgpgin/pgpgout、nr_mapped等(第 18 章铁证来源)。/proc/<pid>/status:VmRSS(常驻)、VmSwap(换出多少)、State(R/S/D)、Threads。/proc/<pid>/smaps:该进程各内存区域占用,定位泄漏具体在哪段(第 22 章真泄漏定位)。/proc/<pid>/oom_score和oom_score_adj:谁容易死(第 19 章)。/proc/net/softnet_stat:每 CPU 软中断丢包/挤压(第 09 章三列含义)。/proc/pressure/:PSI 压力失速(第 22 章)。
五、网络专项(tcpdump/ethtool/nstat)
墨:第 09/10/21 章相关。
tcpdump -i eth0 -n 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' # 抓握手
tcpdump -i eth0 'icmp' # 看 PMTU 的 ICMP(第21章黑洞)
ethtool -S eth0 | grep -i drop/miss # 网卡层丢包(RX drop/MAC error)
ethtool -L eth0 combined 16 # 多队列(第09章根因修复)
ethtool -k eth0 # 看 offload(TSO/GRO/LRO)
nstat -az TcpRetransSegs TcpInSegs TcpOutSegs # TCP 重传计数(第21章)nstat是ss的计数器版,看累计/速率的协议栈统计(TCP 重传、ECN、失败连接),比ss瞬时更适合作趋势。ethtool -S的rx_drop/rx_missed是网卡硬件层丢包,若非零说明 ring buffer 满或 DMA 跟不上(第 09 章)。
六、症状→工具 对照总表(速查)
墨:这张表是你”下次真故障”的第一反应。打印贴墙。
| 症状 | 第一把扳手 | 进阶武器 | 对应章节 |
|---|---|---|---|
| 全机同时卡几秒,CPU 不高 | vmstat 1(看 si/so,b) | bpftrace(allocstall/compact) | 18 |
free 低告警,怕泄漏 | free -h(看 available) + drop_caches 验证 | PSI /proc/pressure, smaps | 22 |
| OOM 连环杀 | dmesg | grep oom(看前缀) | oom_score_adj, cgroup | 19 |
磁盘毛刺,%util 低 | iostat -x 1(aqu-sz/await) | biolatency(bcc) | 20 |
| 网络建连失败(EADDRNOTAVAIL) | ss -tan(TIME_WAIT 数) | nstat, tcp_tw_reuse | 21 |
| 重传率高,ping 正常 | ping -s 1472(大包) | ss -tin(retrans), tcp_mtu_probing | 21 |
| NUMA/大页卡顿 | numastat | perf(cache-miss/remote) | 23 |
| CPU 高/不知热点 | perf top | 火焰图, off-CPU | 05/25 |
| 内核某路径卡 | ftrace | bpftrace kprobe | 06/07 |
| 中断/软中断不均 | cat /proc/net/softnet_stat | softirqs(bcc) | 09 |
| 调度饥饿/节流 | vmstat(r 列) | perf sched, cgroup cpu.stat | 05 |
七、🔧 思考题(都配参考答案)
思考题 1(基础): 你接手一台”偶发卡顿”的机器,但故障已经过去、你不在场。请列出你会第一优先调出哪两个工具的历史数据来”回放”现场,并说明各看什么。
【参考答案】
第一优先两个(都是”历史记录仪”,能回放你不在场的现场):
sar:它常驻每 10 分钟采样存/var/log/sa/。回放看:sar -r(内存 available、kbmemfree)、sar -B/sar -W(页换入换出,swap 颠簸)、sar -b/sar -d(块设备 tps/await,磁盘排队)、sar -n TCP,ETCP(TCP 重传/建连)、sar -q(运行队列/loadavg)。这些能还原”故障前那段时间内核在忙什么”。dmesg -T/journalctl -k:内核日志,OOM kill(Out of memory: Killed)、softlockup(stuck for Ns)、block 层报错全在这。配合时间戳对齐。 补充:/proc/vmstat的累计计数器(allocstall/compact_stall增量)若被采集过也能指方向。核心:故障过去了就靠”历史采集 + 内核日志”两条线回放,而不是凭印象。
思考题 2(深入): vmstat 的 b 列(不可中断睡眠进程数)飙升,通常指向哪几类内核动作?结合前面章节各举一例。
【参考答案】
b 列 = 处于 D 状态(不可中断睡眠)的进程数,它们卡在”等内核完成某件不能打断的事”。飙升通常指向:
- direct reclaim(同步内存回收):内存跌破 low 水位,分配线程被逼亲自回收,卡住(第 06/18 章)。
/proc/vmstat的allocstall同步涨。 - THP 同步 compaction:申请大页碎片多,内核停下进行碎片整理(第 18/23 章)。
compact_stall同步涨。 - 等块设备 I/O(尤其sync写/读元数据):
await高、waCPU 高(第 07/08/20 章)。iostat看aqu-sz。 - 某些锁/信号量不可中断等待、NFS 等网络文件系统卡住。
所以
b飙升 + 对应计数器,能直接区分”卡在回收/卡在 compaction/卡在盘”。这是”内核暂停类”故障的核心观测列。
思考题 3(权衡): ftrace 和 bpftrace(eBPF)都能动态跟踪内核,什么场景用哪个?各有什么代价/前提?
【参考答案】
ftrace:
- 前提:内核自带 debugfs(
/sys/kernel/debug/tracing),无需额外安装 bcc/LLVM。 - 代价:功能相对受限(函数级 tracepoint/function 跟踪为主),有运行时开销,生产需低峰用,且输出是原始 trace 需自己解析。
- 适用:快速确认”某内核函数/事件是否被频繁调”(如 writeback、sched_switch),环境受限(不能装 bcc)时首选。
bpftrace(eBPF):
- 前提:内核支持 eBPF(3.15+ 渐全)、需装
bpftrace/bcc+ LLVM。较新内核最佳。 - 代价:初次安装麻烦,但运行时开销可控、几乎零侵入,能写任意聚合(
@[comm]=sum(bytes))、挂 kprobe/uprobe/tracepoint。 - 适用:现代首选,尤其”我想问一个任意的、动态的统计问题”(谁分配内存最多、IO 延迟直方图、softirq 分布)。
一句话:环境能装 bcc 就用 bpftrace(强且安全);不能装、只问内核函数级就用 ftrace。两者都不改代码、不重启,比
printk调试强万倍。
思考题 4(进阶总账): 综合工具箱,设计一套”新接手一台频繁 P99 毛刺的服务器”的标准排查 SOP(按工具出现顺序,从零依赖到进阶),并说明每步能排除/确认什么。
【参考答案】
SOP(从零依赖到进阶):
vmstat 1+iostat -x 1常驻落盘(零依赖):排除/确认 内存(si/so、b)、CPU(r、wa)、磁盘(aqu-sz/await)。→ 锁定”卡在内存回收 / 盘排队 / 还是 CPU”。sar回放历史 +dmesg看内核日志:确认有无 OOM(看前缀)、lockup、历史 swap/IO 趋势。→ 排除硬件级灾难。ss -tan/ss -tin+nstat:排除/确认 网络 TIME_WAIT 堆积、重传率(第 21 章)。→ 锁定是否网络层。numastat:排除/确认 NUMA 跨 node 访问(第 23 章)。→ 锁定是否 NUMA。perf top/ 火焰图:若 CPU 高,找热点函数;若 CPU 不高但卡,做 off-CPU 火焰图找”等什么”(锁/IO/回收)。→ 锁定应用 or 内核等待。bpftrace/ftrace:前几步指向”某内核路径(cardirect reclaim/compaction/writeback)“后,动态插桩量化(allocstall/compact_stall 来源)。→ 最终定位内核机制。/proc/<pid>/smaps+ 应用pprof:若怀疑泄漏/应用层,定位具体内存区/函数。 顺序原则:先零依赖四把刀(1-4)收窄层级,再进阶武器(5-7)定点。绝不第一反应就bpftrace乱挂——先分层,再深挖。
八、实战:用工具箱把一次”玄学卡顿”讲到根上
墨:光列工具是纸上谈兵。我拿第 18 章那个”全机同时卡几秒”的现场,完整演示怎么用本章工具箱一步步把它逼到真凶,让你看到”先零依赖四把刀、再进阶武器”的 SOP 是怎么落地的。
第 0 步:故障已发生,你不在场。
先回放:sar -q 1 60(看运行队列/loadavg 在历史尖峰时是否同步飙) + dmesg -T | grep -i "oom\|softlockup"(排除整机 OOM/锁死)。本案两者都”尖峰时无明显 OOM、无 lockup”——排除这两个,嫌疑收敛到”更隐蔽的暂停类”。
第 1 步:开 vmstat 1 落盘,对齐尖峰。
卡顿发生时 vmstat 抓到:si/so 同时跳动(即便 free 还有 3-4G),b 列偶尔飙到几十。→ 第一指向:swap 颠簸 / 同步回收(第 18 章第一兄弟)。/proc/vmstat 的 allocstall 在尖峰同步涨 → 坐实 direct reclaim。
第 2 步:缩小”是 swap 还是 compaction”。
/proc/vmstat 的 compact_stall 在”残余的超长卡顿”里飙 → 指向 THP 同步 compaction(第二兄弟)。cat /sys/kernel/mm/transparent_hugepage/defrag = always 坐实。
第 3 步:进阶武器定量。
bpftrace -e 'kprobe:try_to_free_pages { @[comm]=count(); }' 看谁在触发 direct reclaim(锁定那个周期性大分配的监控 agent);挂 compact_zone 看 compaction 耗时分布。→ 定量确认”agent 大分配”和”compaction 冻进程”两个力量。
第 4 步:止血 + 验证。
sysctl -w vm.swappiness=10 + 杀掉 agent 的周期性大分配;THP defrag 改 madvise。改完继续 vmstat 1 观察一周,si/so 归零、compact_stall 不再涨 → 闭环。
墨:你品这个顺序——没有一上来就 bpftrace 乱挂(那是新手最容易犯的”武器炫技”),而是先用 sar/dmesg/vmstat 这三把零依赖刀把”嫌疑范围”从”全宇宙”收窄到”swap/compaction 两类”,再用进阶武器定量。这正是第 25 章开头说的”先四把刀,后核武器”。这套 SOP,你以后对任何玄学卡顿都能照抄。
实战补遗一:用 bpftrace 把”到底卡在内核哪一层”钉死
墨:第 25 章第八节的实战,我们”先用零依赖三把刀(vmstat/sar/dmesg)收窄,再用 bpftrace 定量”。这一节把那最后一步的 bpftrace 怎么写、怎么读讲透——因为很多人卡在”知道该用 eBPF,但不会下嘴”。
25.x.1 先备好工具
bpftrace(基于 eBPF 的内核追踪,现代内核自带 eBPF 框架,无需重编内核);- 或
bcc工具集(/usr/share/bcc/tools/下一堆现成脚本如biolatency、execsnoop、opensnoop)。 - 注意:生产机需内核 ≥ 4.x 且开启
CONFIG_BPF;容器里要--privileged或对应能力。
25.x.2 第一招:块层延迟分布(biolatency)
当怀疑”卡在磁盘 IO”(第 08、20 章),别只看 iostat %util(第 20 章讲过 %util 低也可能是瓶颈)。直接:
bpftrace -e 'kprobe:blk_account_io_done { @us = hist(nsecs / 1000); }'它按每次块 IO 完成的实际微秒数画直方图。如果你看到大量 >50ms 的长尾,但 iostat %util 才 30%——正是第 20 章说的”队列/await 才真相,%util 骗你”。这一步把”怀疑 IO”变成”看见 IO 长尾分布”。
25.x.3 第二招:追踪某个 syscall 的耗时
怀疑”卡在 write”(回到第 11 章第五跳),追踪所有 vfs_write 的耗时:
bpftrace -e 'kprobe:vfs_write { @start[tid] = nsecs; }
kretprobe:vfs_write /@start[tid]/ {
@w = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'如果直方图里出现大量 >100ms 的 vfs_write,而你的应用只是写一行日志——立刻指向 vm.dirty_ratio 前台 sync 雷区(第 11/12 章第五跳)。这一步把”卡在 write”钉死到内核函数。
25.x.4 真实案例:bpftrace 把”网络卡”翻案成”文件系统卡”
📦 案例:某服务"接口偶发 2 秒超时",所有人都认为是网络(第 21 章方向)。 用 biolatency 一跑——块层没问题,<100μs 全清。换 vfs_write 追踪——出现每几分钟一次的 1~2 秒 vfs_write 长尾。再看 vm.dirty_ratio=20(默认),日志突发写时前台 sync 卡住。真相:根本不是网络,是文件系统写被脏页触顶冻住(第 11 章第五跳雷区 A)。调 dirty_ratio=10 + dirty_background_ratio=5 后长尾消失。bpftrace 用 5 分钟把”全网工觉得是网络”翻了案。
25.x.5 sar 历史回溯的标准姿势
第八节讲了 sar 实时。补充历史:sar 默认把数据按天存 /var/log/sa/saXX(XX=日期)。回放昨天 14:00~15:00 的内存:
sar -r -f /var/log/sa/sa28 | sed -n '/14:00/,/15:00/p' # 内存
sar -B -f /var/log/sa/sa28 # 页回收
sar -W -f /var/log/sa/sa28 # swap
sar -n DEV -f /var/log/sa/sa28 # 网卡墨:历史 sar 是事后复盘的神器——故障发生在凌晨 3 点,你早上 9 点才看,实时工具早翻过去了,但 sar 文件还在。结合 bpftrace 的实时定量 + sar 的历史回溯,你的排障就”实时有刀、事后有账”。
25.x.6 一张”哪类故障掏哪把 eBPF 刀”速查
| 你怀疑卡在 | 掏哪把刀 | 看什么 |
|---|---|---|
| 磁盘 IO 长尾 | biolatency / biosnoop | IO 完成延迟直方图 / 每次 IO 详情 |
| 某个 syscall 慢 | vfs_read/vfs_write/syscalls 追踪 | 该调用耗时直方图 |
| 进程频繁创建 | execsnoop | 谁在疯狂 fork/exec |
| 文件被谁打开 | opensnoop | 实时 open 调用 |
| 调度延迟 | runqlat | 任务等 CPU 的排队延迟 |
| 网络重传 | tcpretrans / tcpconnect | 重传 / 连接建立 |
墨:工具箱的终极心法——零依赖三把刀(vmstat/sar/dmesg)负责”收窄到子系统”,bpftrace/bcc 负责”钉死到内核函数”。前者人人都会,后者才让你从”二级(懂机制)“开始摸”三级(能造工具)“:你甚至可以自己写 bpftrace 一行脚本,观测你服务特有的一跳。这,正是第 26 章说的”逃生口”。
实战补遗二:把零依赖刀收窄后,用 perf 火焰图一锤定音
墨:第 25 章你见了”vmstat/sar/dmesg 三把刀先收窄,再 bpftrace 定量”。但有些卡顿是CPU 上的,不是 IO、不是内存——这时候三把刀不够,得上 perf。
你:perf 火焰图我听过,但总觉得”要专门编译内核、要 debuginfo”,生产不敢动。
墨:那是老黄历。perf record + 火焰图(FlameGraph)在绝大多数发行版自带,不用 debuginfo 也能看用户态+内核态的函数栈热点(符号靠二进制自带的符号表,Go 二进制默认带)。我给你一套”卡在 CPU 上”的 SOP。
现象
某 Go 服务一个核 CPU 100%,RT 涨,但 vmstat(内存)、iostat(IO)、nstat(网络)都正常——三把刀指不到方向,说明卡在 CPU 执行本身。
SOP(卡在 CPU 上的定位)
top -H看是哪个线程(LWP)打满,记下 tid。perf top -p <pid>实时看热点函数——若看到runtime.mallocgc/runtime.findrunnable占高,是 GC/调度;若是业务函数,是算法。- 抓火焰图:
perf record -F 99 -p <pid> -g -- sleep 30(99Hz 采样 30s),perf script | stackcollapse-perf.pl | flamegraph.pl > cpu.svg。打开 svg,横向最宽的栈就是最热的路径。 - 本例火焰图最宽的是
json.Unmarshal→reflect.Value调用,占 60% CPU。真相:高频接口在热路径里json.Unmarshal大结构体,反射开销炸。
调整
换成 jsoniter/代码生成(easyjson)或预解析;或把大结构体拆小、只解需要字段。改完 CPU 从单核 100% 降到 30%,RT 回正常。
再观察
换 easyjson 代码生成后,火焰图里 json 相关栈从 60% 掉到 8%,RT P99 从 800ms 回 30ms。
改进(权衡:拿什么换什么)
| 动作 | 拿什么 | 换什么 |
|---|---|---|
| 换解析库/代码生成 | 改代码、加生成步骤 | CPU 降 3 倍 |
perf 采样定位 | 一条命令的轻微开销 | 精准到函数,不盲猜 |
🔧 思考题:perf 采样 99Hz 是什么意思?为什么不用 9999Hz?
【参考答案】
-F 99 是每秒采样 99 次调用栈,足够统计热点又不显著干扰生产(采样本身有开销,频率越高越影响被采进程、且高频率下精度收益递减)。9999Hz(近 10K)会让 perf 自身吃掉可观 CPU、甚至改变程序行为(海森堡效应),而火焰图是看”比例”不是”精确耗时”,99Hz 足矣。生产抓火焰图就 99Hz + 几十秒,安全又准——这也是第 25 章”先零依赖收窄、再上重武器定量”的体现:perf 是 CPU 路径的定量重武器。
实战补遗三:sar 的隐藏字段——90% 人只看 %util,漏掉真正报警的那些
墨:你查 IO 瓶颈,sar -d 只看 %util,一看没到 100% 就说”磁盘没满”。我说:你漏了三个更狠的字段。
你:%util 不就是磁盘忙不忙吗?没满还不够?
墨:%util 是”设备有 IO 在处理的时间占比”,它掩盖了队列深度和效率。100% 不一定瓶颈,50% 也可能已经排队排到天上去(尤其多队列/NVMe)。真正报警的是 aqu-sz、await、以及 sar -r 的 kbcommit 这几个。
三个被漏的字段
- **
aqu-sz(平均队列深度):**等待处理的 IO 请求数。它涨 = 请求在排队,即使%util才 60%,aqu-sz=32说明已经排长队,延迟必升。第 8/20 章讲过:%util低 ≠ 没瓶颈,aqu-sz+await才是排队真相。 - **
await(平均 IO 延迟 ms):**从发请求到完成。它涨 = 用户感知的慢,不管%util。await从 1ms 到 80ms 就是”盘在排队/在抖”。 - **
kbcommit/commit%(sar -r):**已提交的内存承诺占CommitLimit的比例。它比free更早预警 overcommit 水位危险——commit%接近 100% 时,OOM 风险已在门口,而free可能还看着有。第 4/14 章的 overcommit 会计,这里能直接看到水位。 另:sar -w的cswch/nvcswch(自愿/非自愿上下文切换)涨 = 锁竞争或调度抖动(第 5 章);sar -n TCP的retrans(重传段)涨 = 网络在丢(第 15/21 章)。
现象:只看 %util 漏掉的 NVMe 瓶颈
sar -d 看 NVMe %util 才 40%,运维说”盘闲着呢”。但 aqu-sz 常驻 25、await 90ms——实际是队列深、延迟高,某调度器/队列参数错(第 20 章)导致请求堆。应用侧写延迟已经 100ms+,和 %util 完全对不上。
调整:看组合,不盯单字段
- IO 瓶颈判据:
aqu-sz高 或await高 → 瓶颈,不管%util; - 内存承诺:
kbcommit/commit%接近CommitLimit→ overcommit 水位危险,早于 OOM; - 调度:
cswch暴涨 → 锁/抢占问题,看perf而非调vm; - 把这几个字段纳入仪表盘,别只看
%util/free。
再观察
仪表盘加 aqu-sz+await 后,NVMe 排队问题当天被发现(对应第 20 章调度器错),改 none 后 aqu-sz 回落、await 回 40us。
改进(权衡:看更多字段拿什么换什么)
| 动作 | 拿什么 | 换什么 |
|---|---|---|
监控 aqu-sz/await | 多几个图 | 早发现有队列的瓶颈 |
监控 kbcommit | 多指标 | overcommit 早预警 |
🔧 思考题:%util 100% 一定代表瓶颈吗?什么情况下 100% 是正常的?
【参考答案】
不一定。旧式单队列 HDD,%util=100% 基本=盘满、排队,是瓶颈。但 NVMe/多队列设备,%util 是”所有硬件队列忙的时间聚合”,100% 可能只是并行度高(多个队列同时有 IO),此时 aqu-sz 若低、await 正常,说明吞吐拉满但无排队,是健康满负荷,不是瓶颈。反之 %util 才 40% 但 aqu-sz 高、await 高,才是真瓶颈(排队但设备没真忙,被调度/队列参数卡)。所以结论:%util 单看无意义,必须配 aqu-sz+await 一起读——这正是第 25 章”工具箱”的核心:工具给的是字段,判瓶颈的是你的组合解读,别被单一指标骗了。
实战补遗四:offcputime(bpftrace)—定位”卡在等锁/等 IO”的离线时间
墨:第 25 章你用 perf 火焰图看”on-CPU 热点”,找到 CPU 上的瓶颈。但有个反直觉的:某线程 CPU 使用率才 5%,处理却慢。火焰图一片空白——因为它根本不在 CPU 上跑。我说:去看它的”离线时间”。
你:不在 CPU 上,那它在干嘛?睡觉?
墨:它在 off-CPU:等锁(futex)、等 IO(read 阻塞)、等网络(recv 阻塞)。这些时间不烧 CPU,所以 perf 火焰图(on-CPU)看不到,但用户感知的”慢”恰恰包含它们。定位 off-CPU 要用 offcputime(bpftrace 工具),看”线程把时间花在等什么上”。
现象:线程 CPU 5%,但处理慢
某请求线程 top 看 CPU 才 5%,但单个请求处理 200ms。perf top/火焰图on-CPU 看不到热点(它基本不烧 CPU)。运维懵:不烧 CPU 为啥慢?
推断(可能推错):先以为”是网络/外部慢”
第一推断:「下游服务慢,线程在等响应。」——但下游 P99 才 10ms,不是 200ms 的来源。推错,因为没量化”本线程的等待分布”。
真相:慢在 off-CPU,不在 on-CPU
用 bpftrace 的 offcputime(或 perf sched)跟踪该线程的 off-CPU 栈:
bpftrace -e 'tracepoint:sched:sched_switch { ... }' # 或现成工具 offcputime.bt
结果显示:200ms 里 180ms 花在 futex_wait(等一把全局锁)——某热点路径持锁久,本线程在锁上排队。这 180ms 不烧 CPU,所以 on-CPU 火焰图空白,但用户感知的延迟实打实。
offcputime 输出按”off-CPU 栈”聚合,最宽的就是”线程在等什么”:futex_wait(锁)、io_schedule(IO)、sk_wait_data(网络)。它对 on-CPU 火焰图是互补的——一个看”烧 CPU 时卡哪”,一个看”不烧 CPU 时等哪”。
调整:off-CPU 定位 → 治本
offcputime.bt -p <pid>抓该线程离线栈,看最宽的是锁/IO/网络;- 锁等待 → 缩小锁粒度/
sync.Map/分片(第 24 章量化先行案例); - IO 等待 → 看第 8/20 章块层;
- 网络等待 → 看第 15/21 章;
- 验证:改后
offcputime离线栈变窄,请求延迟降。
再观察
offcputime 显示 90% 离线时间在 futex_wait,定位到全局配置锁;改分片锁后,离线时间从 180ms 降到 5ms,请求 P99 回 15ms(CPU 仍 5%,但不再等)。
改进(权衡:看 off-CPU 拿什么换什么)
| 动作 | 拿什么 | 换什么 |
|---|---|---|
offcputime 定位 | 一条 bpftrace | 看清”等什么” |
| on-CPU+off-CPU 互补 | 两个视角 | 延迟全貌 |
🔧 思考题:为什么”CPU 使用率低但慢”的问题,on-CPU 火焰图看不出,必须 off-CPU?
【参考答案】
因为”慢”= 墙钟时间,而墙钟时间 = on-CPU 时间 + off-CPU 时间(等待)。CPU 使用率 5% 说明 95% 的时间线程在 off-CPU(等锁/IO/网络),根本没在跑指令,自然不在 on-CPU 火焰图里(on-CPU 只记录”正在执行的指令栈”)。只看 on-CPU 火焰图,你会以为”这线程啥也没干、不慢”——但它 95% 时间在等,用户感知的慢全在 off-CPU。所以排障铁律:慢但 CPU 低 → 看 off-CPU(offcputime/perf sched);慢且 CPU 高 → 看 on-CPU(perf 火焰图)。两者互补才是延迟全貌,这也是第 25 章工具箱的核心:工具箱里没有”万能图”,只有”按症状选对的那张图”,on-CPU 和 off-CPU 是一对必须一起用的眼睛——你不会只用一只眼找东西,排障也别只盯 on-CPU。和第 25 章开头那句”先零依赖三把刀收窄,再上重武器定量”一致:off-CPU 是”定量”阶段的第二把重武器。
实战补遗五:bpftrace 一行脚本——不重启、不插桩看内核
墨:前面讲了 perf 火焰图(看 CPU 烧在哪)和 offcputime(看卡在哪等)。但有些问题,你要的不是”哪个函数慢”,而是”某个内核事件发生了多少次、花多久”。这时候 bpftrace(eBPF 的用户态前端)比 perf 更锋利——它能动态挂在内核任意函数/跟踪点上,零重启、近乎零开销。
你:bpftrace 听起来很重,要写内核代码吗?
墨:不用写 C,它的 DSL 一行就能干活。讲三个我常用的”救命一行”:
1. 看某个系统调用到底慢在哪——比如怀疑 read() 慢:
bpftrace -e 'tracepoint:syscalls:sys_exit_read { @us = hist(args->ret > 0 ? (nsecs - @start[tid]) / 1000 : 0); } tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }'这条挂在内核 read 的进入/退出跟踪点,按线程记耗时,输出直方图。hist() 直接给你”多少 read 落在 1µs/10µs/1ms/… 区间”——一眼看出是普遍慢还是长尾慢。
2. 谁在疯狂分配内存(kmalloc 跟踪):
bpftrace -e 'kprobe:kmalloc { @bytes[comm] = sum(args->size); }'按进程名(comm)汇总内核 kmalloc 总字节数。第 22 章讲的 SUnreclaim 暴涨,用这条能直接锁定”哪个进程的 skbuff/内核对象在狂涨”,比 slabtop 更精准(还能按 PID、按调用栈)。
3. 哪个进程在触发 page fault 风暴(接第 3 章 TLB 那个):
bpftrace -e 'software:page-fault:1 { @[comm] = count(); }'按进程名数缺页次数。Go 服务若 page-fault 暴涨且 P99 卡,结合第 13 章,基本锁定 THP 合并或缺页来源。
真实事故:一个服务偶发延迟尖刺,perf 火焰图看业务 CPU 正常,offcputime 看卡在 futex(锁)。但哪个锁?用 bpftrace 挂 tracepoint:syscalls:sys_enter_futex,按用户态调用栈(@us[ustack]=count())聚合,发现 90% 的 futex 等待来自一个第三方库的全局配置锁——读多写少却用了互斥锁。换成 sync.RWMutex(Go 侧)或去掉不必要的全局锁后,尖刺消失。
推断验证:bpftrace 的 ustack(用户态栈)直接把”内核视角的等待”映射回”你的哪行 Go 代码”,把第 25 章的”工具箱”接到了你写的业务上——这是 perf 火焰图做不到的”精确到调用栈的等待归因”。
你:bpftrace 这么强,是不是能替代 perf 和 sar?
墨:不能,它们分工不同:
sar:历史趋势(sar -B/sar -n DEV/sar -q),适合”昨天这时候是不是也这样”,零开销常驻;perf:CPU 热点 + 火焰图,适合”现在 CPU 烧哪了”,on-CPU 分析之王;offcputime(bpftrace 脚本):off-CPU 等待,适合”卡在等锁/等 IO/等网络”;bpftrace任意探针:定向验证(某个 syscall 耗时、某内核函数调用次数、某事件直方图),适合”我怀疑 X,用一条命令证伪/证实”。
工具箱不是越多越好,是每个工具对应一类问题。排查的节奏永远是:先用 sar/vmstat/ss 定位”是哪一类问题”(CPU?IO?网络?内存?),再选 perf/offcputime/bpftrace 深挖。新手常犯的错:一上来就 perf record 抓半小时火焰图,结果问题根本不在 CPU。
🔧 思考题:bpftrace 挂 kprobe 有风险吗?生产机器上能随便挂吗?
【参考答案】
风险极低但非零。eBPF(4.x+ 内核)的 kprobe/tracepoint 是沙箱化的:你写的脚本经过 verifier 校验,不能越权访问内存、不能死循环、不能崩溃内核,挂上去的开销通常在 1% 以内(远小于 perf record -F 99 的采样开销)。所以生产上”挂一下看几分钟”是安全的,很多大厂 SRE 把 bpftrace 当生产排障标配。
但有几点注意:① 老内核(3.x)没有 eBPF,只能靠 systemtap(需装内核调试符号、风险更高);② 某些极热的跟踪点(如每个网络包都触发的 tcp_probe)在高 PPS 机器上频繁触发会有可观测开销,挂前想清楚采样率;③ kprobe 挂的函数名随内核版本变,跨版本可能”符号找不到”,用 tracepoint(稳定 ABI)优先于 kprobe(不稳定);④ 挂完记得 Ctrl-C 卸载,别留着。口诀:eBPF 是现代排障最锋利的刀,磨快了放心用,但别拿去当常驻监控(那是 sar/Prometheus 的活)。
实战补遗六:biolatency / biosnoop——把块层延迟分布”看见”
墨:前面讲了 iostat(看盘的整体 util/await)和 bpftrace(任意内核探针)。但磁盘慢还有一种更细的视角:每次 IO 的延迟分布——平均 await 正常,但长尾(99% 分位)可能几百毫秒,iostat 看不出来。这就是 biolatency 的用武之地。
你:iostat 的 await 不就是平均延迟吗?为什么还要分布?
墨:平均会掩盖长尾。举个真实:一块盘 iostat 看 await=2ms(平均很快),但业务偶尔卡 300ms。平均 2ms 让你以为盘很健康,实际是”99% 的 IO 是 1ms,1% 的 IO 是 300ms”——平均被大多数快 IO 拉低了,但业务卡的那 1% 才是事故。这就像”平均薪资”掩盖贫富差距。
biolatency(bpftrace/perf-tools 自带)做的事:挂载内核块层 IO 完成事件,按延迟分桶统计直方图——你能直接看到”多少 IO 落在 1ms 内、多少落在 10ms、多少落在 100ms+”。
真实事故:一个数据库偶尔慢查询,iostat await 正常,vmstat 没 IO 等待。挂 biolatency 跑 1 分钟:
usecs : count
1 -> 2 : 12033
2 -> 4 : 8821
...
1000 -> 2000 : 44
2000 -> 4000 : 12 <-- 长尾!
4000 -> 8000 : 3
发现 99% 的 IO <2ms,但有几十条 >2ms、最长到 8ms(8000µs)。再挂 biosnoop(追踪每次 IO 的发起进程 + 延迟 + 扇区):定位到这些长尾 IO 全来自一个后台 updatedb(mlocate)定时任务在扫全盘,它的大文件顺序读和数据库的随机读抢同一块盘的 IO 带宽,偶尔把数据库的某次读挤到 8ms。
推断:平均 await 掩盖了”后台任务周期性抢盘”导致的长尾。验证:biolatency 看分布 + biosnoop 看长尾 IO 的 PID/扇区,锁定 updatedb。调整:把 updatedb 用 ionice -c 3(idle 级 IO 调度)降权、或 cgroup io.max 限制它盘的带宽(第 8、17 章),让它只在盘空闲时跑。再观察:biolatency 长尾消失(>2ms 的 IO 从几十条降到 0),数据库慢查询不再出现。
你:biolatency 和 blktrace(另一个块层追踪工具)啥区别?什么时候用哪个?
墨:层级不同:
biolatency/biosnoop(基于 eBPF):轻量、实时、看分布和归因,开销极小,适合生产排障”盘慢不慢、慢在哪、谁干的”;blktrace+blkparse:精细到每一次 IO 的完整生命周期(排队→派发→驱动→完成,各阶段耗时),信息极全但开销大、输出海量,适合”我要精确分析调度器/驱动行为”的深度调优,不在生产常驻。
排障节奏:先用 biolatency 确认”有长尾、分布怎样”、用 biosnoop 定位”谁、哪个扇区、多久”,这俩已经能解决 90% 的”盘慢”问题;只有当你要深挖”为什么这次 IO 在队列里等了那么久(是调度器?驱动?还是硬件?)“,才上 blktrace 看每个阶段的耗时分解。
这把第 25 章的工具箱串成一条 IO 排障链:iostat(盘整体健康)→ biolatency(延迟分布/长尾)→ biosnoop(长尾归因到进程)→ blktrace(深度阶段分解)。再配合 bpftrace 任意探针(第 25 章)和 perf/offcputime,从”盘慢”到”哪个进程、哪个系统调用、为什么等”全链路打通。口诀:平均掩盖长尾,分布暴露真相;先 eBPF 看分布归因,再 blktrace 深挖阶段。
🔧 思考题:biolatency 显示长尾,但你确认不是后台任务抢盘,而是”盘自己偶尔慢”(如 SSD GC、或云盘突发限速),怎么区分?
【参考答案】
区分”被别人抢”还是”盘自己慢”,看两点:
-
biosnoop里长尾 IO 的 PID:如果长尾 IO 来自多个不同进程、且盘iostat的aqu-sz(平均队列深度)同时高 → 是”大家抢盘、队列堆积”,属于竞争,用ionice/cgroupio.max限干扰项;如果长尾 IO 全是数据库自己的(没有其他进程同时写),且aqu-sz不高 → 盘在”自己忙”(不是被抢)。 -
aqu-szvsawait的关系:aqu-sz高 +await高 = 请求在队列里等(竞争/带宽满);aqu-sz低 +await偶发高 = 单笔 IO 本身慢(盘内部 GC、云盘突发信用耗尽、或 SSD 写放大触发块擦除)。 -
云盘特有:看是否”突增至基线”(云盘有突发积分,平时高 IOPS、耗尽后掉到基线低 IOPS)。
biolatency长尾的出现是否有”周期性”(积分耗尽时)——是则调业务错峰或买更高基线。
所以区分法:biosnoop 看长尾 IO 归属 + aqu-sz/await 组合判断。被抢→限干扰项;盘自己慢→换盘/调应用 IO 模式(GC 友好)/云盘升配。口诀:长尾不一定是别人抢,但 biosnoop 一行就能告诉你”这慢 IO 是谁发的”——归因永远先于定性。
实战补遗七:eBPF 入门——写第一个 kprobe 抓函数耗时,比 perf 更精准
墨:老哥,你用 perf 抓过内核函数耗时吗?但 perf 抓的是”采样”,会有遗漏吧?
你:perf 是采样,热点函数才抓得准,偶尔的慢函数可能漏掉。
墨:对,采样有概率性。要”精确抓某函数每次调用的耗时分布”,得用 eBPF——它能在内核里挂探针、精确统计,不丢样本。这节给你 eBPF 的入门视角,作为第 25 章工具箱的”进阶武器”。
现象:一个服务偶发某次 vfs_write 特别慢(几百 ms),但 perf record 采样没抓到(它只占 0.1% 采样窗口,被平均掉)。你想”精确看每次 vfs_write 的耗时分布”,而不是采样估计。
推断:perf 采样频率调高(-F 999)也许能抓到。
可能推错:调高采样频率能改善,但采样终归是”概率命中”,且频率越高开销越大、还会干扰生产(-F 999 本身占 CPU)。偶发的、占比极低的慢调用,采样法天生漏——你永远在”覆盖率”和”开销”间纠结。eBPF 走另一条路:在内核函数入口/出口挂探针,每次调用都精确记录耗时,零采样、不遗漏,且支持在生产安全跑(有校验器保证不崩内核)。
调整:用 bpftrace(eBPF 的用户态前端,语法像 awk)写一个 kprobe 抓 vfs_write 耗时:
bpftrace -e '
kprobe:vfs_write { @start[tid]=nsecs; }
kretprobe:vfs_write {
$d=ns(nsecs - @start[tid]); delete(@start[tid]);
@latency=hist($d);
}'
这会输出每次 vfs_write 的耗时直方图(@latency 是 log2 分布),你能直接看到”有没有几次几百 ms 的慢写”、占比多少——这是 perf 采样给不了的”精确分布”。
再观察:eBPF 还能做 perf 做不到的:
- 精确追踪某 PID/某文件/某 syscall 的每一次事件(filters)。
- 内核态任意函数/跟踪点(tracepoint)/USDT 挂探针,不限于采样。
- 生产安全:eBPF 程序过内核校验器(verifier),保证不会死循环/越界,挂上不崩内核;perf 某些用法(如
-g栈回溯)在生产也有开销但可控。 - 经典工具集
bcc/bpftrace:如execsnoop(谁在起进程,抓 fork 炸弹)、opensnoop(谁在开文件)、biosnoop(每次块 IO 耗时)、offcputime(off-CPU 火焰图,见第 1 章火焰图实战补遗)、runqlat(调度延迟分布)。
改进:
- 偶发慢调用/要精确分布:用 eBPF(bpftrace/bcc)而非 perf 采样。
- 热点占比高、要快速看”烧 CPU 在哪”:perf 火焰图更快上手,采样够用。
- eBPF 入门从
bpftrace单行脚本开始(上面那个 vfs_write 例子),再学bcc写 Python 版复杂工具。 - 生产挂探针先小范围、短时长,看
bpftool/dmesg有无校验错误。
🔧 思考题:eBPF 为什么”不采样也不漏、且能在生产安全跑”?内核校验器(verifier)到底在防什么,让你敢往生产内核挂自定义程序?
【参考答案】
为什么不采样也不漏:eBPF 是”事件驱动的内核虚拟机”——你在某个内核函数(kprobe)、跟踪点(tracepoint)或用户态函数(uprobe)上挂一段小程序,每当该事件真实发生一次,这段程序就被内核执行一次,当场记录(耗时、参数、计数)。它不像 perf 那样”每隔 N 微秒中断一次、看此刻在跑什么”(采样,必然有概率漏掉短促/低频事件)。eBPF 是”每次都到、到了就记”,所以零遗漏、且能给出精确的分布直方图(每个 bucket 的准确计数,而非采样估计的百分比)。
为什么能在生产安全跑:传统做法是写内核模块(LKM)来挂探针——但内核模块有完整权限,一个空指针/死循环能直接 panic 整个机器,绝不敢随便在生产加载。eBPF 不同:你写的 eBPF 程序在加载前,先过内核校验器(verifier):
- 无死循环:校验器做有界循环分析(或要求显式
loop上限),确保程序保证终止,不会卡死 CPU。 - 内存访问安全:所有指针解引用必须”可证明在安全范围内”(基于已验证的上下文/map 边界),不能越界读内核内存、不能解引用未校验的指针——防信息泄漏/崩溃。
- 无越权操作:eBPF 程序只能调用内核白名单里的 helper(如
bpf_probe_read、bpf_map_update),不能随便调任意内核函数、不能改任意内存。 - 寄存器/栈有界:栈大小受限(512B)、不能溢出。
通过 verifier 的程序才被 JIT 编译成本地指令执行(现代内核 eBPF 走 JIT,性能接近原生)。因为”跑之前已被证明安全”,所以挂 eBPF 探针不会像内核模块那样”一行 bug 全机 crash”——这就是它能在生产放心用的底气(对比 LKM 的”加载即冒险”)。
代价与边界:eBPF 不是万能——它不能任意改内核行为(只能观测/有限修改),复杂逻辑要用 map 在多次事件间传状态(verifier 保证 map 访问安全)。生产挂探针仍要注意:探针本身有微小开销(每次事件的执行成本 × 事件频率),高频事件(如每个网络包)挂重逻辑会累计开销,所以脚本要尽量轻(如只在出口算一次耗时、用 hist map 聚合)。bpftrace 的单行脚本适合快速排查,bcc 的 Python 封装适合写复杂可复用工具。dmesg 里若出现 bpf verifier 报错,就是你的脚本越界了(改写法即可),不会崩机——这正是 verifier 的价值。
延伸:第 25 章工具箱把 perf(采样、快上手、看热点)和 eBPF(精确、无遗漏、生产安全、能挂任意探针)定位成互补——热点定位用 perf,偶发/精确分布/任意函数追踪用 eBPF,off-CPU 火焰图(offcputime)就是 eBPF 的经典产物(见第 1 章)。
下一章预告
墨:第 25 章我们系统梳理了工具箱——vmstat/iostat/ss/sar 四把瑞士军刀必精通,perf/ftrace/bpftrace 是进阶核武器,外加 numastat/slabtop/dmesg//proc 和”症状→工具”速查总表。下次故障,你照表抡扳手就行。
最后一章(第 26 章)我们收尾:把全书 26 章串成一张”程序从源码到硬件”的认知地图,给你一张”遇事查哪章”的检索表,并聊聊”懂微观”这件事对你作为工程师的长期意义。这一章,是终点,也是你真正开始独立排障的起点。
本章完。结语见第 26 章。