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