﻿# perf —— 采样调用栈，定位到函数/代码行的终极工具

## 这个工具是做什么的

`top`/`pidstat` 只能区分用户态/内核态，`perf` 基于内核 PMU（性能监控单元）和采样，能告诉你**具体是哪个函数、哪行代码、哪条调用链**在烧 CPU。是 CPU 高问题的"终审法官"。

## 可以回答什么问题

| 问题 | 怎么用 |
|------|--------|
| CPU 主要耗在哪个函数？ | `perf record -g` + `perf report` 看热点 |
| 热点函数的完整调用链？ | `perf report` 展开 / 火焰图 |
| 每周期指令数（IPC）高不高？CPU 在有效计算还是空转？ | `perf stat` 的 `IPC = instructions / cycles` |
| 缓存命中率低吗？分支预测失败多吗？ | `perf stat -e cache-misses,branch-misses` |
| 上下文切换多不多？在迁移吗？ | `perf stat -e context-switches,migrations` |
| 现在哪个函数最热（免落盘实时）？ | `perf top` |
| 程序做了哪些系统调用，每次多慢？ | `perf trace`（perf 内置，比 strace 低开销） |

## 数据来源

- **来源接口**：内核 `perf_events` 子系统，通过 `perf_event_open(2)` 系统调用开启事件。事件分三类：CPU **硬件 PMU**（cycles/instructions/cache-misses/branch-misses 等，由 CPU 芯片提供）、**软件事件**（时钟、缺页、上下文切换）、**内核 tracepoint**（预埋在内核里的探针）。
- **采集方式**：**采样**而非读统计值——按频率（如 `-F 999`）周期性中断，记录当时的指令指针和调用栈，事后按出现次数聚合出"热点"。
- **由此决定的特性**：① 结果是**统计意义上的热点**，采样频率越高越精确、开销也越大；② 读硬件 PMU 和内核栈需要权限，受 `kernel.perf_event_paranoid` 控制，容器里常需额外 capability；③ 能看到函数/行级信息，前提是二进制带符号（`-g`、别过度优化），否则栈是地址。

> 想深入**原理**——perf_event_open、PMU 采样、mmap 环形缓冲区、数据怎么落盘到 `perf.data`、以及用 libperf 编程——见 [perf-internals.md](/tools/code/perf-internals.md)。

```bash
# 安装
yum install perf                                         # CentOS/RHEL
apt install linux-tools-common linux-tools-$(uname -r)   # Debian/Ubuntu
perf --version
```

> **前提条件**：分析对象要带调试符号（编译加 `-g`），且别用高优化（`-O2` 会内联/消除函数，栈变得难读甚至误导）。本仓库 `make` 默认 `-O0 -g` 就是为此；对比 `make release`（`-O2`）你会看到热循环被优化没了。生产二进制若 strip 过符号，需额外装 debuginfo 包。

## 一、perf record —— 采样并落盘

```bash
perf record -g -p <PID> -- sleep 30       # 采样已运行进程 30 秒（-- sleep N 是常用控时法）
perf record -g ./cpu_demo                 # 从头采样一个新命令的完整生命周期
perf record -g -a -- sleep 10             # -a 采样整机所有 CPU（找不到具体 PID 时）
perf record -F 999 -g -p <PID> -- sleep 30  # -F 采样频率(Hz)，999 避免和 1000Hz 时钟共振
perf record -g --call-graph dwarf -p <PID> -- sleep 10   # 用 DWARF 展开栈（见下方坑）
```

参数：

- `-g`：记录调用栈（否则只知道热点函数，不知道谁调用的）。
- `-F <Hz>`：每秒采样次数，越高越精细但开销越大，常用 99/199/999。
- `-a`：所有 CPU；`-p`：指定进程；`-t`：指定线程 TID。
- 采样数据默认写到当前目录 `perf.data`。

## 二、perf report —— 查看热点

```bash
perf report                    # 交互式 TUI：↑↓ 选函数，回车展开调用链，a 看反汇编
perf report --stdio            # 直接打印到终端（适合复制/脚本）
perf report -n                 # 显示采样次数而非仅百分比
perf report --sort comm,dso,symbol   # 按 进程/动态库/符号 聚合
```

阅读要点：

