CN

Android Perfetto Series 15: Boot Traces, Long-Running Field Traces, and Captu...

Word count: 7.2kReading time: 44 min
2026/05/04
loading

Many performance problems cannot be reproduced with a single tap. Slow boot, intermittent jank, problems after turning the screen on or off, audio interruptions after several hours, periodic stutters in an automotive system, and performance degradation as temperature rises are all poor fits for a manually recorded 10-second trace.

This article covers long-running field traces: defining the observation window in advance, using triggers to preserve the incident, ensuring the file finishes writing safely, and determining whether the collected data is trustworthy. Boot tracing is covered below as a special observation window. Part 02 already covered basic interactive capture methods—the command line, official scripts, Developer options, and the web interface. Here, we focus on what changes in the field and during long-running recording.

Version note (reviewed October 2026): Configurations and commands are checked against Perfetto v58.3. Check Android, device-side perfetto CLI, traced service, and host analysis-tool versions separately. Android 14 clone support does not imply the v49+ --clone-by-name flag. write_flush_mode requires v54+; remove unsupported fields on older devices and verify their manual-flush behavior.

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

The purpose of a long-running field trace is to retain enough data before and after a problem to explain its root cause. Enabling every option and recording for an hour only brings file-size, overhead, and data-loss problems sooner. A good field capture strategy produces reviewable evidence at a controlled cost.

Three field capture modes

Diagram: a STOP trigger preserves the trigger window, periodic file writes persist data in batches, and cloning leaves the original session running.

Design the Evidence Window First

Long-running traces require tradeoffs: how much to retain before and after the problem, and which data sources are worth keeping. Before writing the configuration, establish the observation window:

Question Configuration implications
How often does the problem recur? duration_ms, whether background execution is needed, whether periodic snapshots are needed
Does the root cause precede or follow the observed problem? Choose a STOP or START trigger
How many seconds before the problem must be retained? Ring buffer size, breadth of data sources, file-write interval
Can the user stop recording manually? Whether automatic triggers or bugreport integration are needed
Is ADB unavailable during the early phase? Boot tracing, platform provisioning, init/service activation, or a vendor solution
Might USB disconnect after recording starts? --background-wait, detached mode, snapshots, or a background service
What is the maximum file size? max_file_size_bytes, upload policy, local TTL
Is the data sensitive? Log tag allowlist, whether to disable atrace_apps: "*"

Intermittent UI jank usually requires tens of seconds before the problem; thermal throttling may require several minutes of trends; long-term automotive stability problems may require hours of execution. Such scenarios often use periodic snapshot retrieval or upload only a summary.

Map common scenarios to presets before writing the TraceConfig:

Scenario Recommended mode Trigger and acceptance checks
Intermittent UI jank Ring buffer + STOP trigger App markers, FrameTimeline coverage of the target window, sufficient pre-trigger history
Signs of an impending ANR Ring buffer + STOP trigger Main thread, Binder, lock waits, I/O, and critical business phases are all present
Thermal throttling write_into_file or snapshots Preserve minute-scale trends for CPU frequency, idle, thermal state, and critical app phases
Business-operation timeout App marker + STOP/START trigger Use STOP with sufficient stop_delay for before/after evidence; use START only when post-event data alone is sufficient
Audio underrun Ring buffer + STOP trigger Align audio/server events, sched, Binder, wakeups, and the underrun marker

Device Capabilities and Permissions Constrain Field Capture

A field capture strategy depends on more than TraceConfig: device version, build type, and the trigger emitter’s permissions also matter.

Environment What you can rely on What you should not assume
userdebug / rooted development device Use config directories, logs, and additional events as actual permissions allow Root alone does not guarantee adb root; check adbd/SELinux, log allowlists, and resource budgets
user build + adb shell Ordinary system traces, package-scoped atrace, reviewed presets Do not rely on android.log by default; do not assume arbitrary config paths are readable; prefer /data/misc/perfetto-configs
Ordinary app Record app-side ATrace / Track Event and activate predeclared triggers This trigger contract grants no system-consumer privileges; constrained APIs such as ProfilingManager have their own version/policy rules
CPU / native / Java profiling Check debuggable, profileable, and Android version as required by the data source Do not treat <profileable> as a universal permission switch for system tracing in the field

Text proto configurations may use either .pbtxt or .pbtx; parsing is determined by perfetto --txt or an encoding step in the toolchain. This article consistently uses .pbtxt filenames below.

Do not concatenate the sections blindly. The background-stop example needs a complete field.pbtxt with sources, duration, and write_into_file; STOP mode follows trigger lifecycle and SIGINT cannot recover an unmatched incident. Use unique output paths per run. Fixed names here denote a single laboratory run; archive earlier outputs first.

Boot Tracing Is a Special Field Observation Window

App cold starts and system boot are different problems. The older Systrace response-latency walkthrough covered app startup paths, and Part 05 covered rendering after startup. Here, we focus only on device-level boot: when ADB is not yet available but Android has reached the boot phase after /data is mounted, how do we capture a complete system trace, interpret it correctly, and produce reviewable conclusions?

Common scenarios requiring boot tracing include:

Symptom Why an ordinary trace is insufficient
Slow boot to the home screen The problem occurs before ADB is available
Slow first boot after an OTA init, zygote, dexopt, and system_server all need examination
Late Launcher first frame system_server, SurfaceFlinger, and Launcher need to be examined together
A service blocks during boot init/service manager/Binder context is needed
A user reports “it takes a long time to become usable after reboot” The readiness criterion must be defined first

