Linux 性能实战 | 第 19 篇:ftrace 内核跟踪入门 [特殊字符]

📋 本章摘要

第十八章中,我们使用 ltrace 捕捉库函数的性能热点,定位到正则表达式库(pcre_exec 占用 89%)、加密函数和 JSON 解析成为新的瓶颈。通过函数级别的性能分析,将帧率从 30 FPS 提升到 980 FPS。

但当问题进入内核路径 (调度延迟、软中断拥塞、块 IO 队列、kworker 异常)时,straceltrace 等用户态工具就显得无能为力了------它们只能看到系统调用的"入口"和"出口",无法观测内核内部的执行链路。

这时,我们需要进入内核的视角------ftrace

ftrace(Function Tracer)是 Linux 内核自带的跟踪框架,从 2.6.27 版本开始内置。它能够:

  • 函数级别追踪:观测任意内核函数的调用链和耗时
  • 事件追踪(tracepoints):监控调度、中断、系统调用等内核事件
  • 延迟分析:测量中断延迟、抢占延迟、调度延迟
  • 动态探针(kprobes):在任意内核函数插入跟踪点
  • 零编译成本 :直接通过 /sys/kernel/debug/tracing 接口使用

本章将从实战出发,带你掌握 ftrace 的核心用法,理解 function/function_graph tracer、tracepoints 的区别,学习如何过滤特定进程和函数,并通过两个真实案例(kworker 高负载软中断风暴)完成完整的内核性能排查。


🔗 从用户态到内核态:为什么需要 ftrace

1. 用户态工具的视野盲区

在自动驾驶系统的性能排查中,我们已经掌握了多种用户态工具:

  • top/htop:进程级别的 CPU、内存占用
  • strace:系统调用追踪,但只能看到调用和返回,无法看到内核内部
  • ltrace:库函数调用追踪,仅限用户态
  • perf:可以采样内核函数,但无法看到完整的调用链

然而,很多性能瓶颈隐藏在内核执行路径中:

场景 1:传感器数据流的软中断风暴

复制代码
现象:top 显示 ksoftirqd/3 占用 CPU 85%,但看不到具体在处理什么
原因:激光雷达以 10Hz × 300,000 点/帧的速率产生网络数据包
瓶颈:网络协议栈的软中断处理(__netif_receive_skb → ip_rcv → udp_rcv)
工具盲区:strace 看不到内核函数,perf 只能采样无法看到调用链

场景 2:块 IO 导致进程进入 D 状态

复制代码
现象:数据录制进程频繁进入 D 状态(不可中断睡眠),load 飙升至 12
原因:SSD 写入队列堵塞,进程在 blk_mq_submit_bio → io_schedule 等待
瓶颈:块 IO 调度器的队列处理逻辑
工具盲区:strace 只能看到 write() 系统调用阻塞,无法深入块设备层

场景 3:kworker 异常占用 CPU

复制代码
现象:kworker/7:2 进程持续占用 CPU 90%,但不知道在执行什么工作
原因:RCU 回调队列积压,频繁执行 rcu_process_callbacks
瓶颈:某个驱动模块泄漏 RCU 引用,导致对象无法释放
工具盲区:kworker 是内核线程,用户态工具无法追踪

2. ftrace 能回答的关键问题

ftrace 可以深入内核,回答以下问题:

问题 ftrace 工具 示例
哪些内核函数被频繁调用? function tracer 发现 tcp_sendmsg 每秒调用 50,000 次
关键调用链的耗时分布? function_graph tracer vfs_write 调用链耗时 450μs,其中 ext4_file_write_iter 占 380μs
CPU 调度延迟的根因? sched tracepoint 高优先级任务被低优先级任务抢占,等待 12ms
网络数据包在哪里丢失? net tracepoint netif_receive_skb 收到 10,000 包,ip_rcv 只处理 8,500 包
kworker 在执行什么工作? workqueue tracepoint process_one_work 正在执行 blk_mq_run_work_fn
软中断处理时间分布? irq tracepoint NET_RX 软中断占用 85%,TASKLET 占用 10%

3. ftrace vs 其他工具

工具 作用域 开销 实时性 调用链 适用场景
strace 系统调用 高(50-100%) 用户态程序的系统调用分析
ltrace 库函数 中(20-50%) 用户态库函数性能分析
perf 用户态+内核态 低(1-5%) 否(采样) 有(需 -g) CPU 热点分析,适合长时间采样
ftrace 内核函数+事件 中(5-20%) 内核路径分析,调度/中断/IO 问题
eBPF 内核+用户态 低(< 5%) 可编程 复杂的自定义监控,需编写程序

ftrace 的优势

  • 内核自带:无需安装额外工具或编译内核模块
  • 实时输出 :可通过 trace_pipe 实时查看,无需等待采样结束
  • 完整调用链:function_graph 可显示函数调用树和耗时
  • 丰富的 tracepoints:调度、中断、网络、块 IO 等子系统预置了大量跟踪点
  • 灵活过滤:可按进程、CPU、函数名、事件类型过滤

ftrace 的限制

  • ⚠️ 性能开销:function tracer 会拖慢系统 10-20%(可通过过滤降低)
  • ⚠️ 输出量大:未过滤时每秒可产生数 MB 日志
  • ⚠️ 学习曲线:需要理解内核函数名和调用关系
  • ⚠️ 缺少编程能力:复杂的条件判断和聚合需要 eBPF

🧠 ftrace 架构与核心概念

1. 整体架构

输出层
存储层
过滤层
数据源
function tracer

(函数调用)
function_graph tracer

(调用链+耗时)
tracepoints

(内核事件)
kprobes

(动态探针)
set_ftrace_filter

(函数名过滤)
set_ftrace_pid

(进程过滤)
set_ftrace_notrace

