EN

Android Perfetto 系列 16:GPU 与功耗 Counter 怎么进入 Trace 分析

Word count: 6.2kReading time: 27 min
2026/05/04
loading

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.countersgpu.counters.adreno 等 | countergpu_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_trackergpu.log 等数据源。它们更偏图形专项,不建议塞进默认长 Trace。先用最小系统上下文定位问题段,再决定是否加专项数据源。

设备支持是第一关

GPU counter 不是 Android 通用能力。官方文档里有几个约束要记住:

  • GPU producer 可能注册带硬件后缀的数据源名,例如 gpu.counters.adrenogpu.renderstages.mali。TraceConfig 需要写精确名称。
  • GPU counters 通过设备自己的 counter ID 或 counter name 选择。可用 ID/名称来自 GPU producer 暴露的 descriptor。
  • counter_idscounter_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_namessupports_counter_name_globs、power rail metadata。常见 Android GPU producer 会带硬件后缀,例如 gpu.counters.adrenogpu.renderstages.mali;TraceConfig 必须写 descriptor 里实际暴露的精确名称。

报告要保留能力清单,避免把“设备不支持”“配置名不匹配”“buffer 丢了”和“信号没升高”混在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"has_gpu_renderstages": true,
"gpu_renderstages_source_name": "gpu.renderstages.mali",
"has_gpu_counters": true,
"gpu_counter_source_name": "gpu.counters.mali",
"gpu_counter_selection_mode": "counter_ids",
"selected_counter_ids_or_names": ["1", "3", "106"],
"has_gpu_freq": true,
"has_gpu_mem": true,
"has_power_rails": false,
"has_battery_current": true,
"has_thermal": true,
"missing_reason": "power_rails_metadata_empty"
}

现场 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
duration_ms: 10000

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

data_sources {
config {
name: "linux.ftrace"
ftrace_config {
compact_sched {
enabled: true
}
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
ftrace_events: "power/gpu_frequency"
ftrace_events: "power/gpu_work_period"
ftrace_events: "gpu_mem/gpu_mem_total"
ftrace_events: "devfreq/devfreq_frequency"

atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "wm"
atrace_categories: "hal"
atrace_categories: "binder_driver"
atrace_apps: "com.example.app"
}
}
}

data_sources {
config {
name: "android.surfaceflinger.frametimeline"
}
}

data_sources {
config {
# 示例占位:替换成 descriptor 中实际暴露的 gpu.renderstages* 名称。
name: "<gpu.renderstages descriptor name>"
}
}

data_sources {
config {
name: "track_event"
track_event_config {
enabled_categories: "ui"
enabled_categories: "app"
}
}
producer_name_filter: "com.example.app"
producer_name_regex_filter: "com\\.example\\.app(:.*)?"
}

data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}

data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
stat_period_ms: 1000
cpufreq_period_ms: 500
cpuidle_period_ms: 500
gpufreq_period_ms: 500
devfreq_period_ms: 500
thermal_period_ms: 1000
}
}
}

data_sources {
config {
name: "android.power"
android_power_config {
battery_poll_ms: 250
battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT
battery_counters: BATTERY_COUNTER_CHARGE
battery_counters: BATTERY_COUNTER_CURRENT
battery_counters: BATTERY_COUNTER_VOLTAGE
collect_power_rails: true
}
}
}

这份配置的重点是“先把同窗证据拿全”。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_frequencydevfreq/devfreq_frequency 是事件路径,gpufreq_period_msdevfreq_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
2
3
adb shell 'cat > /data/local/tmp/gpu_power.pbtxt' < gpu_power.pbtxt
adb shell 'cat /data/local/tmp/gpu_power.pbtxt | perfetto --txt -c - -o /data/misc/perfetto-traces/gpu_power.perfetto-trace'
adb pull /data/misc/perfetto-traces/gpu_power.perfetto-trace

短 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.adrenogpu.counters.mali 这类 suffixed name,配置必须写精确名称。namecounter_idscounter_names 都要以目标设备支持为准。

1
2
3
4
5
6
7
8
9
10
11
data_sources {
config {
name: "gpu.counters"
gpu_counter_config {
counter_period_ns: 1000000
counter_ids: 1
counter_ids: 3
counter_ids: 106
}
}
}

如果 producer 支持按名称选择,可以写成这样:

1
2
3
4
5
6
7
8
9
10
11
data_sources {
config {
name: "gpu.counters.adreno"
gpu_counter_config {
counter_period_ns: 1000000
counter_names: "GPU % Busy"
counter_names: "*Fragment*"
counter_names: "*Texture*"
}
}
}