Boot tracing is a specialized platform or system-startup observation window; app cold-start analysis takes a different route. Establish these boundaries before analysis:

  • Android boot tracing is available on Android 13+. Earlier versions require a vendor-specific boot capture solution.
  • The v58.3 init rule additionally requires persist.traced.enable=1, traced listening, and sys.boot_completed not yet set. Persistent properties load after /data is mounted. It does not cover PMIC power-on, the bootloader, early kernel boot, or first-stage init. Earlier phases require pstore, kernel cmdline/ftrace persistent buffers, or vendor init instrumentation.
  • This workflow generally assumes adb root is available on a development or userdebug device. Production user builds require platform provisioning, an engineering build, or a vendor diagnostic entry point.
  • The start of the UI’s relative timeline is not the moment the power button is pressed or the PMIC powers on. SQL ts retains timestamps in Trace Processor’s unified clock domain and does not automatically reset to zero when tracing starts. Compare relative phases within the same trace in reports; do not directly compare absolute ts values across devices.
  • There is no single definition of “boot complete.” Launcher first frame, accepting input, network availability, navigation service readiness, and background-task completion may be different teams’ metrics.
  • Without streaming, output normally becomes available after recording stops. With write_into_file, a partial file can appear earlier; file existence does not establish completion. persist.debug.perfetto.boottrace is one-shot and must be set again before the next boot.

boottrace.pbtxt is the fixed text-config filename for this boot entry point; do not replace it with a binary proto. duration_ms sets the endpoint. This example uses WRITE_FLUSH_AUTO: with five-second writes, v58.3 requests flushes every five seconds, so an additional flush_period_ms:10000 would not remain effective. Keep the first version modest: start with scheduling, process lifecycle, and limited ATrace, then add signals required by the readiness criterion.

DISCARD retains unread early data but drops new events if it fills before the consumer drains it; RING overwrites unread old events. With periodic writes enabled here, size buffers for peak traffic and delays between actual drains, not automatically for all 90 seconds. The example’s 128 MiB already consumes the default total central-buffer limit under extra guardrails; adding more buffers can exceed 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
duration_ms: 90000
prefer_suspend_clock_for_duration: true
write_flush_mode: WRITE_FLUSH_AUTO
write_into_file: true
file_write_period_ms: 5000
max_file_size_bytes: 536870912

buffers { size_kb: 131072 fill_policy: DISCARD }

data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
ftrace_events: "sched/sched_process_exec"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_fork"
ftrace_events: "sched/sched_process_free"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
atrace_categories: "am"
atrace_categories: "wm"
atrace_categories: "gfx"
}
}
}

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

The following workflow is for a development device with root access. Archive the previous boottrace first, because init deletes the old file when starting a new capture. Check the changed boot ID, consumed one-shot property, stopped dedicated init service, and nonempty output. These checks still do not replace content validation.

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
adb root || exit 1
adb wait-for-device
BOOT_ID_BEFORE=$(adb shell cat /proc/sys/kernel/random/boot_id) || exit 1
adb push boottrace.pbtxt /data/misc/perfetto-configs/boottrace.pbtxt || exit 1
adb shell setprop persist.debug.perfetto.boottrace 1 || exit 1
adb reboot || exit 1
adb wait-for-device
adb root || exit 1
adb wait-for-device
BOOT_ID_AFTER=$(adb shell cat /proc/sys/kernel/random/boot_id) || exit 1
[ "$BOOT_ID_BEFORE" != "$BOOT_ID_AFTER" ] || {
echo "Boot has not changed; do not pull a stale trace" >&2; exit 1;
}
adb shell '
attempts=0
while [ "$(getprop persist.debug.perfetto.boottrace)" = "1" ] ||
[ "$(getprop init.svc.perfetto_trace_on_boot)" != "stopped" ]; do
attempts=$((attempts + 1))
[ "$attempts" -lt 120 ] || { echo "Boot trace did not stop" >&2; exit 1; }
sleep 2
done
[ "$(getprop persist.debug.perfetto.boottrace)" != "1" ] || exit 1
[ -s /data/misc/perfetto-traces/boottrace.perfetto-trace ] || exit 1
' || exit 1
adb pull /data/misc/perfetto-traces/boottrace.perfetto-trace \
boottrace-$(date +%Y%m%d-%H%M%S).perfetto-trace

Validate the boot trace before analyzing it:

This preset does not enable FrameTimeline or a specific readiness marker. Seeing Launcher, sched, or a first-draw marker does not prove presentation or input readiness. Add the relevant sources, application markers, or external observations when those are acceptance criteria.

  • Does the trace cover the Android boot window after /data is mounted through the target readiness milestone?
  • Does stats report data loss, packet loss, or ftrace setup errors?
  • Are processes such as init, system_server, surfaceflinger, and launcher visible?
  • Do process stats, sched, and frequency all contain data? Check logs only when both the configuration and build type support them.
  • Was this a cold boot, an adb reboot, or the first boot after an OTA?
  • Does metadata.json record the button/reboot method, boot ID, readiness criterion, trace start/end, device build, and capture operator’s permissions?

