拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Perfetto性能分析实战:从抓取trace到SQL定位卡顿

做性能优化的朋友大概都体会过这种抓狂线上反馈App卡顿你打开Android Studio Profiler满屏的火焰图看得头晕却找不到到底是哪个函数拖慢了主线程。或者你还在用老掉牙的systrace对着密密麻麻的timeline瞪眼想定位一次掉帧却连线程调度状态都看不清。如果你也处在这种状态我强烈建议你认识一下Perfetto——目前性能分析工具里公认最扛打的一款。这篇就从抓取、分析到原理把Perfetto的基本使用一条线讲清楚适合Android开发、性能优化工程师、系统工程师以及所有被线上卡顿折磨过的人。1. 为什么性能分析工具里Perfetto能成为首选1.1 老牌性能分析工具的无奈先说systrace。它陪着Android开发者走了很多年但上限早就到了。systrace本质上只抓内核ftrace事件和几个atrace打点你能看到线程什么时候在CPU上跑、什么时候睡眠却看不到堆内存分配看不到线程内部的完整方法调用链。而且它的UI是HTML静态页面放大、拖拽都不流畅分析一次卡顿要来回拖动对比好几个截图效率很低。atrace虽然能补充一些Android层的事件但每次都要手动拼标签信息仍然碎片化。Android Studio Profiler虽然上手快但它的开销其实不小。开启CPU录制后App本身会被拖慢这会导致你抓到的是被分析干扰之后的性能而不是线上真实性能。更麻烦的是Profiler的数据结构是封闭的你想叠加内核调度、网络、GPU这些维度的信息基本不可能。自研打点就更痛了要改业务代码侵入性强线上还要做开关和降级维护成本极高。这些痛点本质上指向同一个问题缺一个能把系统级数据和App级数据统一起来、又不干扰被分析对象的高性能工具。1.2 Perfetto的破局思路Perfetto是Google开源的下一代trace系统从Android 10开始系统内置同时支持Linux桌面和Chrome。它没有在systrace基础上修修补补而是重新设计了数据模型所有数据源——包括CPU调度、内存、GPU、网络、Android Framework事件、App自定义事件——最终都规范化成三种基本单元Track轨道、Slice时间片、Counter计数器。这句话是整个Perfetto认知的核心后面所有操作都是围绕它展开的。这个统一模型的收益是巨大的。你可以在同一条时间线上把主线程一次掉帧、对应时刻的CPU频率、内存占用、GC事件、Binder调用全部铺开对照而不是像以前那样几个工具来回切。抓取开销上Perfetto主要靠内核ftrace机制采集用户态只做事件写入实测对一个普通App的影响远小于Android Studio Profiler在CPU录制模式下的干扰。Perfetto还顺手解决了协作问题。trace文件就是个单文件拖到ui.perfetto.dev就能分析不需要装任何本地工具。你可以把复现现场打包发给同事对方能完全还原你的视角。这一点在跨团队定位问题时的价值用过的都懂。1.3 它到底哪里比systrace强一句话systrace能做的它全能做systrace做不了的它也能做。Perfetto提供了全套工具链包括perfetto命令行、trace processorSQL查询分析引擎、内置的heapprofd堆分析器、以及针对长时间抓取的模块化数据源。它还支持通过Protobuf配置自定义数据源团队可以把自己的性能埋点体系直接接进来。所以它不是又一个分析器而是一套可以长期沉淀的性能基础设施。2. 快速上手两条路径跑通Perfetto2.1 Android设备抓取trace的完整步骤先说明前置条件设备上最好运行Android 10及以上系统这样系统自带perfetto不需要root。如果设备更旧也可以手动push一个perfetto二进制进去但这里不展开日常使用基本都走系统自带。第一步打开USB调试连接adb确认设备在线adb devices第二步执行最简单的抓取命令。在PC上输入adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace -t 10s -b 64mb sched freq idle cpu逐项解释一下-o指定输出文件路径这里必须放在/data/misc/perfetto-traces/下因为shell用户对这个目录有写权限其他目录经常报权限错误-t是抓取时长单位支持s、ms-b是环形缓冲区大小64mb通常够用命令末尾的sched freq idle cpu是数据源列表分别代表CPU调度事件、CPU频率变化、idle事件和CPU活动。第三步执行完等待命令返回把trace文件拉到本地adb pull /data/misc/perfetto-traces/trace.perfetto-trace第四步打开浏览器访问ui.perfetto.dev把文件拖进页面就能看到完整的时间线了。整个过程不超过五分钟先跑通这一步后面再谈深入分析。2.2 用配置文件实现精准抓取命令行适合快速验证但要控制追踪范围、加自定义数据源就得用配置文件。Perfetto使用文本格式的protobuf配置pbtx看起来像简化版的JSON。我常用的一个基础配置长这样data_sources { config { name: linux.ftrace ftrace_config { ftrace_events: sched/sched_switch ftrace_events: sched/sched_wakeup ftrace_events: power/cpu_frequency ftrace_events: power/cpu_idle } } } data_sources { config { name: android.log android_log_config { log_ids: LID_DEFAULT } } } duration_ms: 10000 buffer_size_kb: 65536保存为config.pbtxpush到设备adb push config.pbtx /data/local/tmp/config.pbtx adb shell perfetto -c /data/local/tmp/config.pbtx -o /data/misc/perfetto-traces/trace.perfetto-trace配置文件的威力在于你可以精确选择要采集的事件控制数据量还能加入App自定义数据源。比如想做堆分析就加一个com.android.heapprofd的数据源配置想抓GPU就加gpu相关事件。数据源越多能分析的手段越多但buffer压力和文件体积也越大一定要按需配置。2.3 桌面端和Chrome的抓取方式Perfetto不只服务Android。Linux桌面上直接安装perfetto采集器后可以用类似命令抓系统级数据。Chrome浏览器里打开chrome://tracing点击录制就能抓页面脚本执行、渲染、合成相关的trace之后点裁剪按钮导出同样拖进ui.perfetto.dev。前端性能问题的排查在这个模式下非常直观尤其是布局抖动和长任务诊断。3. 读懂Perfetto UI核心概念与视图拆解3.1 三个核心概念决定了你能否看懂tracePerfetto UI打开后所有数据都展示为横向的时间轴。理解UI的关键是理解数据模型。第一个是Track轨道。它是组织数据的基础容器通常一条轨道对应一个进程、一个线程、一个CPU。UI左侧的列表是轨道列表右侧是时间展开区。你可以在轨道列表里过滤、拖拽、固定、折叠就像在音轨软件里操作多轨录音一样。第二个是Slice时间片。它表示某件事在一段时间内发生了在轨道上显示为一个彩色横向矩形。一个slice有明确的开始时间、结束时间、所属线程、深度和自定义参数。举个例子主线程轨道上的Choreographer#doFrame就是主线程执行帧绘制的slice它的宽度直接反映了这次帧准备的耗时。Slice可以嵌套父slice包着子slice深层次对应方法调用栈。第三个是Counter计数器。它表示某个值随时间变化的关系比如CPU频率、RSS内存、线程的内存占用。Counter在UI中显示为折线图可以跟Slice叠在一起看趋势。还有一个Instant瞬时事件表示某个时间点发生了某事不占用持续时间UI里显示为一条竖线。把这些概念记在脑子里Perfetto UI的所有操作就都有了落脚点。Slice太长说明执行函数耗时某个线程的track长期空白说明它被挂起没干活Counter在高峰时飙升说明资源压力来了。3.2 UI操作入门从缩放左手到选区UI的操作习惯跟看视频很像。滚轮缩放时间轴按住空格键拖动平移双击某个区域放大。选中一段区间用左键拖选选中后可以用快捷键Q锁定这段区间方便反复对照。点击任意一个slice右侧的详情面板会展开所有参数包括线程名、进程名、slice的名称、时长、起始时间戳。对性能分析最有用的就是duration和parent slice前者告诉你耗时多少后者帮你定位是哪个上层任务拉起的这段执行。搜索框是日常定位的利器。按/键唤起搜索输入Choreographer或者GCUI会高亮所有匹配的slice并列出出现位置点一下就能跳到对应时间点。排查掉帧时通常先用搜索框找到所有Choreographer#doFrame然后按持续时间排序找出最长的那个再放大看它前后被什么打断。3.3 常用视图火焰图、调度视图和堆分析Perfetto UI自带了几个专用视图藏在选中某个线程后弹出窗口里。进程和线程的调度视图是最常用的。点击一个线程的轨道UI会展示这个线程在所选时间窗内的状态切片运行Running、可运行Runnable、睡眠Sleeping、不可中断Uninterruptible。颜色不同一眼就能看出线程到底是在干活还是在等锁、等IO。火焰图视图适合看调用栈分布。点开一个主线程的火焰图标签按函数调用关系层层堆叠横向宽度就是累计耗时。发现某个函数占了一大块就可以顺着父调子关系找到责任函数。这个视图对普通App开发来说是性价比最高的入口。堆分析视图要配合heapprofd抓取才能用。抓取时指定堆数据源重新运行App中的某个业务路径UI里的堆标签会显示各个调用栈的内存分配占比。排查内存泄漏和臃肿对象时这个视图能直接告诉你内存是哪段代码分配出去的比用Android Studio的Allocation Tracker体感好得多。4. 实战案例定位一次掉帧的完整排查过程4.1 场景设定与复现准备下面用一个我实际处理过的场景串一遍全流程所有细节都是可复现的。背景某App首页信息流列表用户滑动时偶尔掉帧目测1~2秒内会出现两三次卡顿但没有稳定复现路径。我们怀疑主线程做重活但不知道具体在哪。环境是Android 12设备、debug包、USB直连电脑。先写抓取配置。这个场景需要看主线程调用栈、线程调度、GC和Binder调用所以数据源选sched、freq、cpu_idle、am、gfx以及Android logdata_sources { config { name: linux.ftrace ftrace_config { ftrace_events: sched/sched_switch ftrace_events: sched/sched_wakeup ftrace_events: power/cpu_frequency ftrace_events: power/cpu_idle } } } data_sources { config { name: android.log android_log_config { log_ids: LID_DEFAULT } } } data_sources { config { name: android.gpu } } duration_ms: 15000 buffer_size_kb: 1310724.2 抓取trace和保存现场把配置push到设备然后让操作App的人准备开始滑动列表。命令要分段执行adb push config.pbtx /data/local/tmp/config.pbtx adb shell cat /data/local/tmp/config.pbtx | perfetto -c - -o /data/misc/perfetto-traces/feed_scroll.pftrace为什么用管道因为perfetto支持从stdin读配置文件这样省一步push步骤也减少了出错可能性。命令开始后让操作者立刻在App里连续上下滑动列表大概滑动10秒等命令结束后用adb pull拉回文件。这里有个细节抓取时间要覆盖准备操作滑动操作手势停止三个阶段这样便于对比操作前后主线程的负载变化。如果抓取太短问题往往刚好没被录进去还得重来。4.3 在UI里一步步定位到真正的元凶拖入trace打开后我先在搜索框输入Choreographer#doFrame按时间排序。去掉正常时长的片段重点锁定一个耗时特别长的doFrame比如正常帧slice只有10ms左右某个却到了80ms。放大到那个时间点先看主线程这个doFrame内部的子slice通常能直接看到在View.draw或者Layout阶段花了很久。但更狡猾的情况是主线程根本没在做布局绘制而是卡在某个调用等待。这时候要看主线程轨道上doFrame slice里的等待点如果子slice显示在BinderProxy.transact上说明主线程在等跨进程IPC返回问题不在UI代码本身而在服务端或者某个远程调用上。再切到主线程的调度视图看同一个时间窗内线程的状态分布。如果发现状态是Runnable但没在运行说明它被放进了可运行队列却一直没有抢到CPU。那么问题可能是CPU被高优先级任务占满了切到CPU轨道上看同一时刻是哪个线程在占用核心很可能会看到渲染线程或者某个后台线程在狂跑。如果状态是Sleeping且紧跟着GC事件就是内存分配压力导致的阻塞。我们这次案例最终定位到的是一次列表快速上滑触发了大量Bitmap分配主线程在doFrame中触发GCGC线程频繁抢占CPU同时前台的native内存占用直逼阈值。根因是图片加载库在滑动时没有走bitmap复用池每次滑动都新建解码对象。4.4 用SQL把分析结论固化下来Perfetto内置的trace processor支持SQLite查询这是它区别于所有传统trace工具的杀招。UI左下角打开Query标签就可以直接写SQL。定位最耗时top sliceSELECT t.name AS thread_name, s.name AS slice_name, s.dur AS duration_us FROM slices s JOIN threads t ON s.utid t.utid WHERE s.dur 1000000 ORDER BY s.dur DESC LIMIT 20;统计线程时间片状态占比看哪个线程在抢CPUSELECT t.name AS thread_name, state, SUM(dur) AS total_dur_us FROM sched s JOIN threads t ON s.utid t.utid GROUP BY t.name, state ORDER BY total_dur_us DESC LIMIT 30;搜索结果里清晰列出了所有超长slice和每个进程线程的调度总耗时。用SQL做疑点过滤比肉眼一个个点开看又准又快尤其当trace文件很大的时候它几乎是唯一高效的分析路径。5. 常见问题与避坑技巧实录5.1 问题速查表下面这些坑都是我实际踩过的整理成速查表直接拿去对照现象根本原因解决方案抓取报错permission denied输出路径不可写输出到/data/misc/perfetto-traces/或改用adb shell perfetto --txttrace文件打不开抓取时长太短或buffer溢出导致数据损坏增大buffer_size_kb缩短时长重新抓取看不到App方法调用栈数据源没放开atrace category在配置中增加App自定义数据源并确保App启用了Java方法跟踪掉了帧但找不到卡顿点搜索范围不对先用Choreographer#doFrame锁定片段再配合sched状态看上下文时间线对不上实际时钟trace时间戳基于CLOCK_BOOTTIME不要用墙上时间对比用UI顶部的时间刻度换算文件过大UI卡死数据源开太多减少数据源只保留当前问题所需的最小集合5.2 抓取配置的经验调优我个人建议新手第一件事是把模板固化下来。别每次都临时手写配置先在本地维护一个模板库包含日常分析的常用数据源组合。抓取时长从3秒开始试确认链路通了再拉长到10~15秒。buffer大小也不是越大越好64mb能覆盖绝大多数场景128mb对多数据源复合问题也足够再往上只会拖慢加载速度。还有两个实用技巧。一是Android App如果打了systrace标签ATRACE_TAG相关的自定义事件会出现在trace里分析业务链路时特别有用记得在开头加android.log数据源。二是使用debug包测试效果接近线上又方便复现真线上问题则优先用系统自带的perfetto抓短trace不要长时间后台挂机文件膨胀后分析效率会直线下降。5.3 高效协作的团队实践Perfetto一个容易被低估的价值点是协作。排查问题时不需要把结果截几张图发群里直接把trace文件压缩后传到共享盘同事用ui.perfetto.dev打开即可。我现在的习惯是每次线上性能问题复盘都会附带一份trace文件加上一个简短的分析结论文本里面写明复现步骤、关键时间点、SQL查询语句。这样即使几个月后再需要查同一个问题打开文件重跑一遍SQL就能还原现场非常省心。说到底Perfetto并不难学。先把抓取跑通再用UI把已知问题复现一遍慢慢就会习惯所有数据都在同一条时间线上对照的分析方式。我自己的体会是一旦用过一次SQL查询来定位卡顿你就再也回不到纯肉眼盯timeline的日子了。别怕开头慢多抓几次工具变成肌肉记忆之后一次性能问题排查出结论的效率会比你想象的高得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门