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 {ScreenRecordingTraceEntry} from './screen_recording'; 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 describe('Transactions <-> SurfaceFlinger', () => { 379 let transactions: Trace<PropertyTreeNode>; 380 let surfaceFlinger: Trace<HierarchyTreeNode>; 381 let traces: Traces; 382 383 beforeAll(async () => { 384 // TRANSACTIONS: 0 1--2 3 4 385 // \ \ \ 386 // \ \ \ 387 // SURFACE_FLINGER: 0 1 2 388 transactions = new TraceBuilder<PropertyTreeNode>() 389 .setType(TraceType.TRANSACTIONS) 390 .setEntries([ 391 'entry-0' as unknown as PropertyTreeNode, 392 'entry-1' as unknown as PropertyTreeNode, 393 'entry-2' as unknown as PropertyTreeNode, 394 'entry-3' as unknown as PropertyTreeNode, 395 'entry-4' as unknown as PropertyTreeNode, 396 ]) 397 .setTimestamps([time0, time1, time2, time5, time6]) 398 .setParserCustomQueryResult(CustomQueryType.VSYNCID, [ 399 0n, 400 10n, 401 10n, 402 20n, 403 30n, 404 ]) 405 .build(); 406 407 surfaceFlinger = new TraceBuilder<HierarchyTreeNode>() 408 .setType(TraceType.SURFACE_FLINGER) 409 .setEntries([ 410 'entry-0' as unknown as HierarchyTreeNode, 411 'entry-1' as unknown as HierarchyTreeNode, 412 'entry-2' as unknown as HierarchyTreeNode, 413 ]) 414 .setTimestamps([time0, time1, time2]) 415 .setParserCustomQueryResult(CustomQueryType.VSYNCID, [0n, 10n, 20n]) 416 .build(); 417 418 traces = new Traces(); 419 traces.addTrace(transactions); 420 traces.addTrace(surfaceFlinger); 421 await new FrameMapper(traces).computeMapping(); 422 }); 423 424 it('associates entries/frames', async () => { 425 const expectedFrames = new Map< 426 AbsoluteFrameIndex, 427 Map<TraceType, Array<{}>> 428 >(); 429 expectedFrames.set( 430 0, 431 new Map<TraceType, Array<{}>>([ 432 [TraceType.TRANSACTIONS, [await transactions.getEntry(0).getValue()]], 433 [ 434 TraceType.SURFACE_FLINGER, 435 [await surfaceFlinger.getEntry(0).getValue()], 436 ], 437 ]), 438 ); 439 expectedFrames.set( 440 1, 441 new Map<TraceType, Array<{}>>([ 442 [ 443 TraceType.TRANSACTIONS, 444 [ 445 await transactions.getEntry(1).getValue(), 446 await transactions.getEntry(2).getValue(), 447 ], 448 ], 449 [ 450 TraceType.SURFACE_FLINGER, 451 [await surfaceFlinger.getEntry(1).getValue()], 452 ], 453 ]), 454 ); 455 expectedFrames.set( 456 2, 457 new Map<TraceType, Array<{}>>([ 458 [TraceType.TRANSACTIONS, [await transactions.getEntry(3).getValue()]], 459 [ 460 TraceType.SURFACE_FLINGER, 461 [await surfaceFlinger.getEntry(2).getValue()], 462 ], 463 ]), 464 ); 465 466 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 467 }); 468 }); 469 470 describe('SurfaceFlinger <-> ScreenRecording', () => { 471 let surfaceFlinger: Trace<HierarchyTreeNode>; 472 let screenRecording: Trace<ScreenRecordingTraceEntry>; 473 let traces: Traces; 474 475 beforeAll(async () => { 476 // SURFACE_FLINGER: 0 1 2--- 3 4 5 6 477 // \ \ \ \ 478 // \ \ \ \ 479 // SCREEN_RECORDING: 0 1 2 3 4 ... 5 <-- ignored (not connected) because too far 480 // Time: 0 1 2 3 4 5 6 7 8 10s 481 surfaceFlinger = new TraceBuilder<HierarchyTreeNode>() 482 .setType(TraceType.SURFACE_FLINGER) 483 .setEntries([ 484 'entry-0' as unknown as HierarchyTreeNode, 485 'entry-1' as unknown as HierarchyTreeNode, 486 'entry-2' as unknown as HierarchyTreeNode, 487 'entry-3' as unknown as HierarchyTreeNode, 488 'entry-4' as unknown as HierarchyTreeNode, 489 'entry-5' as unknown as HierarchyTreeNode, 490 'entry-6' as unknown as HierarchyTreeNode, 491 ]) 492 .setTimestamps([time0, time1, time2, time4, time6, time7, time8]) 493 .build(); 494 495 screenRecording = new TraceBuilder<ScreenRecordingTraceEntry>() 496 .setType(TraceType.SCREEN_RECORDING) 497 .setEntries([ 498 'entry-0' as unknown as ScreenRecordingTraceEntry, 499 'entry-1' as unknown as ScreenRecordingTraceEntry, 500 'entry-2' as unknown as ScreenRecordingTraceEntry, 501 'entry-3' as unknown as ScreenRecordingTraceEntry, 502 'entry-4' as unknown as ScreenRecordingTraceEntry, 503 'entry-5' as unknown as ScreenRecordingTraceEntry, 504 ]) 505 .setTimestamps([time0, time3, time4, time5, time8, time10seconds]) 506 .build(); 507 508 traces = new Traces(); 509 traces.addTrace(surfaceFlinger); 510 traces.addTrace(screenRecording); 511 await new FrameMapper(traces).computeMapping(); 512 }); 513 514 it('associates entries/frames', async () => { 515 const expectedFrames = new Map< 516 AbsoluteFrameIndex, 517 Map<TraceType, Array<{}>> 518 >(); 519 expectedFrames.set( 520 0, 521 new Map<TraceType, Array<{}>>([ 522 [TraceType.SURFACE_FLINGER, []], 523 [TraceType.SCREEN_RECORDING, ['entry-0']], 524 ]), 525 ); 526 expectedFrames.set( 527 1, 528 new Map<TraceType, Array<{}>>([ 529 [TraceType.SURFACE_FLINGER, ['entry-2']], 530 [TraceType.SCREEN_RECORDING, ['entry-1']], 531 ]), 532 ); 533 expectedFrames.set( 534 2, 535 new Map<TraceType, Array<{}>>([ 536 [TraceType.SURFACE_FLINGER, ['entry-2']], 537 [TraceType.SCREEN_RECORDING, ['entry-2']], 538 ]), 539 ); 540 expectedFrames.set( 541 3, 542 new Map<TraceType, Array<{}>>([ 543 [TraceType.SURFACE_FLINGER, ['entry-3']], 544 [TraceType.SCREEN_RECORDING, ['entry-3']], 545 ]), 546 ); 547 expectedFrames.set( 548 4, 549 new Map<TraceType, Array<{}>>([ 550 [TraceType.SURFACE_FLINGER, ['entry-5']], 551 [TraceType.SCREEN_RECORDING, ['entry-4']], 552 ]), 553 ); 554 expectedFrames.set( 555 5, 556 new Map<TraceType, Array<{}>>([ 557 [TraceType.SURFACE_FLINGER, []], 558 [TraceType.SCREEN_RECORDING, ['entry-5']], 559 ]), 560 ); 561 562 expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames); 563 }); 564 }); 565 566 it('supports multiple traces with same type', async () => { 567 // SURFACE_FLINGER_0: 0 568 // \ 569 // \ 570 // SURFACE_FLINGER_1: 0 \ 571 // \ | 572 // \| 573 // SCREEN_RECORDING: 0 574 // Time: 0 1 575 const surfaceFlinger0 = new TraceBuilder<HierarchyTreeNode>() 576 .setType(TraceType.SURFACE_FLINGER) 577 .setEntries(['entry-0' as unknown as HierarchyTreeNode]) 578 .setTimestamps([time0]) 579 .build(); 580 581 const surfaceFlinger1 = new TraceBuilder<HierarchyTreeNode>() 582 .setType(TraceType.SURFACE_FLINGER) 583 .setEntries(['entry-0' as unknown as HierarchyTreeNode]) 584 .setTimestamps([time0]) 585 .build(); 586 587 const screenRecording = new TraceBuilder<ScreenRecordingTraceEntry>() 588 .setType(TraceType.SCREEN_RECORDING) 589 .setEntries(['entry-0' as unknown as ScreenRecordingTraceEntry]) 590 .setTimestamps([time1]) 591 .build(); 592 593 const traces = new Traces(); 594 traces.addTrace(surfaceFlinger0); 595 traces.addTrace(surfaceFlinger1); 596 traces.addTrace(screenRecording); 597 await new FrameMapper(traces).computeMapping(); 598 599 expect(surfaceFlinger0.getEntry(0).getFramesRange()).toEqual({ 600 start: 0, 601 end: 1, 602 }); 603 expect(surfaceFlinger1.getEntry(0).getFramesRange()).toEqual({ 604 start: 0, 605 end: 1, 606 }); 607 expect(screenRecording.getEntry(0).getFramesRange()).toEqual({ 608 start: 0, 609 end: 1, 610 }); 611 }); 612}); 613