xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/src/android/tools/traces/io/ResultReader.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
1 /*
<lambda>null2  * 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 android.tools.traces.io
18 
19 import android.tools.Tag
20 import android.tools.Timestamp
21 import android.tools.io.Artifact
22 import android.tools.io.FLICKER_IO_TAG
23 import android.tools.io.Reader
24 import android.tools.io.ResultArtifactDescriptor
25 import android.tools.io.TraceType
26 import android.tools.parsers.events.EventLogParser
27 import android.tools.traces.TraceConfig
28 import android.tools.traces.TraceConfigs
29 import android.tools.traces.events.CujTrace
30 import android.tools.traces.events.EventLog
31 import android.tools.traces.parsers.perfetto.LayersTraceParser
32 import android.tools.traces.parsers.perfetto.ProtoLogTraceParser
33 import android.tools.traces.parsers.perfetto.TraceProcessorSession
34 import android.tools.traces.parsers.perfetto.TransactionsTraceParser
35 import android.tools.traces.parsers.perfetto.TransitionsTraceParser
36 import android.tools.traces.parsers.perfetto.WindowManagerTraceParser
37 import android.tools.traces.parsers.wm.LegacyTransitionTraceParser
38 import android.tools.traces.parsers.wm.LegacyWindowManagerTraceParser
39 import android.tools.traces.parsers.wm.WindowManagerDumpParser
40 import android.tools.traces.protolog.ProtoLogTrace
41 import android.tools.traces.surfaceflinger.LayersTrace
42 import android.tools.traces.surfaceflinger.TransactionsTrace
43 import android.tools.traces.wm.TransitionsTrace
44 import android.tools.traces.wm.WindowManagerTrace
45 import android.tools.withTracing
46 import android.util.Log
47 import androidx.annotation.VisibleForTesting
48 import java.io.IOException
49 
50 /**
51  * Helper class to read results from a flicker artifact
52  *
53  * @param _result to read from
54  * @param traceConfig
55  */
56 open class ResultReader(_result: IResultData, internal val traceConfig: TraceConfigs) : Reader {
57     @VisibleForTesting
58     var result = _result
59         internal set
60 
61     override val artifact: Artifact = result.artifact
62     override val artifactPath: String
63         get() = result.artifact.absolutePath
64 
65     override val runStatus
66         get() = result.runStatus
67 
68     internal val transitionTimeRange
69         get() = result.transitionTimeRange
70 
71     override val isFailure
72         get() = runStatus.isFailure
73 
74     override val executionError
75         get() = result.executionError
76 
77     override fun readBytes(traceType: TraceType, tag: String): ByteArray? =
78         artifact.readBytes(ResultArtifactDescriptor(traceType, tag))
79 
80     /**
81      * {@inheritDoc}
82      *
83      * @throws IOException if the artifact file doesn't exist or can't be read
84      */
85     @Throws(IOException::class)
86     override fun readWmState(tag: String): WindowManagerTrace? {
87         return withTracing("readWmState#$tag") {
88             val descriptor = ResultArtifactDescriptor(TraceType.WM_DUMP, tag)
89             Log.d(FLICKER_IO_TAG, "Reading WM trace descriptor=$descriptor from $result")
90             val traceData = artifact.readBytes(descriptor)
91             traceData?.let {
92                 if (android.tracing.Flags.perfettoWmDump()) {
93                     TraceProcessorSession.loadPerfettoTrace(it) { session ->
94                         WindowManagerTraceParser().parse(session)
95                     }
96                 } else {
97                     WindowManagerDumpParser().parse(it, clearCache = true)
98                 }
99             }
100         }
101     }
102 
103     /**
104      * {@inheritDoc}
105      *
106      * @throws IOException if the artifact file doesn't exist or can't be read
107      */
108     @Throws(IOException::class)
109     override fun readWmTrace(): WindowManagerTrace? {
110         return withTracing("readWmTrace") {
111             val trace =
112                 if (android.tracing.Flags.perfettoWmTracing()) {
113                     readPerfettoWindowManagerTrace()
114                 } else {
115                     readLegacyWindowManagerTrace()
116                 }
117             if (trace != null) {
118                 val minimumEntries = minimumTraceEntriesForConfig(traceConfig.wmTrace)
119                 require(trace.entries.size >= minimumEntries) {
120                     "WM trace contained ${trace.entries.size} entries, " +
121                         "expected at least $minimumEntries... :: " +
122                         "transition starts at ${transitionTimeRange.start} and " +
123                         "ends at ${transitionTimeRange.end}."
124                 }
125             }
126             trace
127         }
128     }
129 
130     /**
131      * {@inheritDoc}
132      *
133      * @throws IOException if the artifact file doesn't exist or can't be read
134      */
135     @Throws(IOException::class)
136     override fun readLayersTrace(): LayersTrace? {
137         return withTracing("readLayersTrace") {
138             val descriptor = ResultArtifactDescriptor(TraceType.SF)
139             artifact.readBytes(descriptor)?.let {
140                 val trace =
141                     TraceProcessorSession.loadPerfettoTrace(it) { session ->
142                         LayersTraceParser()
143                             .parse(
144                                 session,
145                                 transitionTimeRange.start,
146                                 transitionTimeRange.end,
147                                 addInitialEntry = true,
148                                 clearCache = true,
149                             )
150                     }
151                 val minimumEntries = minimumTraceEntriesForConfig(traceConfig.layersTrace)
152                 require(trace.entries.size >= minimumEntries) {
153                     "Layers trace contained ${trace.entries.size} entries, " +
154                         "expected at least $minimumEntries... :: " +
155                         "transition starts at ${transitionTimeRange.start} and " +
156                         "ends at ${transitionTimeRange.end}."
157                 }
158                 trace
159             }
160         }
161     }
162 
163     /**
164      * {@inheritDoc}
165      *
166      * @throws IOException if the artifact file doesn't exist or can't be read
167      */
168     @Throws(IOException::class)
169     override fun readLayersDump(tag: String): LayersTrace? {
170         return withTracing("readLayersDump#$tag") {
171             val descriptor = ResultArtifactDescriptor(TraceType.SF_DUMP, tag)
172             val traceData = artifact.readBytes(descriptor)
173             if (traceData != null) {
174                 TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
175                     LayersTraceParser().parse(session, clearCache = true)
176                 }
177             } else {
178                 null
179             }
180         }
181     }
182 
183     /**
184      * {@inheritDoc}
185      *
186      * @throws IOException if the artifact file doesn't exist or can't be read
187      */
188     @Throws(IOException::class)
189     override fun readTransactionsTrace(): TransactionsTrace? =
190         withTracing("readTransactionsTrace") {
191             doReadTransactionsTrace(from = transitionTimeRange.start, to = transitionTimeRange.end)
192         }
193 
194     private fun doReadTransactionsTrace(from: Timestamp, to: Timestamp): TransactionsTrace? {
195         val traceData = artifact.readBytes(ResultArtifactDescriptor(TraceType.TRANSACTION))
196         return traceData?.let {
197             val trace =
198                 TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
199                     TransactionsTraceParser().parse(session, from, to, addInitialEntry = true)
200                 }
201             require(trace.entries.isNotEmpty()) { "Transactions trace cannot be empty" }
202             trace
203         }
204     }
205 
206     /**
207      * {@inheritDoc}
208      *
209      * @throws IOException if the artifact file doesn't exist or can't be read
210      */
211     @Throws(IOException::class)
212     override fun readTransitionsTrace(): TransitionsTrace? {
213         return withTracing("readTransitionsTrace") {
214             val trace =
215                 if (android.tracing.Flags.perfettoTransitionTracing()) {
216                     readPerfettoTransitionsTrace()
217                 } else {
218                     readLegacyTransitionTrace()
219                 }
220 
221             if (trace == null) {
222                 return@withTracing null
223             }
224 
225             if (!traceConfig.transitionsTrace.allowNoChange) {
226                 require(trace.entries.isNotEmpty()) { "Transitions trace cannot be empty" }
227             }
228 
229             trace
230         }
231     }
232 
233     /**
234      * {@inheritDoc}
235      *
236      * @throws IOException if the artifact file doesn't exist or can't be read
237      */
238     @Throws(IOException::class)
239     override fun readProtoLogTrace(): ProtoLogTrace? {
240         return withTracing("readProtoLogTrace") {
241             val traceData = artifact.readBytes(ResultArtifactDescriptor(TraceType.PERFETTO))
242 
243             traceData?.let {
244                 TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
245                     ProtoLogTraceParser()
246                         .parse(
247                             session,
248                             from = transitionTimeRange.start,
249                             to = transitionTimeRange.end,
250                         )
251                 }
252             }
253         }
254     }
255 
256     private fun readPerfettoWindowManagerTrace(): WindowManagerTrace? {
257         val traceData = artifact.readBytes(ResultArtifactDescriptor(TraceType.PERFETTO))
258 
259         return traceData?.let {
260             TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
261                 WindowManagerTraceParser()
262                     .parse(session, from = transitionTimeRange.start, to = transitionTimeRange.end)
263             }
264         }
265     }
266 
267     private fun readLegacyWindowManagerTrace(): WindowManagerTrace? {
268         val traceData = artifact.readBytes(ResultArtifactDescriptor(TraceType.WM))
269 
270         return traceData?.let {
271             LegacyWindowManagerTraceParser()
272                 .parse(
273                     it,
274                     from = transitionTimeRange.start,
275                     to = transitionTimeRange.end,
276                     addInitialEntry = true,
277                     clearCache = true,
278                 )
279         }
280     }
281 
282     private fun readPerfettoTransitionsTrace(): TransitionsTrace? {
283         val traceData = artifact.readBytes(ResultArtifactDescriptor(TraceType.PERFETTO))
284 
285         return traceData?.let {
286             TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
287                 TransitionsTraceParser()
288                     .parse(session, from = transitionTimeRange.start, to = transitionTimeRange.end)
289             }
290         }
291     }
292 
293     private fun readLegacyTransitionTrace(): TransitionsTrace? {
294         val wmSideTraceData =
295             artifact.readBytes(ResultArtifactDescriptor(TraceType.LEGACY_WM_TRANSITION))
296         val shellSideTraceData =
297             artifact.readBytes(ResultArtifactDescriptor(TraceType.LEGACY_SHELL_TRANSITION))
298 
299         return if (wmSideTraceData == null || shellSideTraceData == null) {
300             null
301         } else {
302             LegacyTransitionTraceParser()
303                 .parse(
304                     wmSideTraceData,
305                     shellSideTraceData,
306                     from = transitionTimeRange.start,
307                     to = transitionTimeRange.end,
308                 )
309         }
310     }
311 
312     private fun minimumTraceEntriesForConfig(config: TraceConfig): Int {
313         return if (config.allowNoChange) 1 else 2
314     }
315 
316     /**
317      * {@inheritDoc}
318      *
319      * @throws IOException if the artifact file doesn't exist or can't be read
320      */
321     @Throws(IOException::class)
322     override fun readEventLogTrace(): EventLog? {
323         return withTracing("readEventLogTrace") {
324             val descriptor = ResultArtifactDescriptor(TraceType.EVENT_LOG)
325             artifact.readBytes(descriptor)?.let {
326                 EventLogParser()
327                     .parseSlice(it, from = transitionTimeRange.start, to = transitionTimeRange.end)
328             }
329         }
330     }
331 
332     /**
333      * {@inheritDoc}
334      *
335      * @throws IOException if the artifact file doesn't exist or can't be read
336      */
337     @Throws(IOException::class)
338     override fun readCujTrace(): CujTrace? = readEventLogTrace()?.cujTrace
339 
340     /** @return an [Reader] for the subsection of the trace we are reading in this reader */
341     override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ResultReader {
342         val slicedResult = result.slice(startTimestamp, endTimestamp)
343         return ResultReader(slicedResult, traceConfig)
344     }
345 
346     override fun toString(): String = "$result"
347 
348     /** @return the number of files in the artifact */
349     @VisibleForTesting fun countFiles(): Int = artifact.traceCount()
350 
351     /** @return if a file with type [traceType] linked to a [tag] exists in the artifact */
352     fun hasTraceFile(traceType: TraceType, tag: String = Tag.ALL): Boolean {
353         val descriptor = ResultArtifactDescriptor(traceType, tag)
354         return artifact.hasTrace(descriptor)
355     }
356 }
357