When FrameTimeline marks a late frame but the main thread and RenderThread show no obvious long tasks, GPU work and fences are useful next checks. GPU frequency, battery current, and power rails describe hardware state in that window; they do not establish on their own why an app frame presented late.
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.
Version note (reviewed October 2026): Configurations, descriptors, import behavior, and SQL are checked against Perfetto v58.3. Android version alone guarantees neither GPU producers/counters nor power rails. Record device, driver, capture-side, and parser versions separately. Example counter IDs and windows must be bound to actual evidence.
Perfetto Series Catalog
- Android Perfetto Series Catalog
- Android Perfetto Series 1: Introduction to Perfetto
- Android Perfetto Series 2: Capturing Perfetto Traces
- Android Perfetto Series 3: Familiarizing with the Perfetto View
- Android Perfetto Series 4: Opening Large Traces via Command Line
- Android Perfetto Series 5: Choreographer-based Rendering Flow
- Android Perfetto Series 6: Why 120Hz? Advantages and Challenges
- Android Perfetto Series 7: MainThread and RenderThread Deep Dive
- Android Perfetto Series 8: Understanding Vsync and Performance Analysis
- Android Perfetto Series 9: Interpreting CPU Information
- Android Perfetto Series 10: Binder Scheduling and Lock Contention
- Android Perfetto Series 11: PerfettoSQL, Trace Processor and Regression Detection
- Android Perfetto Series 12: Trace Dataflow and Data Loss
- Android Perfetto Series 13: Perfetto SDK, Track Event and App Field Traces
- Android Perfetto Series 14: heapprofd and Memory Profiling
- Android Perfetto Series 15: Boot Traces and Long-running Field Tracing
- Android Perfetto Series 16: GPU, Power Counters and Hardware Bottlenecks
- Android Perfetto Series 17: Scenario Automation and Platform Tracing
- Android Perfetto Series 18: Input Response Latency
- Video (Bilibili) - Android Perfetto Basics and Case Studies
- 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?

