836 lines
18 KiB
Markdown
836 lines
18 KiB
Markdown
|
|
# 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 <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年*
|