Use a consistent analysis order: init/service startup, zygote, system_server, PackageManager/ActivityTaskManager, SurfaceFlinger, Launcher, and the first-frame/input-ready milestone. State the readiness criterion explicitly in the report; otherwise, “boot is three seconds slower” can easily become a cross-team dispute.

To read the final portion of ftrace before a crash-induced reboot, see linux.frozen_ftrace in Tracing across reboot. It relies on a persistent ring buffer provisioned in the kernel and is a different approach from the boottrace workflow here, which automatically records during the next Android boot.

Three Modes for Long-Running Field Traces

Long-running Perfetto field traces can use these three modes:

Mode Behavior Suitable scenarios Risks
Ring buffer + STOP trigger Continuously records the most recent window and stops after an anomaly Dropped frames, impending ANRs, audio underruns A small buffer overwrites the root cause; without a matching trigger, no incident trace is returned
Long recording with write_into_file Periodically writes the buffer to a file Trends spanning tens of minutes to hours I/O overhead and oversized files
--clone-by-name snapshot Clones the current window on demand while the original session continues Parameter tuning, thermal management, power-policy experiments The device’s perfetto must support --clone-by-name; check perfetto --help / --version. Android service-side cloning typically starts at Android 14, while cloning by name additionally needs a v49+ CLI or corresponding vendor integration

Do not combine all three into one all-purpose configuration. Choose the mode for the problem first, then choose the data sources.

I choose the mode by how far before the incident I need to look. If the cause is in the preceding few seconds, a measured ring window is more useful than a long trace whose volume nobody has checked. If the trend itself lasts minutes, periodic file writes deserve their overhead assessment.

A Baseline Ring Buffer Configuration

The following configuration targets intermittent UI jank. Recording starts immediately, and the ring buffer retains only the recent window. When the app, a platform service, or a test harness detects a slow frame and activates the app_ui_jank trigger, Perfetto records for one more second and stops.

This configuration assumes that app-side business markers and a trigger emitter, discussed in Part 13, are already integrated. trigger_config does not automatically trigger based on FrameTimeline. Without an emitter, the session simply waits for cleanup at trigger_timeout_ms; it does not provide a fallback capture of the final 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
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
unique_session_name: "ui_jank_flight_recorder"

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

buffers {
size_kb: 8192
fill_policy: RING_BUFFER
}

trigger_config {
trigger_mode: STOP_TRACING
trigger_timeout_ms: 3600000
triggers {
name: "app_ui_jank"
producer_name_regex: "com\\.example\\.app"
stop_delay_ms: 1000
max_per_24_h: 10
skip_probability: 0.0
}
}

incremental_state_config {
clear_period_ms: 10000
}

data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
buffer_size_kb: 4096
buffer_size_lower_bound: true
drain_period_ms: 250
compact_sched {
enabled: true
}
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "wm"
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_apps: "com.example.app"
}
}
}

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

data_sources {
config {
name: "track_event"
target_buffer: 1
track_event_config {
disabled_categories: "*"
enabled_categories: "ui"
enabled_categories: "memory"
enabled_categories: "app"
}
}
producer_name_filter: "com.example.app"
}

data_sources {
config {
name: "linux.process_stats"
target_buffer: 1
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 5000
}
}
}

There are two buffers: high-frequency ftrace/atrace goes into the large buffer, while FrameTimeline, process stats, and app Track Events go into the smaller semantic buffer. For graphics problems, retaining sched events is not enough if FrameTimeline, app markers, or process descriptions have been overwritten. You would still be unable to determine whether the jank came from the app, SurfaceFlinger, BufferQueue, or system scheduling.

producer_name_regex restricts which producers may activate the trigger. The regular expression must match the actual emitter’s producer name. If a test harness triggers on its behalf, add the harness producer to the allowlist or give the harness a separate trigger name.

buffer_size_kb=4096 is a per-CPU budget: on eight CPUs this layer alone may take roughly 32 MiB, in addition to shared memory and the 72 MiB central buffers. buffer_size_lower_bound constrains downward adjustment but does not guarantee the exact requested size. Shorter drain_period_ms generally means more wakeups; 250 ms is not universally better than the default. Check actual settings, setup errors, and overhead before tuning.

FrameTimeline requires Android 12 or later. It is not a trigger emitter: it provides facts in the trace, including app frames, SurfaceFlinger frames, jank_type, present_type, on_time_finish, and app-to-SF flows. Restrict atrace_apps to the target package; do not use atrace_apps: "*" as a field default.

Ring buffer retention is determined by the write rate and buffer size, not by trigger_timeout_ms. The estimate remains the one from Part 12: retained seconds per buffer ≈ capacity MiB / rate into that buffer in MiB/s.

For ftrace, examine three buffer layers: kernel per-CPU ftrace buffers, producer shared memory, and the Perfetto central buffer. Enlarging the central buffer only extends the final retained window. It cannot recover events lost earlier because traced_probes could not drain the kernel buffer in time.

Consider ftrace_config.buffer_size_kb, buffer_size_lower_bound, drain_period_ms, and drain_buffer_percent when tuning long-running field traces, after checking overhead and device compatibility in short traces. The current semantics of drain_buffer_percent were introduced in Perfetto v48, and the official proto comments state that it has no effect on Linux kernels older than 6.9. Do not treat it as a universal replacement for drain_period_ms on older kernels.

