这一章在做什么

这一章不查某一种资源,而是给后面各章打底:每个观测工具的数从哪儿来、要付多少代价、能不能在生产机上开。工具分计数器、剖析、跟踪、监控四类;数据源从 /proc、/sys、netlink,到跟踪点、kprobes、uprobes、USDT 和硬件计数器,一个个过。最该带走的是一把尺子:计数器几乎免费,定时采样的开销可以预估,跟踪的开销跟着事件率涨。最后讲怎么让 sar 留下历史数据。

从哪拿数,每个事件多付多少(示意,不按比例) 数据源 每个事件的额外代价 生产机上 内核计数器(/proc、/sys) 一直开着,只付读的钱 硬件计数器:计数 计数模式几乎零开销 定时采样(99 Hz) 频率自己定,开销可预估 跟踪点、kprobe 约百纳秒 先估事件率再开 uprobe、USDT 约微秒 陷入内核,贵一个量级 ptrace(strace、gdb) 目标可能慢上百倍 总开销 = 每个事件的代价 × 每秒事件数。磁盘事件常在每秒万次以下,调度事件可能过十万。
本站画的示意。纳秒、微秒两档是书里在某一台机器上量到的最小值(§4.3.5–4.3.7),换一台机器会不同;ptrace 那一档见 §4.3.10 和下面的第二个实验。

什么时候翻这一章

第 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 做同样的减法,下面的第一个实验就是这么做的。

mpstat 显示 100% 之前做了什么 user system idle t = 0 读一次 1701426 1529049 1306092521 t = 1 秒再读 1701526 1529049 1306092521 相减 +100 +0 +0 这一秒共 100 个 tick,user 占 100 个 → %usr = 100% 单位是 USER_HZ(getconf CLK_TCK,通常是 100),不是内核的 CONFIG_HZ
本站画的示意,数字取自本站 WSL2 上 /proc/stat 的 cpu1 一行(第二行是编的)。真正的行有十列,mpstat 把每一列都这样算一遍。

精度要心里有数:默认配置下 CPU 时间按时钟中断的 tick 记账,一个 tick 可能是 4 毫秒,看分钟级的趋势够用,看几毫秒的短任务就粗了(§4.3.1)。内核也提供更细的记账方式,代价是一点额外开销。

采样和跟踪:开销跟着谁走

计数器只告诉你「多少」,说不出「哪段代码」「哪一次慢」。剖析(profiling)是定时拍快照:每秒在每个 CPU 上打断 99 次,记下当时的指令地址或调用栈。打断的频率是你定的,所以开销事先就能估出来,这是它在生产上相对安全的原因(§4.2.2)。

跟踪(tracing)是每个事件都处理:每次磁盘 IO、每次调度、每次系统调用都走一遍插桩代码,记下来或者在内核里汇总。好处是一个都不漏,代价是开销和事件率成正比,事件一突发,开销跟着涨,被跟踪的程序变慢,量出来的时间戳也可能被跟踪器自己拉长(§4.2.3)。书里给的经验值是:今天常见的 4 到 128 核机器上,每秒一万个事件以下几乎感觉不到,十万以上开始能量出来;磁盘事件多在前一档,调度事件常在后一档(§4.3.5)。

同一段时间,三种观测方式各付多少 发生的事件 突发 采样 99 Hz 打断次数由频率决定,和事件多少无关 跟踪 每个事件付一次,突发时开销一起涨 计数器不在图上:内核每个事件只加一,读的时候才付钱。
本站画的示意。剖析也可以按硬件事件触发(比如每 N 次缓存未命中采一次),那时它的开销又和事件多少有关了(§4.2.2、§4.3.9)。

事件从哪来:静态和动态,内核和用户态

跟踪器本身不产生事件,它挂在内核提供的事件源上。分两个维度看就清楚了。静态插桩是写代码的人预先埋好的点:内核里叫跟踪点(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)。