- `Overhead` 列 = 该函数占总采样的百分比，从高往下看即可。
- **函数名颜色/来源**：属于可执行文件/`.so` 的是**用户态**（cpu_demo 场景1 的累加循环在这里）；属于 `[kernel.kallsyms]` 的是**内核态**（场景2 的 `vfs_write`、`ksys_write`、`__x64_sys_openat` 等 syscall 路径）。
- 回车展开可看**调用链**（谁调用了这个热点），自底向上定位到自己的业务代码。

## 三、火焰图（最直观的可视化）

需要 Brendan Gregg 的 [FlameGraph](https://github.com/brendangregg/FlameGraph) 脚本：

```bash
git clone https://github.com/brendangregg/FlameGraph
perf record -F 999 -g -p <PID> -- sleep 30
perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flame.svg
```

浏览器打开 `flame.svg`：

- **X 轴 = 占比**（横条越宽越热，**不是时间轴**，别按左右理解成时间顺序）。
- **Y 轴 = 调用栈深度**（下面是父调用，上面是子调用，最顶端正在执行）。
- 鼠标点某一格可放大该子树，搜索框可高亮某函数在各处的总占比。

> 变体：`perf script | stackcollapse-perf.pl | flamegraph.pl --color=java` 换配色；差分火焰图（difffolded.pl）可对比优化前后。

## 四、perf top —— 实时热点（免落盘）

```bash
perf top -p <PID>              # 像 top，但列的是热点函数，实时滚动
perf top -g -p <PID>           # 带调用链
perf top -e cache-misses       # 按指定事件排热点
```

适合"现在就卡，想立刻看谁最热"，无需先 record 再 report。

## 五、perf stat —— 整体性能计数（不采样栈，看硬件指标）

```bash
perf stat ./cpu_demo                                  # 跑完打印一份计数汇总
perf stat -p <PID> -- sleep 10                        # 对运行中进程统计 10 秒
perf stat -e cache-misses,branch-misses,cycles,instructions ./cpu_demo
perf stat -d ./cpu_demo                               # -d 更详细（含 cache 层级）
```

关键指标：

| 指标 | 含义与判读 |
|------|-----------|
| `instructions` / `cycles` → `IPC` | 每周期指令数。**IPC 低（<1）= 流水线常停顿**：等内存、分支预测失败、依赖链长 |
| `cache-misses` / `cache-references` | 缓存未命中率高 = 内存访问局部性差（如随机访问大数组），考虑改数据布局 |
| `branch-misses` | 分支预测失败率高 = 分支太随机（如数据无序），排序数据或用无分支写法可改善 |
| `context-switches` | 上下文切换次数，多 = 线程过多/频繁阻塞 |
| `page-faults` | 缺页次数，`major` 缺页触发磁盘 IO |

> 经验：`us` 高但 IPC 也低，说明 CPU 忙在"等"（内存/分支）而非有效计算——这类问题火焰图看着热，真正病根是访存模式，`perf stat` 才看得出。

## 六、perf 可观测指标全景

`perf` 能观测的指标分四大类：**硬件 PMU 事件**、**软件事件**、**内核 Tracepoint 事件**、**专用子命令**。执行 `perf list` 可以列出本机支持的全部事件。

### 6.1 硬件 PMU 事件 —— CPU 微架构

这些事件由 CPU 芯片的性能监控单元（PMU）提供，是理解"代码在 CPU 上真正怎么跑"的核心。不同 CPU 型号支持的事件集不同（Intel/AMD/ARM 各有差异），`perf list` 列出的是当前机器实际可用的。

#### 6.1.1 核心计算指标

| 事件名 | 含义 | 典型判读 |
|--------|------|---------|
| `cycles` | CPU 时钟周期数 | 跟 `task-clock` 结合判断主频是否正常 |
| `instructions` | 已退休指令数 | 跟 `cycles` 算 IPC = `instructions / cycles` |
| IPC | 每周期指令数（派生值） | **IPC >2 优秀，1~2 正常，<0.7 说明 CPU 经常在等** |
| `ref-cycles` | 不受降频影响的参考周期 | 跟 `cycles` 对比判断是否降频（`cycles < ref-cycles` = 降频了） |

#### 6.1.2 流水线停顿（Stall）分析

| 事件名 | 含义 | 判读 |
|--------|------|------|
| `stalled-cycles-frontend` | 前端停顿周期（取指不够快） | 高 → I-cache miss / 分支预测失败 / 指令 TLB miss |
| `stalled-cycles-backend` | 后端停顿周期（执行单元等数据） | 高 → D-cache miss / 内存延迟 / 依赖链长 |
| `stalled-cycles-frontend` / `cycles` | 前端停顿占比 | >30% → 取指瓶颈，改代码布局或减少间接跳转 |
| `stalled-cycles-backend` / `cycles` | 后端停顿占比 | >30% → 数据瓶颈，改数据布局或预取 |

> **区分前端 vs 后端**是性能调优的关键分叉点：前端 = 喂不饱流水线（指令侧），后端 = 流水线满了但等数据（数据侧）。

#### 6.1.3 分支预测

| 事件名 | 含义 | 判读 |
|--------|------|------|
| `branch-instructions` | 分支指令总数 | 配合 `branch-misses` 算失败率 |
| `branch-misses` | 分支预测失败次数 | **失败率 >5% 说明分支模式不规律**：数据无序导致的分支、多态/虚函数太多 |
| `branch-loads` / `branch-load-misses` | 间接分支（如虚函数调用）的预测 | 面向对象代码中多态调用频繁时关注 |
| `branch-misses / branch-instructions` | 分支预测失败率（派生值） | **<1% 优秀，1~3% 正常，>5% 可优化** |

#### 6.1.4 缓存层级

| 事件名 | 含义 | 判读 |
|--------|------|------|
| `cache-references` | 所有缓存访问次数 | 配合 `cache-misses` 算命中率 |
| `cache-misses` | 最后一级缓存（LLC）未命中 | **命中率 <90% → 数据布局或访问模式有问题** |
| `L1-dcache-loads` / `L1-dcache-load-misses` | L1 数据缓存加载/未命中 | L1 miss 率高 → 数据步长太大或不连续 |
| `L1-dcache-stores` / `L1-dcache-store-misses` | L1 数据缓存存储/未命中 | 写操作频繁时关注 |
| `L1-icache-loads` / `L1-icache-load-misses` | L1 指令缓存加载/未命中 | I-cache miss 高 → 代码段太大或跳转太散 |
| `LLC-loads` / `LLC-load-misses` | 最后一级缓存加载/未命中 | LLC miss = 必须访问 DRAM，延迟 ~100-300 cycles |
| `LLC-stores` / `LLC-store-misses` | 最后一级缓存存储/未命中 | 写穿透场景 |

#### 6.1.5 TLB（地址翻译缓存）

| 事件名 | 含义 | 判读 |
|--------|------|------|
| `dTLB-loads` / `dTLB-load-misses` | 数据 TLB 加载/未命中 | 高 → 访问的内存页太散，考虑大页（HugePage） |
| `iTLB-loads` / `iTLB-load-misses` | 指令 TLB 加载/未命中 | 高 → 代码段碎片化严重 |
| `dTLB-stores` / `dTLB-store-misses` | 数据 TLB 存储/未命中 | 写密集且页多时关注 |

> **TLB miss 的真实代价**：一次 TLB miss 需要硬件 page walk 遍历页表（4 级页表 = 4 次内存访问），比 cache miss 更贵。频繁 TLB miss 是 IPC 被拉低的常见元凶之一。

### 6.2 软件事件 —— 内核直接计数

不依赖 CPU PMU，由内核直接计数。无需 `-e` 时 `perf stat` 默认输出其中一部分。

| 事件名 | 含义 | 判读 |
|--------|------|------|
| `task-clock` | 进程实际占用 CPU 的累计时间（ms） | 单核跑满 1 秒 = 1000；多线程可 > 墙钟时间 |
| `context-switches` | 上下文切换次数（自愿+非自愿，全线程合计） | 单线程 >10,000/秒偏高，>50,000/秒严重；多线程需按核折算 |
| `cpu-migrations` | 线程从一个核迁移到另一个核的次数 | 高 → 考虑绑核或 `taskset` |
| `page-faults` | 缺页次数（`minor-faults` + `major-faults`） | 即 minor faults，通常无害 |
| `minor-faults` | 轻微缺页（页已在内存，只需建页表映射） | 正常操作（如 mmap 首次访问），数量可能很大 |
| `major-faults` | 严重缺页（需要从磁盘读取） | >0 = 访问了被 swap 出去的页或 mmap 文件未预读 |
| `alignment-faults` | 非对齐访问异常次数 | ARM/PowerPC 上常见，x86 上极罕见（硬件自动处理） |
| `emulation-faults` | 模拟未实现指令的次数 | 极罕见，虚拟化或特殊指令场景 |
| `cpu-clock` | 进程的 CPU 时钟累计（纳秒） | 类似 `task-clock` 但精度更高 |
| `dummy` | 占位事件（不实际计数） | 用于 `perf record` 的定时采样，不消耗 PMU 硬件计数器 |

### 6.3 内核 Tracepoint 事件 —— 精准捕获内核行为

Tracepoint 是 Linux 内核中预埋的静态探针，覆盖调度、系统调用、磁盘 IO、网络、内存管理等全部子系统。用 `perf list tracepoint` 查看全部；用 `perf record -e <tracepoint>` 采样。

#### 6.3.1 调度类（`sched:*`）

| Tracepoint | 触发时机 | 能看什么 |
|------------|---------|---------|
| `sched:sched_switch` | 每次上下文切换 | 哪个 task 被切走、切给谁、调用栈 |
| `sched:sched_wakeup` | 进程被唤醒 | 谁唤醒了谁、唤醒延迟 |
| `sched:sched_wakeup_new` | 新创建的进程首次被唤醒 | `fork` + `exec` 后的第一次调度 |
| `sched:sched_migrate_task` | 任务跨核迁移 | 从哪个 CPU 迁到哪个 CPU |
| `sched:sched_process_exec` | `exec()` 调用 | 进程启动新程序 |
| `sched:sched_process_fork` | `fork()` 调用 | 进程创建子进程 |
| `sched:sched_process_exit` | 进程退出 | 进程结束 |
| `sched:sched_process_wait` | 父进程 `wait()` | 等待子进程 |
| `sched:sched_stat_runtime` | 调度统计 | 每个 task 的累计运行时间 |
| `sched:sched_stat_wait` | 等待统计 | task 在就绪队列等待的累计时间 |
| `sched:sched_stat_sleep` | 睡眠统计 | task 睡眠的累计时间 |
| `sched:sched_stat_iowait` | IO 等待统计 | task 等待 IO 的时间 |

#### 6.3.2 系统调用类（`syscalls:*`）

| Tracepoint | 触发时机 | 能看什么 |
|------------|---------|---------|
| `syscalls:sys_enter_<name>` | 进入某个系统调用 | 参数、调用方 |
| `syscalls:sys_exit_<name>` | 退出某个系统调用 | 返回值、耗时 |

> 常用：`sys_enter_futex`（锁争用）、`sys_enter_read`/`sys_enter_write`（IO 热点）、`sys_enter_accept`/`sys_enter_connect`（网络连接）。

#### 6.3.3 块设备 IO（`block:*`）

| Tracepoint | 触发时机 | 能看什么 |
|------------|---------|---------|
| `block:block_rq_issue` | IO 请求发送到设备 | 哪个进程发起的 IO、扇区位置、大小 |
| `block:block_rq_complete` | IO 请求完成 | 一次 IO 的完成事件 |
| `block:block_bio_queue` | bio 进入队列 | IO 排队开始时间 |
| `block:block_bio_remap` | bio 被重映射 | DM/md 等设备映射层的转发 |

> 结合 `block_rq_issue` 和 `block_rq_complete` 的时间差，可以算出**单次 IO 的端到端延迟**。

#### 6.3.4 内存管理（`kmem:*`）

| Tracepoint | 触发时机 | 能看什么 |
|------------|---------|---------|
| `kmem:kmalloc` / `kmem:kfree` | 内核内存分配/释放 | 内核 slab 分配行为 |
| `kmem:mm_page_alloc` / `kmem:mm_page_free` | 页分配/释放 | buddy allocator 行为 |
| `kmem:mm_page_pcpu_drain` | per-CPU 页缓存回收 | 内存回收触发频率 |

#### 6.3.5 其他常用 Tracepoint

| 类别 | 关键 Tracepoint | 用途 |
|------|----------------|------|
| 中断 | `irq:irq_handler_entry` / `irq:irq_handler_exit` | 哪个中断处理函数耗时多久 |
| 软中断 | `irq:softirq_entry` / `irq:softirq_exit` | `NET_RX` / `NET_TX` / `TIMER` 等软中断行为 |
| 网络 | `net:net_dev_xmit` / `net:napi_gro_receive_entry` | 网络包收发 |
| 信号 | `signal:signal_generate` / `signal:signal_deliver` | 信号发送和递送 |
| 文件系统 | `ext4:ext4_da_write_begin` / `ext4:ext4_da_write_end` | 写操作全路径 |
| 写回 | `writeback:writeback_dirty_page` / `writeback:writeback_written` | 脏页产生到刷盘 |
| 定时器 | `timer:timer_start` / `timer:timer_expire_entry` | 内核定时器触发 |

### 6.4 专用子命令 —— 各司其职

`perf` 不只是 `stat` + `record` + `report`。以下是完整子命令清单：

| 子命令 | 作用 | 一句话说明 |
|--------|------|-----------|
| `perf stat` | 性能计数器汇总 | "这项指标是多少"，不采样调用栈 |
| `perf record` + `perf report` | 采样调用栈 + 热点分析 | "谁在烧 CPU"，最常见的分析路径 |
| `perf top` | 实时热点（不落盘） | "现在卡住了，立刻看什么函数最热" |
| `perf sched` | 调度延迟/切换分析 | "哪个线程在等调度，等了多久" |
| `perf lock` | 锁争用分析 | "哪个锁竞争最激烈" |
| `perf mem` | 内存访问延迟定位 | "哪条指令的内存访问最慢" |
| `perf trace` | 系统调用跟踪 | 比 `strace` 开销低的 syscall 追踪（基于 perf） |
| `perf probe` | 动态插桩 | 在运行中的内核或用户程序任意位置插入探针 |
| `perf list` | 列出所有可用事件 | 看看这台机器能采哪些指标（硬件/软件/tracepoint） |
| `perf script` | 原始采样数据导出 | 导出成文本，供自定义分析或生成火焰图 |
| `perf kvm` | 虚拟机性能分析 | KVM guest/host 的性能事件 |
| `perf bench` | 微基准测试 | 测 `memcpy`/`pipe`/`futex`/`sched` 等基础操作速度 |
| `perf test` | 自检 | 检查 perf 功能是否正常 |
| `perf diff` | 两份 `perf.data` 对比 | 优化前后的差异量化 |
| `perf archive` | 打包 `perf.data` + 符号 | 把采样数据搬到另一台机器分析 |
| `perf c2c` | **False Sharing 检测** | Cache line 伪共享分析（多线程性能杀手之一） |
| `perf buildid-cache` | 管理 build-id 缓存 | 管理符号文件的缓存 |

### 6.5 常用组合速查

| 我想知道... | 用这个 |
|------------|--------|
| 进程总体硬件效率 | `perf stat -d -p <PID> -- sleep 10` |
| 是取指瓶颈还是数据瓶颈 | `perf stat -e stalled-cycles-frontend,stalled-cycles-backend -p <PID> -- sleep 5` |
| 缓存命中率 | `perf stat -e cache-references,cache-misses -p <PID> -- sleep 5` |
| 线程各自的切换/效率 | `perf stat --per-thread -p <PID> -- sleep 3` |
| 自愿 vs 非自愿切换 | `pidstat -w 1 -p <PID>`（`perf stat` 不区分，需借助 `/proc` 或 `pidstat`） |
| 缺页分 minor/major | `perf stat -e minor-faults,major-faults -p <PID> -- sleep 5` |
| 降频了吗 | `perf stat -e cycles,ref-cycles -p <PID> -- sleep 5`（`cycles < ref-cycles` = 降频） |
| 每个 syscall 的开销 | `perf trace -p <PID>` 或 `perf trace -s` |
| 锁争用 | `perf lock record -p <PID> -- sleep 5 && perf lock report` |
| 调度延迟 | `perf sched record -p <PID> -- sleep 5 && perf sched latency` |
| False Sharing | `perf c2c record -p <PID> -- sleep 5 && perf c2c report` |
| 内存访问延迟 | `perf mem record -p <PID> -- sleep 5 && perf mem report` |

## 七、常见坑与排错

- **栈只有一层 / 一堆 `[unknown]`**：
  - 没编符号或被 strip → 编译带 `-g`，或装对应 debuginfo。
  - 编译器省略了栈帧指针（`-O2` 默认）→ 用 `--call-graph dwarf`（靠 DWARF 调试信息展开，开销大但准），或重编加 `-fno-omit-frame-pointer`。
  - 新硬件可用 `--call-graph lbr`（用 CPU 的 LBR 硬件栈，开销小）。
- **权限不足 / 采不到内核栈**：perf 需要 root，或调低门槛 `sysctl -w kernel.perf_event_paranoid=1`（-1 最宽松）；看内核符号还需 `sysctl -w kernel.kptr_restrict=0`。
- **容器里用不了**：需 `--privileged` 或 `--cap-add SYS_ADMIN`，且容器内要有 perf 和符号。
- **`-O2` 下火焰图对不上源码**：函数被内联，栈里看不到你写的函数名——这不是 bug，是优化的正常结果（正好用本仓库 `make release` 对比体会）。
- **采样频率太高**：`-F` 设很大在高负载机上，perf 自身开销和数据量都暴涨；一般 99~999 足够。

## 八、常见排查方法与分析

### 8.1 CPU 高 → 定位热点函数

```bash
top → 确定高 CPU 进程 PID
  → perf record -g -p <PID> -- sleep 30
    → perf report → 看 Overhead 最高的函数
      → 用户态函数 → 看调用链，定位业务代码
      → 内核态函数 → syscall 路径，用 strace -c 辅助
```

### 8.2 CPU 利用率高但 IPC 低 → 假忙

```bash
perf stat -p <PID> -- sleep 10 → IPC < 0.5
  → CPU 在频繁 stall（等内存/分支预测失败）
    → perf stat -e stalled-cycles-frontend,stalled-cycles-backend -p <PID> -- sleep 5
      → frontend 高 → 指令获取瓶颈
      → backend 高 → 数据/内存瓶颈
    → perf stat -e cache-misses,cache-references -p <PID> -- sleep 5
      → cache miss 率高 → 改数据布局、减少随机访问
```

### 8.3 内核态 CPU 高 → 系统调用热路径

```bash
perf record -g -p <PID> -- sleep 30
  → perf report → 看 [kernel.kallsyms] 函数
    → 结合 strace -c -p <PID> → 确认哪个 syscall
      → 若是锁相关 → perf lock record + perf lock report
      → 若是内存分配 → 减少 malloc 或使用内存池
```

### 8.4 上下文切换频繁 → 调度开销

```bash
perf stat -e context-switches,migrations -p <PID> -- sleep 5
  → 切换多 + pidstat -w → 区分自愿/非自愿
    → perf sched record -- sleep 5
    → perf sched latency → 看每个 task 的调度延迟
```

> **真实案例解读**：一份 `perf stat -p 58931 -- sleep 3` 的数据（75,200 次/秒切换、IPC 3.41、page-faults 仅 2）的完整分析，见 [context-switch.md §5.7-B](/concepts/process/context-switch.md#57-b-实战一份-perf-stat-数据的完整解读)。该案例展示了"CPU 微架构指标很好，但切换极高"时如何判断根因。

### 8.5 进程启动慢 → 缺页分析

```bash
perf stat -e page-faults,major-faults -p <PID> -- sleep 5
  → major-faults 持续非零 → 启动阶段大量 mmap 缺页
    → 减少启动时的 lazy 内存访问，或预热
```

## 九、交叉引用

- **整机 → 进程**：[top](/tools/cpu/top.md) → [pidstat](/tools/cpu/pidstat.md)（确定哪个进程需要 perf 分析）
- **系统调用**：[strace](/tools/code/strace.md)（sy 高时辅助定位 syscall）
- **调度分析**：[scheduling-observation](/tools/cpu/scheduling-observation.md)（perf sched 深度分析）
- **微架构**：[cpu-microarch-overview](/concepts/microarch/cpu-microarch-overview.md)（IPC/cache-miss/branch-miss 的硬件含义）
- **原理深入**：[perf-internals](/tools/code/perf-internals.md)（perf_events 内核机制）

## 十、其他实用子命令

```bash
perf trace -p <PID>            # 类似 strace 但基于 perf，开销更低，看系统调用
perf record -e block:block_rq_issue -a -- sleep 5   # 按内核 tracepoint 事件采样（这里是块 IO 下发）
perf list                      # 列出本机支持的所有事件（硬件/软件/tracepoint）
perf sched record -- sleep 5; perf sched latency     # 调度延迟分析
```

