# btrace (RheaTrace): a Perfetto-based tracer for Android, iOS and HarmonyOS

> btrace 3.0 pairs a synchronized sampling tracer with a command-line capture tool and hands you a Perfetto trace. It is aimed at engineers chasing startup and frame-time problems on real devices, and it comes with hard platform limits worth knowing before you integrate it.

**bytedance/btrace** — 🔥🔥 btrace (AKA RheaTrace) is a high-performance Android & iOS tracing tool built on Perfetto. It not only times your methods but also reveals why they’re slow.

- Repository: https://github.com/bytedance/btrace
- Stars: 2,542 · Forks: 335
- Language: C++
- License: NOASSERTION
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/bytedance-btrace

## What btrace solves that a plain method timer does not

A stopwatch around a method tells you it took 120 ms. It does not tell you whether the thread was runnable but unscheduled, whether it was blocked on a lock, or whether the work happened at all during the window you care about. btrace's stated goal is to answer the second question: the README describes it as timing your methods and revealing why they are slow. That framing is the whole pitch, and it is why the tool is built on Perfetto rather than on a bespoke log format. The intended user is an Android or iOS performance engineer who already knows how to read a Perfetto timeline and wants their own app's methods sitting on the same timeline as CPU scheduling and atrace events. The 3.0 announcement describes a synchronized sampling-based tracing approach and adds iOS support plus an on-device SDK and command-line tooling for HarmonyOS. If you only need a single number for one function, this is more machinery than the job requires.

## How the Android capture pipeline actually flows

Three pieces cooperate. Inside the app, the rhea-inhouse artifact is initialized from attachBaseContext(), and the README notes that when the noop artifact is used instead, RheaTrace3.init() has an empty implementation. That pairing is the mechanism that lets you ship one build variant with tracing compiled in and another where the calls are inert. On the device, the tracer samples stack traces into a buffer whose default ceiling is 200000 entries; once full, older stack traces are overwritten, so a long recording silently trades history for recency. The default minimum interval between backtracing samples is 1000000 nanoseconds. On the host, rhea-trace-shell.jar talks to the device over adb, starts or attaches to the app, waits for the trace data to be written out, and pulls it down as a .pb file. The -mode flag selects what the device can contribute: perfetto is the default on Android 8.1 and above and adds system atrace and ftrace alongside app traces, while simple collects app traces only. The result is a single Perfetto protobuf opened at ui.perfetto.dev. Two knobs govern timing on the host side: -t sets the recording duration in seconds, and -waitTraceTimeout sets how long the tool waits for the write-out and pull, defaulting to 20 seconds.

## Installing btrace on Android and recording a first trace

Integration starts in Gradle. The README shows a conditional dependency so that the real tracer and the noop stand-in swap on a build flag, with the version pinned to 3.0.0 in both branches.

```groovy
dependencies {
    if (enable_btrace == 'true') {
        implementation 'com.bytedance.btrace:rhea-inhouse:3.0.0'
    } else {
        implementation 'com.bytedance.btrace:rhea-inhouse-noop:3.0.0'
    }
}
```

The flag itself lives in gradle.properties, and the README sets it to false by default so that ordinary builds carry the inert implementation.

```properties
enable_btrace=false
```

Initialization goes in attachBaseContext() of your Application class, before the rest of your startup code runs. The comment in the README makes the noop contract explicit: with rhea-inhouse-noop on the classpath, RheaTrace3.init() does nothing.

```java
public class MyApp extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        RheaTrace3.init(base);
    }
}
```

On the host you need adb plus Java and Python 3, per the usage steps. Install the APK that integrates btrace 3.0, download the shell jar, and run it from the directory where the jar sits. The README's example records ten seconds from a named package, writes output.pb, and asks for scheduling data with -r.

```bash
java -jar rhea-trace-shell.jar -a ${your_package_name} -t 10 -o output.pb -r
```

Open the resulting .pb at https://ui.perfetto.dev/. If you omit -t on macOS the README says an interactive tracing mode starts; on Windows the duration must be given because interactive mode is not supported there. For obfuscated builds, -m takes the ProGuard mapping file, and the README is explicit that this is not the btrace 2.0 methodMapping file, which no longer exists in 3.0.

On iOS the host tooling installs through Homebrew and Poetry before any recording happens, and the README gives these commands from the BTraceTool directory.

```bash
# using homebrew
brew install libusbmuxd
brew install poetry

# install from the BTraceTool directory
poetry install
```

After that you activate the virtual environment and run the recorder as a Python module, passing the bundle id and an optional time limit.

```bash
poetry shell
python3 -m btrace record -b ${bundle_id} -t 10
```

## Platform limits that decide whether btrace is usable for you

The known-issues table is unusually candid, and it should be read before any integration work. Devices must run Android 8.0 or higher. Java object allocation monitoring is not adapted for Android 15 and above, so on a modern test fleet you lose allocation detail; the README's advice is to use devices below Android 15 when you need that information. Devices without Perfetto, mostly systems before 8.1, cannot collect CPU scheduling and similar system data, and the suggested workaround is -mode simple, which by definition gives you app traces only. The fourth entry is the one most likely to stop a project outright: 32-bit devices or applications cannot collect tracing data at all, and the README asks for 64-bit applications on 64-bit devices. There is also an operational failure mode in the buffer design. With -maxAppTraceBufferSize at its default of 200000 and old stack traces overwritten when the limit is reached, a busy app recorded for a long window can lose the early part of the session. Shorter windows and deliberate reproduction steps fit this tool better than hour-long background recording.

