FrameTimeline 标出一帧晚了,主线程、RenderThread、SurfaceFlinger 看起来都没有明显越界,这时很容易写出一句“可能是 GPU 瓶颈”。这句话风险很高:GPU 频率、GPU 计数器、电池电流、电源轨都不是 App 单帧归因工具。
07、08 已经讲了 App 到 SurfaceFlinger 的渲染路径,06、09 已经讲了刷新率和 CPU 调度/频率。第 16 篇不重复这些内容,只补一个经常被跳过的环节:当问题看起来已经压到硬件资源层时,GPU、devfreq、battery counter(电池计数器)、power rail(电源轨)这些数据该怎么进入同一段 trace 分析。
这类 counter 的麻烦在于:不是每台设备都有,不同 GPU 厂商的 counter 名称也不一样。写结论时不能说“GPU 高就是 GPU 瓶颈”,只能把 FrameTimeline、RenderThread、SurfaceFlinger、GPU timeline、频率和功耗数据放到同一段时间里看。
这篇的目标是让你知道三件事:哪些硬件数据值得抓、抓不到时怎么降级、抓到了以后怎样避免误判。读完以后不需要变成 GPU driver 工程师,但一次排查要有边界:UI 晚了、线程没忙、合成没卡住时,下一步到底该看哪条硬件信号。
什么时候需要硬件 counter
FrameTimeline 能告诉你某帧 jank,CPU 调度能告诉你线程有没有拿到 CPU,RenderThread/HWUI 能帮助判断 App 侧是否在预算内完成绘制并产出或提交 buffer。GPU 执行、完成和硬件压力要结合 FrameTimeline、GPU render stages、fence、GPU counter 或 AGI/厂商工具看。
硬件 counter 负责回答后半段问题:
| 现象 | counter 能补什么 |
|---|---|
| RenderThread 提交及时,但帧仍晚 | GPU render stages、GPU completion、GPU frequency |
| GPU 频率升高,帧仍慢 | GPU counter、GPU memory、devfreq / interconnect / memory-related device frequency tracks |
| 帧率跑一会儿后下降 | power rails、battery current、thermal log、频率上限变化 |
| 功耗异常但 UI 没明显 jank | power rail、battery counter、屏幕状态、持续动画/唤醒 |
| 游戏或播放器高负载 | AGI GPU counter、Perfetto 系统上下文、业务 Track Event |
硬件 counter 很少单独给出根因。它更像“排除错误方向”的证据:问题到底在 CPU 侧、GPU 侧、合成侧、内存带宽,还是功耗/温控策略。
几个术语先分开:
frequency是频率轨道,说明调频策略给硬件分配了什么档位,不等于利用率。counter(计数器)是硬件或驱动暴露的采样值,可能是百分比、字节数、事件次数,也可能是厂商自定义单位。render stage是 GPU 工作时间片,能帮助判断 GPU 工作是否压过帧预算,但它依赖 producer 和驱动支持。battery current(电池电流)是电池口径,包含整机,不是某个 App 的耗电。power rail(电源轨)是子系统能量口径,能看到 GPU、Display、Memory、WLAN 这类 rail 的变化,但仍然不是进程口径。
硬件 counter 必须和同一段时间内的帧、线程、SurfaceFlinger、业务动作一起读。单条轨道再漂亮,也不能独立完成归因。
几类数据别混用
| 数据 | TraceConfig 采集入口 | PerfettoSQL 模块/表 | 能回答的问题 | 边界 |
| — | — | — | — |
| GPU render stages | gpu.renderstages 或厂商后缀 producer | gpu_slice 等 GPU 轨道 | GPU 工作是否跨过目标窗口 | 依赖 GPU producer 和驱动支持 |
| GPU frequency | linux.ftrace + power/gpu_frequency,或 linux.sys_stats 轮询 | stdlib android.gpu.frequency / view android_gpu_frequency | GPU 调频、上限、温控后的频率状态 | 频率不是利用率,单位要按设备和 track 标注 |
| GPU memory | linux.ftrace + gpu_mem/gpu_mem_total | stdlib android.gpu.memory / view android_gpu_memory_per_process | 进程 GPU 内存趋势、纹理/buffer/Surface 规模方向 | 内存不是带宽,也不是单帧 GPU 忙 |
| GPU counters | gpu.counters、gpu.counters.adreno 等 | counter、gpu_counter_track | fragment、vertex、texture、memory 等硬件事件 | counter ID/名称依赖设备 |
| GPU work period | linux.ftrace + power/gpu_work_period | stdlib android.gpu.work_period | 部分设备上的 UID/GPU 工作周期 | 不等价于单帧 completion;归到 frame/layer 仍需 token、render stages 或 fence |
| Battery counters | android.power | counter | 电池电流、电压、电量变化 | battery 是整机口径,受 USB/充电状态影响,不是 App 功耗 |
| Power rails | android.power + collect_power_rails | stdlib android.power_rails | GPU、Display、Memory 等 rail 的能量变化 | 依赖硬件和 IPowerStats HAL,不是进程口径 |
Perfetto 官方 GPU 文档还提到 vulkan.memory_tracker、gpu.log 等数据源。它们更偏图形专项,不建议塞进默认长 Trace。先用最小系统上下文定位问题段,再决定是否加专项数据源。
设备支持是第一关
GPU counter 不是 Android 通用能力。官方文档里有几个约束要记住:
- GPU producer 可能注册带硬件后缀的数据源名,例如
gpu.counters.adreno、gpu.renderstages.mali。TraceConfig 需要写精确名称。 - GPU counters 通过设备自己的 counter ID 或 counter name 选择。可用 ID/名称来自 GPU producer 暴露的 descriptor。
counter_ids和counter_names二选一,不要混用;counter_names和 glob 选择只在部分 producer 上支持,要看 descriptor 里的supports_counter_names/supports_counter_name_globs。- Android GPU frequency 可以通过 ftrace 的
power/gpu_frequency采集。 - 每进程 GPU memory 可以通过 ftrace 的
gpu_mem/gpu_mem_total采集。
Power counters 也有类似边界。Battery counters 从 Android 10 开始引入,依赖设备电源管理硬件,Pixel 上更常见。Power rails 属于 ODPM 维度,依赖硬件和 IPowerStats HAL;Android Studio Power Profiler 文档把 ODPM 作为 Android 10+、Pixel 6 及之后设备上更现实的能力,很多量产设备没有。
Perfetto ODPM 数据源是 Android 10+ 引入的能力,但真实可用性取决于专用硬件和 HAL;Android Studio Power Profiler 里提到的 Pixel 6+,更像工具体验和现实覆盖面的提醒,不是 Perfetto power rails 的版本下限。
先确认设备能力
GPU/Power 配置不能先写再碰运气。抓取前先用 Perfetto UI 的 Record 页,或在设备上查询数据源 descriptor,确认 producer 名称、counter ID、单位和名称选择能力:
1 | adb shell perfetto --query --long |
查询输出会很长,只摘关键字段:data source name、GPU counter id、unit、description、supports_counter_names、supports_counter_name_globs、power rail metadata。常见 Android GPU producer 会带硬件后缀,例如 gpu.counters.adreno 或 gpu.renderstages.mali;TraceConfig 必须写 descriptor 里实际暴露的精确名称。
报告要保留能力清单,避免把“设备不支持”“配置名不匹配”“buffer 丢了”和“信号没升高”混在一起:
1 | { |
现场 preset 也要分层:
| preset | 数据源 | 场景 | 上传和降级规则 |
|---|---|---|---|
baseline_jank |
FrameTimeline、App marker、sched、CPU/GPU freq、idle | 线上或普通现场第一轮 | 可进入现场包;没有 GPU stage 时只写硬件趋势缺证据 |
gpu_suspect_short |
baseline + render stages、GPU memory、devfreq、power/battery 短轮询 | 复现窗口 10 到 30 秒 | 只用于短窗口;stats 有 data loss 就降级 |
gpu_counter_lab |
gpu counters、AGI/厂商 counter、必要系统上下文 | 实验室专项 | 不默认上传;必须写 counter id/name/unit 和设备 descriptor |
短 Trace 的最小信号集
这份配置假设通过 adb shell、Perfetto UI、record_android_trace 或受信系统 consumer 以 shell/系统权限采集。普通 App 不能自行开启 ftrace、android.power、GPU counters/renderstages;它只能通过 Track Event / ATrace 暴露自己的页面、动作和阶段。
第一版配置要站在第 15 篇 UI jank 基线之上:FrameTimeline、目标包 ATrace / Track Event、sched/freq/idle 是归因主线,GPU/Power 只是追加信号。这份短 Trace 配置只用于复现窗口,长时间现场要沿用第 15 篇的写文件、文件上限和 trigger 方案。
1 | duration_ms: 10000 |
这份配置的重点是“先把同窗证据拿全”。FrameTimeline 需要 Android 12 或更高版本;gpu.renderstages 依赖 GPU producer 和驱动支持,很多设备抓不到。抓不到 render stages 时只能写“缺少 GPU stage 证据”,不能把频率或 power rail 直接升级成 GPU 执行结论。
缺 GPU stage 的报告写法要明确:gpu_stage_status=unavailable; conclusion_scope=hardware_trend_only; cannot_claim=gpu_execution_delay。
power/gpu_frequency 和 devfreq/devfreq_frequency 是事件路径,gpufreq_period_ms 和 devfreq_period_ms 是 sysfs 轮询路径。event 适合精确变化点,但窗口开始处可能要向前找前一条状态;polling 适合持续趋势,但会受采样周期影响,短暂尖峰可能漏掉。power/cpu_idle 是 transition 事件,cpuidle_period_ms 是 idle state duration 轮询;插 USB、wakelock 和场景前后台都会影响 idle 口径。
thermal zone 名称、单位和可用性依设备而定。没有采 thermal_period_ms 或没有 thermal 轨道时,只能写“本 trace 无 thermal 证据”,不要写“没有温控影响”。
这些轮询周期只适合短窗口诊断;长 trace 需要放宽采样周期、拆 buffer,并实测写入速率和 CPU 开销。这里的 power counters 不是 Power HAL trace;除非额外采到 Power HAL 自身的 atrace、log 或厂商事件,否则报告只能写“频率、rail、thermal 同窗变化”,不要写“Power HAL 导致”。
Android 10+ 可以用文本配置;Android 9/P 需要二进制配置。pbtxt 适合手工排查和文章示例,自动化或长期方案更建议生成 binary proto config。Android 10/11 的非 root user build 不要假设设备能从任意路径读配置,优先走 stdin:
1 | adb shell 'cat > /data/local/tmp/gpu_power.pbtxt' < gpu_power.pbtxt |
短 Trace 允许这些信号放在同一个 Buffer 里;长 Trace 要拆开看数据量。GPU counters 的 1ms 采样、logcat、Track Event、ftrace sched 混在一起,会很快压缩 Ring Buffer 窗口。第 12 篇讲过数据丢失和 Buffer 窗口,第 15 篇讲过长时间写文件,这里要沿用同一套约束。
GPU counter 选择示例
GPU counters 要按设备选择。更稳的做法是按 descriptor 里的 counter ID 选择;counter_names 和 glob 只有 producer descriptor 明确支持时才用。
下面只是形状示例;如果 descriptor 暴露的是 gpu.counters.adreno、gpu.counters.mali 这类 suffixed name,配置必须写精确名称。name、counter_ids、counter_names 都要以目标设备支持为准。
1 | data_sources { |
如果 producer 支持按名称选择,可以写成这样:
1 | data_sources { |
专项 counter 的采样周期也要谨慎。1ms 能看细节,但开销和数据量都更高;长时间现场 Trace 不适合默认打开 GPU counters。
AGI 和 Perfetto 怎么分工
AGI 更适合回答图形内部问题:
- 某一帧有哪些 render pass、draw call、shader、pipeline state。
- fragment、vertex、texture、bandwidth 这些 GPU counter 的专项解释。
- Vulkan/OpenGL 层面的图形调优。
Perfetto 更适合回答系统时间关系:
- 问题帧前后主线程、RenderThread、SurfaceFlinger、Binder 有没有异常。
- GPU render stages 是否晚于 App/SF 的目标窗口。
- GPU/CPU 频率、power rails、thermal log 是否同一时间变化。
- 问题是否只在亮屏、刷新率变化、温升、后台竞争时出现。
所以游戏、地图、视频、Flutter/WebView 重场景经常需要两份证据:Perfetto 给系统上下文,AGI 给 GPU 内部细节。
先查 trace 是否健康
拿到 trace 后,先不要直接把缺轨道解释成“设备不支持”。配置名不匹配、ftrace event 启用失败、GPU counter spec 解析错误、power rail 解析错误、buffer 丢包,都可能让信号消失。
1 | SELECT name, idx, severity, source, value |
idx 对应 TraceConfig 里的 buffer 下标。报告里要区分三种情况:设备没有暴露信号、配置没有匹配到 producer、信号被 buffer policy 或 data loss 丢掉。只有确认配置和 trace 健康后,才能把结论降级为“这台设备或这份 trace 没有提供可用信号”。
先确认 trace 里有哪些 counter
先列所有 counter track:
1 | SELECT |
GPU counter 再单独查 gpu_counter_track。名字只是提示,单位和描述也要一起看:
1 | SELECT |
多 GPU 或跨设备报告还要补 GPU 元数据:
1 | SELECT |
如果目标 GPU / power counter 不在结果里,后面的专项 SQL 都应该跳过。
还可以先列 power rail 的元数据。这样写报告时不会把原始 rail 名称当成业务含义:
1 | INCLUDE PERFETTO MODULE android.power_rails; |
如果这个表为空,就不要写“GPU rail 没升高”。应该写:这台设备或这份 trace 没有提供可用的 power rail 元数据。
问题窗口从哪里来
GPU/Power 查询必须绑定同一个问题窗口。窗口来源要写进报告:frametimeline、track_event、trigger 或人工标注。
从 FrameTimeline 选目标 App 的 jank frame,并向前后扩展窗口:
1 | WITH jank_frame AS ( |
从 App Track Event 或第 15 篇的 trigger metadata 选窗口时,保留同一个 event_id:
1 | WITH marker AS ( |
窗口来源缺失时,只能做全局趋势扫查;不要把某段 GPU/Power 波动归到某次滑动或某一帧。
GPU frequency 和 GPU memory
所有专项查询都要先绑定问题窗口。这里用 30s 到 45s 作为示例;实际应来自 FrameTimeline 问题帧、业务 marker 或第 15 篇的 trigger metadata。
标准库有 GPU frequency 模块。这条查询输出问题窗口内的 GPU 频率区间:
1 | INCLUDE PERFETTO MODULE android.gpu.frequency; |
gpu_freq_raw 的单位要以当前设备和 track 输出为准,报告必须写明单位;不能在不同设备之间直接比较裸数值。
GPU memory 可以按进程聚合。这条查询找问题窗口内峰值最高的进程:
1 | INCLUDE PERFETTO MODULE android.gpu.memory; |
GPU 频率高只说明策略把频率抬起来了,不等于 GPU 已经满载。GPU memory 高也不等于 memory bandwidth 瓶颈,它只能提示纹理、buffer、Surface 数量等方向。devfreq 只是设备/内核暴露的频率节点,具体含义要先列 raw name,再结合 SoC、内核节点和厂商文档解释。
1 | WITH target_window AS ( |
Power rails 和 battery counters
Power rails 是累计能量计数。标准库会把相邻采样点换算成 average_power,单位是 mW;energy_delta 是 uWs,也可以当作 micro-joules。下面按问题窗口聚合,不按全 trace 峰值排序:
1 | INCLUDE PERFETTO MODULE android.power_rails; |
energy_delta_uWs 和 avg_power_mw 是按窗口重叠估算的结果。报告里同时写 valid_samples、covered_dur_ns 和窗口长度;覆盖不足时,不要用 rail 排名做结论。
Battery counters 可以直接查原始 counter,但要标单位:
1 | WITH target_window AS ( |
实验室里还有一个坑:USB 连接会影响 battery current。官方文档也提醒,插着 USB 时可能看到正电流和电量上升,这代表设备在充电。做功耗对比时,要么控制 USB 供电,要么把数据口和供电口拆开,要么只把 battery current 当趋势参考。
batt.current_ua 是整机电池口径:负数通常表示电池放电,正数表示外部电源正在给电池充电。这个符号方向只说明电池电流,不等于某个 rail 或某个 App 的功耗。
报告用窗口均值、中位数或稳定区间,不用单点峰值;current 解释必须同时写 USB/充电状态。
功耗分析更像 A/B 实验,不像单帧 jank 定位。你需要控制亮度、刷新率、网络、温度、后台任务、USB 供电、场景时长,然后比较同一设备上的两个版本。Android Studio Power Profiler 文档也把 ODPM 数据用于“同一功能不同实现”的对比,而不是单次采样后给 App 定罪。
按同一窗口推进分析
分析 GPU/Power 问题时,按这个顺序走更稳:
- 用 FrameTimeline 找问题帧或问题窗口。
- 看主线程、RenderThread、SurfaceFlinger 是否已经错过时机。
- 看 GPU render stages / GPU completion 是否跨过目标窗口。
- 看 GPU frequency 是否变化,确认是否存在抬频、降频、温控压上限。
- 查 GPU counters 或 AGI,判断 fragment、texture、shader、bandwidth 等专项方向。
- 查 power rails / battery current,判断是否有功耗或温升约束。
- 回到系统 trace,看 Binder、CPU 竞争、后台动画、刷新率变化是否解释了同一时间段。
不要从 power rail 峰值直接跳到“某 App 耗电”。Battery counters 是整机口径;power rails 是硬件子系统口径。归因到 App 还要结合前台状态、UID、线程、Surface、业务事件。
报告要机器可读
GPU/Power 结论必须带最小 schema,方便复查这份证据到底缺什么、能说明什么:
1 | { |
功耗对 App 团队更常见的是 A/B 报告,而不是单次 trace 定罪:
1 | run_id,group,device,brightness,refresh_rate,network,thermal_state,usb_power_state,scenario_duration_ms,avg_gpu_rail_mw,avg_display_rail_mw,avg_battery_current_ua,jank_rate,frame_count,confidence_notes |
信号和 App 动作也要分开写:
| 信号 | App 可执行方向 |
|---|---|
| fragment/shader pressure | 降低 overdraw、shader 复杂度、模糊/阴影/特效占比 |
| texture/bandwidth pressure | 降纹理尺寸、格式、上传频率和重复采样 |
| GPU memory high | 查 Surface、texture、buffer 生命周期和缓存上限 |
| SF jank / composition pressure | 查 layer 数量、透明层、HWC/GPU composition、BufferQueue |
| power/thermal trend | 降帧率、动画占空比、后台渲染、刷新率策略 |
结论等级建议固定:
| 等级 | 可写结论 | 禁止写法 |
|---|---|---|
| 强结论 | 同窗 frame、线程/SF、GPU stage、counter 或 AGI 一致 | 只凭频率或 rail 归因 |
| 相关方向 | 频率、devfreq、rail、thermal 与窗口同步变化,但缺 GPU stage/counter | 写成 App 单帧根因 |
| 缺证据 | descriptor、stats、metadata 或关键轨道缺失 | 把缺轨道写成没有问题 |
三个常见场景
GPU 压力与帧晚同窗出现
这个场景至少需要这组证据:FrameTimeline 标出问题帧,App 侧 UI/RenderThread/HWUI 没有先越界,SurfaceFlinger 侧时间线能说明 app jank 还是 SF jank,GPU render stage 或 completion 晚,GPU frequency 上升,GPU counters 指向 fragment/texture/bandwidth 某类压力。
还要用 surface_frame_token / display_frame_token、layer_name、process、App/SF actual timeline 校验归属。无法关联到当前 frame、layer 或 process 时,只能写“GPU 侧压力与问题窗口相关”,不要写“某 App GPU 忙导致”。
render stage 不能单独替代 fence;涉及显示完成,要同时校验 present fence、FrameTimeline 和 SurfaceFlinger timeline。
提频后仍慢
常见方向包括:GPU 内部瓶颈、memory/interconnect 相关设备频率压力、SurfaceFlinger 合成路径变复杂、温控压频、App/SF/GPU pipeline 前后依赖太紧。这里更需要 AGI 或厂商 GPU 工具,不要只靠 Perfetto 的频率轨道下结论。
功耗异常
先看屏幕、刷新率、亮度、持续动画、后台渲染,再看 CPU/GPU/devfreq 和 power rails。短时电流峰值不适合做结论,建议结合第 15 篇的长 Trace 或 snapshot,看同一场景多次是否稳定复现。
写报告前检查
| 检查项 | 报告写法 |
|---|---|
| GPU counters、render stages、power rails 缺失 | 写“本 trace 无对应证据”,不要写“没有 GPU/功耗问题” |
| counter 名称和语义随 GPU 厂商变化 | 写设备型号、GPU、counter id/name/unit、descriptor 来源 |
| Battery current 是整机口径 | 写整机趋势,不直接归因到某个 App |
| USB 连接影响 battery current | 写 USB/充电状态和供电控制方式 |
| GPU 频率高或低 | 写频率状态和上限变化,不直接写 GPU 瓶颈或没有响应 |
| Power rails 是子系统能量视角 | 写 rail/subsystem,不写单进程耗电 |
| GPU counters 数据量大 | 写采样周期、窗口长度、是否只限实验室 |
| 同一 counter 跨 GPU 厂商不可比 | 写设备和驱动边界,不做裸值横比 |
把硬件信号写成可复查结论
GPU/Power counters 是渲染分析的硬件补充。它们不能替代 FrameTimeline、RenderThread、SurfaceFlinger 和 CPU 调度,只能帮助判断问题是否已经进入 GPU、memory/interconnect、功耗或温控层。
报告按三条规则写:设备没有提供的信号要写缺失,不要写反证;只有同一窗口内多源证据一致时,才写 GPU/功耗方向;证据指向 GPU 内部时,再切到 AGI 或厂商 GPU 工具。
GPU/Power counter 只能补硬件侧证据;缺设备支持、缺同窗 frame、缺线程和 SF 证据时,不要把它写成 App 单帧根因。
参考文档
- GPU data sources
- Power data sources
- PerfettoSQL standard library
- AGI GPU performance counters
- Android Studio Power Profiler
- FrameTimeline
- CPU frequency and idle states
- Trace Processor Stats
关于我 && 博客
欢迎关注 Android Performance。