blob: a598330a12829add35222dff09336c80979d6bbc [file] [view] [edit]
# Cookbook: Local Android Trace Recording
This page collects **end-to-end recipes** for recording Perfetto traces on
Android in situations that the standard interactive workflow does not cover.
- [Tracing Android boot](#boot-tracing): record a trace covering the boot
sequence, which you cannot start by hand while the device is booting.
- [Capturing a heap dump on OutOfMemoryError](#oom-heap-dump): automatically
dump the Java heap when an app crashes with an `OutOfMemoryError`.
The recipes assume a host with `adb` access to the device. Each recipe is
self-contained: copy the config and commands as they are, then adjust the
highlighted parameters. If you have never recorded a trace before, start with
the [system tracing tutorial](/docs/getting-started/system-tracing.md). For the
full reference on each topic, follow the links into the deeper guides:
- [Trace configuration](/docs/concepts/config.md)
- [ART heap dumps](/docs/data-sources/java-heap-profiler.md)
- [Analysing Android traces](/docs/getting-started/android-trace-analysis.md)
## Recipe: Tracing Android boot {#boot-tracing}
Goal: record a trace covering the Android boot sequence, to profile process
startup, scheduling and everything else that happens while the device boots.
You cannot start a trace by hand while the device is still booting. Instead,
since Android 13 (T), perfetto can be armed to start recording automatically on
the next boot.
**1. Write a config.** The boot trace config must be in **text** format (not
binary). Save the following as `boottrace.pbtxt`. It records process scheduling
and lifecycle events, but any
[trace configuration](/docs/concepts/config.md) works here (more examples in
[/test/configs/](/test/configs/)):
```protobuf
# One buffer allocated within the central tracing binary for the entire trace,
# shared by the two data sources below.
buffers {
size_kb: 32768
fill_policy: DISCARD
}
# Ftrace data from the kernel, mainly the process scheduling events.
data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
ftrace_events: "sched_switch"
ftrace_events: "sched_waking"
ftrace_events: "sched_wakeup_new"
ftrace_events: "task_newtask"
ftrace_events: "task_rename"
ftrace_events: "sched_process_exec"
ftrace_events: "sched_process_exit"
ftrace_events: "sched_process_fork"
ftrace_events: "sched_process_free"
ftrace_events: "sched_process_hang"
ftrace_events: "sched_process_wait"
}
}
}
# Resolve process commandlines and parent/child relationships, to better
# interpret the ftrace events, which are in terms of pids.
data_sources {
config {
name: "linux.process_stats"
target_buffer: 0
}
}
# 10s trace, but can be stopped prematurely via `adb shell pkill perfetto`.
duration_ms: 10000
```
**2. Push the config to the device.** The path is fixed; perfetto only looks
for `/data/misc/perfetto-configs/boottrace.pbtxt`:
```bash
adb push boottrace.pbtxt /data/misc/perfetto-configs/boottrace.pbtxt
```
**3. Arm tracing for the next boot:**
```bash
adb shell setprop persist.debug.perfetto.boottrace 1
```
The property is reset during boot, so each boot trace is one-shot: to trace
another boot, set the property again.
**4. Reboot the device:**
```bash
adb reboot
```
**5. Pull the trace.** The trace is written to
`/data/misc/perfetto-traces/boottrace.perfetto-trace`. The file appears only
after the recording has stopped, once `duration_ms` has elapsed, so keep it to
a reasonable value. (If your config sets `write_into_file: true`, the file is
instead written incrementally, every `file_write_period_ms`.)
```bash
adb pull /data/misc/perfetto-traces/boottrace.perfetto-trace
```
The file is removed before a new boot trace starts, so pull it before arming
the next one.
**6. View it.** Open `boottrace.perfetto-trace` in the
[Perfetto UI](https://ui.perfetto.dev). To dig into the data with SQL, see the
[Android trace analysis cookbook](/docs/getting-started/android-trace-analysis.md).
### How early in boot does the trace start?
The trace is started by the `perfetto_trace_on_boot` oneshot init service,
defined in [perfetto.rc](/perfetto.rc). Init starts it once three conditions
hold: persistent properties have been loaded (which happens only after `/data`
has been mounted), the `traced` daemon is up, and boot has not completed yet.
The last condition is why setting the property on a booted device arms the
*next* boot instead of starting a trace immediately. The earliest boot stages
(kernel init, mounting filesystems) are therefore not covered by the trace.
## Recipe: Capturing a heap dump on OutOfMemoryError {#oom-heap-dump}
Goal: automatically capture an ART (Java/Kotlin) heap dump at the moment a
process crashes with a `java.lang.OutOfMemoryError`, so you can see exactly
what was keeping memory alive when allocations started failing.
Since Android 14 (U), ART notifies perfetto when a Java process is about to
crash with an `OutOfMemoryError`, and perfetto can use that notification as a
trigger to dump the Java heap of the crashing process.
### Option A: using the helper script
If you have a perfetto checkout, `tools/java_heap_dump` drives this end to end.
Pass `--wait-for-oom` together with the process to watch (`-n '*'` matches all
processes):
```bash
tools/java_heap_dump --wait-for-oom --oom-wait-seconds 3600 \
-n 'com.example.myapp' -o oome.pftrace
```
The script starts a tracing session, waits up to `--oom-wait-seconds` for an
`OutOfMemoryError` to be thrown, then pulls the heap dump to `oome.pftrace`.
### Option B: using only adb
If you don't have a checkout, the following command does the same with nothing
but `adb` access. It is safe to copy-paste as-is:
```bash
cat << EOF | adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/oome.pftrace
buffers: {
size_kb: 524288
fill_policy: DISCARD
}
data_sources: {
config {
name: "android.java_hprof.oom"
java_hprof_config {
process_cmdline: "*"
}
}
}
data_source_stop_timeout_ms: 100000
trigger_config {
trigger_mode: START_TRACING
trigger_timeout_ms: 3600000
triggers {
name: "com.android.telemetry.art-outofmemory"
stop_delay_ms: 500
}
}
data_sources {
config {
name: "android.packages_list"
}
}
EOF
```
This starts a tracing session that waits for up to one hour
(`trigger_timeout_ms`) for any ART runtime instance to hit an
`OutOfMemoryError`. To watch only your own app, replace the `"*"` in
`process_cmdline` with its process name (e.g. `"com.example.myapp"`).
Once an error is hit, the heap is dumped and tracing stops:
```text
[862.335] perfetto_cmd.cc:1047 Connected to the Perfetto traced service, TTL: 3601s
[871.335] perfetto_cmd.cc:1210 Wrote 19487866 bytes into /data/misc/perfetto-traces/oome.pftrace
```
Then pull the heap dump:
```bash
adb pull /data/misc/perfetto-traces/oome.pftrace
```
### Analysing the heap dump
Open `oome.pftrace` in the [Perfetto UI](https://ui.perfetto.dev) and click the
diamond marker in the _"Heap Profile"_ track to get a flamegraph of what
retained the memory. For a guided investigation, see:
- [Heap Dump Explorer](/docs/visualization/heap-dump-explorer.md), interactive
dominator-tree and class-level analysis of heap dumps.
- [Debugging memory usage](/docs/case-studies/memory.md), an end-to-end guide
to investigating Android memory issues.
- [ART heap dumps](/docs/data-sources/java-heap-profiler.md), the full
reference for the underlying data source.