Skip to content

Commit ede9293

Browse files
committed
Merge branch 'main' into feat/spring-boot-matrix-auto-update
2 parents 2b77d4e + ca6b6d8 commit ede9293

975 files changed

Lines changed: 69773 additions & 6482 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../.claude/skills

.claude/settings.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(find:*)",
5+
"Bash(ls:*)",
6+
"Bash(git:*)",
7+
"Bash(git status:*)",
8+
"Bash(git log:*)",
9+
"Bash(git diff:*)",
10+
"Bash(git show:*)",
11+
"Bash(git branch:*)",
12+
"Bash(git remote:*)",
13+
"Bash(git tag:*)",
14+
"Bash(git stash list:*)",
15+
"Bash(git rev-parse:*)",
16+
"Bash(gh pr view:*)",
17+
"Bash(gh pr list:*)",
18+
"Bash(gh pr checks:*)",
19+
"Bash(gh pr diff:*)",
20+
"Bash(gh issue view:*)",
21+
"Bash(gh issue list:*)",
22+
"Bash(gh run view:*)",
23+
"Bash(gh run list:*)",
24+
"Bash(gh run logs:*)",
25+
"Bash(gh repo view:*)",
26+
"WebFetch(domain:github.com)",
27+
"WebFetch(domain:docs.sentry.io)",
28+
"WebFetch(domain:develop.sentry.dev)",
29+
"Bash(grep:*)",
30+
"Bash(mv:*)"
31+
],
32+
"deny": []
33+
}
34+
}