Diagram: frames, GPU stages, frequency, and average power aligned to one window. The bottom line represents power derived from energy differences, not decreasing cumulative energy or measured instantaneous power. Overlap alone does not establish 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:
frequencyis a frequency track. It shows the operating level selected by a frequency-scaling policy, not utilization.- A
counteris 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 stageis an interval of GPU work. It helps determine whether GPU work exceeds a frame budget, but depends on producer and driver support. battery currentis measured at the battery and covers the whole device. It is not an individual App’s power consumption.- A
power railmeasures 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.
When I see a high GPU frequency track, I treat it as a reason to inspect the frame, not as a verdict that the GPU held it up. A frequency policy can react to load without telling us which stage missed the deadline. Align render stages and fences with App and SurfaceFlinger frames before assigning the delay.
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 / table 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 / table 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.adrenoorgpu.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_idsorcounter_names; do not mix them. Only some producers supportcounter_namesand glob selection. Checksupports_counter_names/supports_counter_name_globsin 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 depend on device power-management hardware and are more common on Pixel devices. Power rails depend on specialized hardware such as ODPM (on-device power rail monitor) and the IPowerStats HAL. Availability still needs to be checked per device on Android 12–17; many production devices lack it.
Android Studio Power Profiler documents ODPM support on Pixel 6 and later Pixels, with rail sets varying by device. This is not a claim about every Android 12+ device. Perfetto access also depends on hardware, HAL, permissions, and capture configuration; empty metadata alone does not prove unsupported hardware.
Confirm Device Capabilities First
Do not write a GPU/Power configuration and hope the device happens to support it. First identify registered producers and data-source names. In v58.3, --query --long is a human-readable service listing; --long mainly expands category text and does not print complete GPU counter descriptors. For counter IDs, units, and name-selection support, use available UI capability information or decode --query-raw with the matching proto:
1 | adb shell perfetto --query --long |
The first command is for manual name discovery. The next two require host protoc and a Perfetto source checkout as the working directory. Inspect decoded ds_descriptor.gpu_counter_descriptor for specifications and supports_counter_names/supports_counter_name_globs. A producer not yet running/registered can be absent. Confirm rail metadata after a short capture; registration of android.power does not establish rail availability.
GpuCounterSpec expresses units through numerator_units/denominator_units. Also read value_direction: in v58.3 the unspecified default looks backward, as commonly used for GPU measurements ending at the timestamp; explicit FORWARDS_LOOKING applies afterward. Do not copy the forward-held frequency-state model directly to interval measurements such as GPU Busy.
Keep a capability inventory in the report so that “unsupported device,” “configuration name mismatch,” “buffer data loss,” and “signal did not rise” remain separate:
This is an illustrative capture observation, not a universal device list. Support true with descriptors/observed data. Record empty rail metadata as null with a reason until unsupported capability is established.
1 | { |
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 example uses an authorized shell/system consumer. App-side Track Event or ATrace instrumentation does not itself grant ftrace, android.power, or GPU capture access. Constrained APIs such as ProfilingManager have separate version and policy rules; they are not authorization for arbitrary TraceConfigs.
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.
Tailor this template to verified capabilities. Replace the renderstages placeholder, package, and actual producer filters; remove an unavailable source block and record that limitation. An unreplaced but syntactically valid source name can simply match nothing, so command success does not establish captured stages.
1 | duration_ms: 10000 |
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. Leaving stat_counters empty may collect all /proc/stat counters, including per-CPU times and per-IRQ counts. Specifying STAT_CPU_TIMES makes the intended data clear 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 12+ generally permits configs in /data/misc/perfetto-configs, subject to device permissions. Resolve placeholders first and stop on capture failure to avoid pulling an old file at the same path. Use a separate output name per run. Text versus binary proto encoding changes neither authorization nor source availability:
1 | adb push gpu_power.pbtxt /data/misc/perfetto-configs/gpu_power.pbtxt || exit 1 |
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 | data_sources { |
If the producer supports name-based selection, the configuration can look like this:
1 | data_sources { |
counter_period_ns is a requested period, not a guarantee of independent valid samples every millisecond. Hardware counters may have mutually exclusive groups, multiplexing, or minimum-period limits. Check descriptors/vendor contracts and actual timestamps. Merge these data_sources fragments into a complete buffer/duration config rather than starting them as indefinite captures.
Dividing the Work Between AGI and Perfetto
AGI Frame Profiler captures Vulkan directly; its OpenGL ES path translates through ANGLE, so replay/translated results are not automatically equivalent to the original driver path. System and frame profiling also have different device/API coverage. Validate the device and select supported capabilities. Official Frame Profiler overview
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 | SELECT name, idx, severity, source, value |
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 | SELECT |
Query gpu_counter_track for GPU counters. Its unit column is often empty in v58.3: retain unknown and verify descriptor numerator/denominator units and the import path instead of guessing from names. Keep descriptions and GPU identity too:
1 | SELECT |
For reports spanning multiple GPUs or devices, include GPU metadata:
1 | SELECT |
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 | INCLUDE PERFETTO MODULE android.power_rails; |
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.
Identify the specific FrameTimeline slice ID for the target app/layer and replace NULL below with it. Do not automatically pick the first jank from a same-named process. An unclosed frame cannot define a complete frame window. Five seconds before and one second after are investigation choices; retain requested bounds alongside the bounds available in the trace:
1 | WITH selected_frame(id) AS (VALUES (NULL)), |
Use a stable FieldTrigger/app_ui_jank name and place event_id in a debug annotation. This example targets default thread tracks: fill the target upid and event_id, then investigate duplicates rather than picking LIMIT 1. Restore process context separately for custom tracks. Do not hardcode a zero matching delta before validating external JSON clock/event alignment:
1 | INCLUDE PERFETTO MODULE slices.with_context; |
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 | SELECT id, name, machine_id, parent_id, dimension_arg_set_id |
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.
Once tracks exist, list stage candidates in the window, retaining slice_id, track_id, machine_id, and raw nanoseconds. This scans captured render-stage tracks; filter actual GPU/queue ownership when several are present. Summing overlapping durations from different queues is not GPU utilization:
1 | WITH target_window(start_ts, end_ts) AS ( |
This query shows producer-reported stage intervals and their overlap with the window, not an app-specific GPU critical path by itself. Correlate CPU submission, GPU execution/waits, composition, and presentation using submission/context, frame/layer, or fence evidence. GPU pressure can instead be concurrent competition. Inspect actual slice-argument keys before defining joins:
1 | SELECT DISTINCT a.flat_key |
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
Fill every subsequent NULL window with the same investigation’s integer-nanosecond bounds; unset inputs return no analysis, rather than substituting arbitrary 30–45-second data. Frequency/memory intervals generally assume a value holds until the next sample, with the final value possibly extended to trace_end. This is a state model, not proof of complete observation.
android.gpu.frequency selects GPU counters named gpufreq and creates forward intervals, but its output drops track/ugpu/machine identity. The query below retains those fields while using the same track-selection/interval rules and clipping to the target window. Filter the verified GPU when interpreting results:
1 | INCLUDE PERFETTO MODULE counters.intervals; |
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.
The GPU-memory library represents observed per-process state. This query finds the maximum observed value within the window in MiB, not a guaranteed between-sample peak, exclusive physical VRAM, or complete graphics-memory accounting:
1 | INCLUDE PERFETTO MODULE android.gpu.memory; |
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 | WITH target_window(start_ts, end_ts) AS (VALUES (NULL, NULL)) |
Power Rails and Battery Counters
Power-rail energy is cumulative in uWs (microjoules). In v58.3, library average_power uses the forward next_value interval, while energy_delta is backward-looking; do not mix them. To expose sample gaps explicitly, this query pairs raw counter samples by track and estimates overlapping energy from their difference. Set max_gap_ns from the sampling cadence, actual timestamps, and acceptance policy, not a universal threshold:
1 | INCLUDE PERFETTO MODULE android.power_rails; |
energy_delta_uWs assumes constant power within each accepted sample pair. avg_power_mw averages only accepted overlapping durations; gaps are not zero-filled. valid_pairs counts pairs, not original samples. Report requested duration, coverage ratio, and maximum gap, while still checking stats, clocks, and device behavior. Do not extrapolate energy beyond the last sample or integrate across a counter reset. Rails can overlap in scope, so summing them does not necessarily give device power.
Battery counters can be queried directly from raw counters, but units must be labeled:
1 | WITH target_window(start_ts, end_ts) AS (VALUES (NULL, NULL)) |
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.
In v58.3, current.avg_ua is device-reported average current; its averaging window is not automatically this query’s window. current_ua is a different measurement. 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.
Prefer time-weighted statistics over stable scenarios. With uneven sampling, a sample AVG or median is not a time-weighted mean/median. Current is not power: account for voltage, supply path, and time. With USB power connected, battery current especially does not represent all device consumption.
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:
- Use FrameTimeline to locate the problematic frame or window.
- Check whether the main thread, RenderThread, or SurfaceFlinger has already missed its timing target.
- Check whether GPU render stages / GPU completion extend beyond the target window.
- Check GPU frequency changes for boosts, reductions, or thermally constrained caps.
- Inspect GPU counters or AGI for directions such as fragment, texture, shader, or bandwidth pressure.
- Inspect power rails / battery current for power or temperature-rise constraints.
- 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:
The JSON and A/B CSV are hypothetical and do not describe one shared device capture. Do not fill missing frame counts with refresh-rate × duration or infer a frequency cap from the observed maximum alone. Keep unverified states unknown.
1 | { |
For App teams, power investigations more often produce A/B reports than accusations based on a single 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 |
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 | Target frame/layer and GPU dependencies are linked, thread/SF and specialized evidence agree, and alternatives have been checked | 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
A GPU-delay candidate requires the target frame/layer, app/RenderThread/SF dependencies, and related GPU stages/completion against that frame’s deadline. Rising frequency and counter pressure add context but are not prerequisites: the GPU may already be at high frequency or capped. Agreement within a window still does not exclude queueing, other processes, or upstream dependencies; validate through repeatable A/B tests or specialized tools when needed.
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
- 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
The original b23c67be source links (September 19, 2026) remain below. This review also checks v58.3 service queries, GPU descriptors, GPU frequency, GPU memory, power rails, and counter intervals.
About Me and the Blog
Follow Android Performance.