在上一篇文章中,我们通过直接操作 接口,成功排查了 kworker 高负载和软中断风暴问题。然而,纯手工操作 ftrace 的体验并不友好:频繁的 echo 命令容易出错,海量的文本日志难以分析,更别提在多核环境下进行时间维度的对比了。本文将介绍两个强大的工具——trace-cmd 和 kernelshark,它们将彻底改变你分析 Linux 内核性能问题的方式。/sys/kernel/debug/tracing
为什么是 trace-cmd?
trace-cmd 本质上是 ftrace 的命令行封装,它解决了手工操作的一系列痛点。想象一下,你正在排查一个棘手的性能问题,需要同时设置多个追踪点并捕获数据。使用传统方式,你需要在不同的文件系统路径间来回切换,而 trace-cmd 只需一条命令即可完成全部操作。
手工 ftrace 的典型操作流程如下:
# 手工操作需要 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,一切变得异常简洁:
trace-cmd record -p function_graph -g do_sys_open -P 1234 -e sched:sched_switch sleep 10
trace-cmd report > /tmp/output.txt
下表直观对比了两者的差异:
| 特性 | 手工 ftrace | trace-cmd |
|---|---|---|
| 命令复杂度 | 10+ 步骤 | 1 条命令 |
| 数据持久化 | 手动重定向 | 自动保存 trace.dat |
| 数据分享 | 难以移植 | 二进制格式,可跨机器分析 |
| 多核分析 | 手动区分 CPU 列 | 自动标记 CPU 信息 |
| 图形化分析 | 不支持 | 集成 kernelshark |
| 插件扩展 | 不支持 | 支持 Python 插件 |
| 学习成本 | 高(需理解 ftrace 接口) | 低(类似 perf 命令) |
安装 trace-cmd 非常简单,在大多数 Linux 发行版上都可以通过包管理器直接安装:
# 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 不仅简化了操作,更重要的是它提供了数据持久化能力。你可以将追踪结果保存为 trace.dat 文件,方便后续分析或分享给团队成员。
⚙️ trace-cmd 基础实战
掌握 trace-cmd 的核心命令是高效分析的第一步。它提供了类似 Git 的子命令风格,每个子命令都有明确的职责。例如,record 用于记录追踪数据,report 用于读取和分析数据。
核心命令一览:
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 统计信息
record 命令详解:这是最常用的子命令,它允许你指定要追踪的事件、进程和 CPU 核心。通过灵活的 -e 参数,你可以精确控制追踪范围,避免产生过多无关数据。
在 record 命令中,你还可以利用 -P 参数指定追踪特定进程,或者使用 -C 参数指定 CPU 核心,从而精准定位问题域。
# 基本语法
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> # 采样间隔(微秒)
让我们通过几个实战示例来加深理解。假设你是一名 Java 开发者,正在排查一个高并发服务的性能瓶颈,你可能会这样使用 trace-cmd:
示例 1:追踪进程的系统调用
# 追踪 ls 命令的所有系统调用
trace-cmd record -e 'syscalls:*' ls /tmp
# 查看结果
trace-cmd report | less
示例 2:追踪特定函数的调用链(这在分析 C++ 或 Rust 代码时尤其有用)
# 追踪 vfs_read 的调用链
trace-cmd record -p function_graph -g vfs_read cat /etc/hosts
# 查看结果
trace-cmd report
示例 3:追踪运行中进程的调度行为(对于 Python 或 Node.js 应用同样适用)
# 追踪 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:自定义输出文件
# 保存到指定文件
trace-cmd record -o network_trace.dat -e net:* -e irq:* sleep 5
# 分析
trace-cmd report -i network_trace.dat
⚠️ 注意:在追踪时务必控制事件数量,否则可能生成 GB 级别的数据文件,反而拖慢系统性能。
️ kernelshark:时间线可视化利器
如果说 trace-cmd 是瑞士军刀,那么 kernelshark 就是它的可视化驾驶舱。它提供了一个直观的图形界面,让你能够从时间维度观察多核 CPU 上发生的一切。
kernelshark 的界面主要分为几个区域:顶部是事件列表,中间是 CPU 时间线,底部是事件详情。这种布局让你既能宏观把握整体趋势,又能微观查看每个事件的细节。
┌─────────────────────────────────────────────────────────────────┐
│ 文件 编辑 过滤 工具 帮助 │
├─────────────────────────────────────────────────────────────────┤
│ ⏵ 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 │
└─────────────────────────────────────────────────────────────────┘
核心功能包括:多核时间线视图、事件过滤、任务跟踪、延迟测量等。具体功能如下表所示:
| 功能 | 说明 | 快捷键 |
|---|---|---|
| 缩放 | 放大/缩小时间轴 | 滚轮 / +/- |
| 平移 | 左右移动时间轴 | 拖拽 / ←→ |
| 过滤 | 显示/隐藏特定 event | Ctrl+F |
| 搜索 | 查找特定事件 | Ctrl+S |
| 标记 | 标记感兴趣的时间点 | M |
| 测量 | 测量两点间的时间差 | 右键选择 |
| CPU 过滤 | 只显示特定 CPU | 点击 CPU 行 |
| 导出 | 导出选中区域 | Ctrl+E |
启动 kernelshark 非常简单,只需加载 trace-cmd 生成的数据文件即可:
# 分析 trace.dat
kernelshark trace.dat
# 或者直接从 trace-cmd 启动
trace-cmd record -e 'sched:*' -e 'irq:*' sleep 5
kernelshark trace.dat
技巧:在 kernelshark 中,你可以使用鼠标拖拽选择时间范围,放大查看特定时间段的事件,这对于定位毫秒级的延迟问题至关重要。
实战案例 1:网络中断风暴引发 CPU 调度冲突
让我们通过一个真实案例来演示这两个工具的威力。在一个自动驾驶感知系统中,工程师发现系统出现间歇性延迟峰值,正常响应时间约 20ms,但有时会飙升到 150ms。通过 mpstat 观察,发现 CPU 3 的软中断占用率异常增高。
首先,使用 mpstat 确认问题现象:
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 ← 软中断异常!
接下来,使用 trace-cmd 记录相关事件,重点关注调度器和中断处理:
# 记录 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
然后,加载数据到 kernelshark 进行分析:
kernelshark trace.dat
在 kernelshark 的时间线视图中,我们可以清晰地看到:CPU 2 上运行的 perception 进程(一个 C++ 实现的图像处理模块)被频繁打断,而 CPU 3 上则堆满了 事件。每当 CPU 3 的软中断密集时,CPU 2 上的进程就被调度出去。softirq_entry(NET_RX)
时间线分析结果:
时间: 1234.500s - 1234.550s(50ms 窗口)
CPU 2: [perception]────────[idle]──[perception]──[idle]──[perception]
^20ms ^中断 ^5ms ^中断 ^8ms
CPU 3: [ksoftirqd]████████████████████████████████████
softirq softirq softirq softirq (连续 NET_RX)
通过 kernelshark 的测量工具,我们精确地测量了 perception 进程被抢占的时间:从 1234.520s 到 1234.545s,整整 25ms 的延迟,与业务层观察到的延迟峰值完全吻合。
最后,使用 trace-cmd report 进一步确认根因:
# 统计 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。这导致 CPU 3 的软中断占用率高达 65%,调度器无法及时调度其他进程。
解决方案是启用 RPS(Receive Packet Steering)来分散中断负载:
# 方案 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
优化后的效果验证:
# 再次记录 10 秒
trace-cmd record -e 'sched:*' -e 'irq:*' sleep 10
# 用 kernelshark 对比
kernelshark trace.dat
优化后的时间线显示,中断负载被均匀分散到了多个 CPU 核心,冲突明显缓解:
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:跨核心延迟传播分析
第二个案例涉及一个典型的跨核心协作问题。数据融合模块(data_fusion)在 CPU 0 上处理完数据后,需要唤醒 CPU 2 上的决策模块(planning)。但 planning 的唤醒延迟从预期的 1ms 飙升到了 15ms。
使用 trace-cmd 记录唤醒路径:
# 记录调度和 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
在 kernelshark 的时间线中,我们可以观察到完整的唤醒链路:
- 1234.500s:CPU 0 上的 data_fusion 调用
唤醒 planningsched_waking - 1234.502s:CPU 0 发送 IPI 到 CPU 2(
)irq_handler_entry: reschedule - 1234.515s:CPU 2 上的
才真正切换到 planningsched_switch
问题在于:CPU 2 在 1234.502s 到 1234.515s 期间到底在运行什么?kernelshark 显示,CPU 2 正在运行一个低优先级的 任务,且已经连续运行了 13 秒。即使 planning 是 high-priority 进程(nice=-10),也等待了 13ms。background_worker
根因很快被锁定: 关闭了抢占(很可能持有了自旋锁)。background_worker
为了验证这个假设,我们检查了抢占关闭的时间点:
# 使用 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();
}
解决方案是修复驱动代码中的锁使用问题,将自旋锁替换为可睡眠的互斥锁(适用于允许睡眠的上下文):
// 原代码(持锁时间过长)
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,效果显著。
高级功能与最佳实践
除了基础功能外,trace-cmd 和 kernelshark 还提供了一些高级特性,能帮助你更高效地分析问题。
过滤器功能:当追踪数据量过大时,可以使用过滤器只保留感兴趣的事件。例如,只追踪特定进程的事件:
# 只显示特定进程的事件
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
kernelshark 也支持强大的过滤语法,你可以根据事件类型、进程名、CPU 核心等条件进行过滤:
# 显示所有调度事件
sched:*
# 显示 CPU 2 的所有事件
cpu == 2
# 显示特定进程
comm ~ "perception"
# 组合条件
(comm ~ "perception" || comm ~ "planning") && cpu == 2
数据导出与分析:你可以将分析结果导出为文本格式,方便与其他工具(如 Python 脚本)集成进行深度分析:
# 导出为 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 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
kernelshark 的分析技巧总结:
| 任务 | 操作 |
|---|---|
| 找到延迟峰值 | 搜索 sched_switch,查看 |
| 测量中断处理时间 | 标记 irq_handler_entry 和 irq_handler_exit,查看时间差 |
| 对比优化效果 | 加载两个 trace.dat,使用 “Compare” 功能 |
| 导出关键区域 | 选中时间范围,右键 “Export Selection” |
| 查看调用栈 | 记录时加 ,双击事件查看 |
当然,任何工具都有其性能开销,使用时需要权衡:
| 配置 | trace.dat 大小(10秒) | CPU 开销 |
|---|---|---|
| ~5 MB | < 1% | |
| ~20 MB | 2-3% | |
| ~50 MB | 5-8% | |
| ~100 MB | 10-15% | |
| > 500 MB | 30-50% |
总结与展望
通过本文的学习,我们掌握了 trace-cmd 和 kernelshark 这两个强大的性能分析工具。它们不仅简化了 ftrace 的操作流程,更通过可视化手段让我们能够直观地洞察多核 CPU 的复杂交互。
核心要点回顾:
- ✅ trace-cmd 将复杂的 ftrace 操作简化为一条命令
- ✅ kernelshark 提供直观的时间线可视化,快速定位多核问题
- ✅ 数据持久化 功能便于团队协作和离线分析
- ✅ 结合使用多种工具,形成完整的性能分析工具链
工具选择建议:
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 单核热点分析 | perf | 采样开销低 |
| 多核调度分析 | trace-cmd + kernelshark | 可视化时间线 |
| 中断延迟分析 | trace-cmd + kernelshark | 清晰显示中断处理流程 |
| 函数调用链分析 | trace-cmd function_graph | 完整调用树 |
| 实时监控 | ftrace(手工) | 灵活性高 |
| 生产环境 | eBPF + bpftrace | 开销最低 |
常用命令速查表:
# === 记录常见场景 ===
# 调度分析
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
[AFFILIATE_SLOT_1]
无论你是 C++ 系统工程师,还是使用 Java、TypeScript 或 Python 开发上层应用的开发者,理解底层调度行为都能帮助你写出更高效的代码。trace-cmd 和 kernelshark 正是连接上层应用与内核行为的桥梁。
下一章,我们将深入探讨系统调用阻塞分析与高并发优化,学习阻塞 IO、非阻塞 IO 和异步 IO 的性能差异,并通过实战案例解决高并发场景下的典型问题。
[AFFILIATE_SLOT_2]Dive Deep into System Optimization - 从基础观测到高级优化,从 CPU 到存储,从内核到用户态,深入 Linux 性能分析的每一个角落。

prev_state == D-O stacktrace-e sched:*-e 'sched:*' -e 'irq:*'-e 'sched:*' -e 'irq:*' -e 'net:*'-p function -l 'vfs_*'-p function_graph
浙公网安备 33010602011771号