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