EN

Android Perfetto Series 16: GPU, Power Counters, and Hardware Bottleneck Anal...

Word count: 6kReading time: 37 min
2026/05/04
loading

FrameTimeline marks a frame as late, yet the main thread, RenderThread, and SurfaceFlinger all seem to stay within their budgets. It is tempting to write “possibly a GPU bottleneck.” That is a risky claim: GPU frequency, GPU counters, battery current, and power rails are not tools for attributing an individual App frame to a root cause.

Parts 07 and 08 covered the rendering path from the App to SurfaceFlinger; Parts 06 and 09 covered refresh rates and CPU scheduling/frequency. This article adds an often-skipped step without repeating those topics: when a problem appears to have reached the hardware resource layer, how should GPU, devfreq, battery counters, and power rails enter the analysis of the same trace interval?

These counters are difficult because not every device provides them, and different GPU vendors use different counter names. A conclusion cannot simply say “a high GPU value means a GPU bottleneck.” FrameTimeline, RenderThread, SurfaceFlinger, the GPU timeline, frequency, and power data must be examined over the same interval.

Perfetto Series Catalog

  1. Android Perfetto Series Catalog
  2. Android Perfetto Series 1: Introduction to Perfetto
  3. Android Perfetto Series 2: Capturing Perfetto Traces
  4. Android Perfetto Series 3: Familiarizing with the Perfetto View
  5. Android Perfetto Series 4: Opening Large Traces via Command Line
  6. Android Perfetto Series 5: Choreographer-based Rendering Flow
  7. Android Perfetto Series 6: Why 120Hz? Advantages and Challenges
  8. Android Perfetto Series 7: MainThread and RenderThread Deep Dive
  9. Android Perfetto Series 8: Understanding Vsync and Performance Analysis
  10. Android Perfetto Series 9: Interpreting CPU Information
  11. Android Perfetto Series 10: Binder Scheduling and Lock Contention
  12. Android Perfetto Series 11: PerfettoSQL, Trace Processor and Regression Detection
  13. Android Perfetto Series 12: Trace Dataflow and Data Loss
  14. Android Perfetto Series 13: Perfetto SDK, Track Event and App Field Traces
  15. Android Perfetto Series 14: heapprofd and Memory Profiling
  16. Android Perfetto Series 15: Boot Traces and Long-running Field Tracing
  17. Android Perfetto Series 16: GPU, Power Counters and Hardware Bottlenecks
  18. Android Perfetto Series 17: Scenario Automation and Platform Tracing
  19. Android Perfetto Series 18: Input Response Latency
  20. Video (Bilibili) - Android Perfetto Basics and Case Studies
  21. Video (Bilibili) - Android Perfetto: Trace Graph Types - AOSP, WebView, Flutter + OEM System Optimization

This article addresses practical questions: which hardware data is worth capturing, how to narrow conclusions when it is unavailable, and how to avoid misinterpretation when it is available. You do not need to become a GPU driver engineer. You do need to define the limits of an investigation: when the UI is late, threads are not busy, and composition is not blocked, which hardware signal should you examine next?

Hardware signals aligned to the same window

Diagram: frames, GPU stages, frequency, and energy samples aligned to the same window. Temporal overlap alone does not prove a GPU root cause.

When Hardware Counters Are Needed

FrameTimeline can identify a janky frame. CPU scheduling can show whether a thread obtained CPU time. RenderThread/HWUI can help determine whether the App completed drawing and produced or submitted a buffer within budget. GPU execution, completion, and hardware pressure require a combined reading of FrameTimeline, GPU render stages, fences, GPU counters, or AGI/vendor tools.

Hardware counters address the later part of that investigation:

Symptom What counters can add
RenderThread submits on time, but the frame is still late GPU render stages, GPU completion, GPU frequency
GPU frequency rises, but frames remain slow GPU counters, GPU memory, devfreq / interconnect / memory-related device frequency tracks
Frame rate drops after running for a while Power rails, battery current, thermal logs, changes to frequency caps
Abnormal power consumption without obvious UI jank Power rails, battery counters, screen state, continuous animation/wakeups
Heavy load in a game or player AGI GPU counters, Perfetto system context, application Track Events

Hardware counters rarely identify a root cause on their own. They are more useful for ruling out unpromising directions: is the problem on the CPU, GPU, composition, memory-bandwidth, or power/thermal-policy side?

