文章目录
- [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 系统问题时,logcat、dmesg、printk 能告诉我们"发生了什么",但通常不能稳定回答几个更关键的问题:
- 它是什么时候发生的?
- 当时哪个 CPU、哪个进程、哪个中断上下文在运行?
- 一段代码到底耗时多久?
- 线程是被谁抢占、唤醒、阻塞的?
- 用户态行为和内核事件能不能放在同一条时间线上看?
这就是 tracing 要解决的问题。本文按一条主线梳理 Linux/Android 跟踪技术:
- Linux 侧用
ftrace/tracefs抓内核事件。 - 临时调试用
trace_printk或 trace marker 快速打点。 - 长期维护用
TRACE_EVENT添加静态跟踪点。 - Android 侧用
atrace、systrace、perfetto收集和可视化 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,例如 nop、function、function_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、抢占/中断状态、时间戳和事件名的结构化时间线。
trace 和 trace_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;
- 自定义事件和系统调度事件之间的先后关系。
注意两个细节:
B和E要配对,否则时间线会断。pid和name要稳定,否则不同事件会被工具归到错误轨道。
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 上经常会同时听到 atrace、systrace、perfetto。它们不是简单的替代关系。
可以先这样理解:
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 延迟、线程调度、驱动响应慢等问题,就能从日志排查升级到时间线排查。
参考
- Linux Kernel Documentation: Linux Tracing Technologies, https://docs.kernel.org/trace/index.html
- Linux Kernel Documentation: ftrace, https://docs.kernel.org/trace/ftrace.html
- LWN: Using the TRACE_EVENT() macro, https://lwn.net/Articles/379903/
- Perfetto Documentation, https://perfetto.dev/docs/
- Android Developers: Overview of system tracing, https://developer.android.com/topic/performance/tracing/
- Android Developers: Capture a system trace on the command line, https://developer.android.com/topic/performance/tracing/command-line
- Android Developers: perfetto, https://developer.android.com/tools/perfetto