Files
mkdocs/docs/学习笔记/perfeto看trace技巧.md

836 lines
18 KiB
Markdown
Raw Permalink Normal View History

2026-02-11 00:40:25 +08:00
# 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
**阶段2Application初始化Application Init**
- 查找点:`ActivityThread: handleBindApplication`
- 关键指标Application.onCreate()耗时
- 查看位置:目标应用主线程
**阶段3Activity创建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 <utils/Trace.h>
// 开始标记
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年*