First, distinguish these terms:

  • frequency is a frequency track. It shows the operating level selected by a frequency-scaling policy, not utilization.
  • A counter is a sampled value exposed by hardware or a driver. It may represent a percentage, bytes, an event count, or a vendor-defined unit.
  • A render stage is an interval of GPU work. It helps determine whether GPU work exceeds a frame budget, but depends on producer and driver support.
  • battery current is measured at the battery and covers the whole device. It is not an individual App’s power consumption.
  • A power rail measures subsystem energy. It can show changes in rails such as GPU, Display, Memory, and WLAN, but still does not provide per-process measurements.

Read hardware counters together with frames, threads, SurfaceFlinger, and application actions from the same interval. No single track, however clear, can establish attribution by itself.

Keep the Data Types Separate

Data TraceConfig collection entry point PerfettoSQL module/table Questions it can answer Limitations
GPU render stages gpu.renderstages or a producer with a vendor suffix GPU tracks such as gpu_slice Whether GPU work extends beyond the target window Depends on GPU producer and driver support
GPU frequency linux.ftrace + power/gpu_frequency, or linux.sys_stats polling stdlib android.gpu.frequency / view android_gpu_frequency GPU frequency scaling, caps, and frequency state under thermal control Frequency is not utilization; label units according to the device and track
GPU memory linux.ftrace + gpu_mem/gpu_mem_total stdlib android.gpu.memory / view android_gpu_memory_per_process Per-process GPU memory trends and possible texture/buffer/Surface scale issues Memory usage is neither bandwidth nor per-frame GPU activity
GPU counters gpu.counters, gpu.counters.adreno, etc. counter, gpu_counter_track Hardware events involving fragments, vertices, textures, memory, etc. Counter IDs/names depend on the device
GPU work period linux.ftrace + power/gpu_work_period Module android.gpu.work_period; table android_gpu_work_period_track UID/GPU work periods on some devices Not equivalent to per-frame completion; attribution to a frame/layer still needs tokens, render stages, or fences
Battery counters android.power counter Changes in battery current, voltage, and charge Whole-device measurements affected by USB/charging state, not App power consumption
Power rails android.power + collect_power_rails stdlib android.power_rails Energy changes in rails such as GPU, Display, and Memory Depends on hardware and the IPowerStats HAL; not per-process

android_gpu_work_period_track is a track-dimension table providing attribution fields such as id/uid/ugpu/gpu_id; it does not contain work intervals. Join slice using slice.track_id = android_gpu_work_period_track.id and inspect ts/dur for those intervals. Do not assume there is an android_gpu_work_period table, or treat an interval directly as the GPU completion time of an individual frame.

The official Perfetto GPU documentation also mentions data sources such as vulkan.memory_tracker and gpu.log. These serve specialized graphics investigations and should not be included in a default long trace. Locate the problematic interval with minimal system context first, then decide whether to add specialized data sources.

Device Support Comes First

GPU counters are not a universal Android capability. Keep these constraints from the official documentation in mind:

  • A GPU producer may register a data source name with a hardware suffix, such as gpu.counters.adreno or gpu.renderstages.mali. TraceConfig must use the exact name.
  • GPU counters are selected using device-specific counter IDs or counter names. Available IDs/names come from the descriptor exposed by the GPU producer.
  • Choose either counter_ids or counter_names; do not mix them. Only some producers support counter_names and glob selection. Check supports_counter_names / supports_counter_name_globs in the descriptor.
  • Android GPU frequency can be captured through the ftrace event power/gpu_frequency.
  • Per-process GPU memory can be captured through the ftrace event gpu_mem/gpu_mem_total.

Power counters have similar limitations. Battery counters were introduced in Android 10 and depend on device power-management hardware; they are more common on Pixel devices. Power rails depend on specialized hardware such as ODPM (on-device power rail monitor) and the IPowerStats HAL. The Android Studio Power Profiler documentation presents ODPM as a more practical capability on Android 10+ and Pixel 6 or later devices; many production devices lack it.

Perfetto’s ODPM data source was introduced in Android 10+, but actual availability depends on specialized hardware and the HAL. The Pixel 6+ wording in Android Studio Power Profiler documentation is better understood as guidance about tool experience and practical device coverage, not as the minimum version boundary for Perfetto power rails.