(排除函数)
events/*/filter

(事件条件过滤)
Ring Buffer

(环形缓冲区)

默认 1.4MB/CPU
trace

(静态快照)
trace_pipe

(实时流)
trace_marker

(用户标记)

2. 核心组件详解

(1) Tracers:追踪器
Tracer 功能 开销 适用场景
function 记录每个内核函数的调用 高(10-20%) 查找频繁调用的函数
function_graph 记录函数调用链和耗时 很高(20-50%) 分析调用链的耗时分布
nop 无追踪(关闭) 0 停止追踪
irqsoff 记录关中断时间最长的路径 中(5-10%) 中断延迟分析
preemptoff 记录关抢占时间最长的路径 中(5-10%) 调度延迟分析
wakeup 记录高优先级任务的唤醒延迟 中(5-10%) 实时性分析
(2) Events(Tracepoints):内核预置的跟踪点
bash 复制代码
# 查看所有可用 events(通常有 1000+ 个)
ls /sys/kernel/debug/tracing/events/

常用 event 子系统

子系统 典型 events 用途
sched sched_switch、sched_wakeup 进程调度分析
irq irq_handler_entry、irq_handler_exit 中断处理时间
softirq softirq_entry、softirq_exit 软中断分析
syscalls sys_enter_read、sys_exit_write 系统调用追踪
net netif_receive_skb、net_dev_xmit 网络数据包流向
block block_rq_issue、block_rq_complete 块 IO 请求分析
workqueue workqueue_execute_start kworker 工作追踪
kmem kmalloc、kfree 内核内存分配
(3) Ring Buffer:环形缓冲区
bash 复制代码
# 查看缓冲区大小(KB,per CPU)
cat /sys/kernel/debug/tracing/buffer_size_kb
# 输出:1408(即 1.4MB × CPU核数)

# 调整缓冲区大小(避免数据丢失)
echo 4096 > /sys/kernel/debug/tracing/buffer_size_kb  # 增加到 4MB

# 查看总缓冲区大小
cat /sys/kernel/debug/tracing/buffer_total_size_kb
# 输出:22528(8 核 × 2816 KB)

缓冲区溢出的影响

  • 当追踪速度 > 读取速度时,旧数据会被覆盖
  • 可通过 trace_options 中的 overwrite 控制行为

3. ftrace 接口目录结构

bash 复制代码
# 主入口(需要 root 权限或 debugfs 挂载)
/sys/kernel/debug/tracing/

# 关键文件详解
├── current_tracer          # 当前使用的 tracer(读写)
├── available_tracers       # 系统支持的所有 tracers(只读)
├── tracing_on              # 总开关(1=开启,0=暂停,写入不清空缓冲区)
│
├── trace                   # 追踪结果(静态快照,读取时暂停追踪)
├── trace_pipe              # 实时流输出(阻塞读取,读取后清空)
├── trace_marker            # 用户态程序写入标记(调试用)
│
├── set_ftrace_filter       # 只追踪匹配的函数(支持通配符 * 和 ?)
├── set_ftrace_notrace      # 排除不追踪的函数
├── set_ftrace_pid          # 只追踪指定 PID(支持多个)
├── set_graph_function      # function_graph 的函数过滤
│
├── events/                 # 所有 tracepoint events
│   ├── enable              # 全局 event 开关
│   ├── sched/              # 调度相关 events
│   │   ├── sched_switch/
│   │   │   ├── enable      # 单个 event 开关
│   │   │   ├── filter      # 事件过滤条件(如 prev_pid == 1234)
│   │   │   └── format      # 事件数据格式
│   │   └── sched_wakeup/
│   ├── irq/
│   ├── net/
│   └── ...
│
├── buffer_size_kb          # 每 CPU 缓冲区大小
├── buffer_total_size_kb    # 总缓冲区大小(只读)
│
├── trace_options           # 输出格式选项
└── trace_clock             # 时间戳时钟源(local/global/mono)

⚙️ ftrace 基础使用流程

1. 初始化:挂载 debugfs

bash 复制代码
# 检查 debugfs 是否已挂载
mount | grep debugfs
# 输出:debugfs on /sys/kernel/debug type debugfs (rw,relatime)

# 如果未挂载,手动挂载
sudo mount -t debugfs none /sys/kernel/debug

# 进入 ftrace 目录
cd /sys/kernel/debug/tracing

2. 查看可用的追踪器

bash 复制代码
cat /sys/kernel/debug/tracing/available_tracers

典型输出

复制代码
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop

3. 基本操作流程

bash 复制代码
# (1) 清空之前的追踪数据
echo > /sys/kernel/debug/tracing/trace

# (2) 设置追踪器
echo function > /sys/kernel/debug/tracing/current_tracer

# (3) 设置过滤条件(可选,避免输出过多)
echo vfs_* > /sys/kernel/debug/tracing/set_ftrace_filter

# (4) 开启追踪
echo 1 > /sys/kernel/debug/tracing/tracing_on

# (5) 执行目标操作
ls /tmp > /dev/null

# (6) 停止追踪
echo 0 > /sys/kernel/debug/tracing/tracing_on

# (7) 查看结果
cat /sys/kernel/debug/tracing/trace | head -30

# (8) 关闭追踪器
echo nop > /sys/kernel/debug/tracing/current_tracer

🔍 Function Tracer:函数级别跟踪

1. 基本用法

bash 复制代码
# 追踪所有内核函数(⚠️ 输出量巨大!)
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
sleep 1
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace | head -20

输出示例

复制代码
# tracer: function
#
# entries-in-buffer/entries-written: 45720/45720   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq  
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          <idle>-0     [003] dNh.  1234.567890: __do_softirq <-irq_exit
          <idle>-0     [003] dNh.  1234.567892: rcu_process_callbacks <-__do_softirq
          <idle>-0     [003] dNs.  1234.567895: run_timer_softirq <-__do_softirq

字段说明

  • CPU#:执行的 CPU 核心
  • TIMESTAMP:时间戳(秒.微秒)
  • FUNCTION:被调用的函数
  • <-:调用者函数
  • 标志位 (dNh.):
    • d:中断禁用
    • N:需要重新调度
    • h:硬中断
    • s:软中断

2. 过滤特定函数

bash 复制代码
# 只追踪文件操作相关函数
echo > /sys/kernel/debug/tracing/set_ftrace_filter  # 清空过滤器
echo vfs_read > /sys/kernel/debug/tracing/set_ftrace_filter
echo vfs_write >> /sys/kernel/debug/tracing/set_ftrace_filter
echo do_sys_open >> /sys/kernel/debug/tracing/set_ftrace_filter

# 使用通配符
echo > /sys/kernel/debug/tracing/set_ftrace_filter
echo 'vfs_*' > /sys/kernel/debug/tracing/set_ftrace_filter

# 查看当前过滤器
cat /sys/kernel/debug/tracing/set_ftrace_filter

输出

复制代码
vfs_read
vfs_write  
vfs_open
vfs_fsync  
vfs_getattr

3. 过滤特定进程

bash 复制代码
# 只追踪特定 PID
echo 1234 > /sys/kernel/debug/tracing/set_ftrace_pid

# 追踪多个 PID
echo 1234 > /sys/kernel/debug/tracing/set_ftrace_pid
echo 5678 >> /sys/kernel/debug/tracing/set_ftrace_pid

# 清空 PID 过滤
echo > /sys/kernel/debug/tracing/set_ftrace_pid

4. 实战:查找频繁调用的内核函数

场景:自动驾驶感知模块占用 CPU 较高,怀疑内核有频繁调用的函数

bash 复制代码
# 追踪 5 秒,统计函数调用频率
cd /sys/kernel/debug/tracing
echo function > current_tracer
echo 1234 > set_ftrace_pid  # 感知进程的 PID
echo > trace
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on

# 统计调用次数(Top 10)
cat trace | awk '{print $NF}' | sed 's/<-.*//' | sort | uniq -c | sort -nr | head -10

