Cookbook: Periodic Trace Snapshots

In this guide, you'll learn how to:

This workflow is useful when you need to repeatedly observe system metrics (CPU frequency, power rails, temperatures, etc.) while iterating on device or system configuration, without restarting tracing each time.

Use case

Imagine you are tuning device or system parameters (e.g. writing to /proc or /sys nodes) and want to see the effect on power, thermals and CPU behavior within seconds. The traditional workflow of "start trace, stop trace, pull, analyze" adds unnecessary friction.

With periodic trace snapshots you start a single ring-buffer trace once, then clone it as many times as you like. Each clone is an independent snapshot of the ring buffer at that point in time; the original trace keeps running undisturbed.

Prerequisites

  • An Android device running Android 14 (U) or later (the --clone-by-name flag requires the Perfetto v49+ client and service).
  • A host machine with adb on PATH and the device connected via USB.
  • trace_processor_shell on the host (for analysis). Download prebuilts with:
curl -LO https://get.perfetto.dev/trace_processor chmod +x ./trace_processor

  • A Linux machine with Perfetto v49+ installed, or the tracebox binary downloaded. tracebox bundles traced, traced_probes and the perfetto client into a single statically linked executable:
curl -LO https://get.perfetto.dev/tracebox chmod +x tracebox
  • trace_processor_shell (for analysis). Download prebuilts with:
curl -LO https://get.perfetto.dev/trace_processor chmod +x ./trace_processor
  • Access to tracefs for ftrace-based data sources. You do not need to run as root; instead, chown the tracefs directory to your user:
sudo chown -R $USER /sys/kernel/tracing

Step 1: Start a ring-buffer trace

Create a trace config file snapshot_config.pbtxt on the host:

# Identify this session so we can clone it by name later. unique_session_name: "my_snapshot" # Use a ring buffer so the trace never stops. buffers { size_kb: 65536 fill_policy: RING_BUFFER } # CPU frequency (event-driven + polling fallback). data_sources { config { name: "linux.ftrace" ftrace_config { ftrace_events: "power/cpu_frequency" ftrace_events: "power/cpu_idle" ftrace_events: "power/suspend_resume" ftrace_events: "thermal/thermal_temperature" ftrace_events: "thermal/cdev_update" } } } # Periodic CPU frequency polling (useful on platforms where the ftrace # event is not emitted). data_sources { config { name: "linux.sys_stats" sys_stats_config { cpufreq_period_ms: 500 } } } # Battery counters and power rails (Pixel devices). data_sources { config { name: "android.power" android_power_config { battery_poll_ms: 1000 battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT battery_counters: BATTERY_COUNTER_CHARGE battery_counters: BATTERY_COUNTER_CURRENT collect_power_rails: true } } }

Push the config and start tracing:

adb push snapshot_config.pbtxt /data/misc/perfetto-configs/ adb shell perfetto -c /data/misc/perfetto-configs/snapshot_config.pbtxt --txt \ --background -o /data/misc/perfetto-traces/snapshot_bg

The --background flag returns immediately; the trace keeps running in the ring buffer on the device.

Create a trace config file snapshot_config.pbtxt:

# Identify this session so we can clone it by name later. unique_session_name: "my_snapshot" # Use a ring buffer so the trace never stops. buffers { size_kb: 65536 fill_policy: RING_BUFFER } # CPU frequency (event-driven + polling fallback). data_sources { config { name: "linux.ftrace" ftrace_config { ftrace_events: "power/cpu_frequency" ftrace_events: "power/cpu_idle" ftrace_events: "power/suspend_resume" ftrace_events: "thermal/thermal_temperature" ftrace_events: "thermal/cdev_update" } } } # Periodic CPU frequency polling (useful on platforms where the ftrace # event is not emitted, e.g. Intel CPUs). data_sources { config { name: "linux.sys_stats" sys_stats_config { cpufreq_period_ms: 500 } } } # Power monitoring (Chrome OS / Linux). data_sources { config { name: "linux.sysfs_power" } }

Start the tracing services and begin tracing. If you are using tracebox:

# tracebox starts traced and traced_probes automatically. ./tracebox -c snapshot_config.pbtxt --txt \ --background -o /tmp/snapshot_bg

If you have traced, traced_probes and perfetto installed separately:

# Ensure traced and traced_probes are running, then: perfetto -c snapshot_config.pbtxt --txt \ --background -o /tmp/snapshot_bg

The --background flag returns immediately; the trace keeps running in the ring buffer.

Step 2: Take a snapshot

Whenever you want to capture the current state of the ring buffer, clone the session by name:

adb shell perfetto --clone-by-name my_snapshot \ -o /data/misc/perfetto-traces/snapshot_1.pftrace

This creates a read-only copy of the ring buffer contents at that instant. The original tracing session continues to run. You can repeat this as many times as you like, giving each snapshot a different output file name:

# After making a system/device parameter change... adb shell perfetto --clone-by-name my_snapshot \ -o /data/misc/perfetto-traces/snapshot_2.pftrace

perfetto --clone-by-name my_snapshot \ -o /tmp/snapshot_1.pftrace # Or with tracebox: ./tracebox --clone-by-name my_snapshot \ -o /tmp/snapshot_1.pftrace

This creates a read-only copy of the ring buffer contents at that instant. The original tracing session continues to run. You can repeat this as many times as you like, giving each snapshot a different output file name:

# After making a system parameter change... perfetto --clone-by-name my_snapshot \ -o /tmp/snapshot_2.pftrace

Step 3: Pull and analyze a snapshot

Pull the snapshot to your host:

adb pull /data/misc/perfetto-traces/snapshot_1.pftrace /tmp/

The snapshot is already on the local filesystem at /tmp/snapshot_1.pftrace.

You can analyze the snapshot using the trace_processor_shell command line, the Python API, or by opening it in the Perfetto UI.

Querying with trace_processor_shell

Run a one-off query directly from the command line using the query subcommand:

trace_processor_shell query /tmp/snapshot_1.pftrace " INCLUDE PERFETTO MODULE linux.cpu.frequency; SELECT * FROM cpu_frequency_counters LIMIT 100; "

Or open an interactive SQL shell to explore the data:

trace_processor_shell /tmp/snapshot_1.pftrace

Here are some useful queries:

CPU frequency

INCLUDE PERFETTO MODULE linux.cpu.frequency; SELECT * FROM cpu_frequency_counters LIMIT 100;

Power rails (Android, Pixel devices)

INCLUDE PERFETTO MODULE android.power_rails; SELECT * FROM android_power_rails_counters LIMIT 100;

Battery counters (Android)

SELECT ts, t.name, value FROM counter AS c LEFT JOIN counter_track AS t ON c.track_id = t.id WHERE t.name GLOB 'batt.*';

Thermal zones

SELECT ts, t.name, value FROM counter AS c LEFT JOIN counter_track AS t ON c.track_id = t.id WHERE t.name GLOB '*thermal*';

Querying with the Python API

The perfetto Python package lets you load traces and query them programmatically, which is convenient for building custom dashboards or post-processing data with Pandas / Polars. Install it with:

pip install perfetto

Example:

from perfetto.trace_processor import TraceProcessor tp = TraceProcessor(trace='/tmp/snapshot_1.pftrace') # Query CPU frequency as a Pandas DataFrame. qr = tp.query(""" INCLUDE PERFETTO MODULE linux.cpu.frequency; SELECT cpu, ts, freq FROM cpu_frequency_counters """) df = qr.as_pandas_dataframe() print(df.to_string()) # Plot frequency over time for each CPU. import matplotlib.pyplot as plt for cpu, group in df.groupby('cpu'): plt.plot(group['ts'], group['freq'], label=f'cpu {cpu}') plt.legend() plt.xlabel('Timestamp (ns)') plt.ylabel('Frequency (kHz)') plt.show()

See the Trace Processor Python docs for more details.

If you want to analyze multiple snapshots together, Batch Trace Processor lets you run a single query across a set of traces in one go.

Automating snapshots

A simple shell loop can take a snapshot every N seconds and run a query against it:

for i in $(seq 1 10); do SNAP="/data/misc/perfetto-traces/snap_${i}.pftrace" adb shell perfetto --clone-by-name my_snapshot -o "$SNAP" adb pull "$SNAP" /tmp/ echo "=== Snapshot $i ===" trace_processor_shell query /tmp/"snap_${i}.pftrace" " INCLUDE PERFETTO MODULE linux.cpu.frequency; SELECT cpu, avg(freq) AS avg_freq_khz FROM cpu_frequency_counters GROUP BY cpu; " sleep 5 done

for i in $(seq 1 10); do SNAP="/tmp/snap_${i}.pftrace" perfetto --clone-by-name my_snapshot -o "$SNAP" echo "=== Snapshot $i ===" trace_processor_shell query "$SNAP" " INCLUDE PERFETTO MODULE linux.cpu.frequency; SELECT cpu, avg(freq) AS avg_freq_khz FROM cpu_frequency_counters GROUP BY cpu; " sleep 5 done

Stopping the trace

adb shell killall perfetto

killall perfetto # Or if using tracebox: killall tracebox

Limitations and caveats