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.flicker.extractors
18 
19 import android.tools.Timestamp
20 import android.tools.Timestamps
21 import android.tools.io.Reader
22 import android.tools.traces.events.Cuj
23 import android.tools.traces.events.CujType
24 import android.tools.traces.wm.Transition
25 import android.util.Log
26 import kotlin.math.max
27 import kotlin.math.min
28 
29 class TaggedScenarioExtractor(
30     private val targetTag: CujType,
31     private val transitionMatcher: TransitionMatcher?,
32     private val adjustCuj: CujAdjust,
33     private val additionalCujFilter: ((Cuj) -> Boolean)? = null,
34     private val ignoreIfNoMatchingTransition: Boolean = false,
35 ) : ScenarioExtractor {
36     companion object {
37         const val LOG_TAG = "FlickerTaggedScenarioExtractor"
38     }
39 
40     override fun extract(reader: Reader): List<TraceSlice> {
41         val cujTrace = reader.readCujTrace() ?: error("Missing CUJ trace")
42 
43         val targetCujEntries =
44             cujTrace.entries
45                 .filter { it.cuj === targetTag }
46                 .filter { !it.canceled }
47                 .filter { additionalCujFilter?.invoke(it) ?: true }
48                 .map { adjustCuj.adjustCuj(it, reader) }
49 
50         if (targetCujEntries.isEmpty()) {
51             // No scenarios to extract here
52             return emptyList()
53         }
54 
55         return targetCujEntries.mapNotNull { cujEntry ->
56             val associatedTransitions = transitionMatcher?.getMatches(reader, cujEntry)
57 
58             if ((associatedTransitions?.size ?: 0) > 1) {
59                 Log.w(
60                     LOG_TAG,
61                     "Got more than one associated transition: " +
62                         "[${associatedTransitions?.joinToString()}]. " +
63                         "Picking first transition in list.",
64                 )
65             }
66 
67             val associatedTransition = associatedTransitions?.firstOrNull()
68 
69             if (ignoreIfNoMatchingTransition && associatedTransition == null) {
70                 return@mapNotNull null
71             }
72 
73             require(
74                 cujEntry.startTimestamp.hasAllTimestamps && cujEntry.endTimestamp.hasAllTimestamps
75             )
76 
77             val startTimestamp =
78                 estimateScenarioStartTimestamp(cujEntry, associatedTransition, reader)
79             val endTimestamp = estimateScenarioEndTimestamp(cujEntry, associatedTransition, reader)
80 
81             TraceSlice(
82                 startTimestamp,
83                 endTimestamp,
84                 associatedCuj = cujEntry.cuj,
85                 associatedTransition = associatedTransition,
86             )
87         }
88     }
89 
90     private fun estimateScenarioStartTimestamp(
91         cujEntry: Cuj,
92         associatedTransition: Transition?,
93         reader: Reader,
94     ): Timestamp {
95         val interpolatedStartTimestamp =
96             if (associatedTransition != null) {
97                 Utils.interpolateStartTimestampFromTransition(associatedTransition, reader)
98             } else {
99                 null
100             }
101 
102         return Timestamps.from(
103             elapsedNanos =
104                 min(
105                     cujEntry.startTimestamp.elapsedNanos,
106                     interpolatedStartTimestamp?.elapsedNanos ?: cujEntry.startTimestamp.elapsedNanos,
107                 ),
108             systemUptimeNanos =
109                 min(
110                     cujEntry.startTimestamp.systemUptimeNanos,
111                     interpolatedStartTimestamp?.systemUptimeNanos
112                         ?: cujEntry.startTimestamp.systemUptimeNanos,
113                 ),
114             unixNanos =
115                 min(
116                     cujEntry.startTimestamp.unixNanos,
117                     interpolatedStartTimestamp?.unixNanos ?: cujEntry.startTimestamp.unixNanos,
118                 ),
119         )
120     }
121 
122     private fun estimateScenarioEndTimestamp(
123         cujEntry: Cuj,
124         associatedTransition: Transition?,
125         reader: Reader,
126     ): Timestamp {
127         val interpolatedEndTimestamp =
128             if (associatedTransition != null) {
129                 Utils.interpolateFinishTimestampFromTransition(
130                     associatedTransition,
131                     reader,
132                     cujEntry.toString(),
133                 )
134             } else {
135                 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
136                 val nextSfEntry = layersTrace.getFirstEntryWithOnDisplayAfter(cujEntry.endTimestamp)
137                 Utils.getFullTimestampAt(nextSfEntry, reader)
138             }
139 
140         return Timestamps.from(
141             elapsedNanos =
142                 max(cujEntry.endTimestamp.elapsedNanos, interpolatedEndTimestamp.elapsedNanos),
143             systemUptimeNanos =
144                 max(
145                     cujEntry.endTimestamp.systemUptimeNanos,
146                     interpolatedEndTimestamp.systemUptimeNanos,
147                 ),
148             unixNanos = max(cujEntry.endTimestamp.unixNanos, interpolatedEndTimestamp.unixNanos),
149         )
150     }
151 }
152