## iOS and HarmonyOS: same idea, different plumbing

The iOS path does not use adb or a jar. You clone the source and add the BTrace pod with the Core and Debug subspecs from your local btrace-iOS path, alongside fishhook pulled from its Git repository on the main branch. The host tooling installs through Homebrew and Poetry, then runs as a Python module from the BTraceTool directory after you activate the virtual environment with poetry shell or poetry env activate. Recording is a subcommand with its own flags: -b for the bundle id, -o for output (defaulting to ~/Desktop/btrace when unset), -t for a time limit that defaults to 3600 seconds, and -d for a dSYM path. If only one device is connected to the Mac it is chosen automatically; with several, the tool prompts. One constraint stands out: unless -l is specified, the app must already be running before recording starts, so cold-start capture on iOS is a deliberate opt-in rather than the default. HarmonyOS is covered by the btrace-harmony directory and the README's mention of an on-device SDK and command-line tooling, but the excerpt here does not document its flags, so treat that platform as something to inspect in the repository before planning around it.

## How btrace differs from Perfetto's own command-line capture

The obvious alternative is Perfetto itself: run perfetto or record_android_trace, configure a trace config, and pull a protobuf. That gives you system-wide ftrace, atrace and scheduling data without touching your app's source at all. The difference is what lands on the timeline. Perfetto's own tooling cannot label your Kotlin or Java methods, because it has no view into your process beyond what atrace categories you instrument by hand. btrace adds the app-side layer: initialization in attachBaseContext(), a sampling tracer inside the process, and a host jar that drives the whole capture and produces a file that still opens in the same Perfetto UI. You are not choosing a different viewer, you are choosing to add app-level frames to the timeline. The cost is the integration surface: a Gradle dependency, an Application subclass change, a build flag, a versioned jar to keep current, and the platform limits listed above. If your question is purely about system scheduling under load, plain Perfetto is less work. If your question is which of your own methods is slow and what the system was doing at that moment, btrace is the layer that answers it.

## Licence, versioning and the cost of staying current

The repository carries a LICENSE file, and the README badge says Apache, but the repository metadata reports the licence as NOASSERTION, meaning the platform could not map the file to a standard identifier. Read LICENSE yourself before you depend on it; this is a description of what the files say, not legal advice. On maintenance, the last push was on 2026-06-09, which is also the date of the v3.1.0 release, so the project is not archived and the 3.0 line has had a follow-up. The upgrade cost is real but bounded. The Gradle coordinate is version-pinned in two places, the real artifact and the noop, and they must move together or your release build will diverge from your traced build. The shell jar is downloaded separately from a release URL and is not resolved by Gradle, so a version bump means re-downloading the jar and re-checking flag behaviour; the README already documents one breaking change of that kind, the removal of the btrace 2.0 methodMapping file in favour of the ProGuard mapping passed via -m. Before upgrading, re-read the known-issues table, because the Android 15 allocation gap and the 32-bit restriction are the kind of constraint that changes what your test matrix can cover.

## Conclusion

Adopt btrace if you already read Perfetto traces and need method-level timing plus system scheduling data from a real Android device, and if your team can keep the in-house artifact and the shell jar in step. Do not adopt it if you must support 32-bit builds, if your test fleet is mostly Android 15 or newer and you need object allocation detail, or if your product is HarmonyOS only and you have not yet checked what the btrace-harmony directory actually ships. Before integrating, verify three things on your own hardware: that the device is 64-bit and runs Android 8.0 or higher, that Perfetto is available on it so -mode perfetto is the default, and that the app package name you pass to -a matches the installed build. The 3.0 line depends on a Gradle coordinate published as rhea-inhouse, so confirm that artifact resolves from your repositories before you write any initialization code.

## FAQ

### What is btrace (RheaTrace)?

It is a tracing tool for Android, iOS and HarmonyOS apps built on Perfetto. The README describes it as timing your methods and revealing why they are slow, and version 3.0 adds iOS support and a synchronized sampling-based tracing approach.

### How do I install btrace in an Android app?

Add the rhea-inhouse dependency for traced builds and rhea-inhouse-noop otherwise, gate them on the enable_btrace flag in gradle.properties, and call RheaTrace3.init(base) from attachBaseContext(). With the noop artifact, that init call has an empty implementation.

### Which Android versions and devices does btrace support?

The known-issues table requires Android 8.0 or higher, and 32-bit devices or applications cannot collect tracing data at all, so you need 64-bit applications on 64-bit devices. Java object allocation monitoring is also not adapted for Android 15 and above.

### Where do I open the trace file btrace produces?

The README's usage steps say to open the generated trace file at https://ui.perfetto.dev/ for detailed analysis. The Android shell writes a .pb file whose path you set with the -o parameter.

### Can btrace record the app startup stage?

Yes. The -r parameter automatically restarts the app so that the start-up stage is traced. On iOS the equivalent behaviour is opt-in: unless -l is specified, the app must already have been launched before recording.

## Sources

- [bytedance/btrace on GitHub](https://github.com/bytedance/btrace)
- [Issues](https://github.com/bytedance/btrace/issues)
- [README](https://github.com/bytedance/btrace/blob/master/README.md)
- [Releases](https://github.com/bytedance/btrace/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bytedance-btrace