四种事件源 内核 用户态程序和库 静态 预先埋好 跟踪点 tracepoint 如 sched:sched_switch 接口稳定;不开时只是一条 nop USDT 程序自己埋,要编进去 稳定、该有文档 动态 运行时插入 kprobes / kretprobes 几乎任意内核函数 随内核版本变;不用时代码不变 uprobes / uretprobes 任意用户函数 随程序版本变;陷入内核,贵 四种都能通过 perf_event_open 或 tracefs 打开,perf、Ftrace、BCC、bpftrace 用的都是它们。
本站画的示意。USDT 在 Linux 上借 uprobes 实现,所以它的每次开销和 uprobes 同一档;稳定的是它的名字和参数,不是它的价钱。

还有两类源不在这四格里。一是硬件计数器(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 术语页
netlinknetlink 套接字用 AF_NETLINK 套接字收发二进制结构取内核信息,比解析文本省;ss、ip 用它 §4.3.4 术语页
delay accounting延迟统计每个任务等 CPU、等块 IO、等换页、等内存回收各花了多久 §4.3.3 术语页
perf_event_openperf_event_open 系统调用打开 PMC、跟踪点、kprobes、uprobes 等事件的统一入口,支持多个用户同时用 §4.3.5
tracefs跟踪文件系统Ftrace 的控制文件;书里写在 /sys/kernel/debug/tracing,新内核在 /sys/kernel/tracing §4.3.5

方法:按什么顺序查

这一章本身没有给排查顺序,但它把数据源从便宜排到贵,也说清了哪些稳定、哪些会变。本站据此整理出「想看一个数时按什么顺序找来源」,原则是先用现成的计数器,不够再采样,最后才逐个事件跟踪。

  1. 内核是不是已经在数了?

    先翻 /proc(进程和全系统)、/sys(设备和 CPU)、/sys/fs/cgroup(容器),网络的看 nstat、ss。读两次相减就是速率,任何时候都能读。§4.2.1§4.3.1§4.3.2

  2. 某个工具显示的数,到底是哪个计数器?

    strace -e trace=openat,socket 工具名 看它打开了哪些文件、开了什么套接字;再查 man 5 proc 和内核文档里那个字段的定义。§4.3.1§4.3.4

  3. 想知道 CPU 花在哪段代码:采样

    perf record -F 99 -g 这类定时采样,频率定了开销就定了。要看缓存未命中落在哪,用 PMC 的溢出采样,注意 skid。§4.2.2§4.3.9

  4. 要每个事件的细节:先找跟踪点,先估事件率

    perf list tracepoint 或 ls /sys/kernel/tracing/events 找有没有现成的跟踪点;先用计数器估一下每秒多少次,十万级的(比如调度事件)要在内核里汇总,别逐条打印。没有跟踪点再用 kprobes,记住它跨内核版本不保证还能用。§4.3.5§4.3.6

  5. 应用内部:先 USDT,再 uprobes

    先看程序或库有没有带 USDT 探针(有些要从源码重新编译才打开);没有才用 uprobes,热路径上的函数每次多付微秒级,算一算再开。strace、gdb 别挂到线上高频路径上。§4.3.7§4.3.8§4.3.10

  6. 第一次用的指标,先验一验

    用两种来源交叉核对,或者施加一个已知的负载(比如一个绑在某个核上的死循环),看工具报的是不是你预期的数。§4.6

工具速查

想知道敲什么看哪里节
每个 CPU 的时间去哪了(原始计数)grep ^cpu /proc/stat
mpstat -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/schedstat
pidstat -u -p PID 1
stat 第 14、15 个字段是 utime、stime(tick);schedstat 三个数是在 CPU 上的纳秒、排队等 CPU 的纳秒、上 CPU 的次数。pidstat 的 %wait 就是第二个数§4.3.1
每个进程的上下文切换grep ctxt /proc/PID/status
pidstat -w 1
voluntary(自己睡下)和 nonvoluntary(被抢走)分开数;后者涨得快说明在抢 CPU§4.3.1
网卡收发量cat /sys/class/net/eth0/statistics/rx_bytes
sar -n DEV 1
和 /proc/net/dev 是同一份计数;看 rx_dropped、rx_errors 在不在涨§4.3.2
TCP 协议栈计数nstat -z
sar -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/sa15
sar -n DEV -s 14:00:00 -e 15:00:00
要先开了收集;默认十分钟一个点,看不到秒级抖动§4.4.2
把 sar 数据导给别的程序sadf -j -- -n TCP
sadf -d -- -u
-j 是 JSON,-d 是分号分隔、给数据库导入用,-g 出 SVG 图§4.4.2
内核有哪些跟踪点perf list tracepoint
ls /sys/kernel/tracing/events
按子系统分组,如 block:、sched:、tcp:;要 root§4.3.5
一个跟踪点带哪些参数cat /sys/kernel/tracing/events/sched/sched_switch/formatfield 行是参数名和类型,print fmt 是默认的输出格式;跟踪器按名字取参数§4.3.5
能挂哪些 kprobe、uprobebpftrace -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 stapsdt
bpftrace -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_delayacct
pidstat -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
  1. 3 号核一秒里涨了 101 个 tick,几乎全在 user 一列,和 mpstat 一致。一个核一秒大约 100 个 tick,所以不带参数跑 cpupct.sh 时,16 核的机器合计约 1600 个。时钟中断其实是 250 Hz(这台内核的 CONFIG_HZ),但 /proc 统一按 USER_HZ 交给用户态。
  2. pidstat 的 %wait 正是 schedstat 第二个数的增量:500 毫秒在排队,就是 50%。用 strace 看 pidstat 的话,能看到它每秒正是读了 /proc/PID/stat 和 /proc/PID/schedstat(下一个实验)。
  3. 一秒上了 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
  1. mpstat 每次采样读的就是 /proc/stat,开头读一次 /sys 数一数有几个 CPU;pidstat 多读了每个进程的三个文件。ip 走 NETLINK_ROUTE 拿网卡信息,书里拿 ss 举的例子走的是 NETLINK_SOCK_DIAG。照这个办法,任何一个工具、监控代理的数据源都能这样摸清。
  2. bs=1 让 dd 读写两万次,一共四万多个系统调用。单独跑十几毫秒;strace 跟着,每个系统调用都要停两次、在两个进程之间来回切,慢了一百多倍到三百多倍。几次跑之间差了一倍多,因为这台机器上同时有别人的负载,倍数只看量级。
  3. 只加 -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。
核对于 2026-10。出处:Linux 内核文档《Delay accounting》(docs.kernel.org,6.x 各版);Peter Zijlstra 的提交「delayacct: Default disabled」(tip 树,2021-05)。

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 看一眼。
核对于 2026-10。出处:Debian sysstat 软件包的 changelog(12.4.0-1 条目)和 Debian 13 的 sysstat 12.7.5 包内的 sysstat-collect.timer 文件。

分节地图

这一章分四块:工具的分类(§4.1–4.2)、数据源(§4.3)、sar(§4.4)、跟踪器简介和对指标的怀疑(§4.5–4.6)。先读的是三类工具的开销、/proc 和 /sys、出事前该装什么、以及怎么怀疑一个数;跟踪点、kprobes 这几节要用 BPF 或 perf 时再回来查。

  1. Operating systems have historically provided…约 300 词开篇过去靠推断,现在有动态跟踪;学习目标,扫一眼当目录。
  2. Tool Coverage约 100 词工具覆盖面一张按内核组件标注工具的图的引子。
  3. Static Performance Tools约 100 词静态性能工具系统不在负载下时查配置的工具,配合第 2 章的静态调优。
  4. Crisis Tools约 300 词救急工具出事前就该装好的包,还有内核配置和栈回溯的准备。
  5. Tool Types约 100 词工具类型全系统还是单进程,计数还是事件,两个维度分类。
  6. Fixed Counters约 400 词固定计数器成对的累计计数器怎么换算成速率和平均延迟,为什么近乎免费。
  7. Profiling约 300 词剖析定时采样和按硬件事件采样,开销为什么能预估。
  8. Tracing约 400 词跟踪每个事件都处理的代价;调试器为什么不适合生产。
  9. Monitoring约 600 词监控sar、SNMP 和代理式监控;监控产品的系统指标和命令行工具同源。
  10. Observability Sources约 200 词观测数据源一张数据源总表的引子,后面逐个讲。
  11. /proc约 800 词/proc进程和全系统的文件各有哪些、谁在读;CPU 统计的精度。
  12. /sys约 200 词/sys按设备组织的统计和可写的控制文件。
  13. Delay Accounting约 100 词延迟统计每个任务四种等待的时间;新内核要手动打开,见成书之后。
  14. netlink约 200 词netlink二进制的套接字接口,比解析 /proc 省;ss、ip 用它。
  15. Tracepoints约 1100 词跟踪点参数和格式串、两种打开方式、开销的经验值。写跟踪前读开销那段。
  16. kprobes约 600 词kprobes任意内核函数的动态探针,和跟踪点的取舍,返回探针怎么计时。
  17. uprobes约 400 词uprobes用户函数的动态探针,为什么比 kprobes 贵得多。
  18. USDT约 400 词用户态静态跟踪程序自带的稳定探针,常要编译时打开;JIT 语言的动态 USDT。
  19. Hardware Counters (PMCs)约 800 词硬件计数器计数和溢出采样、寄存器数量有限、skid 和精确事件、云上能不能用。
  20. Other Observability Sources约 700 词其他数据源MSR、ptrace、抓包、进程记账、软件事件,以及 Solaris 的 Kstat 对照。
  21. sar约 100 词sar作者为什么在 BPF 时代仍然看重 sar。
  22. sar(1) Coverage约 100 词sar 的覆盖面各个选项对应哪些子系统,连风扇和温度都有。
  23. sar(1) Monitoring约 400 词用 sar 留存数据打开收集、调频率、收全部统计组、导出成 JSON 和 CSV。
  24. sar(1) Live约 100 词实时模式带间隔参数时实时输出,不需要开收集。
  25. sar(1) Documentation约 100 词sar 的文档手册页给每个指标标了对应的 SNMP 名字。
  26. Tracing Tools约 300 词跟踪工具perf、Ftrace、BPF、SystemTap、LTTng 各擅长什么,作者自己怎么分工。
  27. Observing Observability约 400 词观测观测工具本身工具、文档、采集脚本都会错;怎么交叉验证,以及缺失的指标。
  28. Exercises约 100 词习题习题;本页的诊断题和两个实验可以代替。

词与短语

只收先读和用到再查两档的节。斜体那一行是原句里连续的几个词,在电子书里搜它就能找到那一句。

§0 开篇

the art of
… skilled in the art of inference …
……的本事、手艺
art 在这里不是艺术,是靠经验练出来的技巧:从间接的数据推断发生了什么。
dark corners
… Dark corners are now illuminated, including …
(过去)看不见的角落
比喻以前没有工具能观测的地方,比如单个磁盘 IO。
miss out on
… and are missing out on the insight …
错过了(本可得到的好处)
不是「漏掉一项」,是「没享受到」:很多公司还没用系统跟踪,没得到它能给的洞察。

§4.1.2 Crisis Tools

has yet to
… your company has yet to do …
还没有(做)
字面像「还有待」,意思就是「还没」。

§4.2 Tool Types

paint a picture
… painting a coarse picture of the target.
勾勒出(一个大致的样子)
和画画无关。采样给的是一幅粗略的轮廓,不是每个细节。

§4.2.1 Fixed Counters

considered “free”
… counters are considered “free” to use …
被看作「不花钱」
引号表示近似:读的那一下还是有一点代价,只是可以忽略。
At least, this is the intent
At least, this is the intent: …
至少本意是这样
反着读:后面紧跟着说 Linux 版 vmstat 的第一行并不是这样。

§4.2.2 Profiling

in lockstep with
… sampling in lockstep with target activity …
和……步调一致
采样频率和某个周期性活动合拍,样本就会系统地多算或少算它。
on an as-needed basis
… only enabled on an as-needed basis …
需要时才(开)

§4.2.3 Tracing

skewed
… may also be skewed by the tracer.
(被)扭曲、带偏
跟踪器自己的开销会把量出来的时间拉长。

§4.2.4 Monitoring

common denominator
… one common denominator that is covered …
共同的部分
本义是公分母。各家监控产品千差万别,共同的是内核计数器这一层。

§4.3.1 /proc

shell built-ins
… performed by shell built-ins from the …
shell 的内建命令
如 cd、echo、read。连 ps 都跑不起来时,靠它们读 /proc 也能查。
walk page tables
… related files that walk page tables.
遍历页表
walk 在内核语境里是「逐项走一遍」,所以读 smaps 这类文件并不便宜。

§4.3.2 /sys

has evolved over time
… which has evolved over time and …
一点点长成现在这样
暗指没有统一规划:/proc 的全系统文件大多堆在顶层目录里。

§4.3.3 Delay Accounting

Technically
Technically, the scheduler latency statistic is …
严格说来

§4.3.5 Tracepoints

best-effort stable
I’d call it “best-effort stable.”
尽量保持稳定(但不打包票)
best-effort 是「尽力而为」,不是「最好的」。作者说少见,但见过跟踪点变。
go beyond
… tracing tools that go beyond summary statistics …
超出(汇总统计能给的)
well over
… can be well over 100,000 per second …
远远超过
well 是程度副词。调度事件每秒可以远超十万次。

§4.3.6 kprobes

virtually unlimited
… source of virtually unlimited information about …
几乎无限的
virtually 是「几乎、实际上」,和「虚拟」无关。

§4.3.7 uprobes

dive deep into
… for diving deep into software internals …
深入(软件内部)

§4.3.8 USDT

drove adoption
… which drove adoption of USDT in applications.
推动了……被采用
选项名里带 dtrace,是因为当年是 DTrace 让应用纷纷加 USDT。
on the fly
… which usually compiles on the fly.
(运行时)现场、即时地
不是「在飞行中」。JIT 是边跑边编译,所以没法预先把探针编进去。

§4.3.9 Hardware Counters (PMCs)

skid
… (often called “skid”) or out-of-order …
(采样位置的)偏移
本义是车轮打滑。中断有延迟,记下的指令地址滑到了触发事件的那条指令后面。

§4.3.10 Other Observability Sources

hundred-fold
… slow the target over one hundred-fold.
一百倍
date back to
This dates back to mainframes and …
可以追溯到
shed some light on
… to shed some light on an …
帮忙弄清一点
字面是「照进一点光」。语气保守:能帮上一些,不是一锤定音。

§4.4 sar

I’m partly responsible
… superpowers (and I’m partly responsible), you …
(这股热潮)我也有份
作者自嘲:BPF 跟踪的热闹有一部分是作者自己带起来的,所以更要提醒别冷落 sar。

§4.4.2 sar(1) Monitoring

to suit
… can adjust this to suit the …
以适应(所需的精度)

§4.5 Tracing Tools

if and as needed
… in more detail later if and as needed.
如果需要、到需要的时候再
两个条件叠在一起:真需要才看,而且等用到时再看。这正是全书把跟踪器放到最后的理由。

§4.6 Observing Observability

healthy skepticism
Regard with a healthy skepticism any …
合理的怀疑
healthy 不是「健康」,是「适度、有益的」。
company critical
… a result that is company critical.
对公司至关重要的
tempting
… it may be tempting to assume …
(让人)很想、容易就
不是「诱人的」,是提醒:工具一多,人就容易以为什么都覆盖到了。

诊断题:换成你来查

场景和输出都是本站编的。先自己判断,再点选项看解释。

top 把自己排在了前面,怎么回事?§4.3.1

场景:一块 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 就够了。

想数每个进程的上下文切换,该用哪种办法?§4.3.5

场景:32 核服务器跑着你的 C++ 网关,延迟偶尔抖。你想知道哪个进程上下文切换最多。同事提议用 bpftrace 挂 sched:sched_switch,每次切换打印一行。vmstat 显示:

vmstat:   r      cs   us  sy
          6  183402   41   9

内核已经按进程数着自愿和非自愿切换,隔一秒读两次就有速率,几乎免费。调度事件每秒十几万次,正是书里说开销开始能量出来的那一档,逐条打印到用户态更贵。「稳定」说的是接口不随版本变,和开销无关。strace 靠 ptrace,会把网关拖慢上百倍;定时采样数不出事件次数。真要每次切换的细节,也应在内核里汇总成直方图,再短时间开。

升级内核后,跟踪脚本找不到探针了,为什么?§4.3.6

场景:你写了一个 bpftrace 脚本,用 kprobe: 挂在一个 TCP 相关的内核函数上统计重传,在旧内核上一直好用。服务器换到新的大版本内核后,脚本报错说找不到这个探针,root 权限和 bpftrace 都没问题。

重传有现成的跟踪点 tcp:tcp_retransmit_skb,长期用的脚本应挂在它上面。kprobes 能挂几乎任何内核函数,代价就是没有接口承诺:函数是内核开发者的内部实现,随时会改。跟踪点是专门为观测留的,名字和参数尽量保持稳定。写长期要用的工具时优先用跟踪点;只能用 kprobes 的,换内核前先在测试机上跑一遍。可以先用 bpftrace -l 'kprobe:tcp_*' 看新内核里还剩哪些函数。

iodelay 一直是 0,说明服务不等磁盘吗?§4.3.3

场景:一台 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 = 0

iodelay 来自延迟统计里的块 IO 等待时间。Linux 5.14(2021 年)起内核默认不统计,要靠启动参数 delayacct 或 sysctl kernel.task_delayacct=1 打开,而且只有打开之后新起的任务才有数据,所以打开后要重启这个服务。pidstat 的其他列(kB_wr/s 等)来自另一组计数器,照常有效。这正是书 §4.6 说的:一个数为 0,可能是没人在数。

云主机上 perf stat 拿不到周期数,下一步?§4.3.9

场景:你在一台小规格云主机上想量服务的 IPC,perf stat 跑了一分钟:

  <not supported>   cycles
  <not supported>   instructions
     60,012.4 msec    task-clock

PMC 是处理器上的寄存器,虚拟机能不能用取决于宿主机让不让,很多云环境不给访客开。书里提到有的虚拟化平台能按组开放,有的云厂商只在大规格实例上开放,或者提供裸金属实例。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.1
数从哪来,strace 它

strace -e trace=openat,socket 跑一遍工具,打开的文件和 netlink 套接字就是它的数据源。

§4.3.1
跟踪开销 = 事件率 × 单价

每秒一万次以下可以忽略,十万以上能量出来。调度事件常在高的那一档,先用计数器估事件率。

§4.3.5
静态优先,动态兜底

内核里先找跟踪点,没有再用 kprobes;应用里先找 USDT,没有再用 uprobes。动态的跨版本不保证还在,uprobes 每次贵一个量级。

§4.3.6
strace 别挂线上热路径

ptrace 让每个系统调用停两次,目标可能慢上百倍。只看少数调用时加 --seccomp-bpf,或改用基于跟踪点的工具。

§4.3.10
板子上 /proc 就是工具箱

/proc/stat 算 CPU,/proc/PID/stat 和 schedstat 算进程的运行和排队,/sys/class/net 看网卡,busybox 的 awk 就够。

§4.3.2
没见过的指标先验一验

两个来源交叉核对,或者施加已知负载看工具报得对不对。一个数为 0,可能只是没人在数。

§4.6