1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.app.tracing
18 
19 import com.android.app.tracing.coroutines.traceCoroutine
20 import java.util.concurrent.ThreadLocalRandom
21 
22 /**
23  * Writes a trace message to indicate that a given section of code has begun running __on the
24  * current thread__. This must be followed by a corresponding call to [endSlice] in a reasonably
25  * short amount of time __on the same thread__ (i.e. _before_ the thread becomes idle again and
26  * starts running other, unrelated work).
27  *
28  * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as follows:
29  * ```
30  * Thread #1 | [==========================]
31  *           |       [==============]
32  *           |           [====]
33  * ```
34  *
35  * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is more
36  * verbose to call than [Trace.beginSection], but has the added benefit of not throwing an
37  * [IllegalArgumentException] if the provided string is longer than 127 characters. We use the term
38  * "slice" instead of "section" to be consistent with Perfetto.
39  *
40  * # Avoiding malformed traces
41  *
42  * Improper usage of this API will lead to malformed traces with long slices that sometimes never
43  * end. This will look like the following:
44  * ```
45  * Thread #1 | [===================================================================== ...
46  *           |       [==============]         [====================================== ...
47  *           |           [=======]              [======]       [===================== ...
48  *           |                                                       [=======]
49  * ```
50  *
51  * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks (instead,
52  * use [traceCoroutine] for tracing suspending functions). While it would be technically okay to
53  * call from a suspending function if that function were to only wrap non-suspending blocks with
54  * [beginSlice] and [endSlice], doing so is risky because suspend calls could be mistakenly added to
55  * that block as the code is refactored.
56  *
57  * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match it with
58  * a call to [endSlice] inside that callback, even if the callback runs on the same thread. Doing so
59  * would cause malformed traces because the [beginSlice] wasn't closed before the thread became idle
60  * and started running unrelated work.
61  *
62  * @param sliceName The name of the code section to appear in the trace
63  * @see endSlice
64  * @see traceCoroutine
65  */
beginSlicenull66 fun beginSlice(sliceName: String) {
67     traceBegin(sliceName)
68 }
69 
70 /**
71  * Writes a trace message to indicate that a given section of code has ended. This call must be
72  * preceded by a corresponding call to [beginSlice]. See [beginSlice] for important information
73  * regarding usage.
74  *
75  * @see beginSlice
76  * @see traceCoroutine
77  */
endSlicenull78 fun endSlice() {
79     traceEnd()
80 }
81 
82 /**
83  * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
84  * after the passed block.
85  */
traceSectionnull86 inline fun <T> traceSection(tag: String, block: () -> T): T {
87     val tracingEnabled = isEnabled()
88     if (tracingEnabled) beginSlice(tag)
89     return try {
90         // Note that as this is inline, the block section would be duplicated if it is called
91         // several times. For this reason, we're using the try/finally even if tracing is disabled.
92         block()
93     } finally {
94         if (tracingEnabled) endSlice()
95     }
96 }
97 
98 /**
99  * Same as [traceSection], but the tag is provided as a lambda to help avoiding creating expensive
100  * strings when not needed.
101  */
traceSectionnull102 inline fun <T> traceSection(tag: () -> String, block: () -> T): T {
103     val tracingEnabled = isEnabled()
104     if (tracingEnabled) beginSlice(tag())
105     return try {
106         block()
107     } finally {
108         if (tracingEnabled) endSlice()
109     }
110 }
111 
112 object TraceUtils {
113     const val TAG = "TraceUtils"
114     const val DEFAULT_TRACK_NAME = "AsyncTraces"
115 
116     @JvmStatic
tracenull117     inline fun <T> trace(tag: () -> String, block: () -> T): T {
118         return traceSection(tag) { block() }
119     }
120 
121     @JvmStatic
tracenull122     inline fun <T> trace(tag: String, crossinline block: () -> T): T {
123         return traceSection(tag) { block() }
124     }
125 
126     @JvmStatic
traceRunnablenull127     inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
128         return Runnable { traceSection(tag) { block() } }
129     }
130 
131     @JvmStatic
traceRunnablenull132     inline fun traceRunnable(
133         crossinline tag: () -> String,
134         crossinline block: () -> Unit
135     ): Runnable {
136         return Runnable { traceSection(tag) { block() } }
137     }
138 
139     /**
140      * Creates an async slice in a track called "AsyncTraces".
141      *
142      * This can be used to trace coroutine code. Note that all usages of this method will appear
143      * under a single track.
144      */
145     @JvmStatic
traceAsyncnull146     inline fun <T> traceAsync(method: String, block: () -> T): T =
147         traceAsync(DEFAULT_TRACK_NAME, method, block)
148 
149     /**
150      * Creates an async slice in a track with [trackName] while [block] runs.
151      *
152      * This can be used to trace coroutine code. [method] will be the name of the slice, [trackName]
153      * of the track. The track is one of the rows visible in a perfetto trace inside the app
154      * process.
155      */
156     @JvmStatic
157     inline fun <T> traceAsync(trackName: String, method: String, block: () -> T): T {
158         val cookie = ThreadLocalRandom.current().nextInt()
159         asyncTraceForTrackBegin(trackName, method, cookie)
160         try {
161             return block()
162         } finally {
163             asyncTraceForTrackEnd(trackName, method, cookie)
164         }
165     }
166 }
167