• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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 @file:JvmName("Utils")
18 
19 package android.tools.traces
20 
21 import android.app.UiAutomation
22 import android.os.IBinder
23 import android.os.ParcelFileDescriptor
24 import android.os.Process
25 import android.os.SystemClock
26 import android.tools.MILLISECOND_AS_NANOSECONDS
27 import android.tools.io.TraceType
28 import android.tools.traces.io.ResultReader
29 import android.tools.traces.monitors.PerfettoTraceMonitor
30 import android.tools.traces.parsers.DeviceDumpParser
31 import android.tools.traces.surfaceflinger.LayerTraceEntry
32 import android.tools.traces.wm.WindowManagerState
33 import android.util.Log
34 import androidx.test.platform.app.InstrumentationRegistry
35 import com.google.protobuf.InvalidProtocolBufferException
36 import java.text.SimpleDateFormat
37 import java.util.Date
38 import java.util.Locale
39 import java.util.Optional
40 import java.util.TimeZone
41 import perfetto.protos.PerfettoConfig.TracingServiceState
42 
43 fun formatRealTimestamp(timestampNs: Long): String {
44     val timestampMs = timestampNs / MILLISECOND_AS_NANOSECONDS
45     val remainderNs = timestampNs % MILLISECOND_AS_NANOSECONDS
46     val date = Date(timestampMs)
47 
48     val timeFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH)
49     timeFormatter.timeZone = TimeZone.getTimeZone("UTC")
50 
51     return "${timeFormatter.format(date)}${remainderNs.toString().padStart(6, '0')}"
52 }
53 
executeShellCommandnull54 fun executeShellCommand(cmd: String): ByteArray {
55     Log.d(LOG_TAG, "Executing shell command $cmd")
56     val uiAutomation: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
57     val fileDescriptor = uiAutomation.executeShellCommand(cmd)
58     ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use { inputStream ->
59         return inputStream.readBytes()
60     }
61 }
62 
executeShellCommandnull63 fun executeShellCommand(cmd: String, stdin: ByteArray): ByteArray {
64     Log.d(LOG_TAG, "Executing shell command $cmd")
65     val uiAutomation: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
66     val fileDescriptors = uiAutomation.executeShellCommandRw(cmd)
67     val stdoutFileDescriptor = fileDescriptors[0]
68     val stdinFileDescriptor = fileDescriptors[1]
69 
70     ParcelFileDescriptor.AutoCloseOutputStream(stdinFileDescriptor).use { it.write(stdin) }
71 
72     ParcelFileDescriptor.AutoCloseInputStream(stdoutFileDescriptor).use {
73         return it.readBytes()
74     }
75 }
76 
doBinderDumpnull77 private fun doBinderDump(name: String): ByteArray {
78     // create a fd for the binder transaction
79     val pipe = ParcelFileDescriptor.createPipe()
80     val source = pipe[0]
81     val sink = pipe[1]
82 
83     // ServiceManager isn't accessible from tests, so use reflection
84     // this should return an IBinder
85     val service =
86         Class.forName("android.os.ServiceManager")
87             .getMethod("getServiceOrThrow", String::class.java)
88             .invoke(null, name) as IBinder?
89 
90     // this is equal to ServiceManager::PROTO_ARG
91     val args = arrayOf("--proto")
92     service?.dump(sink.fileDescriptor, args)
93     sink.close()
94 
95     // convert the FD into a ByteArray
96     ParcelFileDescriptor.AutoCloseInputStream(source).use { inputStream ->
97         return inputStream.readBytes()
98     }
99 }
100 
getCurrentWindowManagerStatenull101 private fun getCurrentWindowManagerState() = doBinderDump("window")
102 
103 /**
104  * Gets the current device state dump containing the [WindowManagerState] (optional) and the
105  * [LayerTraceEntry] (optional) in raw (byte) data.
106  *
107  * @param dumpTypes Flags determining which types of traces should be included in the dump
108  */
109 fun getCurrentState(
110     vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP)
111 ): Pair<ByteArray, ByteArray> {
112     if (dumpTypes.isEmpty()) {
113         throw IllegalArgumentException("No dump specified")
114     }
115 
116     val traceTypes = dumpTypes.filter { it.isTrace }
117     if (traceTypes.isNotEmpty()) {
118         throw IllegalArgumentException("Only dump types are supported. Invalid types: $traceTypes")
119     }
120 
121     val requestedWmDump = dumpTypes.contains(TraceType.WM_DUMP)
122     val requestedSfDump = dumpTypes.contains(TraceType.SF_DUMP)
123 
124     Log.d(LOG_TAG, "Requesting new device state dump")
125 
126     val reader =
127         PerfettoTraceMonitor.newBuilder()
128             .also {
129                 if (requestedWmDump && android.tracing.Flags.perfettoWmDump()) {
130                     it.enableWindowManagerDump()
131                 }
132 
133                 if (requestedSfDump) {
134                     it.enableLayersDump()
135                 }
136             }
137             .build()
138             .withTracing(resultReaderProvider = { ResultReader(it, SERVICE_TRACE_CONFIG) }) {}
139     val perfettoTrace = reader.readBytes(TraceType.PERFETTO) ?: ByteArray(0)
140 
141     reader.artifact.deleteIfExists()
142 
143     val wmDump =
144         if (android.tracing.Flags.perfettoWmDump()) {
145             if (requestedWmDump) perfettoTrace else ByteArray(0)
146         } else {
147             if (requestedWmDump) {
148                 Log.d(LOG_TAG, "Requesting new legacy WM state dump")
149                 getCurrentWindowManagerState()
150             } else {
151                 ByteArray(0)
152             }
153         }
154 
155     val sfDump = if (requestedSfDump) perfettoTrace else ByteArray(0)
156 
157     return Pair(wmDump, sfDump)
158 }
159 
160 /**
161  * Gets the current device state dump containing the [WindowManagerState] (optional) and the
162  * [LayerTraceEntry] (optional) parsed
163  *
164  * @param dumpTypes Flags determining which types of traces should be included in the dump
165  * @param clearCacheAfterParsing If the caching used while parsing the proto should be
166  *
167  * ```
168  *                               cleared or remain in memory
169  * ```
170  */
171 @JvmOverloads
getCurrentStateDumpNullablenull172 fun getCurrentStateDumpNullable(
173     vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
174     clearCacheAfterParsing: Boolean = true,
175 ): NullableDeviceStateDump {
176     val currentStateDump = getCurrentState(*dumpTypes)
177     return DeviceDumpParser.fromNullableDump(
178         currentStateDump.first,
179         currentStateDump.second,
180         clearCacheAfterParsing = clearCacheAfterParsing,
181     )
182 }
183 
184 @JvmOverloads
getCurrentStateDumpnull185 fun getCurrentStateDump(
186     vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
187     clearCacheAfterParsing: Boolean = true,
188 ): DeviceStateDump {
189     val currentStateDump = getCurrentState(*dumpTypes)
190     return DeviceDumpParser.fromDump(
191         currentStateDump.first,
192         currentStateDump.second,
193         clearCacheAfterParsing = clearCacheAfterParsing,
194     )
195 }
196 
197 @JvmOverloads
busyWaitForDataSourceRegistrationnull198 fun busyWaitForDataSourceRegistration(
199     dataSourceName: String,
200     busyWaitIntervalMs: Long = 100,
201     timeoutMs: Long = 10000,
202 ) {
203     busyWait(
204         busyWaitIntervalMs,
205         timeoutMs,
206         { isDataSourceAvailable(dataSourceName) },
207         { "Data source disn't  become available" },
208     )
209 }
210 
211 @JvmOverloads
busyWaitTracingSessionExistsnull212 fun busyWaitTracingSessionExists(
213     uniqueSessionName: String,
214     busyWaitIntervalMs: Long = 100,
215     timeoutMs: Long = 10000,
216 ) {
217     busyWait(
218         busyWaitIntervalMs,
219         timeoutMs,
220         { sessionExists(uniqueSessionName) },
221         { "Tracing session doesn't exist" },
222     )
223 }
224 
225 @JvmOverloads
busyWaitTracingSessionDoesntExistnull226 fun busyWaitTracingSessionDoesntExist(
227     uniqueSessionName: String,
228     busyWaitIntervalMs: Long = 100,
229     timeoutMs: Long = 10000,
230 ) {
231     busyWait(
232         busyWaitIntervalMs,
233         timeoutMs,
234         { !sessionExists(uniqueSessionName) },
235         { "Tracing session still exists" },
236     )
237 }
238 
isDataSourceAvailablenull239 private fun isDataSourceAvailable(dataSourceName: String): Boolean {
240     val proto = executeShellCommand("perfetto --query-raw")
241 
242     try {
243         val state = TracingServiceState.parseFrom(proto)
244 
245         var producerId = Optional.empty<Int>()
246 
247         for (producer in state.producersList) {
248             if (producer.pid == Process.myPid()) {
249                 producerId = Optional.of(producer.id)
250                 break
251             }
252         }
253 
254         if (!producerId.isPresent) {
255             return false
256         }
257 
258         for (ds in state.dataSourcesList) {
259             if (ds.dsDescriptor.name.equals(dataSourceName) && ds.producerId == producerId.get()) {
260                 return true
261             }
262         }
263     } catch (e: InvalidProtocolBufferException) {
264         throw RuntimeException(e)
265     }
266 
267     return false
268 }
269 
sessionExistsnull270 private fun sessionExists(uniqueSessionName: String): Boolean {
271     val proto = executeShellCommand("perfetto --query-raw")
272 
273     try {
274         val state = TracingServiceState.parseFrom(proto)
275 
276         for (session in state.tracingSessionsList) {
277             if (session.uniqueSessionName.equals(uniqueSessionName)) {
278                 return true
279             }
280         }
281     } catch (e: InvalidProtocolBufferException) {
282         throw RuntimeException(e)
283     }
284 
285     return false
286 }
287 
busyWaitnull288 private fun busyWait(
289     busyWaitIntervalMs: Long,
290     timeoutMs: Long,
291     predicate: () -> Boolean,
292     errorMessage: () -> String,
293 ) {
294     var elapsedMs = 0L
295 
296     while (!predicate()) {
297         SystemClock.sleep(busyWaitIntervalMs)
298         elapsedMs += busyWaitIntervalMs
299         if (elapsedMs >= timeoutMs) {
300             throw java.lang.RuntimeException(errorMessage())
301         }
302     }
303 }
304