典型输出

复制代码
  8523 tcp_sendmsg
  6712 tcp_write_xmit
  5234 ip_queue_xmit
  3421 __netif_receive_skb
  2890 udp_recvmsg
  2456 kmem_cache_alloc
  1987 kmem_cache_free
  1543 schedule
  1201 _raw_spin_lock
  1045 _raw_spin_unlock

分析tcp_sendmsg 调用 8523 次,网络发送是热点路径。


⏱️ Function Graph Tracer:调用链与耗时分析

1. 基本用法

bash 复制代码
# 启用 function_graph tracer
echo function_graph > /sys/kernel/debug/tracing/current_tracer

# 过滤特定函数(避免输出过多)
echo vfs_read > /sys/kernel/debug/tracing/set_graph_function

# 追踪
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /etc/hosts > /dev/null
echo 0 > /sys/kernel/debug/tracing/tracing_on

# 查看结果
cat /sys/kernel/debug/tracing/trace

输出示例

复制代码
# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 0)   ==========> |
 0)               |  vfs_read() {
 0)   0.234 us    |    rw_verify_area();
 0)               |    __vfs_read() {
 0)               |      new_sync_read() {
 0)               |        ext4_file_read_iter() {
 0)               |          generic_file_read_iter() {
 0)               |            page_cache_mapping() {
 0)   0.123 us    |              page_cache_get();
 0)   0.456 us    |            }
 0)   0.125 us    |            copy_page_to_iter();
 0)   2.456 us    |          }
 0)   3.123 us    |        }
 0)   3.789 us    |      }
 0)   4.234 us    |    }
 0)   5.123 us    |  }

关键信息

  • DURATION:函数执行耗时(us)
  • 缩进:表示调用层级
  • { }:函数的进入和退出
  • 总耗时计算vfs_read 总耗时 5.123us,其中 __vfs_read 占 4.234us

2. 耗时分析示例

场景 :数据录制过程中 write() 系统调用延迟高

bash 复制代码
cd /sys/kernel/debug/tracing
echo function_graph > current_tracer
echo ksys_write > set_graph_function  # 追踪 write 系统调用入口
echo > trace
echo 1 > tracing_on

# 在另一个终端执行写操作
dd if=/dev/zero of=/tmp/test.dat bs=1M count=10

# 停止追踪
echo 0 > tracing_on
cat trace | grep -A 50 "ksys_write()"

典型输出分析

复制代码
 ksys_write() {
   __fdget_pos() { ... }  0.125 us
   vfs_write() {
     rw_verify_area() { ... }  0.234 us
     __vfs_write() {
       new_sync_write() {
         ext4_file_write_iter() {
           ext4_buffered_write_iter() {
             generic_perform_write() {
               ext4_da_write_begin() {
                 grab_cache_page_write_begin() { ... }  2.345 us
                 ext4_journal_start() { ... }  15.678 us  ← 瓶颈!
               }  18.234 us
               ... 
             }  25.456 us
           }  26.123 us
         }  26.789 us
       }  27.234 us
     }  27.890 us
   }  28.456 us
 }  29.123 us

分析结论ext4_journal_start 耗时 15.678us,占总耗时的 54%,日志写入是瓶颈。

