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 {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common'; 18import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common'; 19import { 20 PROCESS_SCHEDULING_TRACK_KIND 21} from '../tracks/process_scheduling/common'; 22import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common'; 23 24import {StateActions} from './actions'; 25import { 26 createEmptyState, 27 SCROLLING_TRACK_GROUP, 28 State, 29 TraceUrlSource, 30 TrackKindPriority, 31} from './state'; 32 33function fakeTrack(state: State, args: { 34 id: string, 35 kind?: string, 36 trackGroup?: string, 37 trackKindPriority?: TrackKindPriority, 38 name?: string, 39 tid?: string 40}): State { 41 return produce(state, draft => { 42 StateActions.addTrack(draft, { 43 id: args.id, 44 engineId: '0', 45 kind: args.kind || 'SOME_TRACK_KIND', 46 name: args.name || 'A track', 47 trackKindPriority: args.trackKindPriority === undefined ? 48 TrackKindPriority.ORDINARY : 49 args.trackKindPriority, 50 trackGroup: args.trackGroup || SCROLLING_TRACK_GROUP, 51 config: {tid: args.tid || '0'} 52 }); 53 }); 54} 55 56function fakeTrackGroup( 57 state: State, args: {id: string, summaryTrackId: string}): State { 58 return produce(state, draft => { 59 StateActions.addTrackGroup(draft, { 60 name: 'A group', 61 id: args.id, 62 engineId: '0', 63 collapsed: false, 64 summaryTrackId: args.summaryTrackId 65 }); 66 }); 67} 68 69function pinnedAndScrollingTracks( 70 state: State, 71 ids: string[], 72 pinnedTracks: string[], 73 scrollingTracks: string[]): State { 74 for (const id of ids) { 75 state = fakeTrack(state, {id}); 76 } 77 state = produce(state, draft => { 78 draft.pinnedTracks = pinnedTracks; 79 draft.scrollingTracks = scrollingTracks; 80 }); 81 return state; 82} 83 84test('navigate', () => { 85 const after = produce(createEmptyState(), draft => { 86 StateActions.navigate(draft, {route: '/foo'}); 87 }); 88 expect(after.route).toBe('/foo'); 89}); 90 91test('add scrolling tracks', () => { 92 const once = produce(createEmptyState(), draft => { 93 StateActions.addTrack(draft, { 94 engineId: '1', 95 kind: 'cpu', 96 name: 'Cpu 1', 97 trackKindPriority: TrackKindPriority.ORDINARY, 98 trackGroup: SCROLLING_TRACK_GROUP, 99 config: {}, 100 }); 101 }); 102 const twice = produce(once, draft => { 103 StateActions.addTrack(draft, { 104 engineId: '2', 105 kind: 'cpu', 106 name: 'Cpu 2', 107 trackKindPriority: TrackKindPriority.ORDINARY, 108 trackGroup: SCROLLING_TRACK_GROUP, 109 config: {}, 110 }); 111 }); 112 113 expect(Object.values(twice.tracks).length).toBe(2); 114 expect(twice.scrollingTracks.length).toBe(2); 115}); 116 117test('add track to track group', () => { 118 let state = createEmptyState(); 119 state = fakeTrack(state, {id: 's'}); 120 121 const afterGroup = produce(state, draft => { 122 StateActions.addTrackGroup(draft, { 123 engineId: '1', 124 name: 'A track group', 125 id: '123-123-123', 126 summaryTrackId: 's', 127 collapsed: false, 128 }); 129 }); 130 131 const afterTrackAdd = produce(afterGroup, draft => { 132 StateActions.addTrack(draft, { 133 id: '1', 134 engineId: '1', 135 kind: 'slices', 136 name: 'renderer 1', 137 trackKindPriority: TrackKindPriority.ORDINARY, 138 trackGroup: '123-123-123', 139 config: {}, 140 }); 141 }); 142 143 expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('s'); 144 expect(afterTrackAdd.trackGroups['123-123-123'].tracks[1]).toBe('1'); 145}); 146 147test('reorder tracks', () => { 148 const once = produce(createEmptyState(), draft => { 149 StateActions.addTrack(draft, { 150 engineId: '1', 151 kind: 'cpu', 152 name: 'Cpu 1', 153 trackKindPriority: TrackKindPriority.ORDINARY, 154 config: {}, 155 }); 156 StateActions.addTrack(draft, { 157 engineId: '2', 158 kind: 'cpu', 159 name: 'Cpu 2', 160 trackKindPriority: TrackKindPriority.ORDINARY, 161 config: {}, 162 }); 163 }); 164 165 const firstTrackId = once.scrollingTracks[0]; 166 const secondTrackId = once.scrollingTracks[1]; 167 168 const twice = produce(once, draft => { 169 StateActions.moveTrack(draft, { 170 srcId: `${firstTrackId}`, 171 op: 'after', 172 dstId: `${secondTrackId}`, 173 }); 174 }); 175 176 expect(twice.scrollingTracks[0]).toBe(secondTrackId); 177 expect(twice.scrollingTracks[1]).toBe(firstTrackId); 178}); 179 180test('reorder pinned to scrolling', () => { 181 let state = createEmptyState(); 182 state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']); 183 184 const after = produce(state, draft => { 185 StateActions.moveTrack(draft, { 186 srcId: 'b', 187 op: 'before', 188 dstId: 'c', 189 }); 190 }); 191 192 expect(after.pinnedTracks).toEqual(['a']); 193 expect(after.scrollingTracks).toEqual(['b', 'c']); 194}); 195 196test('reorder scrolling to pinned', () => { 197 let state = createEmptyState(); 198 state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']); 199 200 const after = produce(state, draft => { 201 StateActions.moveTrack(draft, { 202 srcId: 'b', 203 op: 'after', 204 dstId: 'a', 205 }); 206 }); 207 208 expect(after.pinnedTracks).toEqual(['a', 'b']); 209 expect(after.scrollingTracks).toEqual(['c']); 210}); 211 212test('reorder clamp bottom', () => { 213 let state = createEmptyState(); 214 state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']); 215 216 const after = produce(state, draft => { 217 StateActions.moveTrack(draft, { 218 srcId: 'a', 219 op: 'before', 220 dstId: 'a', 221 }); 222 }); 223 expect(after).toEqual(state); 224}); 225 226test('reorder clamp top', () => { 227 let state = createEmptyState(); 228 state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']); 229 230 const after = produce(state, draft => { 231 StateActions.moveTrack(draft, { 232 srcId: 'c', 233 op: 'after', 234 dstId: 'c', 235 }); 236 }); 237 expect(after).toEqual(state); 238}); 239 240test('pin', () => { 241 let state = createEmptyState(); 242 state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']); 243 244 const after = produce(state, draft => { 245 StateActions.toggleTrackPinned(draft, { 246 trackId: 'c', 247 }); 248 }); 249 expect(after.pinnedTracks).toEqual(['a', 'c']); 250 expect(after.scrollingTracks).toEqual(['b']); 251}); 252 253test('unpin', () => { 254 let state = createEmptyState(); 255 state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']); 256 257 const after = produce(state, draft => { 258 StateActions.toggleTrackPinned(draft, { 259 trackId: 'a', 260 }); 261 }); 262 expect(after.pinnedTracks).toEqual(['b']); 263 expect(after.scrollingTracks).toEqual(['a', 'c']); 264}); 265 266test('open trace', () => { 267 const state = createEmptyState(); 268 state.nextId = 100; 269 const recordConfig = state.recordConfig; 270 const after = produce(state, draft => { 271 StateActions.openTraceFromUrl(draft, { 272 url: 'https://example.com/bar', 273 }); 274 }); 275 276 const engineKeys = Object.keys(after.engines); 277 expect(after.nextId).toBe(101); 278 expect(engineKeys.length).toBe(1); 279 expect((after.engines[engineKeys[0]].source as TraceUrlSource).url) 280 .toBe('https://example.com/bar'); 281 expect(after.route).toBe('/viewer'); 282 expect(after.recordConfig).toBe(recordConfig); 283}); 284 285test('open second trace from file', () => { 286 const once = produce(createEmptyState(), draft => { 287 StateActions.openTraceFromUrl(draft, { 288 url: 'https://example.com/bar', 289 }); 290 }); 291 292 const twice = produce(once, draft => { 293 StateActions.addTrack(draft, { 294 engineId: '1', 295 kind: 'cpu', 296 name: 'Cpu 1', 297 trackKindPriority: TrackKindPriority.ORDINARY, 298 config: {}, 299 }); 300 }); 301 302 const thrice = produce(twice, draft => { 303 StateActions.openTraceFromUrl(draft, { 304 url: 'https://example.com/foo', 305 }); 306 }); 307 308 const engineKeys = Object.keys(thrice.engines); 309 expect(engineKeys.length).toBe(1); 310 expect((thrice.engines[engineKeys[0]].source as TraceUrlSource).url) 311 .toBe('https://example.com/foo'); 312 expect(thrice.pinnedTracks.length).toBe(0); 313 expect(thrice.scrollingTracks.length).toBe(0); 314 expect(thrice.route).toBe('/viewer'); 315}); 316 317test('setEngineReady with missing engine is ignored', () => { 318 const state = createEmptyState(); 319 produce(state, draft => { 320 StateActions.setEngineReady( 321 draft, {engineId: '1', ready: true, mode: 'WASM'}); 322 }); 323}); 324 325test('setEngineReady', () => { 326 const state = createEmptyState(); 327 state.nextId = 100; 328 const after = produce(state, draft => { 329 StateActions.openTraceFromUrl(draft, { 330 url: 'https://example.com/bar', 331 }); 332 StateActions.setEngineReady( 333 draft, {engineId: '100', ready: true, mode: 'WASM'}); 334 }); 335 expect(after.engines['100'].ready).toBe(true); 336}); 337 338test('sortTracksByPriority', () => { 339 let state = createEmptyState(); 340 state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'}); 341 state = fakeTrack( 342 state, {id: 'b', kind: HEAP_PROFILE_TRACK_KIND, trackGroup: 'g'}); 343 state = fakeTrack( 344 state, {id: 'a', kind: PROCESS_SCHEDULING_TRACK_KIND, trackGroup: 'g'}); 345 346 const after = produce(state, draft => { 347 StateActions.sortThreadTracks(draft, {}); 348 }); 349 350 // High Priority tracks should be sorted before Low Priority tracks: 351 // 'b' appears twice because it's the summary track 352 expect(after.trackGroups['g'].tracks).toEqual(['a', 'b', 'b']); 353}); 354 355test('sortTracksByPriorityAndKindAndName', () => { 356 let state = createEmptyState(); 357 state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'}); 358 state = fakeTrack( 359 state, {id: 'a', kind: PROCESS_SCHEDULING_TRACK_KIND, trackGroup: 'g'}); 360 state = fakeTrack(state, { 361 id: 'b', 362 kind: SLICE_TRACK_KIND, 363 trackGroup: 'g', 364 trackKindPriority: TrackKindPriority.MAIN_THREAD 365 }); 366 state = fakeTrack(state, { 367 id: 'c', 368 kind: SLICE_TRACK_KIND, 369 trackGroup: 'g', 370 trackKindPriority: TrackKindPriority.RENDER_THREAD 371 }); 372 state = fakeTrack(state, { 373 id: 'd', 374 kind: SLICE_TRACK_KIND, 375 trackGroup: 'g', 376 trackKindPriority: TrackKindPriority.GPU_COMPLETION 377 }); 378 state = fakeTrack( 379 state, {id: 'e', kind: HEAP_PROFILE_TRACK_KIND, trackGroup: 'g'}); 380 state = fakeTrack( 381 state, {id: 'f', kind: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T2'}); 382 state = fakeTrack( 383 state, {id: 'g', kind: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T10'}); 384 385 const after = produce(state, draft => { 386 StateActions.sortThreadTracks(draft, {}); 387 }); 388 389 // The order should be determined by: 390 // 1.High priority 391 // 2.Non ordinary track kinds 392 // 3.Low priority 393 // 4.Collated name string (ie. 'T2' will be before 'T10') 394 expect(after.trackGroups['g'].tracks) 395 .toEqual(['a', 'b', 'b', 'c', 'd', 'e', 'f', 'g']); 396}); 397 398test('sortTracksByTidThenName', () => { 399 let state = createEmptyState(); 400 state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'a'}); 401 state = fakeTrack(state, { 402 id: 'a', 403 kind: SLICE_TRACK_KIND, 404 trackGroup: 'g', 405 name: 'aaa', 406 tid: '1' 407 }); 408 state = fakeTrack(state, { 409 id: 'b', 410 kind: SLICE_TRACK_KIND, 411 trackGroup: 'g', 412 name: 'bbb', 413 tid: '2' 414 }); 415 state = fakeTrack(state, { 416 id: 'c', 417 kind: THREAD_STATE_TRACK_KIND, 418 trackGroup: 'g', 419 name: 'ccc', 420 tid: '1' 421 }); 422 423 const after = produce(state, draft => { 424 StateActions.sortThreadTracks(draft, {}); 425 }); 426 427 expect(after.trackGroups['g'].tracks).toEqual(['a', 'a', 'c', 'b']); 428}); 429