1 /* 2 * 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.surfaceflinger.Display 23 import android.tools.traces.surfaceflinger.LayerTraceEntry 24 import android.tools.traces.wm.Transition 25 import android.util.Log 26 import kotlin.math.abs 27 28 object Utils { 29 const val LOG_TAG = "FlickerExtractorUtils" 30 interpolateStartTimestampFromTransitionnull31 fun interpolateStartTimestampFromTransition(transition: Transition, reader: Reader): Timestamp { 32 val wmTrace = reader.readWmTrace() ?: error("Missing WM trace") 33 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace") 34 val transactionsTrace = 35 reader.readTransactionsTrace() ?: error("Missing transactions trace") 36 37 val lastWmEntryBeforeTransitionWasSentToShell = try { 38 wmTrace.getEntryAt(transition.sendTime) 39 } catch (e: Exception) { 40 throw RuntimeException( 41 "Failed to get last WM entry before transition was sent to shell: $transition", e) 42 } 43 val elapsedNanos = lastWmEntryBeforeTransitionWasSentToShell.timestamp.elapsedNanos 44 val unixNanos = lastWmEntryBeforeTransitionWasSentToShell.timestamp.unixNanos 45 46 assert(elapsedNanos != 0L) { 47 "lastWmEntryBeforeTransitionWasSentToShell.timestamp.elapsedNanos is 0" 48 } 49 assert(unixNanos != 0L) { 50 "lastWmEntryBeforeTransitionWasSentToShell.timestamp.unixNanos is 0" 51 } 52 53 val startTransactionAppliedTimestamp = 54 transition.getStartTransaction(transactionsTrace)?.let { 55 layersTrace.getEntryForTransaction(it).timestamp 56 } 57 58 if (startTransactionAppliedTimestamp != null) { 59 require(startTransactionAppliedTimestamp.systemUptimeNanos != 0L) { 60 "Start transaction applied timestamp is missing system uptime" 61 } 62 } 63 64 // If we don't have a startTransactionAppliedTimestamp it's likely because the start 65 // transaction was merged into another transaction so we can't match the id, so we need to 66 // fallback on the send time reported on the WM side. 67 val systemUptimeNanos = 68 startTransactionAppliedTimestamp?.systemUptimeNanos 69 ?: transition.createTime.systemUptimeNanos 70 71 require(systemUptimeNanos != 0L) { 72 "Both startTransactionAppliedTimestamp and transition's create time are missing " + 73 "system uptime: $startTransactionAppliedTimestamp and $transition" 74 } 75 76 Log.d( 77 LOG_TAG, 78 "Interpolated start timestamp for $transition to: " + 79 "elapsed $elapsedNanos, uptime $systemUptimeNanos, unix $unixNanos", 80 ) 81 82 return Timestamps.from(elapsedNanos, systemUptimeNanos, unixNanos) 83 } 84 interpolateFinishTimestampFromTransitionnull85 fun interpolateFinishTimestampFromTransition( 86 transition: Transition, 87 reader: Reader, 88 debugString: String? = null, 89 ): Timestamp { 90 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace") 91 val wmTrace = reader.readWmTrace() ?: error("Missing WM trace") 92 val transactionsTrace = 93 reader.readTransactionsTrace() ?: error("Missing transactions trace") 94 95 // There is a delay between when we flag that transition as finished with the CUJ tags 96 // and when it is actually finished on the SF side. We try and account for that by 97 // checking when the finish transaction is actually applied. 98 // TODO: Figure out how to get the vSyncId that the Jank tracker actually gets to avoid 99 // relying on the transition and have a common end point. 100 val finishTransactionAppliedTimestamp = 101 transition.getFinishTransaction(transactionsTrace)?.let { 102 layersTrace.getEntryForTransaction(it).timestamp 103 } 104 105 val elapsedNanos: Long 106 val systemUptimeNanos: Long 107 val unixNanos: Long 108 val sfEntryAtTransitionFinished: LayerTraceEntry 109 if (finishTransactionAppliedTimestamp == null) { 110 // If we don't have a finishTransactionAppliedTimestamp it's likely because the finish 111 // transaction was merged into another transaction so we can't match the id, so we need 112 // to fallback on the finish time reported on the WM side. 113 val wmEntryAtTransitionFinished = 114 wmTrace.entries.firstOrNull { it.timestamp >= transition.finishTime } 115 116 elapsedNanos = 117 wmEntryAtTransitionFinished?.timestamp?.elapsedNanos 118 ?: transition.finishTime.elapsedNanos 119 120 unixNanos = 121 if (wmEntryAtTransitionFinished != null) { 122 wmEntryAtTransitionFinished.timestamp.unixNanos 123 } else { 124 require(wmTrace.entries.isNotEmpty()) { "WM trace should not be empty!" } 125 val closestWmEntry = 126 wmTrace.entries.minByOrNull { 127 abs(it.timestamp.elapsedNanos - transition.finishTime.elapsedNanos) 128 } ?: error("WM entry was unexpectedly empty!") 129 val offset = 130 closestWmEntry.timestamp.unixNanos - closestWmEntry.timestamp.elapsedNanos 131 transition.finishTime.elapsedNanos + offset 132 } 133 134 sfEntryAtTransitionFinished = 135 layersTrace.entries.firstOrNull { it.timestamp.unixNanos >= unixNanos } 136 ?: error("No SF entry for finish timestamp") 137 systemUptimeNanos = sfEntryAtTransitionFinished.timestamp.systemUptimeNanos 138 } else { 139 elapsedNanos = 140 (wmTrace.entries.firstOrNull { it.timestamp >= finishTransactionAppliedTimestamp } 141 ?: wmTrace.entries.last()) 142 .timestamp 143 .elapsedNanos 144 systemUptimeNanos = 145 layersTrace 146 .getEntryAt(finishTransactionAppliedTimestamp) 147 .timestamp 148 .systemUptimeNanos 149 unixNanos = 150 layersTrace.getEntryAt(finishTransactionAppliedTimestamp).timestamp.unixNanos 151 } 152 153 Log.d( 154 LOG_TAG, 155 "Interpolated finish timestamp for $transition to: " + 156 "elapsed $elapsedNanos, uptime $systemUptimeNanos, unix $unixNanos", 157 ) 158 return Timestamps.from(elapsedNanos, systemUptimeNanos, unixNanos) 159 } 160 getFullTimestampAtnull161 fun getFullTimestampAt(layersTraceEntry: LayerTraceEntry, reader: Reader): Timestamp { 162 val wmTrace = reader.readWmTrace() ?: error("Missing WM trace") 163 164 val elapsedNanos = 165 (wmTrace.entries.firstOrNull { it.timestamp >= layersTraceEntry.timestamp } 166 ?: wmTrace.entries.last()) 167 .timestamp 168 .elapsedNanos 169 val systemUptimeNanos = layersTraceEntry.timestamp.systemUptimeNanos 170 val unixNanos = layersTraceEntry.timestamp.unixNanos 171 172 return Timestamps.from(elapsedNanos, systemUptimeNanos, unixNanos) 173 } 174 getOnDisplayFornull175 fun getOnDisplayFor(layerTraceEntry: LayerTraceEntry): Display { 176 val displays = layerTraceEntry.displays.filter { !it.isVirtual } 177 require(displays.isNotEmpty()) { "Failed to get a display for provided entry" } 178 val onDisplays = displays.filter { it.isOn } 179 require(onDisplays.isNotEmpty()) { "No on displays found for entry" } 180 require(onDisplays.size == 1) { "More than one on display found!" } 181 return onDisplays.first() 182 } 183 } 184