For a 64 MiB buffer receiving 2 MiB/s, the ideal steady-state window is about 32 seconds. Additional events routed to that buffer shorten it; calculate other buffers independently. Recording the one-second post-trigger tail also consumes ring capacity and overwrites earlier data, so budget both that tail and stop/flush delays. An unmatched STOP returns no data; manual fallback needs an allowed manual trigger or a separate, verified snapshot session.

After recording with the UI jank preset, first confirm that FrameTimeline data actually exists:

1
2
3
4
5
SELECT 'expected' AS table_name, COUNT(*) AS rows
FROM expected_frame_timeline_slice
UNION ALL
SELECT 'actual' AS table_name, COUNT(*) AS rows
FROM actual_frame_timeline_slice;

Find the app and SurfaceFlinger upids in process, then replace NULL below with those process instances and the incident window’s integer-nanosecond bounds. The query retains zero counts and unclosed frames that might overlap the window rather than combining same-named processes or fuzzy cmdline matches. Presence is not proof of the target layer, causality, or continuous coverage:

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 target_window(start_ns, end_ns) AS (
VALUES (NULL, NULL)
), targets(role, upid) AS (
VALUES ('app', NULL), ('surfaceflinger', NULL)
)
SELECT
t.role, p.upid, p.pid, p.name AS process_name,
COUNT(a.id) AS candidate_frame_rows,
COUNT(CASE WHEN a.dur >= 0 THEN 1 END) AS closed_frame_rows,
COUNT(CASE WHEN a.dur < 0 THEN 1 END) AS unclosed_frame_rows,
MIN(a.ts) AS first_start_ns,
MAX(CASE WHEN a.dur >= 0 THEN a.ts + a.dur END) AS last_known_end_ns
FROM targets t
JOIN process p ON p.upid = t.upid
CROSS JOIN target_window w
LEFT JOIN actual_frame_timeline_slice a
ON a.upid = p.upid
AND a.ts < w.end_ns
AND (a.dur < 0
OR (a.dur = 0 AND a.ts >= w.start_ns)
OR (a.dur > 0 AND a.ts + a.dur > w.start_ns))
WHERE typeof(w.start_ns) = 'integer' AND typeof(w.end_ns) = 'integer'
AND w.end_ns > w.start_ns
GROUP BY t.role, p.upid, p.pid, p.name;

If you suspect hardirq, softirq, or kworker activity is taking CPU time, do not put these events into the default long-running trace. Start with a focused short-window preset, then decide from the results whether to include them in field capture:

This is a data-source fragment to merge into a complete config with an explicit duration_ms; it is not a standalone capture command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "irq/irq_handler_entry"
ftrace_events: "irq/irq_handler_exit"
ftrace_events: "irq/softirq_entry"
ftrace_events: "irq/softirq_exit"
ftrace_events: "irq/softirq_raise"
ftrace_events: "workqueue/workqueue_queue_work"
ftrace_events: "workqueue/workqueue_execute_start"
ftrace_events: "workqueue/workqueue_execute_end"
}
}
}

Long Recordings Need Periodic File Writes

Problems spanning hours cannot rely only on memory buffers. Save this as field.pbtxt for the background/stop examples below. It records low-rate CPU-frequency, thermal-node, and system-memory trends, without sched or frame events. Remeasure file growth and perturbation after adding high-rate sources:

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
unique_session_name: "field_trends"
duration_ms: 3600000
prefer_suspend_clock_for_duration: true
write_into_file: true
file_write_period_ms: 10000
max_file_size_bytes: 2147483648
write_flush_mode: WRITE_FLUSH_AUTO

buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}

data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
cpufreq_period_ms: 1000
thermal_period_ms: 1000
meminfo_period_ms: 1000
meminfo_counters: MEMINFO_MEM_AVAILABLE
meminfo_counters: MEMINFO_CACHED
}
}
}

By default, duration_ms excludes system suspend time. For field problems measured by wall-clock time, such as screen on/off, standby, or automotive sleep, consider prefer_suspend_clock_for_duration: true. It also changes the time semantics of a trigger’s stop_delay_ms.

Use consistent binary units: 2147483648 bytes is 2 GiB. At measured file growth of 1–2 MiB/s, the cap is reached in about 17–34 minutes, before the one-hour duration. Published scheduler rates are only reference points; budget using the actual preset’s peak/average traffic. Shorter write intervals do not reduce total file volume.

Logs, syscalls, page faults, and broad application events can sharply increase volume. With write_into_file + AUTO in v58.3, write periods over five seconds flush before each write. This ten-second preset therefore would not also follow flush_period_ms:30000. A supporting version can use WRITE_FLUSH_DISABLED plus flush_period_ms for an explicit manual period; historical 10–30-second guidance does not describe current AUTO behavior.

Background Recording and Safe Shutdown

USB often disconnects during field tests. In v58.3, --background-wait waits up to 30 seconds for startup acknowledgement and exits zero on success. A printed PID alone is not that acknowledgement. This does not guarantee every expected event or late-registering producer is ready. Begin the test action after success and validate sources/window afterward. A background child may remain after timeout; inspect session state before retrying.

On Android 12 or later, place the configuration in /data/misc/perfetto-configs, then start the background trace; check write access on the target device:

1
2
3
4
5
6
7
8
9
10
11
adb push field.pbtxt /data/misc/perfetto-configs/field.pbtxt || exit 1

