# Perfetto看Trace技巧 ## 一、Perfetto基础操作 ### 1. 录制Trace #### 1.1 命令行录制 ```bash # 基础录制命令 adb shell perfetto -c - --out /data/misc/perfetto-traces/trace # 指定时长(10秒) adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s # 指定应用包名 adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -a com.example.app # 拉取Trace文件 adb pull /data/misc/perfetto-traces/trace trace.pb ``` #### 1.2 使用配置文件录制 **启动分析配置文件(startup_config.pb):** ```protobuf buffers: { size_kb: 63488 fill_policy: DISCARD } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_waking" ftrace_events: "sched/sched_process_exit" ftrace_events: "sched/sched_process_free" ftrace_events: "task/task_newtask" ftrace_events: "task/task_rename" ftrace_events: "power/cpu_frequency" ftrace_events: "power/cpu_idle" ftrace_events: "power/suspend_resume" atrace_categories: "am" atrace_categories: "wm" atrace_categories: "gfx" atrace_categories: "view" atrace_categories: "binder_driver" atrace_categories: "binder_lock" atrace_categories: "input" atrace_categories: "res" atrace_categories: "dalvik" buffer_size_kb: 4096 drain_period_ms: 250 } } } data_sources: { config { name: "android.surfaceflinger.frame" } } duration_ms: 10000 ``` **游戏丢帧分析配置文件(jank_config.pb):** ```protobuf buffers: { size_kb: 63488 fill_policy: DISCARD } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_waking" ftrace_events: "power/cpu_frequency" ftrace_events: "power/cpu_idle" ftrace_events: "gfx/mali_gpu_total" ftrace_events: "gpu_mem_total/gpu_mem_total" atrace_categories: "gfx" atrace_categories: "view" atrace_categories: "sched" atrace_categories: "freq" atrace_categories: "idle" buffer_size_kb: 8192 drain_period_ms: 250 } } } data_sources: { config { name: "android.surfaceflinger.frame" } } duration_ms: 30000 ``` #### 1.3 使用配置文件录制 ```bash # 将配置文件推送到设备 adb push startup_config.pb /data/local/tmp/ # 使用配置文件录制 adb shell perfetto -c /data/local/tmp/startup_config.pb -o /data/local/tmp/trace.pb # 拉取Trace文件 adb pull /data/local/tmp/trace.pb trace.pb ``` ### 2. 打开Trace文件 #### 2.1 Web UI打开 1. 访问 https://ui.perfetto.dev/ 2. 点击 "Open trace file" 3. 选择 trace.pb 文件 4. 等待加载完成 #### 2.2 Android Studio打开 1. 打开 Android Studio 2. 连接设备 3. 打开 Profiler 4. 选择 CPU Profiler 5. 点击 "Record" 开始录制 6. 执行操作后停止录制 ## 二、应用冷启动分析技巧 ### 1. 启动流程关键节点 #### 1.1 启动阶段划分 **阶段1:进程创建(Process Creation)** - 查找点:`ActivityManager: startProcess` - 关键指标:进程fork时间、进程初始化时间 - 查看位置:SystemServer进程的ActivityManagerService **阶段2:Application初始化(Application Init)** - 查找点:`ActivityThread: handleBindApplication` - 关键指标:Application.onCreate()耗时 - 查看位置:目标应用主线程 **阶段3:Activity创建(Activity Creation)** - 查找点:`ActivityThread: performLaunchActivity` - 关键指标:Activity.onCreate()耗时 - 查看位置:目标应用主线程 **阶段4:首帧渲染(First Frame)** - 查找点:`Choreographer#doFrame` 或 `SurfaceFlinger: onMessageReceived` - 关键指标:首帧输出时间 - 查看位置:应用主线程、RenderThread、SurfaceFlinger #### 1.2 在Perfetto中定位启动节点 **步骤1:找到应用进程** ``` 1. 在左侧进程列表中找到目标应用进程 2. 展开进程,找到主线程(通常是包名) 3. 点击主线程,查看时间线 ``` **步骤2:查找启动关键事件** ``` 1. 在搜索框输入 "ActivityManager: startProcess" 2. 找到进程启动事件 3. 标记为起点(按M键) ``` **步骤3:查找Application初始化** ``` 1. 在主线程时间线中查找 "handleBindApplication" 2. 展开查看 Application.onCreate() 3. 分析初始化耗时 ``` **步骤4:查找Activity创建** ``` 1. 在主线程时间线中查找 "performLaunchActivity" 2. 展开查看 Activity.onCreate() 3. 分析创建耗时 ``` **步骤5:查找首帧** ``` 1. 在SurfaceFlinger进程中查找 "onMessageReceived" 2. 或查找应用的 "Choreographer#doFrame" 3. 标记为终点(按M键) 4. 计算总耗时 ``` ### 2. 启动性能瓶颈分析 #### 2.1 主线程阻塞分析 **查找主线程Sleep状态:** ``` 1. 选中应用主线程 2. 查看线程状态(Running/Sleep/Runnable) 3. 查找长时间Sleep的片段 4. 分析Sleep原因(等待Binder、等待锁、等待IO等) ``` **常见阻塞场景:** 1. **等待Binder调用** - 特征:主线程处于Sleep状态,等待Binder返回 - 查找:查看wakeup信息,找到对应的Binder调用 - 分析:检查SystemServer中的Binder处理耗时 2. **等待锁** - 特征:主线程处于Sleep状态,等待锁释放 - 查找:查看wakeup信息,找到锁持有者 - 分析:检查锁持有时间是否过长 3. **等待IO** - 特征:主线程处于UninterruptibleSleep-IO状态 - 查找:查看IO操作(文件读写、数据库操作) - 分析:检查IO操作是否在主线程执行 #### 2.2 CPU调度分析 **查看CPU频率:** ``` 1. 在顶部选择 "CPU frequency" 2. 查看启动期间CPU频率变化 3. 检查是否降频或频率不足 ``` **查看CPU使用率:** ``` 1. 在顶部选择 "CPU usage" 2. 查看启动期间CPU使用情况 3. 检查是否有CPU瓶颈 ``` **查看任务调度:** ``` 1. 在CPU核心时间线中查看任务调度 2. 检查关键任务是否跑在小核 3. 检查是否有调度延迟 ``` #### 2.3 内存压力分析 **查看GC活动:** ``` 1. 在主线程中查找 "HeapTaskDaemon" 2. 查看GC频率和耗时 3. 检查是否频繁GC影响启动 ``` **查看内存分配:** ``` 1. 在进程信息中查看内存使用 2. 检查内存分配速度 3. 识别大对象分配 ``` #### 2.4 系统服务分析 **SystemServer分析:** ``` 1. 找到SystemServer进程 2. 查看ActivityManagerService处理耗时 3. 查看WindowManagerService处理耗时 4. 检查是否有锁竞争或处理延迟 ``` **SurfaceFlinger分析:** ``` 1. 找到SurfaceFlinger进程 2. 查看Buffer分配和合成耗时 3. 检查是否有合成延迟 ``` ### 3. 启动优化验证 #### 3.1 对比分析 **优化前后对比:** ``` 1. 录制优化前的Trace 2. 录制优化后的Trace 3. 在Perfetto中同时打开两个Trace 4. 对比关键节点耗时 5. 验证优化效果 ``` **关键指标对比:** - 进程创建时间 - Application初始化时间 - Activity创建时间 - 首帧输出时间 - 总启动时间 #### 3.2 SQL查询分析 **查询启动总耗时:** ```sql -- 查询进程启动到首帧的时间 SELECT (SELECT ts FROM slice WHERE name = 'Choreographer#doFrame' LIMIT 1) - (SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1) AS startup_time_ns; ``` **查询Application初始化耗时:** ```sql -- 查询Application.onCreate耗时 SELECT name, dur / 1000000.0 AS duration_ms FROM slice WHERE name LIKE '%Application.onCreate%' ORDER BY dur DESC LIMIT 10; ``` **查询主线程阻塞时间:** ```sql -- 查询主线程Sleep时间 SELECT SUM(dur) / 1000000.0 AS total_sleep_ms FROM sched WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1) AND state = 'S'; ``` ## 三、游戏丢帧问题分析技巧 ### 1. 帧率分析 #### 1.1 查看帧率信息 **方法1:查看Frame信息** ``` 1. 在顶部选择 "Frame Timeline" 2. 查看每一帧的状态(绿色/黄色/红色) 3. 绿色:正常帧(< 16.67ms) 4. 黄色:轻微掉帧(16.67-33.33ms) 5. 红色:严重掉帧(> 33.33ms) ``` **方法2:查看VSync信息** ``` 1. 在SurfaceFlinger进程中查找 "VSync" 2. 查看VSync周期是否正常(60Hz = 16.67ms) 3. 检查是否有VSync丢失 ``` **方法3:查看Choreographer** ``` 1. 在应用主线程中查找 "Choreographer#doFrame" 2. 查看每一帧的处理时间 3. 识别耗时较长的帧 ``` #### 1.2 丢帧统计 **使用SQL查询丢帧:** ```sql -- 查询所有掉帧(> 16.67ms) SELECT COUNT(*) AS jank_count, AVG(dur) / 1000000.0 AS avg_duration_ms, MAX(dur) / 1000000.0 AS max_duration_ms FROM slice WHERE name = 'Choreographer#doFrame' AND dur > 16666667; ``` **查询连续丢帧:** ```sql -- 查询连续丢帧的情况 SELECT ts, dur / 1000000.0 AS duration_ms FROM slice WHERE name = 'Choreographer#doFrame' AND dur > 16666667 ORDER BY ts; ``` ### 2. 渲染性能分析 #### 2.1 RenderThread分析 **查看RenderThread状态:** ``` 1. 找到应用的RenderThread线程 2. 查看线程状态和执行情况 3. 检查是否有阻塞或延迟 ``` **分析渲染耗时:** ``` 1. 在RenderThread中查找关键操作: - drawFrame:绘制帧 - dequeueBuffer:获取Buffer - queueBuffer:提交Buffer 2. 分析每个操作的耗时 3. 识别渲染瓶颈 ``` **常见渲染问题:** 1. **dequeueBuffer耗时** - 特征:RenderThread等待Buffer分配 - 原因:SurfaceFlinger繁忙或Buffer不足 - 分析:查看SurfaceFlinger进程状态 2. **queueBuffer耗时** - 特征:RenderThread等待Buffer提交 - 原因:SurfaceFlinger处理延迟 - 分析:查看SurfaceFlinger合成耗时 3. **drawFrame耗时** - 特征:RenderThread绘制耗时过长 - 原因:绘制内容复杂、GPU性能不足 - 分析:查看GPU使用情况和绘制命令 #### 2.2 GPU性能分析 **查看GPU使用率:** ``` 1. 在顶部选择 "GPU frequency" 或 "GPU usage" 2. 查看GPU频率和使用率 3. 检查是否有GPU瓶颈 ``` **查看GPU渲染时间:** ``` 1. 在RenderThread中查找GPU相关操作 2. 查看GPU渲染耗时 3. 检查是否有GPU过载 ``` **分析GPU瓶颈:** ``` 1. 查看GPU频率是否达到最大值 2. 查看GPU使用率是否过高 3. 检查是否有GPU调度问题 ``` #### 2.3 SurfaceFlinger分析 **查看合成耗时:** ``` 1. 找到SurfaceFlinger进程 2. 查看合成操作耗时 3. 检查是否有合成延迟 ``` **查看Buffer管理:** ``` 1. 查看Buffer分配和释放 2. 检查Buffer队列状态 3. 识别Buffer泄漏或不足 ``` ### 3. 主线程性能分析 #### 3.1 主线程耗时分析 **查找耗时操作:** ``` 1. 选中应用主线程 2. 按耗时排序(点击Duration列) 3. 查看耗时最长的操作 4. 分析是否可以优化 ``` **使用SQL查询耗时操作:** ```sql -- 查询主线程耗时最长的操作 SELECT name, dur / 1000000.0 AS duration_ms, ts FROM slice WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1) ORDER BY dur DESC LIMIT 20; ``` #### 3.2 主线程阻塞分析 **查找阻塞点:** ``` 1. 查看主线程状态变化 2. 查找长时间Sleep的片段 3. 分析阻塞原因 ``` **常见阻塞场景:** 1. **等待网络请求** - 特征:主线程等待网络响应 - 解决:使用异步网络请求 2. **等待数据库操作** - 特征:主线程等待数据库读写 - 解决:使用异步数据库操作 3. **等待文件IO** - 特征:主线程等待文件读写 - 解决:使用异步IO操作 4. **等待锁** - 特征:主线程等待锁释放 - 解决:优化锁的使用 ### 4. CPU性能分析 #### 4.1 CPU频率分析 **查看CPU频率:** ``` 1. 在顶部选择 "CPU frequency" 2. 查看游戏运行期间CPU频率 3. 检查是否降频或频率不足 ``` **分析频率问题:** ``` 1. 检查CPU频率是否达到最大值 2. 查看是否有温控降频 3. 检查频率调度是否合理 ``` #### 4.2 CPU使用率分析 **查看CPU使用率:** ``` 1. 在顶部选择 "CPU usage" 2. 查看各核心使用情况 3. 检查是否有CPU瓶颈 ``` **分析CPU负载:** ``` 1. 检查CPU使用率是否过高 2. 查看是否有核心过载 3. 检查负载是否均衡 ``` #### 4.3 CPU调度分析 **查看任务调度:** ``` 1. 在CPU核心时间线中查看任务调度 2. 检查关键任务是否跑在小核 3. 查看调度延迟 ``` **分析调度问题:** ``` 1. 检查关键线程是否绑定到高性能核心 2. 查看线程优先级是否合理 3. 检查是否有调度延迟 ``` ### 5. 内存性能分析 #### 5.1 GC压力分析 **查看GC活动:** ``` 1. 在主线程中查找 "HeapTaskDaemon" 2. 查看GC频率和耗时 3. 检查是否频繁GC影响帧率 ``` **分析GC影响:** ``` 1. 检查GC是否在关键帧期间发生 2. 查看GC耗时是否过长 3. 分析GC对帧率的影响 ``` #### 5.2 内存分配分析 **查看内存分配:** ``` 1. 在进程信息中查看内存使用 2. 检查内存分配速度 3. 识别大对象分配 ``` **分析内存问题:** ``` 1. 检查是否有内存泄漏 2. 查看内存碎片化情况 3. 分析内存分配模式 ``` ## 四、Perfetto高级技巧 ### 1. 自定义Trace标记 #### 1.1 在代码中添加Trace **Java/Kotlin代码:** ```java import android.os.Trace; // 开始标记 Trace.beginSection("my_custom_section"); // 执行代码 doSomething(); // 结束标记 Trace.endSection(); ``` **C++代码:** ```cpp #include // 开始标记 ATRACE_BEGIN("my_custom_section"); // 执行代码 doSomething(); // 结束标记 ATRACE_END(); ``` #### 1.2 在Perfetto中查看自定义标记 ``` 1. 在搜索框输入自定义标记名称 2. 找到对应的Trace事件 3. 分析该代码段的性能 ``` ### 2. SQL查询技巧 #### 2.1 常用查询 **查询帧率统计:** ```sql -- 计算平均帧率 SELECT COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps FROM slice WHERE name = 'Choreographer#doFrame'; ``` **查询CPU使用率:** ```sql -- 查询CPU使用率 SELECT cpu, COUNT(*) * 100.0 / (SELECT COUNT(*) FROM sched WHERE cpu = s.cpu) AS usage_percent FROM sched s GROUP BY cpu; ``` **查询内存使用:** ```sql -- 查询内存使用情况 SELECT name, value / 1024.0 / 1024.0 AS size_mb FROM counter WHERE name LIKE '%mem%' ORDER BY ts DESC LIMIT 10; ``` #### 2.2 复杂查询 **查询启动时间分布:** ```sql -- 查询不同阶段的启动时间 SELECT 'Process Creation' AS stage, (SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1) AS start_ts, (SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) AS end_ts, ((SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) - (SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1)) / 1000000.0 AS duration_ms UNION ALL SELECT 'Application Init' AS stage, (SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) AS start_ts, (SELECT ts FROM slice WHERE name LIKE '%performLaunchActivity%' LIMIT 1) AS end_ts, ((SELECT ts FROM slice WHERE name LIKE '%performLaunchActivity%' LIMIT 1) - (SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1)) / 1000000.0 AS duration_ms; ``` ### 3. 对比分析技巧 #### 3.1 多Trace对比 **方法1:时间对齐** ``` 1. 打开多个Trace文件 2. 找到相同的起始事件 3. 对齐时间线 4. 对比关键指标 ``` **方法2:指标对比** ``` 1. 分别计算各Trace的关键指标 2. 制作对比表格 3. 分析差异原因 ``` #### 3.2 版本对比 **优化前后对比:** ``` 1. 录制优化前的Trace(基准版本) 2. 录制优化后的Trace(优化版本) 3. 对比关键指标: - 启动时间 - 帧率 - CPU使用率 - 内存使用 4. 验证优化效果 ``` ### 4. 性能监控技巧 #### 4.1 长时间监控 **录制长时间Trace:** ```bash # 录制30秒的Trace adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 30s # 录制5分钟的Trace adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 5m ``` **分析长时间Trace:** ``` 1. 使用时间轴缩放功能 2. 查找性能问题的时间点 3. 分析性能趋势 ``` #### 4.2 自动化分析 **导出数据:** ``` 1. 使用SQL查询导出数据 2. 使用脚本自动化分析 3. 生成性能报告 ``` ## 五、常见问题排查 ### 1. Trace文件过大 **问题:** Trace文件太大,打开缓慢 **解决方案:** 1. 减少追踪时间 2. 减少追踪类别 3. 使用更小的缓冲区 4. 分段录制和分析 ### 2. 找不到关键事件 **问题:** 在Trace中找不到关键事件 **解决方案:** 1. 确认追踪类别包含相关事件 2. 检查应用是否添加了Trace标记 3. 使用搜索功能查找 4. 查看相关进程和线程 ### 3. 性能数据不准确 **问题:** Trace中的性能数据与实际不符 **解决方案:** 1. 确认追踪配置正确 2. 检查是否有其他进程干扰 3. 多次录制取平均值 4. 对比其他性能工具 ### 4. 分析效率低 **问题:** 分析Trace耗时过长 **解决方案:** 1. 使用SQL查询快速定位问题 2. 使用书签标记关键位置 3. 使用筛选功能缩小范围 4. 建立分析模板和流程 ## 六、最佳实践 ### 1. 录制Trace 1. **明确目标**:在录制前明确要分析的问题 2. **合适时长**:选择适当的追踪时间(启动5-10秒,游戏30-60秒) 3. **关键类别**:只追踪相关的类别,减少文件大小 4. **多次录制**:多次录制确保结果一致 5. **环境一致**:保持测试环境一致(温度、电量、后台应用等) ### 2. 分析Trace 1. **系统化分析**:按照固定流程分析(启动流程、渲染流程等) 2. **关键指标**:关注关键性能指标(启动时间、帧率等) 3. **对比分析**:与基准版本或竞品对比 4. **深入分析**:找到根本原因,不只是表面现象 5. **验证优化**:优化后再次录制验证效果 ### 3. 优化验证 1. **量化指标**:使用具体数值衡量优化效果 2. **多维度验证**:从多个角度验证优化效果 3. **回归测试**:确保优化没有引入新问题 4. **持续监控**:建立性能监控体系 --- *最后更新:2024年*