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