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 {FileUtils} from 'common/file_utils'; 19import {TimeRange} from 'common/time'; 20import {UserWarning} from 'messaging/user_warning'; 21import {TraceHasOldData, TraceOverridden} from 'messaging/user_warnings'; 22import {FileAndParser} from 'parsers/file_and_parser'; 23import {FileAndParsers} from 'parsers/file_and_parsers'; 24import {ParserBuilder} from 'test/unit/parser_builder'; 25import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 26import {Parser} from 'trace/parser'; 27import {TraceFile} from 'trace/trace_file'; 28import {TraceType} from 'trace/trace_type'; 29import {LoadedParsers} from './loaded_parsers'; 30 31describe('LoadedParsers', () => { 32 const realZeroTimestamp = TimestampConverterUtils.makeRealTimestamp(0n); 33 const elapsedZeroTimestamp = TimestampConverterUtils.makeElapsedTimestamp(0n); 34 const oldTimestamps = [ 35 realZeroTimestamp, 36 TimestampConverterUtils.makeRealTimestamp(1n), 37 TimestampConverterUtils.makeRealTimestamp(2n), 38 TimestampConverterUtils.makeRealTimestamp(3n), 39 TimestampConverterUtils.makeRealTimestamp(4n), 40 ]; 41 42 const elapsedTimestamps = [ 43 elapsedZeroTimestamp, 44 TimestampConverterUtils.makeElapsedTimestamp(1n), 45 TimestampConverterUtils.makeElapsedTimestamp(2n), 46 TimestampConverterUtils.makeElapsedTimestamp(3n), 47 TimestampConverterUtils.makeElapsedTimestamp(4n), 48 ]; 49 50 const timestamps = [ 51 TimestampConverterUtils.makeRealTimestamp(5n * 60n * 1000000000n + 10n), // 5m10ns 52 TimestampConverterUtils.makeRealTimestamp(5n * 60n * 1000000000n + 11n), // 5m11ns 53 TimestampConverterUtils.makeRealTimestamp(5n * 60n * 1000000000n + 12n), // 5m12ns 54 ]; 55 56 const filename = 'filename'; 57 58 const parserSf0 = new ParserBuilder<object>() 59 .setType(TraceType.SURFACE_FLINGER) 60 .setTimestamps(timestamps) 61 .setDescriptors([filename]) 62 .build(); 63 const parserSf1 = new ParserBuilder<object>() 64 .setType(TraceType.SURFACE_FLINGER) 65 .setTimestamps(timestamps) 66 .setDescriptors([filename]) 67 .build(); 68 const parserSf_longButOldData = new ParserBuilder<object>() 69 .setType(TraceType.SURFACE_FLINGER) 70 .setTimestamps(oldTimestamps) 71 .setDescriptors([filename]) 72 .build(); 73 const parserSf_empty = new ParserBuilder<object>() 74 .setType(TraceType.SURFACE_FLINGER) 75 .setTimestamps([]) 76 .setDescriptors([filename]) 77 .build(); 78 const parserSf_elapsed = new ParserBuilder<object>() 79 .setType(TraceType.SURFACE_FLINGER) 80 .setTimestamps(elapsedTimestamps) 81 .setDescriptors([filename]) 82 .setNoOffsets(true) 83 .build(); 84 const parserWm0 = new ParserBuilder<object>() 85 .setType(TraceType.WINDOW_MANAGER) 86 .setTimestamps(timestamps) 87 .setDescriptors([filename]) 88 .build(); 89 const parserWm1 = new ParserBuilder<object>() 90 .setType(TraceType.WINDOW_MANAGER) 91 .setTimestamps(timestamps) 92 .setDescriptors([filename]) 93 .build(); 94 const parserWm_dump = new ParserBuilder<object>() 95 .setType(TraceType.WINDOW_MANAGER) 96 .setTimestamps([realZeroTimestamp]) 97 .setDescriptors([filename]) 98 .build(); 99 const parserWm_elapsed = new ParserBuilder<object>() 100 .setType(TraceType.WINDOW_MANAGER) 101 .setTimestamps(elapsedTimestamps) 102 .setDescriptors([filename]) 103 .setNoOffsets(true) 104 .build(); 105 const parserWmTransitions = new ParserBuilder<object>() 106 .setType(TraceType.WM_TRANSITION) 107 .setTimestamps([ 108 elapsedZeroTimestamp, 109 elapsedZeroTimestamp, 110 elapsedZeroTimestamp, 111 ]) 112 .setDescriptors([filename]) 113 .build(); 114 const parserEventlog = new ParserBuilder<object>() 115 .setType(TraceType.EVENT_LOG) 116 .setTimestamps(timestamps) 117 .setDescriptors([filename]) 118 .setNoOffsets(true) 119 .build(); 120 const parserScreenRecording = new ParserBuilder<object>() 121 .setType(TraceType.SCREEN_RECORDING) 122 .setTimestamps(timestamps) 123 .setDescriptors([filename]) 124 .build(); 125 const parserViewCapture0 = new ParserBuilder<object>() 126 .setType(TraceType.VIEW_CAPTURE) 127 .setEntries([]) 128 .setDescriptors([filename]) 129 .build(); 130 const parserViewCapture1 = new ParserBuilder<object>() 131 .setType(TraceType.VIEW_CAPTURE) 132 .setEntries([]) 133 .setDescriptors([filename]) 134 .build(); 135 136 let loadedParsers: LoadedParsers; 137 let warnings: UserWarning[] = []; 138 139 beforeEach(async () => { 140 loadedParsers = new LoadedParsers(); 141 expect(loadedParsers.getParsers().length).toEqual(0); 142 }); 143 144 it('can load a single legacy parser', () => { 145 loadParsers([parserSf0], []); 146 expectLoadResult([parserSf0], []); 147 }); 148 149 it('can load a single perfetto parser', () => { 150 loadParsers([], [parserSf0]); 151 expectLoadResult([parserSf0], []); 152 }); 153 154 it('loads multiple perfetto parsers with same trace type', async () => { 155 loadParsers([], [parserSf0, parserSf1]); 156 expectLoadResult([parserSf0, parserSf1], []); 157 }); 158 159 it('loads legacy parser without dropping already-loaded legacy parser (different trace type)', async () => { 160 loadParsers([parserSf0], []); 161 expectLoadResult([parserSf0], []); 162 163 loadParsers([parserWm0], []); 164 expectLoadResult([parserSf0, parserWm0], []); 165 }); 166 167 it('loads legacy parser without dropping already-loaded legacy parser (same trace type)', async () => { 168 loadParsers([parserSf0], []); 169 expectLoadResult([parserSf0], []); 170 171 loadParsers([parserSf1], []); 172 expectLoadResult([parserSf0, parserSf1], []); 173 }); 174 175 it('drops elapsed-only parsers if parsers with real timestamps present', () => { 176 loadParsers([parserSf_elapsed, parserSf0], []); 177 expectLoadResult([parserSf0], [new TraceHasOldData(filename)]); 178 }); 179 180 it('doesnt drop elapsed-only parsers if no parsers with real timestamps present', () => { 181 loadParsers([parserSf_elapsed, parserWm_elapsed], []); 182 expectLoadResult([parserSf_elapsed, parserWm_elapsed], []); 183 }); 184 185 it('keeps real-time parsers without offset', () => { 186 loadParsers([parserSf0, parserEventlog], []); 187 expectLoadResult([parserSf0, parserEventlog], []); 188 }); 189 190 describe('drops legacy parser with old data (dangling old trace file)', () => { 191 const timeGapFrom = assertDefined( 192 parserSf_longButOldData.getTimestamps()?.at(-1), 193 ); 194 const timeGapTo = assertDefined(parserWm0.getTimestamps()?.at(0)); 195 const timeGap = new TimeRange(timeGapFrom, timeGapTo); 196 197 it('taking into account other legacy parsers', () => { 198 loadParsers([parserSf_longButOldData, parserWm0], []); 199 expectLoadResult([parserWm0], [new TraceHasOldData(filename, timeGap)]); 200 }); 201 202 it('taking into account perfetto parsers', () => { 203 loadParsers([parserSf_longButOldData], [parserWm0]); 204 expectLoadResult([parserWm0], [new TraceHasOldData(filename, timeGap)]); 205 }); 206 207 it('taking into account already-loaded parsers', () => { 208 loadParsers([parserWm0], []); 209 210 // Drop parser with old data, even if it provides 211 // a longer trace than the already-loaded parser 212 loadParsers([parserSf_longButOldData], []); 213 expectLoadResult([parserWm0], [new TraceHasOldData(filename, timeGap)]); 214 }); 215 216 it('doesnt drop legacy parser with dump (zero timestamp)', () => { 217 loadParsers([parserWm_dump, parserSf0], []); 218 expectLoadResult([parserWm_dump, parserSf0], []); 219 }); 220 221 it('doesnt drop legacy parser with wm transitions', () => { 222 // Only Shell Transition data used to set timestamps of merged Transition trace, 223 // so WM Transition data should not be considered by "old data" policy 224 loadParsers([parserWmTransitions, parserSf0], []); 225 expectLoadResult([parserWmTransitions, parserSf0], []); 226 }); 227 228 it('is robust to traces with time range overlap', () => { 229 const parser = parserSf0; 230 const timestamps = assertDefined(parserSf0.getTimestamps()); 231 232 const timestampsOverlappingFront = [ 233 timestamps[0].add(-1n), 234 timestamps[0].add(1n), 235 ]; 236 const parserOverlappingFront = new ParserBuilder<object>() 237 .setType(TraceType.TRANSACTIONS) 238 .setTimestamps(timestampsOverlappingFront) 239 .setDescriptors([filename]) 240 .build(); 241 242 const timestampsOverlappingBack = [ 243 timestamps[timestamps.length - 1].add(-1n), 244 timestamps[timestamps.length - 1].add(1n), 245 ]; 246 const parserOverlappingBack = new ParserBuilder<object>() 247 .setType(TraceType.TRANSITION) 248 .setTimestamps(timestampsOverlappingBack) 249 .setDescriptors([filename]) 250 .build(); 251 252 const timestampsOverlappingEntirely = [ 253 timestamps[0].add(-1n), 254 timestamps[timestamps.length - 1].add(1n), 255 ]; 256 const parserOverlappingEntirely = new ParserBuilder<object>() 257 .setType(TraceType.VIEW_CAPTURE) 258 .setTimestamps(timestampsOverlappingEntirely) 259 .setDescriptors([filename]) 260 .build(); 261 262 const timestampsOverlappingExactly = [ 263 timestamps[0], 264 timestamps[timestamps.length - 1], 265 ]; 266 const parserOverlappingExactly = new ParserBuilder<object>() 267 .setType(TraceType.WINDOW_MANAGER) 268 .setTimestamps(timestampsOverlappingExactly) 269 .setDescriptors([filename]) 270 .build(); 271 272 loadParsers( 273 [ 274 parser, 275 parserOverlappingFront, 276 parserOverlappingBack, 277 parserOverlappingEntirely, 278 parserOverlappingExactly, 279 ], 280 [], 281 ); 282 expectLoadResult( 283 [ 284 parser, 285 parserOverlappingFront, 286 parserOverlappingBack, 287 parserOverlappingEntirely, 288 parserOverlappingExactly, 289 ], 290 [], 291 ); 292 }); 293 }); 294 295 it('loads perfetto parser dropping all already-loaded perfetto parsers', () => { 296 loadParsers([], [parserSf0, parserWm0]); 297 expectLoadResult([parserSf0, parserWm0], []); 298 299 // We currently run only one Perfetto TP WebWorker at a time, 300 // so Perfetto parsers previously loaded are now invalid 301 // and must be removed (previous WebWorker is not running anymore). 302 loadParsers([], [parserSf1, parserWm1]); 303 expectLoadResult([parserSf1, parserWm1], []); 304 }); 305 306 describe('prioritizes perfetto parsers over legacy parsers', () => { 307 // While transitioning to the Perfetto format, devices might still have old legacy trace files 308 // dangling in the disk that get automatically included into bugreports. Hence, Perfetto parsers 309 // must always override legacy ones so that dangling legacy files are ignored. 310 311 it('when a perfetto parser is already loaded', () => { 312 loadParsers([parserSf0], [parserSf1]); 313 expectLoadResult([parserSf1], [new TraceOverridden(filename)]); 314 315 loadParsers([parserSf0], []); 316 expectLoadResult([parserSf1], [new TraceOverridden(filename)]); 317 }); 318 319 it('when a perfetto parser is loaded afterwards', () => { 320 loadParsers([parserSf0], []); 321 expectLoadResult([parserSf0], []); 322 323 loadParsers([], [parserSf1]); 324 expectLoadResult([parserSf1], [new TraceOverridden(filename)]); 325 }); 326 }); 327 328 describe('is robust to multiple parsers of same type loaded at once', () => { 329 it('legacy parsers', () => { 330 loadParsers([parserSf0, parserSf1], []); 331 expectLoadResult([parserSf0, parserSf1], []); 332 }); 333 334 it('legacy + perfetto parsers', () => { 335 loadParsers([parserSf0, parserSf0], [parserSf1]); 336 expectLoadResult( 337 [parserSf1], 338 [new TraceOverridden(filename), new TraceOverridden(filename)], 339 ); 340 }); 341 }); 342 343 describe('is robust to parser with no entries', () => { 344 it('legacy parser', () => { 345 loadParsers([parserSf_empty], []); 346 expectLoadResult([parserSf_empty], []); 347 }); 348 349 it('perfetto parser', () => { 350 loadParsers([], [parserSf_empty]); 351 expectLoadResult([parserSf_empty], []); 352 }); 353 }); 354 355 describe('handles screen recordings and screenshots', () => { 356 const parserScreenRecording0 = new ParserBuilder<object>() 357 .setType(TraceType.SCREEN_RECORDING) 358 .setTimestamps(timestamps) 359 .setDescriptors(['screen_recording.mp4']) 360 .build(); 361 const parserScreenRecording1 = new ParserBuilder<object>() 362 .setType(TraceType.SCREEN_RECORDING) 363 .setTimestamps(timestamps) 364 .setDescriptors(['screen_recording.mp4']) 365 .build(); 366 const parserScreenshot0 = new ParserBuilder<object>() 367 .setType(TraceType.SCREENSHOT) 368 .setTimestamps(timestamps) 369 .setDescriptors(['screenshot.png']) 370 .build(); 371 const parserScreenshot1 = new ParserBuilder<object>() 372 .setType(TraceType.SCREENSHOT) 373 .setTimestamps(timestamps) 374 .setDescriptors(['screenshot.png']) 375 .build(); 376 const overrideError = new TraceOverridden( 377 'screenshot.png', 378 TraceType.SCREEN_RECORDING, 379 ); 380 381 it('loads screenshot parser', () => { 382 loadParsers([parserScreenshot0], []); 383 expectLoadResult([parserScreenshot0], []); 384 }); 385 386 it('loads screen recording parser', () => { 387 loadParsers([parserScreenRecording0], []); 388 expectLoadResult([parserScreenRecording0], []); 389 }); 390 391 it('discards screenshot parser in favour of screen recording parser', () => { 392 loadParsers([parserScreenshot0, parserScreenRecording0], []); 393 expectLoadResult([parserScreenRecording0], [overrideError]); 394 }); 395 396 it('does not load screenshot parser after loading screen recording parser in same call', () => { 397 loadParsers([parserScreenRecording0, parserScreenshot0], []); 398 expectLoadResult([parserScreenRecording0], [overrideError]); 399 }); 400 401 it('does not load screenshot parser after loading screen recording parser in previous call', () => { 402 loadParsers([parserScreenRecording0], []); 403 expectLoadResult([parserScreenRecording0], []); 404 405 loadParsers([parserScreenshot0], []); 406 expectLoadResult([parserScreenRecording0], [overrideError]); 407 }); 408 409 it('overrides previously loaded screenshot parser with screen recording parser', () => { 410 loadParsers([parserScreenshot0], []); 411 expectLoadResult([parserScreenshot0], []); 412 413 loadParsers([parserScreenRecording0], []); 414 expectLoadResult([parserScreenRecording0], [overrideError]); 415 }); 416 417 it('enforces limit of single screenshot or screenrecord parser', () => { 418 loadParsers([parserScreenshot0], []); 419 expectLoadResult([parserScreenshot0], []); 420 421 loadParsers([parserScreenshot1], []); 422 expectLoadResult( 423 [parserScreenshot0], 424 [new TraceOverridden('screenshot.png', TraceType.SCREENSHOT)], 425 ); 426 427 loadParsers([parserScreenRecording0], []); 428 expectLoadResult( 429 [parserScreenRecording0], 430 [new TraceOverridden('screenshot.png', TraceType.SCREEN_RECORDING)], 431 ); 432 433 loadParsers([parserScreenRecording1], []); 434 expectLoadResult( 435 [parserScreenRecording0], 436 [ 437 new TraceOverridden( 438 'screen_recording.mp4', 439 TraceType.SCREEN_RECORDING, 440 ), 441 ], 442 ); 443 }); 444 }); 445 446 it('can remove parsers', () => { 447 loadParsers([parserSf0], [parserWm0]); 448 expectLoadResult([parserSf0, parserWm0], []); 449 450 loadedParsers.remove(parserWm0); 451 expectLoadResult([parserSf0], []); 452 453 loadedParsers.remove(parserSf0); 454 expectLoadResult([], []); 455 }); 456 457 it('can be cleared', () => { 458 loadedParsers.clear(); 459 loadParsers([parserSf0], [parserWm0]); 460 expectLoadResult([parserSf0, parserWm0], []); 461 462 loadedParsers.clear(); 463 expectLoadResult([], []); 464 465 loadParsers([parserSf0], [parserWm0]); 466 expectLoadResult([parserSf0, parserWm0], []); 467 }); 468 469 it('can make zip archive of traces with appropriate directories and extensions', async () => { 470 const fileDuplicated = new File([], filename); 471 472 const legacyFiles = [ 473 // ScreenRecording 474 new File([], filename), 475 476 // ViewCapture 477 // Multiple parsers point to the same viewcapture file, 478 // but we expect to see only one in the output archive (deduplicated) 479 fileDuplicated, 480 fileDuplicated, 481 482 // WM 483 new File([], filename + '.pb'), 484 485 // WM 486 // Same filename as above. 487 // Expect this file to be automatically renamed to avoid clashes/overwrites 488 new File([], filename + '.pb'), 489 ]; 490 491 loadParsers( 492 [ 493 parserScreenRecording, 494 parserViewCapture0, 495 parserViewCapture1, 496 parserWm0, 497 parserWm1, 498 ], 499 [parserSf0, parserWmTransitions], 500 legacyFiles, 501 ); 502 expectLoadResult( 503 [ 504 parserScreenRecording, 505 parserViewCapture0, 506 parserViewCapture1, 507 parserWm0, 508 parserWm1, 509 parserSf0, 510 parserWmTransitions, 511 ], 512 [], 513 ); 514 515 const zipArchive = await loadedParsers.makeZipArchive(); 516 const zipFile = new File([zipArchive], 'winscope.zip'); 517 const actualArchiveContents = (await FileUtils.unzipFile(zipFile)) 518 .map((file) => file.name) 519 .sort(); 520 521 const expectedArchiveContents = [ 522 'filename.mp4', 523 'filename.perfetto-trace', 524 'vc/filename.winscope', 525 'wm/filename (1).pb', 526 'wm/filename.pb', 527 ]; 528 expect(actualArchiveContents).toEqual(expectedArchiveContents); 529 }); 530 531 function loadParsers( 532 legacy: Array<Parser<object>>, 533 perfetto: Array<Parser<object>>, 534 legacyFiles?: File[], 535 ) { 536 const legacyFileAndParsers = legacy.map((parser, i) => { 537 const legacyFile = legacyFiles ? legacyFiles[i] : new File([], filename); 538 return new FileAndParser(new TraceFile(legacyFile), parser); 539 }); 540 541 const perfettoTraceFile = new TraceFile(new File([], filename)); 542 const perfettoFileAndParsers = 543 perfetto.length > 0 544 ? new FileAndParsers(perfettoTraceFile, perfetto) 545 : undefined; 546 547 warnings = []; 548 const listener = { 549 onNotifications(notifications: UserWarning[]) { 550 warnings.push(...notifications); 551 }, 552 }; 553 554 loadedParsers.addParsers( 555 legacyFileAndParsers, 556 perfettoFileAndParsers, 557 listener, 558 ); 559 } 560 561 function expectLoadResult( 562 expectedParsers: Array<Parser<object>>, 563 expectedWarnings: UserWarning[], 564 ) { 565 const actualParsers = loadedParsers.getParsers(); 566 expect(actualParsers.length).toEqual(expectedParsers.length); 567 expect(new Set([...actualParsers])).toEqual(new Set([...expectedParsers])); 568 569 expect(warnings).toEqual(expectedWarnings); 570 } 571}); 572