Confirm Device Capabilities First

Do not write a GPU/Power configuration and hope it works. Before capturing, use the Record page in Perfetto UI or query data source descriptors on the device to confirm producer names, counter IDs, units, and support for selection by name:

1
adb shell perfetto --query --long

The query output can be long. Extract the key fields: data source name, GPU counter ID, unit, description, supports_counter_names, supports_counter_name_globs, and power rail metadata. Common Android GPU producers have hardware suffixes, such as gpu.counters.adreno or gpu.renderstages.mali. TraceConfig must use the exact name exposed by the descriptor.

Keep a capability inventory in the report so that “unsupported device,” “configuration name mismatch,” “buffer data loss,” and “signal did not rise” remain separate:

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"
}

Use separate tiers of field presets as well:

Preset Data sources Scenario Upload and fallback rules
baseline_jank FrameTimeline, App markers, sched, CPU/GPU freq, idle First pass in production or an ordinary field investigation May be included in a field package; without GPU stages, explicitly record missing evidence and limit findings to hardware trends
gpu_suspect_short Baseline + render stages, GPU memory, devfreq, short-interval power/battery polling A 10–30-second reproduction window Short windows only; downgrade conclusions if stats report data loss
gpu_counter_lab GPU counters, AGI/vendor counters, necessary system context Specialized laboratory investigation No default upload; record counter ID/name/unit and the device descriptor

A Minimal Signal Set for Short Traces

This configuration assumes collection with shell/system privileges through adb shell, Perfetto UI, record_android_trace, or a trusted system consumer. An ordinary App cannot independently enable ftrace, android.power, or GPU counters/renderstages. It can expose only its own pages, actions, and phases through Track Event / ATrace.

Build the first configuration on the UI jank baseline from Part 15: FrameTimeline, ATrace / Track Event for the target package, and sched/freq/idle form the primary attribution path; GPU/Power adds supporting signals. This short-trace configuration is for reproduction windows only. For long-running field tracing, reuse the file writing, file limits, and trigger approach from Part 15.

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
95
96
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 {
# Example placeholder: replace with the exact gpu.renderstages* name exposed by the descriptor.
name: "<gpu.renderstages descriptor name>"
}
}

data_sources {
config {
name: "track_event"
track_event_config {
disabled_categories: "*"
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
stat_counters: STAT_CPU_TIMES
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
}
}
}

The aim is to collect a complete set of evidence for the same window first. FrameTimeline requires Android 12 or later. gpu.renderstages depends on GPU producer and driver support and is unavailable on many devices. If render stages cannot be captured, report “GPU stage evidence is missing.” Do not promote frequency or power rail measurements directly into a conclusion about GPU execution.

Be explicit when GPU stages are missing: gpu_stage_status=unavailable; conclusion_scope=hardware_trend_only; cannot_claim=gpu_execution_delay.

Remember to pair stat_period_ms with explicit stat_counters. In traced_probes versions after mid-2019, leaving stat_counters empty collects all /proc/stat counters, including per-CPU times and per-IRQ counts; earlier versions may collect nothing. Explicitly specifying STAT_CPU_TIMES both keeps behavior consistent across versions and avoids pulling hundreds of IRQ counters into the trace.

power/gpu_frequency and devfreq/devfreq_frequency are event paths; gpufreq_period_ms and devfreq_period_ms are sysfs polling paths. Events provide precise change points, but you may need to look before the window for the preceding state. Polling captures sustained trends but is constrained by its sampling period and may miss brief spikes. power/cpu_idle records transitions; cpuidle_period_ms polls idle-state durations. USB connections, wakelocks, and whether a scenario is in the foreground or background all affect idle measurements.

Thermal-zone names, units, and availability vary by device. If thermal_period_ms was not collected or there are no thermal tracks, report only “this trace contains no thermal evidence,” not “thermal control had no effect.”

These polling periods suit only short diagnostic windows. Long traces require longer sampling periods, separate buffers, and measurements of write rate and CPU overhead. These power counters are not a Power HAL trace. Unless you also capture the Power HAL’s own atrace, logs, or vendor events, report only “frequency, rail, and thermal changes occurred in the same window,” not “the Power HAL caused it.”

