EN

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

Word count: 5.9kReading time: 36 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.

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 when the root cause precedes the timeout; use START when recovery needs further observation
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 adb root, /data/misc/perfetto-configs, android.log, more system services and ftrace events Log allowlists, buffer sizes, and upload scope still need limits
user build + adb shell Ordinary system traces, package-scoped atrace, reviewed presets Do not rely on android.log by default; non-root access on Android 10/11 may not be able to read configurations from /data/misc/perfetto-configs
Ordinary app Record app-side ATrace / Track Event and activate predeclared triggers It cannot decide which system data to collect, how much to capture, or where to upload it
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.

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.
  • Recording begins after persistent properties load, which means /data is already 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.
  • The output file is not ready until tracing stops, or at least until the first flush/file-write interval. persist.debug.perfetto.boottrace is one-shot and must be set again before the next boot.

boottrace.pbtxt must specify a sensible duration_ms or flush_period_ms. Keep the initial configuration focused: init, sched, frequency, idle, process lifecycle, process stats, and Activity/Window/SurfaceFlinger-related ATrace. Enable logs only with an allowlist on userdebug or platform diagnostic builds.

The boot trace’s buffer policy must match the readiness milestone. DISCARD is better at preserving early init/zygote events, but loses later readiness events once the buffer fills. RING_BUFFER preserves the recent window but may overwrite early boot evidence. Do not expect a 32 MB DISCARD buffer to handle a 90-second boot window. Increase the buffer, narrow the data sources, or enable periodic file writes and check traced_buf_chunks_discarded.

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
duration_ms: 90000
flush_period_ms: 10000
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
}
}
}

A reusable capture workflow can look like this. Do not pull the file merely because it is nonempty: an automation script should wait for the configured duration to end, then verify that the file size remains stable across consecutive checks:

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
adb root
adb push boottrace.pbtxt /data/misc/perfetto-configs/boottrace.pbtxt
adb shell setprop persist.debug.perfetto.boottrace 1
adb reboot
adb wait-for-device
adb root
adb shell '
path=/data/misc/perfetto-traces/boottrace.perfetto-trace
sleep 100
stable=0
prev=0
attempts=0
while [ "$stable" -lt 3 ] && [ "$attempts" -lt 30 ]; 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 2
done
[ "$stable" -ge 3 ] || { echo "Trace missing or not stable" >&2; 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:

  • 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. On Android, this generally requires Android 14 (U)+ or a newer Perfetto integrated by the vendor

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

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.

ftrace_config.buffer_size_kb, buffer_size_lower_bound, and drain_period_ms increase kernel per-CPU ftrace buffer usage and the frequency of traced_probes drain wakeups. They should not be defaults for every field preset. They are included in this UI jank preset to reduce the chance of high-frequency scheduling events being overwritten in kernel ftrace within a short window. Before rollout, use a short trace to validate ftrace_setup_errors, CPU overhead, and write rate.

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 window in seconds = buffer MB / write rate in MB/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.

At an ftrace write rate of 2 MB/s, 64 MB retains only about 32 seconds. Enabling logs, Track Events, and camera/audio events in the field makes that window shorter. Retaining two minutes before the problem requires fewer data sources, a larger buffer, or periodic file writes. A STOP trigger returns no trace data unless it is matched; manually stopping cannot bypass that condition. For a manual fallback, activate a preauthorized manual trigger or start a separate named snapshot/fallback session that is not subject to that trigger restriction. Whether a snapshot can read pre-trigger data must also be verified for the specific version.

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;

Then confirm that both the app and SurfaceFlinger have frame records within the target 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
WITH target_window AS (
SELECT trace_start() AS ts, trace_dur() AS dur
),
frame_process AS (
SELECT
p.name AS process_name,
COUNT(*) AS frame_count,
MIN(a.ts) / 1e9 AS first_frame_s,
MAX(a.ts) / 1e9 AS last_frame_s
FROM actual_frame_timeline_slice a
JOIN process p USING (upid)
JOIN target_window tw
WHERE a.ts BETWEEN tw.ts AND tw.ts + tw.dur
AND (
p.name = 'com.example.app'
OR p.name = 'surfaceflinger'
OR p.name = '/system/bin/surfaceflinger'
OR p.cmdline LIKE '%surfaceflinger%'
)
GROUP BY p.name
)
SELECT
process_name,
frame_count,
first_frame_s,
last_frame_s
FROM frame_process
ORDER BY frame_count DESC;

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:

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. This fragment shows only the fields relevant to long recordings; add data sources before using it:

1
2
3
4
5
6
7
8
9
10
11
duration_ms: 3600000
prefer_suspend_clock_for_duration: true
write_into_file: true
file_write_period_ms: 10000
max_file_size_bytes: 2147483648
flush_period_ms: 30000

buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}

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.

The official buffers documentation gives a typical Android scheduler tracing rate of about 1–2 MB/s; use the measured rate for your actual preset. Estimate recording time as max_file_size_bytes / measured write rate. At 1–2 MB/s, a 2 GB limit lasts only about 17–34 minutes, not an hour. To record for an hour, narrow the data sources, raise the file-size limit, or switch to a summary/snapshot strategy.

