• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {produce} from 'immer';
16
17import {assertExists} from '../base/logging';
18import {PrimaryTrackSortKey} from '../public';
19import {HEAP_PROFILE_TRACK_KIND} from '../core_plugins/heap_profile';
20import {PROCESS_SCHEDULING_TRACK_KIND} from '../core_plugins/process_summary/process_scheduling_track';
21
22import {StateActions} from './actions';
23import {createEmptyState} from './empty_state';
24import {
25  InThreadTrackSortKey,
26  SCROLLING_TRACK_GROUP,
27  State,
28  TraceUrlSource,
29  TrackSortKey,
30} from './state';
31import {
32  THREAD_SLICE_TRACK_KIND,
33  THREAD_STATE_TRACK_KIND,
34} from '../core/track_kinds';
35
36function fakeTrack(
37  state: State,
38  args: {
39    key: string;
40    uri?: string;
41    trackGroup?: string;
42    trackSortKey?: TrackSortKey;
43    name?: string;
44    tid?: string;
45  },
46): State {
47  return produce(state, (draft) => {
48    StateActions.addTrack(draft, {
49      uri: args.uri || 'sometrack',
50      key: args.key,
51      name: args.name || 'A track',
52      trackSortKey:
53        args.trackSortKey === undefined
54          ? PrimaryTrackSortKey.ORDINARY_TRACK
55          : args.trackSortKey,
56      trackGroup: args.trackGroup || SCROLLING_TRACK_GROUP,
57    });
58  });
59}
60
61function fakeTrackGroup(
62  state: State,
63  args: {key: string; summaryTrackKey: string},
64): State {
65  return produce(state, (draft) => {
66    StateActions.addTrackGroup(draft, {
67      name: 'A group',
68      key: args.key,
69      collapsed: false,
70      summaryTrackKey: args.summaryTrackKey,
71    });
72  });
73}
74
75function pinnedAndScrollingTracks(
76  state: State,
77  keys: string[],
78  pinnedTracks: string[],
79  scrollingTracks: string[],
80): State {
81  for (const key of keys) {
82    state = fakeTrack(state, {key});
83  }
84  state = produce(state, (draft) => {
85    draft.pinnedTracks = pinnedTracks;
86    draft.scrollingTracks = scrollingTracks;
87  });
88  return state;
89}
90
91test('add scrolling tracks', () => {
92  const once = produce(createEmptyState(), (draft) => {
93    StateActions.addTrack(draft, {
94      uri: 'cpu',
95      name: 'Cpu 1',
96      trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
97      trackGroup: SCROLLING_TRACK_GROUP,
98    });
99  });
100  const twice = produce(once, (draft) => {
101    StateActions.addTrack(draft, {
102      uri: 'cpu',
103      name: 'Cpu 2',
104      trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
105      trackGroup: SCROLLING_TRACK_GROUP,
106    });
107  });
108
109  expect(Object.values(twice.tracks).length).toBe(2);
110  expect(twice.scrollingTracks.length).toBe(2);
111});
112
113test('add track to track group', () => {
114  let state = createEmptyState();
115  state = fakeTrack(state, {key: 's'});
116
117  const afterGroup = produce(state, (draft) => {
118    StateActions.addTrackGroup(draft, {
119      name: 'A track group',
120      key: '123-123-123',
121      summaryTrackKey: 's',
122      collapsed: false,
123    });
124  });
125
126  const afterTrackAdd = produce(afterGroup, (draft) => {
127    StateActions.addTrack(draft, {
128      key: '1',
129      uri: 'slices',
130      name: 'renderer 1',
131      trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
132      trackGroup: '123-123-123',
133    });
134  });
135
136  expect(afterTrackAdd.trackGroups['123-123-123'].summaryTrack).toBe('s');
137  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('1');
138});
139
140test('reorder tracks', () => {
141  const once = produce(createEmptyState(), (draft) => {
142    StateActions.addTrack(draft, {
143      uri: 'cpu',
144      name: 'Cpu 1',
145      trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
146    });
147    StateActions.addTrack(draft, {
148      uri: 'cpu',
149      name: 'Cpu 2',
150      trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
151    });
152  });
153
154  const firstTrackKey = once.scrollingTracks[0];
155  const secondTrackKey = once.scrollingTracks[1];
156
157  const twice = produce(once, (draft) => {
158    StateActions.moveTrack(draft, {
159      srcId: `${firstTrackKey}`,
160      op: 'after',
161      dstId: `${secondTrackKey}`,
162    });
163  });
164
165  expect(twice.scrollingTracks[0]).toBe(secondTrackKey);
166  expect(twice.scrollingTracks[1]).toBe(firstTrackKey);
167});
168
169test('reorder pinned to scrolling', () => {
170  let state = createEmptyState();
171  state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']);
172
173  const after = produce(state, (draft) => {
174    StateActions.moveTrack(draft, {
175      srcId: 'b',
176      op: 'before',
177      dstId: 'c',
178    });
179  });
180
181  expect(after.pinnedTracks).toEqual(['a']);
182  expect(after.scrollingTracks).toEqual(['b', 'c']);
183});
184
185test('reorder scrolling to pinned', () => {
186  let state = createEmptyState();
187  state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']);
188
189  const after = produce(state, (draft) => {
190    StateActions.moveTrack(draft, {
191      srcId: 'b',
192      op: 'after',
193      dstId: 'a',
194    });
195  });
196
197  expect(after.pinnedTracks).toEqual(['a', 'b']);
198  expect(after.scrollingTracks).toEqual(['c']);
199});
200
201test('reorder clamp bottom', () => {
202  let state = createEmptyState();
203  state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']);
204
205  const after = produce(state, (draft) => {
206    StateActions.moveTrack(draft, {
207      srcId: 'a',
208      op: 'before',
209      dstId: 'a',
210    });
211  });
212  expect(after).toEqual(state);
213});
214
215test('reorder clamp top', () => {
216  let state = createEmptyState();
217  state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']);
218
219  const after = produce(state, (draft) => {
220    StateActions.moveTrack(draft, {
221      srcId: 'c',
222      op: 'after',
223      dstId: 'c',
224    });
225  });
226  expect(after).toEqual(state);
227});
228
229test('pin', () => {
230  let state = createEmptyState();
231  state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']);
232
233  const after = produce(state, (draft) => {
234    StateActions.toggleTrackPinned(draft, {
235      trackKey: 'c',
236    });
237  });
238  expect(after.pinnedTracks).toEqual(['a', 'c']);
239  expect(after.scrollingTracks).toEqual(['b']);
240});
241
242test('unpin', () => {
243  let state = createEmptyState();
244  state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']);
245
246  const after = produce(state, (draft) => {
247    StateActions.toggleTrackPinned(draft, {
248      trackKey: 'a',
249    });
250  });
251  expect(after.pinnedTracks).toEqual(['b']);
252  expect(after.scrollingTracks).toEqual(['a', 'c']);
253});
254
255test('open trace', () => {
256  const state = createEmptyState();
257  const recordConfig = state.recordConfig;
258  const after = produce(state, (draft) => {
259    StateActions.openTraceFromUrl(draft, {
260      url: 'https://example.com/bar',
261    });
262  });
263
264  expect(after.engine).not.toBeUndefined();
265  expect((after.engine!!.source as TraceUrlSource).url).toBe(
266    'https://example.com/bar',
267  );
268  expect(after.recordConfig).toBe(recordConfig);
269});
270
271test('open second trace from file', () => {
272  const once = produce(createEmptyState(), (draft) => {
273    StateActions.openTraceFromUrl(draft, {
274      url: 'https://example.com/bar',
275    });
276  });
277
278  const twice = produce(once, (draft) => {
279    StateActions.addTrack(draft, {
280      uri: 'cpu',
281      name: 'Cpu 1',
282      trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
283    });
284  });
285
286  const thrice = produce(twice, (draft) => {
287    StateActions.openTraceFromUrl(draft, {
288      url: 'https://example.com/foo',
289    });
290  });
291
292  expect(thrice.engine).not.toBeUndefined();
293  expect((thrice.engine!!.source as TraceUrlSource).url).toBe(
294    'https://example.com/foo',
295  );
296  expect(thrice.pinnedTracks.length).toBe(0);
297  expect(thrice.scrollingTracks.length).toBe(0);
298});
299
300test('setEngineReady with missing engine is ignored', () => {
301  const state = createEmptyState();
302  produce(state, (draft) => {
303    StateActions.setEngineReady(draft, {
304      engineId: '1',
305      ready: true,
306      mode: 'WASM',
307    });
308  });
309});
310
311test('setEngineReady', () => {
312  const state = createEmptyState();
313  const after = produce(state, (draft) => {
314    StateActions.openTraceFromUrl(draft, {
315      url: 'https://example.com/bar',
316    });
317    const latestEngineId = assertExists(draft.engine).id;
318    StateActions.setEngineReady(draft, {
319      engineId: latestEngineId,
320      ready: true,
321      mode: 'WASM',
322    });
323  });
324  expect(after.engine!!.ready).toBe(true);
325});
326
327test('sortTracksByPriority', () => {
328  let state = createEmptyState();
329  state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'});
330  state = fakeTrack(state, {
331    key: 'b',
332    uri: HEAP_PROFILE_TRACK_KIND,
333    trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
334    trackGroup: 'g',
335  });
336  state = fakeTrack(state, {
337    key: 'a',
338    uri: PROCESS_SCHEDULING_TRACK_KIND,
339    trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK,
340    trackGroup: 'g',
341  });
342
343  const after = produce(state, (draft) => {
344    StateActions.sortThreadTracks(draft, {});
345  });
346
347  // High Priority tracks should be sorted before Low Priority tracks:
348  // 'b' appears twice because it's the summary track
349  expect(after.trackGroups['g'].tracks).toEqual(['a', 'b']);
350});
351
352test('sortTracksByPriorityAndKindAndName', () => {
353  let state = createEmptyState();
354  state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'});
355  state = fakeTrack(state, {
356    key: 'a',
357    uri: PROCESS_SCHEDULING_TRACK_KIND,
358    trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK,
359    trackGroup: 'g',
360  });
361  state = fakeTrack(state, {
362    key: 'b',
363    uri: THREAD_SLICE_TRACK_KIND,
364    trackGroup: 'g',
365    trackSortKey: PrimaryTrackSortKey.MAIN_THREAD,
366  });
367  state = fakeTrack(state, {
368    key: 'c',
369    uri: THREAD_SLICE_TRACK_KIND,
370    trackGroup: 'g',
371    trackSortKey: PrimaryTrackSortKey.RENDER_THREAD,
372  });
373  state = fakeTrack(state, {
374    key: 'd',
375    uri: THREAD_SLICE_TRACK_KIND,
376    trackGroup: 'g',
377    trackSortKey: PrimaryTrackSortKey.GPU_COMPLETION_THREAD,
378  });
379  state = fakeTrack(state, {
380    key: 'e',
381    uri: HEAP_PROFILE_TRACK_KIND,
382    trackGroup: 'g',
383  });
384  state = fakeTrack(state, {
385    key: 'f',
386    uri: THREAD_SLICE_TRACK_KIND,
387    trackGroup: 'g',
388    name: 'T2',
389  });
390  state = fakeTrack(state, {
391    key: 'g',
392    uri: THREAD_SLICE_TRACK_KIND,
393    trackGroup: 'g',
394    name: 'T10',
395  });
396
397  const after = produce(state, (draft) => {
398    StateActions.sortThreadTracks(draft, {});
399  });
400
401  // The order should be determined by:
402  // 1.High priority
403  // 2.Non ordinary track kinds
404  // 3.Low priority
405  // 4.Collated name string (ie. 'T2' will be before 'T10')
406  expect(after.trackGroups['g'].tracks).toEqual([
407    'a',
408    'b',
409    'c',
410    'd',
411    'e',
412    'f',
413    'g',
414  ]);
415});
416
417test('sortTracksByTidThenName', () => {
418  let state = createEmptyState();
419  state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'a'});
420  state = fakeTrack(state, {
421    key: 'a',
422    uri: THREAD_SLICE_TRACK_KIND,
423    trackSortKey: {
424      utid: 1,
425      priority: InThreadTrackSortKey.ORDINARY,
426    },
427    trackGroup: 'g',
428    name: 'aaa',
429    tid: '1',
430  });
431  state = fakeTrack(state, {
432    key: 'b',
433    uri: THREAD_SLICE_TRACK_KIND,
434    trackSortKey: {
435      utid: 2,
436      priority: InThreadTrackSortKey.ORDINARY,
437    },
438    trackGroup: 'g',
439    name: 'bbb',
440    tid: '2',
441  });
442  state = fakeTrack(state, {
443    key: 'c',
444    uri: THREAD_STATE_TRACK_KIND,
445    trackSortKey: {
446      utid: 1,
447      priority: InThreadTrackSortKey.ORDINARY,
448    },
449    trackGroup: 'g',
450    name: 'ccc',
451    tid: '1',
452  });
453
454  const after = produce(state, (draft) => {
455    StateActions.sortThreadTracks(draft, {});
456  });
457
458  expect(after.trackGroups['g'].tracks).toEqual(['a', 'c', 'b']);
459});
460