# Cookbook: Recording Android Traces Locally

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.