Android 10+ supports text configurations; Android 9/P requires binary configurations. pbtxt works well for manual investigation and article examples, while generated binary proto configurations are preferable for automation or long-term deployment. On non-root user builds of Android 10/11, do not assume the device can read a configuration from any path. Prefer 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

For short traces, these signals can share one buffer. For long traces, separate them and examine their data volumes. Combining 1 ms GPU counter sampling, logcat, Track Event, and ftrace sched can quickly shrink the ring-buffer window. Reuse the constraints on data loss and buffer windows from Part 12 and on long-running file output from Part 15.

GPU Counter Selection Examples

Select GPU counters for the particular device. Selecting counter IDs from its descriptor is the more robust approach; use counter_names and globs only when the producer descriptor explicitly supports them.

The following illustrates the configuration shape only. If the descriptor exposes a suffixed name such as gpu.counters.adreno or gpu.counters.mali, use that exact name. name, counter_ids, and counter_names must all match what the target device supports.

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
}
}
}

If the producer supports name-based selection, the configuration can look like this:

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*"
}
}
}

Choose specialized counter sampling periods carefully. A 1 ms period provides detail but increases overhead and data volume. GPU counters should not be enabled by default for long-running field traces.

Dividing the Work Between AGI and Perfetto

AGI is better suited to questions inside the graphics pipeline:

  • Which render passes, draw calls, shaders, and pipeline states make up a frame?
  • How should specialized GPU counters for fragments, vertices, textures, and bandwidth be interpreted?
  • How can rendering be optimized at the Vulkan/OpenGL level?

Perfetto is better suited to system timing relationships:

  • Is there unusual behavior in the main thread, RenderThread, SurfaceFlinger, or Binder around the problematic frame?
  • Do GPU render stages run beyond the App/SF target window?
  • Do GPU/CPU frequency, power rails, and thermal logs change at the same time?
  • Does the issue occur only with the screen on, during refresh-rate changes, as temperature rises, or under background contention?

Games, maps, video, and demanding Flutter/WebView scenarios therefore often need both sets of evidence: Perfetto supplies system context, and AGI supplies GPU internals.

Check Trace Health First

After obtaining a trace, do not immediately interpret missing tracks as “device unsupported.” Part 12 covers the general data-loss check, including stats for buffer packet loss and ftrace enablement failures and how to handle them. Run that query first. GPU and power data also have their own parsing stats: GPU counter specification errors, render-stage parsing errors, and power-rail parsing errors can all make signals silently disappear. Use this additional query for domain-specific entries:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name IN (
'gpu_counters_invalid_spec',
'gpu_counters_missing_spec',
'gpu_render_stage_parser_errors',
'power_rail_empty_packet',
'power_rail_unknown_index'
)
)
ORDER BY name, idx;

The meaning of idx depends on the statistic: traced_buf_* usually uses buffer indices, ftrace CPU entries use CPU numbers, and non-indexed entries may be null. Do not map every idx to a buffer. Distinguish three cases in the report: the device did not expose a signal, the configuration did not match a producer, or buffer policy/data loss discarded the signal. Only after checking configuration and trace health can you narrow the conclusion to “this device or this trace did not provide a usable signal.”

Identify the Counters Present in the Trace

First, list all counter tracks:

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;

Then query gpu_counter_track specifically for GPU counters. Names are only hints; inspect units and descriptions too:

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;

For reports spanning multiple GPUs or devices, include GPU metadata:

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;

If the target GPU / power counter is absent from the results, skip the specialized SQL that follows.

You can also list power-rail metadata first, so the report does not mistake a raw rail name for an application-level meaning:

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;

If this table is empty, do not write “the GPU rail did not rise.” Write instead: this device or this trace did not provide usable power-rail metadata.

Where the Problem Window Comes From

GPU/Power queries must use the same problem window. Record its source in the report: frametimeline, track_event, trigger, or manual annotation.

Select a janky frame for the target App from FrameTimeline and extend the window before and after it:

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;

When selecting a window from an App Track Event or the trigger metadata from Part 15, retain the same 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;

Without a window source, you can scan only for global trends. Do not attribute a GPU/Power fluctuation to a particular scroll or frame.

Querying GPU Render Stages

The analysis step “check whether GPU work extends beyond the target window” uses the gpu.renderstages data source. Support is much narrower than for frequency, and many devices have no corresponding producer. Always start by confirming that this trace actually contains render-stage tracks:

