1/* 2 * Copyright (C) 2022 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 {ProgressListenerStub} from 'messaging/progress_listener_stub'; 20import {UserWarning} from 'messaging/user_warning'; 21import { 22 CorruptedArchive, 23 InvalidPerfettoTrace, 24 NoValidFiles, 25 TraceOverridden, 26 UnsupportedFileFormat, 27} from 'messaging/user_warnings'; 28import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 29import {TracesUtils} from 'test/unit/traces_utils'; 30import {UserNotifierChecker} from 'test/unit/user_notifier_checker'; 31import {UnitTestUtils} from 'test/unit/utils'; 32import {TraceType} from 'trace/trace_type'; 33import {FilesSource} from './files_source'; 34import {TracePipeline} from './trace_pipeline'; 35 36describe('TracePipeline', () => { 37 let validSfFile: File; 38 let validWmFile: File; 39 let shellTransitionFile: File; 40 let wmTransitionFile: File; 41 let screenshotFile: File; 42 let screenRecordingFile: File; 43 let brMainEntryFile: File; 44 let brCodenameFile: File; 45 let brSfFile: File; 46 let jpgFile: File; 47 48 let progressListener: ProgressListenerStub; 49 let tracePipeline: TracePipeline; 50 let userNotifierChecker: UserNotifierChecker; 51 52 beforeAll(async () => { 53 userNotifierChecker = new UserNotifierChecker(); 54 wmTransitionFile = await UnitTestUtils.getFixtureFile( 55 'traces/elapsed_and_real_timestamp/wm_transition_trace.pb', 56 ); 57 shellTransitionFile = await UnitTestUtils.getFixtureFile( 58 'traces/elapsed_and_real_timestamp/shell_transition_trace.pb', 59 ); 60 validSfFile = await UnitTestUtils.getFixtureFile( 61 'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb', 62 ); 63 validWmFile = await UnitTestUtils.getFixtureFile( 64 'traces/elapsed_and_real_timestamp/WindowManager.pb', 65 ); 66 screenshotFile = await UnitTestUtils.getFixtureFile( 67 'traces/screenshot.png', 68 ); 69 screenRecordingFile = await UnitTestUtils.getFixtureFile( 70 'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4', 71 ); 72 brMainEntryFile = await UnitTestUtils.getFixtureFile( 73 'bugreports/main_entry.txt', 74 'main_entry.txt', 75 ); 76 brCodenameFile = await UnitTestUtils.getFixtureFile( 77 'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 78 'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 79 ); 80 brSfFile = await UnitTestUtils.getFixtureFile( 81 'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb', 82 'FS/data/misc/wmtrace/surface_flinger.bp', 83 ); 84 jpgFile = await UnitTestUtils.getFixtureFile('winscope_homepage.jpg'); 85 }); 86 87 beforeEach(async () => { 88 jasmine.addCustomEqualityTester(UnitTestUtils.timestampEqualityTester); 89 90 progressListener = new ProgressListenerStub(); 91 spyOn(progressListener, 'onProgressUpdate'); 92 spyOn(progressListener, 'onOperationFinished'); 93 userNotifierChecker.reset(); 94 95 tracePipeline = new TracePipeline(); 96 }); 97 98 it('can load valid trace files', async () => { 99 expect(tracePipeline.getTraces().getSize()).toEqual(0); 100 101 await loadFiles([validSfFile, validWmFile], FilesSource.TEST); 102 await expectLoadResult(2, []); 103 104 expect(tracePipeline.getDownloadArchiveFilename()).toMatch( 105 new RegExp(`${FilesSource.TEST}_`), 106 ); 107 expect(tracePipeline.getTraces().getSize()).toEqual(2); 108 109 const traceEntries = await TracesUtils.extractEntries( 110 tracePipeline.getTraces(), 111 ); 112 expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan( 113 0, 114 ); 115 expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.length).toBeGreaterThan( 116 0, 117 ); 118 }); 119 120 it('can load valid gzipped file and archive', async () => { 121 expect(tracePipeline.getTraces().getSize()).toEqual(0); 122 123 const gzippedFile = await UnitTestUtils.getFixtureFile( 124 'traces/WindowManager.pb.gz', 125 ); 126 const gzippedArchive = await UnitTestUtils.getFixtureFile( 127 'traces/WindowManager.zip.gz', 128 ); 129 130 await loadFiles([gzippedFile, gzippedArchive], FilesSource.TEST); 131 await expectLoadResult(2, []); 132 133 const traces = tracePipeline.getTraces(); 134 expect(traces.getSize()).toEqual(2); 135 expect(traces.getTraces(TraceType.WINDOW_MANAGER).length).toEqual(2); 136 137 const traceEntries = await TracesUtils.extractEntries(traces); 138 expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan( 139 0, 140 ); 141 }); 142 143 it('can set download archive filename based on files source', async () => { 144 await loadFiles([validSfFile]); 145 await expectLoadResult(1, []); 146 expect(tracePipeline.getDownloadArchiveFilename()).toMatch( 147 new RegExp('SurfaceFlinger_'), 148 ); 149 150 tracePipeline.clear(); 151 152 await loadFiles([validSfFile, validWmFile], FilesSource.COLLECTED); 153 await expectLoadResult(2, []); 154 expect(tracePipeline.getDownloadArchiveFilename()).toMatch( 155 new RegExp(`${FilesSource.COLLECTED}_`), 156 ); 157 }); 158 159 it('can convert illegal uploaded archive filename to legal name for download archive', async () => { 160 const fileWithIllegalName = await UnitTestUtils.getFixtureFile( 161 'traces/SFtrace(with_illegal_characters).pb', 162 ); 163 await loadFiles([fileWithIllegalName]); 164 await expectLoadResult(1, []); 165 const downloadFilename = tracePipeline.getDownloadArchiveFilename(); 166 expect(FileUtils.DOWNLOAD_FILENAME_REGEX.test(downloadFilename)).toBeTrue(); 167 }); 168 169 it('detects bugreports and filters out files based on their directory', async () => { 170 expect(tracePipeline.getTraces().getSize()).toEqual(0); 171 172 const bugreportFiles = [ 173 brMainEntryFile, 174 brCodenameFile, 175 brSfFile, 176 await UnitTestUtils.getFixtureFile( 177 'traces/elapsed_and_real_timestamp/wm_transition_trace.pb', 178 'FS/data/misc/ignored-dir/window_manager.bp', 179 ), 180 ]; 181 182 const bugreportArchive = new File( 183 [await FileUtils.createZipArchive(bugreportFiles)], 184 'bugreport.zip', 185 ); 186 187 // Corner case: 188 // Another file is loaded along the bugreport -> the file must not be ignored 189 // 190 // Note: 191 // The even weirder corner case where two bugreports are loaded at the same time is 192 // currently not properly handled. 193 const otherFile = await UnitTestUtils.getFixtureFile( 194 'traces/elapsed_and_real_timestamp/InputMethodClients.pb', 195 'would-be-ignored-if-was-in-bugreport-archive/input_method_clients.pb', 196 ); 197 198 await loadFiles([bugreportArchive, otherFile]); 199 await expectLoadResult(2, []); 200 201 const traces = tracePipeline.getTraces(); 202 expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined(); 203 expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeUndefined(); // ignored 204 expect(traces.getTrace(TraceType.INPUT_METHOD_CLIENTS)).toBeDefined(); 205 }); 206 207 it('detects bugreports and extracts timezone info, then calculates utc offset', async () => { 208 const bugreportFiles = [brMainEntryFile, brCodenameFile, brSfFile]; 209 const bugreportArchive = new File( 210 [await FileUtils.createZipArchive(bugreportFiles)], 211 'bugreport.zip', 212 ); 213 214 await loadFiles([bugreportArchive]); 215 await expectLoadResult(1, []); 216 217 const timestampConverter = tracePipeline.getTimestampConverter(); 218 expect(timestampConverter); 219 expect(timestampConverter.getUTCOffset()).toEqual('UTC+05:30'); 220 221 const expectedTimestamp = 222 TimestampConverterUtils.makeRealTimestampWithUTCOffset( 223 1659107089102062832n, 224 ); 225 expect( 226 timestampConverter.makeTimestampFromMonotonicNs(14500282843n), 227 ).toEqual(expectedTimestamp); 228 }); 229 230 it('is robust to corrupted archive', async () => { 231 const corruptedArchive = await UnitTestUtils.getFixtureFile( 232 'corrupted_archive.zip', 233 ); 234 235 await loadFiles([corruptedArchive]); 236 237 await expectLoadResult(0, [ 238 new CorruptedArchive(corruptedArchive), 239 new NoValidFiles(), 240 ]); 241 }); 242 243 it('is robust to invalid trace files', async () => { 244 const invalidFiles = [jpgFile]; 245 await loadFiles(invalidFiles); 246 247 await expectLoadResult(0, [ 248 new UnsupportedFileFormat('winscope_homepage.jpg'), 249 ]); 250 }); 251 252 it('is robust to invalid perfetto trace files', async () => { 253 const invalidFiles = [ 254 await UnitTestUtils.getFixtureFile( 255 'traces/perfetto/invalid_protolog.perfetto-trace', 256 ), 257 ]; 258 259 await loadFiles(invalidFiles); 260 261 await expectLoadResult(0, [ 262 new InvalidPerfettoTrace('invalid_protolog.perfetto-trace', [ 263 'Perfetto trace has no IME Clients entries', 264 'Perfetto trace has no IME system_server entries', 265 'Perfetto trace has no IME Service entries', 266 'Perfetto trace has no ProtoLog entries', 267 'Perfetto trace has no Surface Flinger entries', 268 'Perfetto trace has no Transactions entries', 269 'Perfetto trace has no Transitions entries', 270 'Perfetto trace has no ViewCapture windows', 271 'Perfetto trace has no Window Manager entries', 272 'Perfetto trace has no Motion Events entries', 273 'Perfetto trace has no Key Events entries', 274 ]), 275 ]); 276 }); 277 278 it('is robust to mixed valid and invalid trace files', async () => { 279 expect(tracePipeline.getTraces().getSize()).toEqual(0); 280 const files = [ 281 jpgFile, 282 await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb'), 283 ]; 284 285 await loadFiles(files); 286 287 await expectLoadResult(1, [ 288 new UnsupportedFileFormat('winscope_homepage.jpg'), 289 ]); 290 }); 291 292 it('can remove traces', async () => { 293 await loadFiles([validSfFile, validWmFile]); 294 await expectLoadResult(2, []); 295 296 const sfTrace = assertDefined( 297 tracePipeline.getTraces().getTrace(TraceType.SURFACE_FLINGER), 298 ); 299 const wmTrace = assertDefined( 300 tracePipeline.getTraces().getTrace(TraceType.WINDOW_MANAGER), 301 ); 302 303 tracePipeline.removeTrace(sfTrace); 304 await expectLoadResult(1, []); 305 306 tracePipeline.removeTrace(wmTrace); 307 await expectLoadResult(0, []); 308 }); 309 310 it('removes constituent traces of transitions trace but keeps for download', async () => { 311 const files = [wmTransitionFile, wmTransitionFile, shellTransitionFile]; 312 await loadFiles(files); 313 await expectLoadResult(1, []); 314 315 const transitionTrace = assertDefined( 316 tracePipeline.getTraces().getTrace(TraceType.TRANSITION), 317 ); 318 319 tracePipeline.removeTrace(transitionTrace); 320 await expectLoadResult(0, []); 321 322 await loadFiles([wmTransitionFile]); 323 await expectLoadResult(1, []); 324 expect( 325 tracePipeline.getTraces().getTrace(TraceType.WM_TRANSITION), 326 ).toBeDefined(); 327 await expectDownloadResult([ 328 'transition/shell_transition_trace.pb', 329 'transition/wm_transition_trace.pb', 330 ]); 331 }); 332 333 it('removes constituent traces of CUJs trace but keeps for download', async () => { 334 const files = [ 335 await UnitTestUtils.getFixtureFile('traces/eventlog.winscope'), 336 ]; 337 await loadFiles(files); 338 await expectLoadResult(1, []); 339 340 const cujTrace = assertDefined( 341 tracePipeline.getTraces().getTrace(TraceType.CUJS), 342 ); 343 344 tracePipeline.removeTrace(cujTrace); 345 await expectLoadResult(0, []); 346 await expectDownloadResult(['eventlog/eventlog.winscope']); 347 }); 348 349 it('removes constituent traces of input trace but keeps for download', async () => { 350 const files = [ 351 await UnitTestUtils.getFixtureFile( 352 'traces/perfetto/input-events.perfetto-trace', 353 ), 354 ]; 355 await loadFiles(files); 356 await expectLoadResult(1, []); 357 358 const inputTrace = assertDefined( 359 tracePipeline.getTraces().getTrace(TraceType.INPUT_EVENT_MERGED), 360 ); 361 362 tracePipeline.removeTrace(inputTrace); 363 await expectLoadResult(0, []); 364 await expectDownloadResult(['input-events.perfetto-trace']); 365 }); 366 367 it('gets loaded traces', async () => { 368 await loadFiles([validSfFile, validWmFile]); 369 await expectLoadResult(2, []); 370 371 const traces = tracePipeline.getTraces(); 372 373 const actualTraceTypes = new Set(traces.mapTrace((trace) => trace.type)); 374 const expectedTraceTypes = new Set([ 375 TraceType.SURFACE_FLINGER, 376 TraceType.WINDOW_MANAGER, 377 ]); 378 expect(actualTraceTypes).toEqual(expectedTraceTypes); 379 380 const sfTrace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER)); 381 expect(sfTrace.getDescriptors().length).toBeGreaterThan(0); 382 }); 383 384 it('gets screenrecording data', async () => { 385 const files = [screenRecordingFile]; 386 await loadFiles(files); 387 await expectLoadResult(1, []); 388 389 const video = await tracePipeline.getScreenRecordingVideo(); 390 expect(video).toBeDefined(); 391 expect(video?.size).toBeGreaterThan(0); 392 }); 393 394 it('gets screenshot data', async () => { 395 const files = [screenshotFile]; 396 await loadFiles(files); 397 await expectLoadResult(1, []); 398 399 const video = await tracePipeline.getScreenRecordingVideo(); 400 expect(video).toBeDefined(); 401 expect(video?.size).toBeGreaterThan(0); 402 }); 403 404 it('prioritizes screenrecording over screenshot data', async () => { 405 const files = [screenshotFile, screenRecordingFile]; 406 await loadFiles(files); 407 await expectLoadResult(1, [ 408 new TraceOverridden('screenshot.png', TraceType.SCREEN_RECORDING), 409 ]); 410 411 const video = await tracePipeline.getScreenRecordingVideo(); 412 expect(video).toBeDefined(); 413 expect(video?.size).toBeGreaterThan(0); 414 }); 415 416 it('creates traces with correct type', async () => { 417 await loadFiles([validSfFile, validWmFile]); 418 await expectLoadResult(2, []); 419 420 const traces = tracePipeline.getTraces(); 421 traces.forEachTrace((trace, type) => { 422 expect(trace.type).toEqual(type); 423 }); 424 }); 425 426 it('creates zip archive with loaded trace files', async () => { 427 const files = [ 428 screenRecordingFile, 429 await UnitTestUtils.getFixtureFile( 430 'traces/perfetto/transactions_trace.perfetto-trace', 431 ), 432 ]; 433 await loadFiles(files); 434 await expectLoadResult(2, []); 435 436 await expectDownloadResult([ 437 'screen_recording_metadata_v2.mp4', 438 'transactions_trace.perfetto-trace', 439 ]); 440 }); 441 442 it('can be cleared', async () => { 443 await loadFiles([validSfFile, validWmFile]); 444 await expectLoadResult(2, []); 445 446 tracePipeline.clear(); 447 expect(tracePipeline.getTraces().getSize()).toEqual(0); 448 }); 449 450 it('can filter traces without visualization', async () => { 451 await loadFiles([shellTransitionFile]); 452 await expectLoadResult(1, []); 453 454 tracePipeline.filterTracesWithoutVisualization(); 455 expect(tracePipeline.getTraces().getSize()).toEqual(0); 456 expect( 457 tracePipeline.getTraces().getTrace(TraceType.SHELL_TRANSITION), 458 ).toBeUndefined(); 459 }); 460 461 it('tries to create search trace', async () => { 462 const perfettoFile = await UnitTestUtils.getFixtureFile( 463 'traces/perfetto/layers_trace.perfetto-trace', 464 ); 465 await loadFiles([perfettoFile]); 466 const validQuery = 'select ts from surfaceflinger_layers_snapshot'; 467 expect(await tracePipeline.tryCreateSearchTrace(validQuery)).toBeDefined(); 468 expect(await tracePipeline.tryCreateSearchTrace('fail')).toBeUndefined(); 469 }); 470 471 it('creates screen recording using metadata', async () => { 472 const screenRecording = await UnitTestUtils.getFixtureFile( 473 'traces/elapsed_and_real_timestamp/screen_recording_no_metadata.mp4', 474 ); 475 const metadata = await UnitTestUtils.getFixtureFile( 476 'traces/elapsed_and_real_timestamp/screen_recording_metadata.json', 477 ); 478 await loadFiles([screenRecording, metadata]); 479 await expectLoadResult(1, []); 480 }); 481 482 async function loadFiles( 483 files: File[], 484 source: FilesSource = FilesSource.TEST, 485 ) { 486 await tracePipeline.loadFiles(files, source, progressListener); 487 expect(progressListener.onOperationFinished).toHaveBeenCalled(); 488 await tracePipeline.buildTraces(); 489 } 490 491 async function expectLoadResult( 492 numberOfTraces: number, 493 expectedWarnings: UserWarning[], 494 ) { 495 userNotifierChecker.expectAdded(expectedWarnings); 496 expect(tracePipeline.getTraces().getSize()).toEqual(numberOfTraces); 497 } 498 499 async function expectDownloadResult(expectedArchiveContents: string[]) { 500 const zipArchive = await tracePipeline.makeZipArchiveWithLoadedTraceFiles(); 501 const actualArchiveContents = (await FileUtils.unzipFile(zipArchive)) 502 .map((file) => file.name) 503 .sort(); 504 expect(actualArchiveContents).toEqual(expectedArchiveContents); 505 } 506}); 507