• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
20  TimestampConverterUtils,
21  timestampEqualityTester,
22} from 'common/time/test_utils';
23import {ProgressListenerStub} from 'messaging/progress_listener_stub';
24import {UserWarning} from 'messaging/user_warning';
25import {
26  CorruptedArchive,
27  InvalidPerfettoTrace,
28  NoValidFiles,
29  PerfettoPacketLoss,
30  TraceOverridden,
31  UnsupportedFileFormat,
32} from 'messaging/user_warnings';
33import {getFixtureFile} from 'test/unit/fixture_utils';
34import {TracesUtils} from 'test/unit/traces_utils';
35import {UserNotifierChecker} from 'test/unit/user_notifier_checker';
36import {TraceType} from 'trace/trace_type';
37import {QueryResult} from 'trace_processor/query_result';
38import {TraceProcessor} from 'trace_processor/trace_processor';
39import {FilesSource} from './files_source';
40import {TracePipeline} from './trace_pipeline';
41
42describe('TracePipeline', () => {
43  let validSfFile: File;
44  let validWmFile: File;
45  let shellTransitionFile: File;
46  let wmTransitionFile: File;
47  let screenshotFile: File;
48  let screenRecordingFile: File;
49  let brMainEntryFile: File;
50  let brCodenameFile: File;
51  let brSfFile: File;
52  let jpgFile: File;
53
54  let progressListener: ProgressListenerStub;
55  let tracePipeline: TracePipeline;
56  let userNotifierChecker: UserNotifierChecker;
57
58  beforeAll(async () => {
59    userNotifierChecker = new UserNotifierChecker();
60    wmTransitionFile = await getFixtureFile(
61      'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
62    );
63    shellTransitionFile = await getFixtureFile(
64      'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
65    );
66    validSfFile = await getFixtureFile(
67      'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb',
68    );
69    validWmFile = await getFixtureFile(
70      'traces/elapsed_and_real_timestamp/WindowManager.pb',
71    );
72    screenshotFile = await getFixtureFile('traces/screenshot.png');
73    screenRecordingFile = await getFixtureFile(
74      'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4',
75    );
76    brMainEntryFile = await getFixtureFile(
77      'bugreports/main_entry.txt',
78      'main_entry.txt',
79    );
80    brCodenameFile = await getFixtureFile(
81      'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
82      'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
83    );
84    brSfFile = await getFixtureFile(
85      'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb',
86      'FS/data/misc/wmtrace/surface_flinger.bp',
87    );
88    jpgFile = await getFixtureFile('winscope_homepage.jpg');
89  });
90
91  beforeEach(async () => {
92    jasmine.addCustomEqualityTester(timestampEqualityTester);
93
94    progressListener = new ProgressListenerStub();
95    spyOn(progressListener, 'onProgressUpdate');
96    spyOn(progressListener, 'onOperationFinished');
97    userNotifierChecker.reset();
98
99    tracePipeline = new TracePipeline();
100  });
101
102  it('can load valid trace files', async () => {
103    expect(tracePipeline.getTraces().getSize()).toEqual(0);
104
105    await loadFiles([validSfFile, validWmFile], FilesSource.TEST);
106    await expectLoadResult(2, []);
107
108    expect(tracePipeline.getDownloadArchiveFilename()).toMatch(
109      new RegExp(`${FilesSource.TEST}_`),
110    );
111    expect(tracePipeline.getTraces().getSize()).toEqual(2);
112
113    const traceEntries = await TracesUtils.extractEntries(
114      tracePipeline.getTraces(),
115    );
116    expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan(
117      0,
118    );
119    expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.length).toBeGreaterThan(
120      0,
121    );
122  });
123
124  it('can load valid gzipped file and archive', async () => {
125    expect(tracePipeline.getTraces().getSize()).toEqual(0);
126
127    const gzippedFile = await getFixtureFile('traces/WindowManager.pb.gz');
128    const gzippedArchive = await getFixtureFile('traces/WindowManager.zip.gz');
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 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 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 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 getFixtureFile('corrupted_archive.zip');
232
233    await loadFiles([corruptedArchive]);
234
235    await expectLoadResult(0, [
236      new CorruptedArchive(corruptedArchive),
237      new NoValidFiles(),
238    ]);
239  });
240
241  it('is robust to invalid trace files', async () => {
242    const invalidFiles = [jpgFile];
243    await loadFiles(invalidFiles);
244
245    await expectLoadResult(0, [
246      new UnsupportedFileFormat('winscope_homepage.jpg'),
247    ]);
248  });
249
250  it('is robust to invalid perfetto trace files', async () => {
251    const invalidFiles = [
252      await getFixtureFile('traces/perfetto/invalid_protolog.perfetto-trace'),
253    ];
254
255    await loadFiles(invalidFiles);
256
257    await expectLoadResult(0, [
258      new InvalidPerfettoTrace('invalid_protolog.perfetto-trace', [
259        'Perfetto trace has no Winscope trace entries',
260      ]),
261    ]);
262  });
263
264  it('shows warning for packet loss', async () => {
265    const file = [
266      await getFixtureFile('traces/perfetto/layers_trace.perfetto-trace'),
267    ];
268    const queryResultObj = jasmine.createSpyObj<QueryResult>('result', [
269      'numRows',
270      'firstRow',
271      'waitAllRows',
272    ]);
273    queryResultObj.numRows.and.returnValue(1);
274    queryResultObj.firstRow.and.returnValue({value: 2n});
275    queryResultObj.waitAllRows.and.returnValue(Promise.resolve(queryResultObj));
276
277    const spy = spyOn(
278      TraceProcessor.prototype,
279      'queryAllRows',
280    ).and.callThrough();
281    spy
282      .withArgs(
283        "select name, value from stats where name = 'traced_buf_trace_writer_packet_loss'",
284      )
285      .and.returnValue(Promise.resolve(queryResultObj));
286
287    await loadFiles(file);
288    await expectLoadResult(1, [
289      new PerfettoPacketLoss('layers_trace.perfetto-trace', 2),
290    ]);
291  });
292
293  it('is robust to mixed valid and invalid trace files', async () => {
294    expect(tracePipeline.getTraces().getSize()).toEqual(0);
295    const files = [
296      jpgFile,
297      await getFixtureFile('traces/dump_WindowManager.pb'),
298    ];
299
300    await loadFiles(files);
301
302    await expectLoadResult(1, [
303      new UnsupportedFileFormat('winscope_homepage.jpg'),
304    ]);
305  });
306
307  it('can remove traces', async () => {
308    await loadFiles([validSfFile, validWmFile]);
309    await expectLoadResult(2, []);
310
311    const sfTrace = assertDefined(
312      tracePipeline.getTraces().getTrace(TraceType.SURFACE_FLINGER),
313    );
314    const wmTrace = assertDefined(
315      tracePipeline.getTraces().getTrace(TraceType.WINDOW_MANAGER),
316    );
317
318    tracePipeline.removeTrace(sfTrace);
319    await expectLoadResult(1, []);
320
321    tracePipeline.removeTrace(wmTrace);
322    await expectLoadResult(0, []);
323  });
324
325  it('removes constituent traces of transitions trace but keeps for download', async () => {
326    const files = [wmTransitionFile, wmTransitionFile, shellTransitionFile];
327    await loadFiles(files);
328    await expectLoadResult(1, []);
329
330    const transitionTrace = assertDefined(
331      tracePipeline.getTraces().getTrace(TraceType.TRANSITION),
332    );
333
334    tracePipeline.removeTrace(transitionTrace);
335    await expectLoadResult(0, []);
336
337    await loadFiles([wmTransitionFile]);
338    await expectLoadResult(1, []);
339    expect(
340      tracePipeline.getTraces().getTrace(TraceType.WM_TRANSITION),
341    ).toBeDefined();
342    await expectDownloadResult([
343      'transition/shell_transition_trace.pb',
344      'transition/wm_transition_trace.pb',
345    ]);
346  });
347
348  it('removes constituent traces of CUJs trace but keeps for download', async () => {
349    const files = [await getFixtureFile('traces/eventlog.winscope')];
350    await loadFiles(files);
351    await expectLoadResult(1, []);
352
353    const cujTrace = assertDefined(
354      tracePipeline.getTraces().getTrace(TraceType.CUJS),
355    );
356
357    tracePipeline.removeTrace(cujTrace);
358    await expectLoadResult(0, []);
359    await expectDownloadResult(['eventlog/eventlog.winscope']);
360  });
361
362  it('removes constituent traces of input trace but keeps for download', async () => {
363    const files = [
364      await getFixtureFile('traces/perfetto/input-events.perfetto-trace'),
365    ];
366    await loadFiles(files);
367    await expectLoadResult(1, []);
368
369    const inputTrace = assertDefined(
370      tracePipeline.getTraces().getTrace(TraceType.INPUT_EVENT_MERGED),
371    );
372
373    tracePipeline.removeTrace(inputTrace);
374    await expectLoadResult(0, []);
375    await expectDownloadResult(['input-events.perfetto-trace']);
376  });
377
378  it('gets loaded traces', async () => {
379    await loadFiles([validSfFile, validWmFile]);
380    await expectLoadResult(2, []);
381
382    const traces = tracePipeline.getTraces();
383
384    const actualTraceTypes = new Set(traces.mapTrace((trace) => trace.type));
385    const expectedTraceTypes = new Set([
386      TraceType.SURFACE_FLINGER,
387      TraceType.WINDOW_MANAGER,
388    ]);
389    expect(actualTraceTypes).toEqual(expectedTraceTypes);
390
391    const sfTrace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER));
392    expect(sfTrace.getDescriptors().length).toBeGreaterThan(0);
393  });
394
395  it('gets screenrecording data', async () => {
396    const files = [screenRecordingFile];
397    await loadFiles(files);
398    await expectLoadResult(1, []);
399
400    const video = await tracePipeline.getScreenRecordingVideo();
401    expect(video).toBeDefined();
402    expect(video?.size).toBeGreaterThan(0);
403  });
404
405  it('gets screenshot data', async () => {
406    const files = [screenshotFile];
407    await loadFiles(files);
408    await expectLoadResult(1, []);
409
410    const video = await tracePipeline.getScreenRecordingVideo();
411    expect(video).toBeDefined();
412    expect(video?.size).toBeGreaterThan(0);
413  });
414
415  it('prioritizes screenrecording over screenshot data', async () => {
416    const files = [screenshotFile, screenRecordingFile];
417    await loadFiles(files);
418    await expectLoadResult(1, [
419      new TraceOverridden('screenshot.png', TraceType.SCREEN_RECORDING),
420    ]);
421
422    const video = await tracePipeline.getScreenRecordingVideo();
423    expect(video).toBeDefined();
424    expect(video?.size).toBeGreaterThan(0);
425  });
426
427  it('creates traces with correct type', async () => {
428    await loadFiles([validSfFile, validWmFile]);
429    await expectLoadResult(2, []);
430
431    const traces = tracePipeline.getTraces();
432    traces.forEachTrace((trace, type) => {
433      expect(trace.type).toEqual(type);
434    });
435  });
436
437  it('creates zip archive with loaded trace files', async () => {
438    const files = [
439      screenRecordingFile,
440      await getFixtureFile('traces/perfetto/transactions_trace.perfetto-trace'),
441    ];
442    await loadFiles(files);
443    await expectLoadResult(2, []);
444
445    await expectDownloadResult([
446      'screen_recording_metadata_v2.mp4',
447      'transactions_trace.perfetto-trace',
448    ]);
449  });
450
451  it('can be cleared', async () => {
452    await loadFiles([validSfFile, validWmFile]);
453    await expectLoadResult(2, []);
454
455    tracePipeline.clear();
456    expect(tracePipeline.getTraces().getSize()).toEqual(0);
457  });
458
459  it('can filter traces without visualization', async () => {
460    await loadFiles([shellTransitionFile]);
461    await expectLoadResult(1, []);
462
463    tracePipeline.filterTracesWithoutVisualization();
464    expect(tracePipeline.getTraces().getSize()).toEqual(0);
465    expect(
466      tracePipeline.getTraces().getTrace(TraceType.SHELL_TRANSITION),
467    ).toBeUndefined();
468  });
469
470  it('tries to create search trace', async () => {
471    const perfettoFile = await getFixtureFile(
472      'traces/perfetto/layers_trace.perfetto-trace',
473    );
474    await loadFiles([perfettoFile]);
475    const validQuery = 'select ts from surfaceflinger_layers_snapshot';
476    expect(await tracePipeline.tryCreateSearchTrace(validQuery)).toBeDefined();
477    expect(await tracePipeline.tryCreateSearchTrace('fail')).toBeUndefined();
478  });
479
480  it('creates screen recording using metadata', async () => {
481    const screenRecording = await getFixtureFile(
482      'traces/elapsed_and_real_timestamp/screen_recording_no_metadata.mp4',
483    );
484    const metadata = await getFixtureFile(
485      'traces/elapsed_and_real_timestamp/screen_recording_metadata.json',
486    );
487    await loadFiles([screenRecording, metadata]);
488    await expectLoadResult(1, []);
489  });
490
491  async function loadFiles(
492    files: File[],
493    source: FilesSource = FilesSource.TEST,
494  ) {
495    await tracePipeline.loadFiles(files, source, progressListener);
496    expect(progressListener.onOperationFinished).toHaveBeenCalled();
497    await tracePipeline.buildTraces();
498  }
499
500  async function expectLoadResult(
501    numberOfTraces: number,
502    expectedWarnings: UserWarning[],
503  ) {
504    userNotifierChecker.expectAdded(expectedWarnings);
505    expect(tracePipeline.getTraces().getSize()).toEqual(numberOfTraces);
506  }
507
508  async function expectDownloadResult(expectedArchiveContents: string[]) {
509    const zipArchive = await tracePipeline.makeZipArchiveWithLoadedTraceFiles();
510    const actualArchiveContents = (await FileUtils.unzipFile(zipArchive))
511      .map((file) => file.name)
512      .sort();
513    expect(actualArchiveContents).toEqual(expectedArchiveContents);
514  }
515});
516