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