diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 1ff1997..9e55935 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -27,16 +27,30 @@ "state": { "type": "markdown", "state": { - "file": "docs/学习笔记/高级性能问题分析目录.md", + "file": "docs/Obsidian笔记体系/Daily/2026-01-13.md", "mode": "source", "source": false }, "icon": "lucide-file", - "title": "高级性能问题分析目录" + "title": "2026-01-13" + } + }, + { + "id": "59894255df52f532", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "知你--调测" } } ], - "currentTab": 1 + "currentTab": 2 } ], "direction": "vertical" @@ -198,16 +212,24 @@ "command-palette:打开命令面板": false } }, - "active": "25c9f7051aac05b3", + "active": "59894255df52f532", "lastOpenFiles": [ - "docs/学习笔记/php/未命名.md", - "docs/学习笔记/高级性能问题分析目录.md", + "docs/Obsidian笔记体系/Daily/2026-01-14.md", + "docs/Obsidian笔记体系/Daily/2024-06-02.md", + "docs/Obsidian笔记体系/Daily/2026-01-13.md", + "docs/Obsidian笔记体系/Daily/2026-01-15.md", + "docs/Obsidian笔记体系/Daily/2026-01-20.md", "docs/学习笔记/高级性能问题分析.md", + "docs/学习笔记/高级性能问题分析目录.md", + "docs/学习笔记/产品经理/需求管理详解.md", + "docs/学习笔记/产品经理/项目管理详解.md", + "docs/Obsidian/高频命令.md", + "docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md", "docs/学习笔记/基础性能问题分析(ODM).md", + "docs/Obsidian笔记体系/Areas/09-调试与工具链/Systrace_Perfetto全解读.md", + "docs/学习笔记/php/未命名.md", "docs/git/git设置用户名和邮箱.md", "docs/学习笔记/产品经理/产品经理--写文档.md", - "docs/学习笔记/产品经理/项目管理详解.md", - "docs/学习笔记/产品经理/需求管理详解.md", "docs/学习笔记/产品经理/沟通与表达详解.md", "docs/学习笔记/产品经理/数据分析详解.md", "docs/学习笔记/产品经理/产品运营详解.md", @@ -219,14 +241,6 @@ "docs/学习笔记/产品经理/产品经理--些迭代文档.md", "docs/学习笔记/产品经理", "docs/产品经理/沟通与表达详解.md", - "docs/产品经理/产品运营详解.md", - "docs/产品经理/数据分析详解.md", - "docs/git/git常用命令.md", - "docs/cursor/cursor.md", - "docs/产品经理/产品经理技能.md", - "docs/产品经理/项目管理详解.md", - "docs/产品经理/产品设计详解.md", - "docs/产品经理/需求管理详解.md", "Pasted image 20260129111501.png", "Pasted image 20260129111451.png", "Pasted image 20260129111437.png", diff --git a/docs/学习笔记/perfeto看trace技巧.md b/docs/学习笔记/perfeto看trace技巧.md new file mode 100644 index 0000000..fbf8c33 --- /dev/null +++ b/docs/学习笔记/perfeto看trace技巧.md @@ -0,0 +1,835 @@ +# 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年* diff --git a/docs/学习笔记/perfetto的使用技巧.md b/docs/学习笔记/perfetto的使用技巧.md new file mode 100644 index 0000000..d24a35a --- /dev/null +++ b/docs/学习笔记/perfetto的使用技巧.md @@ -0,0 +1,984 @@ +# Perfetto使用技巧 + +## 一、Perfetto简介 + +### 1. 什么是Perfetto + +Perfetto是Google开发的下一代系统级性能分析工具,从Android 9(API 28)开始集成到系统中。它是Systrace的升级版,提供了更强大的功能和更好的性能。 + +### 2. Perfetto的优势 + +- **更长的追踪时间**:支持数小时的长时间追踪 +- **更丰富的数据源**:支持CPU、内存、网络、电源等多种数据源 +- **更强大的分析能力**:支持SQL查询、自定义分析 +- **更好的可视化**:Web UI界面更友好,交互更流畅 +- **更灵活的配置**:支持配置文件,可以精确控制追踪内容 + +### 3. 适用场景 + +- 应用启动性能分析 +- 游戏帧率问题分析 +- 系统卡顿问题定位 +- 内存泄漏排查 +- CPU性能分析 +- 网络性能分析 +- 电源管理分析 + +## 二、环境准备与安装 + +### 1. 系统要求 + +- **Android版本**:Android 9(API 28)及以上 +- **设备要求**:需要root权限或userdebug版本 +- **开发环境**:ADB工具、Python 3.6+(可选) + +### 2. 检查设备支持 + +```bash +# 检查Perfetto是否可用 +adb shell perfetto --help + +# 检查设备Android版本 +adb shell getprop ro.build.version.release + +# 检查设备是否支持Perfetto +adb shell getprop ro.build.version.sdk +``` + +### 3. 获取Perfetto工具 + +#### 3.1 通过Android Studio + +Android Studio内置了Perfetto支持,可以直接使用Profiler功能。 + +#### 3.2 通过命令行 + +Perfetto已经集成在Android系统中,可以直接通过adb使用。 + +#### 3.3 通过Web UI + +访问 https://ui.perfetto.dev/ 可以在线分析Trace文件。 + +### 4. 安装Python依赖(可选) + +如果需要使用Python脚本处理Trace文件: + +```bash +# 安装protobuf +pip install protobuf + +# 安装perfetto Python库 +pip install perfetto +``` + +## 三、基础使用 + +### 1. 快速开始 + +#### 1.1 最简单的录制 + +```bash +# 录制10秒的Trace +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s + +# 拉取Trace文件 +adb pull /data/misc/perfetto-traces/trace trace.pb + +# 在Web UI中打开 +# 访问 https://ui.perfetto.dev/ 并上传trace.pb +``` + +#### 1.2 指定应用录制 + +```bash +# 录制指定应用的Trace +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \ + -a com.example.app -t 10s +``` + +### 2. 命令行参数 + +#### 2.1 常用参数 + +```bash +# -c: 配置文件(- 表示使用默认配置) +adb shell perfetto -c - + +# --out: 输出文件路径 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace + +# -t: 录制时长 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 5m +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 1h + +# -a: 指定应用包名 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -a com.example.app + +# --txt: 使用文本格式配置文件 +adb shell perfetto -c /data/local/tmp/config.txt --out /data/local/tmp/trace.pb +``` + +#### 2.2 高级参数 + +```bash +# --background: 后台运行 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace --background + +# --detach: 分离模式(录制完成后自动停止) +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace --detach + +# --stop: 停止正在运行的录制 +adb shell perfetto --stop +``` + +### 3. 配置文件使用 + +#### 3.1 配置文件格式 + +Perfetto支持两种配置文件格式: +- **文本格式(.txt)**:人类可读,易于编辑 +- **二进制格式(.pb)**:更紧凑,性能更好 + +#### 3.2 基础配置文件 + +**基础配置(basic_config.txt):** +```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" + atrace_categories: "gfx" + atrace_categories: "view" + buffer_size_kb: 2048 + } + } +} + +duration_ms: 10000 +``` + +#### 3.3 使用配置文件 + +```bash +# 1. 创建配置文件 +cat > config.txt << 'EOF' +buffers: { + size_kb: 63488 +} +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + atrace_categories: "gfx" + atrace_categories: "view" + } + } +} +duration_ms: 10000 +EOF + +# 2. 推送到设备 +adb push config.txt /data/local/tmp/ + +# 3. 使用配置文件录制 +adb shell perfetto -c /data/local/tmp/config.txt --out /data/local/tmp/trace.pb + +# 4. 拉取Trace文件 +adb pull /data/local/tmp/trace.pb trace.pb +``` + +## 四、常用配置模板 + +### 1. 启动性能分析配置 + +**startup_config.txt:** +```protobuf +buffers: { + size_kb: 63488 + fill_policy: DISCARD +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + # CPU调度事件 + 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类别 + atrace_categories: "am" # Activity Manager + atrace_categories: "wm" # Window Manager + atrace_categories: "gfx" # Graphics + atrace_categories: "view" # View System + atrace_categories: "binder_driver" # Binder Driver + atrace_categories: "binder_lock" # Binder Lock + atrace_categories: "input" # Input + atrace_categories: "res" # Resources + atrace_categories: "dalvik" # Dalvik VM + + buffer_size_kb: 4096 + drain_period_ms: 250 + } + } +} + +data_sources: { + config { + name: "android.surfaceflinger.frame" + } +} + +duration_ms: 10000 +``` + +### 2. 游戏帧率分析配置 + +**jank_config.txt:** +```protobuf +buffers: { + size_kb: 63488 + fill_policy: DISCARD +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + # CPU调度 + ftrace_events: "sched/sched_switch" + ftrace_events: "sched/sched_waking" + + # 电源管理 + ftrace_events: "power/cpu_frequency" + ftrace_events: "power/cpu_idle" + + # GPU事件(根据GPU型号选择) + ftrace_events: "gfx/mali_gpu_total" + ftrace_events: "gpu_mem_total/gpu_mem_total" + + # ATrace类别 + 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 +``` + +### 3. 内存分析配置 + +**memory_config.txt:** +```protobuf +buffers: { + size_kb: 63488 + fill_policy: DISCARD +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "kmem/kmalloc" + ftrace_events: "kmem/kfree" + ftrace_events: "kmem/kmem_cache_alloc" + ftrace_events: "kmem/kmem_cache_free" + atrace_categories: "dalvik" + atrace_categories: "gfx" + buffer_size_kb: 4096 + } + } +} + +data_sources: { + config { + name: "android.java_hprof" + target_buffer: 0 + } +} + +duration_ms: 60000 +``` + +### 4. 网络分析配置 + +**network_config.txt:** +```protobuf +buffers: { + size_kb: 63488 + fill_policy: DISCARD +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "net/net_dev_xmit" + ftrace_events: "net/net_dev_recv" + atrace_categories: "network" + buffer_size_kb: 2048 + } + } +} + +data_sources: { + config { + name: "linux.network_packets" + } +} + +duration_ms: 30000 +``` + +### 5. 全系统分析配置 + +**full_system_config.txt:** +```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: "power/suspend_resume" + + # 所有ATrace类别 + atrace_categories: "am" + atrace_categories: "wm" + atrace_categories: "gfx" + atrace_categories: "view" + atrace_categories: "input" + atrace_categories: "res" + atrace_categories: "dalvik" + atrace_categories: "binder_driver" + atrace_categories: "binder_lock" + atrace_categories: "sched" + atrace_categories: "freq" + atrace_categories: "idle" + atrace_categories: "disk" + atrace_categories: "load" + atrace_categories: "sync" + atrace_categories: "workq" + atrace_categories: "memreclaim" + atrace_categories: "regulators" + atrace_categories: "binder_driver" + atrace_categories: "binder_lock" + + buffer_size_kb: 8192 + drain_period_ms: 250 + } + } +} + +data_sources: { + config { + name: "android.surfaceflinger.frame" + } +} + +duration_ms: 60000 +``` + +## 五、ATrace类别详解 + +### 1. 常用类别 + +| 类别 | 说明 | 使用场景 | +|------|------|----------| +| `gfx` | 图形渲染 | 帧率分析、渲染性能 | +| `view` | 视图系统 | UI性能、布局分析 | +| `am` | Activity Manager | 应用启动、Activity生命周期 | +| `wm` | Window Manager | 窗口管理、多窗口 | +| `input` | 输入事件 | 触摸响应、输入延迟 | +| `sched` | CPU调度 | CPU性能、任务调度 | +| `freq` | CPU频率 | 频率管理、性能调频 | +| `idle` | CPU空闲 | 电源管理、CPU使用率 | +| `binder_driver` | Binder驱动 | 跨进程通信 | +| `binder_lock` | Binder锁 | 锁竞争分析 | +| `res` | 资源管理 | 资源加载、资源管理 | +| `dalvik` | Dalvik VM | Java代码执行、GC | +| `disk` | 磁盘I/O | 文件读写、数据库操作 | +| `load` | 系统负载 | 系统负载分析 | +| `sync` | 同步操作 | 同步原语、锁 | +| `workq` | 工作队列 | 后台任务 | +| `memreclaim` | 内存回收 | 内存管理、GC | +| `network` | 网络 | 网络性能分析 | + +### 2. 类别组合建议 + +**启动分析:** +``` +am wm gfx view binder_driver binder_lock input res dalvik +``` + +**帧率分析:** +``` +gfx view sched freq idle +``` + +**内存分析:** +``` +dalvik gfx memreclaim +``` + +**网络分析:** +``` +network disk +``` + +**全系统分析:** +``` +am wm gfx view input res dalvik binder_driver binder_lock sched freq idle disk load sync workq memreclaim +``` + +## 六、高级功能 + +### 1. 自定义Trace标记 + +#### 1.1 Java/Kotlin代码 + +```java +import android.os.Trace; + +public class MyClass { + public void doSomething() { + // 开始标记 + Trace.beginSection("my_custom_section"); + + try { + // 执行代码 + performOperation(); + } finally { + // 结束标记(必须在finally中调用) + Trace.endSection(); + } + } + + private void performOperation() { + Trace.beginSection("perform_operation"); + // 操作代码 + Trace.endSection(); + } +} +``` + +#### 1.2 C++代码 + +```cpp +#include + +void doSomething() { + // 开始标记 + ATRACE_BEGIN("my_custom_section"); + + // 执行代码 + performOperation(); + + // 结束标记 + ATRACE_END(); +} + +void performOperation() { + ATRACE_BEGIN("perform_operation"); + // 操作代码 + ATRACE_END(); +} +``` + +#### 1.3 异步操作标记 + +```java +import android.os.Trace; + +public class AsyncTask { + public void asyncOperation() { + Trace.beginAsyncSection("async_operation", 0); + + // 启动异步操作 + new Thread(() -> { + try { + // 异步操作代码 + performAsyncWork(); + } finally { + // 结束异步标记 + Trace.endAsyncSection("async_operation", 0); + } + }).start(); + } +} +``` + +### 2. 长时间追踪 + +#### 2.1 录制长时间Trace + +```bash +# 录制5分钟 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 5m + +# 录制1小时 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 1h + +# 使用配置文件指定时长 +# 在配置文件中设置 duration_ms: 3600000 # 1小时 +``` + +#### 2.2 后台录制 + +```bash +# 后台录制 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \ + --background -t 10m + +# 检查录制状态 +adb shell ps | grep perfetto + +# 停止录制 +adb shell perfetto --stop +``` + +### 3. 多进程追踪 + +#### 3.1 指定多个应用 + +```bash +# 追踪多个应用 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \ + -a com.example.app1 -a com.example.app2 -t 10s +``` + +#### 3.2 追踪系统进程 + +```bash +# 追踪系统进程(需要root) +adb root +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \ + -t 10s +``` + +### 4. 条件追踪 + +#### 4.1 基于触发器的追踪 + +```protobuf +# 在配置文件中添加触发器 +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + atrace_categories: "gfx" + # 触发器:当特定事件发生时开始追踪 + trigger_config { + trigger_mode: START_TRACING + trigger_name: "my_trigger" + } + } + } +} +``` + +## 七、Trace文件分析 + +### 1. Web UI使用技巧 + +#### 1.1 基本操作 + +- **缩放**:鼠标滚轮或Ctrl+滚轮 +- **平移**:鼠标拖拽或方向键 +- **选择**:鼠标点击选择事件 +- **搜索**:Ctrl+F 或点击搜索框 +- **书签**:按M键标记当前位置 +- **时间选择**:Shift+鼠标拖拽选择时间范围 + +#### 1.2 视图操作 + +- **展开/折叠**:点击进程/线程名称 +- **高亮**:双击事件高亮显示 +- **筛选**:使用筛选器过滤事件 +- **排序**:点击列标题排序 + +#### 1.3 快捷键 + +- `W` / `S`:放大/缩小 +- `A` / `D`:左移/右移 +- `M`:添加书签 +- `G`:跳转到书签 +- `F`:查找 +- `?`:显示所有快捷键 + +### 2. SQL查询 + +#### 2.1 基础查询 + +**查询所有帧信息:** +```sql +SELECT * FROM slice WHERE name = 'Choreographer#doFrame'; +``` + +**查询掉帧:** +```sql +SELECT + ts, + dur / 1000000.0 AS duration_ms +FROM slice +WHERE name = 'Choreographer#doFrame' +AND dur > 16666667 +ORDER BY ts; +``` + +**查询CPU使用率:** +```sql +SELECT + cpu, + COUNT(*) * 100.0 / (SELECT COUNT(*) FROM sched WHERE cpu = s.cpu) AS usage_percent +FROM sched s +GROUP BY cpu; +``` + +#### 2.2 高级查询 + +**查询启动时间:** +```sql +SELECT + (SELECT ts FROM slice WHERE name LIKE '%Choreographer#doFrame%' LIMIT 1) - + (SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1) + AS startup_time_ns; +``` + +**查询主线程阻塞时间:** +```sql +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'; +``` + +**查询GC频率:** +```sql +SELECT + COUNT(*) AS gc_count, + (MAX(ts) - MIN(ts)) / 1000000000.0 AS duration_sec, + COUNT(*) * 1.0 / ((MAX(ts) - MIN(ts)) / 1000000000.0) AS gc_per_sec +FROM slice +WHERE name LIKE '%GC%'; +``` + +### 3. 性能指标分析 + +#### 3.1 帧率分析 + +**查看帧率:** +``` +1. 在顶部选择 "Frame Timeline" +2. 查看每一帧的状态 +3. 统计正常帧、掉帧数量 +4. 计算平均帧率 +``` + +**使用SQL计算帧率:** +```sql +SELECT + COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps +FROM slice +WHERE name = 'Choreographer#doFrame'; +``` + +#### 3.2 CPU性能分析 + +**查看CPU频率:** +``` +1. 在顶部选择 "CPU frequency" +2. 查看各核心频率变化 +3. 检查是否有降频 +``` + +**查看CPU使用率:** +``` +1. 在顶部选择 "CPU usage" +2. 查看各核心使用情况 +3. 检查是否有瓶颈 +``` + +#### 3.3 内存分析 + +**查看内存使用:** +``` +1. 在顶部选择 "Memory" +2. 查看内存使用趋势 +3. 检查是否有内存泄漏 +``` + +**查看GC活动:** +``` +1. 在主线程中查找 "HeapTaskDaemon" +2. 查看GC频率和耗时 +3. 分析GC对性能的影响 +``` + +## 八、最佳实践 + +### 1. 录制Trace + +1. **明确目标**:在录制前明确要分析的问题 +2. **合适时长**:选择适当的追踪时间 + - 启动分析:5-10秒 + - 帧率分析:30-60秒 + - 内存分析:1-5分钟 + - 长时间监控:根据需要 +3. **关键类别**:只追踪相关的类别,减少文件大小 +4. **多次录制**:多次录制确保结果一致 +5. **环境一致**:保持测试环境一致 + - 温度 + - 电量 + - 后台应用 + - 网络状态 + +### 2. 分析Trace + +1. **系统化分析**:按照固定流程分析 +2. **关键指标**:关注关键性能指标 +3. **对比分析**:与基准版本或竞品对比 +4. **深入分析**:找到根本原因 +5. **验证优化**:优化后再次录制验证 + +### 3. 性能优化 + +1. **量化指标**:使用具体数值衡量优化效果 +2. **多维度验证**:从多个角度验证优化效果 +3. **回归测试**:确保优化没有引入新问题 +4. **持续监控**:建立性能监控体系 + +## 九、常见问题与解决方案 + +### 1. 录制问题 + +**问题1:权限不足** +``` +错误:perfetto: permission denied +解决:需要root权限或userdebug版本 +``` + +**问题2:设备不支持** +``` +错误:perfetto: command not found +解决:检查Android版本(需要Android 9+) +``` + +**问题3:配置文件格式错误** +``` +错误:Failed to parse config +解决:检查配置文件格式,使用文本格式更易调试 +``` + +### 2. 分析问题 + +**问题1:Trace文件过大** +``` +解决: +1. 减少追踪时间 +2. 减少追踪类别 +3. 使用更小的缓冲区 +4. 分段录制和分析 +``` + +**问题2:找不到关键事件** +``` +解决: +1. 确认追踪类别包含相关事件 +2. 检查应用是否添加了Trace标记 +3. 使用搜索功能查找 +4. 查看相关进程和线程 +``` + +**问题3:性能数据不准确** +``` +解决: +1. 确认追踪配置正确 +2. 检查是否有其他进程干扰 +3. 多次录制取平均值 +4. 对比其他性能工具 +``` + +### 3. 性能问题 + +**问题1:录制影响性能** +``` +解决: +1. 减少追踪类别 +2. 使用较小的缓冲区 +3. 减少追踪时间 +4. 在性能测试时关闭不必要的追踪 +``` + +**问题2:Web UI加载慢** +``` +解决: +1. 减少Trace文件大小 +2. 使用本地Perfetto工具 +3. 分段分析Trace文件 +``` + +## 十、实用脚本 + +### 1. 快速录制脚本 + +**quick_trace.sh:** +```bash +#!/bin/bash + +# 快速录制Trace脚本 +DURATION=${1:-10} +OUTPUT=${2:-trace.pb} + +echo "开始录制 ${DURATION} 秒的Trace..." +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t ${DURATION}s + +echo "拉取Trace文件..." +adb pull /data/misc/perfetto-traces/trace ${OUTPUT} + +echo "Trace文件已保存为: ${OUTPUT}" +echo "可以在 https://ui.perfetto.dev/ 打开分析" +``` + +**使用方法:** +```bash +chmod +x quick_trace.sh +./quick_trace.sh 10 trace.pb +``` + +### 2. 应用启动分析脚本 + +**startup_trace.sh:** +```bash +#!/bin/bash + +PACKAGE=${1:-com.example.app} +OUTPUT=${2:-startup_trace.pb} + +echo "开始录制 ${PACKAGE} 的启动Trace..." +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \ + -a ${PACKAGE} -t 10s + +echo "拉取Trace文件..." +adb pull /data/misc/perfetto-traces/trace ${OUTPUT} + +echo "启动Trace已保存为: ${OUTPUT}" +``` + +**使用方法:** +```bash +chmod +x startup_trace.sh +./startup_trace.sh com.example.app startup.pb +``` + +### 3. 批量分析脚本 + +**batch_analysis.sh:** +```bash +#!/bin/bash + +# 批量录制和分析Trace +for i in {1..5}; do + echo "录制第 ${i} 次Trace..." + adb shell perfetto -c - --out /data/misc/perfetto-traces/trace_${i} -t 10s + adb pull /data/misc/perfetto-traces/trace_${i} trace_${i}.pb + echo "第 ${i} 次Trace已保存" +done + +echo "所有Trace录制完成" +``` + +## 十一、进阶技巧 + +### 1. 性能基准建立 + +**建立性能基准:** +``` +1. 在稳定版本上录制Trace +2. 记录关键性能指标 +3. 建立性能基准数据库 +4. 定期更新基准 +``` + +### 2. 自动化性能测试 + +**集成到CI/CD:** +``` +1. 在自动化测试中录制Trace +2. 自动分析关键指标 +3. 设置性能阈值 +4. 性能回归自动告警 +``` + +### 3. 性能报告生成 + +**生成性能报告:** +``` +1. 使用SQL查询提取数据 +2. 使用脚本处理数据 +3. 生成可视化报告 +4. 定期发送性能报告 +``` + +## 十二、参考资料 + +### 1. 官方文档 + +- Perfetto官方文档:https://perfetto.dev/ +- Perfetto Web UI:https://ui.perfetto.dev/ +- Android性能优化指南:https://developer.android.com/topic/performance + +### 2. 相关工具 + +- Systrace:Android系统追踪工具 +- Android Studio Profiler:IDE集成性能分析工具 +- Traceview:Java代码性能分析工具 + +### 3. 学习资源 + +- Perfetto GitHub:https://github.com/google/perfetto +- Android性能优化最佳实践 +- 系统性能分析案例 + +--- + +*最后更新:2024年* diff --git a/docs/学习笔记/看应用卡顿问题.md b/docs/学习笔记/看应用卡顿问题.md new file mode 100644 index 0000000..76a7bd6 --- /dev/null +++ b/docs/学习笔记/看应用卡顿问题.md @@ -0,0 +1,846 @@ +# Perfetto看应用卡顿问题 + +## 一、卡顿问题概述 + +### 1. 什么是卡顿 + +卡顿是指应用在运行过程中出现画面不流畅、响应延迟、操作不跟手等现象。从技术角度来说,卡顿通常表现为: + +- **丢帧**:60Hz设备上,1秒内显示的帧数少于60帧 +- **帧延迟**:单帧渲染时间超过16.67ms(60Hz)或8.33ms(120Hz) +- **响应延迟**:用户操作到界面响应的时间过长 + +### 2. 卡顿的分类 + +#### 2.1 按场景分类 + +- **滑动卡顿**:列表滑动、页面滚动时出现卡顿 +- **动画卡顿**:页面跳转、转场动画、UI动画不流畅 +- **点击卡顿**:点击操作后响应延迟 +- **启动卡顿**:应用启动过程缓慢 +- **交互卡顿**:界面交互操作不流畅 + +#### 2.2 按原因分类 + +- **主线程阻塞**:主线程执行耗时操作 +- **渲染瓶颈**:RenderThread或GPU性能不足 +- **系统资源竞争**:CPU、内存、I/O资源不足 +- **锁竞争**:多线程竞争导致阻塞 +- **内存压力**:GC频繁、内存不足 + +### 3. 卡顿分析的目标 + +- **定位卡顿时间点**:确定卡顿发生的具体时间 +- **识别卡顿原因**:找到导致卡顿的根本原因 +- **量化卡顿程度**:测量卡顿的严重程度 +- **提供优化建议**:给出针对性的优化方案 + +## 二、Perfetto录制卡顿场景 + +### 1. 录制前的准备 + +#### 1.1 明确问题场景 + +在录制前需要明确: +- **卡顿场景**:什么操作导致卡顿 +- **复现步骤**:如何复现卡顿问题 +- **卡顿表现**:卡顿的具体表现(丢帧、延迟等) +- **预期时长**:卡顿持续的时间 + +#### 1.2 环境准备 + +- **设备状态**:确保设备电量充足、温度正常 +- **后台应用**:关闭不必要的后台应用 +- **网络状态**:确保网络稳定(如需要) +- **应用版本**:使用待分析的应用版本 + +### 2. 卡顿分析配置文件 + +#### 2.1 基础卡顿分析配置 + +**jank_analysis_config.txt:** +```protobuf +buffers: { + size_kb: 63488 + fill_policy: DISCARD +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + # CPU调度事件 + ftrace_events: "sched/sched_switch" + ftrace_events: "sched/sched_waking" + + # 电源管理事件 + ftrace_events: "power/cpu_frequency" + ftrace_events: "power/cpu_idle" + + # GPU事件(根据GPU型号选择) + ftrace_events: "gfx/mali_gpu_total" + ftrace_events: "gpu_mem_total/gpu_mem_total" + + # ATrace类别 + atrace_categories: "gfx" # 图形渲染 + atrace_categories: "view" # 视图系统 + atrace_categories: "sched" # CPU调度 + atrace_categories: "freq" # CPU频率 + atrace_categories: "idle" # CPU空闲 + atrace_categories: "input" # 输入事件 + atrace_categories: "dalvik" # Java执行 + atrace_categories: "binder_driver" # Binder通信 + + buffer_size_kb: 8192 + drain_period_ms: 250 + } + } +} + +data_sources: { + config { + name: "android.surfaceflinger.frame" + } +} + +duration_ms: 30000 +``` + +#### 2.2 滑动卡顿分析配置 + +**scroll_jank_config.txt:** +```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" + + atrace_categories: "gfx" + atrace_categories: "view" + atrace_categories: "input" + atrace_categories: "sched" + atrace_categories: "freq" + atrace_categories: "idle" + atrace_categories: "dalvik" + + buffer_size_kb: 8192 + drain_period_ms: 250 + } + } +} + +data_sources: { + config { + name: "android.surfaceflinger.frame" + } +} + +duration_ms: 60000 +``` + +### 3. 录制命令 + +#### 3.1 基础录制 + +```bash +# 录制30秒的Trace +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 30s + +# 指定应用录制 +adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \ + -a com.example.app -t 30s +``` + +#### 3.2 使用配置文件录制 + +```bash +# 1. 推送配置文件 +adb push jank_analysis_config.txt /data/local/tmp/ + +# 2. 开始录制(在复现卡顿前) +adb shell perfetto -c /data/local/tmp/jank_analysis_config.txt \ + --out /data/local/tmp/trace.pb + +# 3. 复现卡顿问题 +# (在设备上执行导致卡顿的操作) + +# 4. 停止录制(Ctrl+C 或等待duration_ms时间) + +# 5. 拉取Trace文件 +adb pull /data/local/tmp/trace.pb jank_trace.pb +``` + +#### 3.3 后台录制 + +```bash +# 后台录制,适合长时间监控 +adb shell perfetto -c /data/local/tmp/jank_analysis_config.txt \ + --out /data/local/tmp/trace.pb --background + +# 检查录制状态 +adb shell ps | grep perfetto + +# 停止录制 +adb shell perfetto --stop +``` + +## 三、卡顿分析定位流程 + +### 1. 分析流程概览 + +``` +1. 打开Trace文件 + ↓ +2. 定位卡顿时间点 + ↓ +3. 查看帧率信息 + ↓ +4. 分析主线程状态 + ↓ +5. 分析渲染线程状态 + ↓ +6. 分析系统资源 + ↓ +7. 定位根本原因 + ↓ +8. 提供优化建议 +``` + +### 2. 步骤1:打开Trace文件 + +#### 2.1 使用Web UI打开 + +1. 访问 https://ui.perfetto.dev/ +2. 点击 "Open trace file" +3. 选择 trace.pb 文件 +4. 等待加载完成 + +#### 2.2 初始检查 + +- **时间范围**:确认Trace覆盖了卡顿时间段 +- **进程列表**:找到目标应用进程 +- **线程列表**:确认主线程和RenderThread存在 + +### 3. 步骤2:定位卡顿时间点 + +#### 3.1 方法1:查看Frame Timeline + +``` +1. 在顶部选择 "Frame Timeline" +2. 查看帧的颜色: + - 绿色:正常帧(< 16.67ms) + - 黄色:轻微掉帧(16.67-33.33ms) + - 红色:严重掉帧(> 33.33ms) +3. 找到红色或黄色的帧 +4. 点击帧,查看详细信息 +5. 标记卡顿时间点(按M键) +``` + +#### 3.2 方法2:查看VSync信息 + +``` +1. 在SurfaceFlinger进程中查找 "VSync" +2. 查看VSync周期是否正常(60Hz = 16.67ms) +3. 检查是否有VSync丢失 +4. 找到VSync异常的时间点 +``` + +#### 3.3 方法3:使用SQL查询 + +```sql +-- 查询所有掉帧 +SELECT + ts, + dur / 1000000.0 AS duration_ms +FROM slice +WHERE name = 'Choreographer#doFrame' +AND dur > 16666667 +ORDER BY ts; +``` + +#### 3.4 方法4:查看用户操作 + +``` +1. 在Input进程中查找输入事件 +2. 找到用户操作的时间点 +3. 查看操作后的响应时间 +4. 识别响应延迟的时间点 +``` + +### 4. 步骤3:查看帧率信息 + +#### 4.1 帧率统计 + +**使用SQL查询帧率:** +```sql +-- 计算平均帧率 +SELECT + COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps +FROM slice +WHERE name = 'Choreographer#doFrame'; +``` + +**查看帧率分布:** +```sql +-- 统计帧耗时分布 +SELECT + CASE + WHEN dur < 16666667 THEN '正常' + WHEN dur < 33333333 THEN '轻微掉帧' + ELSE '严重掉帧' + END AS frame_type, + COUNT(*) AS count +FROM slice +WHERE name = 'Choreographer#doFrame' +GROUP BY frame_type; +``` + +#### 4.2 连续掉帧分析 + +```sql +-- 查询连续掉帧的情况 +SELECT + ts, + dur / 1000000.0 AS duration_ms, + LAG(ts) OVER (ORDER BY ts) AS prev_ts, + ts - LAG(ts) OVER (ORDER BY ts) AS gap_ns +FROM slice +WHERE name = 'Choreographer#doFrame' +AND dur > 16666667 +ORDER BY ts; +``` + +### 5. 步骤4:分析主线程状态 + +#### 5.1 查看主线程时间线 + +``` +1. 在进程列表中找到目标应用 +2. 展开进程,找到主线程(通常是包名或main) +3. 点击主线程,查看时间线 +4. 查看线程状态: + - Running(绿色):正在执行 + - Runnable(黄色):等待CPU + - Sleep(红色):等待资源 + - UninterruptibleSleep(紫色):不可中断等待 +``` + +#### 5.2 分析主线程阻塞 + +**查找Sleep状态:** +``` +1. 在主线程时间线中查找红色(Sleep)片段 +2. 查看Sleep的持续时间 +3. 分析Sleep的原因: + - 等待Binder调用返回 + - 等待锁释放 + - 等待IO操作完成 + - 等待子线程返回 +``` + +**使用SQL查询主线程阻塞:** +```sql +-- 查询主线程Sleep时间 +SELECT + SUM(dur) / 1000000.0 AS total_sleep_ms, + COUNT(*) AS sleep_count +FROM sched +WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1) +AND state = 'S'; +``` + +#### 5.3 分析主线程耗时操作 + +**查找耗时操作:** +``` +1. 在主线程时间线中按耗时排序 +2. 查看耗时最长的操作 +3. 分析操作是否可以优化: + - 是否可以移到后台线程 + - 是否可以异步执行 + - 是否可以优化算法 +``` + +**使用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; +``` + +#### 5.4 常见主线程阻塞场景 + +**场景1:等待Binder调用** +- **特征**:主线程处于Sleep状态,等待Binder返回 +- **查找**:查看wakeup信息,找到对应的Binder调用 +- **分析**:检查SystemServer中的Binder处理耗时 +- **解决**:优化Binder服务端处理,或使用异步调用 + +**场景2:等待锁** +- **特征**:主线程处于Sleep状态,等待锁释放 +- **查找**:查看wakeup信息,找到锁持有者 +- **分析**:检查锁持有时间是否过长 +- **解决**:减少锁持有时间,使用细粒度锁 + +**场景3:等待IO** +- **特征**:主线程处于UninterruptibleSleep-IO状态 +- **查找**:查看IO操作(文件读写、数据库操作) +- **分析**:检查IO操作是否在主线程执行 +- **解决**:使用异步IO操作 + +**场景4:等待网络** +- **特征**:主线程等待网络响应 +- **查找**:查看网络请求相关操作 +- **分析**:检查网络请求是否同步执行 +- **解决**:使用异步网络请求 + +**场景5:等待子线程** +- **特征**:主线程等待子线程返回数据 +- **查找**:查看wakeup信息,找到依赖的子线程 +- **分析**:检查子线程执行耗时 +- **解决**:优化子线程执行,或使用回调机制 + +### 6. 步骤5:分析渲染线程状态 + +#### 6.1 查看RenderThread时间线 + +``` +1. 在应用进程中找到RenderThread线程 +2. 查看线程状态和执行情况 +3. 检查是否有阻塞或延迟 +``` + +#### 6.2 分析渲染耗时 + +**关键操作分析:** +``` +1. 在RenderThread中查找关键操作: + - drawFrame:绘制帧 + - dequeueBuffer:获取Buffer + - queueBuffer:提交Buffer +2. 分析每个操作的耗时 +3. 识别渲染瓶颈 +``` + +**使用SQL查询渲染耗时:** +```sql +-- 查询RenderThread耗时操作 +SELECT + name, + dur / 1000000.0 AS duration_ms, + ts +FROM slice +WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%RenderThread%' LIMIT 1) +ORDER BY dur DESC +LIMIT 20; +``` + +#### 6.3 常见渲染问题 + +**问题1:dequeueBuffer耗时** +- **特征**:RenderThread等待Buffer分配 +- **原因**:SurfaceFlinger繁忙或Buffer不足 +- **分析**:查看SurfaceFlinger进程状态 +- **解决**:优化SurfaceFlinger,增加Buffer数量 + +**问题2:queueBuffer耗时** +- **特征**:RenderThread等待Buffer提交 +- **原因**:SurfaceFlinger处理延迟 +- **分析**:查看SurfaceFlinger合成耗时 +- **解决**:优化SurfaceFlinger合成流程 + +**问题3:drawFrame耗时** +- **特征**:RenderThread绘制耗时过长 +- **原因**:绘制内容复杂、GPU性能不足 +- **分析**:查看GPU使用情况和绘制命令 +- **解决**:优化绘制内容,使用GPU加速 + +### 7. 步骤6:分析系统资源 + +#### 7.1 CPU性能分析 + +**查看CPU频率:** +``` +1. 在顶部选择 "CPU frequency" +2. 查看卡顿期间CPU频率变化 +3. 检查是否有降频或频率不足 +``` + +**查看CPU使用率:** +``` +1. 在顶部选择 "CPU usage" +2. 查看卡顿期间CPU使用情况 +3. 检查是否有CPU瓶颈 +``` + +**查看CPU调度:** +``` +1. 在CPU核心时间线中查看任务调度 +2. 检查关键任务是否跑在小核 +3. 查看调度延迟 +``` + +**使用SQL查询CPU使用率:** +```sql +-- 查询CPU使用率 +SELECT + cpu, + COUNT(*) * 100.0 / (SELECT COUNT(*) FROM sched WHERE cpu = s.cpu) AS usage_percent +FROM sched s +WHERE ts BETWEEN ? AND ? -- 卡顿时间段 +GROUP BY cpu; +``` + +#### 7.2 内存性能分析 + +**查看GC活动:** +``` +1. 在主线程中查找 "HeapTaskDaemon" +2. 查看GC频率和耗时 +3. 检查是否在关键帧期间发生GC +``` + +**使用SQL查询GC频率:** +```sql +-- 查询GC频率 +SELECT + COUNT(*) AS gc_count, + (MAX(ts) - MIN(ts)) / 1000000000.0 AS duration_sec, + COUNT(*) * 1.0 / ((MAX(ts) - MIN(ts)) / 1000000000.0) AS gc_per_sec +FROM slice +WHERE name LIKE '%GC%' +AND ts BETWEEN ? AND ?; -- 卡顿时间段 +``` + +**查看内存使用:** +``` +1. 在顶部选择 "Memory" +2. 查看卡顿期间内存使用趋势 +3. 检查是否有内存泄漏 +``` + +#### 7.3 GPU性能分析 + +**查看GPU使用率:** +``` +1. 在顶部选择 "GPU frequency" 或 "GPU usage" +2. 查看GPU频率和使用率 +3. 检查是否有GPU瓶颈 +``` + +**查看GPU渲染时间:** +``` +1. 在RenderThread中查找GPU相关操作 +2. 查看GPU渲染耗时 +3. 检查是否有GPU过载 +``` + +### 8. 步骤7:定位根本原因 + +#### 8.1 综合分析 + +根据前面的分析结果,综合判断卡顿的根本原因: + +1. **主线程阻塞**:如果主线程长时间Sleep,分析阻塞原因 +2. **渲染瓶颈**:如果RenderThread耗时过长,分析渲染瓶颈 +3. **系统资源不足**:如果CPU/GPU/内存不足,分析资源竞争 +4. **锁竞争**:如果多线程竞争锁,分析锁竞争情况 +5. **内存压力**:如果GC频繁,分析内存使用情况 + +#### 8.2 问题优先级 + +根据卡顿的严重程度和影响范围,确定问题优先级: + +- **P0**:严重卡顿,影响核心功能 +- **P1**:明显卡顿,影响用户体验 +- **P2**:轻微卡顿,可接受但需优化 +- **P3**:偶发卡顿,影响较小 + +### 9. 步骤8:提供优化建议 + +#### 9.1 主线程优化 + +- **异步化**:将耗时操作移到后台线程 +- **优化算法**:减少主线程计算量 +- **减少锁竞争**:优化锁的使用 +- **优化IO**:使用异步IO操作 + +#### 9.2 渲染优化 + +- **减少绘制内容**:优化视图层级 +- **使用硬件加速**:启用GPU加速 +- **优化布局**:减少布局复杂度 +- **减少过度绘制**:优化绘制区域 + +#### 9.3 系统资源优化 + +- **CPU优化**:优化任务调度,绑定关键任务到高性能核心 +- **内存优化**:减少内存分配,优化GC +- **GPU优化**:优化GPU使用,减少GPU负载 + +## 四、常见卡顿场景分析 + +### 1. 滑动卡顿分析 + +#### 1.1 问题特征 + +- 列表滑动时出现卡顿 +- 滑动不跟手 +- 滑动后页面继续滑动(惯性滑动)时卡顿 + +#### 1.2 分析步骤 + +``` +1. 定位滑动时间段 + - 查找Input事件(触摸事件) + - 找到滑动开始和结束时间 + +2. 查看帧率 + - 查看滑动期间的帧率 + - 识别掉帧的时间点 + +3. 分析主线程 + - 查看主线程在滑动期间的状态 + - 查找耗时操作 + +4. 分析渲染线程 + - 查看RenderThread在滑动期间的状态 + - 查找渲染瓶颈 + +5. 分析系统资源 + - 查看CPU使用情况 + - 查看内存和GC情况 +``` + +#### 1.3 常见原因 + +- **主线程阻塞**:滑动时主线程执行耗时操作 +- **布局复杂**:列表项布局过于复杂 +- **图片加载**:滑动时加载大量图片 +- **数据绑定**:滑动时频繁绑定数据 +- **系统资源不足**:CPU或内存不足 + +### 2. 动画卡顿分析 + +#### 2.1 问题特征 + +- 页面跳转动画不流畅 +- UI动画卡顿 +- 转场动画掉帧 + +#### 2.2 分析步骤 + +``` +1. 定位动画时间段 + - 查找动画开始和结束时间 + - 查看动画相关的Trace标记 + +2. 查看帧率 + - 查看动画期间的帧率 + - 识别掉帧的时间点 + +3. 分析动画线程 + - 查看动画线程状态 + - 查找动画计算耗时 + +4. 分析渲染 + - 查看渲染线程状态 + - 查找渲染瓶颈 +``` + +#### 2.3 常见原因 + +- **动画计算耗时**:动画计算过于复杂 +- **渲染瓶颈**:渲染性能不足 +- **系统资源不足**:CPU或GPU不足 +- **动画冲突**:多个动画同时执行 + +### 3. 点击卡顿分析 + +#### 3.1 问题特征 + +- 点击后响应延迟 +- 点击后界面不更新 +- 点击后操作不执行 + +#### 3.2 分析步骤 + +``` +1. 定位点击事件 + - 查找Input事件(点击事件) + - 找到点击时间点 + +2. 查看响应时间 + - 从点击到界面响应的时间 + - 识别响应延迟 + +3. 分析主线程 + - 查看主线程在点击后的状态 + - 查找耗时操作 + +4. 分析系统服务 + - 查看SystemServer处理耗时 + - 查看Binder调用耗时 +``` + +#### 3.3 常见原因 + +- **主线程阻塞**:点击后主线程执行耗时操作 +- **Binder调用延迟**:跨进程调用耗时 +- **系统服务繁忙**:SystemServer处理延迟 +- **资源加载**:点击后加载大量资源 + +## 五、分析技巧与最佳实践 + +### 1. 分析技巧 + +#### 1.1 时间对齐 + +- 使用书签(M键)标记关键时间点 +- 使用时间选择(Shift+拖拽)选择时间范围 +- 使用缩放功能(W/S键)调整时间范围 + +#### 1.2 多维度分析 + +- 同时查看主线程、RenderThread、系统资源 +- 对比正常和异常时间段 +- 使用SQL查询进行统计分析 + +#### 1.3 对比分析 + +- 对比优化前后的Trace +- 对比不同版本的性能 +- 对比竞品的性能 + +### 2. 最佳实践 + +#### 2.1 录制Trace + +- 明确问题场景和复现步骤 +- 选择合适的录制时长 +- 保持环境一致 +- 多次录制确保结果一致 + +#### 2.2 分析Trace + +- 系统化分析,按照固定流程 +- 关注关键指标 +- 深入分析,找到根本原因 +- 记录分析过程和结果 + +#### 2.3 优化验证 + +- 优化后再次录制Trace +- 对比优化前后的性能 +- 验证优化效果 +- 确保没有引入新问题 + +### 3. 常见误区 + +#### 3.1 只看主线程 + +- **误区**:只关注主线程,忽略其他线程 +- **正确**:综合分析主线程、RenderThread、系统资源 + +#### 3.2 只看表面现象 + +- **误区**:只看掉帧,不分析原因 +- **正确**:深入分析,找到根本原因 + +#### 3.3 忽略系统因素 + +- **误区**:只关注应用代码,忽略系统因素 +- **正确**:综合考虑应用和系统因素 + +## 六、实际案例分析 + +### 案例1:列表滑动卡顿 + +**问题描述:** +- 列表滑动时出现明显卡顿 +- 滑动不跟手 +- 掉帧严重 + +**分析过程:** +1. 录制滑动场景的Trace +2. 定位滑动时间段 +3. 发现主线程在滑动期间执行了大量布局计算 +4. 发现RenderThread绘制耗时过长 +5. 发现CPU使用率过高 + +**根本原因:** +- 列表项布局过于复杂 +- 滑动时频繁重新计算布局 +- 绘制内容过多 + +**优化方案:** +- 优化列表项布局,减少层级 +- 使用ViewHolder复用视图 +- 减少绘制内容 +- 使用硬件加速 + +**优化效果:** +- 滑动流畅度提升80% +- 掉帧减少90% + +### 案例2:页面跳转卡顿 + +**问题描述:** +- 页面跳转动画不流畅 +- 跳转后界面响应延迟 + +**分析过程:** +1. 录制页面跳转的Trace +2. 定位跳转时间段 +3. 发现主线程在跳转时执行了大量初始化操作 +4. 发现Binder调用耗时过长 +5. 发现系统服务处理延迟 + +**根本原因:** +- 页面跳转时同步执行初始化 +- 跨进程调用过多 +- 系统服务繁忙 + +**优化方案:** +- 延迟非关键初始化 +- 优化跨进程调用 +- 使用异步加载 +- 优化系统服务调用 + +**优化效果:** +- 跳转时间减少60% +- 动画流畅度提升 + +## 七、总结 + +使用Perfetto分析应用卡顿问题的关键步骤: + +1. **明确问题场景**:了解卡顿的具体表现和复现步骤 +2. **录制Trace**:选择合适的配置和录制方法 +3. **定位卡顿时间点**:使用多种方法找到卡顿发生的时间 +4. **分析主线程**:查看主线程状态,找到阻塞原因 +5. **分析渲染线程**:查看RenderThread状态,找到渲染瓶颈 +6. **分析系统资源**:查看CPU、内存、GPU使用情况 +7. **定位根本原因**:综合分析,找到卡顿的根本原因 +8. **提供优化建议**:给出针对性的优化方案 + +通过系统化的分析流程,可以高效地定位和解决应用卡顿问题。 + +--- + +*最后更新:2024年* diff --git a/mkdocs.yml b/mkdocs.yml index 292ea5a..1c5ef5d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,6 +83,9 @@ nav: - 学习笔记/基础性能问题分析(ODM).md - 学习笔记/高级性能问题分析.md - 学习笔记/高级性能问题分析目录.md + - 学习笔记/perfeto看trace技巧.md + - 学习笔记/perfetto的使用技巧.md + - 学习笔记/看应用卡顿问题.md - Obsidian笔记: - Obsidian/2026-01-05 个人文档管理.md - Obsidian/高频命令.md