tree: e0eb1fc1515c79aeb83155b12b6cb8f116bc63e7 [path history] [tgz]
  1. benchmark/
  2. core/
  3. demo/
  4. robolectric/
  5. Android.bp
  6. README.md
tracinglib/README.md

Coroutine Tracing

This library contains utilities for tracing coroutines. Coroutines cannot be traced using the android.os.Trace APIs because suspension points will lead to malformed trace sections. This is because each Trace.beginSection must have a matching Trace.endSection; if a coroutine suspends before Trace.endSection is called, the trace section will remain open while other unrelated work executes.

To address this, we introduce a function traceCoroutine("name") { ... } that can be used for tracing sections of coroutine code. When invoked, a trace section with the given name will start immediately, and its name will also be written to an object in the current CoroutineContext used for coroutine-local storage. When the coroutine suspends, all trace sections will end immediately. When resumed, the coroutine will read the names of the previous sections from coroutine-local storage, and it will begin the sections again.

For example, the following coroutine code will be traced as follows:

traceCoroutine("Slice A") {
  println("Start")
  delay(10)
  println("End")
}
Thread #1 |  [==== Slice ====]          [==== Slice ====]
               ^ "Start" printed          ^ "End" printed

If multiple threads are used, it would be as follows:

traceCoroutine("Slice") {
  println("Start")
  delay(10)
  withContext(backgroundThread) {
    println("End")
  }
}
Thread #1 |  [==== Slice ====]
          |    ^ "Start" printed
----------+---------------------------------------------------------
Thread #2 |                              [==== Slice ====]
                                           ^ "End" printed

This library also provides wrappers for some of the coroutine functions provided in the kotlinx.coroutines.* package. For example, instead of: launch { traceCoroutine("my-launch") { /* block */ } }, you can instead write: launch("my-launch") { /* block */ }.

It also provides a wrapper for tracing Flow emissions. For example,

val coldFlow = flow {
  emit(1)
  emit(2)
  emit(3)
}

coldFlow.collect("F") {
  println(it)
  yield()
}

Would be traced as follows:

Thread #1 |  [====== collect:F ======]    [==== collect:F =====]    [====== collect:F ======]
          |    [== collect:F:emit ==]     [== collect:F:emit ==]    [== collect:F:emit ==]

Building and Running

Host Tests

Host tests are implemented in tracinglib-host-test. To run the host tests:

atest tracinglib-host-test

Feature Flag

Coroutine tracing is flagged off by default. To enable coroutine tracing on a device, flip the flag and restart the user-space system:

adb shell device_config override systemui com.android.systemui.coroutine_tracing true
adb shell am restart

Demo App

Build and install the app using Soong and adevice:

adevice track CoroutineTracingDemoApp
m CoroutineTracingDemoApp
adevice update

Then, open the app and tap an experiment to run it. The experiments run in the background. To see the effects of what coroutine tracing is doing, you will need to capture a Perfetto trace. The coroutine_tracing flag will need to be enabled for coroutine trace sections to work.