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 {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 18import {TracesUtils} from 'test/unit/traces_utils'; 19import {TraceBuilder} from 'test/unit/trace_builder'; 20import {CustomQueryType} from './custom_query'; 21import {FrameMapper} from './frame_mapper'; 22import {AbsoluteFrameIndex} from './index_types'; 23import {MediaBasedTraceEntry} from './media_based_trace_entry'; 24import {Trace} from './trace'; 25import {Traces} from './traces'; 26import {TraceType} from './trace_type'; 27import {HierarchyTreeNode} from './tree_node/hierarchy_tree_node'; 28import {PropertyTreeNode} from './tree_node/property_tree_node'; 29 30describe('FrameMapper', () => { 31 const time0 = TimestampConverterUtils.makeRealTimestamp(0n); 32 const time1 = TimestampConverterUtils.makeRealTimestamp(1n); 33 const time2 = TimestampConverterUtils.makeRealTimestamp(2n); 34 const time3 = TimestampConverterUtils.makeRealTimestamp(3n); 35 const time4 = TimestampConverterUtils.makeRealTimestamp(4n); 36 const time5 = TimestampConverterUtils.makeRealTimestamp(5n); 37 const time6 = TimestampConverterUtils.makeRealTimestamp(6n); 38 const time7 = TimestampConverterUtils.makeRealTimestamp(7n); 39 const time8 = TimestampConverterUtils.makeRealTimestamp(8n); 40 const time10seconds = TimestampConverterUtils.makeRealTimestamp( 41 10n * 1000000000n, 42 ); 43 44 describe('ProtoLog <-> WindowManager', () => { 45 let protoLog: Trace<PropertyTreeNode>; 46 let windowManager: Trace<HierarchyTreeNode>; 47 let traces: Traces; 48 49 beforeAll(async () => { 50 // Frames F0 F1 51 // |<------>| |<->| 52 // PROTO_LOG: 0 1 2 3 4 5 53 // WINDOW_MANAGER: 0 1 54 // Time: 0 1 2 3 4 5 6 55 protoLog = new TraceBuilder<PropertyTreeNode>() 56 .setType(TraceType.PROTO_LOG) 57 .setEntries([ 58 'entry-0' as unknown as PropertyTreeNode, 59 'entry-1' as unknown as PropertyTreeNode, 60 'entry-2' as unknown as PropertyTreeNode, 61 'entry-3' as unknown as PropertyTreeNode, 62 'entry-4' as unknown as PropertyTreeNode, 63 'entry-5' as unknown as PropertyTreeNode, 64 ]) 65 .setTimestamps([time0, time1, time2, time4, time5, time6]) 66 .build(); 67 68 windowManager = new TraceBuilder<HierarchyTreeNode>() 69 .setType(TraceType.WINDOW_MANAGER) 70 .setEntries([ 71 'entry-0' as unknown as HierarchyTreeNode, 72 'entry-1' as unknown as HierarchyTreeNode, 73 ]) 74 .setTimestamps([time3, time5]) 75 .build(); 76 77 traces = new Traces(); 78 traces.addTrace(protoLog); 79 traces.addTrace(windowManager); 80 await new FrameMapper(traces).computeMapping(); 81 }); 82 83 it('associates entries/frames', async () => { 84 const expectedFrames = new Map< 85 AbsoluteFrameIndex, 86 Map<TraceType, Array<{}>> 87 >(); 88 expectedFrames.set( 89 0, 90 new Map<TraceType, Array<{}>>([ 91 [TraceType.PROTO_LOG, ['entry-0', 'entry-1', 'entry-2']], 92 [TraceType.WINDOW_MANAGER, ['entry-0']], 93 ]), 94 ); 95 expectedFrames.set( 96 1, 97 new Map<TraceType, Array<{}>>([ 98 [TraceType.PROTO_LOG, ['entry-3', 'entry-4']], 99 [TraceType.WINDOW_MANAGER, ['entry-1']], 100 ]), 101 ); 102 103 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 104 }); 105 }); 106 107 describe('IME <-> WindowManager', () => { 108 let ime: Trace<HierarchyTreeNode>; 109 let windowManager: Trace<HierarchyTreeNode>; 110 let traces: Traces; 111 112 beforeAll(async () => { 113 // IME: 0--1--2 3 114 // | | 115 // WINDOW_MANAGER: 0 1 2 116 // Time: 0 1 2 3 4 5 117 ime = new TraceBuilder<HierarchyTreeNode>() 118 .setType(TraceType.INPUT_METHOD_CLIENTS) 119 .setEntries([ 120 'entry-0' as unknown as HierarchyTreeNode, 121 'entry-1' as unknown as HierarchyTreeNode, 122 'entry-2' as unknown as HierarchyTreeNode, 123 'entry-3' as unknown as HierarchyTreeNode, 124 ]) 125 .setTimestamps([time0, time1, time2, time4]) 126 .build(); 127 128 windowManager = new TraceBuilder<HierarchyTreeNode>() 129 .setType(TraceType.WINDOW_MANAGER) 130 .setEntries([ 131 'entry-0' as unknown as HierarchyTreeNode, 132 'entry-1' as unknown as HierarchyTreeNode, 133 'entry-2' as unknown as HierarchyTreeNode, 134 ]) 135 .setTimestamps([time1, time4, time5]) 136 .build(); 137 138 traces = new Traces(); 139 traces.addTrace(ime); 140 traces.addTrace(windowManager); 141 await new FrameMapper(traces).computeMapping(); 142 }); 143 144 it('associates entries/frames', async () => { 145 const expectedFrames = new Map< 146 AbsoluteFrameIndex, 147 Map<TraceType, Array<{}>> 148 >(); 149 expectedFrames.set( 150 0, 151 new Map<TraceType, Array<{}>>([ 152 [TraceType.INPUT_METHOD_CLIENTS, ['entry-0', 'entry-1', 'entry-2']], 153 [TraceType.WINDOW_MANAGER, ['entry-0']], 154 ]), 155 ); 156 expectedFrames.set( 157 1, 158 new Map<TraceType, Array<{}>>([ 159 [TraceType.INPUT_METHOD_CLIENTS, ['entry-3']], 160 [TraceType.WINDOW_MANAGER, ['entry-1']], 161 ]), 162 ); 163 expectedFrames.set( 164 2, 165 new Map<TraceType, Array<{}>>([ 166 [TraceType.INPUT_METHOD_CLIENTS, []], 167 [TraceType.WINDOW_MANAGER, ['entry-2']], 168 ]), 169 ); 170 171 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 172 }); 173 }); 174 175 describe('WindowManager <-> Transactions', () => { 176 let windowManager: Trace<HierarchyTreeNode>; 177 let transactions: Trace<PropertyTreeNode>; 178 let traces: Traces; 179 180 beforeAll(async () => { 181 // WINDOW_MANAGER: 0 1 2 3 182 // | | | \ 183 // TRANSACTIONS: 0 1 2--3 4 5 ... 6 <-- ignored (not connected) because too far 184 // | | | | | | 185 // Frames: 0 1 2 3 4 ... 5 186 // Time: 0 1 2 3 4 5 6 ... 10s 187 windowManager = new TraceBuilder<HierarchyTreeNode>() 188 .setType(TraceType.WINDOW_MANAGER) 189 .setEntries([ 190 'entry-0' as unknown as HierarchyTreeNode, 191 'entry-1' as unknown as HierarchyTreeNode, 192 'entry-2' as unknown as HierarchyTreeNode, 193 'entry-3' as unknown as HierarchyTreeNode, 194 ]) 195 .setTimestamps([time1, time2, time4, time5]) 196 .build(); 197 198 transactions = new TraceBuilder<PropertyTreeNode>() 199 .setType(TraceType.TRANSACTIONS) 200 .setEntries([ 201 'entry-0' as unknown as PropertyTreeNode, 202 'entry-1' as unknown as PropertyTreeNode, 203 'entry-2' as unknown as PropertyTreeNode, 204 'entry-3' as unknown as PropertyTreeNode, 205 'entry-4' as unknown as PropertyTreeNode, 206 'entry-5' as unknown as PropertyTreeNode, 207 'entry-6' as unknown as PropertyTreeNode, 208 ]) 209 .setTimestamps([ 210 time0, 211 time1, 212 time2, 213 time3, 214 time4, 215 time5, 216 time10seconds, 217 ]) 218 .setFrame(0, 0) 219 .setFrame(1, 1) 220 .setFrame(2, 2) 221 .setFrame(3, 2) 222 .setFrame(4, 3) 223 .setFrame(5, 4) 224 .setFrame(6, 5) 225 .build(); 226 227 traces = new Traces(); 228 traces.addTrace(windowManager); 229 traces.addTrace(transactions); 230 await new FrameMapper(traces).computeMapping(); 231 }); 232 233 it('associates entries/frames', async () => { 234 const expectedFrames = new Map< 235 AbsoluteFrameIndex, 236 Map<TraceType, Array<{}>> 237 >(); 238 expectedFrames.set( 239 0, 240 new Map<TraceType, Array<{}>>([ 241 [TraceType.WINDOW_MANAGER, []], 242 [TraceType.TRANSACTIONS, ['entry-0']], 243 ]), 244 ); 245 expectedFrames.set( 246 1, 247 new Map<TraceType, Array<{}>>([ 248 [TraceType.WINDOW_MANAGER, ['entry-0']], 249 [TraceType.TRANSACTIONS, ['entry-1']], 250 ]), 251 ); 252 expectedFrames.set( 253 2, 254 new Map<TraceType, Array<{}>>([ 255 [TraceType.WINDOW_MANAGER, ['entry-1']], 256 [TraceType.TRANSACTIONS, ['entry-2', 'entry-3']], 257 ]), 258 ); 259 expectedFrames.set( 260 3, 261 new Map<TraceType, Array<{}>>([ 262 [TraceType.WINDOW_MANAGER, ['entry-2']], 263 [TraceType.TRANSACTIONS, ['entry-4']], 264 ]), 265 ); 266 expectedFrames.set( 267 4, 268 new Map<TraceType, Array<{}>>([ 269 [TraceType.WINDOW_MANAGER, ['entry-3']], 270 [TraceType.TRANSACTIONS, ['entry-5']], 271 ]), 272 ); 273 expectedFrames.set( 274 5, 275 new Map<TraceType, Array<{}>>([ 276 [TraceType.WINDOW_MANAGER, []], 277 [TraceType.TRANSACTIONS, ['entry-6']], 278 ]), 279 ); 280 281 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 282 }); 283 }); 284 285 describe('ViewCapture <-> SurfaceFlinger', () => { 286 let viewCapture: Trace<PropertyTreeNode>; 287 let surfaceFlinger: Trace<HierarchyTreeNode>; 288 let traces: Traces; 289 290 beforeAll(async () => { 291 // VIEW_CAPTURE: 0 1 2--- 3 292 // \ \ \ \ 293 // \ \ \ \ 294 // SURFACE_FLINGER: 0 1 2 3 295 // Time: 0 1 2 3 4 5 6 296 viewCapture = new TraceBuilder<PropertyTreeNode>() 297 .setType(TraceType.VIEW_CAPTURE) 298 .setEntries([ 299 'entry-0' as unknown as PropertyTreeNode, 300 'entry-1' as unknown as PropertyTreeNode, 301 'entry-2' as unknown as PropertyTreeNode, 302 'entry-3' as unknown as PropertyTreeNode, 303 ]) 304 .setTimestamps([time0, time1, time2, time5]) 305 .build(); 306 307 surfaceFlinger = new TraceBuilder<HierarchyTreeNode>() 308 .setType(TraceType.SURFACE_FLINGER) 309 .setEntries([ 310 'entry-0' as unknown as HierarchyTreeNode, 311 'entry-1' as unknown as HierarchyTreeNode, 312 'entry-2' as unknown as HierarchyTreeNode, 313 'entry-3' as unknown as HierarchyTreeNode, 314 ]) 315 .setTimestamps([time1, time3, time4, time6]) 316 .setFrame(0, 0) 317 .setFrame(1, 1) 318 .setFrame(2, 2) 319 .setFrame(3, 3) 320 .build(); 321 322 traces = new Traces(); 323 traces.addTrace(viewCapture); 324 traces.addTrace(surfaceFlinger); 325 await new FrameMapper(traces).computeMapping(); 326 }); 327 328 it('associates entries/frames', async () => { 329 const expectedFrames = new Map< 330 AbsoluteFrameIndex, 331 Map<TraceType, Array<{}>> 332 >(); 333 expectedFrames.set( 334 0, 335 new Map<TraceType, Array<{}>>([ 336 [TraceType.VIEW_CAPTURE, [await viewCapture.getEntry(0).getValue()]], 337 [ 338 TraceType.SURFACE_FLINGER, 339 [await surfaceFlinger.getEntry(0).getValue()], 340 ], 341 ]), 342 ); 343 expectedFrames.set( 344 1, 345 new Map<TraceType, Array<{}>>([ 346 [TraceType.VIEW_CAPTURE, [await viewCapture.getEntry(2).getValue()]], 347 [ 348 TraceType.SURFACE_FLINGER, 349 [await surfaceFlinger.getEntry(1).getValue()], 350 ], 351 ]), 352 ); 353 expectedFrames.set( 354 2, 355 new Map<TraceType, Array<{}>>([ 356 [TraceType.VIEW_CAPTURE, [await viewCapture.getEntry(2).getValue()]], 357 [ 358 TraceType.SURFACE_FLINGER, 359 [await surfaceFlinger.getEntry(2).getValue()], 360 ], 361 ]), 362 ); 363 expectedFrames.set( 364 3, 365 new Map<TraceType, Array<{}>>([ 366 [TraceType.VIEW_CAPTURE, [await viewCapture.getEntry(3).getValue()]], 367 [ 368 TraceType.SURFACE_FLINGER, 369 [await surfaceFlinger.getEntry(3).getValue()], 370 ], 371 ]), 372 ); 373 374 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 375 }); 376 }); 377 378 const TRACES_WITH_VSYNC_IDS = [ 379 TraceType.TRANSACTIONS, 380 TraceType.INPUT_EVENT_MERGED, 381 ]; 382 383 TRACES_WITH_VSYNC_IDS.forEach((traceType) => { 384 describe(`TraceType[${traceType}] <-> SurfaceFlinger`, () => { 385 let trace: Trace<PropertyTreeNode>; 386 let surfaceFlinger: Trace<HierarchyTreeNode>; 387 let traces: Traces; 388 389 beforeAll(async () => { 390 // TRACE: 0 1--2 3 4 391 // \ \ \ 392 // \ \ \ 393 // SURFACE_FLINGER: 0 1 2 394 trace = new TraceBuilder<PropertyTreeNode>() 395 .setType(traceType) 396 .setEntries([ 397 'entry-0' as unknown as PropertyTreeNode, 398 'entry-1' as unknown as PropertyTreeNode, 399 'entry-2' as unknown as PropertyTreeNode, 400 'entry-3' as unknown as PropertyTreeNode, 401 'entry-4' as unknown as PropertyTreeNode, 402 ]) 403 .setTimestamps([time0, time1, time2, time5, time6]) 404 .setParserCustomQueryResult(CustomQueryType.VSYNCID, [ 405 0n, 406 10n, 407 10n, 408 20n, 409 30n, 410 ]) 411 .build(); 412 413 surfaceFlinger = new TraceBuilder<HierarchyTreeNode>() 414 .setType(TraceType.SURFACE_FLINGER) 415 .setEntries([ 416 'entry-0' as unknown as HierarchyTreeNode, 417 'entry-1' as unknown as HierarchyTreeNode, 418 'entry-2' as unknown as HierarchyTreeNode, 419 ]) 420 .setTimestamps([time0, time1, time2]) 421 .setParserCustomQueryResult(CustomQueryType.VSYNCID, [0n, 10n, 20n]) 422 .build(); 423 424 traces = new Traces(); 425 traces.addTrace(trace); 426 traces.addTrace(surfaceFlinger); 427 await new FrameMapper(traces).computeMapping(); 428 }); 429 430 it('associates entries/frames', async () => { 431 const expectedFrames = new Map< 432 AbsoluteFrameIndex, 433 Map<TraceType, Array<{}>> 434 >(); 435 expectedFrames.set( 436 0, 437 new Map<TraceType, Array<{}>>([ 438 [traceType, [await trace.getEntry(0).getValue()]], 439 [ 440 TraceType.SURFACE_FLINGER, 441 [await surfaceFlinger.getEntry(0).getValue()], 442 ], 443 ]), 444 ); 445 expectedFrames.set( 446 1, 447 new Map<TraceType, Array<{}>>([ 448 [ 449 traceType, 450 [ 451 await trace.getEntry(1).getValue(), 452 await trace.getEntry(2).getValue(), 453 ], 454 ], 455 [ 456 TraceType.SURFACE_FLINGER, 457 [await surfaceFlinger.getEntry(1).getValue()], 458 ], 459 ]), 460 ); 461 expectedFrames.set( 462 2, 463 new Map<TraceType, Array<{}>>([ 464 [traceType, [await trace.getEntry(3).getValue()]], 465 [ 466 TraceType.SURFACE_FLINGER, 467 [await surfaceFlinger.getEntry(2).getValue()], 468 ], 469 ]), 470 ); 471 472 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 473 }); 474 }); 475 }); 476 477 describe('SurfaceFlinger <-> ScreenRecording', () => { 478 let surfaceFlinger: Trace<HierarchyTreeNode>; 479 let screenRecording: Trace<MediaBasedTraceEntry>; 480 let traces: Traces; 481 482 beforeAll(async () => { 483 // SURFACE_FLINGER: 0 1 2--- 3 4 5 6 484 // \ \ \ \ 485 // \ \ \ \ 486 // SCREEN_RECORDING: 0 1 2 3 4 ... 5 <-- ignored (not connected) because too far 487 // Time: 0 1 2 3 4 5 6 7 8 10s 488 surfaceFlinger = new TraceBuilder<HierarchyTreeNode>() 489 .setType(TraceType.SURFACE_FLINGER) 490 .setEntries([ 491 'entry-0' as unknown as HierarchyTreeNode, 492 'entry-1' as unknown as HierarchyTreeNode, 493 'entry-2' as unknown as HierarchyTreeNode, 494 'entry-3' as unknown as HierarchyTreeNode, 495 'entry-4' as unknown as HierarchyTreeNode, 496 'entry-5' as unknown as HierarchyTreeNode, 497 'entry-6' as unknown as HierarchyTreeNode, 498 ]) 499 .setTimestamps([time0, time1, time2, time4, time6, time7, time8]) 500 .build(); 501 502 screenRecording = new TraceBuilder<MediaBasedTraceEntry>() 503 .setType(TraceType.SCREEN_RECORDING) 504 .setEntries([ 505 'entry-0' as unknown as MediaBasedTraceEntry, 506 'entry-1' as unknown as MediaBasedTraceEntry, 507 'entry-2' as unknown as MediaBasedTraceEntry, 508 'entry-3' as unknown as MediaBasedTraceEntry, 509 'entry-4' as unknown as MediaBasedTraceEntry, 510 'entry-5' as unknown as MediaBasedTraceEntry, 511 ]) 512 .setTimestamps([time0, time3, time4, time5, time8, time10seconds]) 513 .build(); 514 515 traces = new Traces(); 516 traces.addTrace(surfaceFlinger); 517 traces.addTrace(screenRecording); 518 await new FrameMapper(traces).computeMapping(); 519 }); 520 521 it('associates entries/frames', async () => { 522 const expectedFrames = new Map< 523 AbsoluteFrameIndex, 524 Map<TraceType, Array<{}>> 525 >(); 526 expectedFrames.set( 527 0, 528 new Map<TraceType, Array<{}>>([ 529 [TraceType.SURFACE_FLINGER, []], 530 [TraceType.SCREEN_RECORDING, ['entry-0']], 531 ]), 532 ); 533 expectedFrames.set( 534 1, 535 new Map<TraceType, Array<{}>>([ 536 [TraceType.SURFACE_FLINGER, ['entry-2']], 537 [TraceType.SCREEN_RECORDING, ['entry-1']], 538 ]), 539 ); 540 expectedFrames.set( 541 2, 542 new Map<TraceType, Array<{}>>([ 543 [TraceType.SURFACE_FLINGER, ['entry-2']], 544 [TraceType.SCREEN_RECORDING, ['entry-2']], 545 ]), 546 ); 547 expectedFrames.set( 548 3, 549 new Map<TraceType, Array<{}>>([ 550 [TraceType.SURFACE_FLINGER, ['entry-3']], 551 [TraceType.SCREEN_RECORDING, ['entry-3']], 552 ]), 553 ); 554 expectedFrames.set( 555 4, 556 new Map<TraceType, Array<{}>>([ 557 [TraceType.SURFACE_FLINGER, ['entry-5']], 558 [TraceType.SCREEN_RECORDING, ['entry-4']], 559 ]), 560 ); 561 expectedFrames.set( 562 5, 563 new Map<TraceType, Array<{}>>([ 564 [TraceType.SURFACE_FLINGER, []], 565 [TraceType.SCREEN_RECORDING, ['entry-5']], 566 ]), 567 ); 568 569 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 570 }); 571 }); 572 573 it('supports multiple traces with same type', async () => { 574 // SURFACE_FLINGER_0: 0 575 // \ 576 // \ 577 // SURFACE_FLINGER_1: 0 \ 578 // \ | 579 // \| 580 // SCREEN_RECORDING: 0 581 // Time: 0 1 582 const surfaceFlinger0 = new TraceBuilder<HierarchyTreeNode>() 583 .setType(TraceType.SURFACE_FLINGER) 584 .setEntries(['entry-0' as unknown as HierarchyTreeNode]) 585 .setTimestamps([time0]) 586 .build(); 587 588 const surfaceFlinger1 = new TraceBuilder<HierarchyTreeNode>() 589 .setType(TraceType.SURFACE_FLINGER) 590 .setEntries(['entry-0' as unknown as HierarchyTreeNode]) 591 .setTimestamps([time0]) 592 .build(); 593 594 const screenRecording = new TraceBuilder<MediaBasedTraceEntry>() 595 .setType(TraceType.SCREEN_RECORDING) 596 .setEntries(['entry-0' as unknown as MediaBasedTraceEntry]) 597 .setTimestamps([time1]) 598 .build(); 599 600 const traces = new Traces(); 601 traces.addTrace(surfaceFlinger0); 602 traces.addTrace(surfaceFlinger1); 603 traces.addTrace(screenRecording); 604 await new FrameMapper(traces).computeMapping(); 605 606 expect(surfaceFlinger0.getEntry(0).getFramesRange()).toEqual({ 607 start: 0, 608 end: 1, 609 }); 610 expect(surfaceFlinger1.getEntry(0).getFramesRange()).toEqual({ 611 start: 0, 612 end: 1, 613 }); 614 expect(screenRecording.getEntry(0).getFramesRange()).toEqual({ 615 start: 0, 616 end: 1, 617 }); 618 }); 619}); 620