优化方向

  • 调整 ext4 日志模式(data=writeback 代替 data=ordered
  • 增大日志缓冲区大小
  • 考虑使用 XFS(日志性能更好)

3. 多层嵌套调用分析

bash 复制代码
echo function_graph > current_tracer
echo do_sys_open > set_graph_function
echo > trace
echo 1 > tracing_on
ls /tmp > /dev/null
echo 0 > tracing_on
cat trace | head -60

输出

复制代码
do_sys_open() {
  getname() {
    getname_flags() {
      kmem_cache_alloc() { ... }  0.234 us
      strncpy_from_user() { ... }  0.456 us
    }  0.890 us
  }  1.123 us
  get_unused_fd_flags() { ... }  0.345 us
  do_filp_open() {
    path_openat() {
      link_path_walk() {
        walk_component() {
          lookup_fast() {
            __d_lookup() { ... }  0.567 us
          }  0.789 us
        }  1.234 us
      }  2.456 us
      do_last() {
        lookup_open() { ... }  1.890 us
        vfs_open() {
          do_dentry_open() {
            ext4_file_open() { ... }  0.678 us
          }  1.123 us
        }  1.456 us
      }  4.567 us
    }  7.890 us
  }  8.234 us
  fd_install() { ... }  0.123 us
}  10.456 us

📌 Tracepoints:事件级别跟踪

1. Tracepoints 的优势

相比 function tracer,tracepoints 具有以下特点:

  • 开销更低:只在预设的事件点记录,不追踪整个函数
  • 结构化数据:提供格式化的事件字段,易于过滤和分析
  • 稳定接口:内核 API 可能变化,但 tracepoints 保持稳定
  • 语义清晰 :事件名称明确表达语义(如 sched_switchblock_rq_issue

2. 查看可用 events

bash 复制代码
# 列出所有 events 分类
ls /sys/kernel/debug/tracing/events/

# 常见分类
sched/       # 进程调度
irq/         # 中断
softirq/     # 软中断
syscalls/    # 系统调用
net/         # 网络
block/       # 块 IO
workqueue/   # 工作队列
timer/       # 定时器
kmem/        # 内存分配
bash 复制代码
# 查看特定分类下的 events
ls /sys/kernel/debug/tracing/events/sched/

sched_kthread_stop/
sched_kthread_stop_ret/
sched_migrate_task/
sched_move_numa/
sched_pi_setprio/
sched_process_exec/
sched_process_exit/
sched_process_fork/
sched_process_free/
sched_process_wait/
sched_stat_blocked/
sched_stat_iowait/
sched_stat_runtime/
sched_stat_sleep/
sched_stat_wait/
sched_stick_numa/
sched_swap_numa/
sched_switch/
sched_wait_task/
sched_wake_idle_without_ipi/
sched_wakeup/
sched_wakeup_new/
sched_waking/

3. 查看 event 格式

bash 复制代码
cat /sys/kernel/debug/tracing/events/sched/sched_switch/format

输出

复制代码
name: sched_switch
ID: 315
format:
    field:unsigned short common_type;       offset:0;  size:2; signed:0;
    field:unsigned char common_flags;       offset:2;  size:1; signed:0;
    field:unsigned char common_preempt_count;   offset:3;  size:1; signed:0;
    field:int common_pid;                   offset:4;  size:4; signed:1;

    field:char prev_comm[16];               offset:8;  size:16;    signed:1;
    field:pid_t prev_pid;                   offset:24; size:4; signed:1;
    field:int prev_prio;                    offset:28; size:4; signed:1;
    field:long prev_state;                  offset:32; size:8; signed:1;
    field:char next_comm[16];               offset:40; size:16;    signed:1;
    field:pid_t next_pid;                   offset:56; size:4; signed:1;
    field:int next_prio;                    offset:60; size:4; signed:1;

print fmt: "prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s ==> next_comm=%s next_pid=%d next_prio=%d"

关键字段

  • prev_comm:切换出去的进程名
  • prev_pid:切换出去的进程 PID
  • prev_state:进程状态(R=running, S=sleeping, D=uninterruptible)
  • next_comm:切换进来的进程名
  • next_pid:切换进来的进程 PID

4. 启用调度事件

bash 复制代码
# 启用 sched_switch 事件
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable

# 追踪 3 秒
echo 1 > /sys/kernel/debug/tracing/tracing_on
sleep 3
echo 0 > /sys/kernel/debug/tracing/tracing_on

# 查看结果
cat /sys/kernel/debug/tracing/trace | grep sched_switch | head -10

输出示例

复制代码
 <idle>-0     [003] d...  1234.567890: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/3:2 next_pid=1234 next_prio=120
kworker/3:2-1234  [003] d...  1234.568012: sched_switch: prev_comm=kworker/3:2 prev_pid=1234 prev_prio=120 prev_state=S ==> next_comm=perception next_pid=5678 next_prio=110
perception-5678  [003] d...  1234.570543: sched_switch: prev_comm=perception prev_pid=5678 prev_prio=110 prev_state=R ==> next_comm=migration/3 next_pid=23 next_prio=0

5. 过滤事件

bash 复制代码
# 只显示特定进程的调度事件
echo 'next_pid == 5678 || prev_pid == 5678' > /sys/kernel/debug/tracing/events/sched/sched_switch/filter

# 只显示进入 D 状态的进程
echo 'prev_state == 2' > /sys/kernel/debug/tracing/events/sched/sched_switch/filter

# 清空过滤
echo 0 > /sys/kernel/debug/tracing/events/sched/sched_switch/filter

6. 实战:软中断处理分析

场景:自动驾驶数据采集系统网络延迟高,怀疑软中断处理慢

bash 复制代码
cd /sys/kernel/debug/tracing

# 启用软中断 events
echo 1 > events/irq/softirq_entry/enable
echo 1 > events/irq/softirq_exit/enable

# 追踪 5 秒
echo > trace
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on

# 查看结果
cat trace | head -50

输出示例

复制代码
ksoftirqd/3-25   [003] ..s.  1234.567890: softirq_entry: vec=3 [action=NET_RX]
ksoftirqd/3-25   [003] ..s.  1234.568234: softirq_exit: vec=3 [action=NET_RX]
ksoftirqd/3-25   [003] ..s.  1234.568456: softirq_entry: vec=3 [action=NET_RX]
ksoftirqd/3-25   [003] ..s.  1234.569123: softirq_exit: vec=3 [action=NET_RX]
ksoftirqd/3-25   [003] ..s.  1234.569345: softirq_entry: vec=1 [action=TIMER]
ksoftirqd/3-25   [003] ..s.  1234.569456: softirq_exit: vec=1 [action=TIMER]

统计软中断类型和耗时

bash 复制代码
# 计算每种软中断的处理次数
cat trace | grep softirq_entry | awk '{print $NF}' | sort | uniq -c

# 输出:
#  1542 [action=NET_RX]
#   234 [action=TIMER]
#   123 [action=TASKLET]
#    45 [action=SCHED]

计算单次处理耗时

python 复制代码
# parse_softirq.py
import re

entry_times = {}
durations = []

with open('/sys/kernel/debug/tracing/trace') as f:
    for line in f:
        if 'softirq_entry' in line:
            m = re.search(r'(\d+\.\d+):.*vec=(\d+)', line)
            if m:
                ts, vec = float(m.group(1)), m.group(2)
                entry_times[vec] = ts
        elif 'softirq_exit' in line:
            m = re.search(r'(\d+\.\d+):.*vec=(\d+)', line)
            if m:
                ts, vec = float(m.group(1)), m.group(2)
                if vec in entry_times:
                    duration = (ts - entry_times[vec]) * 1000000  # 转为微秒
                    durations.append(duration)
                    del entry_times[vec]

print(f"平均: {sum(durations)/len(durations):.2f} us")
print(f"最大: {max(durations):.2f} us")
print(f"最小: {min(durations):.2f} us")

🧪 实战案例 1:kworker 高负载排查

1. 问题现象

自动驾驶数据录制系统中,发现某个 CPU 核心持续 90% 负载,top 显示 kworker/3:2 占用最高,但看不到具体在执行什么工作。

top 输出

复制代码
  PID USER      PR  NI    VIRT    RES  %CPU %MEM     TIME+ COMMAND
  278 root      20   0       0      0  89.3  0.0  45:32.12 kworker/3:2
 5678 user      20   0    4.5g   1.2g  12.5  3.2  12:34.56 perception

vmstat 1

复制代码
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 3456M  567M  12.3G    0    0  1234  5678 15k 25k 15 85  0  0  0

观察

  • CPU 3 号核心被 kworker/3:2 占用 90%
  • 系统态 CPU(sy)占用 85%
  • 中断数(in)15k/s,上下文切换(cs)25k/s 都很高

2. 排查思路

发现 kworker 高负载
定位工作类型
启用 workqueue events
查看执行的 work 函数
分析函数耗时
function_graph tracer
定位具体瓶颈
分析对应内核子系统

3. 步骤 1:识别 kworker 的工作内容

bash 复制代码
cd /sys/kernel/debug/tracing

# 找到 kworker PID
ps -ef | grep "kworker/3:2"
# 输出:root  278  2  89.3  0  ... kworker/3:2

# 启用 workqueue events
echo 1 > events/workqueue/workqueue_execute_start/enable
echo 1 > events/workqueue/workqueue_execute_end/enable

# 过滤特定 kworker
echo 'common_pid == 278' > events/workqueue/workqueue_execute_start/filter
echo 'common_pid == 278' > events/workqueue/workqueue_execute_end/filter

# 追踪 5 秒
echo > trace
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on

# 查看结果
cat trace | head -30

输出分析

复制代码
kworker/3:2-278   [003] ....  1234.567890: workqueue_execute_start: work struct ffff88810abcd000: function blk_mq_run_work_fn
kworker/3:2-278   [003] ....  1234.568234: workqueue_execute_end: work struct ffff88810abcd000
kworker/3:2-278   [003] ....  1234.568456: workqueue_execute_start: work struct ffff88810abcd100: function blk_mq_run_work_fn
kworker/3:2-278   [003] ....  1234.568890: workqueue_execute_end: work struct ffff88810abcd100
kworker/3:2-278   [003] ....  1234.569012: workqueue_execute_start: work struct ffff88810abcd200: function blk_mq_run_work_fn

关键发现

  • kworker 一直在执行 blk_mq_run_work_fn 函数
  • 该函数是块 IO 多队列(blk-mq)的工作处理函数
  • 平均每 0.4ms 执行一次(频率 ~2500 Hz)

4. 步骤 2:分析函数调用链和耗时

bash 复制代码
# 切换到 function_graph tracer
echo nop > current_tracer
echo function_graph > current_tracer

# 过滤 blk_mq_run_work_fn 函数
echo blk_mq_run_work_fn > set_graph_function

# 过滤 kworker PID
echo 278 > set_ftrace_pid

# 追踪 3 秒
echo > trace
echo 1 > tracing_on
sleep 3
echo 0 > tracing_on

# 查看调用链
cat trace | head -80

输出示例

复制代码
kworker/3:2-278   [003] ....  1234.567890: blk_mq_run_work_fn() {
  blk_mq_run_hw_queue() {
    __blk_mq_delay_run_hw_queue() {
      __blk_mq_run_hw_queue() {
        blk_mq_sched_dispatch_requests() {
          __blk_mq_sched_dispatch_requests() {
            blk_mq_do_dispatch_sched() {
              blk_mq_dispatch_rq_list() {
                blk_mq_request_issue_directly() {
                  nvme_queue_rq() {
                    nvme_setup_cmd() { ... }  0.234 us
                    nvme_submit_cmd() {
                      _raw_spin_lock() { ... }  0.123 us
                      __nvme_submit_cmd() { ... }  0.456 us
                      _raw_spin_unlock() { ... }  0.089 us
                    }  0.890 us
                  }  1.456 us
                }  2.123 us
              }  2.789 us
            }  3.456 us
          }  4.123 us
        }  4.789 us
      }  5.456 us
    }  6.123 us
  }  6.789 us
}  7.456 us

耗时分析

  • 单次 blk_mq_run_work_fn 耗时 ~7.5us
  • 主要耗时在 nvme_queue_rq(1.456us)和调度开销(~6us)
  • 频率 2500 Hz × 7.5us = 18.75ms/秒 1.875% CPU(理论值)

问题:理论 CPU 占用只有 2%,但实际占用 90%,说明有其他因素!

5. 步骤 3:查看块 IO 请求频率

bash 复制代码
# 启用块 IO events
echo 1 > events/block/block_rq_issue/enable
echo 1 > events/block/block_rq_complete/enable

# 追踪 1 秒
echo > trace
echo 1 > tracing_on
sleep 1
echo 0 > tracing_on

# 统计 IO 请求数
cat trace | grep block_rq_issue | wc -l
# 输出:3456  (每秒 3456 个 IO 请求!)

# 查看 IO 类型
cat trace | grep block_rq_issue | head -10

输出

复制代码
kworker/3:2-278   [003] ....  1234.567: block_rq_issue: 259,0 WS 4096 () 1048576 + 8 [kworker/3:2]
kworker/3:2-278   [003] ....  1234.568: block_rq_issue: 259,0 WS 4096 () 1048584 + 8 [kworker/3:2]
kworker/3:2-278   [003] ....  1234.569: block_rq_issue: 259,0 WS 4096 () 1048592 + 8 [kworker/3:2]

关键信息

  • 259,0 :设备号(/dev/nvme0n1
  • WS:Write Sync(同步写)
  • 4096:请求大小(4KB)
  • 1048576 + 8:起始扇区 + 扇区数

问题根因

  • 每秒 3456 个小 IO 请求(4KB)
  • 全部是同步写操作(WS)
  • 没有 IO 合并,每个请求独立处理

6. 步骤 4:定位 IO 来源

bash 复制代码
# 查看是哪个进程产生的 IO
iostat -x 1

# 或使用 iotop
sudo iotop -o

# 或通过 ftrace syscall events
echo 1 > events/syscalls/sys_exit_write/enable
echo 'ret > 0' > events/syscalls/sys_exit_write/filter

echo > trace
echo 1 > tracing_on
sleep 2
echo 0 > tracing_on

cat trace | head -20

发现

复制代码
data_recorder-5678  [002] ....  1234.567: sys_exit_write: 0x1000  # 4KB
data_recorder-5678  [002] ....  1234.568: sys_exit_write: 0x1000  # 4KB
data_recorder-5678  [002] ....  1234.569: sys_exit_write: 0x1000  # 4KB

根本原因

  • data_recorder 进程以 4KB 为单位频繁写入
  • 每次写入触发一次同步 IO(可能设置了 O_SYNC 或频繁 fsync()
  • 小 IO 导致块设备层无法合并,kworker 频繁调度处理

7. 优化方案

方案 1:增大写入缓冲区

python 复制代码
# 原代码(慢):
for data in sensor_data:
    f.write(data)  # 每次写 4KB
    f.flush()      # 立即刷盘

# 优化后(快):
buffer = []
for data in sensor_data:
    buffer.append(data)
    if len(buffer) >= 256:  # 累积 1MB
        f.write(b''.join(buffer))
        f.flush()
        buffer = []

方案 2:移除不必要的 fsync

python 复制代码
# 原代码:
f = open('data.log', 'wb', buffering=0)  # 无缓冲
f.write(data)

# 优化后:
f = open('data.log', 'wb', buffering=1048576)  # 1MB 缓冲
f.write(data)
# 只在关键节点 fsync

效果对比

指标 优化前 优化后 改善
IO 请求数 3456/秒 14/秒 ↓ 99.6%
kworker CPU 89% 2% ↓ 97.8%
写入吞吐量 13.5 MB/s 350 MB/s ↑ 25 倍
录制延迟 450ms 18ms ↓ 96%

🌪️ 实战案例 2:软中断风暴定位

1. 问题现象

自动驾驶感知系统在处理密集点云数据时,某个 CPU 核心的 ksoftirqd 占用 85%,导致感知延迟从 20ms 飙升至 120ms。

top 输出

复制代码
  PID USER      PR  NI    VIRT    RES  %CPU %MEM     TIME+ COMMAND
   25 root      20   0       0      0  85.2  0.0  67:45.23 ksoftirqd/3
 6789 user      20   0    8.5g   3.2g  10.5  8.1  23:45.67 perception

mpstat -P ALL 1

复制代码
CPU    %usr   %nice    %sys %iowait    %irq   %soft   %idle
  0    15.2     0.0    3.4     0.0     0.5     2.1    78.8
  1    14.8     0.0    3.2     0.0     0.6     2.3    79.1
  2    16.1     0.0    3.5     0.0     0.4     1.9    78.1
  3    12.3     0.0    2.8     0.0     1.2    85.2     0.0  ← 异常!

2. 步骤 1:确认软中断类型

bash 复制代码
watch -n 1 cat /proc/softirqs

输出

复制代码
                    CPU0       CPU1       CPU2       CPU3
          HI:          0          0          0          0
       TIMER:     123456     124567     125678     126789
      NET_TX:          0          0          0          0
      NET_RX:      45678      46789      47890   12456789  ← 异常高!
       BLOCK:        234        245        256        267
    IRQ_POLL:          0          0          0          0
     TASKLET:       1234       1245       1256       1267
       SCHED:      34567      35678      36789      37890
     HRTIMER:       5678       5789       5890       5901
         RCU:      78901      79012      80123      81234

发现 :CPU 3 的 NET_RX(网络接收软中断)计数异常高,每秒增加 ~200 万次!

3. 步骤 2:追踪网络软中断处理

bash 复制代码
cd /sys/kernel/debug/tracing

# 启用软中断和网络 events
echo 1 > events/irq/softirq_entry/enable
echo 1 > events/irq/softirq_exit/enable
echo 1 > events/net/netif_receive_skb/enable

# 过滤 NET_RX 软中断(vec=3)
echo 'vec == 3' > events/irq/softirq_entry/filter
echo 'vec == 3' > events/irq/softirq_exit/filter

# 追踪 1 秒
echo > trace
echo 1 > tracing_on
sleep 1
echo 0 > tracing_on

# 统计软中断触发次数
cat trace | grep softirq_entry | wc -l
# 输出:2145672  (每秒 214 万次!)

# 查看网络数据包数量
cat trace | grep netif_receive_skb | wc -l
# 输出:2145672  (与软中断数一致)

4. 步骤 3:分析数据包来源

bash 复制代码
# 查看数据包详情
cat trace | grep netif_receive_skb | head -10

输出

复制代码
ksoftirqd/3-25   [003] ..s.  1234.567: netif_receive_skb: dev=eth0 napi_id=0x3 queue_mapping=0 skbaddr=ffff888100001000 protocol=8 (IP) ip_summed=0 hash=0x12345678 l4_hash=0 len=98 data_len=0 truesize=768
ksoftirqd/3-25   [003] ..s.  1234.568: netif_receive_skb: dev=eth0 ...

关键信息

  • 网卡:eth0
  • 协议:IP
  • 包长:98 字节(小包!)
  • 频率:214 万包/秒

5. 步骤 4:使用 tcpdump 确认数据源

bash 复制代码
# 查看网络流量
sudo tcpdump -i eth0 -n -c 100

# 统计流量来源
sudo tcpdump -i eth0 -n -c 10000 | awk '{print $3}' | sort | uniq -c | sort -nr | head -5

发现

复制代码
2145672 192.168.1.100.5201 > 192.168.1.200.12345: UDP, length 64

分析

  • 192.168.1.100 是激光雷达设备
  • 以 每秒 214 万包的速率发送 64 字节小包
  • 吞吐量:214万 × 64 字节 = 137 MB/s(正常雷达数据应为 ~30 MB/s)

6. 步骤 5:分析网络协议栈耗时

bash 复制代码
cd /sys/kernel/debug/tracing

# 使用 function_graph 追踪网络接收路径
echo function_graph > current_tracer
echo __netif_receive_skb_core > set_graph_function

# 追踪 0.1 秒(避免输出过多)
echo > trace
echo 1 > tracing_on
sleep 0.1
echo 0 > tracing_on

# 查看调用链
cat trace | head -100

典型调用链

复制代码
__netif_receive_skb_core() {
  ip_rcv() {
    ip_rcv_core() { ... }  0.234 us
    ip_rcv_finish() {
      ip_local_deliver() {
        ip_local_deliver_finish() {
          udp_rcv() {
            __udp4_lib_rcv() {
              udp_queue_rcv_skb() {
                __udp_queue_rcv_skb() {
                  sock_queue_rcv_skb() {
                    __skb_queue_tail() { ... }  0.123 us
                    sk_data_ready() {
                      sock_def_readable() {
                        __wake_up_common() { ... }  0.456 us
                      }  0.678 us
                    }  0.890 us
                  }  1.234 us
                }  1.456 us
              }  1.678 us
            }  1.890 us
          }  2.123 us
        }  2.345 us
      }  2.567 us
    }  2.789 us
  }  3.012 us
}  3.456 us

耗时分析

  • 单包处理耗时 ~3.5us
  • 214万包/秒 × 3.5us = 7.49秒/秒 → 750% CPU(理论值)
  • 但只有 1 个核心,最多 100%,因此会积压

7. 根本原因与优化方案

根本原因

  1. 激光雷达配置错误,包大小设置为 64B 而非 1400B
  2. 小包导致包数量暴增(64B vs 1400B = 21 倍差距)
  3. 协议栈处理开销主要在固定成本(头部解析、队列操作),小包效率低

优化方案 1:修正雷达配置

bash 复制代码
# 登录雷达设备,修改 UDP 包大小
ssh lidar@192.168.1.100
# 配置文件:/etc/lidar.conf
packet_size=1400  # 原来是 64

# 重启雷达服务
systemctl restart lidar

优化方案 2:启用 GRO(Generic Receive Offload)

bash 复制代码
# 启用 GRO(将小包在硬件/驱动层合并)
ethtool -K eth0 gro on

# 查看状态
ethtool -k eth0 | grep generic-receive-offload
# 输出:generic-receive-offload: on

优化方案 3:调整网卡中断亲和性

bash 复制代码
# 查看网卡中断号
cat /proc/interrupts | grep eth0
# 输出:125:  12345678  ...  eth0-TxRx-0

# 将 eth0 中断绑定到 CPU 0-2(避免影响 perception 进程所在的 CPU 3)
echo 07 > /proc/irq/125/smp_affinity  # 二进制 0111 = CPU 0,1,2

效果对比

指标 优化前 优化后 改善
包频率 214 万/秒 10 万/秒 ↓ 95.3%
包大小 64B 1400B ↑ 21 倍
吞吐量 137 MB/s 140 MB/s ↑ 2.2%
ksoftirqd CPU 85% 4% ↓ 95.3%
软中断延迟 15ms 0.8ms ↓ 94.7%
感知处理延迟 120ms 22ms ↓ 81.7%

🎯 过滤与性能控制最佳实践

1. 函数过滤技巧

bash 复制代码
# (1) 通配符过滤
echo 'vfs_*' > set_ftrace_filter      # 所有 vfs_ 开头的函数
echo '*_read' > set_ftrace_filter     # 所有 _read 结尾的函数
echo '*tcp*' > set_ftrace_filter      # 所有包含 tcp 的函数

# (2) 多条件过滤(OR 关系)
echo 'vfs_read' > set_ftrace_filter
echo 'vfs_write' >> set_ftrace_filter  # 注意是 >>
echo 'vfs_open' >> set_ftrace_filter

# (3) 模块过滤
echo ':mod:ext4' > set_ftrace_filter   # 只追踪 ext4 模块的函数

# (4) 排除函数(NOT)
echo '!schedule' >> set_ftrace_notrace  # 不追踪 schedule 函数
echo '!*lock*' >> set_ftrace_notrace    # 不追踪锁相关函数

# (5) 清空过滤器
echo > set_ftrace_filter
echo > set_ftrace_notrace

2. PID 过滤技巧

bash 复制代码
# (1) 单个 PID
echo 1234 > set_ftrace_pid

# (2) 多个 PID
echo 1234 > set_ftrace_pid
echo 5678 >> set_ftrace_pid

# (3) 批量添加(某个进程的所有线程)
pgrep -T perception | while read pid; do
    echo $pid >> /sys/kernel/debug/tracing/set_ftrace_pid
done

# (4) 动态追踪新创建的子进程
echo 1 > options/function-fork  # 自动追踪 fork 出的子进程

# (5) 清空 PID 过滤
echo > set_ftrace_pid

3. Event 条件过滤

bash 复制代码
# (1) 数值比较
echo 'prev_prio < 120' > events/sched/sched_switch/filter  # 只显示高优先级调度

# (2) 字符串匹配
echo 'prev_comm ~ "kworker*"' > events/sched/sched_switch/filter

# (3) 逻辑运算
echo 'prev_pid == 1234 || next_pid == 1234' > events/sched/sched_switch/filter
echo 'common_pid == 5678 && ret > 4096' > events/syscalls/sys_exit_write/filter

# (4) 支持的操作符
# ==  !=  >  <  >=  <=  &  &&  ||  !  ~(正则匹配)

# (5) 查看当前过滤条件
cat events/sched/sched_switch/filter

# (6) 清空过滤
echo 0 > events/sched/sched_switch/filter

4. 性能开销控制

bash 复制代码
# (1) 减小缓冲区(降低内存占用)
echo 512 > buffer_size_kb  # 每 CPU 512KB

# (2) 启用 trace_options 优化
echo nop > current_tracer
echo 0 > options/context-info        # 不显示上下文信息
echo 0 > options/graph-time          # 不显示函数耗时
echo 0 > options/print-parent        # 不显示父函数

# (3) 使用 trace_pipe 代替 trace(避免缓冲积压)
cat trace_pipe | tee output.log

# (4) 限制追踪时间(避免长时间高负载)
echo 1 > tracing_on
sleep 10
echo 0 > tracing_on

# (5) 追踪特定 CPU
echo 1 > tracing_cpumask  # 只追踪 CPU 0(二进制掩码)
echo 3 > tracing_cpumask  # 只追踪 CPU 0 和 1

5. 实用脚本:ftrace 快速分析

脚本 1:追踪进程的系统调用

bash 复制代码
#!/bin/bash
# trace_syscalls.sh <PID> <duration>

PID=$1
DURATION=${2:-5}
TRACE_DIR=/sys/kernel/debug/tracing

cd $TRACE_DIR

# 清理
echo nop > current_tracer
echo 0 > tracing_on
echo > trace

# 启用系统调用 events
echo 1 > events/syscalls/enable
echo "common_pid == $PID" > events/syscalls/filter

# 追踪
echo 1 > tracing_on
sleep $DURATION
echo 0 > tracing_on

# 统计调用次数
echo "=== Top 10 System Calls ==="
cat trace | awk '{print $(NF-1)}' | grep sys_ | sort | uniq -c | sort -nr | head -10

# 清理
echo 0 > events/syscalls/enable
echo 0 > events/syscalls/filter

脚本 2:分析函数耗时

bash 复制代码
#!/bin/bash
# trace_function_latency.sh <function_name> <duration>

FUNC=$1
DURATION=${2:-10}
TRACE_DIR=/sys/kernel/debug/tracing

cd $TRACE_DIR

# 设置
echo nop > current_tracer
echo > trace
echo function_graph > current_tracer
echo $FUNC > set_graph_function

# 追踪
echo 1 > tracing_on
sleep $DURATION
echo 0 > tracing_on

# 提取耗时
cat trace | grep -E '\| *}' | awk '{print $NF}' | sed 's/[^0-9.]//g' | sort -n | awk '
BEGIN {count=0; sum=0}
{
    latency[count++] = $1
    sum += $1
}
END {
    if (count > 0) {
        print "Count:", count
        print "Min:", latency[0], "us"
        print "Max:", latency[count-1], "us"
        print "Avg:", sum/count, "us"
        print "P50:", latency[int(count*0.5)], "us"
        print "P90:", latency[int(count*0.9)], "us"
        print "P99:", latency[int(count*0.99)], "us"
    }
}'

# 清理
echo nop > current_tracer

🧩 Kprobes 与 Uprobes:动态探针

1. Kprobes:内核动态探针

当内核没有现成的 tracepoint 时,可以通过 kprobes 在任意内核函数插入探针。

示例 1:在 do_sys_open 插入探针

bash 复制代码
cd /sys/kernel/debug/tracing

# 创建 kprobe
echo 'p:myprobe_open do_sys_open filename=+0(%si):string' > kprobe_events
# 格式:p:探针名 函数名 参数定义

# 启用探针
echo 1 > events/kprobes/myprobe_open/enable

# 追踪
echo 1 > tracing_on
ls /tmp
echo 0 > tracing_on

# 查看结果
cat trace | grep myprobe_open

输出

复制代码
ls-1234  [002] ....  1234.567: myprobe_open: (do_sys_open+0x0/0x1a0) filename="/tmp"
ls-1234  [002] ....  1234.568: myprobe_open: (do_sys_open+0x0/0x1a0) filename="/tmp/."

清理

bash 复制代码
echo 0 > events/kprobes/myprobe_open/enable
echo '-:myprobe_open' >> kprobe_events  # 删除探针

示例 2:获取函数返回值

bash 复制代码
# 在函数返回处插入探针(r 表示 return probe)
echo 'r:myprobe_read_ret vfs_read ret=$retval' > kprobe_events

echo 1 > events/kprobes/myprobe_read_ret/enable
echo 1 > tracing_on
cat /etc/hosts > /dev/null
echo 0 > tracing_on

cat trace | grep myprobe_read_ret

输出

复制代码
cat-2345  [001] ....  1234.567: myprobe_read_ret: (vfs_read+0x120/0x150 <- ksys_read) ret=1024

2. 参数格式

bash 复制代码
# 寄存器(x86_64)
echo 'p:probe do_sys_open arg1=%di arg2=%si arg3=%dx' > kprobe_events

# 字符串
echo 'p:probe do_sys_open filename=+0(%si):string' > kprobe_events

# 指针解引用
echo 'p:probe do_sys_open mode=+16(%si):u32' > kprobe_events

# 结构体字段
echo 'p:probe tcp_sendmsg sk=+0(%di) size=+8(%si)' > kprobe_events

3. Uprobes:用户态探针

Uprobes 可以在用户态函数插入探针(需要符号表)。

bash 复制代码
# 在用户态程序的 malloc 函数插入探针
echo 'p:/lib/x86_64-linux-gnu/libc.so.6:malloc size=%di' > uprobe_events

echo 1 > events/uprobes/p_malloc_0/enable
echo 1 > tracing_on

# 运行某个程序
./my_program

echo 0 > tracing_on
cat trace | grep p_malloc_0

4. 性能与注意事项

特性 Tracepoints Kprobes Uprobes
开销 低(~50-100ns) 中(~500ns-1us) 高(~2-5us)
稳定性 稳定(内核 ABI) 不稳定(函数可能变化) 中(依赖符号表)
灵活性 低(预设点) 高(任意函数) 高(任意函数)
使用场景 通用监控 内核调试 应用调试

最佳实践

  • ✅ 优先使用 tracepoints(开销低、稳定)
  • ✅ Kprobes 用于短期调试(不要长期开启)
  • ✅ 避免在热路径函数(如 schedule)插入 kprobes
  • ⚠️ Kprobes 不要用于生产环境长期监控(改用 eBPF)

✅ 小结与最佳实践

1. 核心要点回顾

  • ftrace 是内核自带的强大跟踪框架,无需编译模块或安装工具
  • function tracer 用于发现频繁调用的内核函数
  • function_graph tracer 用于分析调用链和耗时分布
  • tracepoints 提供低开销的结构化事件追踪(sched/irq/net/block 等)
  • 过滤机制 是控制输出量和性能开销的关键
  • kprobes 可以动态插入探针,但开销较高,适合短期调试

2. 典型分析流程

CPU 占用高
延迟高
调度问题
网络/IO


发现性能问题
确定问题域
function tracer

查找热点函数
function_graph

分析调用链耗时
sched tracepoints

分析调度行为
net/block tracepoints

追踪数据流
设置函数过滤
设置 graph_function
设置 event 过滤
追踪 5-10 秒
分析输出
找到根因?
扩大追踪范围

或更换工具
实施优化
验证效果

3. 性能开销对比

追踪配置 CPU 开销 适用场景
无追踪 0% 正常运行
单个 tracepoint < 1% 生产环境可用
5-10 个 tracepoints 1-3% 生产环境可用
function tracer(过滤 10 个函数) 3-5% 短期调试
function tracer(过滤 100 个函数) 10-15% 短期调试
function tracer(无过滤) 30-50% 不推荐
function_graph(过滤单个函数) 5-10% 短期调试
function_graph(过滤 10 个函数) 20-40% 仅离线分析

4. 常见问题排查清单

症状 快速检查 ftrace 工具
kworker CPU 高 `ps aux grep kworker`
ksoftirqd CPU 高 /proc/softirqs softirq tracepoints
进程频繁 D 状态 `ps aux awk '$8=="D"'`
网络延迟高 netstat -s net tracepoints + function_graph
IO 延迟高 iostat -x 1 block tracepoints + function_graph
调度延迟高 vmstat 1 sched tracepoints
内存分配慢 /proc/slabinfo kmem tracepoints

5. 实用命令速查

bash 复制代码
# === 初始化 ===
cd /sys/kernel/debug/tracing

# === 清理之前的追踪 ===
echo 0 > tracing_on
echo nop > current_tracer
echo > trace
echo > set_ftrace_filter
echo > set_ftrace_pid

# === Function Tracer ===
echo function > current_tracer
echo 'vfs_*' > set_ftrace_filter
echo 1234 > set_ftrace_pid

# === Function Graph Tracer ===
echo function_graph > current_tracer
echo sys_write > set_graph_function

# === Tracepoints ===
echo 1 > events/sched/sched_switch/enable
echo 'prev_pid == 1234' > events/sched/sched_switch/filter

# === 追踪与查看 ===
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
cat trace | less

# === 实时查看 ===
cat trace_pipe

# === 统计分析 ===
cat trace | awk '{print $NF}' | sort | uniq -c | sort -nr | head -10

6. 下一步学习方向

  • trace-cmd:ftrace 的命令行封装工具,使用更简便
  • kernelshark:ftrace 的图形化分析工具
  • eBPF + bpftrace:更强大的内核追踪框架,支持复杂逻辑和聚合
  • perf + ftrace 结合:CPU 热点分析(perf)+ 内核路径分析(ftrace)

🔗 参考资料


下一章预告第 20 篇:trace-cmd 与 kernelshark 可视化分析

我们将学习如何使用 trace-cmd 简化 ftrace 操作,使用 kernelshark 进行时间线可视化分析,并通过实战案例分析网络中断风暴与 CPU 调度冲突问题。

相关推荐
花伤情犹在3 小时前
万物皆可自动化:用 Python 摆脱繁琐点击(以企业微信批量退群为例)
python·自动化·gui·脚本
济6173 小时前
ARM Linux 驱动开发篇---TFTP挂载内核设备树,NFS挂载文件系统教程--解决高版本 Ubuntu的nfs挂载系统失败-- Ubuntu20.04
linux·arm开发·驱动开发
RisunJan3 小时前
Linux命令-lsof(列出所有进程打开的所有资源)
linux·服务器
徐同保3 小时前
python项目:Flask 异步改造实战:从同步到异步的完整指南
python
Trouvaille ~3 小时前
【Linux】TCP可靠性与性能优化详解:从确认应答到拥塞控制
linux·运维·服务器·网络·tcp/ip·性能优化·操作系统
之歆10 小时前
Linux文件系统与FHS详解
linux·文件系统
Ulyanov11 小时前
高保真单脉冲雷达导引头回波生成:Python建模与实践
开发语言·python·仿真·系统设计·单脉冲雷达
Li emily11 小时前
成功接入A股实时行情API获取实时市场数据
人工智能·python·金融·fastapi
zl_dfq12 小时前
Linux 之 【多线程】(死锁、同步与竞态条件、条件变量、pthread_cond_xxx、POSIX信号量、sem_xxx)
linux