专项 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name IN (
'ftrace_setup_errors',
'ftrace_cpu_has_data_loss',
'gpu_counters_invalid_spec',
'gpu_counters_missing_spec',
'gpu_render_stage_parser_errors',
'power_rail_empty_packet',
'power_rail_unknown_index',
'traced_buf_trace_writer_packet_loss',
'traced_buf_chunks_discarded',
'traced_buf_chunks_overwritten',
'traced_buf_patches_failed'
)
OR name GLOB 'ftrace_cpu_*overrun*'
OR name GLOB 'ftrace_cpu_*dropped*'
OR name GLOB 'traced_buf_*packet_loss'
)
ORDER BY name, idx;

idx 对应 TraceConfig 里的 buffer 下标。报告里要区分三种情况:设备没有暴露信号、配置没有匹配到 producer、信号被 buffer policy 或 data loss 丢掉。只有确认配置和 trace 健康后,才能把结论降级为“这台设备或这份 trace 没有提供可用信号”。

先确认 trace 里有哪些 counter

先列所有 counter track:

1
2
3
4
5
6
7
8
9
10
SELECT
c.track_id,
t.name,
COUNT(*) AS samples,
MIN(c.ts) / 1e9 AS first_s,
MAX(c.ts) / 1e9 AS last_s
FROM counter c
JOIN counter_track t ON c.track_id = t.id
GROUP BY c.track_id, t.name
ORDER BY samples DESC;

GPU counter 再单独查 gpu_counter_track。名字只是提示,单位和描述也要一起看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT
c.track_id,
g.ugpu,
g.gpu_id,
g.name,
g.unit,
g.description,
COUNT(*) AS samples,
MIN(c.ts) / 1e9 AS first_s,
MAX(c.ts) / 1e9 AS last_s
FROM counter c
JOIN gpu_counter_track g ON c.track_id = g.id
GROUP BY c.track_id, g.ugpu, g.gpu_id, g.name, g.unit, g.description
ORDER BY samples DESC;

多 GPU 或跨设备报告还要补 GPU 元数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT
gct.id AS track_id,
gct.name AS counter_name,
gct.unit,
gct.description,
gct.gpu_id AS raw_gpu_id,
gpu.name AS gpu_name,
gpu.vendor,
gpu.model,
gpu.architecture,
gpu.machine_id
FROM gpu_counter_track gct
LEFT JOIN gpu ON gct.ugpu = gpu.id
ORDER BY gpu.machine_id, gct.gpu_id, gct.name;

如果目标 GPU / power counter 不在结果里,后面的专项 SQL 都应该跳过。

还可以先列 power rail 的元数据。这样写报告时不会把原始 rail 名称当成业务含义:

1
2
3
4
5
6
7
8
9
INCLUDE PERFETTO MODULE android.power_rails;

SELECT
power_rail_name,
raw_power_rail_name,
friendly_name,
subsystem_name
FROM android_power_rails_metadata
ORDER BY subsystem_name, power_rail_name;

如果这个表为空,就不要写“GPU rail 没升高”。应该写:这台设备或这份 trace 没有提供可用的 power rail 元数据。

问题窗口从哪里来

GPU/Power 查询必须绑定同一个问题窗口。窗口来源要写进报告:frametimelinetrack_eventtrigger 或人工标注。

从 FrameTimeline 选目标 App 的 jank frame,并向前后扩展窗口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
WITH jank_frame AS (
SELECT
a.ts,
a.dur,
p.name AS process_name,
a.jank_type,
a.display_frame_token,
a.surface_frame_token
FROM actual_frame_timeline_slice a
JOIN process p USING (upid)
WHERE p.name = 'com.example.app'
AND COALESCE(a.jank_type, '') NOT IN ('', 'None')
ORDER BY a.ts
LIMIT 1
)
SELECT
'frametimeline' AS window_source,
ts - 5000000000 AS start_ts,
ts + dur + 1000000000 AS end_ts,
display_frame_token,
surface_frame_token,
process_name,
jank_type
FROM jank_frame;

从 App Track Event 或第 15 篇的 trigger metadata 选窗口时,保留同一个 event_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
WITH marker AS (
SELECT
s.ts,
s.dur,
s.name
FROM slice s
WHERE s.name = 'FieldTrigger/app_ui_jank/event_id=evt-20260504-153012-001'
LIMIT 1
)
SELECT
'track_event' AS window_source,
ts - 5000000000 AS start_ts,
ts + MAX(dur, 1000000000) AS end_ts,
name AS marker_name,
0 AS matched_marker_delta_ms
FROM marker;

