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 17import {assertDefined} from 'common/assert_utils'; 18import {CustomQueryType} from './custom_query'; 19import {FrameMapBuilder} from './frame_map_builder'; 20import {FramesRange, Trace, TraceEntry} from './trace'; 21import {Traces} from './traces'; 22import {TraceType} from './trace_type'; 23 24export class FrameMapper { 25 // Value used to narrow time-based searches of corresponding trace entries 26 private static readonly MAX_UI_PIPELINE_LATENCY_NS = 2000000000n; // 2 seconds 27 28 constructor(private traces: Traces) {} 29 30 async computeMapping() { 31 this.pickMostReliableTraceAndSetInitialFrameInfo(); 32 await this.propagateFrameInfoToOtherTraces(); 33 } 34 35 private pickMostReliableTraceAndSetInitialFrameInfo() { 36 const TRACES_IN_PREFERENCE_ORDER = [ 37 TraceType.SCREEN_RECORDING, 38 TraceType.SURFACE_FLINGER, 39 TraceType.WINDOW_MANAGER, 40 ]; 41 42 const type = TRACES_IN_PREFERENCE_ORDER.find( 43 (type) => this.traces.getTrace(type) !== undefined, 44 ); 45 if (type === undefined) { 46 return; 47 } 48 49 const trace = assertDefined(this.traces.getTrace(type)); 50 const frameMapBuilder = new FrameMapBuilder( 51 trace.lengthEntries, 52 trace.lengthEntries, 53 ); 54 55 for (let i = 0; i < trace.lengthEntries; ++i) { 56 frameMapBuilder.setFrames(i, {start: i, end: i + 1}); 57 } 58 59 const frameMap = frameMapBuilder.build(); 60 trace.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange()); 61 } 62 63 private async propagateFrameInfoToOtherTraces() { 64 await this.tryPropagateMapping( 65 TraceType.SCREEN_RECORDING, 66 TraceType.SURFACE_FLINGER, 67 this.propagateFromScreenRecordingToSurfaceFlinger, 68 ); 69 await this.tryPropagateMapping( 70 TraceType.SURFACE_FLINGER, 71 TraceType.TRANSACTIONS, 72 this.propagateFromSurfaceFlingerToTransactions, 73 ); 74 await this.tryPropagateMapping( 75 TraceType.SURFACE_FLINGER, 76 TraceType.VIEW_CAPTURE, 77 this.propagateFromSurfaceFlingerToViewCapture, 78 ); 79 await this.tryPropagateMapping( 80 TraceType.TRANSACTIONS, 81 TraceType.WINDOW_MANAGER, 82 this.propagateFromTransactionsToWindowManager, 83 ); 84 await this.tryPropagateMapping( 85 TraceType.WINDOW_MANAGER, 86 TraceType.PROTO_LOG, 87 this.propagateFromWindowManagerToProtoLog, 88 ); 89 await this.tryPropagateMapping( 90 TraceType.WINDOW_MANAGER, 91 TraceType.INPUT_METHOD_CLIENTS, 92 this.propagateFromWindowManagerToIme, 93 ); 94 await this.tryPropagateMapping( 95 TraceType.WINDOW_MANAGER, 96 TraceType.INPUT_METHOD_MANAGER_SERVICE, 97 this.propagateFromWindowManagerToIme, 98 ); 99 await this.tryPropagateMapping( 100 TraceType.WINDOW_MANAGER, 101 TraceType.INPUT_METHOD_SERVICE, 102 this.propagateFromWindowManagerToIme, 103 ); 104 } 105 106 private async propagateFromScreenRecordingToSurfaceFlinger( 107 screenRecording: Trace<object>, 108 surfaceFlinger: Trace<object>, 109 frameMapBuilder: FrameMapBuilder, 110 ) { 111 screenRecording.forEachEntry((srcEntry) => { 112 const startSearchTime = srcEntry 113 .getTimestamp() 114 .add(-FrameMapper.MAX_UI_PIPELINE_LATENCY_NS); 115 const endSearchTime = srcEntry.getTimestamp(); 116 const matches = surfaceFlinger.sliceTime(startSearchTime, endSearchTime); 117 if (matches.lengthEntries > 0) { 118 const dstEntry = matches.getEntry(matches.lengthEntries - 1); 119 frameMapBuilder.setFrames( 120 dstEntry.getIndex(), 121 srcEntry.getFramesRange(), 122 ); 123 } 124 }); 125 } 126 127 private async propagateFromSurfaceFlingerToTransactions( 128 surfaceFlinger: Trace<object>, 129 transactions: Trace<object>, 130 frameMapBuilder: FrameMapBuilder, 131 ) { 132 const transactionEntries = await transactions.customQuery( 133 CustomQueryType.VSYNCID, 134 ); 135 136 const surfaceFlingerEntries = await surfaceFlinger.customQuery( 137 CustomQueryType.VSYNCID, 138 ); 139 140 const vsyncIdToFrames = new Map<bigint, FramesRange>(); 141 142 surfaceFlingerEntries.forEach((srcEntry) => { 143 const vsyncId = srcEntry.getValue(); 144 const srcFrames = srcEntry.getFramesRange(); 145 if (!srcFrames) { 146 return; 147 } 148 let frames = vsyncIdToFrames.get(vsyncId); 149 if (!frames) { 150 frames = {start: Number.MAX_VALUE, end: Number.MIN_VALUE}; 151 } 152 frames.start = Math.min(frames.start, srcFrames.start); 153 frames.end = Math.max(frames.end, srcFrames.end); 154 vsyncIdToFrames.set(vsyncId, frames); 155 }); 156 157 transactionEntries.forEach((dstEntry) => { 158 const vsyncId = dstEntry.getValue(); 159 const frames = vsyncIdToFrames.get(vsyncId); 160 if (frames === undefined) { 161 return; 162 } 163 frameMapBuilder.setFrames(dstEntry.getIndex(), frames); 164 }); 165 } 166 167 private async propagateFromSurfaceFlingerToViewCapture( 168 surfaceFlinger: Trace<object>, 169 viewCapture: Trace<object>, 170 frameMapBuilder: FrameMapBuilder, 171 ) { 172 surfaceFlinger.forEachEntry((srcEntry) => { 173 const dstEntry = viewCapture.findLastLowerEntry(srcEntry.getTimestamp()); 174 if (!dstEntry) { 175 return; 176 } 177 frameMapBuilder.setFrames(dstEntry.getIndex(), srcEntry.getFramesRange()); 178 }); 179 } 180 181 private async propagateFromTransactionsToWindowManager( 182 transactions: Trace<object>, 183 windowManager: Trace<object>, 184 frameMapBuilder: FrameMapBuilder, 185 ) { 186 let prevWindowManagerEntry: TraceEntry<object> | undefined; 187 windowManager.forEachEntry((windowManagerEntry) => { 188 if (prevWindowManagerEntry) { 189 const matches = transactions.sliceTime( 190 prevWindowManagerEntry.getTimestamp(), 191 windowManagerEntry.getTimestamp(), 192 ); 193 frameMapBuilder.setFrames( 194 prevWindowManagerEntry.getIndex(), 195 matches.getFramesRange(), 196 ); 197 } 198 prevWindowManagerEntry = windowManagerEntry; 199 }); 200 201 if (windowManager.lengthEntries > 0) { 202 const lastWindowManagerEntry = windowManager.getEntry(-1); 203 const startSearchTime = lastWindowManagerEntry.getTimestamp(); 204 const endSearchTime = startSearchTime.add( 205 FrameMapper.MAX_UI_PIPELINE_LATENCY_NS, 206 ); 207 const matches = transactions.sliceTime(startSearchTime, endSearchTime); 208 frameMapBuilder.setFrames( 209 lastWindowManagerEntry.getIndex(), 210 matches.getFramesRange(), 211 ); 212 } 213 } 214 215 private async propagateFromWindowManagerToProtoLog( 216 windowManager: Trace<object>, 217 protoLog: Trace<object>, 218 frameMapBuilder: FrameMapBuilder, 219 ) { 220 windowManager.forEachEntry((prevSrcEntry) => { 221 const srcEntryIndex = prevSrcEntry.getIndex() + 1; 222 const srcEntry = 223 srcEntryIndex < windowManager.lengthEntries 224 ? windowManager.getEntry(srcEntryIndex) 225 : undefined; 226 if (srcEntry === undefined) { 227 return; 228 } 229 const startSearchTime = prevSrcEntry.getTimestamp().add(1n); 230 const endSearchTime = srcEntry.getTimestamp().add(1n); 231 const matches = protoLog.sliceTime(startSearchTime, endSearchTime); 232 matches.forEachEntry((dstEntry) => { 233 frameMapBuilder.setFrames( 234 dstEntry.getIndex(), 235 srcEntry.getFramesRange(), 236 ); 237 }); 238 }); 239 240 if (windowManager.lengthEntries > 0) { 241 const firstEntry = windowManager.getEntry(0); 242 const startSearchTime = firstEntry 243 .getTimestamp() 244 .add(-FrameMapper.MAX_UI_PIPELINE_LATENCY_NS); 245 const endSearchTime = firstEntry.getTimestamp().add(1n); 246 const matches = protoLog.sliceTime(startSearchTime, endSearchTime); 247 matches.forEachEntry((dstEntry) => { 248 frameMapBuilder.setFrames( 249 dstEntry.getIndex(), 250 firstEntry.getFramesRange(), 251 ); 252 }); 253 } 254 } 255 256 private async propagateFromWindowManagerToIme( 257 windowManager: Trace<object>, 258 ime: Trace<object>, 259 frameMapBuilder: FrameMapBuilder, 260 ) { 261 // Value used to narrow time-based searches of corresponding WindowManager entries 262 const MAX_TIME_DIFFERENCE_NS = 200000000n; // 200 ms 263 264 const abs = (n: bigint): bigint => (n < 0n ? -n : n); 265 266 ime.forEachEntry((dstEntry) => { 267 const srcEntry = windowManager.findClosestEntry(dstEntry.getTimestamp()); 268 if (!srcEntry) { 269 return; 270 } 271 const timeDifferenceNs = abs( 272 srcEntry.getTimestamp().getValueNs() - 273 dstEntry.getTimestamp().getValueNs(), 274 ); 275 if (timeDifferenceNs > MAX_TIME_DIFFERENCE_NS) { 276 return; 277 } 278 frameMapBuilder.setFrames(dstEntry.getIndex(), srcEntry.getFramesRange()); 279 }); 280 } 281 282 private async tryPropagateMapping( 283 srcTraceType: TraceType, 284 dstTraceType: TraceType, 285 mappingLogic: ( 286 srcTrace: Trace<{}>, 287 dstTrace: Trace<{}>, 288 frameMapBuilder: FrameMapBuilder, 289 ) => Promise<void>, 290 ) { 291 const srcTrace = this.traces.getTrace(srcTraceType); 292 if (!srcTrace || !srcTrace.hasFrameInfo()) { 293 return; 294 } 295 296 const promises = this.traces 297 .getTraces(dstTraceType) 298 .map(async (dstTrace) => { 299 const framesRange = srcTrace.getFramesRange(); 300 const lengthFrames = framesRange ? framesRange.end : 0; 301 const frameMapBuilder = new FrameMapBuilder( 302 dstTrace.lengthEntries, 303 lengthFrames, 304 ); 305 306 await mappingLogic(srcTrace, dstTrace, frameMapBuilder); 307 308 const frameMap = frameMapBuilder.build(); 309 dstTrace.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange()); 310 }); 311 312 await Promise.all(promises); 313 } 314} 315