这一章在做什么
这一章不查某一种资源,而是给后面各章打底:每个观测工具的数从哪儿来、要付多少代价、能不能在生产机上开。工具分计数器、剖析、跟踪、监控四类;数据源从 /proc、/sys、netlink,到跟踪点、kprobes、uprobes、USDT 和硬件计数器,一个个过。最该带走的是一把尺子:计数器几乎免费,定时采样的开销可以预估,跟踪的开销跟着事件率涨。最后讲怎么让 sar 留下历史数据。
什么时候翻这一章
- 板子上只有 busybox,没有 sysstat、perfmpstat、pidstat、vmstat 的数都是从 /proc 和 /sys 读出来再相减的,自己读也能算。§4.2.1§4.3.1§4.3.2场景手册 →
- 想在线上跟踪,又怕把服务拖慢先分清你要的是计数、采样还是逐个事件,再按事件率估开销。§4.2.3§4.3.5§4.3.7
- 某个工具或监控面板的数看不懂、或者怀疑它算错了找到它读的是哪个文件、哪个接口,再拿另一个来源交叉核对。§4.3.1§4.6
- 问题昨天下午出现过,现在没了sar 开着的话,历史数据就在
/var/log/sysstat里。§4.4.2 - 新机器上线前,想把排查工具备齐出事时再装往往来不及,内核配置和帧指针也要提前准备。§4.1.2
第 1 章 §1.7 先介绍过计数器、剖析、跟踪和静态、动态插桩,这一章把它们落到 Linux 的具体接口上。第 6–11 章按资源用这些工具;perf、Ftrace、BPF 三件跟踪器的细节在第 13–15 章。
先用中文过一遍
计数器:内核一直在数,工具只是读两次相减
内核里到处是只增不减的整数:收了多少包、发了多少次磁盘 IO、每个 CPU 在用户态待了多少个 tick。它们本来就在加,你不读也在加,所以开销只在读的那一下(§4.2.1)。常见的做法是成对维护:一个数事件次数,一个累计事件花的时间,两者一除就是平均延迟。
工具拿到的都是「从开机到现在」的累计值,单看没有意义。vmstat 1、mpstat 1 做的事就是:隔一秒读一次,相减,再除以间隔。这也解释了一个老现象:这类工具的第一行常常是开机以来的平均,和后面几行不是一回事(§4.2.1)。在只有 busybox 的板子上,你可以用 cat 和 awk 做同样的减法,下面的第一个实验就是这么做的。
精度要心里有数:默认配置下 CPU 时间按时钟中断的 tick 记账,一个 tick 可能是 4 毫秒,看分钟级的趋势够用,看几毫秒的短任务就粗了(§4.3.1)。内核也提供更细的记账方式,代价是一点额外开销。
采样和跟踪:开销跟着谁走
计数器只告诉你「多少」,说不出「哪段代码」「哪一次慢」。剖析(profiling)是定时拍快照:每秒在每个 CPU 上打断 99 次,记下当时的指令地址或调用栈。打断的频率是你定的,所以开销事先就能估出来,这是它在生产上相对安全的原因(§4.2.2)。
跟踪(tracing)是每个事件都处理:每次磁盘 IO、每次调度、每次系统调用都走一遍插桩代码,记下来或者在内核里汇总。好处是一个都不漏,代价是开销和事件率成正比,事件一突发,开销跟着涨,被跟踪的程序变慢,量出来的时间戳也可能被跟踪器自己拉长(§4.2.3)。书里给的经验值是:今天常见的 4 到 128 核机器上,每秒一万个事件以下几乎感觉不到,十万以上开始能量出来;磁盘事件多在前一档,调度事件常在后一档(§4.3.5)。
事件从哪来:静态和动态,内核和用户态
跟踪器本身不产生事件,它挂在内核提供的事件源上。分两个维度看就清楚了。静态插桩是写代码的人预先埋好的点:内核里叫跟踪点(tracepoint),用户程序里叫 USDT。它们有名字、有固定的参数,相对稳定,作者的说法是「尽力而为的稳定」(§4.3.5);不开的时候在 x86_64 上只是一条 5 字节的空指令。动态插桩是运行时把指令改掉、临时插进去:内核里叫 kprobes,几乎任何内核函数都能挂;用户态叫 uprobes,任何用户函数都能挂。好处是没有人预先埋点也能看,坏处是挂的是内部函数,换个内核版本或程序版本就可能改名、被内联、参数变了(§4.3.6)。
书里自己的类比是:USDT 之于 uprobes,正如跟踪点之于 kprobes(§4.3.8)。所以选的顺序也一样:有静态的点就用静态的,没有才用动态的兜底。用户态那一列还有一个额外的价钱:uprobes 靠陷入内核实现,每次的代价比内核里的探针高一个量级,USDT 在 Linux 上也是用 uprobes 实现的(§4.3.7、§4.3.8)。
还有两类源不在这四格里。一是硬件计数器(PMC):处理器里能编程的计数寄存器,只有它能告诉你每周期执行了几条指令、缓存命中率多少。它有两种用法:只计数,开销几乎为零;溢出采样,每 N 次事件打断一次记下现场,用来找是哪段代码(§4.3.9)。二是 ptrace:strace、gdb 用的老接口,每个系统调用都要把目标停下来、切到跟踪器、再切回去,书里说可能慢一百倍以上(§4.3.10)。
| 英文 | 中文 | 一句话 |
|---|---|---|
| counter | 计数器 | 内核一直在加的整数,默认就开着;读两次相减得到速率 §4.2.1 术语页 |
| metric | 指标 | 监控软件把计数器换算之后展示出来的数 §4.2.1 |
| profiling | 剖析 | 定时或按硬件事件采样,开销由采样频率决定 §4.2.2 术语页 |
| tracing | 跟踪 | 每个事件都处理,开销随事件率涨;日志可以看成低频的跟踪 §4.2.3 术语页 |
| static / dynamic instrumentation | 静态插桩 / 动态插桩 | 预先写进代码的探测点 / 运行时改指令插进去的探测点 §4.3.5 §4.3.6 术语页 |
| tracepoint | 跟踪点 | 内核的静态插桩,名字和参数相对稳定,数量有限 §4.3.5 术语页 |
| kprobes | 内核动态探针 | 几乎能挂任何内核函数,接口随内核版本变 §4.3.6 术语页 |
| uprobes | 用户态动态探针 | 挂用户程序和库里的函数,靠陷入内核实现,每次较贵 §4.3.7 术语页 |
| USDT | 用户态静态跟踪点 | 程序自己埋的稳定探针,要在编译时打开 §4.3.8 术语页 |
| PMC (performance monitoring counter) | 硬件性能计数器 | 计数模式几乎零开销;溢出采样能落到指令,但有偏移(skid) §4.3.9 术语页 |
| netlink | netlink 套接字 | 用 AF_NETLINK 套接字收发二进制结构取内核信息,比解析文本省;ss、ip 用它 §4.3.4 术语页 |
| delay accounting | 延迟统计 | 每个任务等 CPU、等块 IO、等换页、等内存回收各花了多久 §4.3.3 术语页 |
| perf_event_open | perf_event_open 系统调用 | 打开 PMC、跟踪点、kprobes、uprobes 等事件的统一入口,支持多个用户同时用 §4.3.5 |
| tracefs | 跟踪文件系统 | Ftrace 的控制文件;书里写在 /sys/kernel/debug/tracing,新内核在 /sys/kernel/tracing §4.3.5 |
方法:按什么顺序查
这一章本身没有给排查顺序,但它把数据源从便宜排到贵,也说清了哪些稳定、哪些会变。本站据此整理出「想看一个数时按什么顺序找来源」,原则是先用现成的计数器,不够再采样,最后才逐个事件跟踪。
- 内核是不是已经在数了?
先翻
/proc(进程和全系统)、/sys(设备和 CPU)、/sys/fs/cgroup(容器),网络的看nstat、ss。读两次相减就是速率,任何时候都能读。§4.2.1§4.3.1§4.3.2 - 某个工具显示的数,到底是哪个计数器?
strace -e trace=openat,socket 工具名看它打开了哪些文件、开了什么套接字;再查man 5 proc和内核文档里那个字段的定义。§4.3.1§4.3.4 - 想知道 CPU 花在哪段代码:采样
perf record -F 99 -g这类定时采样,频率定了开销就定了。要看缓存未命中落在哪,用 PMC 的溢出采样,注意 skid。§4.2.2§4.3.9 - 要每个事件的细节:先找跟踪点,先估事件率
perf list tracepoint或ls /sys/kernel/tracing/events找有没有现成的跟踪点;先用计数器估一下每秒多少次,十万级的(比如调度事件)要在内核里汇总,别逐条打印。没有跟踪点再用 kprobes,记住它跨内核版本不保证还能用。§4.3.5§4.3.6 - 应用内部:先 USDT,再 uprobes
先看程序或库有没有带 USDT 探针(有些要从源码重新编译才打开);没有才用 uprobes,热路径上的函数每次多付微秒级,算一算再开。strace、gdb 别挂到线上高频路径上。§4.3.7§4.3.8§4.3.10
- 第一次用的指标,先验一验
用两种来源交叉核对,或者施加一个已知的负载(比如一个绑在某个核上的死循环),看工具报的是不是你预期的数。§4.6
工具速查
| 想知道 | 敲什么 | 看哪里 | 节 |
|---|---|---|---|
| 每个 CPU 的时间去哪了(原始计数) | grep ^cpu /proc/statmpstat -P ALL 1 | /proc/stat 每行依次是 user、nice、system、idle、iowait、irq、softirq、steal,单位是 USER_HZ;隔一秒两次相减才有意义 | §4.3.1 |
| 一个进程的 CPU 和排队 | cat /proc/PID/stat /proc/PID/schedstatpidstat -u -p PID 1 | stat 第 14、15 个字段是 utime、stime(tick);schedstat 三个数是在 CPU 上的纳秒、排队等 CPU 的纳秒、上 CPU 的次数。pidstat 的 %wait 就是第二个数 | §4.3.1 |
| 每个进程的上下文切换 | grep ctxt /proc/PID/statuspidstat -w 1 | voluntary(自己睡下)和 nonvoluntary(被抢走)分开数;后者涨得快说明在抢 CPU | §4.3.1 |
| 网卡收发量 | cat /sys/class/net/eth0/statistics/rx_bytessar -n DEV 1 | 和 /proc/net/dev 是同一份计数;看 rx_dropped、rx_errors 在不在涨 | §4.3.2 |
| TCP 协议栈计数 | nstat -zsar -n TCP,ETCP 1 | 重传(RetransSegs、retrans/s)、主动和被动建连数;-a 忽略历史、显示开机以来的累计值,不加 -a 时 nstat 显示上次运行以来的增量 | §4.2.1 |
| 套接字的细节 | ss -tin | 每个连接的 rtt、cwnd、重传;数据默认走 netlink,不靠解析 /proc/net/tcp | §4.3.4 |
| 这个工具的数从哪来 | strace -e trace=openat,socket mpstat 1 1 | 打开的 /proc、/sys 文件和 AF_NETLINK 套接字就是它的数据源 | §4.3.1 |
| 昨天某个时段的数据 | sar -u -f /var/log/sysstat/sa15sar -n DEV -s 14:00:00 -e 15:00:00 | 要先开了收集;默认十分钟一个点,看不到秒级抖动 | §4.4.2 |
| 把 sar 数据导给别的程序 | sadf -j -- -n TCPsadf -d -- -u | -j 是 JSON,-d 是分号分隔、给数据库导入用,-g 出 SVG 图 | §4.4.2 |
| 内核有哪些跟踪点 | perf list tracepointls /sys/kernel/tracing/events | 按子系统分组,如 block:、sched:、tcp:;要 root | §4.3.5 |
| 一个跟踪点带哪些参数 | cat /sys/kernel/tracing/events/sched/sched_switch/format | field 行是参数名和类型,print fmt 是默认的输出格式;跟踪器按名字取参数 | §4.3.5 |
| 能挂哪些 kprobe、uprobe | bpftrace -l 'kprobe:tcp_*'bpftrace -l 'uprobe:/usr/sbin/nginx:*' | 列得出来不等于下个版本还在;要 root | §4.3.6§4.3.7 |
| 程序带不带 USDT 探针 | readelf -n /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep -A3 stapsdtbpftrace -l 'usdt:/usr/lib/x86_64-linux-gnu/libstdc++.so.6:*' | readelf 不要 root。本站查过:Debian 13 的 libstdc++ 带 throw、catch、rethrow 三个探针,能数 C++ 异常;libc 没有编进 USDT。有的程序要用编译选项打开 | §4.3.8 |
| 硬件计数器能不能用 | perf stat -e cycles,instructions ./prog | 显示 <not supported> 说明拿不到,云主机上常见;拿到了就看 insn per cycle | §4.3.9 |
| 每个任务等 IO 等了多久 | sysctl kernel.task_delayacctpidstat -d 1 | iodelay 列来自延迟统计;新内核默认关着,值恒为 0 时先查这个开关 | §4.3.3 |
procps(ps、top、vmstat)和 util-linux 一般系统自带;mpstat、pidstat、sar、sadf 在 sysstat 包里;nstat、ss 在 iproute2;strace 单独一个包;perf 在 Debian 上是 linux-perf;bpftrace 和 bpfcc-tools 各一个包。读 /proc、/sys 的计数器、用 mpstat 和 sar 不需要 root;列跟踪点、读 tracefs、bpftrace 和 BCC 都要 root。书 §4.1.2 有一张出事前就该装好的包的清单,值得照着给自己的机器过一遍。
动手:在自己的机器上试
不用 sysstat,从 /proc 算出 mpstat 和 pidstat 的数
下面两段 shell 脚本分别存成 cpupct.sh 和 pidpct.sh,只用 grep、cut、awk、sleep,busybox 里一般都有。先造一个已知的负载:一个死循环绑在 3 号核上,3 号核应该是 100% 用户态。再看两个死循环挤在同一个核上,每个应该一半时间在跑、一半时间在排队。
# cpupct.sh [N]:隔 1 秒读两次 /proc/stat,算出 cpuN(不给 N 就是全部 CPU)这一秒的各项百分比
c="cpu${1:-}"
a=$(grep "^$c " /proc/stat); sleep 1; b=$(grep "^$c " /proc/stat)
printf '%s\n%s\n' "$a" "$b" | awk '
{ for (i = 2; i <= 9; i++) v[NR, i] = $i } # 第 2–9 列:user nice system idle iowait irq softirq steal
END {
for (i = 2; i <= 9; i++) { d[i] = v[2, i] - v[1, i]; t += d[i] }
if (t == 0) t = 1
printf "%-5s usr %5.1f sys %5.1f iowait %5.1f soft %5.1f steal %5.1f idle %5.1f (共 %d 个 tick)\n",
$1, 100*d[2]/t, 100*d[4]/t, 100*d[6]/t, 100*d[8]/t, 100*d[9]/t, 100*d[5]/t, t
}'
# pidpct.sh PID:隔 1 秒读两次 /proc/PID/stat 和 /proc/PID/schedstat
# stat 的第 14、15 个字段是 utime、stime(单位 tick);进程名里带空格时字段会错位,这里的 sh 没有
p=$1; hz=$(getconf CLK_TCK)
s1=$(cut -d' ' -f14,15 /proc/$p/stat); q1=$(cat /proc/$p/schedstat)
sleep 1
s2=$(cut -d' ' -f14,15 /proc/$p/stat); q2=$(cat /proc/$p/schedstat)
echo "$s1 $q1 $s2 $q2" | awk -v hz=$hz '{
printf "utime %+d tick stime %+d tick -> usr %.0f%% sys %.0f%%\n", $6-$1, $7-$2, 100*($6-$1)/hz, 100*($7-$2)/hz
printf "schedstat: 在 CPU 上 %.0f ms,排队等 CPU %.0f ms,上 CPU %d 次\n", ($8-$3)/1e6, ($9-$4)/1e6, $10-$5 }'
# 计数单位:USER_HZ $ getconf CLK_TCK # 一个死循环绑在 3 号核上,8 秒后自动退出;同时用脚本和 mpstat 各量一秒 $ taskset -c 3 timeout 8 sh -c 'while :; do :; done' & $ sh cpupct.sh 3 & mpstat -P 3 1 1 # 两个死循环挤在 5 号核上,取其中一个的 PID,脚本和 pidstat 各量一秒 $ taskset -c 5 timeout 8 sh -c 'while :; do :; done' & $ taskset -c 5 timeout 8 sh -c 'while :; do :; done' & $ P=$(pgrep -n -f 'sh -c while') $ sh pidpct.sh $P & pidstat -u -p $P 1 1
本站在 16 核的 WSL2 上看到的(mpstat 和 pidstat 只留了几列):
CLK_TCK: 100 cpupct.sh: cpu3 usr 100.0 sys 0.0 idle 0.0 (共 101 个 tick) mpstat: CPU %usr %sys %idle 3 100.00 0.00 0.00 pidpct.sh: utime +50 tick stime +0 tick -> usr 50% schedstat: 在 CPU 上 504 ms,排队等 CPU 500 ms,上 CPU 125 次 pidstat: %usr %system %wait Command 50.00 0.00 50.00 sh
- 3 号核一秒里涨了 101 个 tick,几乎全在 user 一列,和 mpstat 一致。一个核一秒大约 100 个 tick,所以不带参数跑
cpupct.sh时,16 核的机器合计约 1600 个。时钟中断其实是 250 Hz(这台内核的 CONFIG_HZ),但 /proc 统一按 USER_HZ 交给用户态。 - pidstat 的
%wait正是 schedstat 第二个数的增量:500 毫秒在排队,就是 50%。用strace看 pidstat 的话,能看到它每秒正是读了/proc/PID/stat和/proc/PID/schedstat(下一个实验)。 - 一秒上了 125 次 CPU,两个任务轮流,每次大约 4 毫秒。这类细节 top 里看不到,schedstat 里一直都在。
- WSL2
- 本站实测过,不需要 root(Debian 13,内核 6.6,sysstat 12.7)。这台内核的 /proc/PID/schedstat 有数。别人的负载会让全系统的那一行抖,绑核的那一行很稳。
- 服务器
- 同样适用,监控代理读的也是这些文件。在容器里,/proc/stat 一般显示的是整台宿主机的 CPU,不是容器分到的那一份,容器的用量看它所在 cgroup 的
cpu.stat。 - ARM 板
- 两个脚本只用到 busybox 常见的几个命令,先确认你的 busybox 带了 awk 和 cut。没有 getconf 时,ARM 上的 USER_HZ 按 100 算。schedstat 要内核开了
CONFIG_SCHED_INFO(板子上有 /proc/config.gz 的话 zcat 后 grep 一下)。没有 taskset 时,busybox 也可能带了同名的小工具。
看工具从哪里拿数,再量一量 ptrace 的代价
strace 是 ptrace 的典型用户。先用它看几个工具读了什么,再反过来看它自己:同一个只做系统调用的程序,被 strace 跟着时慢了多少。先猜一个倍数。
# mpstat、pidstat 各打开了哪些 /proc、/sys 文件(只统计路径) $ strace -e trace=openat -o mp.log mpstat 1 1 > /dev/null $ grep -o '"/\(proc\|sys\)/[^"]*' mp.log | tr -d '"' | sort | uniq -c $ strace -e trace=openat -o pid.log pidstat -u -p $$ 1 1 > /dev/null $ grep -o '"/\(proc\|sys\)/[^"]*' pid.log | tr -d '"' | sort | uniq -c # ip 不读文件,开的是 netlink 套接字 $ strace -e trace=socket ip -s link show eth0 2>&1 > /dev/null | grep NETLINK # 同一个程序:单独跑、被 strace 全程跟着、只让内核在 openat 上停 $ time dd if=/dev/zero of=/dev/null bs=1 count=20000 $ time strace -o /dev/null dd if=/dev/zero of=/dev/null bs=1 count=20000 $ time strace -e trace=openat -o /dev/null dd if=/dev/zero of=/dev/null bs=1 count=20000 $ time strace --seccomp-bpf -f -e trace=openat -o /dev/null dd if=/dev/zero of=/dev/null bs=1 count=20000
本站 WSL2 上的结果(截了关键的几行):
mpstat: 2 /proc/stat
2 /proc/uptime
1 /proc/interrupts
1 /proc/softirqs
16 /sys/devices/system/cpu/cpuN
pidstat: 2 /proc/PID/stat
2 /proc/PID/schedstat
2 /proc/PID/status
ip: socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3
real 0m0.015s dd 单独跑
real 0m5.315s strace 全部系统调用(另一次 2.1 s)
real 0m3.209s strace -e trace=openat
real 0m0.027s strace --seccomp-bpf -f -e trace=openat
- mpstat 每次采样读的就是 /proc/stat,开头读一次 /sys 数一数有几个 CPU;pidstat 多读了每个进程的三个文件。ip 走 NETLINK_ROUTE 拿网卡信息,书里拿 ss 举的例子走的是 NETLINK_SOCK_DIAG。照这个办法,任何一个工具、监控代理的数据源都能这样摸清。
bs=1让 dd 读写两万次,一共四万多个系统调用。单独跑十几毫秒;strace 跟着,每个系统调用都要停两次、在两个进程之间来回切,慢了一百多倍到三百多倍。几次跑之间差了一倍多,因为这台机器上同时有别人的负载,倍数只看量级。- 只加
-e trace=openat并没有变快:过滤发生在 strace 里,目标照样每个系统调用都停。加了--seccomp-bpf,过滤挪进内核,只在 openat 上停,几乎恢复原速。这个选项 strace 5.3(2019 年)起才有,书里没有提。
- WSL2
- 本站实测过(strace 6.13,从 Debian 的 deb 包解出来用的;读者
sudo apt install strace即可)。这台机器的 ptrace_scope 是 1:strace 自己启动的程序不用 root,用-p挂到已经在跑、又不是自己子进程的进程上要 root。 - 服务器
- 用 strace 看工具读什么文件很安全,工具几秒就退出。别用
strace -p挂正在服务的进程,尤其是系统调用多的;要看线上服务的系统调用,用基于跟踪点的perf trace或 BPF 工具(要 root)。 - ARM 板
- busybox 不带 strace,Buildroot、Yocto 里都能选上,体积不大。没有 strace 时,
ls -l /proc/PID/fd能看到一个进程此刻开着哪些文件和套接字,不过只是一瞬间的快照。
成书之后
延迟统计默认关了
- 书里
- 内核开了
CONFIG_TASK_DELAY_ACCT,就会按任务记录等 CPU、等块 IO、等换页、等回收的时间,用 taskstats 读出来(§4.3.3)。 - 今天
- Linux 5.14(2021 年)合入了「delayacct: Default disabled」这个提交:编译选项开着,运行时也默认不统计。要用就在启动参数里加
delayacct,或者运行时sysctl kernel.task_delayacct=1,打开之后新起的任务才有数。本站的 WSL2 上/proc/sys/kernel/task_delayacct是 0,pidstat -d的 iodelay 列因此恒为 0。等 CPU 那一项不受影响,它本来就来自 schedstat。
Debian 系的 sar 收集改由 systemd 定时器触发
- 书里
- 在 Ubuntu 上改
/etc/default/sysstat打开收集,再改/etc/cron.d/sysstat调频率(§4.4.2)。 - 今天
- Debian 的 sysstat 从 12.4.0-1(2020 年 8 月)起优先用 systemd 定时器
sysstat-collect.timer(默认每 10 分钟),有 systemd 的系统上 cron 脚本会直接退出,改 cron.d 不再起作用。调频率用systemctl edit sysstat-collect.timer改OnCalendar;要收集全部统计组,改/etc/sysstat/sysstat里的SADC_OPTIONS。默认开没开因发行版和安装时的选择而异,用systemctl is-enabled sysstat-collect.timer看一眼。
分节地图
这一章分四块:工具的分类(§4.1–4.2)、数据源(§4.3)、sar(§4.4)、跟踪器简介和对指标的怀疑(§4.5–4.6)。先读的是三类工具的开销、/proc 和 /sys、出事前该装什么、以及怎么怀疑一个数;跟踪点、kprobes 这几节要用 BPF 或 perf 时再回来查。
- Operating systems have historically provided…约 300 词开篇过去靠推断,现在有动态跟踪;学习目标,扫一眼当目录。
- Tool Coverage约 100 词工具覆盖面一张按内核组件标注工具的图的引子。
- Static Performance Tools约 100 词静态性能工具系统不在负载下时查配置的工具,配合第 2 章的静态调优。
- Crisis Tools约 300 词救急工具出事前就该装好的包,还有内核配置和栈回溯的准备。
- Tool Types约 100 词工具类型全系统还是单进程,计数还是事件,两个维度分类。
- Fixed Counters约 400 词固定计数器成对的累计计数器怎么换算成速率和平均延迟,为什么近乎免费。
- Profiling约 300 词剖析定时采样和按硬件事件采样,开销为什么能预估。
- Tracing约 400 词跟踪每个事件都处理的代价;调试器为什么不适合生产。
- Monitoring约 600 词监控sar、SNMP 和代理式监控;监控产品的系统指标和命令行工具同源。
- Observability Sources约 200 词观测数据源一张数据源总表的引子,后面逐个讲。
- /proc约 800 词/proc进程和全系统的文件各有哪些、谁在读;CPU 统计的精度。
- /sys约 200 词/sys按设备组织的统计和可写的控制文件。
- Delay Accounting约 100 词延迟统计每个任务四种等待的时间;新内核要手动打开,见成书之后。
- netlink约 200 词netlink二进制的套接字接口,比解析 /proc 省;ss、ip 用它。
- Tracepoints约 1100 词跟踪点参数和格式串、两种打开方式、开销的经验值。写跟踪前读开销那段。
- kprobes约 600 词kprobes任意内核函数的动态探针,和跟踪点的取舍,返回探针怎么计时。
- uprobes约 400 词uprobes用户函数的动态探针,为什么比 kprobes 贵得多。
- USDT约 400 词用户态静态跟踪程序自带的稳定探针,常要编译时打开;JIT 语言的动态 USDT。
- Hardware Counters (PMCs)约 800 词硬件计数器计数和溢出采样、寄存器数量有限、skid 和精确事件、云上能不能用。
- Other Observability Sources约 700 词其他数据源MSR、ptrace、抓包、进程记账、软件事件,以及 Solaris 的 Kstat 对照。
- sar约 100 词sar作者为什么在 BPF 时代仍然看重 sar。
- sar(1) Coverage约 100 词sar 的覆盖面各个选项对应哪些子系统,连风扇和温度都有。
- sar(1) Monitoring约 400 词用 sar 留存数据打开收集、调频率、收全部统计组、导出成 JSON 和 CSV。
- sar(1) Live约 100 词实时模式带间隔参数时实时输出,不需要开收集。
- sar(1) Documentation约 100 词sar 的文档手册页给每个指标标了对应的 SNMP 名字。
- Tracing Tools约 300 词跟踪工具perf、Ftrace、BPF、SystemTap、LTTng 各擅长什么,作者自己怎么分工。
- Observing Observability约 400 词观测观测工具本身工具、文档、采集脚本都会错;怎么交叉验证,以及缺失的指标。
- Exercises约 100 词习题习题;本页的诊断题和两个实验可以代替。
词与短语
只收先读和用到再查两档的节。斜体那一行是原句里连续的几个词,在电子书里搜它就能找到那一句。
§0 开篇
§4.1.2 Crisis Tools
§4.2 Tool Types
§4.2.1 Fixed Counters
§4.2.2 Profiling
§4.2.3 Tracing
§4.2.4 Monitoring
§4.3.1 /proc
§4.3.2 /sys
§4.3.3 Delay Accounting
§4.3.5 Tracepoints
§4.3.6 kprobes
§4.3.7 uprobes
§4.3.8 USDT
§4.3.9 Hardware Counters (PMCs)
§4.3.10 Other Observability Sources
§4.4 sar
§4.4.2 sar(1) Monitoring
§4.5 Tracing Tools
§4.6 Observing Observability
诊断题:换成你来查
场景和输出都是本站编的。先自己判断,再点选项看解释。
场景:一块 4 核 ARM 板跑着你的视频采集程序,进程加线程有八百多个。你开着 busybox 的 top,刷新间隔调到了 0.5 秒,发现排第一的是 top 自己:
PID %CPU COMMAND 612 12.0 top 455 6.1 camd
/proc 的数是读的时候才从内核结构里拼成文本的,单个文件很便宜,但 top 每次刷新要对所有进程挨个打开、读、关。书里专门说过 top 可能把自己报成最大的 CPU 消耗者。0.5 秒刷一次、八百多个任务,在板子的小核上就能看出来。办法是拉长刷新间隔;只关心 camd 的话,隔几秒读一次 /proc/455/stat 和 schedstat 就够了。
场景:32 核服务器跑着你的 C++ 网关,延迟偶尔抖。你想知道哪个进程上下文切换最多。同事提议用 bpftrace 挂 sched:sched_switch,每次切换打印一行。vmstat 显示:
vmstat: r cs us sy
6 183402 41 9内核已经按进程数着自愿和非自愿切换,隔一秒读两次就有速率,几乎免费。调度事件每秒十几万次,正是书里说开销开始能量出来的那一档,逐条打印到用户态更贵。「稳定」说的是接口不随版本变,和开销无关。strace 靠 ptrace,会把网关拖慢上百倍;定时采样数不出事件次数。真要每次切换的细节,也应在内核里汇总成直方图,再短时间开。
场景:你写了一个 bpftrace 脚本,用 kprobe: 挂在一个 TCP 相关的内核函数上统计重传,在旧内核上一直好用。服务器换到新的大版本内核后,脚本报错说找不到这个探针,root 权限和 bpftrace 都没问题。
重传有现成的跟踪点 tcp:tcp_retransmit_skb,长期用的脚本应挂在它上面。kprobes 能挂几乎任何内核函数,代价就是没有接口承诺:函数是内核开发者的内部实现,随时会改。跟踪点是专门为观测留的,名字和参数尽量保持稳定。写长期要用的工具时优先用跟踪点;只能用 kprobes 的,换内核前先在测试机上跑一遍。可以先用 bpftrace -l 'kprobe:tcp_*' 看新内核里还剩哪些函数。
场景:一台 6.x 内核的服务器,你的日志服务写盘很多,iostat 里磁盘的 await 不低,ps 里还能看到它处于 D 状态。可是 pidstat -d 1 的 iodelay 列一直是 0:
pidstat: kB_wr/s iodelay Command
5120.0 0 logd
sysctl: kernel.task_delayacct = 0iodelay 来自延迟统计里的块 IO 等待时间。Linux 5.14(2021 年)起内核默认不统计,要靠启动参数 delayacct 或 sysctl kernel.task_delayacct=1 打开,而且只有打开之后新起的任务才有数据,所以打开后要重启这个服务。pidstat 的其他列(kB_wr/s 等)来自另一组计数器,照常有效。这正是书 §4.6 说的:一个数为 0,可能是没人在数。
场景:你在一台小规格云主机上想量服务的 IPC,perf stat 跑了一分钟:
<not supported> cycles
<not supported> instructions
60,012.4 msec task-clockPMC 是处理器上的寄存器,虚拟机能不能用取决于宿主机让不让,很多云环境不给访客开。书里提到有的虚拟化平台能按组开放,有的云厂商只在大规格实例上开放,或者提供裸金属实例。task-clock 是软件事件,所以照样有数。下一步是换成开放了 PMC 的实例类型或裸金属实例,或者先在自己的物理机上量。能拿到的环境里,看 insn per cycle 判断是不是在等内存。
值得停下来的句子
event rates of less than 10,000 per second cost negligible overhead, and only over 100,000 does the overhead begin to become measurable.— §4.3.5。本站译:每秒一万个事件以下,开销可以忽略;超过十万,开销才开始量得出来。
这是一条能直接拿来算的经验值。要开跟踪之前,先用便宜的计数器估一估事件率:磁盘 IO 在 /proc/diskstats 或 iostat 里,上下文切换在 vmstat 的 cs 列,网络包在 /proc/net/dev。落在万次以下,放心开;到了十万级,就把逐条输出换成内核里的直方图,并缩短开着的时间。作者说这是自己在 4 到 128 核的机器上的体会。本站的看法:核少、主频低的 ARM 板上,门槛要再放低一些。
Regard with a healthy skepticism any statistics that are new to you, questioning what they really mean and whether they are really correct.— §4.6。本站译:对任何你没见过的统计数据,都要保持适度的怀疑:问一问它到底是什么意思,是不是真的对。
这一章前面讲的都是「数从哪来」,这一句告诉你知道来源的用处:能验证。第一次在仪表盘上看到一个指标,先找到它读的文件,再用一个已知的负载去对它;本页第一个实验就是这么做的。延迟统计默认关掉、iodelay 恒为 0 这种事,不去验证就会被当成「没有 IO 等待」。
带走的几条
一句话:先用内核一直在数的计数器,不够再采样,最后才逐个事件跟踪;每个数都要知道从哪来、要付多少代价。
/proc、/sys 的数都是开机以来的累计值,隔一秒读两次相减才是速率。工具的第一行常常是开机以来的平均。
§4.2.1strace -e trace=openat,socket 跑一遍工具,打开的文件和 netlink 套接字就是它的数据源。
§4.3.1每秒一万次以下可以忽略,十万以上能量出来。调度事件常在高的那一档,先用计数器估事件率。
§4.3.5内核里先找跟踪点,没有再用 kprobes;应用里先找 USDT,没有再用 uprobes。动态的跨版本不保证还在,uprobes 每次贵一个量级。
§4.3.6ptrace 让每个系统调用停两次,目标可能慢上百倍。只看少数调用时加 --seccomp-bpf,或改用基于跟踪点的工具。
§4.3.10/proc/stat 算 CPU,/proc/PID/stat 和 schedstat 算进程的运行和排队,/sys/class/net 看网卡,busybox 的 awk 就够。
§4.3.2两个来源交叉核对,或者施加已知负载看工具报得对不对。一个数为 0,可能只是没人在数。
§4.6