PID=$(adb shell perfetto --background-wait --txt \
-c /data/misc/perfetto-configs/field.pbtxt \
-o /data/misc/perfetto-traces/field.perfetto-trace) || {
printf 'Startup unconfirmed; inspect session before retrying. Output: %s\n' "$PID" >&2
exit 1
}
PID=$(printf '%s' "$PID" | tr -d '\r')

echo "perfetto pid: $PID"

If the Android 12–17 target device cannot use the configuration path above, pass the config through stdin. Production M2M toolchains should use reviewed presets or binary TraceConfig rather than allowing a server to send arbitrary text configurations:

1
2
3
4
5
6
7
8
9
PID=$(adb shell perfetto --background-wait --txt \
-c - \
-o /data/misc/perfetto-traces/field.perfetto-trace < field.pbtxt) || {
printf 'Startup unconfirmed; inspect session before retrying. Output: %s\n' "$PID" >&2
exit 1
}
PID=$(printf '%s' "$PID" | tr -d '\r')

echo "perfetto pid: $PID"

Choose one startup method and retain this run’s PID, boot ID, and process-start ticks in the same host shell. Across terminals, persist and restore those fields through case metadata; never kill using only a stale PID. Do not run adb pull immediately after signaling; Perfetto still needs to write the tail. Verify identity before SIGINT, then wait for that process to exit and the file to stabilize before pulling. This example is for non-detached recording.

1
2
3
4
5
6
7
8
9
10
11
12
13
case "$PID" in
''|*[!0-9]*) echo "Invalid perfetto PID" >&2; exit 1 ;;
esac
[ "$PID" -gt 1 ] || exit 1
read_start_ticks() {
proc_stat=$(adb shell "cat /proc/$1/stat") || return 1
printf '%s\n' "$proc_stat" | sed 's/.*) //' | awk '{print $20}'
}
RECORDING_BOOT_ID=$(adb shell cat /proc/sys/kernel/random/boot_id) || exit 1
RECORDING_START_TICKS=$(read_start_ticks "$PID") || exit 1
case "$RECORDING_START_TICKS" in
''|*[!0-9]*) echo "Cannot identify recording process" >&2; exit 1 ;;
esac

Run the preceding identity capture immediately after successful startup. After exercising the scenario, use the following stop/pull steps. If the process ended automatically, distinguish normal completion from failure using logs and stats.

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
CURRENT_BOOT_ID=$(adb shell cat /proc/sys/kernel/random/boot_id) || exit 1
[ "$CURRENT_BOOT_ID" = "$RECORDING_BOOT_ID" ] || exit 1
state=$(adb shell "if [ -d /proc/$PID ]; then echo PRESENT; else echo EXITED; fi") || exit 1
state=$(printf '%s' "$state" | tr -d '\r')
case "$state" in
PRESENT)
CURRENT_START_TICKS=$(read_start_ticks "$PID") || exit 1
[ "$CURRENT_START_TICKS" = "$RECORDING_START_TICKS" ] || exit 1
PROCESS_COMM=$(adb shell "cat /proc/$PID/comm") || exit 1
PROCESS_COMM=$(printf '%s' "$PROCESS_COMM" | tr -d '\r')
[ "$PROCESS_COMM" = "perfetto" ] || exit 1
adb shell "kill -INT $PID" || exit 1
;;
EXITED) ;;
*) exit 1 ;;
esac
attempts=0
while true; do
state=$(adb shell "if [ -d /proc/$PID ]; then echo PRESENT; else echo EXITED; fi") || exit 1
state=$(printf '%s' "$state" | tr -d '\r')
[ "$state" = "EXITED" ] && break
[ "$state" = "PRESENT" ] || exit 1
CURRENT_START_TICKS=$(read_start_ticks "$PID") || exit 1
[ "$CURRENT_START_TICKS" = "$RECORDING_START_TICKS" ] || break
attempts=$((attempts + 1))
[ "$attempts" -lt 60 ] || { echo "Recording process still present" >&2; exit 1; }
sleep 1
done
adb shell '
path=/data/misc/perfetto-traces/field.perfetto-trace
stable=0
prev=0
attempts=0
while [ "$stable" -lt 3 ] && [ "$attempts" -lt 60 ]; do
attempts=$((attempts + 1))
size=$(stat -c %s "$path" 2>/dev/null || echo 0)
if [ "$size" -gt 0 ] && [ "$size" = "$prev" ]; then
stable=$((stable + 1))
else
stable=0
fi
prev=$size
sleep 1
done
[ "$stable" -ge 3 ] || { echo "Trace missing or not stable" >&2; exit 1; }
' || exit 1
adb pull /data/misc/perfetto-traces/field.perfetto-trace

When inotifyd is available, install the watch before signaling to avoid missing close_write. Stable size is not proof of close/fsync, and identity-check plus kill is not atomic. Long-running automation should let the session owner manage its lifecycle. Identity changes, timeouts, or inaccessible process state require investigation, not an assumption of completeness or killall.

Using Triggers

The value of Perfetto triggers is permission separation. A privileged party declares trigger names and behavior in advance. An ordinary app or system component only reports that an event occurred; it cannot decide what to capture, how much to capture, or where to upload it.

A field capture strategy must identify the trigger emitter explicitly. For UI jank, app-side JankStats, FrameMetrics, or a custom threshold detector can activate app_ui_jank; a platform service or test harness can also activate it on the app’s behalf. Either path requires deduplication and rate limiting, with the trigger source, activation time, and decision threshold recorded in metadata.

