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