Perfetto & performance evidence

Separate CPU work, blocking, and measurement noise with a repeatable investigation.

10 min readChapter 47 of 50Advanced practice
In this chapter

Performance work is a comparison under controlled conditions, not a hunt for a large trace or a “fast” number. This Android 14–scoped exercise uses supported system tracing categories on an authorized disposable virtual target, explains capture boundaries, and then analyzes an explicitly synthetic CSV dataset. It does not promise that every device exposes identical trace data or that a trace proves hardware behavior.

Before you start#

If you have no disposable target, complete the supplied-data practice only. Never run an unbounded capture on a daily-use device.

Learning goals#

You will form a falsifiable performance hypothesis, capture a short Android system trace using a supported entry point and explicit device selection, save artifacts without overwriting unrelated files, distinguish wall-clock latency from CPU execution and blocked time, account for trace overhead and thermal/warm-up state, and report uncertainty rather than overclaiming a benchmark.

Practice#

1. State the question before capture

Use a bounded question such as: “Does opening this screen spend most of the added 60 ms runnable on CPU, or waiting for I/O/another task?” State what observation would distinguish those alternatives. Do not begin with “find a slow function.” Record Android build fingerprint, device type, refresh mode if known, app version, reproduction steps, and whether the test is an emulator or physical development target. Do not compare unlike targets as though they were interchangeable.

2. Select the target and capture a short trace

The commands below are an optional disposable-target procedure, not commands to paste blindly. First list devices and select the exact authorized serial:

sh
adb devices -l
adb -s SERIAL shell getprop ro.build.fingerprint

Replace SERIAL only after confirming the intended target. The following Bash commands create a fresh host directory and a unique device trace filename, then record a bounded 10-second capture. The device-side trace write is an explicit storage mutation; it does not flash, root, or change persistent system settings.

bash
# Run on the host; confirm SERIAL with `adb devices -l` first.
set -e
SERIAL='replace-with-confirmed-serial'
HOST_DIR="$(mktemp -d /tmp/opencode/perfetto-XXXXXX)"
RUN_ID="$(date +%s)-$$"
DEVICE_TRACE="/data/misc/perfetto-traces/course-${RUN_ID}.pftrace"
adb -s "$SERIAL" shell perfetto -o "$DEVICE_TRACE" \
  -t 10s sched freq idle am wm gfx view
adb -s "$SERIAL" pull "$DEVICE_TRACE" "$HOST_DIR/"
printf 'host trace: %s/%s.pftrace\n' "$HOST_DIR" "course-$RUN_ID"

The Android 14 pinned Perfetto CLI reference documents -o output paths, bounded -t, and simple-mode category/event selectors. It does not document --no-clobber, and the pinned src/perfetto_cmd/perfetto_cmd.cc opens the -o file with O_RDWR | O_CREAT | O_TRUNC, so an existing file at that path is truncated. Do not assume the flag exists on Android 14; newer upstream docs list it, but the pinned CLI does not. The pinned Android quickstart demonstrates writing the trace under /data/misc/perfetto-traces/; use that destination rather than assuming the Perfetto service can write /data/local/tmp. RUN_ID combines host epoch seconds and shell PID, so each run in this procedure gets a distinct device filename and does not truncate an earlier trace. mktemp creates a unique host directory for this invocation, not a global guarantee against every filesystem or process failure. If capture fails, preserve the error and do not pull a nonexistent file or change device policy. Selectors (sched, freq, idle, am, wm, gfx, view) are Android atrace categories in the documented simple-mode family; actual category/data availability depends on target build and access policy. Do not substitute guessed kernel selectors. The remote trace persists until you deliberately remove that exact file after verifying the host copy; its creation is a device-storage write. Traces can expose process names, scheduling behavior, and app activity: keep them private and redact before sharing. Open the host trace in Perfetto UI; do not upload sensitive traces to a public service.

This capture is a short system-level trace, not a benchmark result. It has tracing overhead, can miss events before/after the window, and its categories may not expose vendor GPU internals. A missing event is ambiguous: not emitted, unavailable, filtered, or outside capture are all possible. Avoid claiming “the GPU is slow” based solely on the gfx category.

3. Analyze a synthetic trace-derived dataset

Warm wall times, compared

Synthetic teaching data, not a device capture.

Baseline118 msmedian
Baseline warm samples116, 121, 118 ms; median 118, range 116 to 121. Displayed scale 90 to 130 ms.
Change101 msmedian
Change warm samples101, 105, 99 ms; median 101, range 99 to 105. Displayed scale 90 to 130 ms.

Shared wall-time scale: 90–130 ms. Dots are warm runs; diamonds are medians. The range spans the smallest and largest sample.

Read the values
ConditionWarm samples (ms)MedianMin–max
Baseline116, 121, 118118116–121
Change101, 105, 9910199–105
Synthetic teaching data. Warm runs 3–5 only, in milliseconds; cold and warm-up runs are excluded explicitly. Whiskers show sample min–max, dots individual runs, diamonds medians. Three samples do not establish a population effect or cause.

The following is fabricated CSV for arithmetic and reasoning practice; it is not a Perfetto export, device benchmark, or empirical claim. Values are milliseconds over one user-visible interaction, partitioned into estimated wall time, sampled CPU time, and blocked/wait time. The categories are simplified and need not add exactly to wall time because sampling and intervals overlap.

csv
condition,run,wall_ms,cpu_ms,blocked_ms,notes
baseline,1,142,34,89,cold launch
baseline,2,119,32,72,warm-up
baseline,3,116,31,69,warm
baseline,4,121,33,71,warm
baseline,5,118,32,70,warm
change,1,127,30,78,cold launch
change,2,102,29,58,warm-up
change,3,101,28,57,warm
change,4,105,29,59,warm
change,5,99,28,56,warm

