xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/src/android/tools/traces/Utils.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
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 perfettoTrace =
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             .readBytes(TraceType.PERFETTO) ?: ByteArray(0)
140 
141     val wmDump =
142         if (android.tracing.Flags.perfettoWmDump()) {
143             if (requestedWmDump) perfettoTrace else ByteArray(0)
144         } else {
145             if (requestedWmDump) {
146                 Log.d(LOG_TAG, "Requesting new legacy WM state dump")
147                 getCurrentWindowManagerState()
148             } else {
149                 ByteArray(0)
150             }
151         }
152 
153     val sfDump = if (requestedSfDump) perfettoTrace else ByteArray(0)
154 
155     return Pair(wmDump, sfDump)
156 }
157 
158 /**
159  * Gets the current device state dump containing the [WindowManagerState] (optional) and the
160  * [LayerTraceEntry] (optional) parsed
161  *
162  * @param dumpTypes Flags determining which types of traces should be included in the dump
163  * @param clearCacheAfterParsing If the caching used while parsing the proto should be
164  *
165  * ```
166  *                               cleared or remain in memory
167  * ```
168  */
169 @JvmOverloads
getCurrentStateDumpNullablenull170 fun getCurrentStateDumpNullable(
171     vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
172     clearCacheAfterParsing: Boolean = true,
173 ): NullableDeviceStateDump {
174     val currentStateDump = getCurrentState(*dumpTypes)
175     return DeviceDumpParser.fromNullableDump(
176         currentStateDump.first,
177         currentStateDump.second,
178         clearCacheAfterParsing = clearCacheAfterParsing,
179     )
180 }
181 
182 @JvmOverloads
getCurrentStateDumpnull183 fun getCurrentStateDump(
184     vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
185     clearCacheAfterParsing: Boolean = true,
186 ): DeviceStateDump {
187     val currentStateDump = getCurrentState(*dumpTypes)
188     return DeviceDumpParser.fromDump(
189         currentStateDump.first,
190         currentStateDump.second,
191         clearCacheAfterParsing = clearCacheAfterParsing,
192     )
193 }
194 
195 @JvmOverloads
busyWaitForDataSourceRegistrationnull196 fun busyWaitForDataSourceRegistration(
197     dataSourceName: String,
198     busyWaitIntervalMs: Long = 100,
199     timeoutMs: Long = 10000,
200 ) {
201     var elapsedMs = 0L
202 
203     while (!isDataSourceAvailable(dataSourceName)) {
204         SystemClock.sleep(busyWaitIntervalMs)
205         elapsedMs += busyWaitIntervalMs
206         if (elapsedMs >= timeoutMs) {
207             throw java.lang.RuntimeException(
208                 "Data source didn't become available. Waited for: $timeoutMs ms"
209             )
210         }
211     }
212 }
213 
isDataSourceAvailablenull214 fun isDataSourceAvailable(dataSourceName: String): Boolean {
215     val proto = executeShellCommand("perfetto --query-raw")
216 
217     try {
218         val state = TracingServiceState.parseFrom(proto)
219 
220         var producerId = Optional.empty<Int>()
221 
222         for (producer in state.producersList) {
223             if (producer.pid == Process.myPid()) {
224                 producerId = Optional.of(producer.id)
225                 break
226             }
227         }
228 
229         if (!producerId.isPresent) {
230             return false
231         }
232 
233         for (ds in state.dataSourcesList) {
234             if (ds.dsDescriptor.name.equals(dataSourceName) && ds.producerId == producerId.get()) {
235                 return true
236             }
237         }
238     } catch (e: InvalidProtocolBufferException) {
239         throw RuntimeException(e)
240     }
241 
242     return false
243 }
244