Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门


文章目录

  • [Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门](#Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门)
    • [1. ftrace / tracefs:先理解控制面和数据面](#1. ftrace / tracefs:先理解控制面和数据面)
    • [2. trace_printk / trace_marker:把代码段变成时间线](#2. trace_printk / trace_marker:把代码段变成时间线)
    • [3. TRACE_EVENT:把临时调试升级为可维护的静态跟踪点](#3. TRACE_EVENT:把临时调试升级为可维护的静态跟踪点)
    • [4. Android 抓 trace:atrace、systrace、perfetto 的关系](#4. Android 抓 trace:atrace、systrace、perfetto 的关系)
      • [4.1 atrace:快速抓一段原始 trace](#4.1 atrace:快速抓一段原始 trace)
      • [4.2 systrace:旧式 HTML 时间线](#4.2 systrace:旧式 HTML 时间线)
      • [4.3 perfetto:现代 Android trace 管线](#4.3 perfetto:现代 Android trace 管线)
    • [5. 实战选择:遇到问题时怎么选工具](#5. 实战选择:遇到问题时怎么选工具)
    • [6. 常见坑](#6. 常见坑)
      • [6.1 ring buffer 太小](#6.1 ring buffer 太小)
      • [6.2 忘记清空旧 trace](#6.2 忘记清空旧 trace)
      • [6.3 打开了事件但没有打开 tracing_on](#6.3 打开了事件但没有打开 tracing_on)
      • [6.4 自定义事件在 perfetto 中看不到](#6.4 自定义事件在 perfetto 中看不到)
      • [6.5 trace_printk 长期留在正式代码里](#6.5 trace_printk 长期留在正式代码里)
    • 总结
    • 参考

Linux/Android 跟踪技术:ftrace、TRACE_EVENT、atrace、systrace 与 perfetto 入门

定位内核和 Android 系统问题时,logcatdmesgprintk 能告诉我们"发生了什么",但通常不能稳定回答几个更关键的问题:

  • 它是什么时候发生的?
  • 当时哪个 CPU、哪个进程、哪个中断上下文在运行?
  • 一段代码到底耗时多久?
  • 线程是被谁抢占、唤醒、阻塞的?
  • 用户态行为和内核事件能不能放在同一条时间线上看?

这就是 tracing 要解决的问题。本文按一条主线梳理 Linux/Android 跟踪技术:

  1. Linux 侧用 ftrace/tracefs 抓内核事件。
  2. 临时调试用 trace_printk 或 trace marker 快速打点。
  3. 长期维护用 TRACE_EVENT 添加静态跟踪点。
  4. Android 侧用 atracesystraceperfetto 收集和可视化 trace。

1. ftrace / tracefs:先理解控制面和数据面

ftrace 是 Linux 内核内置的跟踪框架。它最初常被理解成 function tracer,但实际已经是一组跟踪能力的集合:函数跟踪、事件跟踪、延迟分析、function graph、kprobe/uprobe、trace marker 等。

使用时最常接触的是 tracefs:

shell 复制代码
/sys/kernel/tracing

# 某些旧系统或旧挂载方式下也可能在:
/sys/kernel/debug/tracing

可以先看几个核心节点:

节点 作用
available_events 当前系统支持的 trace event 列表
events/<group>/<event>/enable 打开或关闭某个事件
set_event 通过写事件名批量打开事件
current_tracer 当前 tracer,例如 nopfunctionfunction_graph
tracing_on 全局开始/停止记录
buffer_size_kb 每 CPU ring buffer 大小
trace 读取当前缓冲区内容,类似快照
trace_pipe 流式读取,读走后从 ring buffer 消费掉
trace_marker 用户态向 trace 写标记,常用于用户态/内核态时间线对齐

一个最小抓取流程如下:

shell 复制代码
cd /sys/kernel/tracing

# 1. 清空旧数据
echo > trace

# 2. 打开一个事件
echo 1 > events/sched/sched_switch/enable

# 3. 开始记录
echo 1 > tracing_on

# 4. 复现问题后停止记录
echo 0 > tracing_on

# 5. 读取结果
cat trace > /data/local/tmp/trace.log

trace 输出中常见的表头大概是这样:

text 复制代码
# tracer: nop
#
# entries-in-buffer/entries-written: 557557/557557   #P:8
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| / _-=> migrate-disable
#                              |||| /     delay
#           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
#              | |         |   |||||     |         |

理解这个表头很重要。它不是单纯的字符串日志,而是带了任务、PID、CPU、抢占/中断状态、时间戳和事件名的结构化时间线。

tracetrace_pipe 的区别也要分清:

shell 复制代码
# 像拍快照:读取当前 buffer 内容
cat trace

# 像水管:边产生边消费,适合持续保存
cat trace_pipe > trace.log

如果只是复现一次问题,通常用 trace。如果要持续观察或担心 ring buffer 覆盖旧事件,可以考虑 trace_pipe

2. trace_printk / trace_marker:把代码段变成时间线

当问题出现在自己的驱动或模块里,已有事件不一定够用。最直接的办法是在关键路径上打点。

内核里可以用 trace_printk()

c 复制代码
trace_printk("irq enter, status=%d\n", status);

它和 printk() 不同,输出进入 ftrace ring buffer,更适合看快速路径上的时间顺序。但它只适合临时调试,不建议长期保留在正式代码里。原因是 trace_printk() 会引入额外缓冲和调试痕迹,内核文档也明确把它定位为开发调试工具。

如果希望 trace 能被 systrace/perfetto 这类工具识别成时间线,常见格式是:

text 复制代码
B|pid|name          # Begin,一个区间开始
E|pid|              # End,一个区间结束
C|pid|name|value    # Counter,一个计数值变化

可以封装成类似 Android ATrace 的宏:

c 复制代码
extern void tracing_mark_write(char trace_type,
                               const char *name,
                               bool is_int,
                               int value);

#define ATRACE_BEGIN(name) tracing_mark_write('B', name, 0, 0)
#define ATRACE_INT(name, value) tracing_mark_write('C', name, 1, value)
#define ATRACE_END() tracing_mark_write('E', "", 0, 0)

void tracing_mark_write(char trace_type, const char *name, bool is_int, int value)
{
    if (is_int) {
        trace_printk("%c|%d|%s|%d\n", trace_type, current->tgid, name, value);
    } else {
        trace_printk("%c|%d|%s\n", trace_type, current->tgid, name);
    }
}

抓到的 ftrace 可能长这样:

text 复制代码
irq/358-8030_io-1755 [000] 128.277067: tracing_mark_write: B|1755|Nanosic_i2c_irq
irq/358-8030_io-1755 [000] 128.277070: tracing_mark_write: C|1755|work_buf|1
irq/358-8030_io-1755 [000] 128.277100: tracing_mark_write: E|1755|

可视化工具识别 B/E/C 后,就能把它画成区间和计数器曲线。这样我们不只知道"函数打印了什么",还能看到:

  • IRQ 处理区间持续了多久;
  • work queue 是否及时调度;
  • 某个变量在时间线上什么时候变成 1 或 0;
  • 自定义事件和系统调度事件之间的先后关系。

注意两个细节:

  • BE 要配对,否则时间线会断。
  • pidname 要稳定,否则不同事件会被工具归到错误轨道。

3. TRACE_EVENT:把临时调试升级为可维护的静态跟踪点

trace_printk() 的问题是"好用但不适合长期留下"。如果要把跟踪能力提交到正式驱动里,应该优先考虑 TRACE_EVENT

TRACE_EVENT 的价值可以概括成四点:

  • 字段类型稳定,不依赖临时字符串;
  • 事件可以通过 tracefs 按 group 或单事件打开关闭;
  • 关闭时开销低,适合默认关闭、排障时打开;
  • 不只 ftrace 能用,perf、LTTng、Perfetto 等工具也能消费这些事件。

一个简化的自定义事件头文件如下:

c 复制代码
#undef TRACE_SYSTEM
#define TRACE_SYSTEM nanosic

#if !defined(_NANOSIC_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _NANOSIC_TRACE_H_

#include <linux/tracepoint.h>

TRACE_EVENT(tracing_mark_write,
    TP_PROTO(char trace_type,
             const struct task_struct *task,
             const char *name,
             int value),

    TP_ARGS(trace_type, task, name, value),

    TP_STRUCT__entry(
        __field(char, trace_type)
        __field(int, pid)
        __string(trace_name, name)
        __field(int, value)
    ),

    TP_fast_assign(
        __entry->trace_type = trace_type;
        __entry->pid = task ? task->tgid : 0;
        __assign_str(trace_name, name);
        __entry->value = value;
    ),

    TP_printk("%c|%d|%s|%d",
              __entry->trace_type,
              __entry->pid,
              __get_str(trace_name),
              __entry->value)
);

#define ATRACE_BEGIN(name) trace_tracing_mark_write('B', current, name, 0)
#define ATRACE_INT(name, value) trace_tracing_mark_write('C', current, name, value)
#define ATRACE_END() trace_tracing_mark_write('E', current, "", 0)

#endif

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .

#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE nanosic_trace

#include <trace/define_trace.h>

然后在一个 C 文件中生成实现:

c 复制代码
#define CREATE_TRACE_POINTS
#include "nanosic_trace.h"

编译后,tracefs 中会出现类似节点:

shell 复制代码
/sys/kernel/tracing/events/nanosic/tracing_mark_write/enable

使用时打开事件:

shell 复制代码
adb shell "echo 1 > /sys/kernel/tracing/events/nanosic/tracing_mark_write/enable"
adb shell "echo 1 > /sys/kernel/tracing/events/nanosic/enable"

再触发代码路径,读取 trace:

shell 复制代码
adb shell "cat /sys/kernel/tracing/trace" > trace.log

这套机制比 trace_printk() 更适合工程化。对于驱动团队来说,一个好的判断标准是:

场景 建议
本地临时验证猜想 trace_printk()
用户态快速插入时间线标记 trace_marker / ATrace
需要提交到正式驱动、后续可开关排障 TRACE_EVENT
需要跨工具分析 TRACE_EVENT 或系统已有 trace event

4. Android 抓 trace:atrace、systrace、perfetto 的关系

Android 上经常会同时听到 atracesystraceperfetto。它们不是简单的替代关系。

可以先这样理解:

  • atrace:设备端命令,负责按 category 打开/关闭一批 ftrace/atrace 事件,并导出原始 trace。
  • systrace:一套旧式工具链,底层使用 atrace,最终生成 HTML 时间线。
  • perfetto:现代 Android tracing 管线,能采集更丰富的数据源,输出二进制 trace,并用 Perfetto UI 分析。

4.1 atrace:快速抓一段原始 trace

查看支持的 category:

shell 复制代码
adb shell atrace --list_categories

抓一段 10 秒 trace:

shell 复制代码
adb shell "atrace -b 10240 -t 10 -o /sdcard/atrace.log \
  gfx input view webview wm am sched irq freq idle disk workq i2c aidl"

adb pull /sdcard/atrace.log

atrace 适合快速确认事件有没有出现、时间顺序是否符合预期。缺点是产物偏原始,复杂分析不如 Perfetto UI 方便。

4.2 systrace:旧式 HTML 时间线

systrace.py 通常在 Android 源码树的 external/chromium-trace 目录下。典型用法:

shell 复制代码
python systrace.py -b 10240 -t 10 -o trace.html \
  gfx input view webview wm am sched irq freq idle disk workq i2c aidl

它会通过设备端 atrace 控制采集,然后把结果打包成 trace.html。在旧项目或已有流程里还会遇到它,但新系统级性能分析更建议优先看 Perfetto。

4.3 perfetto:现代 Android trace 管线

Perfetto 可以直接在设备上运行:

shell 复制代码
adb shell perfetto \
  -c /data/local/tmp/config.pbtxt \
  -o /data/misc/perfetto-traces/trace

adb pull /data/misc/perfetto-traces/trace

如果只是快速抓常见 Android trace,也可以用轻量参数:

shell 复制代码
adb shell perfetto -t 10s -b 32mb -o /data/misc/perfetto-traces/trace \
  sched freq idle am wm gfx view input

如果需要完整控制,推荐写配置文件。与 ftrace 相关的核心配置是 linux.ftrace data source,例如:

protobuf 复制代码
buffers {
  size_kb: 32768
  fill_policy: RING_BUFFER
}

data_sources {
  config {
    name: "linux.ftrace"
    ftrace_config {
      ftrace_events: "sched/sched_switch"
      ftrace_events: "sched/sched_wakeup"
      ftrace_events: "irq/irq_handler_entry"
      ftrace_events: "irq/irq_handler_exit"
      ftrace_events: "nanosic/*"
      atrace_categories: "gfx"
      atrace_categories: "input"
      atrace_categories: "view"
      atrace_apps: "*"
      buffer_size_kb: 32768
    }
  }
}

duration_ms: 10000

自定义 TRACE_EVENT 想在 Perfetto 里看到,关键是配置里要包含对应 ftrace event,例如:

protobuf 复制代码
ftrace_events: "nanosic/*"

5. 实战选择:遇到问题时怎么选工具

不要从工具名字开始选,要从问题开始选。

问题 推荐路径
想知道线程为何卡住、谁抢占了谁 打开 sched/*,用 ftrace 或 perfetto 看调度时间线
想确认驱动 IRQ 是否进来、耗时多久 打开 irq/*,必要时加自定义 TRACE_EVENT
想快速验证某个变量变化 临时 trace_printk()ATRACE_INT()
想长期保留驱动打点能力 定义 TRACE_EVENT,默认关闭,排障时开启
想把 Android 应用、Framework、内核放到一张时间线 优先 Perfetto
只想快速抓原始 ftrace 文本 atrace 或直接读 tracefs
需要交付旧流程中的 HTML systrace.py

一个相对稳妥的排障流程:

text 复制代码
1. 先用已有 event 抓一版:sched、irq、freq、idle、binder、i2c 等。
2. 如果现有 event 不够,再在自己的模块里加临时 trace_printk。
3. 如果这个点后续还会反复用,把它改成 TRACE_EVENT。
4. 简单文本分析用 trace/trace_pipe;系统级复杂时间线用 Perfetto UI。

6. 常见坑

6.1 ring buffer 太小

trace 是 ring buffer,事件太多会覆盖旧数据。复现窗口长、事件密度高时,要调大 buffer:

shell 复制代码
echo 32768 > /sys/kernel/tracing/buffer_size_kb

6.2 忘记清空旧 trace

复现前建议先清空:

shell 复制代码
echo > /sys/kernel/tracing/trace

否则新旧事件混在一起,很容易误判。

6.3 打开了事件但没有打开 tracing_on

事件 enable 和全局 tracing_on 是两件事。调试时可以明确写:

shell 复制代码
echo 1 > events/sched/sched_switch/enable
echo 1 > tracing_on

6.4 自定义事件在 perfetto 中看不到

先确认三件事:

shell 复制代码
cat /sys/kernel/tracing/available_events | grep nanosic
cat /sys/kernel/tracing/events/nanosic/tracing_mark_write/enable
cat /sys/kernel/tracing/trace

如果 ftrace 原始 trace 里有,Perfetto 里没有,通常是 Perfetto 配置没有包含对应 ftrace_events

6.5 trace_printk 长期留在正式代码里

trace_printk() 是调试工具。临时验证可以,用完应删除或升级为 TRACE_EVENT

总结

Linux/Android tracing 的核心不是背命令,而是建立一条数据流心智模型:

text 复制代码
事件源 → tracefs 开关 → per-CPU ring buffer → trace/trace_pipe/perfetto → 时间线分析

trace_printk() 解决"我现在想快速知道这里发生了什么",TRACE_EVENT 解决"这个事件以后还要被稳定观测",atrace/systrace/perfetto 解决"如何在 Android 上采集和查看"。把这三层关系理清后,面对卡顿、IRQ 延迟、线程调度、驱动响应慢等问题,就能从日志排查升级到时间线排查。

参考

相关推荐
空中海2 小时前
安卓逆向03. 动态调试、抓包分析与 Frida Hook
android
比昨天多敲两行2 小时前
Linux基础开发工具(下)
linux·运维·服务器
一起搞IT吧3 小时前
相机Camera日志实例分析之二十:相机Camx【照片后置4800/5000/6400万拍照】单帧流程日志详解
android·嵌入式硬件·数码相机·智能手机
feng14563 小时前
OpenSREClaw - 故障复盘和变更评审双 Agent 案例
运维·人工智能
linux修理工3 小时前
chrome官方下载地址
运维·服务器
无忧智库4 小时前
IT运维正在经历一场真正的范式革命:从告警风暴到AIOps自主自愈的完整工程解构(WORD)
运维
jinanwuhuaguo4 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
笨笨饿4 小时前
69_如何给自己手搓一个串口
linux·c语言·网络·单片机·嵌入式硬件·算法·个人开发
cn_lyg4 小时前
Linux的入门级常用操作命令
linux·运维·服务器