Updates skills (2026-08-17 17:17)

This commit is contained in:
android-devrel-github-bot
2026-08-17 17:17:28 +00:00
parent 1e5e7ae613
commit 86932856ef
40 changed files with 1738 additions and 13762 deletions
+1 -2
View File
@@ -28,8 +28,7 @@
"./play/engage-sdk-integration", "./play/engage-sdk-integration",
"./play/play-billing-library-version-upgrade", "./play/play-billing-library-version-upgrade",
"./play/play-policy-insights", "./play/play-policy-insights",
"./profilers/perfetto-sql", "./profilers/android-profiler",
"./profilers/perfetto-trace-analysis",
"./security/android-intent-security", "./security/android-intent-security",
"./system/edge-to-edge", "./system/edge-to-edge",
"./testing/testing-setup", "./testing/testing-setup",
+1 -6
View File
@@ -87,12 +87,7 @@
}, },
{ {
"source": { "source": {
"path": "./profilers/perfetto-sql" "path": "./profilers/android-profiler"
}
},
{
"source": {
"path": "./profilers/perfetto-trace-analysis"
} }
}, },
{ {
+57
View File
@@ -0,0 +1,57 @@
---
name: android-profiler
description: >
Manages Android performance profiling and debugging. Triggers when the user
asks to record or analyze Android performance data, such as system traces,
heap dumps, method recordings, callstack samples, memory allocations, or
investigate bottlenecks, jank, memory leaks, and app startup issues on
Android, or when the user asks to write, debug, or execute ad-hoc SQL
queries. Applies to both user and system apps or services.
license: Complete terms in LICENSE.txt
metadata:
author: Google LLC
last-updated: '2026-08-06'
keywords:
- Android performance
- debugging
- profiling
- recording
- trace analysis
- memory leaks
- bottleneck
- jank
- SQL
---
# Android Profiler Orchestrator
Your primary role is **Intent Disambiguation and Routing**; route the user to
the correct workflow or prepare an execution plan for the user. Work with the
user to finalize the plan and then proceed with the plan execution, addressing
singular as well as composite needs.
## Prerequisites and Setup
Before executing any workflows, read
[`references/env_setup.md`](references/env_setup.md) (it sits next to this file
in the skill root). It defines what to set `$SKILL_ROOT` to - the anchor every
other path in this skill is written against.
## Intent Disambiguation
Do not guess the user's intent. If the user request is not clear, **ask the
user** what they want to do before proceeding.
## Recording
Route all recording requests through
`$SKILL_ROOT/recording/recording_orchestrator.md`. This defines guidelines and
pre-flight checks or dependency checks that apply to all recording workflows,
and ensures you have the necessary setup to proceed. Read the orchestrator and
execute the plan it describes based on what the user wants to record (for
example, a system trace or a heap dump).
## Analysis
Route all analysis requests through
`$SKILL_ROOT/analysis/analysis_orchestrator.md`.
@@ -0,0 +1,32 @@
# Analysis Orchestrator
Use the guidelines below to find the right analysis workflow to execute based
on the user intent (for example, analyzing a system trace or a heap dump).
Ensure you can answer the following: "User intends to
[analyze/query/investigate] ______". For example,
"User intends to analyze a heap dump to investigate a memory leak".
## Handling Composite Requests
If the user's request involves multiple distinct analysis goals (for example,
analyzing a trace for jank AND checking for memory leaks or GPU issues),
do not execute them simultaneously.
1. Break down the request and propose a sequential execution plan to the user.
2. Ask the user for confirmation to start the first step.
3. Do not proceed to the next workflow until the current one is completed.
## Workflow Discovery and Routing
This skill supports multiple specialized analysis workflows. To determine the
right workflows to use:
1. Use your file search tools (for example, `grep_search`) to recursively scan
the `$SKILL_ROOT/analysis/workflows/` directory for workflow entrypoints
(markdown files defining a top-level `name:` key in their frontmatter,
ignoring internal `references/` subdirectories).
2. Compare the user's request and intent against the `name:`, `description:`,
and `keywords:` fields to identify matching workflows. If multiple are
found, present them as options to the user and proceed with the
user's selection.
@@ -0,0 +1,28 @@
---
name: perfetto-sql
description: >
Translates natural language data intents into syntactically valid PerfettoSQL
queries and/or executes them against a trace file, if provided. Use this
workflow to draft, debug, or execute queries extracting slice, thread, or
memory data from Android Perfetto traces using trace_processor.
keywords:
- Perfetto SQL
- SQL Guidelines
- SQL Best Practices
- Ad-hoc Query
- Trace Processor
- SPAN_JOIN
- Idempotency
---
# Ad-Hoc PerfettoSQL Querying
Use this workflow to write, debug, or execute PerfettoSQL queries:
- **Executing against a trace:** If a trace file path or URL is provided, verify
environment prerequisites in `$SKILL_ROOT/references/perfetto/setup.md` and
follow the session execution workflow in
`$SKILL_ROOT/references/perfetto/sql.md`.
- **Static query authoring or debugging:** If no trace is attached, draft,
optimize, or debug the queries using the syntax, schema guidelines, and
standard library best practices in `$SKILL_ROOT/references/perfetto/sql.md`.
@@ -0,0 +1,121 @@
---
name: perfetto-trace-analysis
description: >
Analyzes Perfetto traces to find the root cause of performance issues in
user or system Android apps (for example, janks, app startup, memory, or
latency stalls).
keywords:
- perfetto
- trace
- jank
- startup
- latency
- stall
- thread
- bottleneck
---
# Perfetto Trace Analysis
Use this workflow to diagnose general Android performance issues: jank, app
startup, latency, stall, or a thread-blocking problem.
Follow these steps in order:
## Step 1: Identify what to investigate first
Work with the user (for example, ask questions, present options) to
understand the following:
- _What is the symptom_: Do they want to investigate frame drops, jank,
startup issues, app crash, system crash, ANR?
- _Who is the victim:_ Where did they observe the symptom? For example, "a
frame drop in `com.example.sample`".
> **Input Trace Required:** Confirm that the user has provided a trace file
> path or URL (for example, `.pftrace`, `.perfetto-trace`, or a Perfetto UI
> link). If none is provided, pause and ask the user to provide one before
> proceeding.
> If you do not know the symptom, pause and ask the user for clarification.
> If you know the symptom but not the victim, proceed to Step 2 (Triage) to
> discover candidates. Present the 3-5 most severe instances to the user and
> proceed with the user selection.
> **A/B Trace Comparison:** If the user provides multiple traces (for
> example, a baseline and an issue trace), explicitly clarify which is the
> baseline. You will pass both paths to the subagents in Step 3 so they can
> use the baseline trace to establish expected behavior.
## Step 2: Run a system-wide triage
Spin off a subagent/task and instruct it to follow instructions from
`$SKILL_ROOT/analysis/workflows/perfetto-trace-analysis/references/triage.md`
to run a system-wide triage. (Do not read this file yourself. Pass this path
in the sub-agent prompt so it loads the file and performs the triage steps).
Note the output produced by the subagent to identify the filtered list of
**candidates** that we need to investigate.
> **Note:**
> - If no candidates are found, ask the user for symptom clarification and
> specific timestamps.
> - If the triage reveals multiple candidates, select the top 2 or 3 most
> severe, representative candidates and ask the user before proceeding
> which ones to investigate. The user may choose one or more.
**Expected outcome:** Confirmation on which candidate(s) to investigate
before proceeding.
## Step 3: Investigate each branch in parallel
For **every** candidate (one or more) identified in the previous step, spin
off a task/subagent to investigate these tracks _in parallel_.
Construct a prompt for each candidate containing:
```markdown
Trace path: [path]
Baseline Trace Path: [path, if provided]
Candidate Info: [`utid`, `upid`, thread_name, process_name, `render_thread_utid` (if applicable)]
Symptom Window: [start_ts, end_ts, duration]
System Vitals: [payload from Step 2]
Budget: [expected duration, if applicable]
Execution Protocol: Read
`$SKILL_ROOT/analysis/workflows/perfetto-trace-analysis/references/per_candidate_analysis.md`
and follow its instructions end-to-end.
```
SPAWN the subagent(s) in parallel and await completion of all subagents before
proceeding to Step 4.
## Step 4: Final report and consolidation
Read the output from every spawned subagent and generate a consolidated "Trace
Analysis Report" adhering to these instructions:
1. **Summary and Root Cause:** State the findings in simple, clear language
and classify root causes (hardware, software/code, scheduling policy,
system exhaustion, or external dependency).
2. **Dependency Chain:** Map the full path from symptom to root cause.
Include thread names, `UTID`s, blocking states, and exact timestamps at
every hop.
3. **Evidence Tags:** Tag every single claim with `[SQL]` (backed by data),
`[INFERRED]` (logical deduction), or `[GAP]` (partial or missing data),
and briefly define these tags at the start.
4. **Platform Context:** Explain Android system behaviors _only_ by citing
specific retrieved slice names, values, and timestamps.
**What's bad:** "SurfaceFlinger does X around vsync"
**What's a good explanation:** "SurfaceFlinger's composition slice at
`ts=142.3ms` ran `4ms` after the vsync signal at `ts=138.1ms`, consistent
with X".
5. **Partial Suspects:** List all branches that were investigated but did not
reach a terminal root cause, ranked by evidence strength. Include what
was found and where verification fell short. A partial suspect can be the
real contributor - report it even if a terminal root cause was found
elsewhere.
> If evidence is evenly split between multiple potential causes, report each
> suspicion with its supporting data and evidence so that engineers can
> evaluate probabilities without false certainty. Do not arbitrarily pick
> a winner.
@@ -0,0 +1,51 @@
# Guiding Principles for Trace Analysis
Whether you are augmented with domain-specific knowledge or doing a standard
workflow analysis, follow these rules.
**Why:** Performance analysis is complex. It is entirely possible to end up
identifying a root cause while the reality is different (for example, a hardware
bottleneck causing a cascade of failures). The principles below keep your
analysis grounded in truth and ensure that you investigate the entire causal
chain to discover the true bottleneck.
1. **Schema Validation via Intrinsic Discovery:**
Read `$SKILL_ROOT/references/perfetto/sql.md` and query table schemas
using `LIMIT 0` before drafting queries.
- **Why:** Trace processor schemas evolve across versions; discovering
schema directly prevents invalid assumptions and syntax failures.
2. **Empirical Data Grounding:** Support every claim with tool output or
queried timestamps, slice durations or counter values (`[SQL]`).
- **Why:** General Android heuristics cannot substitute for ground-truth
trace metrics. When queries return empty results, broaden search
constraints using fuzzy matching or wider time windows.
3. **Causation vs. Correlation:** Verify that the blocker's active execution
overlaps with the victim's wait interval.
- **Why:** Concurrent anomalies are only causally linked if their
execution lifetimes intersect. For example, just because thread A was busy
while thread B was waiting does not necessarily mean thread A caused the
wait.
4. **Follow Evidence:** Follow dependency chains across thread, process and
kernel boundaries to the terminal root cause. Keep going as long as
**each hop meaningfully explains the symptom**.
- **Why:** Halting blocker traversal prematurely reports intermediate
symptoms rather than the true origin.
5. **Explicit Uncertainty Reporting:** Categorize unverified execution paths
as `[GAP]` or partial suspects.
- **Why:** Transparent reporting of missing data enables engineers to evaluate
probabilities without false certainty.
6. **Evidence-First Explanation:** Cite concrete retrieved metrics and slice
timestamps before asserting platform behavioral context.
- **Why:** Contextual explanations are only reliable when anchored in
empirical trace observations.
7. **Systemic Confound Sweeps:** Before attributing a bottleneck to
application software, verify that thermal throttling, CPU capping
(`cpufreq`), scheduling (`sched_slice`), or LMKD pressure isn't uniformly
degrading the system. Report such confounds as root cause modifiers.
> To uncover short-lived anomalies that get mathematically missed by simple
> averages or aggregate queries, isolate and look around the symptom window
> and query for individual event spikes or percentiles.
8. **Systematic Step Adherence:** At every step of your investigation, strictly
follow the defined investigation steps.
- **Why:** Structured verification prevents analytical blind spots and
premature conclusions on obvious but non-critical anomalies.
@@ -0,0 +1,7 @@
---
domain: The specific domain or product.
description: Description of the domain/subsystem for effective routing
keywords:
- List of symptoms and keywords where these hints are applicable,
for example, 'jank', 'surfaceflinger', 'vulkan'.
---
@@ -0,0 +1,56 @@
---
subsystem: cpu
description: >
Expert hints for CPU scheduling, thread states, CPU contention, and
frequency scaling.
keywords:
- cpu
- scheduling
- latency
- runnable
- running
- thread state
- governor
- core
- frequency
---
# CPU & Scheduling Expert Hints
- When debugging a long slice: Examine its thread states to understand what the
thread was doing (running, sleeping, blocked on I/O).
- When debugging a long slice for latency issues: Check if its duration is
caused by one or more long-running child slices. Apply recursively.
- When a thread is woken up but there is a delay before it runs, check the
"IRQ" track for the corresponding CPU to see if an interrupt is the cause.
- Check if kernel threads associated with hardware are running with real-time
priority; if not, they can be preempted.
- When analysis of the primary application package does not reveal root cause,
expand to all threads and processes. Search for other runnable threads on the
same CPU or high-priority kernel threads.
- When investigating app startup, use an SQL query to aggregate reasons for
uninterruptible sleep on the main thread.
- Check the "cpu_frequency" counter for CPU cores that ran the main process.
Missing frequency data or stuck frequencies indicate a kernel-level bug in
the governor.
- Query raw "ftrace" events for logs related to the governor thread (for
example, "su_gov").
- To find concurrency issues, search for critical threads (for example,
'RenderThread') in a blocked state (`thread_state.state = 'D'`) and join with
scheduling data to find the waker.
- Compare time spent in userspace functions vs kernel (slices with [k] prefix).
- To detect a 'catch-up storm', look for threads with long gaps in
thread_state/cpu_slice activity immediately followed by a high-density burst.
- Quantify scheduler contention by calculating scheduling latency (measure
duration of preceding 'Runnable' state using
`preceding_sched_slice_for_thread`). Search for maximums and high percentiles
(p95/p99).
- If a task exhibits high scheduling latency, check if other CPUs were idle
(running swapper or idle thread).
- Check the `cpu_id` for key threads; if consistently scheduled on slower
cores, it signals a potential performance gain by allowing them on big cores.
- For a struggling thread, analyze 'Runnable' vs 'Running' state time. A large
'Runnable' time indicates CPU contention.
- If a slice's wall duration increases but the percentage of 'Running' time is
unchanged, it strongly suggests a lower CPU frequency. Check `sched_switch`
to focus on the correct cores.
@@ -0,0 +1,51 @@
---
subsystem: graphics
description: >
Expert hints for graphics performance, UI jank, GPU memory, and frame
rendering.
keywords:
- graphics
- ui jank
- gpu
- frame
- vsync
- render
- surfaceflinger
- texture
- bitmap
---
# Graphics & UI Jank Expert Hints
- When investigating UI jank, check for long-running slices on the main
thread; if a slice like "ConstraintLayout.onMeasure" is taking a long time
(for example, >8ms), it is a likely cause of the jank.
- When a `bitmap_write_to_parcel` slice takes milliseconds instead of
microseconds, investigate its children slices to see how time is spent. Long
durations often point to suboptimal kernel operations like memory mapping
(`mmap`) or unnecessary data zeroing.
- For a high-level overview of graphics memory usage, track `gpu_mem_total`
counter for a specific process (`upid`).
- To find the largest graphics allocations, query the `android_graphics_allocs`
table and sort by `size_bytes` in descending order. Compare width and height
against the device's display resolution.
- To detect the "double memory cost" of an image existing on both CPU and GPU,
look for a large buffer in `android_graphics_allocs` and a simultaneous CPU
memory allocation of a similar size (`rss_anon_bytes` or `heap_graph`).
- To find potentially costly intermediate render targets, look for large
buffers in `android_graphics_allocs` where `usage_bits` lack a
`COMPOSER_OVERLAY` flag.
- To check if an allocation is actually presented on screen, correlate
`buffer_id` with SurfaceFlinger events.
- To see if high graphics memory is causing performance issues, check the frame
duration in `actual_frame_timeline_slice`. Durations over vsync (for
example, 16.6ms) correlating with `gpu_mem_total` spikes suggest memory
pressure jank.
- Within the main thread of a janky graphics application, look for frequent or
long-running `texture_upload` slices.
- Analyze the duration of `eglSwapBuffersWithDamageKHR` or similar buffer-swap
slices in the graphics rendering thread. Consistently long durations suggest
a large "damage area" being redrawn every frame.
- To identify UI jank, compare `actual_frame_timeline` against the
`expected_frame_timeline` on the main process; a significant deviation
indicates missed frames.
@@ -0,0 +1,46 @@
---
subsystem: io
description: >
Expert hints for I/O and storage bottlenecks, uninterruptible sleep, and
page cache.
keywords:
- i/o
- storage
- disk
- uninterruptible sleep
- d state
- page fault
- dm-verity
- file
- syscall
---
# I/O & Storage Expert Hints
- When debugging a long, uninterruptible sleep state related to I/O: Check for
overlapping slices with "verity" in their name (dm-verity).
- When a thread is stuck in an uninterruptible sleep with no `blocked_function`,
look for other threads that might be holding the memory lock (for example,
"jit-thread-pool", memory mapping ops).
- When analyzing a long uninterruptible sleep, check the `blocked_function` in
the thread state details (from `sched_blocked_reason` ftrace event).
- A lot of time spent in `do_page_fault` during app startup is a strong
indicator of I/O contention.
- For file integrity mechanisms like DM-Verity, search for events like
`dm_verity_fec_prefetch`.
- To find the specific kernel dependency of a stalled app thread, locate the
thread in state 'D', then look for kworker or kernel threads that become
runnable immediately after.
- For app stalls caused by I/O, analyze the scheduling latency of the relevant
kworker threads handling the request.
- To find inefficient file I/O, query the syscall table for a high frequency
of small, sequential `read()` or `pread()` syscalls on a single fd.
- If a thread spends significant time in 'Uninterruptible Sleep', check if
`blocked_function` is `page_cache_readahead`. Correlate waking timestamps
with `filemap_add_to_page_cache` ftrace events.
- Aggregate counts of `filemap_add_to_page_cache` grouping by `inode` to find
the specific file causing I/O pressure.
- Inspect `nr_sector` in `block_rq_issue` ftrace events to understand file
read-ahead size.
- If an I/O issue disappears on subsequent launches, it is a 'cold start'
problem (populating page cache).
@@ -0,0 +1,41 @@
---
subsystem: ipc
description: >
Expert hints for IPC, binder transactions, and cross-process dependencies.
keywords:
- ipc
- binder
- transaction
- cross-process
- bottleneck
- service
- system_server
---
# IPC & Binder Expert Hints
- Look for multiple outbound binder transactions from the same process
(`system_server`) that carry similar data to different destinations in a
short time frame. This "binder storm" indicates a lack of multiplexing.
- To trace data across processes, correlate slices using flow events by
linking a slice's ID to `flow.source_slice_id` or `flow.dest_slice_id`.
- To detect binder spam, query the `binder_transaction` table and group by
thread ID (`tid`), `service_name`, and `method_name` to find high numbers of
identical calls.
- When high binder concurrency is found, identify the bottleneck server
process by grouping transactions by `server_upid`.
- To analyze latency of a slow binder transaction, calculate time spent
outside the server by subtracting `server_dur` from total `dur` in the
`binder_transaction` table.
- When a thread is suspected of binder spam, correlate its `tid` with the
`cpu_slice` table to check for high CPU consumption.
- To find code responsible for binder spam, get the `utid` of the problematic
thread and use it to query `stack_profile_callsite`.
- To find callers of a problematic function, filter `stack_profile_callsite`
for frames mapping to it, then trace upwards using `parent_id`.
- A long-running slice on one thread causally linked to a slice on another
thread (for example, binder from `system_server` to SystemUI) indicates a
scheduling dependency bottleneck.
- To find asynchronous operations that might cause UI jank, look for a binder
transaction from a controlling process that returns quickly, followed by a
long-running slice in the receiving process.
@@ -0,0 +1,55 @@
---
subsystem: memory
description: >
Expert hints for memory pressure, Low Memory Killer (LMK), and allocations.
keywords:
- memory
- lmk
- lmkd
- oom
- rss
- swap
- kswapd
- thrashing
- pressure
- allocation
---
# Memory & LMK Expert Hints
- To investigate low memory kills, look for the `lmk_kill_occurred` ftrace
event; a high count indicates severe memory pressure.
- When you see a burst of `lmk_kill_occurred` events, query for processes with
high CPU `wall_duration` in the `sched_slice` table during the same time
window. Runaway processes consuming CPU exacerbate memory pressure.
- To understand a process's memory impact, inspect its `anon_rss` (Anonymous
Resident Set Size) from memory counters like `mem.info`.
- Check system-wide memory stats for a high or rapidly increasing `swap_used`
value.
- To confirm memory thrashing, look for high CPU usage by the `kswapd` kernel
thread.
- To find the direct trigger for LMKD, look for `memory_pressure` trace events
from Pressure Stall Information (PSI).
- To detect 'lost' memory for processes using hardware accelerators (like a
TPU), query the `counter` table for `RssFile` values. If it drops
significantly while hardware is active, check for `kswapd0` scheduling
slices.
- When analyzing memory shared with hardware, query `dma_heap_stat` and
`dmabuf_total_size` counters (or `ion_total_size` for older devices) for a
more accurate picture than RSS.
- Be aware that `RssFile` can over-report memory if the same physical page is
mapped multiple times.
- When investigating OOMs, establish a baseline by querying process memory
counters (for example, `mem.rss`) and compare the median and 95th percentile
against its history.
- If bitmaps are a major memory consumer, check for outliers in bitmap count by
querying `android.graphics.Bitmap` instances across traces.
- To find what is holding onto an object (like a bitmap), trace its retainer
path back to a GC root by querying `heap_graph_reference`. Pay close
attention to custom application classes.
- Query the `android_bitmaps` table to check the width and height properties of
bitmaps.
- Check for the presence of software bitmaps, which consume memory on both the
app heap and in graphics memory.
- When analyzing bitmaps, look for duplicates by checking for multiple
`android.graphics.Bitmap` objects with identical properties.
@@ -0,0 +1,33 @@
---
subsystem: power
description: >
Expert hints for battery drain, power rails, network traffic, and wake locks.
keywords:
- power
- battery
- wake lock
- suspend
- network
- modem
- bluetooth
- drain
---
# Power & Network Expert Hints
- When investigating overall battery drain, start by querying `power_rails`
track and summing energy consumed (`power_ma * duration`) for each rail.
- To check if a device is sleeping correctly during screen-off periods, query
the `suspend_state` track; a lack of time in "suspended" state indicates a
wakefulness problem.
- To find the root cause of the device failing to suspend, query the
`kernel_wakelock` track and aggregate total duration for each wake lock.
- If a top kernel wake lock name contains "bt_" or "bcm" (for example,
`bt_host_wake`), cross-reference its timing with events in the
`bluetooth_scan_results` track.
- If the modem rail in `power_rails` shows high consumption, query the
`network_packets` table and aggregate traffic volume by UID to identify app.
- When analyzing network traffic from a shared UID, use the `socket_tag`
associated with network packets for granular attribution.
- Convert impact into a common energy unit like milliwatt-hours (mWh) to
compare the severity of different issues.
@@ -0,0 +1,172 @@
# Candidate Investigation Protocol
This is the execution protocol for a deep-dive subagent during trace analysis.
## Context
Execute the protocol below using the candidate details (Trace Paths, Symptom
Window, UTID, UPID, System Vitals) provided in your initial prompt.
## Investigation Protocol
**High-Level Algorithm:**
1. Prerequisites (Step 1)
2. Calculate time distribution and identify state buckets (Step 2)
3. Domain and System hints discovery and selection (Step 3)
4. Run exhaustive investigation for each significant state bucket sequentially
(Steps 4 and 5), starting with the largest bucket. Output total time
explained so far after investigating each.
5. Output the result (Step 6)
### Step 1: Prerequisites
- Follow `$SKILL_ROOT/references/perfetto/sql.md` for session-based query execution.
- Read
`$SKILL_ROOT/analysis/workflows/perfetto-trace-analysis/references/guiding_principles.md`
to identify best practices, ensure data-driven analysis, and avoid pitfalls.
### Step 2: Calculate the time distribution
1. **Establish a baseline if possible:** For example, if investigating a jank
candidate, find a _non-janky_ instance of the **same** operation first to
establish expected behavior and rule out red herrings. _If no healthy
instance exists in the trace, note this as a `[GAP]` and proceed with the
next steps_.
2. **Calculate time spent in each state:** Sum the time spent in `Running`,
`R`, `S` and `D` states. Investigate all substantial buckets. Also look for
composite bottlenecks (for example, "40% CPU-starved + 35% IO-blocked").
3. **Run checks to rule out red herrings:** For example:
- A thread in `S` state could be normal behavior -> investigate only if
the sleep actually overlaps with a pending obligation (for example, a
pending binder reply).
- A thread in `Running/Runnable`: When a thread spends high duration in
`Running/Runnable`, inspect CPU frequency, throttling counters, and core
migrations before attributing latency to code inefficiency, because
hardware throttling inflates wall time without increasing instruction
overhead:
- Verify first that an identified code path is actually doing
disproportionate work.
- Query the trace to find what the thread was doing during this window.
Query the `slice` table for the longest duration slices during this
window.
- Beyond blocking slices, query and identify repetitive micro-operations
or gaps that collectively exhaust the budget to prevent tunnel vision.
- Perform a system-wide check to identify if there was CPU throttling or
core migrations around the symptom window (see Principle 7).
4. **Drill down:** For every significant bucket revealed from the time
distribution analysis during the symptom window, identify what the thread
was doing at the transition point. Investigate every significant bucket (for
example, >= 20% of the time window) to avoid missing real bottlenecks or
composite issues.
> **Action:** Tag missing data as `[GAP]`. Do not guess.
### Step 3: Domain and Hints Discovery
Even if you find a major bottleneck, continue searching for other bottlenecks
using expert-vetted domain and system hints and techniques. Follow these steps
in order:
- **Domain discovery:** Use your file search tools (for example, `grep_search`)
to scan frontmatter (`domain:`, `description:`, `keywords:`) in
`$SKILL_ROOT/analysis/workflows/perfetto-trace-analysis/references/hints/domains/`
(if it exists) and identify matching domain hint files based on collective
investigation state (victim, intermediate processes).
- **Subsystem hints:** Use your file search tools to scan frontmatter
(`subsystem:`, `description:`, `keywords:`) in
`$SKILL_ROOT/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/`
for relevant tracks (for example, keywords for sleep states, memory, IPC).
- **Hint Selection and Application:** Apply all the matched subsystem and
domain hint files during the Step 4 investigation, and list them under
`Applied Hints` in your Step 6 output.
### Step 4: Exhaustive Investigation (Do Not Give Up Early)
- **Follow the dependency chain:**
- If the victim thread was waiting or blocked by another thread, follow the
chain to the leaf. Find out what it is waiting _for_. Cross process
boundaries if necessary. Do not conclude without following the entire
blocker chain (See Principle 4 in `guiding_principles.md`).
- **Dynamic hint injection:** If your dependency chain leads to a new
subsystem or process you haven't researched yet, do a single search in
`$SKILL_ROOT/analysis/workflows/perfetto-trace-analysis/references/hints/`
for relevant hints before proceeding. Do not fall into infinite loops.
- Every state bucket investigation should end in one of three states:
**Terminal root cause**, **Blocked by another thread**, or
**Partial suspect**.
> A finding is a "terminal root cause" if:
>
> - You can trace it down to a physical bottleneck (thermal throttle, GPU,
> storage). **Require Specificity:** Do not conclude with generic labels.
> Specify the _what_ clearly.
> - A specific function or code path is identified as doing disproportionate
> work relative to its purpose (for example, synchronous disk IO on main
> thread, unnecessary object allocation triggering GC).
> - A scheduling policy or resource limit is identified as artificially
> constraining the thread (for example, background CPU cap, foreground
> service restriction).
> - The bottleneck is identified in a different process/service that the
> investigated process cannot control (for example, `system_server` lock
> contention, `SurfaceFlinger` throttling).
- **Systemic sweep before concluding:** Discovering an application-layer
bottleneck (software root cause) does not terminate the investigation of a
state bucket. Before concluding any state bucket as a terminal root cause,
check relevant system hints for that state (`CPU`, `IO`, `Memory`, `IPC`)
and verify whether platform-level confounds - such as CPU scaling, memory
pressure, thermal throttling, or I/O saturation - simultaneously degraded
performance **around the symptom window.** Report discovered systemic
confounds as **co-root causes** or duration modifiers (see Principle 7 in
`guiding_principles.md`).
**Why:** Software execution duration is not an absolute constant; it is
modulated by platform state (frequency scaling, thermals, memory reclamation).
Always check system-wide confounds before reporting an inefficient code path as
the sole root cause - to avoid concluding inflated software duration as the
sole root cause while missing the underlying kernel or hardware anomaly that
magnified it. Back findings with empirical proof (`[SQL]`) or mark as `[GAP]`
if inconclusive.
### Step 5: Contextualize the Workload
Once the mechanical bottleneck is identified in Step 4, query the trace to
identify the high-level user feature, UI operation, or exact workload that
triggered it. Explain the _why_, not just the _what_. This workload context
ensures that we can provide actionable next steps for the user instead of
leaving them confused.
### Step 6: Output
Output the following investigation result:
```markdown
# Investigation Result
Candidate:
Symptom Window: [Start TS] and [End TS]
Symptom Duration: [Duration]
Budget (Expected Duration): [If applicable]
Applied Hints: [List of matched subsystem and domain hint files used]
## Primary Finding
- **Classification:** [Hardware | Software/Code | System Exhaustion | Scheduling Policy | External Dependency]
- **Details and Reasoning:** `___`
- **Explained Duration:** `___`
- **Evidence SQL or backing data:** `___`
- **Dependency chain:** [Root to Leaf Dependency Chain]
- **Root cause conclusion:** `___`
## Other Findings
- **Status:** [Partial Suspect | Low Confidence Suspicion | Terminal Root Cause]
- _(same fields)_
## Verification Checklist
- All claims tagged (`[SQL]`/`[GAP]`/`[INFERRED]`): [yes/no]
- Total explained time accounts for most of the symptom window: [yes/no]
- Unexplored state buckets: [none/list with reason why]
- Workload contextualized (for example, specific slices/layers involved): [yes/no]
```
@@ -0,0 +1,94 @@
# System-Wide Triage
Follow these steps in order:
1. **Verify data integrity:** Query `stats` for CPU and data loss indicators.
If found, warn the user that findings may be incomplete and proceed with
`[GAP]` awareness.
2. **Run metrics:** Systemic issues (like thermal throttling, LMKs, binder
contention) affect all threads.
> **Prerequisite:** Verify that `trace_processor` is available in your
> environment before executing commands (see
> `$SKILL_ROOT/references/perfetto/setup.md`).
Run the following query based on the user's intent to understand the
high-level picture:
```sh
trace_processor [trace_file] --run-metrics [comma_separated_metrics]
```
3. **Define target and symptom window:** Find the specific issue's start
(`ts`), duration (`dur`) and end timestamp (`ts + dur`). Verify the trace
actually covers the full window.
> After identifying the specific instance to investigate and the symptom
> window (for example, janky frame at `ts = 5.1s`), check whether the first
> blocking event was caused by a stall that _started before_ the symptom
> window. Expanding the window upstream is essential because stalls (for
> example, in binder servers, memory reclamation or kernel locks) often
> originate hundreds of milliseconds before the user-visible frame drop or
> latency spike occurs.
>
> - Find the victim's first non-Running state transition within the symptom
> window.
> - Follow the waker chain for that transition. Check waker timestamps -
> if the blocking event began significantly before the symptom window,
> expand the investigation window upstream to include that origin.
> - If it cascades down to an origin in another process (for example, a
> binder server that stalled `500ms` before the jank), **note** the
> expanded window and include the upstream stall as a co-candidate to
> investigate further.
4. **Output** the triage summary using the format defined below.
## Quick reference for triaging
Key available metrics for `--run-metrics`:
`android_startup`, `android_cpu`, `android_mem`, `android_lmk`,
`android_binder`, `android_surfaceflinger`, `android_gpu`
Quick lookup table based on symptom:
| Symptom/Issue | What to check | Useful Perfetto tables |
| :--------------- | :------------------------------------ | :---------------------------------------------- |
| App startup | Main thread | `android.startup.startups` (`android_startups`) |
| App jank | Main, render threads | `actual_frame_timeline_slice` |
| System jank | SurfaceFlinger | `actual_frame_timeline_slice` |
| App/system crash | `Process crashed` or `tombstoned` | `slice` |
| ANR | Main thread, `system_server` watchdog | `thread_state`, `slice` |
| Frame issues | `DrawFrame` or `doFrame` slices | `slice` |
## Final Output Format
Output the triage summary in the following format:
```markdown
## Trace Metadata
- **Device Model:** `[String, e.g., Pixel 7 Pro]`
- **Android Build:** `[String, e.g., TQ2A.230505.002]`
## System Vitals Summary
- **Status:** `[Nominal | Degraded | Critical]`
- **Flags Raised:** _(Only list systemic issues that are actually detected)_
- **Metric:** `[String, e.g., thermal_throttling, lmkd, binder_contention]`
- **Description:** `[e.g., 'Thermal throttling during symptom window']`
## Candidate
- **Issue Classification:** `[String, e.g., Startup, Jank, ANR_Input, Crash]`
- **Package Name:** `[String]`
- **Process Name:** `[String]`
- **Thread Name:** `[String]`
- **UPID:** `[Integer]`
- **Target UTID:** `[Integer]` _(The primary thread)_
- **Render Thread UTID:** `[Integer or null]` _(If issue is jank)_
- **Start TS:** `[Integer Timestamp]`
- **End TS:** `[Integer Timestamp]`
- **Duration (ms):** `[Float]`
- **Severity Note:** `[String, e.g., '150ms missed frame - worst instance']`
```
@@ -0,0 +1,36 @@
# Recording Orchestrator
Use the guidelines below to orchestrate between recording workflows after
disambiguating the user intent (for example, recording a system trace or a
heap dump). The guidelines below ensure pre-flight checks are always performed
and domain-specific profiling flags are applied accurately.
## Workflow Selection
At this point, you should have an answer to the following question: "User
intends to record a _____". For example, "User intends to record a system
trace".
## Handling Composite Requests
If the user's request involves multiple distinct recording goals (for example,
setting up a specialized environment AND recording a generic trace), do not
execute them simultaneously.
1. Break down the request and propose a sequential execution plan to the user.
2. Ask the user for confirmation to start the first step.
3. Do not proceed to the next workflow until the current one is completed.
## Workflow Discovery and Routing
This skill supports multiple specialized recording workflows. To determine the
right workflows to use:
1. Use your file search tools (for example, `grep_search`) to recursively scan
the `$SKILL_ROOT/recording/workflows/` directory for workflow entrypoints
(markdown files defining a top-level `name:` key in their frontmatter,
ignoring internal `references/` subdirectories).
2. Compare the user's request and intent against the `name:`, `description:`,
and `keywords:` fields to identify matching workflows. If multiple are
found, present them as options to the user and proceed with the
user's selection.
@@ -0,0 +1,148 @@
---
name: perfetto-trace-recording
description: >
Records Java/native heap dumps, system traces, or custom configs via
Perfetto helper scripts on Android.
keywords:
- perfetto
- record
- system trace
- heap dump
- memory leak
- custom config
---
# Recording Perfetto Traces on Android (Helper Scripts)
> [!IMPORTANT] **Scope:** This guide is **strictly for recording traces on
> Android devices**. For other platforms (Linux, macOS, or Chrome), refer to
> the platform documentation on [perfetto.dev/docs](https://perfetto.dev/docs/).
Rather than running raw `adb` commands, use the official Perfetto helper
scripts. They automatically handle pushing configurations, starting tracing
daemons, pulling the trace file, and optionally opening it in the browser.
Ensure **Developer options** and **USB debugging** are enabled, and your
device is connected via USB before starting.
---
## 0. Download the Helper Scripts
Download the helper scripts from the official Perfetto repository:
```bash
TOOLS_URL="https://raw.githubusercontent.com/google/perfetto/main/tools"
# Java Heap Dump (ART)
curl -O "$TOOLS_URL/java_heap_dump" && chmod +x java_heap_dump
# Native Heap Profiling (heapprofd)
curl -O "$TOOLS_URL/heap_profile" && chmod +x heap_profile
# CPU Stack Sampling (traced_perf)
curl -O "$TOOLS_URL/cpu_profile" && chmod +x cpu_profile
# General Tracing (Ftrace, ATrace, custom configs)
curl -O "$TOOLS_URL/record_android_trace" && chmod +x record_android_trace
```
---
## 1. Memory Tracing
Use these tools to analyze memory leaks, object retention, C/C++ allocations,
or system-wide memory counters.
### A. Java Heap Dump (ART)
Capture a snapshot of all Java objects in a process to investigate memory
leaks. Reference docs:
<https://perfetto.dev/docs/data-sources/java-heap-profiler>.
```bash
# Trigger a Java heap dump for a specific app
./java_heap_dump -n YOUR_APP_PACKAGE_NAME -o ./heap_dump.perfetto-trace
```
### B. Native C/C++ Heap Profiling (heapprofd)
Track C/C++ memory allocations (malloc/free) to find native leaks. Reference
docs: <https://perfetto.dev/docs/data-sources/native-heap-profiler>.
```bash
# Profile native allocations for a specific app
./heap_profile -n YOUR_APP_PACKAGE_NAME
# Profile with custom sampling interval (default is 4096 bytes)
./heap_profile -n YOUR_APP_PACKAGE_NAME -i 2048
```
### C. System-wide Memory Counters
Track RSS, Swap, and process stats over time. Reference docs:
<https://perfetto.dev/docs/data-sources/memory-counters-sys-stats>.
**Note:** To record memory counters, you must use a custom config via
`record_android_trace` (see Section 4).
---
## 2. Stack Sampling / Callstack Profiling (traced_perf)
Identify CPU hotspots in C/C++ or Rust code by periodically sampling
callstacks. Reference docs:
<https://perfetto.dev/docs/data-sources/cpu-profiler>.
```bash
# Profile CPU usage by sampling callstacks at 100Hz (default) for 10 seconds
./cpu_profile -n YOUR_APP_PACKAGE_NAME -d 10000
# Profile at a custom frequency (e.g., 200Hz)
./cpu_profile -n YOUR_APP_PACKAGE_NAME -f 200
```
---
## 3. System Tracing (CPU, Scheduling, & ATrace)
Investigate jank, slow transitions, CPU scheduling, and system calls.
Reference docs: <https://perfetto.dev/docs/data-sources/ftrace>.
You can specify duration, buffer size, and categories directly on the command
line:
```bash
# Record scheduling, frequency, and window manager events for 5 seconds
./record_android_trace -t 5s -b 32mb sched gfx wm -a YOUR_APP_PACKAGE_NAME
```
Common categories: `sched` (CPU scheduling), `freq` (CPU frequency), `gfx`
(Graphics), `am` (Activity Manager), `wm` (Window Manager), `view` (View
System).
---
## 4. Custom Configs (Mix & Match Data Sources)
If you need a custom mixture of data sources (e.g., combining Java heap dumps
with ftrace), or control that command-line flags do not offer (ring buffers,
long traces, per-counter polling), synthesize a config:
1. **Read the config reference:**
`$SKILL_ROOT/recording/workflows/perfetto-trace-recording/references/trace_config_reference.md`
explains config structure and data sources, pointing to exemplar configs in
`$SKILL_ROOT/recording/workflows/perfetto-trace-recording/references/example-configs/`
that you can start from and merge. For other sources, consult the
official guide at <https://perfetto.dev/docs/data-sources/>.
2. **Save the Config:** Write the synthesized text configuration to a local
file (e.g., `config.pftxt`).
3. **Execute the Trace:** Run the trace using the general recorder script
`record_android_trace` (specialized scripts do not accept custom configs):
```bash
./record_android_trace -c config.pftxt -o ./my_trace.perfetto-trace
```
If the config has a typo, this fails fast with a parse error naming the bad
field - fix the config and retry.
@@ -0,0 +1,43 @@
# App jank / slow UI: framework + app atrace events, frame deadlines from
# SurfaceFlinger, and the scheduling context underneath.
# Replace com.example.myapp with the app's package name.
buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}
duration_ms: 10000
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_wakeup"
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: "am"
atrace_categories: "input"
atrace_apps: "com.example.myapp"
}
}
}
# Per-frame expected vs actual timelines (actual jank classification).
data_sources {
config {
name: "android.surfaceflinger.frametimeline"
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}
@@ -0,0 +1,35 @@
# CPU callstack sampling via traced_perf: periodic stack samples to find hot
# functions. Prefer the cpu_profile helper script for a standalone profile;
# use this config when combining with other data sources.
# Replace com.example.myapp with the app's package name, or drop the scope
# block to sample all processes.
buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}
duration_ms: 10000
data_sources {
config {
name: "linux.perf"
perf_event_config {
timebase {
frequency: 100
}
callstack_sampling {
scope {
target_cmdline: "com.example.myapp"
}
}
}
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}
@@ -0,0 +1,21 @@
# Java heap dump (retention graph) of one app. Prefer the java_heap_dump
# helper script for a standalone dump; use this config when combining a heap
# dump with other data sources in one trace.
# Replace com.example.myapp with the app's package name.
buffers {
size_kb: 102400
}
duration_ms: 30000
data_sources {
config {
name: "android.java_hprof"
java_hprof_config {
process_cmdline: "com.example.myapp"
}
}
}
# Heap dumps can be large; stream to the output file instead of relying on
# the in-memory buffer alone.
write_into_file: true
@@ -0,0 +1,37 @@
# Long / field trace: runs until stopped, keeps the most recent data in a
# ring buffer, and periodically flushes to the output file so the trace
# survives longer than RAM allows.
buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}
# No duration_ms: stop manually (Ctrl-C on record_android_trace), or add one.
# Move data to the output file periodically instead of only at the end.
write_into_file: true
file_write_period_ms: 2500
# Commit app shared-memory buffers periodically so events stay ordered.
flush_period_ms: 30000
# Stop when the output file reaches this size (bytes).
max_file_size_bytes: 1000000000
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
}
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}
@@ -0,0 +1,45 @@
# System and per-process memory over time: meminfo/vmstat counters polled
# periodically, per-process RSS via both polling and kernel rss_stat events,
# and low-memory-killer activity.
buffers {
size_kb: 32768
}
duration_ms: 30000
data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
meminfo_period_ms: 1000
meminfo_counters: MEMINFO_MEM_TOTAL
meminfo_counters: MEMINFO_MEM_FREE
meminfo_counters: MEMINFO_MEM_AVAILABLE
meminfo_counters: MEMINFO_SWAP_FREE
vmstat_period_ms: 1000
vmstat_counters: VMSTAT_NR_FREE_PAGES
vmstat_counters: VMSTAT_PGFAULT
vmstat_counters: VMSTAT_PGMAJFAULT
}
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
proc_stats_poll_ms: 10000
scan_all_processes_on_start: true
}
}
}
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "kmem/rss_stat"
ftrace_events: "lowmemorykiller/lowmemory_kill"
ftrace_events: "oom/oom_score_adj_update"
}
}
}
@@ -0,0 +1,21 @@
# Native (C/C++) heap profiling via heapprofd: sampled malloc/free callstacks
# for one app. Prefer the heap_profile helper script for a standalone profile;
# use this config when combining with other data sources.
# Replace com.example.myapp with the app's package name.
buffers {
size_kb: 65536
}
duration_ms: 30000
data_sources {
config {
name: "android.heapprofd"
heapprofd_config {
sampling_interval_bytes: 4096
process_cmdline: "com.example.myapp"
# To profile ART/JNI and custom allocators too, add: all_heaps: true
}
}
}
write_into_file: true
@@ -0,0 +1,33 @@
# CPU scheduling: who ran when, on which core, at what clock speed.
# The base layer for almost any performance investigation.
buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}
duration_ms: 10000
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
ftrace_events: "power/suspend_resume"
}
}
}
# Resolves thread/process ids in sched events to readable names.
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}
@@ -0,0 +1,102 @@
# Synthesizing Perfetto Trace Configs (Mix & Match)
Read this when you need a custom trace config - a mixture of data sources the
specialized helper scripts do not cover. Start from the exemplar configs in
`$SKILL_ROOT/recording/workflows/perfetto-trace-recording/references/example-configs/`,
then add, remove, or merge the pieces described below.
## Shape of a config
A config is a `TraceConfig` protobuf - authoritative schema:
<https://raw.githubusercontent.com/google/perfetto/main/protos/perfetto/config/trace_config.proto>
(per-source option messages live under `protos/perfetto/config/` in the same
repo, e.g. `.../ftrace/ftrace_config.proto`) - written in protobuf **text
format** (conventionally saved as `config.pftxt`):
```protobuf
buffers {
size_kb: 32768 # sizes are in KB, not bytes
fill_policy: RING_BUFFER # keep newest data; DISCARD keeps oldest
}
duration_ms: 10000 # omit to trace until stopped manually
data_sources {
config {
name: "linux.ftrace" # which producer to enable
ftrace_config { ... } # that producer's own options
}
}
# ...more data_sources blocks, one per source...
```
Merging two configs = keep one `buffers` section and concatenate their
`data_sources` blocks. Each data source may appear at most once (the
`linux.ftrace` config especially: merge all `ftrace_events`,
`atrace_categories`, and `atrace_apps` entries into a single block).
## Exemplar configs
Location:
`$SKILL_ROOT/recording/workflows/perfetto-trace-recording/references/example-configs/`
| File | Use case |
| :--- | :--- |
| `sched_cpu.pftxt` | CPU scheduling + frequency/idle (base layer). |
| `app_jank.pftxt` | Slow UI / dropped frames: atrace + frame timeline. |
| `memory_counters.pftxt` | System + per-process memory, LMK activity. |
| `java_heap_dump.pftxt` | Java heap retention graph for one app. |
| `native_heap.pftxt` | Sampled native malloc/free callstacks (heapprofd). |
| `cpu_profile.pftxt` | Periodic CPU callstack samples (traced_perf). |
| `long_background.pftxt` | Long/field traces: ring buffer + periodic writes. |
For a standalone heap dump, native heap profile, or CPU profile, prefer the
dedicated helper scripts in:
`$SKILL_ROOT/recording/workflows/perfetto-trace-recording/perfetto_trace_recording.md`.
Use exemplars when one trace must combine several sources.
## Data sources at a glance
| `name` | What it records | Key options |
| :--- | :--- | :--- |
| `linux.ftrace` | Kernel events and atrace | `ftrace_events`, `atrace_categories`, `atrace_apps` |
| `linux.process_stats` | Process/thread names & stats | `scan_all_processes_on_start`, `proc_stats_poll_ms` |
| `linux.sys_stats` | Periodic `/proc` counters | `meminfo_period_ms`, `vmstat_period_ms`, `stat_period_ms` |
| `android.log` | Logcat | `android_log_config { log_ids: ... }` |
| `android.surfaceflinger.frametimeline` | Frame timelines (jank) | None needed |
| `android.java_hprof` | Java heap dump | `java_hprof_config { process_cmdline: ... }` |
| `android.heapprofd` | Native heap profiling | `heapprofd_config { sampling_interval_bytes, ... }` |
| `linux.perf` | CPU callstack sampling | `perf_event_config { timebase, callstack_sampling }` |
| `android.packages_list` | Package mapping | None needed |
| `android.power` | Battery counters | `android_power_config { battery_poll_ms, ... }` |
| `track_event` | Custom app trace events | `track_event_config { enabled_categories }` |
The full, authoritative field list for every data source is the generated
[TraceConfig reference](https://perfetto.dev/docs/reference/trace-config-proto);
per-source guides live under
[perfetto.dev/docs/data-sources](https://perfetto.dev/docs/data-sources/).
## Top-level knobs
- `duration_ms` - trace length. Omit it to trace until the recording command
is stopped (Ctrl-C on `record_android_trace`).
- `buffers.fill_policy` - `RING_BUFFER` keeps newest data (right choice when
the interesting moment is at the end); `DISCARD` keeps the oldest.
- Long traces: `write_into_file: true` + `file_write_period_ms` stream buffer
to disk periodically so the trace can exceed RAM; `flush_period_ms: 30000`
keeps app-emitted events ordered; `max_file_size_bytes` bounds output.
- Buffer sizing rule of thumb: 3264 MB (`size_kb: 32768``65536`) is plenty
for most 1030s traces; heap dumps need ~100 MB or `write_into_file`.
## Pitfalls
- All buffer sizes are **KB** (`size_kb: 32768` = 32 MB); durations are
**ms**.
- atrace data (categories and app events) only flows through the
`linux.ftrace` data source - there is no separate "atrace" source, and an
app's custom trace events appear only if its package is listed in
`atrace_apps` (or `atrace_apps: "*"`).
- Field-name typos are only caught when the config is parsed at record time:
`record_android_trace -c config.pftxt` fails fast with a parse error naming
the bad field, so treat that as your validator, fix, and retry.
- Text-format enum values are bare identifiers (`fill_policy: RING_BUFFER`),
and strings are quoted.
@@ -0,0 +1,15 @@
# Environment setup
## Set `$SKILL_ROOT`
Set up `$SKILL_ROOT` once per session.
Every file this skill references - workflow markdown, reference docs, helper
scripts, and downloaded dependencies - is written in the form `$SKILL_ROOT/...`,
relative to the **skill root** (the directory holding this skill's `SKILL.md`).
Set it once to the absolute path of the directory you loaded `SKILL.md` from:
```sh
# Substitute with the directory the SKILL.md lives in.
export SKILL_ROOT="/absolute/path/to/skills/profilers/android-profiler"
```
@@ -0,0 +1,29 @@
# Getting `trace_processor` working
> **Prerequisite:** Ensure `$SKILL_ROOT` is initialized per
> `$SKILL_ROOT/references/env_setup.md`.
## Put `trace_processor` on the `PATH`
If the workspace does not already have a pre-downloaded `trace_processor`
wrapper, download it from <https://get.perfetto.dev/trace_processor> to
`$SKILL_ROOT/bin/trace_processor` and make it invocable for this session:
```sh
chmod +x "$SKILL_ROOT/bin/trace_processor" # ensure exec bit is set
export PATH="$SKILL_ROOT/bin:$PATH"
trace_processor --version # smoke test (ensure > v57.0, e.g. v57.1+)
```
After this, every bare `trace_processor ...` command in this skill works
verbatim. On Windows, skip the `PATH` setup and invoke it as
`python "$SKILL_ROOT/bin/trace_processor" ...` instead.
Notes:
- The first invocation downloads the prebuilt native binary (picking the
right one for the host platform) into `~/.local/share/perfetto/prebuilts/`
and caches it; only the first call pays the download cost.
- If the user's environment has its own mandatory `trace_processor`
(Google-internal, OEM build environments, CI images), prefer that
team-specific setup instead.
@@ -0,0 +1,327 @@
# Querying Perfetto traces
This reference explains how to extract data from a Perfetto trace file
(`.pftrace`, `.perfetto-trace`, `.pb`) using `trace_processor` and
PerfettoSQL. Read it for ad-hoc querying outside a guided workflow; the
workflows under `$SKILL_ROOT/analysis/workflows` and
`$SKILL_ROOT/recording/workflows` carry their own queries.
The `trace_processor` binary is what every other Perfetto analysis tool
runs on top of, including the Perfetto UI. Reference docs:
<https://perfetto.dev/docs/analysis/trace-processor>.
> **Prerequisite - `trace_processor` must be invokable.** Before
> running any of the shell commands below, read
> `$SKILL_ROOT/references/perfetto/setup.md`. It defines how to make
> the bare `trace_processor` commands below work in this environment.
## Querying a trace: sessions
Querying goes through a **session**: load the trace once into a named
background session, then run every query against it with `--remote`.
Parsing a trace is the expensive part (tens of seconds for a multi-GB
trace); the session pays it once, and every real analysis runs more than
one query.
```sh
# 1. Load the trace into a background session - once per trace.
# Pick a descriptive session name (e.g. derived from the trace file).
trace_processor server unix --name mysession --daemonize TRACE_FILE
# 2. Run queries against the warm session: instant, no reparse.
trace_processor query --remote mysession \
"SELECT ts, dur, name FROM slice WHERE dur > 5e8 LIMIT 5"
# 3. When you are completely done with the trace:
trace_processor server kill mysession
```
Multiple statements separated by `;` are supported in one invocation.
Session rules:
- Session names are managed by trace_processor in a per-user session
directory - there are no ports to choose and no collisions with other
agents or the Perfetto UI.
- **Session state persists across `query --remote` calls.** A
`CREATE PERFETTO TABLE` or `INCLUDE PERFETTO MODULE` run in one call is
visible in the next, so materializing intermediate results pays off
across invocations.
- Flags that configure trace loading (`--full-sort`, `--add-sql-package`, ...)
belong on the `server unix` invocation, not on `query --remote` - the client
rejects them with an explanatory error.
- `--remote` also accepts an absolute `*.sock` path or `host:port`;
names are the common case.
- Forgotten sessions are reaped automatically after 30 minutes idle
(`--idle-timeout`), but kill your session when the analysis is done.
`TRACE_FILE` can be a local path, an `http(s)://` URL, or a Perfetto UI
share link (`https://ui.perfetto.dev/#!/?s=<hash>`) - in the last two
cases trace_processor downloads the trace for you (cached under
`~/.cache/perfetto/` or the platform equivalent), resolving the share
link to its underlying trace first.
For a single throwaway query on a small trace you _can_ skip the session
(`trace_processor query TRACE_FILE "..."` parses, queries, and exits),
but treat that as the exception: it re-parses the trace on every
invocation and forgets created tables and included modules between
calls. Default to a session.
## Discovering what's in the trace
PerfettoSQL ships with **intrinsic table-functions** for browsing the
loaded standard library - modules, tables/views, functions, macros. Use
these to find what's available and to verify if a Standard Library module
already provides the needed abstraction before drafting custom logic.
**Mandatory Schema Check:** Do not guess column names or join keys. Always
use a plain `LIMIT 0` query to read the exact column schema of any specific
table, view, or query result before drafting your query.
> **Intrinsic surface - not stable API.** The `__intrinsic_*` names below
> are an implementation detail of trace processor. They're fair game for
> an agent to use during a session because this reference is loaded, but
> **don't bake `__intrinsic_*` names into committed scripts, dashboards,
> or stdlib modules** - they can change without notice.
```sql
-- 1. List every stdlib module currently available.
SELECT package, module FROM __intrinsic_stdlib_modules ORDER BY 1, 2;
-- 2. List the tables/views a specific module exposes
-- (after INCLUDE PERFETTO MODULE).
INCLUDE PERFETTO MODULE slices.with_context;
SELECT name, type, exposed, description
FROM __intrinsic_stdlib_tables('slices.with_context');
-- 3. List functions / macros a module exposes.
SELECT name, return_type, args
FROM __intrinsic_stdlib_functions('slices.with_context');
SELECT name, return_type, args
FROM __intrinsic_stdlib_macros('android.memory.heap_graph.helpers');
-- 4. Read the column schema of any table, view, or query.
-- LIMIT 0 returns the result header with no row scan; trace_processor
-- prints "column N = <name>" lines for each column.
SELECT * FROM slice LIMIT 0;
SELECT * FROM thread_or_process_slice LIMIT 0;
SELECT * FROM (SELECT ts, dur, name FROM slice WHERE dur > 0) LIMIT 0;
```
Useful starting points for any trace:
| View | What's in it |
| :------------- | :--------------------------------------------------------------- |
| `slice` | Atrace slices, async slices, anything with a duration on a track |
| `thread` | One row per thread |
| `process` | One row per process |
| `thread_state` | State transitions (Running, Runnable, Sleeping, ...) |
| `sched_slice` | When threads were on-CPU |
| `counter` | Time-series counter samples |
| `track` | Every track in the trace; join on `track_id` to other tables |
Static reference for the public surface (does not require a running
trace_processor): <https://perfetto.dev/docs/analysis/sql-tables>.
## Using the standard library
Most useful queries are _much_ shorter when you build on stdlib modules
instead of joining raw tables yourself. Generated stdlib reference:
<https://perfetto.dev/docs/analysis/stdlib-docs>.
Include a module before referencing the views, tables or macros it
defines:
```sql
INCLUDE PERFETTO MODULE slices.with_context;
SELECT name, dur, thread_name, process_name
FROM thread_or_process_slice
WHERE dur > 1e9 -- slices longer than 1s
ORDER BY dur DESC
LIMIT 20;
```
A few commonly used modules to know:
- `slices.with_context` - slice rows joined with their thread / process.
- `sched.with_context` - `sched_slice` joined with thread / process.
- `android.startup.startups` - one row per app startup.
- `stacks.cpu_profiling` - flat samples and call-graph helpers.
- `android.memory.heap_graph.dominator_tree` - retained-size analysis for
Java heap dumps.
The module name maps directly to the file path under the stdlib root:
`foo.bar` lives at `foo/bar.sql`. Browse the full list at the stdlib
reference linked above.
## Tips for writing good PerfettoSQL
- **Reach for stdlib first.** If you find yourself joining `slice` to
`thread_track` to `thread` to `process`, there is almost certainly a
stdlib module that already does it. Check the stdlib reference before
writing the join.
- **Filter on `dur > 0` and Trace Boundaries carefully.** Some slices have
`dur = -1` (still open at trace end) and some have `dur = 0` (instant
events). Be explicit about which you mean. When calculating a bounding box
(for example, `ts + dur`) or summing durations (`SUM(dur)`), handle
incomplete durations using: `IIF(dur = -1, trace_end() - ts, dur)`.
- **Robust State Transitions.** Avoid manual timestamp arithmetic (for
example, `ts + dur = next.ts`) to join adjacent events. Rely on standard
library modules (for example, `sched.runnable`, `linux.perf.counters`,
`intervals.overlap`) which safely handle trace gaps and preemptions.
- **Working with Identifiers:**
- **Use Unique Identifiers for Joins:** When writing SQL queries in
Perfetto, you must join tables using `utid` (unique thread ID) or `upid`
(unique process ID) instead of regular `tid` or `pid`. **Why it's
useful**: The operating system recycles `TIDs` and `PIDs`, while `UTIDs`
and `UPIDs` remain unique for the lifetime of the trace, which prevents
incorrect joins.
- Columns like `id`, `utid`, `upid`, `track_id` are not stable across traces
or even runs of trace_processor on the same trace. You can use them
**inside** a query as join keys, but alongside IDs, always join out to
a stable name (`thread.name`, `process.name`, `slice.name`) when reporting
results to the user.
- **Materialize expensive intermediate results.** `CREATE PERFETTO TABLE foo
AS SELECT ...` caches the result so subsequent queries don't redo the work.
- _Note for `SPAN_JOIN`:_ Intermediate tables fed into a `SPAN_JOIN` must
be materialized using `CREATE PERFETTO TABLE`, not `CREATE VIEW`.
- **Idempotency.** Ensure queries are idempotent to prevent "already exists"
errors during multiple executions.
- For Perfetto objects, always use `CREATE OR REPLACE`:
`CREATE OR REPLACE PERFETTO {TABLE|VIEW|MACRO|FUNCTION}`.
- For SQLite Virtual Tables (such as `SPAN_JOIN`), `CREATE OR REPLACE` is
not supported. Explicitly drop them first:
`DROP TABLE IF EXISTS my_table;`
`CREATE VIRTUAL TABLE my_table USING SPAN_JOIN(...);`
- For standard SQLite indexes, prepend `DROP INDEX IF EXISTS index_name;`.
- **`SPAN_JOIN` safety.** `SPAN_JOIN` will crash if intervals **within the
same input table** overlap. Always use the `PARTITIONED {column}` (for
example, `PARTITIONED track_id`) clause to isolate intervals.
- **Avoid `SELECT *` in saved queries.** Trace processor table schemas can
gain columns; pin the columns you actually use.
- **Use `EXPLAIN QUERY PLAN` if a query is slow.** It shows whether SQLite is
using indexes. Counter and slice tables have built-in indexes on `ts` and
`track_id`; queries that don't filter on either will scan the whole table.
- **Argument Extraction:** Use `EXTRACT_ARG(arg_set_id, 'key')` to fetch event
properties instead of manually joining the `args` table.
- **JSON Parsing:** When dealing with JSON text, use standard SQLite JSON
functions (for example, `json_extract()`) to extract values.
- **String Matching (Always use GLOB).** Use `GLOB` instead of `LIKE`. `LIKE`
causes performance bottlenecks and treats underscores (`_`) as wildcards,
leading to bugs.
- **Exact matches:** Use `=`.
- **Substring matches:** Use `GLOB` with `*` (for example,
`name GLOB '*RenderThread*'`).
- **Case-insensitive matches:** Use `LOWER(name) GLOB` and make sure the
search string is fully lowercase (for example,
`LOWER(name) GLOB '*renderthread*'`). Use this when dealing with
inconsistent trace capitalization (for example, `WakeLock` vs `wakelock`).
- **Alias Precision.** Always prefix column names with table or view alias,
that is: `{alias}.{column_name}`.
## Common Analysis Patterns
- **Calculating Time Overlaps & CPU Time:**
1. **Primary Method (MANDATORY):** Always search the standard library first
before writing custom interval logic. For example, to find the exact CPU
execution time of a slice, do not calculate it manually; instead, search
the docs and use the `slices.cpu_time` module.
2. **Fallback Method (Use ONLY if you have verified no stdlib module or
`SPAN_JOIN` applies):** If you must calculate custom overlap durations
between two different sets of time intervals `[start1, end1]` and
`[start2, end2]`:
- **Condition:** The intervals overlap if `start1 < end2` and
`start2 < end1`.
- **Duration:** The overlap duration is calculated as
`MIN(end1, end2) - MAX(start1, start2)`.
- **Important:** Incomplete Perfetto slices have a duration of -1
(`dur = -1`). Always calculate the effective end time using
`ts + IIF(dur = -1, trace_end() - ts, dur)` before applying this logic.
- Include the `android.startup.startups` module and query
`android_thread_slices_for_all_startups` (or `android_startups`) for
app startup requests.
- Join `counter_track` with `counter` to get values of counter with a
specific name.
- When querying for a CPU frequency counter, include the `linux.cpu.frequency`
module and use the `cpu_frequency_counters` table.
- **Window Size:** When looking for events around a specific timestamp, start
with 100ms as the window size.
- **Total Duration:** To calculate the total time spent in slices matching a
specific name pattern (for example, `*{name_pattern}*`), you must sum their
durations. **Why it's useful**: This helps quantify the total impact of a
specific function or feature on performance across multiple calls. Here is
an example query (note the safe handling of incomplete slices):
```sql
SELECT
count(*) as total_count,
sum(IIF(slice.dur = -1, trace_end() - slice.ts, slice.dur)) / 1e6
as total_dur_ms
FROM slice
WHERE slice.name GLOB '*{name_pattern}*';
```
## Analytical Workflow (Standard Operating Procedure)
To ensure accuracy and efficiency, follow these steps:
1. **Research & Dissection:** Identify the core question and required data
points.
2. **Mandatory Schema Validation:** Locate relevant modules via
`__intrinsic_stdlib_modules` and their tables via
`__intrinsic_stdlib_tables('module_name')`. Verify column names and types.
- **Intent Check:** You must verify if a stdlib module already provides
the needed abstraction before drafting manual arithmetic or custom joins.
- **IMPORTANT:** If your query requires calculating overlaps, intersections,
or boundaries between intervals, you MUST search `__intrinsic_stdlib_modules`
globally (for example, `WHERE module GLOB '*overlap*'`) before writing
`MIN()/MAX()` or `IIF(dur = -1...)` logic.
3. **Draft & Validate Loop (Max 3 Iterations):**
- [ ] **Draft:** Use only verified schemas. Ensure `INCLUDE PERFETTO MODULE`
is present for non-prelude modules.
- [ ] **Verify Idempotency:** Use `CREATE OR REPLACE` or `DROP TABLE IF EXISTS`
for virtual tables.
- [ ] **Check Precision:** Are ALL columns prefixed with aliases (e.g.,
`s.name`)? Are you joining on `utid`/`upid`?
- [ ] **String Matching:** Did you use `GLOB` or `=` instead of `LIKE`?
- [ ] **Span Join Check:** If using `SPAN_JOIN`, are tables `PARTITIONED`
and materialized?
- [ ] **Execute:** Run against the session:
`trace_processor query --remote SESSION "QUERY"`.
**Execution Rules:**
- **File Usage:** If you must create a SQL file to execute queries (for
example, due to query length or escaping issues), you must create them
in the `/tmp/` directory.
- **Failure Resilience:** Debug and fix SQL syntax and logic errors when
query fails. Don't simplify the analytical intent to pass validation.
For example, if requested to calculate an overlap or intersection, you
must fix the intersection math. Don't substitute with disjoint queries
(for example, returning independent total durations) as a workaround.
4. **Cleanup & Finalize:**
- Explicitly return and state the final validated SQL and explain the
results to the user.
- **Save an analysis report.** Write a markdown file in the working
directory (default `perfetto_analysis_report.md`) containing: the
question investigated, the trace file(s) analyzed, the findings with
concrete numbers, the final validated queries (so the analysis can be
re-run), and open questions / next steps. Point the user at it in
your final message.
- Before finishing, delete any temporary SQL files created in `/tmp/`.
## Where to look for more
- Language tour:
<https://perfetto.dev/docs/analysis/perfetto-sql-getting-started>
- Trace processor reference:
<https://perfetto.dev/docs/analysis/trace-processor>
- Generated table reference:
<https://perfetto.dev/docs/analysis/sql-tables>
- Generated stdlib reference:
<https://perfetto.dev/docs/analysis/stdlib-docs>
-142
View File
@@ -1,142 +0,0 @@
---
name: perfetto-sql
description: Translates natural language data intents into syntactically valid Perfetto
SQL queries and executes them against a local trace file. Use this skill to extract
slice, thread, or memory data from Android Perfetto traces using trace_processor.
license: Complete terms in LICENSE.txt
metadata:
author: Google LLC
last-updated: '2026-05-14'
keywords:
- Android
- Perfetto SQL
- Query Guidelines
- Performance Profiling
- Trace Analysis
- SQL Best Practices
- SPAN_JOIN
- Idempotency
---
## Guidelines and Hints
- **Idempotency:** Ensure queries are idempotent to prevent "already exists" errors during multiple executions.
- For Perfetto objects, always use `CREATE OR REPLACE`: `CREATE OR REPLACE
PERFETTO TABLE`, `CREATE OR REPLACE PERFETTO VIEW`, `CREATE OR REPLACE
PERFETTO FUNCTION`, `CREATE OR REPLACE PERFETTO MACRO`.
- For SQLite Virtual Tables (such as `SPAN_JOIN`), `CREATE OR REPLACE` is not supported. Explicitly drop them first: `DROP TABLE IF EXISTS
my_table; CREATE VIRTUAL TABLE my_table USING SPAN_JOIN(...);`
- For standard SQLite indexes, prepend `DROP INDEX IF EXISTS index_name;`.
- `SPAN_JOIN` will crash if intervals **within the same input table** overlap. Always use the `PARTITIONED {column}` (for example, `PARTITIONED upid`) clause to isolate intervals.
- Intermediate tables fed into a `SPAN_JOIN` must be materialized using `CREATE PERFETTO TABLE`, not `CREATE VIEW`.
- **Trace Boundaries (`dur = -1`):** Slices or thread states that don't finish before the trace ends are recorded with `dur = -1`. When calculating a bounding box (for example, `ts + dur`) or summing durations (`SUM(dur)`), handle incomplete durations using: `IIF(dur = -1, trace_end() - ts, dur)`.
- **Robust State Transitions:** Avoid manual timestamp arithmetic (for example, `ts + dur = next.ts`) to join adjacent events. Rely on standard library modules (for example, `sched.runnable`, `linux.perf.counters`, `intervals.overlap`) which safely handle trace gaps and preemptions.
- **Unique Identifiers:** When writing SQL queries in Perfetto, you must join tables using `utid` (unique thread ID) or `upid` (unique process ID) instead of the regular `tid` or `pid`. **Why it's useful** : The operating system recycles `TIDs` and `PIDs`, while `UTIDs` and `UPIDs` remain unique for the lifetime of the trace, which prevents incorrect joins.
- **Safe Argument Extraction:** Use `EXTRACT_ARG(arg_set_id, 'key')` to extract dictionary or JSON-like properties from slices or tracks. Don't attempt string parsing.
- **String Matching (Always use GLOB):** Use `GLOB` instead of `LIKE`. `LIKE` causes performance bottlenecks and treats underscores (`_`) as wildcards, leading to bugs.
- **Exact matches:** Use `=`.
- **Substring matches:** Use `GLOB` with `*` (for example, `name GLOB
'*RenderThread*'`).
- **Case-insensitive matches:** Use `LOWER(name) GLOB` and make sure the search string is fully lowercase (for example, `LOWER(name) GLOB
'*renderthread*'`). Use this when dealing with inconsistent trace capitalization (for example, `WakeLock` versus `wakelock`).
- **Calculating Time Overlaps:** To calculate the overlap duration between two
time intervals `[start1, end1]` and `[start2, end2]`:
> **Precedence Rule:** Always prefer using `SPAN_JOIN` or standard library
> functions (for example, `intervals.overlap`) to calculate overlaps
> **between two different sets of intervals** . Avoid manual arithmetic if a
> standard library feature or `SPAN_JOIN` can achieve the same result. Use
> the following logic if no built-in alternative exists.
1. **Condition:** The intervals overlap if `start1 < end2` and `start2 <
end1`.
2. **Duration:** The overlap duration is calculated as `MIN(end1, end2) -
MAX(start1, start2)`
> **Important:** Incomplete Perfetto slices have a duration of -1
> (`dur = -1`). Always calculate the effective end time using `ts +
> IIF(dur = -1, trace_end() - ts, dur)` before applying this logic.
- Query `android_thread_slices_for_all_startups` for app startup requests.
- Join `counter_track` with `counter` to get values of counter with a specific
name.
- When querying for a CPU frequency counter, include the `linux.cpu.frequency`
module and use the `cpu_frequency_counters` table.
- When looking for events around a specific timestamp, start with 100ms as the
window size.
- Always prefix column names with table or view alias, that is:
`{alias}.{column_name}`.
- To calculate the total time spent in slices matching a specific name pattern
(for example, `*{name_pattern}*`), you must sum their durations. **Why it's
useful** : This helps quantify the total impact of a specific function or
feature on performance across multiple calls. Here is an example query (note
the safe handling of incomplete slices): `sql SELECT count(*) as
total_count, sum(IIF(slice.dur = -1, trace_end() - slice.ts, slice.dur)) /
1000000.0 as total_dur_ms FROM slice WHERE slice.name GLOB
'*{name_pattern}*';`
## Resources
- **Documentation:** The Perfetto Standard Library documentation is in [`perfetto-stdlib.md`](references/perfetto-stdlib.md). Use this file as a reference to discover available modules, find schemas (columns and types) for specific tables or views, or determine the `INCLUDE PERFETTO MODULE` statements required before drafting SQL query.
- **Execution Tool:** Queries are executed using the official `trace_processor` wrapper script downloaded directly from Perfetto. Output is returned in pure CSV format.
## Execution Protocol
You must follow these steps sequentially, mirroring a multi-agent pipeline:
### Step 0: Tool Setup
**Fetch the Wrapper:** You must use the top level of the current project workspace (`./trace_processor`).
> **CRITICAL GUARDRAIL:** NEVER use filesystem search tools (`find`, `find_by_name`, `grep`, `dir /s`, `Get-ChildItem`) across the home directory or workspace to locate `trace_processor` — unconstrained searches across entire workspaces will stop responding or time out.
Perform a direct file check at the top level of your workspace (e.g., `ls trace_processor`). If missing, download `https://get.perfetto.dev/trace_processor` directly into the root workspace (`curl -LO`), make it executable on macOS/Linux (`chmod +x`), and ensure `trace_processor` is added to `.gitignore`. Execute queries directly via `./trace_processor` (on Windows, explicitly invoke `python trace_processor`).
> **Important:** The file served at this URL is a `~10KB` Python wrapper script. Don't assume the download failed because it is human-readable text. This is the intended behavior. This script handles lazy-loading the precompiled binary automatically on its first run. Use it directly.
### Step 1: Dissection and Schema Research
1. Identify the core question, required data points, and filtering conditions.
2. **Precedence Rule:** If the user's request contains a SQL query, use it **without modification** and skip to Step 2 for validation.
3. **Mandatory Schema and Module Search:** For every table or view you plan to use, you MUST find its schema in [`perfetto-stdlib.md`](references/perfetto-stdlib.md). **Don't read the entire documentation file** --- it consumes the context window. Follow this precise workflow:
- **Discovery and Search:** Use available search tools (`grep`, `read_file` or file search) with line limits to discover relevant views, tables or modules based on your problem domain and high-level intents (for example, 'CPU time', 'running time', 'overlap', 'jank').
- **Why:** Searching solely for exact table names misses comprehensive, pre-computed views built for these analyses.
- **Note:** You must verify if a Standard Library module already provides the needed abstraction before drafting manual arithmetic or custom functions.
- **Targeted Bounded Reads:** Once you identify the relevant modules, efficiently read the tables and views within that module section.
- **Extract:** Extract only the schema, columns, and the exact `INCLUDE
PERFETTO MODULE` statements for the required object from the documentation.
- **Verify:** Review the columns, types, and descriptions to ensure the table matches your needs.
4. Print the research results before drafting the query:
5. *Tables/Views:* `Schema for {name}:` listing columns and types.
### Step 2: Draft and Validate Loop (Max 3 Iterations)
Draft the SQL query in SQLite syntax using **only** the schemas retrieved in
Step 1. After drafting, you must validate against this checklist:
- \[ \] **SQLite Syntax:** Does the query parse successfully without syntax errors?
- \[ \] **Idempotency:** Are all object creations safe to re-run? (Did you use `CREATE OR REPLACE PERFETTO` and `DROP TABLE IF EXISTS` for virtual tables?)
- \[ \] **Existence:** Were all tables found in the documentation?
- \[ \] **Intent Check:** Is there a pre-existing standard library table or view that will fulfill this intent before instead of writing manual arithmetic?
- \[ \] **Column Accuracy:** Do columns match the retrieved schemas?
- \[ \] **Alias Check:** Are ALL column names prefixed with their table or view alias (for example, `alias.column_name`)?
- \[ \] **Module Check:** Are `INCLUDE PERFETTO MODULE` statements included for all non-prelude modules? **You must use the exact module names provided in
the documentation.**
- \[ \] **Span Join Check:** If using `SPAN_JOIN`, are tables safely `PARTITIONED` to prevent overlapping interval crashes? Are intermediate tables materialized with `CREATE PERFETTO TABLE`?
- \[ \] **No LIKE Constraint:** Did you map string matches using `GLOB` or `=` instead of prohibited `LIKE`?
- \[ \] **Execution Check:** You MUST run queries using the standalone
`./trace_processor` wrapper with the `--query-string` flag:
`./trace_processor --query-string "QUERY" {trace_file}`.
**Execution Rules:**
- **File Usage** : If you must create a SQL file to execute queries (for example, due to query length or escaping issues), you must create them in the `/tmp/` directory.
- **State:** The execution is purely ephemeral. Database state does not persist across turns. You **cannot** share state (like views or tables) across queries in different turns. Every query must be standalone and fully self-contained.
- **Failure Resilience:** Debug and fix SQL syntax and logic errors when query fails.Don't simplify the analytical intent to pass validation. For example, if requested to calculate an overlap or intersection, you must fix the intersection math. Don't substitute with disjoint queries (for example, returning independent total durations) as a workaround.
### Step 3: Final Output
1. Explicitly return and state the final validated SQL and explain the results to the user.
2. Before finishing your response, delete all temporary SQL files you created in `/tmp/` directory.
File diff suppressed because it is too large Load Diff
@@ -1,79 +0,0 @@
---
name: perfetto-trace-analysis
description: Analyzes Perfetto traces to find the root cause of latency, memory, or
jank issues in Android apps. Use when the user provides a Perfetto trace file and
asks any question, ongoing investigation, or open-ended request to analyze its contents.
license: Complete terms in LICENSE.txt
metadata:
author: Google LLC
last-updated: '2026-05-14'
keywords:
- Perfetto
- trace analysis
- Android performance
- debugging
- profiling
- jank
- bottleneck
- SQL
---
## Resources
- **Domain Hints:** Reference files for specific performance areas: [`CPU`](references/hints_cpu.md), [`Graphics`](references/hints_graphics.md), [`I/O`](references/hints_io.md), [`IPC`](references/hints_ipc.md), [`Memory`](references/hints_memory.md), [`Power`](references/hints_power.md). These files each contain multiple expert-vetted, powerful trace analysis techniques to steer and aid in the analysis.
- **Perfetto SQL Reference:** Reference guidelines for translating intents into valid queries are located in [the SQL reference](references/sql.md). You must read this reference and follow its Execution Protocol for all SQL generation.
## Setup Phase (Mandatory)
1. **Initialize Scratchpad (Chain of Evidence):**
- Maintain your working memory in a local scratchpad file located in the exact same directory as the target trace file.
- Name the file using the trace's filename appended with `_analysis.md` (e.g., `[trace_filename]_analysis.md`). Before creating it, check if a file with that name already exists by listing the directory's contents---to avoid biasing your investigation, DO NOT read the file's contents to check for its existence. If it does, append an incrementing version number (e.g., `_v2.md`, `_v3.md`) until you find an available filename. You MUST hardcode this exact filename in all subsequent tool calls.
- Use this scratchpad STRICTLY to log verified facts: timestamps, slice names, thread IDs (utid/tid), and thread states.
- DO NOT write preliminary hypotheses or premature conclusions in the scratchpad. It is a strict Chain of Evidence.
2. **Review Domain Hints:** Read the Domain Hints in each file to get a high-level overview of what techniques are possible. Make sure to use this baseline knowledge when researching and retrieving hints during the ongoing investigation.
3. **Review SQL Reference:** Read the SQL reference in [`references/sql.md`](references/sql.md) and follow its Execution Protocol for all SQL generation. Do not guess schemas.
4. **Target Resolution:** If the user's request is broad (e.g., "why is the app slow?") and doesn't specify a package name:
- Execute a query to identify the active application: `sql INCLUDE
PERFETTO MODULE android.startup.startups; SELECT package FROM
android_startups;`
- If multiple packages are returned, ask the user to choose one. Save the chosen `package_name` to your scratchpad.
## Investigation Protocol
Follow this iterative loop until you have isolated the definitive root cause(s):
### 1. Formulate Hypothesis
- **Prioritization:** Form hypotheses using information from: user prompt \> "Domain Hints" ([`CPU`](references/hints_cpu.md), [`Graphics`](references/hints_graphics.md), [`I/O`](references/hints_io.md), [`IPC`](references/hints_ipc.md), [`Memory`](references/hints_memory.md), [`Power`](references/hints_power.md)) \> general knowledge. Be sure to leverage these "Domain Hints" as they are expert-vetted analysis techniques.
- **Source Attribution:** Explicitly mention the source of your hypothesis (e.g., "Based on hints_io.md...").
- **Focus Constraint:** Focus on the primary bottleneck. Avoid investigating deep into binder transactions unless the user explicitly asks for it or there is no other obvious bottleneck.
- **State Reasoning:** Briefly state your reasoning based on previous findings *before* generating a new query.
### 2. Plan and Collect Data
- **Metrics First:** Start with a high-level view using trace metrics before diving into custom SQL (e.g., `./trace_processor --run-metrics
android_startup`).
- **Broad to Narrow:** Begin with broad queries using minimal filters. Favor fuzzy matching (e.g., `GLOB '*abc*'`) over exact matching.
- **Overlapping Time:** When filtering by time, you MUST check for events that overlap with the target time range (e.g., `start1 < end2 AND start2 < end1`) to ensure you don't miss slices that span across the boundaries.
### 3. Analyze and Drill Down (Depth-First)
- **Evidentiary Rigor:** Do not draw conclusions without explicit data.
- **Wall Time vs. CPU Time:** Do not assume a long-running slice is actively computing. You MUST query the `thread_state` table for the exact timestamp window of suspicious slices to verify if the thread was `Running`, `Runnable` (waiting for CPU), or `Sleeping`/`Uninterruptible Sleep` (blocked).
- **Follow Dependencies:** If a thread is blocked/waiting, you MUST find what it is waiting *for* (Binder, Lock, I/O, etc.). Cross process boundaries if necessary. You cannot conclude an investigation on a waiting thread without identifying the blocker.
### 4. Exhaustive Investigation (Do Not Give Up Early)
- **Multiple Bottlenecks:** Complex performance issues rarely have a single cause. Do NOT stop your investigation after finding the first anomaly. Even if you find a major bottleneck (e.g., emulator graphics lag), you MUST continue searching for other independent system-wide issues (e.g., lock contention, I/O stalls). To find other bottlenecks, search through the content of the "Domain Hints" files ([`CPU`](references/hints_cpu.md), [`Graphics`](references/hints_graphics.md), [`I/O`](references/hints_io.md), [`IPC`](references/hints_ipc.md), [`Memory`](references/hints_memory.md), [`Power`](references/hints_power.md)) to retrieve and leverage expert-vetted, powerful trace analysis techniques. Investigate each relevant hint with depth.
- **Global Verification:** Periodically perform a system-wide query for the longest running slices (`ORDER BY slice.dur DESC`) and most frequent D-states to ensure your local investigation hasn't missed a massive, unrelated system stall.
- **Persist Through Dead Ends:** If a hypothesis is disproven or a query returns empty, do not conclude. Pivot your focus, broaden your search constraints (fuzzy matching, wider time windows), and continue the mission.
## Final Report
Only when you have followed the entire chain of dependencies to the root
cause(s) AND confirmed through exhaustive search that no other major bottlenecks
exist: 1. Summarize your findings detailing the verified chain of evidence. 2.
Conclude with: "This concludes the trace analysis. You can review the full chain
of evidence in \[scratchpad_filename\]. Let me know if you would like me to drill
down into any of these specific threads, or if you'd like help drafting a bug
report."
@@ -1,16 +0,0 @@
- When debugging a long slice: Examine its thread states to understand what the thread was doing (running, sleeping, blocked on I/O).
- When debugging a long slice for latency issues: Check if its duration is caused by one or more long-running child slices. Apply recursively.
- When a thread is woken up but there's a delay before it runs, check the "IRQ" track for the corresponding CPU to see if an interrupt is the cause.
- Check if kernel threads associated with hardware are running with real-time priority; if not, they can be preempted.
- When analysis of the primary application package does not reveal the root cause, expand to all threads and processes. Search for other runnable threads on the same CPU or high-priority kernel threads.
- When investigating app startup, use an SQL query to aggregate the reasons for uninterruptible sleep on the main thread.
- Check the "cpu_frequency" counter for the CPU cores that ran the main process. Missing frequency data or stuck frequencies indicate a kernel-level bug in the governor.
- Query raw "ftrace" events for logs related to the governor thread (e.g., "su_gov").
- To find concurrency issues, search for critical threads (e.g., 'RenderThread') in a blocked state (thread_state.state = 'D') and join with scheduling data to find the waker.
- Compare time spent in userspace functions vs kernel (slices with \[k\] prefix).
- To detect a 'catch-up storm', look for threads with long gaps in thread_state/cpu_slice activity immediately followed by a high-density burst.
- Quantify scheduler contention by calculating scheduling latency (measure duration of preceding 'Runnable' state using preceding_sched_slice_for_thread). Search for maximums and high percentiles (p95/p99).
- If a task exhibits high scheduling latency, check if other CPUs were idle (running swapper or idle thread).
- Check the cpu_id for key threads; if consistently scheduled on slower cores, it signals a potential performance gain by allowing them on big cores.
- For a struggling thread, analyze 'Runnable' vs 'Running' state time. A large 'Runnable' time indicates CPU contention.
- If a slice's wall duration increases but the percentage of 'Running' time is unchanged, it strongly suggests a lower CPU frequency. Check sched_switch to focus on the correct cores.
@@ -1,11 +0,0 @@
- When investigating UI jank, check for long-running slices on the main thread; if a slice like "ConstraintLayout.onMeasure" is taking a long time (e.g., \>8ms), it is a likely cause of the jank.
- When a bitmap_write_to_parcel slice takes milliseconds instead of microseconds, investigate its children slices to see how time is spent. Long durations often point to suboptimal kernel operations like memory mapping (mmap) or unnecessary data zeroing.
- For a high-level overview of graphics memory usage, track the 'gpu_mem_total' counter for a specific process (upid).
- To find the largest graphics allocations, query the android_graphics_allocs table and sort by size_bytes in descending order. Compare its 'width' and 'height' against the device's display resolution.
- To detect the "double memory cost" of an image existing on both CPU and GPU, look for a large buffer in android_graphics_allocs and a simultaneous CPU memory allocation of a similar size (rss_anon_bytes or heap_graph).
- To find potentially costly intermediate render targets, look for large buffers in android_graphics_allocs where 'usage_bits' lack a 'COMPOSER_OVERLAY' flag.
- To check if an allocation is actually presented on screen, correlate 'buffer_id' with SurfaceFlinger events.
- To see if high graphics memory is causing performance issues, check the frame duration in 'actual_frame_timeline_frame'. Durations over vsync (e.g., 16.6ms) correlating with 'gpu_mem_total' spikes suggest memory pressure jank.
- Within the main thread of a janky graphics application, look for frequent or long-running 'texture_upload' slices.
- Analyze the duration of 'eglSwapBuffersWithDamageKHR' or similar buffer-swapping slices in the graphics rendering thread. Consistently long durations suggest a large "damage area" being redrawn every frame.
- To identify UI jank, compare the 'actual_frame_timeline' against the 'expected_frame_timeline' on the main process; a significant deviation indicates missed frames.
@@ -1,12 +0,0 @@
- When debugging a long, uninterruptible sleep state related to I/O: Check for overlapping slices with "verity" in their name (dm-verity).
- When a thread is stuck in an uninterruptible sleep with no blocked_function, look for other threads that might be holding the memory lock (e.g., "jit-thread-pool", memory mapping ops).
- When analyzing a long uninterruptible sleep, check the "blocked_function" in the thread state details (from sched_blocked_reason ftrace event).
- A lot of time spent in "do_page_fault" during app startup is a strong indicator of I/O contention.
- For file integrity mechanisms like DM-Verity, search for events like dm_verity_fec_prefetch.
- To find the specific kernel dependency of a stalled app thread, locate the thread in state 'D', then look for kworker or kernel threads that become runnable immediately after.
- For app stalls caused by I/O, analyze the scheduling latency of the relevant kworker threads handling the request.
- To find inefficient file I/O, query the syscall table for a high frequency of small, sequential read() or pread() syscalls on a single fd.
- If a thread spends significant time in 'Uninterruptible Sleep', check if 'blocked_function' is 'page_cache_readahead'. Correlate waking timestamps with 'filemap_add_to_page_cache' ftrace events.
- Aggregate counts of 'filemap_add_to_page_cache' grouping by 'inode' to find the specific file causing I/O pressure.
- Inspect 'nr_sector' in 'block_rq_issue' ftrace events to understand file read-ahead size.
- If an I/O issue disappears on subsequent launches, it's a 'cold start' problem (populating page cache).
@@ -1,10 +0,0 @@
- Look for multiple outbound binder transactions from the same process (system_server) that carry similar data to different destinations in a short time frame. This "binder storm" indicates a lack of multiplexing.
- To trace data across processes, correlate slices using flow events by linking a slice's ID to flow.source_slice_id or flow.dest_slice_id.
- To detect binder spam, query the binder_transaction table and group by thread ID (tid), service_name, and method_name to find high numbers of identical calls.
- When high binder concurrency is found, identify the bottleneck server process by grouping transactions by server_upid.
- To analyze the latency of a slow binder transaction, calculate the time spent outside the server by subtracting server_dur from the total dur in the binder_transaction table.
- When a thread is suspected of binder spam, correlate its tid with the cpu_slice table to check for high CPU consumption.
- To find the code responsible for binder spam, get the utid of the problematic thread and use it to query stack_profile_callsite.
- To find the callers of a problematic function, filter stack_profile_callsite for frames mapping to it, then trace upwards using parent_id.
- A long-running slice on one thread causally linked to a slice on another thread (e.g., binder from system_server to SystemUI) indicates a scheduling dependency bottleneck.
- To find asynchronous operations that might cause UI jank, look for a binder transaction from a controlling process that returns quickly, followed by a long-running slice in the receiving process.
@@ -1,15 +0,0 @@
- To investigate low memory kills, look for the "lmk_kill_occurred" ftrace event; a high count indicates severe memory pressure.
- When you see a burst of "lmk_kill_occurred" events, query for processes with high CPU wall_duration in the "sched_slice" table during the same time window. Runaway processes consuming CPU exacerbate memory pressure.
- To understand a process's memory impact, inspect its "anon_rss" (Anonymous Resident Set Size) from memory counters like "mem.info".
- Check system-wide memory stats for a high or rapidly increasing "swap_used" value.
- To confirm memory thrashing, look for high CPU usage by the "kswapd" kernel thread.
- To find the direct trigger for LMKD, look for "memory_pressure" trace events from Pressure Stall Information (PSI).
- To detect 'lost' memory for processes using hardware accelerators (like a TPU), query the counter table for RssFile values. If it drops significantly while hardware is active, check for kswapd0 scheduling slices.
- When analyzing memory shared with hardware, query the dma_heap_stat and dmabuf_total_size counters (or ion_total_size for older devices) for a more accurate picture than RSS.
- Be aware that RssFile can over-report memory if the same physical page is mapped multiple times.
- When investigating OOMs, establish a baseline by querying process memory counters (e.g., mem.rss) and compare the median and 95th percentile against its history.
- If bitmaps are a major memory consumer, check for outliers in bitmap count by querying android.graphics.Bitmap instances across traces.
- To find what is holding onto an object (like a bitmap), trace its retainer path back to a GC root by querying heap_graph_reference. Pay close attention to custom application classes.
- Query the android_bitmaps table to check the width and height properties of bitmaps.
- Check for the presence of software bitmaps, which consume memory on both the app heap and in graphics memory.
- When analyzing bitmaps, look for duplicates by checking for multiple android.graphics.Bitmap objects with identical properties.
@@ -1,7 +0,0 @@
- When investigating overall battery drain, start by querying the power_rails track and summing the energy consumed (power_ma \* duration) for each rail name.
- To check if a device is sleeping correctly during screen-off periods, query the suspend_state track; a lack of time in "suspended" state indicates a wakefulness problem.
- To find the root cause of the device failing to suspend, query the kernel_wakelock track and aggregate the total duration for each wake lock name.
- If a top kernel wake lock name contains "bt_" or "bcm" (e.g., bt_host_wake), cross-reference its timing with events in the bluetooth_scan_results track.
- If the modem rail in power_rails shows high consumption, query the network_packets table and aggregate traffic volume by uid to identify the app.
- When analyzing network traffic from a shared uid, use the socket_tag associated with network packets for granular attribution.
- Convert impact into a common energy unit like milliwatt-hours (mWh) to compare the severity of different issues.
File diff suppressed because it is too large Load Diff
@@ -1,122 +0,0 @@
## Guidelines and Hints
- **Idempotency:** Ensure queries are idempotent to prevent "already exists" errors during multiple executions.
- For Perfetto objects, always use `CREATE OR REPLACE`: `CREATE OR REPLACE
PERFETTO TABLE`, `CREATE OR REPLACE PERFETTO VIEW`, `CREATE OR REPLACE
PERFETTO FUNCTION`, `CREATE OR REPLACE PERFETTO MACRO`.
- For SQLite Virtual Tables (such as `SPAN_JOIN`), `CREATE OR REPLACE` is not supported. Explicitly drop them first: `DROP TABLE IF EXISTS
my_table; CREATE VIRTUAL TABLE my_table USING SPAN_JOIN(...);`
- For standard SQLite indexes, prepend `DROP INDEX IF EXISTS index_name;`.
- `SPAN_JOIN` will crash if intervals **within the same input table** overlap. Always use the `PARTITIONED {column}` (for example, `PARTITIONED upid`) clause to isolate intervals.
- Intermediate tables fed into a `SPAN_JOIN` must be materialized using `CREATE PERFETTO TABLE`, not `CREATE VIEW`.
- **Trace Boundaries (`dur = -1`):** Slices or thread states that don't finish before the trace ends are recorded with `dur = -1`. When calculating a bounding box (for example, `ts + dur`) or summing durations (`SUM(dur)`), handle incomplete durations using: `IIF(dur = -1, trace_end() - ts, dur)`.
- **Robust State Transitions:** Avoid manual timestamp arithmetic (for example, `ts + dur = next.ts`) to join adjacent events. Rely on standard library modules (for example, `sched.runnable`, `linux.perf.counters`, `intervals.overlap`) which safely handle trace gaps and preemptions.
- **Unique Identifiers:** When writing SQL queries in Perfetto, you must join tables using `utid` (unique thread ID) or `upid` (unique process ID) instead of the regular `tid` or `pid`. **Why it's useful** : The operating system recycles `TIDs` and `PIDs`, while `UTIDs` and `UPIDs` remain unique for the lifetime of the trace, which prevents incorrect joins.
- **Safe Argument Extraction:** Use `EXTRACT_ARG(arg_set_id, 'key')` to extract dictionary or JSON-like properties from slices or tracks. Don't attempt string parsing.
- **String Matching (Always use GLOB):** Use `GLOB` instead of `LIKE`. `LIKE` causes performance bottlenecks and treats underscores (`_`) as wildcards, leading to bugs.
- **Exact matches:** Use `=`.
- **Substring matches:** Use `GLOB` with `*` (for example, `name GLOB
'*RenderThread*'`).
- **Case-insensitive matches:** Use `LOWER(name) GLOB` and make sure the search string is fully lowercase (for example, `LOWER(name) GLOB
'*renderthread*'`). Use this when dealing with inconsistent trace capitalization (for example, `WakeLock` versus `wakelock`).
- **Calculating Time Overlaps:** To calculate the overlap duration between two
time intervals `[start1, end1]` and `[start2, end2]`:
> **Precedence Rule:** Always prefer using `SPAN_JOIN` or standard library
> functions (for example, `intervals.overlap`) to calculate overlaps
> **between two different sets of intervals** . Avoid manual arithmetic if a
> standard library feature or `SPAN_JOIN` can achieve the same result. Use
> the following logic if no built-in alternative exists.
1. **Condition:** The intervals overlap if `start1 < end2` and `start2 <
end1`.
2. **Duration:** The overlap duration is calculated as `MIN(end1, end2) -
MAX(start1, start2)`
> **Important:** Incomplete Perfetto slices have a duration of -1
> (`dur = -1`). Always calculate the effective end time using `ts +
> IIF(dur = -1, trace_end() - ts, dur)` before applying this logic.
- Query `android_thread_slices_for_all_startups` for app startup requests.
- Join `counter_track` with `counter` to get values of counter with a specific
name.
- When querying for a CPU frequency counter, include the `linux.cpu.frequency`
module and use the `cpu_frequency_counters` table.
- When looking for events around a specific timestamp, start with 100ms as the
window size.
- Always prefix column names with table or view alias, that is:
`{alias}.{column_name}`.
- To calculate the total time spent in slices matching a specific name pattern
(for example, `*{name_pattern}*`), you must sum their durations. **Why it's
useful** : This helps quantify the total impact of a specific function or
feature on performance across multiple calls. Here is an example query (note
the safe handling of incomplete slices): `sql SELECT count(*) as
total_count, sum(IIF(slice.dur = -1, trace_end() - slice.ts, slice.dur)) /
1000000.0 as total_dur_ms FROM slice WHERE slice.name GLOB
'*{name_pattern}*';`
## Resources
- **Documentation:** The Perfetto Standard Library documentation is in [`perfetto-stdlib.md`](perfetto-stdlib.md). Use this file as a reference to discover available modules, find schemas (columns and types) for specific tables or views, or determine the `INCLUDE PERFETTO MODULE` statements required before drafting SQL query.
- **Execution Tool:** Queries are executed using the official `trace_processor` wrapper script downloaded directly from Perfetto. Output is returned in pure CSV format.
## Execution Protocol
You must follow these steps sequentially, mirroring a multi-agent pipeline:
### Step 0: Tool Setup
**Fetch the Wrapper:** You must use the top level of the current project workspace (`./trace_processor`).
> **CRITICAL GUARDRAIL:** NEVER use filesystem search tools (`find`, `find_by_name`, `grep`, `dir /s`, `Get-ChildItem`) across the home directory or workspace to locate `trace_processor` — unconstrained searches across entire workspaces will stop responding or time out.
Perform a direct file check at the top level of your workspace (e.g., `ls trace_processor`). If missing, download `https://get.perfetto.dev/trace_processor` directly into the root workspace (`curl -LO`), make it executable on macOS/Linux (`chmod +x`), and ensure `trace_processor` is added to `.gitignore`. Execute queries directly via `./trace_processor` (on Windows, explicitly invoke `python trace_processor`).
> **Important:** The file served at this URL is a `~10KB` Python wrapper script. Don't assume the download failed because it is human-readable text. This is the intended behavior. This script handles lazy-loading the precompiled binary automatically on its first run. Use it directly.
### Step 1: Dissection and Schema Research
1. Identify the core question, required data points, and filtering conditions.
2. **Precedence Rule:** If the user's request contains a SQL query, use it **without modification** and skip to Step 2 for validation.
3. **Mandatory Schema and Module Search:** For every table or view you plan to use, you MUST find its schema in [`perfetto-stdlib.md`](perfetto-stdlib.md). **Don't read the entire documentation file** --- it consumes the context window. Follow this precise workflow:
- **Discovery and Search:** Use available search tools (`grep`, `read_file` or file search) with line limits to discover relevant views, tables or modules based on your problem domain and high-level intents (for example, 'CPU time', 'running time', 'overlap', 'jank').
- **Why:** Searching solely for exact table names misses comprehensive, pre-computed views built for these analyses.
- **Note:** You must verify if a Standard Library module already provides the needed abstraction before drafting manual arithmetic or custom functions.
- **Targeted Bounded Reads:** Once you identify the relevant modules, efficiently read the tables and views within that module section.
- **Extract:** Extract only the schema, columns, and the exact `INCLUDE
PERFETTO MODULE` statements for the required object from the documentation.
- **Verify:** Review the columns, types, and descriptions to ensure the table matches your needs.
4. Print the research results before drafting the query:
5. *Tables/Views:* `Schema for {name}:` listing columns and types.
### Step 2: Draft and Validate Loop (Max 3 Iterations)
Draft the SQL query in SQLite syntax using **only** the schemas retrieved in
Step 1. After drafting, you must validate against this checklist:
- \[ \] **SQLite Syntax:** Does the query parse successfully without syntax errors?
- \[ \] **Idempotency:** Are all object creations safe to re-run? (Did you use `CREATE OR REPLACE PERFETTO` and `DROP TABLE IF EXISTS` for virtual tables?)
- \[ \] **Existence:** Were all tables found in the documentation?
- \[ \] **Intent Check:** Is there a pre-existing standard library table or view that will fulfill this intent before instead of writing manual arithmetic?
- \[ \] **Column Accuracy:** Do columns match the retrieved schemas?
- \[ \] **Alias Check:** Are ALL column names prefixed with their table or view alias (for example, `alias.column_name`)?
- \[ \] **Module Check:** Are `INCLUDE PERFETTO MODULE` statements included for all non-prelude modules? **You must use the exact module names provided in
the documentation.**
- \[ \] **Span Join Check:** If using `SPAN_JOIN`, are tables safely `PARTITIONED` to prevent overlapping interval crashes? Are intermediate tables materialized with `CREATE PERFETTO TABLE`?
- \[ \] **No LIKE Constraint:** Did you map string matches using `GLOB` or `=` instead of prohibited `LIKE`?
- \[ \] **Execution Check:** You MUST run queries using the standalone
`./trace_processor` wrapper with the `--query-string` flag:
`./trace_processor --query-string "QUERY" {trace_file}`.
**Execution Rules:**
- **File Usage** : If you must create a SQL file to execute queries (for example, due to query length or escaping issues), you must create them in the `/tmp/` directory.
- **State:** The execution is purely ephemeral. Database state does not persist across turns. You **cannot** share state (like views or tables) across queries in different turns. Every query must be standalone and fully self-contained.
- **Failure Resilience:** Debug and fix SQL syntax and logic errors when query fails.Don't simplify the analytical intent to pass validation. For example, if requested to calculate an overlap or intersection, you must fix the intersection math. Don't substitute with disjoint queries (for example, returning independent total durations) as a workaround.
### Step 3: Final Output
1. Explicitly return and state the final validated SQL and explain the results to the user.
2. Before finishing your response, delete all temporary SQL files you created in `/tmp/` directory.