1
2
3
SELECT id, name
FROM track
WHERE type = 'gpu_render_stage';

Older articles and scripts often use SELECT id, name FROM gpu_track WHERE scope = 'gpu_render_stage'. This still works today, but gpu_track is marked deprecated in Perfetto source: it mixes unrelated tracks such as drm, mali, render stage, vulkan, and gpu_log in one table. Its scope column is also just an alias for type. New scripts should query track.type directly.

If the query returns zero rows, stop here and return to “Device Support Comes First.” Report gpu_stage_status=unavailable; do not substitute frequency or rail data.

If tracks exist, extract stage slices within the problem window and sort them by time spent inside it. Focus on which stage types on which queues fill the window:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
WITH target_window(start_ts, end_ts) AS (
VALUES (30000000000, 45000000000)
),
stage_tracks AS (
SELECT id, name
FROM track
WHERE type = 'gpu_render_stage'
)
SELECT
st.name AS queue_name,
s.name AS stage_name,
ROUND(s.ts / 1e6, 3) AS start_ms,
ROUND(MAX(0, MIN(s.ts + s.dur, w.end_ts) - MAX(s.ts, w.start_ts)) / 1e6, 3) AS overlap_ms,
ROUND(s.dur / 1e6, 3) AS stage_dur_ms,
s.arg_set_id
FROM slice s
JOIN stage_tracks st ON s.track_id = st.id
JOIN target_window w
WHERE s.dur > 0
AND s.ts < w.end_ts
AND s.ts + s.dur > w.start_ts
ORDER BY overlap_ms DESC
LIMIT 30;

This query answers “what was the GPU executing in the target window, and for how long?” A stage that extends beyond or fills the target frame window, combined with FrameTimeline and RenderThread evidence, can move the finding from “the frame was late” toward “GPU execution was late.” Correlation fields such as submission and render target vary by device. They are stored in slice args; inspect the keys actually present before deciding how to join the data:

1
2
3
4
5
6
SELECT DISTINCT a.flat_key
FROM slice s
JOIN track t ON s.track_id = t.id
JOIN args a USING (arg_set_id)
WHERE t.type = 'gpu_render_stage'
LIMIT 50;

Record two limitations in script comments. First, the GPU producer determines track names, stage names, and args keys. Adreno and Mali output differs, so scripts intended for multiple devices must not hardcode those names. Second, newer trace processor versions organize render-stage tracks by internal track type; gpu_track is only a compatibility view for older scripts. If a query reports no such table/column, use the three-way diagnosis from Part 11 and start with PRAGMA table_info, rather than changing application logic first.

GPU Frequency and GPU Memory

Every specialized query must first bind to a problem window. The examples use 30–45 seconds; in practice, derive the window from a problematic FrameTimeline frame, an application marker, or the trigger metadata from Part 15.

The standard library provides a GPU frequency module. This query returns GPU frequency intervals overlapping the problem window:

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;

The unit of gpu_freq_raw must follow the current device and track output, and the report must state it. Do not compare raw numbers directly across devices.

GPU memory can be aggregated by process. This query finds the processes with the highest peaks in the problem window:

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

WITH target_window AS (
SELECT 30e9 AS start_ts, 45e9 AS end_ts
)
SELECT
p.upid,
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 < target_window.end_ts
AND g.ts + g.dur > target_window.start_ts
GROUP BY p.upid, p.name
ORDER BY max_gpu_mem_mb DESC
LIMIT 20;

High GPU frequency means only that policy raised the frequency, not that the GPU is fully utilized. High GPU memory does not establish a memory-bandwidth bottleneck either; it suggests directions such as textures, buffers, or Surface counts. devfreq represents frequency nodes exposed by the device/kernel. List raw names first, then interpret them using the SoC, kernel nodes, and vendor documentation.

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 and Battery Counters

