Perfetto简易入门
Perfetto 简易入门
参考链接:Perfetto详细解析
一、Perfetto 基础
1. Perfetto 介绍
Perfetto 是一个生产级的开源堆栈,用于性能插桩和 trace 分析。与 Systrace 不同,它提供了数据源的超集,可以用 protobuf 编码的二进制流形式记录任意长度的跟踪记录。
可以将 Perfetto 理解为 Systrace 的升级版,适用于更新的平台,以更丰富的图表展示更多信息。它可帮助开发者收集 Android 关键子系统(如 SurfaceFlinger、SystemServer、Input、Display 等 Framework 部分关键模块、服务,View 系统等)的运行信息,从而更直观地分析系统瓶颈、改进性能。
Perfetto 基本功能包括:
更多官方文档参考:perfetto.dev
2. Perfetto 使用流程
使用 Perfetto 前,需先了解其基本流程:
- 在手机上准备好要抓取的界面
- 点击开始抓取(命令行的话就是执行命令)
- 在手机上操作(不要太长时间,文件过大会很卡,且不好定位问题)
- 设定的时间到了之后,将生成的 trace 文件用 Perfetto UI 打开分析
3. 抓取 Trace 的几种方式
3.1 使用 perfetto 命令抓取
命令行形式比较灵活,速度也快。相关的 TAG 一次性配置好后,再次使用会很快出结果;也可以写成脚本,方便复用。
常用参数说明:
| 参数 | 说明 |
|---|---|
-o |
输出文件的路径和名字 |
-t |
抓取时间(最新版本可以不指定,按 Enter 即可结束) |
-b |
指定 buffer 大小(默认 Buffer 一般够用;抓很长的 Trace 建议调大) |
-a |
指定 app 包名(如果 Debug 自定义 Trace 点,记得加这个) |
在低于 Android R 的版本上,perfetto 默认是关闭的,需要先执行以下命令开启:
adb shell setprop persist.traced.enable 1
抓取并导出 perfetto-trace:
# 抓取 trace(可根据需要增减 tag)
adb shell perfetto \
-o /data/misc/perfetto-traces/trace_file.perfetto-trace \
-t 10s \
sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
# 导出 trace 文件到本地
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace
3.2 使用 atrace 命令抓取
# 抓取 atrace
adb shell atrace -z -b 40000 \
am wm view res ss gfx view hal bionic pm sched freq idle disk load sync \
binder_driver binder_lock memreclaim dalvik input \
-t 10 \
> /data/local/tmp/trace_output.atrace
# 导出 trace 文件
adb pull /data/local/tmp/trace_output.atrace
3.3 使用 systrace 脚本抓取
- 配置好 Python 环境
- 解压
systrace.zip - 进入解压后的 systrace 目录,找到
systrace.py文件,执行:
python systrace.py \
am wm view res ss gfx rs hal bionic pm sched freq idle disk \
binder_driver binder_lock memreclaim dalvik input database \
-t 10 \
-o tracelog/systrace.html
生成的 systrace.html 文件在 systrace/tracelog/ 目录下。
3.4 通过手机 System Tracing 抓取
- 打开 开发者选项 → 系统跟踪(System Tracing)→ 开启 Show Quick Settings tile,添加快捷图标
- 在设置中配置需要录制的 类别(Categories) 和 缓冲区大小(Buffer Size)
- 点击 录制跟踪记录(Record trace) 开始录制
- 复现问题或进行特定操作
- 完成后点击通知栏中的 点按即可停止跟踪 停止并保存
- 弹出 “trace saved” 通知后,文件保存在
/data/local/traces/ - 使用
adb pull /data/local/traces/将 trace 文件导出到本地
4. 查看线程状态
Perfetto 会用不同颜色标识不同线程状态。在每个方法上面都有对应的线程状态标记,通过查看线程状态可以判断当前瓶颈所在——是 CPU 执行慢、Binder 调用、IO 操作,还是拿不到 CPU 时间片。
4.1 绿色 — 运行中(Running)
只有处于该状态的线程才可能在 CPU 上运行。同一时刻可能有多个线程处于可执行状态,这些线程的 task_struct 结构被放入对应 CPU 的可执行队列(一个线程最多只能出现在一个 CPU 的可执行队列中)。调度器从各个 CPU 的可执行队列中分别选择一个线程在该 CPU 上运行。
分析方向:
- 是否频率不够?
- 是否跑在了小核上?
- 是否频繁在 Running 和 Runnable 之间切换?
- 是否频繁在 Running 和 Sleep 之间切换?
- 是否跑在了不该跑的核上?(如不重要的线程占用了超大核)
4.2 蓝色 — 可运行(Runnable)
线程可以运行但当前没有被调度,正在等待 CPU 调度。
分析方向:
- 是否后台有太多任务在跑?
- 是否频率太低导致处理不及时?
- 是否被限制到某个 cpuset 里,但该 CPU 负载已满?
- 此时 Running 的任务是什么?为什么?
4.3 白色 — 休眠中(Sleep)
线程没有工作要做,可能是因为在互斥锁上被阻塞,也可能在等待某个线程返回。
分析方向:查看被谁唤醒,确认等待的目标线程。
4.4 橘色 — 不可中断睡眠态(Uninterruptible Sleep - IO Block)
线程在 I/O 上被阻塞或等待磁盘操作完成,通常底部会标识出 callsite:wait_on_page_locked_killable。
分析方向:大量橘色出现时,一般是由于低内存状态下申请内存触发 page fault,Linux 的 page cache 链表中有些 page 还没准备好(磁盘内容未完全读出),此时访问该 page 就会出现 wait_on_page_locked_killable 阻塞。当 IO 操作繁忙、每笔 IO 都需要排队时,极易出现且阻塞时间往往较长。
4.5 棕色 — 不可中断睡眠态(Uninterruptible Sleep - non-IO)
线程在另一个内核操作(通常是内存管理)上被阻塞。
分析方向:一般陷入了内核态,需根据具体情况分析是否正常。
5. 任务唤醒信息分析
线程被唤醒的信息非常重要。知道线程被谁唤醒,就能理清调用等待关系。如果某线程出现较长的 Sleep 后被唤醒,可查看是谁唤醒了它,分析唤醒者为什么这么晚才唤醒。
典型场景一:应用主线程通过 Binder 与 SystemServer 的 AMS 通信,但 AMS 的函数正在等待锁释放(或函数本身执行时间很长),导致应用主线程长时间等待,出现响应慢或卡顿。这就是后台大量进程运行或跑完 Monkey 后整机性能下降的主要原因。
典型场景二:应用主线程在等待本应用其他线程的执行结果。此时线程唤醒信息可用于分析主线程究竟被哪个线程 Block。
操作方式:点击 Sleeping 之后的 Running 段,查看它运行在哪个 CPU 上,以及是哪个线程唤醒它的,从而判断是否存在异常。
6. 信息区数据解析
6.1 CPU 架构
目前手机的 CPU 按核心数和架构分为三类:
- 非大小核架构:所有核心相同
- 大小核架构:通常 0-3 为小核,4-7 为大核
- 大中小核架构:通常 0-3 为小核,4-6 为中核,7 为超大核
6.2 CPU Info
Perfetto 中 CPU Info 信息一般在最上方,常用的分析维度:
- CPU 频率变化情况
- 任务执行情况
- 大小核的调度情况
- CPU Boost 调度情况
典型分析问题:
- 任务执行慢 → 是否被调度到了小核?
- 任务执行慢 → 当前 CPU 频率是否不够?
- 特殊任务 → 能否调度到大核执行?
- 场景对 CPU 要求高 → 能否限制 CPU 最低频率?
7. 快捷键
掌握快捷键可大幅提升 Perfetto 查看效率:
| 快捷键 | 功能 |
|---|---|
| W | 放大(查看局部细节) |
| S | 缩小(查看整体) |
| A | 左移 |
| D | 右移 |
| M | 选中该时间段范围,方便上下对照 |
| Ctrl + F | 搜索(与自带搜索框配合使用,自带搜索需 4 个字符以上) |
8. 何为刷新率
- 60 fps 指画面每秒更新 60 次(针对软件),每次更新约为 1000ms / 60 ≈ 16.67 ms
- 这 60 次更新需要匀速,时快时慢同样会让视觉不流畅
- 屏幕刷新率针对硬件。目前大部分手机屏幕刷新率维持在 60Hz,移动设备上使用 60Hz 是因为对功耗要求更高——提高刷新率意味着逻辑功耗线性增大,同时更短的 TFT 数据写入时间对屏幕设计难度也更大
二、主线程与渲染线程
1. 主线程的创建
Android App 的进程基于 Linux,其管理也基于 Linux 的进程管理机制,因此创建过程同样调用了 fork 系统调用。
frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
/**
* Zygote 进程通过 fork() 创建新的 App 进程。
* fork 之后子进程继承了父进程的地址空间,
* 然后通过 exec 或直接调用 ActivityThread.main() 进入 App 主线程逻辑。
*/
pid_t pid = fork();
if (pid == 0) {
// 子进程:进入 App 进程初始化流程
// ...
} else if (pid > 0) {
// 父进程(Zygote):记录子进程 pid,继续等待下一个请求
// ...
} else {
// fork 失败处理
ALOGE("Fork failed: %s", strerror(errno));
}
Fork 出来的进程可以视为主线程,但此时它还未与 Android 运行时连接,无法处理 Android App 的 Message。由于 Android App 基于消息机制运行,这个 Fork 出来的主线程需要与 Android Message 体系绑定,才能处理各种消息。
这里引入 ActivityThread。确切地说,ActivityThread 更应该叫 ProcessThread——它连接了 Fork 出来的进程和 App 的 Message 机制,二者配合组成了我们熟知的 Android App 主线程。
关键认知:ActivityThread 并不是一个 Thread,它只是初始化了 Message 机制所需的 MessageQueue、Looper、Handler,其 Handler 负责处理大部分 Message 消息。因此我们习惯上认为 ActivityThread 是"主线程",其实它只是主线程的一个逻辑处理单元。
1.1 ActivityThread 的创建
App 进程 fork 出来后,查找 ActivityThread 的 main 函数入口。
com/android/internal/os/ZygoteInit.java
static final Runnable childZygoteInit(
int targetSdkVersion, String[] argv, ClassLoader classLoader) {
RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader);
}
这里的 args.startClass 就是 ActivityThread。找到之后调用,逻辑进入 ActivityThread.main()。
android/app/ActivityThread.java
public static void main(String[] args) {
// 1. 初始化主线程 Looper、MessageQueue
Looper.prepareMainLooper();
// 2. 实例化 ActivityThread
ActivityThread thread = new ActivityThread();
// 3. 调用 AMS.attachApplicationLocked,同步进程信息,完成初始化
thread.attach(false, startSeq);
// 4. 获取主线程 Handler(H 类),App 的 Message 绝大部分在此处理
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 5. 初始化完成,Looper 开始轮询消息队列
Looper.loop();
// 注意:Looper.loop() 是死循环,后面的代码正常情况下不会执行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
startSeq是由 AMS 分配的进程启动序列号,在进程 attach 前传入,用于跟踪和去重。
main 函数执行完成后,主线程正式上线工作。
文档完结。如需补充其他 Perfetto 进阶用法(如自定义 Trace 点、SQL 查询分析、堆分析等),可进一步查阅 Perfetto 官方文档。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)