A useful app-emitter contract is a fixed JSON event accompanied by a Track Event with the same event_id:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"clock_domain": "CLOCK_BOOTTIME",
"event_id": "evt-20260504-153012-001",
"trigger_name": "app_ui_jank",
"trigger_source": "app_jankstats",
"page": "Feed",
"action": "scroll",
"reason": "slow_frame",
"threshold_ms": 50,
"observed_value_ms": 83.4,
"window_start_elapsed_ns": 123456700000000,
"window_end_elapsed_ns": 123456789000000,
"dedupe_key": "Feed:scroll:slow_frame:202605041530",
"sample_decision": "record",
"app_state": "foreground"
}

The server or analysis script uses event_id and the time window to align external trigger records, Track Events inside the trace, and slow FrameTimeline frames. If they do not align, the trace provides only system-side context; it cannot directly attribute the problem to a page or business action.

sample_decision=record is the app’s choice, not service acceptance. event_id identifies a business event, not a FrameTimeline frame token; establish the target process, layer, and mapping rule. CLOCK_BOOTTIME here assumes conversion has already been performed, not that raw JankStats timestamps can be placed into elapsed fields unchanged.

In a test environment, these shell methods can activate a trigger, but this example also sets producer_name_regex. A shell emitter may not match that filter. Use an emitter with a matching producer name, or adjust the filter in a laboratory-only preset; a successful shell command alone does not prove that the session matched the trigger:

1
adb shell /system/bin/trigger_perfetto app_ui_jank

Alternatively, activate the same trigger using a configuration containing only activate_triggers:

1
2
echo 'activate_triggers: "app_ui_jank"' | \
adb shell perfetto --txt -c -

STOP triggers suit problems whose root cause precedes activation, such as slow frames, impending ANRs, and audio underruns. START triggers suit cases that need observation after the event, such as recording for another 10 seconds after temperature enters the hot state.

When using a START trigger, do not also configure an ordinary duration_ms; trigger_config manages the trigger window.

Set trigger_matched=false only after confirming successful session startup and expiry at trigger_timeout_ms without an accepted activation. START/STOP then return no trace data; a file may be empty or absent. An empty file can also reflect permission, configuration, process, or I/O failures. Keep the cause unknown until established. Accepted triggers end the session after stop_delay_ms; restart with a new case ID/file and account for the coverage gap between sessions.

Snapshots for Parameter Tuning

Some scenarios call for a snapshot of the current window every few seconds without stopping the original trace—for example, tuning CPU policies, thermal parameters, or power policies. The --clone-by-name command-line entry point was added in Perfetto v49.0. Session cloning itself also requires Android 14 (U)+ platform support or a vendor integration. Ultimately, check adb shell perfetto --help / --version.

Save the following complete ten-minute example as thermal.pbtxt. It polls CPU frequency and thermal nodes once per second, subject to field/device support and access. It enables neither a STOP trigger nor periodic file writes, so snapshots read the current ring window:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unique_session_name: "thermal_snapshot"
duration_ms: 600000
prefer_suspend_clock_for_duration: true

buffers { size_kb: 65536 fill_policy: RING_BUFFER }
data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
cpufreq_period_ms: 1000
thermal_period_ms: 1000
}
}
}

First confirm no session already uses the name, then start it and clone on demand after success. The original continues until its ten-minute duration expires. Give each clone a new filename. These host commands require --clone-by-name support:

1
2
3
4
adb shell perfetto --query
SNAPSHOT_PID=$(adb shell perfetto --background-wait --txt \
-c - -o /data/misc/perfetto-traces/thermal-final.pftrace < thermal.pbtxt) || exit 1
printf 'snapshot session pid: %s\n' "$SNAPSHOT_PID"

After startup succeeds, exercise the scenario, then capture the desired window with the following commands.

1
2
3
4
SNAPSHOT_FILE="thermal-$(date +%Y%m%d-%H%M%S).pftrace"
adb shell perfetto --clone-by-name thermal_snapshot \
-o "/data/misc/perfetto-traces/$SNAPSHOT_FILE" || exit 1
adb pull "/data/misc/perfetto-traces/$SNAPSHOT_FILE" .

Cloning requests a flush and consumes extra buffer/read resources; it is not free. Frequent snapshots can overlap and do not automatically form a continuous history, nor recover overwritten events. Check overlap and gaps before treating them as a trend.

Use Detached Mode Sparingly

--detach separates the tracing session from the lifetime of the perfetto command, leaving the traced service holding the session. The official documentation explicitly discourages routine use because it can leak sessions and leave devices tracing for long periods.

If detached mode is necessary, enable write_into_file in the configuration and prepare commands to inspect and stop the session:

1
2
3
4
5
6
adb shell perfetto --detach=field_session --txt \
-c /data/misc/perfetto-configs/field.pbtxt \
-o /data/misc/perfetto-traces/field.perfetto-trace

adb shell perfetto --is_detached=field_session
adb shell perfetto --attach=field_session --stop

Automation scripts need fallback cleanup: check for old sessions before starting, stop sessions after testing, and stop them automatically when their TTL expires.

A detach key is not unique_session_name. Reattachment requires the same Unix UID; changing root/shell identity can matter. --is_detached returns 0 for an attachable session, 2 when none is found, and 1 for an error; communication failure is not absence. field.pbtxt must contain actual sources, write_into_file, and duration_ms.

