📋 本章摘要
在第十九章中,我们掌握了 ftrace 的核心用法,通过直接操作 /sys/kernel/debug/tracing 接口完成了 kworker 高负载和软中断风暴的排查。但手工操作 ftrace 存在明显的痛点:
- 操作繁琐:需要手动 echo 到多个文件,容易出错
- 数据管理难:trace 数据无法持久化保存和分享
- 分析低效:纯文本输出难以发现时间维度的关联性
- 多核分析难:8 核 CPU 的并发事件难以可视化对比
本章将引入两个强大的工具:
- trace-cmd:ftrace 的命令行封装,一条命令完成记录、分析、导出
- kernelshark:图形化时间线分析工具,可视化多核、多事件的关联关系
我们将通过两个实战案例(网络中断风暴导致 CPU 调度冲突 和 跨核心延迟传播分析),演示如何使用 trace-cmd + kernelshark 快速定位复杂的多核性能问题。
🚀 trace-cmd:ftrace 的瑞士军刀
1. 为什么需要 trace-cmd
手工 ftrace 的痛点:
bash
# 手工操作需要 10+ 步
cd /sys/kernel/debug/tracing
echo 0 > tracing_on
echo nop > current_tracer
echo > trace
echo function_graph > current_tracer
echo do_sys_open > set_graph_function
echo 1234 > set_ftrace_pid
echo 1 > events/sched/sched_switch/enable
echo 1 > tracing_on
sleep 10
echo 0 > tracing_on
cat trace > /tmp/output.txt
trace-cmd 一行搞定:
bash
trace-cmd record -p function_graph -g do_sys_open -P 1234 -e sched:sched_switch sleep 10
trace-cmd report > /tmp/output.txt
2. trace-cmd vs 手工 ftrace
| 特性 | 手工 ftrace | trace-cmd |
|---|---|---|
| 命令复杂度 | 10+ 步骤 | 1 条命令 |
| 数据持久化 | 手动重定向 | 自动保存 trace.dat |
| 数据分享 | 难以移植 | 二进制格式,可跨机器分析 |
| 多核分析 | 手动区分 CPU 列 | 自动标记 CPU 信息 |
| 图形化分析 | 不支持 | 集成 kernelshark |
| 插件扩展 | 不支持 | 支持 Python 插件 |
| 学习成本 | 高(需理解 ftrace 接口) | 低(类似 perf 命令) |
3. 安装 trace-cmd
bash
# Ubuntu/Debian
sudo apt install trace-cmd kernelshark
# RHEL/CentOS
sudo yum install trace-cmd kernelshark
# 验证安装
trace-cmd --version
# 输出:trace-cmd version 3.1.2
kernelshark --version
# 输出:KernelShark version 2.2.0
⚙️ trace-cmd 基础使用
1. 核心命令
bash
trace-cmd record # 记录 trace 数据(生成 trace.dat)
trace-cmd report # 解析 trace.dat 并输出文本
trace-cmd show # 查看当前 ftrace 状态
trace-cmd reset # 重置 ftrace 配置
trace-cmd list # 列出可用的 tracers/events
trace-cmd stat # 显示 ftrace 统计信息
2. record 命令详解
bash
# 基本语法
trace-cmd record [选项] [命令]
# 常用选项
-p <tracer> # 指定 tracer(function/function_graph/nop)
-e <event> # 启用 event(支持通配符)
-P <pid> # 过滤 PID
-g <function> # function_graph 的函数过滤
-l <function> # function tracer 的函数过滤
-F # 过滤当前命令的进程(自动设置 PID)
-O <option> # 设置 trace_options
-b <size> # 缓冲区大小(KB)
-o <file> # 输出文件名(默认 trace.dat)
-d # 实时输出(不保存文件)
-s <usecs> # 采样间隔(微秒)
3. 实战示例
示例 1:追踪进程的系统调用
bash
# 追踪 ls 命令的所有系统调用
trace-cmd record -e 'syscalls:*' ls /tmp
# 查看结果
trace-cmd report | less
示例 2:追踪特定函数的调用链
bash
# 追踪 vfs_read 的调用链
trace-cmd record -p function_graph -g vfs_read cat /etc/hosts
# 查看结果
trace-cmd report
示例 3:追踪运行中进程的调度行为
bash
# 追踪 PID 1234 的调度事件 10 秒
trace-cmd record -e sched:sched_switch -e sched:sched_wakeup -P 1234 sleep 10
# 查看结果
trace-cmd report | grep -E "perception|sched_switch"
示例 4:自定义输出文件
bash
# 保存到指定文件
trace-cmd record -o network_trace.dat -e net:* -e irq:* sleep 5
# 分析
trace-cmd report -i network_trace.dat
🖥️ kernelshark:时间线可视化分析
1. kernelshark 界面概览
┌─────────────────────────────────────────────────────────────────┐
│ 文件 编辑 过滤 工具 帮助 │
├─────────────────────────────────────────────────────────────────┤
│ ⏵ CPU 0 ████▓▓░░████▓▓░░████▓▓░░ sched_switch, irq_handler │
│ ⏵ CPU 1 ░░████▓▓░░████▓▓░░████▓ sched_switch, kworker │
│ ⏵ CPU 2 ▓▓░░████▓▓░░████▓▓░░██ sched_wakeup, net_rx │
│ ⏵ CPU 3 ████░░▓▓████░░▓▓████░░ softirq_entry, tcp_v4_rcv │
├─────────────────────────────────────────────────────────────────┤
│ 时间轴: |-------|-------|-------|-------|-------| │
│ 1234.0 1234.5 1235.0 1235.5 1236.0 │
├─────────────────────────────────────────────────────────────────┤
│ Event 详情: │
│ CPU: 2 Time: 1234.567890 │
│ Event: net:netif_receive_skb │
│ dev=eth0 len=1400 protocol=IP │
└─────────────────────────────────────────────────────────────────┘
2. 核心功能
| 功能 | 说明 | 快捷键 |
|---|---|---|
| 缩放 | 放大/缩小时间轴 | 滚轮 / +/- |
| 平移 | 左右移动时间轴 | 拖拽 / ←→ |
| 过滤 | 显示/隐藏特定 event | Ctrl+F |
| 搜索 | 查找特定事件 | Ctrl+S |
| 标记 | 标记感兴趣的时间点 | M |
| 测量 | 测量两点间的时间差 | 右键选择 |
| CPU 过滤 | 只显示特定 CPU | 点击 CPU 行 |
| 导出 | 导出选中区域 | Ctrl+E |
3. 启动 kernelshark
bash
# 分析 trace.dat
kernelshark trace.dat
# 或者直接从 trace-cmd 启动
trace-cmd record -e 'sched:*' -e 'irq:*' sleep 5
kernelshark trace.dat
🧪 实战案例 1:网络中断风暴导致 CPU 调度冲突
1. 问题背景
自动驾驶感知系统在处理多路摄像头数据时,出现间歇性延迟峰值(正常 20ms,峰值 150ms)。通过 mpstat 观察到 CPU 3 的软中断占用突然飙升,怀疑网络中断影响了感知进程的调度。
mpstat -P ALL 1 输出:
CPU %usr %sys %irq %soft %idle
0 25.3 4.2 0.5 2.1 68.0
1 23.8 5.1 0.4 1.8 69.0
2 78.5 8.2 0.3 1.5 11.5 ← perception 进程
3 12.3 3.5 2.8 65.4 16.0 ← 软中断异常!
2. 使用 trace-cmd 记录
bash
# 记录 10 秒的调度、中断、网络事件
trace-cmd record \
-e sched:sched_switch \
-e sched:sched_wakeup \
-e irq:irq_handler_entry \
-e irq:irq_handler_exit \
-e irq:softirq_entry \
-e irq:softirq_exit \
-e net:netif_receive_skb \
-b 20480 \
sleep 10
# 生成 trace.dat(约 50MB)
ls -lh trace.dat
# 输出:-rw-r--r-- 1 user user 52M Feb 13 14:23 trace.dat
3. 使用 kernelshark 可视化分析
步骤 1:打开 trace.dat
bash
kernelshark trace.dat
步骤 2:观察 CPU 时间线
在 kernelshark 中可以清晰看到:
- CPU 2(perception 进程):持续运行,偶尔被中断
- CPU 3 :大量连续的
softirq_entry(NET_RX)事件 - 关键发现:每当 CPU 3 的软中断密集时,CPU 2 的 perception 进程就被调度出去
时间线分析:
时间: 1234.500s - 1234.550s(50ms 窗口)
CPU 2: [perception]────────[idle]──[perception]──[idle]──[perception]
^20ms ^中断 ^5ms ^中断 ^8ms
CPU 3: [ksoftirqd]████████████████████████████████████
softirq softirq softirq softirq (连续 NET_RX)
步骤 3:测量延迟
- 在 kernelshark 中标记 perception 进程被抢占的时间点(1234.520s)
- 标记 perception 进程恢复运行的时间点(1234.545s)
- 时间差:25ms(正好对应延迟峰值!)
4. report 分析确认
bash
# 统计 CPU 3 的软中断频率
trace-cmd report | grep 'CPU 3.*softirq_entry' | wc -l
# 输出:145672 (10 秒内 145,672 次 = 14,567 次/秒)
# 查看网络包接收频率
trace-cmd report | grep 'netif_receive_skb' | grep 'CPU 3' | wc -l
# 输出:145672 (与软中断一致)
# 查看包大小
trace-cmd report | grep netif_receive_skb | head -5
输出:
ksoftirqd/3-25 [003] 1234.567: netif_receive_skb: dev=eth1 len=66
ksoftirqd/3-25 [003] 1234.568: netif_receive_skb: dev=eth1 len=66
ksoftirqd/3-25 [003] 1234.569: netif_receive_skb: dev=eth1 len=66
根因确认:
- 网卡 eth1(某个摄像头)以 ~15k 包/秒的速率发送 66 字节小包
- 中断全部分配到 CPU 3,导致软中断占用 65%
- CPU 3 调度器无法及时调度其他进程,影响整体系统响应
5. 解决方案
bash
# 方案 1:分散网卡中断到多个 CPU
cat /proc/interrupts | grep eth1
# 输出:128: 12345678 ... eth1-TxRx-0
# 将 eth1 中断分散到 CPU 0-2
echo 07 > /proc/irq/128/smp_affinity # 二进制 0111
# 方案 2:启用 RPS(Receive Packet Steering)
echo f > /sys/class/net/eth1/queues/rx-0/rps_cpus # 使用 CPU 0-3
效果验证:
bash
# 再次记录 10 秒
trace-cmd record -e 'sched:*' -e 'irq:*' sleep 10
# 用 kernelshark 对比
kernelshark trace.dat
优化后时间线:
CPU 0: [ksoftirqd]██░░██░░ (25% softirq)
CPU 1: [ksoftirqd]██░░██░░ (25% softirq)
CPU 2: [perception]████████ (不再被中断)
CPU 3: [ksoftirqd]██░░██░░ (25% softirq)
性能对比:
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| CPU 3 软中断 | 65% | 16% | ↓ 75% |
| perception P99 延迟 | 150ms | 25ms | ↓ 83% |
| 调度等待时间 | 25ms | 1.2ms | ↓ 95% |
🔄 实战案例 2:跨核心延迟传播分析
1. 问题场景
数据融合模块(data_fusion)在 CPU 0 处理完数据后,唤醒 CPU 2 上的决策模块(planning),但 planning 的唤醒延迟从预期的 1ms 飙升到 15ms。
2. 记录唤醒路径
bash
# 记录调度和 IPI(核间中断)事件
trace-cmd record \
-e sched:sched_switch \
-e sched:sched_wakeup \
-e sched:sched_waking \
-e irq:irq_handler_entry \
-e irq:irq_handler_exit \
-O stacktrace \
sleep 5
3. kernelshark 时间线分析
关键观察:
- 1234.500s :CPU 0 的 data_fusion 调用
sched_waking(唤醒 planning) - 1234.502s :CPU 0 发送 IPI 到 CPU 2(
irq_handler_entry: reschedule) - 1234.515s :CPU 2 的
sched_switch才切换到 planning - 延迟 = 15ms(预期应该 < 2ms)
问题定位:
- CPU 2 在 1234.502s - 1234.515s 期间运行什么?
- kernelshark 显示:CPU 2 正在运行低优先级的
background_worker(13 秒连续运行) - planning 是高优先级进程(nice=-10),但仍等待了 13ms
根因 :background_worker 关闭了抢占(可能持有自旋锁)
4. 验证抢占关闭
bash
# 使用 function_graph 追踪 CPU 2
trace-cmd record \
-p function_graph \
-P $(pgrep background_worker) \
-g '*lock*' \
sleep 5
trace-cmd report | grep -A 10 "spin_lock"
发现:
background_worker-5678 [002] 1234.502: spin_lock() {
_raw_spin_lock();
critical_section() {
expensive_computation() {
... (13ms)
}
}
_raw_spin_unlock();
}
5. 解决方案
c
// 原代码(持锁时间过长)
spin_lock(&data_lock);
expensive_computation(); // 13ms!
spin_unlock(&data_lock);
// 优化后
local_data = copy_data_under_lock(); // 0.1ms
expensive_computation(local_data); // 无锁执行
update_result_under_lock(); // 0.1ms
效果 :唤醒延迟从 15ms 降至 1.2ms
📊 高级功能:多事件关联分析
1. 使用 trace-cmd 的过滤器
bash
# 只显示特定进程的事件
trace-cmd report -F 'common_pid == 1234'
# 只显示特定 CPU
trace-cmd report -F 'common_cpu == 2'
# 组合条件
trace-cmd report -F 'common_pid == 1234 && common_cpu == 2'
# 只显示特定时间范围
trace-cmd report -S 1234.5 -E 1235.0
2. kernelshark 的高级过滤
过滤器语法:
# 显示所有调度事件
sched:*
# 显示 CPU 2 的所有事件
cpu == 2
# 显示特定进程
comm ~ "perception"
# 组合条件
(comm ~ "perception" || comm ~ "planning") && cpu == 2
3. 导出分析结果
bash
# 导出为 CSV(用于 Python 分析)
trace-cmd report -F 'cpu == 3' -O csv > cpu3_events.csv
# Python 分析脚本
import pandas as pd
df = pd.read_csv('cpu3_events.csv')
print(df.groupby('event')['duration'].agg(['count', 'mean', 'max']))
✅ 最佳实践
1. trace-cmd 使用建议
bash
# (1) 控制缓冲区大小(避免内存不足)
trace-cmd record -b 10240 ... # 10MB per CPU
# (2) 使用事件过滤减少数据量
trace-cmd record -e 'sched:sched_switch' -F 'prev_pid == 1234 || next_pid == 1234' ...
# (3) 限制追踪时间
trace-cmd record ... sleep 10 # 最多 10 秒
# (4) 实时查看(不保存文件)
trace-cmd record -d -e 'net:*' | head -100
# (5) 压缩输出文件
trace-cmd record -o trace.dat ... && gzip trace.dat
2. kernelshark 分析技巧
| 任务 | 操作 |
|---|---|
| 找到延迟峰值 | 搜索 sched_switch,查看 prev_state == D |
| 测量中断处理时间 | 标记 irq_handler_entry 和 irq_handler_exit,查看时间差 |
| 对比优化效果 | 加载两个 trace.dat,使用 "Compare" 功能 |
| 导出关键区域 | 选中时间范围,右键 "Export Selection" |
| 查看调用栈 | 记录时加 -O stacktrace,双击事件查看 |
3. 性能开销
| 配置 | trace.dat 大小(10秒) | CPU 开销 |
|---|---|---|
-e sched:* |
~5 MB | < 1% |
-e 'sched:*' -e 'irq:*' |
~20 MB | 2-3% |
-e 'sched:*' -e 'irq:*' -e 'net:*' |
~50 MB | 5-8% |
-p function -l 'vfs_*' |
~100 MB | 10-15% |
-p function_graph |
> 500 MB | 30-50% |
🎯 小结
核心要点
- ✅ trace-cmd 大幅简化 ftrace 操作,一条命令完成记录和分析
- ✅ kernelshark 提供时间线可视化,快速发现多核、多事件的关联性
- ✅ 多核分析 是 kernelshark 的杀手级功能,瞬间定位跨核心问题
- ✅ 数据持久化 trace.dat 格式便于分享和离线分析
- ✅ 结合使用 trace-cmd + kernelshark + 手工 ftrace,形成完整工具链
典型分析流程
否
是
发现性能问题
trace-cmd 记录
相关 events
kernelshark
可视化分析
定位根因?
调整 events 范围
重新记录
验证假设
实施优化
trace-cmd 对比
优化效果
工具选择
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 单核热点分析 | perf | 采样开销低 |
| 多核调度分析 | trace-cmd + kernelshark | 可视化时间线 |
| 中断延迟分析 | trace-cmd + kernelshark | 清晰显示中断处理流程 |
| 函数调用链分析 | trace-cmd function_graph | 完整调用树 |
| 实时监控 | ftrace(手工) | 灵活性高 |
| 生产环境 | eBPF + bpftrace | 开销最低 |
常用命令速查
bash
# === 记录常见场景 ===
# 调度分析
trace-cmd record -e 'sched:*' sleep 10
# 中断分析
trace-cmd record -e 'irq:*' -e 'softirq:*' sleep 10
# 网络分析
trace-cmd record -e 'net:*' -e 'irq:*' sleep 10
# 块 IO 分析
trace-cmd record -e 'block:*' sleep 10
# 特定进程
trace-cmd record -e 'sched:*' -P $(pgrep perception) sleep 10
# 函数调用链
trace-cmd record -p function_graph -g vfs_read cat /etc/hosts
# === 分析与查看 ===
trace-cmd report # 文本输出
trace-cmd report | less # 分页查看
trace-cmd report -F 'cpu == 2' # 过滤 CPU
kernelshark trace.dat # 图形化分析
# === 管理 ===
trace-cmd reset # 重置 ftrace
trace-cmd show # 查看当前配置
trace-cmd list -e # 列出所有 events
📖 系列封面

Dive Deep into System Optimization - 从基础观测到高级优化,从 CPU 到存储,从内核到用户态,深入 Linux 性能分析的每一个角落。
下一章预告 :第 21 篇:系统调用阻塞分析与高并发优化
我们将深入分析阻塞 IO、非阻塞 IO 和异步 IO 的性能差异,学习 select、poll、epoll 的原理与优化技巧,并通过实战案例解决高并发场景下的 load 高但 CPU 空闲的问题。