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