README.md
1# Coroutine Tracing
2
3This library contains utilities for tracing coroutines. Coroutines cannot be traced using the
4`android.os.Trace` APIs because suspension points will lead to malformed trace sections. This is
5because each `Trace.beginSection` must have a matching `Trace.endSection`; if a coroutine suspends
6before `Trace.endSection` is called, the trace section will remain open while other unrelated work
7executes.
8
9To address this, we introduce a function `traceCoroutine("name") { ... }` that can be used for
10tracing sections of coroutine code. When invoked, a trace section with the given name will start
11immediately, and its name will also be written to an object in the current `CoroutineContext` used
12for coroutine-local storage. When the coroutine suspends, all trace sections will end immediately.
13When resumed, the coroutine will read the names of the previous sections from coroutine-local
14storage, and it will begin the sections again.
15
16For example, the following coroutine code will be traced as follows:
17
18```
19traceCoroutine("Slice A") {
20 println("Start")
21 delay(10)
22 println("End")
23}
24```
25
26```
27Thread #1 | [==== Slice ====] [==== Slice ====]
28 ^ "Start" printed ^ "End" printed
29```
30
31If multiple threads are used, it would be as follows:
32
33```
34traceCoroutine("Slice") {
35 println("Start")
36 delay(10)
37 withContext(backgroundThread) {
38 println("End")
39 }
40}
41```
42
43```
44Thread #1 | [==== Slice ====]
45 | ^ "Start" printed
46----------+---------------------------------------------------------
47Thread #2 | [==== Slice ====]
48 ^ "End" printed
49```
50
51This library also provides wrappers for some of the coroutine functions provided in the
52`kotlinx.coroutines.*` package. For example, instead of:
53`launch { traceCoroutine("my-launch") { /* block */ } }`, you can instead write:
54`launch("my-launch") { /* block */ }`.
55
56It also provides a wrapper for tracing Flow emissions. For example,
57
58```
59val coldFlow = flow {
60 emit(1)
61 emit(2)
62 emit(3)
63}
64
65coldFlow.collect("F") {
66 println(it)
67 yield()
68}
69```
70
71Would be traced as follows:
72
73```
74Thread #1 | [====== collect:F ======] [==== collect:F =====] [====== collect:F ======]
75 | [== collect:F:emit ==] [== collect:F:emit ==] [== collect:F:emit ==]
76```
77
78# Building and Running
79
80## Host Tests
81
82Host tests are implemented in `tracinglib-host-test`. To run the host tests:
83
84```
85atest tracinglib-host-test
86```
87
88## Feature Flag
89
90Coroutine tracing is flagged off by default. To enable coroutine tracing on a device, flip the flag
91and restart the user-space system:
92
93```
94adb shell device_config override systemui com.android.systemui.coroutine_tracing true
95adb shell am restart
96```
97
98## Demo App
99
100Build and install the app using Soong and adevice:
101
102```
103adevice track CoroutineTracingDemoApp
104m CoroutineTracingDemoApp
105adevice update
106```
107
108Then, open the app and tap an experiment to run it. The experiments run in the background. To see
109the effects of what coroutine tracing is doing, you will need to capture a Perfetto trace. The
110[`coroutine_tracing` flag](#feature-flag) will need to be enabled for coroutine trace sections to
111work.
112