4、Perfetto实战

发布时间:2026/9/7 17:55:15
4、Perfetto实战 4.1 Perfetto架构与原理我们先看看Perfetto长什么样。它的架构其实不复杂我画了张图你一看就明白。Perfetto的核心思想是「生产者-消费者」模型。内核里的Ftrace、Kprobes这些数据源加上用户空间的进程都是生产者。它们把事件写入共享内存缓冲区然后消费者比如perfetto命令行工具或者UI读取并解析。Perfetto的缓冲区是环形缓冲区。如果数据产生太快而你来不及消费老数据会被覆盖。我在项目里就吃过这个亏——抓了一个长时间trace结果关键的那几秒被冲掉了。核心要点Perfetto 内核数据源 用户空间守护进程 消费者工具数据通过共享内存传递零拷贝设计性能开销极低支持多数据源同时采集时间戳统一对齐配置建议Perfetto的配置是通过一个protobuf格式的配置文件来控制的。我贴一个我常用的模板# 配置文件: trace_config.pbtxt buffers { size_kb: 65536 # 每个缓冲区64MB fill_policy: RING_BUFFER } 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/suspend_resume } } } data_sources { config { name: linux.process_stats process_stats_config { scan_all_processes: true } } } duration_ms: 10000 # 抓取10秒小技巧缓冲区大小别设太小。我一般设64MB起步如果抓长时间trace比如30秒以上会设到256MB。不然很容易丢数据。有时候你需要抓取特定业务场景下的trace。比如用户点击某个按钮后系统发生了什么。这时候可以用Perfetto的SDK在代码里埋点// 在你的App中集成Perfetto SDK #include perfetto/tracing.h // 定义一个trace类别 PERFETTO_DEFINE_CATEGORIES( perfetto::Category(myapp.ui) .SetDescription(UI事件), perfetto::Category(myapp.network) .SetDescription(网络请求)); // 在关键路径上埋点 void onButtonClick() { TRACE_EVENT(myapp.ui, ButtonClick); // ... 你的业务逻辑 }注意生产环境慎用代码埋点虽然Perfetto SDK的开销很小微秒级但在高频调用路径上比如每帧渲染累积的开销还是可观的。我建议只在debug版本开启埋点。抓取后的第一件事Trace抓下来后别急着分析。先检查一下数据完整性用perfetto --query查看trace的基本信息检查时间戳范围确保覆盖了你关心的时段看看有没有丢数据buffer overruns# 查看trace信息 ./perfetto --query trace.perfetto-trace # 输出示例 # Trace file: trace.perfetto-trace # Duration: 10.2s # Buffer stats: # Buffer 0: 64MB, 98% used, 0 overruns # Buffer 1: 64MB, 45% used, 0 overruns如果看到overruns不为0说明缓冲区太小了。下次把size_kb调大点。总结一下抓trace的黄金法则明确目标你想分析什么问题CPU内存还是I/O控制时长别一抓就是几分钟10-30秒足够控制数据源只开你需要的不然数据太多反而难分析检查完整性抓完后先看有没有丢数据4.2 CPU调度分析谁在偷跑某款手机待机功耗偏高测出来比竞品多了50mA。我抓了Perfetto trace一看发现CPU0在待机状态下每隔几秒就有一个小尖峰。点进去看是一个叫com.xxx.push的进程在跑。这个进程干了什么呢它每隔5秒唤醒一次检查一下有没有新消息。每次只跑几十毫秒看起来不多。但你想想看5秒一次一小时就是720次。每次唤醒不光CPU要起来可能还要拉起来WiFi或数据网络。积少成多功耗就上去了。在Perfetto里看CPU调度主要关注这几个维度CPU频率看是不是经常跳到高频。如果一个小任务频繁触发升频那功耗肯定高。调度延迟看一个任务从就绪到真正运行花了多久。延迟大说明系统负载高。跨核迁移看一个线程是不是频繁在不同CPU核心间跳来跳去。迁移有代价会刷缓存。核心指标我一般会先看sched_wakeup和sched_switch这两个事件。前者告诉你谁唤醒了谁后者告诉你谁在跑、跑了多久。这两个事件配合起来基本能还原出整个调度链路。举个例子你在Perfetto里看到CPU0上有个sched_wakeup事件是system_server唤醒了com.xxx.push。然后紧接着sched_switch显示com.xxx.push开始运行。这就说明是系统服务主动拉起了这个应用。那问题可能出在系统服务上而不是应用本身。进程/线程状态分析它在干嘛光看调度还不够你还得知道线程在运行的时候到底在干嘛。Perfetto里每个线程都有一个状态机常见的有这么几种状态含义功耗影响Running正在CPU上执行高取决于频率Runnable就绪但没轮到CPU中等待调度Sleeping主动休眠等待事件低正常状态Uninterruptible Sleep不可中断的休眠D状态中可能卡在IOStopped被暂停低我特别关注的是Uninterruptible Sleep状态。这个状态意味着线程在等某个内核资源比如磁盘IO或者锁。如果这个状态持续时间很长说明系统可能有IO瓶颈或者死锁风险。有一次我发现一个系统进程经常进入D状态每次持续几百毫秒。查了半天发现是某个驱动在读写eMMC时没有做好超时处理。驱动卡住了上层应用也跟着卡。功耗倒不是主要问题但用户体验极差——手机时不时就卡一下。小技巧在Perfetto里你可以直接点击某个线程的Running片段然后看它的调用栈。如果调用栈里出现了mutex_lock或者down_read之类的函数那基本就是在等锁。等锁意味着其他线程占着资源不放这时候就要去查那个线程在干嘛。4.3 SystemServer关键线程分析SystemServer是Android系统的核心进程里面跑了几十个线程。每个线程负责不同的系统服务。功耗问题里SystemServer往往是重灾区。我总结了一下最需要关注的几个线程ActivityManager管理应用生命周期。如果这个线程频繁跑说明有应用在频繁启动或切换。PowerManagerService管理电源状态。这个线程的唤醒次数直接决定了待机功耗。WindowManager管理窗口。如果这个线程跑得多说明有动画或者界面刷新。Binder线程池处理跨进程调用。Binder调用频繁说明系统服务之间交互多。我习惯的做法是在Perfetto里先搜system_server然后展开它的线程列表。按CPU占用排序看看哪些线程跑得最多。然后逐个点进去看调用栈。举个例子有一次我发现PowerManagerService的线程在待机状态下每隔几秒就醒一次。点进去看调用栈发现它在处理一个wakeLock的释放。再往下追发现是一个第三方应用持有了一个PARTIAL_WAKE_LOCK释放不及时。这就是典型的应用层问题导致系统层频繁唤醒。注意SystemServer里的Binder线程池很容易成为瓶颈。如果Binder调用量很大你会发现Binder线程全部处于Running状态其他服务线程反而在等Binder响应。这时候整个系统都会变慢功耗也会上升。我曾经遇到过一个问题某个应用每秒发起几百次Binder调用查询传感器数据直接把Binder线程池打满了。4.4 实战用Perfetto定位一个调度问题来走一遍实战。假设你拿到一个trace发现待机功耗异常。怎么用Perfetto定位先看CPU整体负载在Perfetto的CPU slice视图里看有没有小尖峰。如果有点进去看是哪个进程在跑。看唤醒链找到那个进程后看它的sched_wakeup事件是谁唤醒了它。如果是系统服务继续追系统服务。看线程状态点进那个进程的线程看它的状态分布。是不是频繁在Running和Sleeping之间切换切换频率是多少看调用栈在Running片段上点右键选择Show callstack。看它在执行什么代码。如果是Binder调用看是哪个服务。看频率在CPU频率视图里看这个进程运行时CPU频率是多少。如果频率很高说明系统可能因为这个小任务升频了。这一套走下来基本能定位到问题根因。要么是应用层频繁唤醒要么是系统服务做了多余的事要么是驱动有问题。用以下命令启动录制# 录制10秒包含CPU、调度、频率、电源状态 adb shell perfetto \ -c - --txt \ -o /data/misc/perfetto-traces/trace.perfetto \ --duration 10 \ buffers: { size_kb: 65536, fill_policy: RING_BUFFER } data_sources: [ { config: { name: linux.process_stats } }, { config: { name: linux.sys_stats } }, { config: { name: linux.ftrace } }, { config: { name: android.power } } ]我个人习惯把trace文件拉到电脑上用UI分析adb pull /data/misc/perfetto-traces/trace.perfetto .然后打开 Perfetto UI 加载这个文件。注意别用Chrome的旧版本我踩过坑——渲染会卡死。4.54 App启动功耗分析App启动是功耗优化的重灾区。很多同学以为启动快就等于功耗低其实不一定。我见过一个App启动时间只有800ms但CPU一直跑在最高频功耗反而比一个1.2秒启动的App高。咱们先录一个App启动的trace。打开Perfetto录制后快速点击App图标等界面完全显示后停止录制。在UI中重点关注这几个指标CPU频率曲线看是否长时间停留在最高频调度延迟看线程是否被频繁迁移唤醒锁看是否有不必要的wakelock举个例子我分析一个新闻App的启动trace时发现-- 在SQL分析器中查询CPU频率分布 SELECT cpu, freq, count(*) as duration_ms FROM cpu_freq WHERE ts 1000000000 AND ts 2000000000 GROUP BY cpu, freq ORDER BY duration_ms DESC结果发现CPU0在2.8GHz上跑了整整3秒。为什么因为App在启动时同时做了三件事网络请求、数据库读写、图片解码。这三件事挤在一个线程里导致CPU降不下来。核心结论App启动功耗优化的关键是「削峰填谷」。把密集计算分散到不同线程让CPU有机会进入低频率。4.65 屏幕唤醒功耗分析屏幕唤醒这个场景说白了就是手机从灭屏到亮屏的过程。我遇到过最离谱的一个case某款手机亮屏后CPU在最高频跑了5秒就为了显示一个锁屏界面。录制trace时先让手机灭屏然后按电源键唤醒录制5秒左右。在Perfetto中重点看这几个信号Display相关的ftrace事件如display_power_stateGPU频率唤醒时GPU是否被过度唤醒中断看是否有异常的中断风暴我曾经帮一个厂商分析屏幕唤醒功耗发现每次唤醒都会触发一个定时器中断频率高达100Hz。查了半天原来是某个第三方输入法在后台注册了高频定时器。我的经验屏幕唤醒功耗的优化80%的问题出在「谁在唤醒系统」上。用Perfetto的SQL查询所有唤醒源SELECT name, count(*) as wakeup_count FROM ftrace_event WHERE name irq_handler_entry AND ts 唤醒开始时间 AND ts 唤醒结束时间 GROUP BY name ORDER BY wakeup_count DESC4.7 网络请求功耗分析网络请求是功耗的隐形杀手。你想想看一个App如果每隔几秒就发一个心跳包Modem就得一直保持连接状态功耗能不高吗录制trace时让App执行一次网络请求比如刷新列表。在Perfetto中关注Modem状态看是否频繁进入/退出低功耗模式网络数据包看请求和响应的间隔CPU唤醒看网络请求是否导致CPU频繁唤醒我分析过一个即时通讯App发现它每30秒发一次心跳包。每次心跳都会让Modem从休眠状态唤醒持续约200ms。算下来一天光心跳就消耗了约5%的电量。避坑指南我曾经遇到过一个问题——Perfetto里看到网络请求的功耗很高但实际测试发现是WiFi芯片的驱动bug导致的。所以Perfetto的数据要结合功耗计如Monsoon来验证不要只看trace。4.8 知识体系总览下面这张图是我自己整理的Perfetto功耗分析框架涵盖了今天讲的三个场景