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 kotlin.math.abs 26 27 object Utils { interpolateStartTimestampFromTransitionnull28 fun interpolateStartTimestampFromTransition(transition: Transition, reader: Reader): Timestamp { 29 val wmTrace = reader.readWmTrace() ?: error("Missing WM trace") 30 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace") 31 val transactionsTrace = 32 reader.readTransactionsTrace() ?: error("Missing transactions trace") 33 34 val lastWmEntryBeforeTransitionCreated = wmTrace.getEntryAt(transition.createTime) 35 val elapsedNanos = lastWmEntryBeforeTransitionCreated.timestamp.elapsedNanos 36 val unixNanos = lastWmEntryBeforeTransitionCreated.timestamp.unixNanos 37 38 val startTransactionAppliedTimestamp = 39 transition.getStartTransaction(transactionsTrace)?.let { 40 layersTrace.getEntryForTransaction(it).timestamp 41 } 42 43 // If we don't have a startTransactionAppliedTimestamp it's likely because the start 44 // transaction was merged into another transaction so we can't match the id, so we need to 45 // fallback on the send time reported on the WM side. 46 val systemUptimeNanos = 47 startTransactionAppliedTimestamp?.systemUptimeNanos 48 ?: transition.createTime.systemUptimeNanos 49 50 // This fallback doesn't really work because then systemUptimeNanos is 0... 51 return Timestamps.from(elapsedNanos, systemUptimeNanos, unixNanos) 52 } 53 interpolateFinishTimestampFromTransitionnull54 fun interpolateFinishTimestampFromTransition( 55 transition: Transition, 56 reader: Reader 57 ): Timestamp { 58 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace") 59 val wmTrace = reader.readWmTrace() ?: error("Missing WM trace") 60 val transactionsTrace = 61 reader.readTransactionsTrace() ?: error("Missing transactions trace") 62 63 // There is a delay between when we flag that transition as finished with the CUJ tags 64 // and when it is actually finished on the SF side. We try and account for that by 65 // checking when the finish transaction is actually applied. 66 // TODO: Figure out how to get the vSyncId that the Jank tracker actually gets to avoid 67 // relying on the transition and have a common end point. 68 val finishTransactionAppliedTimestamp = 69 transition.getFinishTransaction(transactionsTrace)?.let { 70 layersTrace.getEntryForTransaction(it).timestamp 71 } 72 73 val elapsedNanos: Long 74 val systemUptimeNanos: Long 75 val unixNanos: Long 76 val sfEntryAtTransitionFinished: LayerTraceEntry 77 if (finishTransactionAppliedTimestamp == null) { 78 // If we don't have a finishTransactionAppliedTimestamp it's likely because the finish 79 // transaction was merged into another transaction so we can't match the id, so we need 80 // to fallback on the finish time reported on the WM side. 81 val wmEntryAtTransitionFinished = 82 wmTrace.entries.firstOrNull { it.timestamp >= transition.finishTime } 83 84 elapsedNanos = 85 wmEntryAtTransitionFinished?.timestamp?.elapsedNanos 86 ?: transition.finishTime.elapsedNanos 87 88 unixNanos = 89 if (wmEntryAtTransitionFinished != null) { 90 wmEntryAtTransitionFinished.timestamp.unixNanos 91 } else { 92 require(wmTrace.entries.isNotEmpty()) { "WM trace should not be empty!" } 93 val closestWmEntry = 94 wmTrace.entries.minByOrNull { 95 abs(it.timestamp.elapsedNanos - transition.finishTime.elapsedNanos) 96 } 97 ?: error("WM entry was unexpectedly empty!") 98 val offset = 99 closestWmEntry.timestamp.unixNanos - closestWmEntry.timestamp.elapsedNanos 100 transition.finishTime.elapsedNanos + offset 101 } 102 103 sfEntryAtTransitionFinished = 104 layersTrace.entries.firstOrNull { it.timestamp.unixNanos >= unixNanos } 105 ?: error("No SF entry for finish timestamp") 106 systemUptimeNanos = sfEntryAtTransitionFinished.timestamp.systemUptimeNanos 107 } else { 108 elapsedNanos = 109 (wmTrace.entries.firstOrNull { it.timestamp >= finishTransactionAppliedTimestamp } 110 ?: error( 111 "No WM trace entry with timestamp greater than or equal to the " + 112 "layers trace the finish transaction was applied in" 113 )) 114 .timestamp 115 .elapsedNanos 116 systemUptimeNanos = 117 layersTrace 118 .getEntryAt(finishTransactionAppliedTimestamp) 119 .timestamp 120 .systemUptimeNanos 121 unixNanos = 122 layersTrace.getEntryAt(finishTransactionAppliedTimestamp).timestamp.unixNanos 123 } 124 125 return Timestamps.from(elapsedNanos, systemUptimeNanos, unixNanos) 126 } 127 getFullTimestampAtnull128 fun getFullTimestampAt(layersTraceEntry: LayerTraceEntry, reader: Reader): Timestamp { 129 val wmTrace = reader.readWmTrace() ?: error("Missing WM trace") 130 131 val elapsedNanos = 132 (wmTrace.entries.firstOrNull { it.timestamp >= layersTraceEntry.timestamp } 133 ?: wmTrace.entries.last()) 134 .timestamp 135 .elapsedNanos 136 val systemUptimeNanos = layersTraceEntry.timestamp.systemUptimeNanos 137 val unixNanos = layersTraceEntry.timestamp.unixNanos 138 139 return Timestamps.from(elapsedNanos, systemUptimeNanos, unixNanos) 140 } 141 getOnDisplayFornull142 fun getOnDisplayFor(layerTraceEntry: LayerTraceEntry): Display { 143 val displays = layerTraceEntry.displays.filter { !it.isVirtual } 144 require(displays.isNotEmpty()) { "Failed to get a display for provided entry" } 145 val onDisplays = displays.filter { it.isOn } 146 require(onDisplays.isNotEmpty()) { "No on displays found for entry" } 147 require(onDisplays.size == 1) { "More than one on display found!" } 148 return onDisplays.first() 149 } 150 } 151