窗口来源缺失时,只能做全局趋势扫查;不要把某段 GPU/Power 波动归到某次滑动或某一帧。

GPU frequency 和 GPU memory

所有专项查询都要先绑定问题窗口。这里用 30s 到 45s 作为示例;实际应来自 FrameTimeline 问题帧、业务 marker 或第 15 篇的 trigger metadata。

标准库有 GPU frequency 模块。这条查询输出问题窗口内的 GPU 频率区间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
INCLUDE PERFETTO MODULE android.gpu.frequency;

WITH target_window AS (
SELECT 30e9 AS start_ts, 45e9 AS end_ts
)
SELECT
ts / 1e9 AS ts_s,
dur / 1e9 AS dur_s,
gpu_id,
gpu_freq AS gpu_freq_raw
FROM android_gpu_frequency
JOIN target_window
WHERE ts < end_ts
AND ts + dur > start_ts
ORDER BY ts
LIMIT 200;

gpu_freq_raw 的单位要以当前设备和 track 输出为准,报告必须写明单位;不能在不同设备之间直接比较裸数值。

GPU memory 可以按进程聚合。这条查询找问题窗口内峰值最高的进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
INCLUDE PERFETTO MODULE android.gpu.memory;

WITH target_window AS (
SELECT 30e9 AS start_ts, 45e9 AS end_ts
)
SELECT
p.name AS process_name,
MAX(g.gpu_memory) / 1048576.0 AS max_gpu_mem_mb
FROM android_gpu_memory_per_process g
JOIN process p USING (upid)
JOIN target_window
WHERE g.ts < end_ts
AND g.ts + g.dur > start_ts
GROUP BY p.name
ORDER BY max_gpu_mem_mb DESC
LIMIT 20;

GPU 频率高只说明策略把频率抬起来了,不等于 GPU 已经满载。GPU memory 高也不等于 memory bandwidth 瓶颈,它只能提示纹理、buffer、Surface 数量等方向。devfreq 只是设备/内核暴露的频率节点,具体含义要先列 raw name,再结合 SoC、内核节点和厂商文档解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WITH target_window AS (
SELECT 30e9 AS start_ts, 45e9 AS end_ts
)
SELECT
t.name AS devfreq_track_name,
COUNT(*) AS samples,
MIN(c.ts) / 1e9 AS first_s,
MAX(c.ts) / 1e9 AS last_s,
MIN(c.value) AS min_value_raw,
MAX(c.value) AS max_value_raw
FROM counter c
JOIN counter_track t ON c.track_id = t.id
JOIN target_window
WHERE LOWER(t.name) GLOB '*devfreq*'
AND c.ts >= start_ts
AND c.ts < end_ts
GROUP BY t.name
ORDER BY samples DESC;

Power rails 和 battery counters

Power rails 是累计能量计数。标准库会把相邻采样点换算成 average_power,单位是 mW;energy_delta 是 uWs,也可以当作 micro-joules。下面按问题窗口聚合,不按全 trace 峰值排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
INCLUDE PERFETTO MODULE android.power_rails;

WITH target_window AS (
SELECT 30e9 AS start_ts, 45e9 AS end_ts
),
rail_in_window AS (
SELECT
c.power_rail_name,
m.raw_power_rail_name,
m.friendly_name,
m.subsystem_name,
MIN(c.ts + c.dur, end_ts) - MAX(c.ts, start_ts) AS overlap_dur_ns,
c.energy_delta * (
CAST(MIN(c.ts + c.dur, end_ts) - MAX(c.ts, start_ts) AS DOUBLE) / c.dur
) AS overlap_energy_delta_uWs
FROM android_power_rails_counters c
LEFT JOIN android_power_rails_metadata m USING (power_rail_name)
JOIN target_window
WHERE c.ts < end_ts
AND c.ts + c.dur > start_ts
AND c.dur > 0
AND c.energy_delta IS NOT NULL
)
SELECT
power_rail_name,
raw_power_rail_name,
friendly_name,
subsystem_name,
COUNT(*) AS valid_samples,
SUM(overlap_dur_ns) AS covered_dur_ns,
SUM(overlap_energy_delta_uWs) AS energy_delta_uWs,
SUM(overlap_energy_delta_uWs) / ((SELECT end_ts - start_ts FROM target_window) / 1e9) / 1000.0 AS avg_power_mw
FROM rail_in_window
GROUP BY power_rail_name, raw_power_rail_name, friendly_name, subsystem_name
ORDER BY avg_power_mw DESC
LIMIT 20;