Logs, syscalls, page faults, camera/audio events, and overly broad Track Event collection can increase the write rate by an order of magnitude. flush_period_ms is useful for long traces because it reduces ordering and import problems caused by low-frequency data sources holding data for too long before submitting it. Frequent flushes increase producer wakeups and I/O pressure. Field presets typically start with 10–30 seconds; record the Perfetto/Android versions, write_flush_mode, and measured write rate in the report.

Background Recording and Safe Shutdown

USB often disconnects during field testing. Prefer --background-wait for ordinary long-running field traces: it waits for data sources to start before returning the PID, reducing the chance of missing the beginning compared with plain --background.

On Android 12+, when root/userdebug access or a platform-provisioned path is available, place the configuration in /data/misc/perfetto-configs, then start the background trace:

1
2
3
4
5
6
7
adb push field.pbtxt /data/misc/perfetto-configs/field.pbtxt

PID=$(adb shell perfetto --background-wait --txt \
-c /data/misc/perfetto-configs/field.pbtxt \
-o /data/misc/perfetto-traces/field.perfetto-trace | tr -d '\r')

echo "perfetto pid: $PID"

Do not assume that this path is readable on non-root Android 10/11 user builds; use stdin instead. 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
PID=$(adb shell perfetto --background-wait --txt \
-c - \
-o /data/misc/perfetto-traces/field.perfetto-trace < field.pbtxt | tr -d '\r')

echo "perfetto pid: $PID"

When stopping, do not run adb pull immediately after kill. Perfetto still needs to finish writing the tail of the trace. At minimum, wait for the perfetto process to exit, then confirm that the file exists and its size is stable before pulling 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
case "$PID" in
''|*[!0-9]*) echo "Invalid perfetto PID" >&2; exit 1 ;;
esac
[ "$PID" -gt 1 ] || exit 1
adb shell "kill -INT $PID" || exit 1
adb shell "attempts=0; while kill -0 $PID 2>/dev/null; do
attempts=\$((attempts + 1))
[ \$attempts -lt 60 ] || exit 1
sleep 1
done" || exit 1
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

If the script controls both ends, it can start inotifyd to watch for close_write before sending kill. When the device lacks inotifyd, consecutive stable-size checks after process exit provide a fallback. Stable file size alone does not prove that the file has closed. An unmatched trigger, startup failure, or empty file must cause a timeout and exit; the script must not wait forever or proceed with the pull.

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
{
"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.

In a test environment, you can activate the trigger directly:

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.

The toolchain must also handle the case where no trigger matches. Under the official TraceConfig semantics, if trigger_timeout_ms expires without any matching trigger, both STOP_TRACING and START_TRACING stop the session and return no data. The output file is empty by design, not because capture failed. The script should explicitly record trigger_matched=false and clean up the empty file. An empty trace is not evidence that no problem occurred. Conversely, once a trigger matches, the session ends after stop_delay_ms. To maintain long-term flight-recorder coverage, platform-side supervisory logic must restart the next session and generate a fresh case ID and output filename for each round, avoiding overwriting the previous incident.

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.

Name the session in the configuration:

1
2
3
4
5
6
unique_session_name: "thermal_snapshot"

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

After it starts, clone the current ring buffer on demand. The original session keeps running:

1
2
3
4
adb shell perfetto --clone-by-name thermal_snapshot \
-o /data/misc/perfetto-traces/thermal_snap_1.pftrace

adb pull /data/misc/perfetto-traces/thermal_snap_1.pftrace

Snapshots are not the only way to capture field problems. They suit experiments that repeatedly observe trends, rather than trigger-based problems that require a single capture to preserve the incident.

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.

Bugreport as a Field Fallback

Users, testers, or operations staff often initiate a bugreport first when a field problem occurs. A long-running trace intended for inclusion in a bugreport must explicitly declare that in TraceConfig:

1
2
3
4
5
6
7
8
unique_session_name: "field_ui_jank_bugreport"
bugreport_score: 100
bugreport_filename: "field-ui-jank.pftrace"

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

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 V / 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
adb shell perfetto --save-for-bugreport
adb shell perfetto --save-all-for-bugreport

On Android S/T, saving for a bugreport takes the trace contents and stops the session early. On Android U+, 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.

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
{
"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": 123456730000000,
"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
{
"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": 123456730000000,
"trace_end_ns": 123457790000000,
"pre_trigger_window_ms": 59,
"post_trigger_window_ms": 1001,
"trigger_matched": true,
"file_complete": true,
"stats_status": "warning",
"affected_sources": ["linux.ftrace", "android.surfaceflinger.frametimeline"],
"usable_metrics": ["frame_timeline", "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"
}

Keep summary.json to machine-readable states, rather than narrative conclusions. evidence_grade can use A/B/C/D: A means the target window, key data sources, stats, and metadata are all complete; B means there is minor data loss but the core judgment remains usable; C permits only directional judgments; D means the trace can only document the environment.

Long-running traces also need explicit clock-domain information. ftrace commonly uses CLOCK_BOOTTIME, Android logs carry wall time, and other data sources may be synchronized through Perfetto’s ClockSnapshot.

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 to cap file size.
  • 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
3
SELECT
trace_start() / 1e9 AS trace_start_s,
trace_dur() / 1e9 AS trace_dur_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

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

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