First exclude or separately report cold/warm-up trials; do not quietly cherry-pick. For warm runs 3–5, baseline median is 118 ms and change median is 101 ms: a 17 ms lower median (about 14.4% of the baseline median). Those three samples are too few to claim a stable population effect. CPU medians are 32 ms vs 28 ms, while blocked medians are 70 ms vs 57 ms. That pattern makes reduced waiting a candidate explanation, not a demonstrated causal mechanism. The change may have shifted caching, scheduling, or test setup. Investigate which wait intervals and threads changed in the real trace.

At 60 Hz, a nominal display period is 1000/60 ≈ 16.7 ms. That is a useful reference for a frame interval, not a universal end-to-end latency budget: input, app work, compositor, display pipeline, refresh rate, missed vsync, and buffering all matter. The 17 ms interaction median change is not “one frame faster” merely because it is near 16.7 ms.

For a real comparison, hold constant the build, device, input, background load, refresh mode, capture configuration, and temperature as far as practicable. Alternate baseline/change order when possible to reduce drift. Run enough repetitions to estimate variability; report per-run values, median and spread (for example min–max or interquartile range), and note exclusions. Warm the system according to the same rule for both conditions. Thermal throttling, battery/power mode, DVFS, cache state, compilation, and background work can alter timing. Do not disable thermal protections or force clocks for a course exercise.

Trace capture itself can perturb scheduling and CPU use. Compare instrumented runs with a lightweight untraced timing where safe; use the trace to locate intervals, not to claim exact uninstrumented latency. If the trace says CPU is mostly idle while wall time elapses, distinguish runnable delay from blocked I/O, lock/fence wait, Binder wait, and sleep; then inspect the specific task and event sequence. A long duration in a slice is not necessarily CPU consumption.

4. Form an experiment that could disprove your explanation

Suppose your initial hypothesis is “the change reduces storage wait.” Specify a falsifier: if the matching trace shows no reduction in the relevant read/wait interval and the wall-time effect disappears under repeated warm trials, this explanation weakens. Check whether the path really performs I/O and whether the event/counter is available. Alternative hypotheses include reduced CPU work, different scheduling, cache warming, input timing, or thermal drift. Choose one narrow comparison at a time. If using an emulator, explicitly limit conclusions to that emulator/software path; it has no evidentiary power about physical panel, flash, radio, or thermal hardware.

Keep a compact results table:

ClaimEvidenceEstablishesDoes not establish / next test
Warm interaction median changedPer-run CSV and controlled procedureObserved sample difference on this targetPopulation effect; repeat and report spread
Waiting may explain part of itTrace interval and task stateTime-correlated wait in captured windowThat storage caused it; identify resource/caller
Change is safeRelevant correctness/regression testsCovered assertions passed in that environmentAll workloads/devices; broader compatibility tests needed

When viewing the trace, begin with the exact interval and process/thread identity, then zoom out to see what preceded it. A slice labeled with a familiar function name can be nested, asynchronous, or from another process; confirm its track and timestamp before joining it to the interaction. Compare an on-time control and a late example, not just the most dramatic event. Keep the original trace unchanged and note the viewer/version and any filters used so another reader can reproduce the interpretation. If the trace is too large or sensitive to share, provide a redacted screenshot or a small table of event timestamps and state precisely what was omitted; never publish account identifiers or network payloads to make a performance claim easier to review.

For a proposed code optimization, correctness is a separate axis from speed. First run a focused behavior test on both unchanged and changed code; then compare performance with the same input and build type. A faster result produced by skipping work or changing output is not an optimization. Conversely, a correctness-preserving patch that improves one synthetic scenario can still regress another workload. Include one relevant non-target workload in the regression plan and report it even if it is unchanged.

Knowledge check#

  1. Which command field bounds capture duration? What state does the command leave behind?
  2. In the synthetic table, why exclude or separately report runs 1–2 rather than silently remove them?
  3. Can wall_ms - cpu_ms be labeled “disk time”? Why or why not?
  4. What does a trace with no GPU event prove?
  5. Design one falsifier for “the change reduced storage wait,” and name one confounder that remains.

Answers#

  1. -t 10s bounds duration. The command creates a trace file on the device and the pull creates a host file; it does not establish that every requested category is available. Use a unique path and preserve/clean up only your own artifact.
  2. They are explicitly cold/warm-up observations and may reflect a different state; keeping them visible prevents cherry-picking and makes the warm-up rule auditable. A predeclared exclusion is not a license to hide results.
  3. No. The difference can include blocked locks/fences/Binder, sleep, scheduling delay, and measurement gaps; CPU sampling is also statistical. Identify the particular wait using correlated events and ownership evidence.
  4. Nothing definitive. The event may be unsupported, disabled, absent from this window, lost, or inaccessible; it does not prove the GPU did no work or caused no delay.
  5. Example: predict a named I/O wait interval decreases and compare it under repeated matched warm runs; if it does not decrease while wall time changes, weaken the hypothesis. Thermal drift, cache state, or background load remains a confounder unless measured/controlled.

Checkpoint#

Artifact: a hypothesis, target/build identity, capture settings and duration, privacy statement, per-run table, median plus spread, cold/warm-up rule, trace observation, one alternative explanation, and one falsifier. Ready: your conclusion is narrower than your evidence and distinguishes wall/CPU/wait. Revise: you give only a single best run, call all non-CPU time I/O, or infer hardware behavior from Cuttlefish. Not yet: capture is unbounded, target selection is ambiguous, or device policy is bypassed. Remediation: redo the arithmetic with the supplied CSV and revisit kernel debugging and ADB safe lab; no live capture is required.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.