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         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(associatedTransition, reader)
130             } else {
131                 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
132                 val nextSfEntry = layersTrace.getFirstEntryWithOnDisplayAfter(cujEntry.endTimestamp)
133                 Utils.getFullTimestampAt(nextSfEntry, reader)
134             }
135 
136         return Timestamps.from(
137             elapsedNanos =
138                 max(cujEntry.endTimestamp.elapsedNanos, interpolatedEndTimestamp.elapsedNanos),
139             systemUptimeNanos =
140                 max(
141                     cujEntry.endTimestamp.systemUptimeNanos,
142                     interpolatedEndTimestamp.systemUptimeNanos
143                 ),
144             unixNanos = max(cujEntry.endTimestamp.unixNanos, interpolatedEndTimestamp.unixNanos)
145         )
146     }
147 }
148