Bugreport as a Field Fallback

A user, tester, or operator often encounters the field problem first and triggers a bugreport. Bugreport capture selects eligible sessions that still exist at that time. These are fields to merge into a complete config, not a standalone recording. For thermal.pbtxt above, replace its unique_session_name, keep sources/duration, and start a new session with the revised config:

1
2
3
unique_session_name: "field_thermal_bugreport"
bugreport_score: 100
bugreport_filename: "field-thermal.pftrace"

Only sessions with bugreport_score > 0 become bugreport candidates; higher scores receive higher priority for saving. bugreport_filename supplies a readable filename for --save-all-for-bugreport starting with Android 15 / Perfetto v42; older versions use a default name. This is distinct from ordinary -o /path/trace.perfetto-trace and --upload: the former serves bugreports, while the latter two serve the current command’s output or framework reporting.

Development or platform scripts can verify bugreport behavior with these commands:

1
2
3
4
adb shell perfetto --save-for-bugreport

# Alternative, only when supported:
adb shell perfetto --save-all-for-bugreport

On Android 12/13, saving for a bugreport takes the trace contents and stops the session early. On Android 14 or later, the system creates a read-only snapshot and the original session can continue. Record the Android version and verification-command output in the report. Do not treat bugreport packaging as equivalent to ordinary background tracing.

Choose a save entry point supported by the device rather than running both as a required sequence: --save-for-bugreport selects the highest score, while supported --save-all-for-bugreport saves all eligible sessions. Device-side saving does not itself generate or upload a bugreport ZIP. A positive bugreport_score also makes the session eligible for cloning by other UIDs; scope its data accordingly.

What a Field Evidence Package Should Contain

A .perfetto-trace file alone is not enough. At minimum, a field evidence package must explain the scenario, when it was triggered, and whether the file is complete.

1
2
3
4
5
6
7
case-20260504-153012/
trace.perfetto-trace
config.pbtxt
metadata.json
stats.sql.txt
log_excerpt.txt
summary.json

Recommended fields for metadata.json include:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"clock_domain": "CLOCK_BOOTTIME",
"case_id": "case-20260504-153012",
"trace_config": "ui_jank_flight_recorder_v3",
"trigger": "app_ui_jank",
"trigger_source": "app_jankstats",
"trigger_elapsed_realtime_ns": 123456789000000,
"trigger_wall_time": "2026-05-04T15:30:12+08:00",
"trace_start_elapsed_realtime_ns": 123436789000000,
"trace_end_elapsed_realtime_ns": 123457790000000,
"boot_id": "device_boot_id",
"device": "device_name",
"build_fingerprint": "fingerprint",
"app_version": "1.2.3",
"battery_percent": 78,
"thermal_state": "nominal",
"upload_reason": "slow_frame"
}

This metadata does not replace the trace. It helps investigators locate the problem window, filter versions, and assess environmental differences. Part 13 covered adding app-side page, business-phase, and user-action information. Metadata in the field evidence package must align those business markers.

summary.json is the entry point for automation, with fields that directly drive triage, upload, and retention:

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
{
"clock_domain": "CLOCK_BOOTTIME",
"case_id": "case-20260504-153012",
"preset_id": "ui_jank_flight_recorder_v3",
"trigger_name": "app_ui_jank",
"trigger_source": "app_jankstats",
"trigger_ts_elapsed_ns": 123456789000000,
"trace_start_ns": 123436789000000,
"trace_end_ns": 123457790000000,
"pre_trigger_window_ms": 20000,
"post_trigger_window_ms": 1001,
"trigger_matched": true,
"file_complete": true,
"stats_status": "warning",
"affected_sources": ["linux.ftrace"],
"usable_metrics": ["frame_timeline"],
"unusable_metrics": ["main_thread_sched"],
"evidence_grade": "B",
"privacy_mode": "log_tag_whitelist",
"upload_mode": "summary_first",
"conclusion_level": "probable",
"next_action": "collect_short_irq_preset",
"sampling_policy_version": "2026-05-04",
"sample_rate": 1.0,
"skip_probability": 0.0,
"sample_decision": "record",
"quota_bucket": "ui_jank_daily",
"quota_remaining": 8,
"upload_allowed": true,
"upload_block_reason": "",
"retention_until": "2026-05-07T15:30:12+08:00"
}

These JSON objects illustrate a hypothetically validated incident with a twenty-second pre-window and roughly one-second tail; they are not defaults or results inferred from file size. trigger_matched, file_complete, usable_metrics, and upload_allowed need separate evidence; retain null/unknown until verified. Enclosing earliest/latest events do not prove continuity. A/B/C/D are reporting conventions, and a usable core judgment for B needs independent support, not merely a small loss count.

Record clock domains explicitly. Android ftrace commonly uses CLOCK_BOOTTIME, while original log wall time may already be normalized by the parser; do not apply an offset twice. JankStats/FrameMetrics timestamps cannot become elapsed realtime merely by renaming fields. External JSON requires same-boot clock pairs and in-trace markers; wall time can also change due to clock correction.

External metadata should record at least elapsed realtime, wall time, and boot ID together. A more robust approach also records the trigger itself as a Track Event inside the trace. If analysis reveals clock synchronization failures or invalid clock snapshots, reduce confidence in alignment across data sources.

Trim at Capture Time First