.claude/skills/.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Ignore dotagents-managed skills (synced from agents.toml)
2+
*
3+
# Keep custom repo-specific skills
4+
!.gitignore
5+
!create-java-pr/
6+
!create-java-pr/**
7+
!test/
8+
!test/**
9+
!btrace-perfetto/
10+
!btrace-perfetto/**
11+
!check-code-attribution/
12+
!check-code-attribution/**
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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. After capture, query traces locally with `trace_processor` to compute comparison stats.
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. **Perfetto trace_processor**: Check if `/tmp/trace_processor` exists. If not, download it:
24+
```bash
25+
# Download trace_processor (--fail ensures HTTP errors don't leave a file behind)
26+
curl -sSL --fail "https://get.perfetto.dev/trace_processor" -o /tmp/trace_processor
27+
28+
# Verify magic bytes directly — file(1) output is too inconsistent across
29+
# versions/platforms to rely on for scripts or PIE binaries.
30+
magic=$(head -c 4 /tmp/trace_processor 2>/dev/null | od -An -vtx1 -N4 | tr -d ' \n')
31+
case "$magic" in
32+
2321*) ;; # #! shebang (script)
33+
7f454c46) ;; # ELF (Linux)
34+
cffaedfe|cefaedfe|feedfacf|feedface) ;; # Mach-O (macOS)
35+
cafebabe) ;; # Mach-O universal
36+
*)
37+
echo "Error: Downloaded file is not a valid script or executable (magic: ${magic:-empty})"
38+
rm -f /tmp/trace_processor
39+
exit 1
40+
;;
41+
esac
42+
43+
# Make executable only after verification
44+
chmod +x /tmp/trace_processor
45+
```
46+
4. **Device ABI**: Run `adb shell getprop ro.product.cpu.abi` — btrace only supports arm64-v8a and armeabi-v7a (no x86/x86_64)
47+
48+
## Step 1: Parse Arguments
49+
50+
| Argument | Default | Description |
51+
|----------|---------|-------------|
52+
| branch1 | current branch | First branch to trace |
53+
| branch2 | `main` | Second branch to compare against |
54+
| duration | `30` | Trace duration in seconds |
55+
| sql-query | see below | SQL query to prefill in Perfetto UI |
56+
57+
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).
58+
59+
## Step 2: Integrate btrace into Sample App
60+
61+
The sample app is at `sentry-samples/sentry-samples-android/`.
62+
63+
### 2a: Add btrace dependency
64+
65+
In `sentry-samples/sentry-samples-android/build.gradle.kts`, add to the `dependencies` block:
66+
67+
```kotlin
68+
implementation("com.bytedance.btrace:rhea-inhouse:3.0.0")
69+
```
70+
71+
### 2b: Restrict ABI to device architecture
72+
73+
The btrace native library (shadowhook) does not support x86/x86_64. Replace the `ndk` abiFilters line in `defaultConfig` to match the connected device:
74+
75+
```kotlin
76+
ndk { abiFilters.addAll(listOf("arm64-v8a")) }
77+
```
78+
79+
Adjust if the device reports a different ABI.
80+
81+
### 2c: Initialize btrace in Application
82+
83+
In `MyApplication.java`, add `attachBaseContext`:
84+
85+
```java
86+
import android.content.Context;
87+
import com.bytedance.rheatrace.RheaTrace3;
88+
89+
// Add before onCreate:
90+
@Override
91+
protected void attachBaseContext(Context base) {
92+
super.attachBaseContext(base);
93+
RheaTrace3.init(base);
94+
}
95+
```
96+
97+
**Important**: The package is `com.bytedance.rheatrace`, not `com.bytedance.btrace`.
98+
99+
### 2d: Add ProGuard keep rules (release builds only)
100+
101+
Only needed when building release. In `sentry-samples/sentry-samples-android/proguard-rules.pro`, add:
102+
103+
```
104+
-keep class com.bytedance.rheatrace.** { *; }
105+
-keepnames class io.sentry.** { *; }
106+
```
107+
108+
The first rule prevents R8 from stripping btrace's HTTP server classes (fails with `SocketException` otherwise). The second preserves Sentry class and method names so they appear readable in the Perfetto trace instead of obfuscated single-letter names.
109+
110+
## Step 3: Build and Install
111+
112+
Prefer **debug builds** — they provide richer tracing instrumentation (Handler, MessageQueue, Monitor:Lock slices visible) which is essential for comparing internal SDK behavior. Use the default 1kHz btrace sampling rate for debug builds.
113+
114+
```bash
115+
./gradlew :sentry-samples:sentry-samples-android:installDebug
116+
```
117+
118+
**Release builds** are useful when you need to measure real-world performance without StrictMode/debuggable overhead or with R8 optimizations. Require the ProGuard keep rules from step 2d. Use `-sampleInterval 333000` (333μs / 3kHz) for finer granularity since release code runs faster.
119+
120+
```bash
121+
./gradlew :sentry-samples:sentry-samples-android:installRelease
122+
```
123+
124+
## Step 4: Capture Trace
125+
126+
For each branch to trace:
127+
128+
### 4a: Set btrace properties and launch app
129+
130+
Clear any stale port files, set properties, and launch:
131+
132+
```bash
133+
adb shell "rm -rf /storage/emulated/0/Android/data/io.sentry.samples.android/files/rhea-port"
134+
adb shell setprop debug.rhea3.startWhenAppLaunch 1
135+
adb shell setprop debug.rhea3.waitTraceTimeout 60
136+
adb shell am force-stop io.sentry.samples.android
137+
sleep 2
138+
adb shell am start -n io.sentry.samples.android/.MainActivity
139+
sleep 5
140+
```
141+
142+
The app must be started AFTER `debug.rhea3.startWhenAppLaunch` is set, otherwise the trace server won't initialize. The 5s sleep after launch gives the btrace HTTP server time to start.
143+
144+
### 4b: Play a sound to signal the user, then capture
145+
146+
Play a sound when tracing actually starts so the user knows to begin interacting. Pipe btrace output through a loop that triggers the sound on the "start tracing" line:
147+
148+
```bash
149+
java -jar tools/btrace/rhea-trace-shell.jar \
150+
-a io.sentry.samples.android \
151+
-t ${duration} \
152+
-waitTraceTimeout 60 \
153+
-o tools/btrace/traces/${branch_name}.pb \
154+
sched 2>&1 | while IFS= read -r line; do
155+
echo "$line"
156+
if [[ "$line" == *"start tracing"* ]]; then
157+
afplay -v 1.5 /System/Library/Sounds/Ping.aiff &
158+
fi
159+
done
160+
```
161+
162+
For release builds with finer sampling, add `-sampleInterval 333000`.
163+
164+
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.
165+
166+
### 4c: Switch branches for comparison
167+
168+
When capturing a second branch:
169+
170+
1. Stash the btrace integration changes:
171+
```bash
172+
git stash push -m "btrace integration" -- \
173+
sentry-samples/sentry-samples-android/build.gradle.kts \
174+
sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java \
175+
sentry-samples/sentry-samples-android/proguard-rules.pro
176+
```
177+
2. Checkout the other branch
178+
3. Pop the stash: `git stash pop`
179+
4. Rebuild and install (same variant — debug or release — as the first branch)
180+
5. Repeat steps 4a and 4b with a different output filename
181+
6. Switch back to the original branch and restore files
182+
183+
## Step 5: Open in Perfetto UI
184+
185+
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:
186+
187+
- `TRACE_FILES`: array of `{file, title}` objects for each captured trace
188+
- `SQL_QUERY`: the SQL query to prefill
189+
190+
The SQL query is passed via the URL hash parameter: `https://ui.perfetto.dev/#!/?query=...`
191+
192+
The trace data is sent via the postMessage API (required for local files — URL deep-linking does not work with `file://`).
193+
194+
Start a local HTTP server and open the viewer:
195+
196+
```bash
197+
cd tools/btrace/traces && python3 -m http.server 8008 &
198+
open http://localhost:8008/viewer.html
199+
```
200+
201+
### Default SQL Query
202+
203+
If no custom query is provided, use:
204+
205+
```sql
206+
SELECT
207+
s.name AS slice_name,
208+
s.dur / 1e6 AS dur_ms,
209+
s.ts,
210+
t.name AS track_name
211+
FROM slice s
212+
JOIN thread_track t ON s.track_id = t.id
213+
WHERE s.name GLOB '*SentryWindowCallback.dispatch*'
214+
ORDER BY s.ts
215+
```
216+
217+
## Step 6: Query and Compare Traces
218+
219+
After capturing both branches, use `trace_processor` to compute comparison stats locally.
220+
221+
### Basic stats query
222+
223+
For each trace file, run:
224+
225+
```bash
226+
/tmp/trace_processor -Q "
227+
WITH events AS (
228+
SELECT s.dur / 1e6 as dur_ms FROM slice s
229+
WHERE s.name GLOB '*${METHOD_GLOB}*' AND s.dur > 0
230+
ORDER BY s.dur
231+
)
232+
SELECT COUNT(*) as count,
233+
ROUND(AVG(dur_ms), 4) as avg_ms,
234+
ROUND((SELECT dur_ms FROM events LIMIT 1 OFFSET (SELECT COUNT(*)/2 FROM events)), 4) as median_ms,
235+
ROUND(MIN(dur_ms), 4) as min_ms,
236+
ROUND(MAX(dur_ms), 4) as max_ms
237+
FROM events
238+
" tools/btrace/traces/${trace_file}.pb
239+
```
240+
241+
Replace `${METHOD_GLOB}` with the method pattern to compare (e.g. `SentryGestureDetector.onTouchEvent`, `SentryWindowCallback.dispatchTouchEvent`).
242+
243+
### Finding child calls (debug builds)
244+
245+
To find what happens inside a method (e.g. Handler calls, lock acquisitions):
246+
247+
```bash
248+
/tmp/trace_processor -Q "
249+
WITH RECURSIVE descendants(id, depth) AS (
250+
SELECT s.id, 0 FROM slice s WHERE s.name GLOB '*${PARENT_METHOD}*'
251+
UNION ALL
252+
SELECT s.id, d.depth + 1 FROM slice s JOIN descendants d ON s.parent_id = d.id WHERE d.depth < 10
253+
)
254+
SELECT s.name, COUNT(*) as count, ROUND(AVG(s.dur / 1e6), 3) as avg_ms
255+
FROM slice s JOIN descendants d ON s.id = d.id
256+
WHERE d.depth > 0
257+
GROUP BY s.name ORDER BY count DESC
258+
LIMIT 20
259+
" tools/btrace/traces/${trace_file}.pb
260+
```
261+
262+
### Build the comparison table
263+
264+
Run the stats query on both trace files, then present a markdown table:
265+
266+
```
267+
| Metric | Branch A | Branch B | Delta |
268+
|--------|----------|----------|-------|
269+
| Count | ... | ... | |
270+
| Average| ... | ... | -X% |
271+
| Median | ... | ... | -X% |
272+
| Max | ... | ... | -X% |
273+
```
274+
275+
Compute delta as `(branchA - branchB) / branchB * 100`. Negative means branch A is faster.
276+
277+
### Sampling rate reference
278+
279+
| Rate | Interval | `-sampleInterval` | Use case |
280+
|------|----------|-------------------|----------|
281+
| 1 kHz | 1ms | `1000000` (default) | Debug builds, general profiling |
282+
| 3 kHz | 333μs | `333000` | Release builds, finer granularity |
283+
| 10 kHz | 100μs | `100000` | Maximum detail, higher overhead |
284+
285+
Higher sampling rates capture shorter method calls but add CPU overhead which can skew results. For most comparisons, the default 1kHz is sufficient.
286+
287+
## Cleanup
288+
289+
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.
290+
291+
## Troubleshooting
292+
293+
| Problem | Solution |
294+
|---------|----------|
295+
| `No compatible library found [shadowhook]` | Restrict `ndk.abiFilters` to arm64-v8a only |
296+
| `package com.bytedance.btrace does not exist` | Use `com.bytedance.rheatrace` (not `btrace`) |
297+
| `ResolverActivity does not exist` with `-r` flag | Don't use `-r`; launch the app manually before capturing |
298+
| `wait for trace ready timeout` on download | Set `debug.rhea3.startWhenAppLaunch=1` BEFORE launching the app, and use `-waitTraceTimeout 60` |
299+
| Empty jar file (0 bytes) | Download from Maven Central (`repo1.maven.org`), not `oss.sonatype.org` |
300+
| `FileNotFoundException` on sampling download | App was already running when properties were set; force-stop and relaunch |
301+
| `SocketException: Unexpected end of file` in release builds | R8 stripped btrace classes; add `-keep class com.bytedance.rheatrace.** { *; }` to proguard-rules.pro |
302+
| Stale port from previous session | Run `adb shell "rm -rf /storage/emulated/0/Android/data/io.sentry.samples.android/files/rhea-port"` before launching |
303+
| Most `onTouchEvent` durations are 0ms | Increase sampling rate with `-sampleInterval 333000` (3kHz) |

0 commit comments

Comments
 (0)