Power rails are cumulative energy counters. The standard library converts adjacent samples into average_power in mW; energy is measured in uWs, or microjoules. Note that energy_delta is the difference from the preceding sample, whereas ts/dur describes the interval toward the following sample. Do not directly combine them by multiplication when clipping to a window. The query below uses energy_since_boot_at_end - energy_since_boot to calculate energy for the current interval, joins metadata by track_id, and then aggregates over the problem window:

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
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.track_id,
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_since_boot_at_end - c.energy_since_boot) * (
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 (track_id)
JOIN target_window
WHERE c.ts < end_ts
AND c.ts + c.dur > start_ts
AND c.dur > 0
AND c.energy_since_boot_at_end IS NOT NULL
AND c.energy_since_boot_at_end >= c.energy_since_boot
)
SELECT
track_id,
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) / (SUM(overlap_dur_ns) / 1e9) / 1000.0 AS avg_power_mw
FROM rail_in_window
GROUP BY track_id, power_rail_name, raw_power_rail_name, friendly_name, subsystem_name
ORDER BY avg_power_mw DESC
LIMIT 20;

energy_delta_uWs estimates energy over the overlap assuming constant power between adjacent samples. avg_power_mw averages only over covered intervals with valid adjacent samples; missing intervals are not treated as zero power. Intervals where the cumulative count decreases are excluded. Include valid_samples, covered_dur_ns, and the window length in the report. Do not draw conclusions from rail rankings when coverage is insufficient.

Battery counters can be queried directly from raw counters, but units must be labeled:

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 GLOB '*current_ua' THEN 'uA'
WHEN t.name GLOB '*charge_uah' THEN 'uAh'
WHEN t.name GLOB '*capacity_pct' THEN '%'
WHEN t.name GLOB '*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;

There is another laboratory trap: USB connections affect battery current. The official documentation also warns that a connected USB cable may produce positive current and rising charge, indicating that the device is charging. For power comparisons, control USB power, separate the data and power connections, or use battery current only as a trend reference.

Counter track names take the form batt.<counter>. When the device reports a battery name, they become batt.<battery_name>.<counter>. Determine units with suffix GLOB patterns rather than hardcoding full names.

batt.current_ua measures current at the whole-device battery: negative values usually indicate battery discharge, while positive values indicate that external power is charging the battery. This sign describes battery current only; it does not represent the power consumption of a particular rail or App.

Use window averages, medians, or stable intervals in reports, not isolated peaks. Every interpretation of current must also state USB/charging status.

Power analysis is closer to an A/B experiment than to locating individual janky frames. Control brightness, refresh rate, network, temperature, background tasks, USB power, and scenario duration, then compare two versions on the same device. Android Studio Power Profiler documentation likewise uses ODPM data to compare different implementations of the same feature, not to blame an App after a single sample.

Work Through the Same Window

For GPU/Power investigations, this sequence is more reliable:

  1. Use FrameTimeline to locate the problematic frame or window.
  2. Check whether the main thread, RenderThread, or SurfaceFlinger has already missed its timing target.
  3. Check whether GPU render stages / GPU completion extend beyond the target window.
  4. Check GPU frequency changes for boosts, reductions, or thermally constrained caps.
  5. Inspect GPU counters or AGI for directions such as fragment, texture, shader, or bandwidth pressure.
  6. Inspect power rails / battery current for power or temperature-rise constraints.
  7. Return to the system trace and check whether Binder, CPU contention, background animations, or refresh-rate changes explain the same interval.

Do not jump from a power-rail peak to “this App consumes excessive power.” Battery counters cover the whole device; power rails cover hardware subsystems. App attribution also requires foreground state, UID, threads, Surfaces, and application events.

Make Reports Machine-Readable

GPU/Power conclusions need a minimal schema so reviewers can see what evidence is missing and what the available evidence supports:

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"
}

For App teams, power investigations more often produce A/B reports than accusations based on a single 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

Report signals separately from actions available to the App:

Signal Possible App actions
Fragment/shader pressure Reduce overdraw, shader complexity, and the share of blur/shadows/effects
Texture/bandwidth pressure Reduce texture dimensions and format cost, upload frequency, and repeated sampling
High GPU memory Inspect Surface, texture, and buffer lifecycles and cache limits
SF jank / composition pressure Inspect layer counts, transparent layers, HWC/GPU composition, and BufferQueue
Power/thermal trend Reduce frame rate, animation duty cycle, and background rendering; adjust refresh-rate policy

Use consistent conclusion grades:

Grade Supported conclusion Claims to avoid
Strong conclusion Frames, thread/SF evidence, GPU stages, and counters or AGI agree within the same window Attribution based only on frequency or rails
Correlated direction Frequency, devfreq, rails, and thermal signals change with the window, but GPU stages/counters are missing Presenting this as the root cause of an individual App frame
Missing evidence Descriptors, stats, metadata, or key tracks are missing Treating missing tracks as proof that no problem exists