Perfetto traces contain incremental state, track descriptors, stats, and metadata. Cutting a binary file after capture can easily break parsing. The recommended order is:

  • Use a ring buffer to retain only the recent window.
  • Use stop_delay_ms to control how much to retain after the trigger.
  • Use max_file_size_bytes with write_into_file to stop file growth; reaching the cap ends recording, potentially before the incident. It is not offline trimming.
  • Upload the original trace covering the complete incident window.
  • Generate summaries, screenshots, and SQL results on the server as lightweight indexes.

If offline trimming is unavoidable, the tool must rewrite TracePackets and retain the necessary descriptors and stats. Do not use dd, text truncation, or arbitrary cuts through a protobuf byte stream.

Upload and Cleanup Policies

Long-running field traces are collected close to real user environments, so upload policies must consider more than engineering convenience:

  • Upload by default only under conditions such as Wi-Fi, charging, or user authorization.
  • When a file exceeds the threshold, upload the summary first and retain the original trace locally for manual export.
  • Collect logs using a tag allowlist and redact them before upload.
  • Give trace files a TTL, such as automatic cleanup after three days.
  • Apply both TraceConfig’s max_per_24_h and client-side rate limiting to triggers.
  • Run stats before upload and record data loss in metadata.
  • Do not allow servers to send arbitrary text proto configurations to user devices; allow selection only from reviewed presets.

These constraints reduce ad hoc tuning flexibility, but control upload, privacy, and stability risks.

Standard Checks Before Analysis

Long-running field traces are more susceptible to data loss. Before analysis, check two acceptance criteria: whether the time covered by the trace meets expectations, and whether stats supports trusting the key data sources.

1
2
SELECT trace_start() AS trace_start_ns, trace_end() AS trace_end_ns,
trace_dur() / 1e9 AS trace_span_s;

If the duration is unexpected, first check trigger_timeout_ms, max_file_size_bytes, whether the background process was killed, whether the file is still open, and whether duration_ms accounts for suspend as intended. When data is incomplete, explicitly state which conclusions must be withheld.

Then run the global quality check. Part 12 already provides the complete stats health-check query and a mapping from common stats items to corrective actions. Reuse that SQL at the beginning of your analysis script; it is not repeated here. Long-running field traces need two additional layers of checking.

First, check each buffer. For traced_buf_*, idx is the buffer index. The global flush/config items listed alongside them below must not be interpreted as buffer-specific indices:

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
name GLOB 'traced_buf_*'
OR name IN (
'traced_flushes_failed',
'traced_final_flush_failed',
'config_write_into_file_no_flush',
'config_write_into_file_discard'
)
)
ORDER BY idx, name;

With RING_BUFFER, traced_buf_chunks_overwritten does not necessarily indicate an error: it usually means old data outside the retained window was overwritten. Check separately whether it overwrote the root cause before the trigger:

1
2
3
4
5
SELECT name, idx, severity, source, value
FROM stats
WHERE name = 'traced_buf_chunks_overwritten'
AND value != 0
ORDER BY idx;

If ftrace is enabled in the field, also record the ftrace clock, available events, and setup errors. When comparing traces, differences in clocks or event sets directly affect the credibility of sched/frequency conclusions:

1
2
adb shell cat /sys/kernel/tracing/trace_clock
adb shell cat /sys/kernel/tracing/available_events | head

Turn Intermittent Problems into Evidence

Long-running field traces require observation windows, triggers, completeness criteria, and upload boundaries to be defined in advance. A ring buffer retains the recent window; triggers preserve the incident; write_into_file controls long recordings; --background-wait and file-close checks help ensure the data reaches disk; and stats plus metadata establish how much of that evidence remains trustworthy.

Part 13 covered app-side Track Events, while this article covers field observation windows. Together, they provide the foundation for bringing slow frames, impending ANRs, and business-operation timeouts into a single incident package.

References

  1. Trace configuration
  2. Buffers and dataflow
  3. Tracing in Background
  4. Running perfetto in detached mode
  5. Perfetto command-line reference
  6. TraceConfig proto reference
  7. Android boot tracing
  8. FrameTimeline
  9. ATrace
  10. Android Log
  11. Clock synchronization
  12. Trace Processor Stats

Original source baseline: b23c67be (September 19, 2026), retained below. This review also checks v58.3 init rules, the CLI implementation, TraceConfig, service write/flush logic, and the v49 changelog.

About Me and the Blog

Follow Android Performance.

CATALOG
  1. 1. Perfetto Series Catalog
  2. 2. Design the Evidence Window First
  3. 3. Device Capabilities and Permissions Constrain Field Capture
  4. 4. Boot Tracing Is a Special Field Observation Window
  5. 5. Three Modes for Long-Running Field Traces
  6. 6. A Baseline Ring Buffer Configuration
  7. 7. Long Recordings Need Periodic File Writes
  8. 8. Background Recording and Safe Shutdown
  9. 9. Using Triggers
  10. 10. Snapshots for Parameter Tuning
  11. 11. Use Detached Mode Sparingly
  12. 12. Bugreport as a Field Fallback
  13. 13. What a Field Evidence Package Should Contain
  14. 14. Trim at Capture Time First
  15. 15. Upload and Cleanup Policies
  16. 16. Standard Checks Before Analysis
  17. 17. Turn Intermittent Problems into Evidence
  18. 18. References
  19. 19. About Me and the Blog