energy_delta_uWsavg_power_mw 是按窗口重叠估算的结果。报告里同时写 valid_samplescovered_dur_ns 和窗口长度;覆盖不足时,不要用 rail 排名做结论。

Battery counters 可以直接查原始 counter,但要标单位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WITH target_window AS (
SELECT 30e9 AS start_ts, 45e9 AS end_ts
)
SELECT
c.ts / 1e9 AS ts_s,
t.name,
c.value,
CASE
WHEN t.name = 'batt.current_ua' THEN 'uA'
WHEN t.name = 'batt.charge_uah' THEN 'uAh'
WHEN t.name = 'batt.capacity_pct' THEN '%'
WHEN t.name = 'batt.voltage_uv' THEN 'uV'
ELSE 'unknown'
END AS unit
FROM counter c
JOIN counter_track t ON c.track_id = t.id
JOIN target_window
WHERE t.name GLOB 'batt.*'
AND c.ts >= start_ts
AND c.ts < end_ts
ORDER BY c.ts;

实验室里还有一个坑: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 问题时,按这个顺序走更稳:

  1. 用 FrameTimeline 找问题帧或问题窗口。
  2. 看主线程、RenderThread、SurfaceFlinger 是否已经错过时机。
  3. 看 GPU render stages / GPU completion 是否跨过目标窗口。
  4. 看 GPU frequency 是否变化,确认是否存在抬频、降频、温控压上限。
  5. 查 GPU counters 或 AGI,判断 fragment、texture、shader、bandwidth 等专项方向。
  6. 查 power rails / battery current,判断是否有功耗或温升约束。
  7. 回到系统 trace,看 Binder、CPU 竞争、后台动画、刷新率变化是否解释了同一时间段。

不要从 power rail 峰值直接跳到“某 App 耗电”。Battery counters 是整机口径;power rails 是硬件子系统口径。归因到 App 还要结合前台状态、UID、线程、Surface、业务事件。

报告要机器可读

GPU/Power 结论必须带最小 schema,方便复查这份证据到底缺什么、能说明什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"trace_name": "gpu_power_20260504_153012",
"device": "device_name",
"android_build": "fingerprint",
"gpu_vendor": "Qualcomm",
"gpu_model": "Adreno 740",
"scenario": "feed_scroll",
"window_source": "frametimeline",
"window_start_ms": 30000,
"window_end_ms": 45000,
"app_frame_count": 900,
"jank_count": 12,
"app_thread_status": "within_budget",
"rt_status": "within_budget",
"sf_status": "within_budget",
"gpu_stage_status": "available",
"gpu_freq_status": "high_raw_khz",
"gpu_counter_status": "fragment_pressure",
"power_rail_status": "available",
"battery_status": "usb_power_connected",
"thermal_status": "nominal",
"stats_quality_level": "warning",
"fallback_used": "none",
"conclusion_scope": "gpu_direction",
"evidence_grade": "B",
"next_tool": "AGI"
}

功耗对 App 团队更常见的是 A/B 报告,而不是单次 trace 定罪:

1
2
3
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
run-001,before,pixel,200nit,120hz,wifi,nominal,disconnected,300000,420,510,-820000,0.034,36000,stable temperature
run-002,after,pixel,200nit,120hz,wifi,nominal,disconnected,300000,360,508,-760000,0.031,36000,same preset

信号和 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_tokenlayer_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 单帧根因。

参考文档

  1. GPU data sources
  2. Power data sources
  3. PerfettoSQL standard library
  4. AGI GPU performance counters
  5. Android Studio Power Profiler
  6. FrameTimeline
  7. CPU frequency and idle states
  8. Trace Processor Stats

关于我 && 博客

欢迎关注 Android Performance

CATALOG
  1. 1. 什么时候需要硬件 counter
  2. 2. 几类数据别混用
  3. 3. 设备支持是第一关
  4. 4. 先确认设备能力
  5. 5. 短 Trace 的最小信号集
  6. 6. GPU counter 选择示例
  7. 7. AGI 和 Perfetto 怎么分工
  8. 8. 先查 trace 是否健康
  9. 9. 先确认 trace 里有哪些 counter
  10. 10. 问题窗口从哪里来
  11. 11. GPU frequency 和 GPU memory
  12. 12. Power rails 和 battery counters
  13. 13. 按同一窗口推进分析
  14. 14. 报告要机器可读
  15. 15. 三个常见场景
    1. 15.1. GPU 压力与帧晚同窗出现
    2. 15.2. 提频后仍慢
    3. 15.3. 功耗异常
  16. 16. 写报告前检查
  17. 17. 把硬件信号写成可复查结论
  18. 18. 参考文档
  19. 19. 关于我 && 博客