Three Common Scenarios

GPU Pressure and Late Frames in the Same Window

This scenario requires at least the following evidence: FrameTimeline identifies problematic frames; App-side UI/RenderThread/HWUI did not exceed budget first; the SurfaceFlinger timeline distinguishes App jank from SF jank; GPU render stages or completion are late; GPU frequency rises; and GPU counters indicate some form of fragment/texture/bandwidth pressure.

Also validate attribution using surface_frame_token / display_frame_token, layer_name, process, and the App/SF actual timelines. If the evidence cannot be associated with the current frame, layer, or process, report only “GPU-side pressure correlates with the problem window,” not “this App’s GPU work caused it.”

Render stages cannot independently replace fences. For display completion, also validate the present fence, FrameTimeline, and SurfaceFlinger timeline.

Still Slow After a Frequency Boost

Common directions include internal GPU bottlenecks, frequency pressure on memory/interconnect-related devices, a more complex SurfaceFlinger composition path, thermal throttling, and tight dependencies between stages in the App/SF/GPU pipeline. AGI or vendor GPU tools are especially useful here. Do not conclude from Perfetto frequency tracks alone.

Abnormal Power Consumption

Start with screen state, refresh rate, brightness, continuous animation, and background rendering, then inspect CPU/GPU/devfreq and power rails. Brief current peaks are not a sound basis for a conclusion. Combine this analysis with long traces or snapshots from Part 15 and check whether the same scenario reproduces consistently across runs.

Checks Before Writing the Report

Check How to report it
GPU counters, render stages, or power rails are missing Write “this trace has no corresponding evidence,” not “there is no GPU/power problem”
Counter names and semantics differ by GPU vendor Record device model, GPU, counter ID/name/unit, and descriptor source
Battery current covers the whole device Report whole-device trends; do not attribute them directly to an App
USB affects battery current Record USB/charging state and how power supply was controlled
GPU frequency is high or low Describe frequency state and cap changes; do not directly claim a GPU bottleneck or lack of response
Power rails measure subsystem energy Report the rail/subsystem, not an individual process’s consumption
GPU counters generate large data volumes Record sampling period, window length, and whether collection is restricted to the laboratory
The same counter is not comparable across GPU vendors State device and driver boundaries; do not compare raw values across vendors

Turn Hardware Signals into Reviewable Conclusions

GPU/Power counters supplement rendering analysis with hardware evidence. They cannot replace FrameTimeline, RenderThread, SurfaceFlinger, or CPU scheduling. They help determine whether the problem has reached the GPU, memory/interconnect, power, or thermal-control layer.

Follow three rules in reports: mark signals the device did not provide as missing rather than as counterevidence; identify a GPU/power direction only when multiple sources agree within the same window; and move to AGI or vendor GPU tools when the evidence points inside the GPU.

References

  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

Source revision checked: b23c67be1159e3f0b0657bd97be9e139e0d8ee2c (2026-09-19).

About Me and the Blog

Follow Android Performance.

CATALOG
  1. 1. Perfetto Series Catalog
  2. 2. When Hardware Counters Are Needed
  3. 3. Keep the Data Types Separate
  4. 4. Device Support Comes First
  5. 5. Confirm Device Capabilities First
  6. 6. A Minimal Signal Set for Short Traces
  7. 7. GPU Counter Selection Examples
  8. 8. Dividing the Work Between AGI and Perfetto
  9. 9. Check Trace Health First
  10. 10. Identify the Counters Present in the Trace
  11. 11. Where the Problem Window Comes From
  12. 12. Querying GPU Render Stages
  13. 13. GPU Frequency and GPU Memory
  14. 14. Power Rails and Battery Counters
  15. 15. Work Through the Same Window
  16. 16. Make Reports Machine-Readable
  17. 17. Three Common Scenarios
    1. 17.1. GPU Pressure and Late Frames in the Same Window
    2. 17.2. Still Slow After a Frequency Boost
    3. 17.3. Abnormal Power Consumption
  18. 18. Checks Before Writing the Report
  19. 19. Turn Hardware Signals into Reviewable Conclusions
  20. 20. References
  21. 21. About Me and the Blog