Skip to content

Commit 76e9b72

Browse files
romtsnclaude
andcommitted
chore: Add btrace-perfetto skill for Perfetto trace capture
Workflow skill that automates capturing and comparing Perfetto traces using btrace 3.0 on Android devices. Includes a Perfetto UI viewer template with SQL query deep-linking via postMessage API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5726ea7 commit 76e9b72

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

.claude/skills/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
!create-java-pr/**
77
!test/
88
!test/**
9+
!btrace-perfetto/
10+
!btrace-perfetto/**
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
---
2+
name: btrace-perfetto
3+
description: Capture and compare Perfetto traces using btrace 3.0 on an Android device. Use when asked to "profile", "capture trace", "perfetto trace", "btrace", "compare traces", "record perfetto", "trace touch events", "measure performance on device", or benchmark Android SDK changes between branches.
4+
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion
5+
argument-hint: "[branch1] [branch2] [duration] [sql-query]"
6+
---
7+
8+
# btrace Perfetto Trace Capture
9+
10+
Capture Perfetto traces with btrace 3.0 on a connected Android device, optionally comparing two branches. Opens results in Perfetto UI with a prefilled SQL query.
11+
12+
## Prerequisites
13+
14+
Before starting, verify:
15+
16+
1. **Connected device**: `adb devices` shows a device (Android 8.0+, 64-bit)
17+
2. **btrace CLI jar**: Check if `tools/btrace/rhea-trace-shell.jar` exists. If not, download it:
18+
```bash
19+
mkdir -p tools/btrace/traces
20+
curl -sL "https://repo1.maven.org/maven2/com/bytedance/btrace/rhea-trace-processor/3.0.0/rhea-trace-processor-3.0.0.jar" \
21+
-o tools/btrace/rhea-trace-shell.jar
22+
```
23+
3. **Device ABI**: Run `adb shell getprop ro.product.cpu.abi` — btrace only supports arm64-v8a and armeabi-v7a (no x86/x86_64)
24+
25+
## Step 1: Parse Arguments
26+
27+
| Argument | Default | Description |
28+
|----------|---------|-------------|
29+
| branch1 | current branch | First branch to trace |
30+
| branch2 | `main` | Second branch to compare against |
31+
| duration | `20` | Trace duration in seconds |
32+
| sql-query | see below | SQL query to prefill in Perfetto UI |
33+
34+
If no arguments are provided, ask the user what they want to trace and which branches to compare. If only one branch is given, capture only that branch (no comparison).
35+
36+
## Step 2: Integrate btrace into Sample App
37+
38+
The sample app is at `sentry-samples/sentry-samples-android/`.
39+
40+
### 2a: Add btrace dependency
41+
42+
In `sentry-samples/sentry-samples-android/build.gradle.kts`, add to the `dependencies` block:
43+
44+
```kotlin
45+
implementation("com.bytedance.btrace:rhea-inhouse:3.0.0")
46+
```
47+
48+
### 2b: Restrict ABI to device architecture
49+
50+
The btrace native library (shadowhook) does not support x86/x86_64. Replace the `ndk` abiFilters line in `defaultConfig` to match the connected device:
51+
52+
```kotlin
53+
ndk { abiFilters.addAll(listOf("arm64-v8a")) }
54+
```
55+
56+
Adjust if the device reports a different ABI.
57+
58+
### 2c: Initialize btrace in Application
59+
60+
In `MyApplication.java`, add `attachBaseContext`:
61+
62+
```java
63+
import android.content.Context;
64+
import com.bytedance.rheatrace.RheaTrace3;
65+
66+
// Add before onCreate:
67+
@Override
68+
protected void attachBaseContext(Context base) {
69+
super.attachBaseContext(base);
70+
RheaTrace3.init(base);
71+
}
72+
```
73+
74+
**Important**: The package is `com.bytedance.rheatrace`, not `com.bytedance.btrace`.
75+
76+
## Step 3: Build and Install
77+
78+
```bash
79+
./gradlew :sentry-samples:sentry-samples-android:installDebug
80+
```
81+
82+
## Step 4: Capture Trace
83+
84+
For each branch to trace:
85+
86+
### 4a: Set btrace properties and launch app
87+
88+
```bash
89+
adb shell setprop debug.rhea3.startWhenAppLaunch 1
90+
adb shell setprop debug.rhea3.waitTraceTimeout 60
91+
adb shell am force-stop io.sentry.samples.android
92+
sleep 1
93+
adb shell am start -n io.sentry.samples.android/.MainActivity
94+
```
95+
96+
The app must be started AFTER `debug.rhea3.startWhenAppLaunch` is set, otherwise the trace server won't initialize.
97+
98+
### 4b: Tell the user to interact with the app, then capture
99+
100+
```bash
101+
java -jar tools/btrace/rhea-trace-shell.jar \
102+
-a io.sentry.samples.android \
103+
-t ${duration} \
104+
-waitTraceTimeout 60 \
105+
-o tools/btrace/traces/${branch_name}.pb \
106+
sched
107+
```
108+
109+
Do NOT use the `-r` flag — it fails to resolve the launcher activity because LeakCanary registers a second one. Launch the app manually in step 4a instead.
110+
111+
### 4c: Switch branches for comparison
112+
113+
When capturing a second branch:
114+
115+
1. Stash the btrace integration changes:
116+
```bash
117+
git stash push -m "btrace integration" -- \
118+
sentry-samples/sentry-samples-android/build.gradle.kts \
119+
sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java
120+
```
121+
2. Checkout the other branch
122+
3. Pop the stash: `git stash pop`
123+
4. Rebuild and install: `./gradlew :sentry-samples:sentry-samples-android:installDebug`
124+
5. Repeat steps 4a and 4b with a different output filename
125+
6. Switch back to the original branch and restore files
126+
127+
## Step 5: Open in Perfetto UI
128+
129+
Generate a viewer HTML and serve it locally. Use the template at `assets/viewer-template.html` as a base — copy it to `tools/btrace/traces/viewer.html` and replace the placeholder values:
130+
131+
- `TRACE_FILES`: array of `{file, title}` objects for each captured trace
132+
- `SQL_QUERY`: the SQL query to prefill
133+
134+
The SQL query is passed via the URL hash parameter: `https://ui.perfetto.dev/#!/?query=...`
135+
136+
The trace data is sent via the postMessage API (required for local files — URL deep-linking does not work with `file://`).
137+
138+
Start a local HTTP server and open the viewer:
139+
140+
```bash
141+
cd tools/btrace/traces && python3 -m http.server 8008 &
142+
open http://localhost:8008/viewer.html
143+
```
144+
145+
### Default SQL Query
146+
147+
If no custom query is provided, use:
148+
149+
```sql
150+
SELECT
151+
s.name AS slice_name,
152+
s.dur / 1e6 AS dur_ms,
153+
s.ts,
154+
t.name AS track_name
155+
FROM slice s
156+
JOIN thread_track t ON s.track_id = t.id
157+
WHERE s.name GLOB '*SentryWindowCallback.dispatch*'
158+
ORDER BY s.ts
159+
```
160+
161+
## Cleanup
162+
163+
After tracing is complete, remind the user that the btrace integration changes to the sample app should NOT be committed. The `tools/btrace/` directory is gitignored.
164+
165+
## Troubleshooting
166+
167+
| Problem | Solution |
168+
|---------|----------|
169+
| `No compatible library found [shadowhook]` | Restrict `ndk.abiFilters` to arm64-v8a only |
170+
| `package com.bytedance.btrace does not exist` | Use `com.bytedance.rheatrace` (not `btrace`) |
171+
| `ResolverActivity does not exist` with `-r` flag | Don't use `-r`; launch the app manually before capturing |
172+
| `wait for trace ready timeout` on download | Set `debug.rhea3.startWhenAppLaunch=1` BEFORE launching the app, and use `-waitTraceTimeout 60` |
173+
| Empty jar file (0 bytes) | Download from Maven Central (`repo1.maven.org`), not `oss.sonatype.org` |
174+
| `FileNotFoundException` on sampling download | App was already running when properties were set; force-stop and relaunch |
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head><title>btrace Trace Viewer</title></head>
4+
<body>
5+
<h2>Perfetto Trace Viewer</h2>
6+
<div id="buttons"><!-- BUTTONS_PLACEHOLDER --></div>
7+
<p id="status"></p>
8+
<script>
9+
// Replace these values when copying the template
10+
var TRACE_FILES = [/* TRACE_FILES_PLACEHOLDER */];
11+
var SQL_QUERY = `SQL_QUERY_PLACEHOLDER`;
12+
13+
var container = document.getElementById('buttons');
14+
TRACE_FILES.forEach(function(t) {
15+
var btn = document.createElement('button');
16+
btn.textContent = 'Open ' + t.title;
17+
btn.style.marginRight = '8px';
18+
btn.onclick = function() { openTrace(t.file, t.title); };
19+
container.appendChild(btn);
20+
});
21+
22+
function openTrace(file, title) {
23+
var status = document.getElementById('status');
24+
status.textContent = 'Opening Perfetto UI...';
25+
var url = 'https://ui.perfetto.dev/#!/?query=' + encodeURIComponent(SQL_QUERY);
26+
var w = window.open(url);
27+
var ping = setInterval(function() { w.postMessage('PING', '*'); }, 100);
28+
window.onmessage = function(e) {
29+
if (e.data === 'PONG') {
30+
clearInterval(ping);
31+
status.textContent = 'Loading trace: ' + file;
32+
fetch(file)
33+
.then(function(r) { return r.arrayBuffer(); })
34+
.then(function(buf) {
35+
w.postMessage({
36+
perfetto: {
37+
buffer: buf,
38+
title: title,
39+
fileName: file
40+
}
41+
}, '*');
42+
status.textContent = 'Trace sent to Perfetto UI!';
43+
});
44+
}
45+
};
46+
}
47+
</script>
48+
</body>
49+
</html>

0 commit comments

Comments
 (0)