To debug and speed up a smartphone you need tracing — a low-overhead recording of what the system did — but the fast tracers borrowed from servers give each CPU core (or each thread) its own small buffer, and because those buffers fill and overwrite independently, the moment you go back to look, the crucial clue has often already been overwritten; this paper (Huawei, Shanghai Jiao Tong University, and TU Dresden — Jiawei Wang, Haibo Chen, Hermann Hartig, and colleagues) presents BTrace, a mobile tracer that keeps the speed of per-core buffering while dividing one shared buffer into blocks it hands to whichever cores are busiest, so continuous traces survive long enough to be useful.
The argument in one glance
-
Problem
Fast phone tracing keeps overwriting the very clues you need
Tracing is how engineers understand what a running system actually did: the operating system and apps emit a stream of tiny records — this function ran, this lock was taken, this thread was scheduled — and a tracer stores them so they can be inspected later to find bugs or performance problems. On a smartphone, where the system is complex and the user experience is easily ruined by a stutter, good tracing is essential. The catch is speed. Recording events cannot slow the phone down, so tracers must add almost no overhead. The standard way to make a tracer fast, inherited from servers, is to distribute the storage: give each CPU core its own buffer (or each thread its own buffer), so that cores writing trace records never contend with each other for a shared lock. This works well for throughput, but it creates a subtle, damaging problem for actually using the trace. Each per-core buffer is small and independent, and when it fills up it overwrites its oldest records to make room for new ones. So by the time an engineer goes back to look for the moments leading up to a glitch — the crucial clues — those records have, with high probability, already been overwritten, because the buffer that held them kept churning. On a server this was tolerable; on a phone, where the interesting events are exactly the rare, bursty ones you were trying to catch, it drastically limits how useful tracing is.
-
What people did
Use per-core or per-thread buffers, as server tracers do
The prevailing tracers take the distributed route: partition the trace storage so each core, or each thread, writes into its own buffer with no shared lock. This is what fast tracing tools were built to do, because avoiding contention on a shared buffer is the obvious way to keep overhead low when many cores are emitting events at once.
-
Why it fell short
Distributed buffers trade away trace continuity for speed
The distributed design optimizes for the wrong thing in the smartphone setting. Because the buffers are per-core (or per-thread) and each is small, a busy core fills and overwrites its buffer quickly, so the window of history it retains is short. But smartphone problems are uneven: at any moment only some cores are doing the interesting work, and the events worth keeping are concentrated there and are bursty. Splitting the memory evenly across cores means the busy cores — the ones generating the clues — get no more room than idle cores, so their history is overwritten fastest, precisely where you least want to lose it. Meanwhile a naive alternative, a single shared global buffer that keeps one long continuous trace, would preserve that history but reintroduces exactly the contention the per-core design was avoiding: every core fighting over one lock, which kills the low-overhead property. So there is a genuine tension — per-core buffers give speed but lose continuity, and a shared buffer gives continuity but loses speed — and existing tools sit on the fast-but-forgetful side of it. What was missing was a design that keeps the low overhead of per-core buffering while retaining long, continuous traces where they matter.
-
This paper's idea
Split one shared buffer into blocks, and give them to the busiest cores
BTrace is a tracing framework designed for the smartphone setting, and its central idea, as described by the authors, is to get the best of both worlds by rethinking how the trace buffer is organized. Instead of statically giving each core its own fixed small buffer, or forcing all cores to share one contended buffer, BTrace partitions a single global buffer into many blocks and assigns those blocks dynamically to the cores that need them most. A core actively emitting a burst of trace records gets more blocks, so its slice of history is deeper and its records survive longer before being overwritten; a quiet core holds few or none, freeing that memory for the busy cores. Because a core writes into the block currently assigned to it, it still gets the low-overhead, contention-free writing of a per-core buffer for the common case, but the pool of blocks is shared, so the total memory follows the demand rather than being split evenly and wasted on idle cores. The effect is that continuous traces — the sequences of events leading up to a glitch — are retained far longer where the interesting activity actually is, without giving up the performance that made distributed buffering attractive in the first place. The authors report that BTrace has been deployed in production for smartphone tracing.
Why this problem is worth solving
Tracing is the primary way to diagnose the stability and smoothness problems that define a phone’s user experience, so a tracer that loses the evidence undermines the whole effort. Modern smartphones run an intricate stack — a large operating system, many concurrent apps and services, and tight real-time deadlines to keep the interface smooth. When something goes wrong (a janky scroll, a delayed tap response, an occasional freeze), the cause is usually a rare, timing-dependent interaction that is nearly impossible to reproduce on demand. Tracing is the main tool for catching such problems, because it records what actually happened as it happened, so engineers can reconstruct the sequence of events that led to the glitch. But this only works if the trace still contains those events when someone looks. On a phone, the events of interest are exactly the uncommon, bursty ones, and they may be examined only after a user report or a lab reproduction, possibly seconds or more after they occurred. A tracer that overwrites its history quickly will have discarded the evidence by then. So the value of solving this is direct: it determines whether tracing can actually catch the elusive problems that most hurt the user experience, rather than routinely losing them.
The difficulty is a real tension between two goods — speed and retention — that the standard design resolves in the wrong direction for phones. A tracer has two jobs that fight each other. It must be cheap, adding almost no overhead, because it runs alongside the workload it observes and any slowdown both distorts the measurement and hurts the user. And it must retain enough continuous history that the events leading up to a problem are still present when the trace is read. The cheap way to be fast is to remove contention by giving every core its own buffer, so writes never wait on a shared lock — but small independent buffers overwrite their history quickly, so retention suffers. The obvious way to retain long history is one big shared buffer, but then every core contends on it, so speed suffers. Neither pure design is right for a phone, where activity is uneven and bursty: the memory should flow to wherever the interesting events are being produced, not be split evenly regardless of demand, and it should do so without reintroducing global contention on the common write path. Reconciling these — the throughput of local buffers with the continuity of a shared one, adapting to where the load actually is — is the crux, and it is what makes a smartphone-specific tracer worth designing rather than reusing a server tracer as-is.
What people did before, and where it broke
- Give each core its own buffer (per-core tracing). Partition storage per CPU so writes never contend for a shared lock. Where it broke. Each per-core buffer is small and overwrites independently, so a busy core loses its recent history fastest — exactly where the clues are.
- Give each thread its own buffer (per-thread tracing). Partition per thread for the same low-contention reason. Where it broke. Same issue: small independent buffers churn quickly, so continuous traces of the interesting activity are lost with high probability.
- Use one shared global buffer for long continuous traces. Keep all events in a single buffer so history is preserved. Where it broke. Every core contends on that one buffer, reintroducing the overhead that the distributed designs were built to avoid.
- Just make the per-core buffers bigger. Enlarge each buffer to retain more history. Where it broke. Splitting more memory evenly still wastes it on idle cores while the busy cores remain starved, and phone memory is limited, so this does not target the demand where it actually is.
- The gap. No tracer combined the low overhead of per-core buffering with long, continuous trace retention concentrated where the load actually occurs.
How BTrace works
Turning evenly-split buffers into demand-driven shared blocks
-
-
new
-
new
-
new
-
new
-
new
The idea
Capture the trace without losing the events that matter. BTrace’s whole reason for existing is that a trace is worthless if the crucial records have been overwritten by the time you read it, so it reorganizes buffering to keep the important history intact. Designing a tracing system so that it actually retains the information an engineer will need, rather than cheaply discarding it, is the same undertaking the work on cost-efficient tracing with all-requests collection (Mint) covered elsewhere on this site pursues, making sure the trace keeps what matters instead of dropping it for the sake of overhead. BTrace does this for the smartphone setting, where the events worth keeping are rare and bursty and the naive fast design overwrites them first.
Send the shared resource to wherever the demand actually is. The core mechanism is demand-driven allocation: rather than splitting the trace memory evenly, BTrace hands blocks of a shared pool to the cores that are busiest, so the resource concentrates where the load is. Dynamically steering a shared, limited resource toward the consumers that need it most, instead of provisioning everyone equally, is the same principle the work on exploiting temporal patterns for resource oversubscription (COACH) covered elsewhere on this site is built on, matching allocation to the shifting demand rather than to a static split. BTrace applies this to trace-buffer memory, so the busy cores get the depth of history they need while idle cores do not waste it.
Fit the design to the constraints of a phone, not a server. BTrace exists because a tracer tuned for server throughput behaves badly on a mobile device with limited memory and bursty, uneven activity, so it is re-engineered around those realities. Rethinking a system specifically for the tight resource and usage constraints of a smartphone, rather than porting a server design unchanged, is the same idea the work on exploiting human color discrimination for energy-efficient mobile displays covered elsewhere on this site develops, redesigning around what the mobile setting actually demands. BTrace treats the phone’s uneven load and limited memory as first-class design constraints, which is why its buffering follows demand instead of being split evenly.
Following a trace through BTrace
Retaining the history around a glitch
An illustrative sketch, based on the paper's abstract and reported descriptions, of BTrace's approach. It is not a detailed mechanism from the full paper, which was not available; specifics should be read there.
Step 1 of 5
The system emits a stream of trace events
As apps and the operating system run, each core produces trace records — scheduling, locks, function entries. Only some cores are busy at any moment, and the events worth keeping are concentrated in bursts on those cores.
The interesting history is uneven across cores and over time.
Why it works
- A shared pool lets memory follow the demand. Per-core buffers split memory evenly regardless of load → but pooling it into shared blocks assigned to busy cores → concentrates the retained history where the interesting events actually are.
- Local writes keep the common path cheap. A single shared buffer would force every core to contend on one lock → but each core writes into its own currently-assigned block → so ordinary tracing stays contention-free and low-overhead.
- Depth where it matters beats uniform depth. Enlarging every per-core buffer wastes limited phone memory on idle cores → but giving the busy cores more blocks → deepens history precisely on the cores producing the clues.
- Continuity is preserved without a global lock. Long continuous traces normally require a shared buffer and its contention → but the block pool provides continuity by concentration, not by centralization → so retention improves without paying the contention cost.
- Matching the design to bursty, uneven load fits phones. A server-style even split ignores that phone activity is concentrated and bursty → but demand-driven block assignment tracks that unevenness → so the tracer keeps what matters under real smartphone workloads.
What it costs (trade-offs and scope)
- Dynamic allocation adds coordination. Handing blocks from a shared pool to cores on demand requires machinery to decide who gets blocks and when, which is more complex than a static per-core split; the design must keep that coordination cheap so it does not erode the low-overhead property it is protecting.
- It optimizes retention where load concentrates, not everywhere. By steering memory to the busiest cores, BTrace deepens history there at the expense of quiet cores; this is the right trade for catching bursty problems, but a workload whose important events are spread thinly and evenly would benefit less from demand-driven concentration.
- It targets the smartphone setting. The design assumes limited memory and uneven, bursty activity typical of phones; the same reorganization may offer less over a plain per-core design in settings (like some servers) where the distributed design’s downsides were already tolerable.
- Retention is still bounded by total memory. Pooling and steering memory makes better use of a limited budget, but it does not create unlimited history; if the total trace memory is small relative to the burst, even well-placed blocks can eventually be overwritten.
- This explainer is drawn from limited source material. The full paper text was not accessible for this write-up; the description here is based on the paper’s abstract and secondary descriptions of the design, so the precise mechanism, the allocation policy, and the quantitative results should be confirmed in the paper.
How it compares
Buffering strategies for mobile tracing
| Property | Per-core / per-thread buffers | Single shared global buffer | BTrace (block pool) |
|---|---|---|---|
| Write overhead | |||
| Memory allocation | |||
| History on busy cores | |||
| Continuous trace retention | |||
| Fit for smartphones |
Check yourself
Check your understanding
1. Why do fast, server-style tracers lose crucial clues on smartphones?
2. What is the core idea BTrace uses to keep both speed and trace continuity?
3. Why is simply making every per-core buffer bigger not a good fix?
4. Predict: two workloads run on a phone using BTrace. In one, trace-worthy events burst heavily on a couple of cores; in the other, they are spread thinly and evenly across all cores. Where does BTrace's demand-driven allocation help most, and why?
Sources
The paper is “Enabling Efficient Mobile Tracing with BTrace” by Jiawei Wang, Nian Liu, Arnau Casadevall-Saiz, Yutao Liu, Diogo Behrens, Ming Fu, Ning Jia, Hermann Hartig, and Haibo Chen (Huawei, Shanghai Jiao Tong University, and Technische Universitat Dresden), published at ASPLOS 2025, DOI 10.1145/3676641.3715994.
This explainer is written from the paper’s abstract and secondary descriptions of the work, because the full paper text was not accessible for this write-up (the ACM version was not retrievable and no author preprint or open copy was found). The problem is stated by the authors: as smartphone systems grow more complex, effective tracing is vital for stability and user experience, but existing tracing tools are inefficient on smartphones because their distributed designs — using either per-core or per-thread buffers — prioritize performance but lead to missing crucial clues with high probability, a problem that mattered less on servers but drastically limits the usefulness of tracing on smartphones. The described solution — organizing trace memory as a single global buffer partitioned into blocks that are dynamically assigned to the most demanding cores, combining the low overhead of per-core buffering with longer continuous trace retention — reflects reported descriptions of BTrace rather than the paper’s own text, and the specific mechanisms, allocation policy, and quantitative results (including any reported improvements in retention or performance) could not be verified against the full paper and should be confirmed there. The authors report BTrace has been deployed in production. This page is marked draft because the full text was unavailable.