1// Copyright (C) 2020 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 {v4 as uuidv4} from 'uuid'; 16 17import {assertExists} from '../base/logging'; 18import {sqliteString} from '../base/string_utils'; 19import {Actions, AddTrackArgs, DeferredAction} from '../common/actions'; 20import { 21 InThreadTrackSortKey, 22 SCROLLING_TRACK_GROUP, 23 TrackSortKey, 24 UtidToTrackSortKey, 25} from '../common/state'; 26import {globals} from '../frontend/globals'; 27import {PERF_SAMPLE_FLAG} from '../core/feature_flags'; 28import {PrimaryTrackSortKey} from '../public'; 29import {getTrackName} from '../public/utils'; 30import {Engine, EngineBase} from '../trace_processor/engine'; 31import {NUM, NUM_NULL, STR, STR_NULL} from '../trace_processor/query_result'; 32import { 33 ENABLE_SCROLL_JANK_PLUGIN_V2, 34 getScrollJankTracks, 35} from '../core_plugins/chrome_scroll_jank'; 36import {decideTracks as scrollJankDecideTracks} from '../core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track'; 37import {COUNTER_TRACK_KIND} from '../core_plugins/counter'; 38import {decideTracks as screenshotDecideTracks} from '../core_plugins/screenshots'; 39import { 40 ACTUAL_FRAMES_SLICE_TRACK_KIND, 41 ASYNC_SLICE_TRACK_KIND, 42 EXPECTED_FRAMES_SLICE_TRACK_KIND, 43 THREAD_SLICE_TRACK_KIND, 44 THREAD_STATE_TRACK_KIND, 45} from '../core/track_kinds'; 46 47const MEM_DMA_COUNTER_NAME = 'mem.dma_heap'; 48const MEM_DMA = 'mem.dma_buffer'; 49const MEM_ION = 'mem.ion'; 50const F2FS_IOSTAT_TAG = 'f2fs_iostat.'; 51const F2FS_IOSTAT_GROUP_NAME = 'f2fs_iostat'; 52const F2FS_IOSTAT_LAT_TAG = 'f2fs_iostat_latency.'; 53const F2FS_IOSTAT_LAT_GROUP_NAME = 'f2fs_iostat_latency'; 54const DISK_IOSTAT_TAG = 'diskstat.'; 55const DISK_IOSTAT_GROUP_NAME = 'diskstat'; 56const BUDDY_INFO_TAG = 'mem.buddyinfo'; 57const UFS_CMD_TAG_REGEX = new RegExp('^io.ufs.command.tag.*$'); 58const UFS_CMD_TAG_GROUP = 'io.ufs.command.tags'; 59// NB: Userspace wakelocks start with "WakeLock" not "Wakelock". 60const KERNEL_WAKELOCK_REGEX = new RegExp('^Wakelock.*$'); 61const KERNEL_WAKELOCK_GROUP = 'Kernel wakelocks'; 62const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$'); 63const NETWORK_TRACK_GROUP = 'Networking'; 64const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:'); 65const ENTITY_RESIDENCY_GROUP = 'Entity residency'; 66const UCLAMP_REGEX = new RegExp('^UCLAMP_'); 67const UCLAMP_GROUP = 'Scheduler Utilization Clamping'; 68const POWER_RAILS_GROUP = 'Power Rails'; 69const POWER_RAILS_REGEX = new RegExp('^power.'); 70const FREQUENCY_GROUP = 'Frequency Scaling'; 71const TEMPERATURE_REGEX = new RegExp('^.* Temperature$'); 72const TEMPERATURE_GROUP = 'Temperature'; 73const IRQ_GROUP = 'IRQs'; 74const IRQ_REGEX = new RegExp('^(Irq|SoftIrq) Cpu.*'); 75const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*'); 76const CHROME_TRACK_GROUP = 'Chrome Global Tracks'; 77const MISC_GROUP = 'Misc Global Tracks'; 78 79export async function decideTracks( 80 engine: EngineBase, 81): Promise<DeferredAction[]> { 82 return new TrackDecider(engine).decideTracks(); 83} 84 85class TrackDecider { 86 private engine: EngineBase; 87 private upidToUuid = new Map<number, string>(); 88 private utidToUuid = new Map<number, string>(); 89 private tracksToAdd: AddTrackArgs[] = []; 90 private addTrackGroupActions: DeferredAction[] = []; 91 92 constructor(engine: EngineBase) { 93 this.engine = engine; 94 } 95 96 async guessCpuSizes(): Promise<Map<number, string>> { 97 const cpuToSize = new Map<number, string>(); 98 await this.engine.query(` 99 include perfetto module cpu.size; 100 `); 101 const result = await this.engine.query(` 102 select cpu, cpu_guess_core_type(cpu) as size 103 from cpu_counter_track 104 join _counter_track_summary using (id); 105 `); 106 107 const it = result.iter({ 108 cpu: NUM, 109 size: STR_NULL, 110 }); 111 112 for (; it.valid(); it.next()) { 113 const size = it.size; 114 if (size !== null) { 115 cpuToSize.set(it.cpu, size); 116 } 117 } 118 119 return cpuToSize; 120 } 121 122 async addCpuSchedulingTracks(): Promise<void> { 123 const cpus = globals.traceContext.cpus; 124 const cpuToSize = await this.guessCpuSizes(); 125 126 for (const cpu of cpus) { 127 const size = cpuToSize.get(cpu); 128 const name = size === undefined ? `Cpu ${cpu}` : `Cpu ${cpu} (${size})`; 129 this.tracksToAdd.push({ 130 uri: `perfetto.CpuSlices#cpu${cpu}`, 131 trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, 132 name, 133 trackGroup: SCROLLING_TRACK_GROUP, 134 }); 135 } 136 } 137 138 async addCpuFreqTracks(engine: Engine): Promise<void> { 139 const cpus = globals.traceContext.cpus; 140 141 for (const cpu of cpus) { 142 // Only add a cpu freq track if we have 143 // cpu freq data. 144 // TODO(hjd): Find a way to display cpu idle 145 // events even if there are no cpu freq events. 146 const cpuFreqIdleResult = await engine.query(` 147 select 148 id as cpuFreqId, 149 ( 150 select id 151 from cpu_counter_track 152 where name = 'cpuidle' 153 and cpu = ${cpu} 154 limit 1 155 ) as cpuIdleId 156 from cpu_counter_track 157 join _counter_track_summary using (id) 158 where name = 'cpufreq' and cpu = ${cpu} 159 limit 1; 160 `); 161 162 if (cpuFreqIdleResult.numRows() > 0) { 163 this.tracksToAdd.push({ 164 uri: `perfetto.CpuFreq#${cpu}`, 165 trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, 166 name: `Cpu ${cpu} Frequency`, 167 trackGroup: SCROLLING_TRACK_GROUP, 168 }); 169 } 170 } 171 } 172 173 async addGlobalAsyncTracks(engine: Engine): Promise<void> { 174 const rawGlobalAsyncTracks = await engine.query(` 175 with global_tracks_grouped as ( 176 select distinct t.parent_id, t.name 177 from track t 178 join _slice_track_summary using (id) 179 where t.type in ('track', 'gpu_track', 'cpu_track') 180 ) 181 select 182 t.name as name, 183 t.parent_id as parentId, 184 p.name as parentName 185 from global_tracks_grouped AS t 186 left join track p on (t.parent_id = p.id) 187 order by p.name, t.name 188 `); 189 const it = rawGlobalAsyncTracks.iter({ 190 name: STR_NULL, 191 parentId: NUM_NULL, 192 parentName: STR_NULL, 193 }); 194 195 const parentIdToGroupKey = new Map<number, string>(); 196 for (; it.valid(); it.next()) { 197 const kind = ASYNC_SLICE_TRACK_KIND; 198 const rawName = it.name === null ? undefined : it.name; 199 const rawParentName = it.parentName === null ? undefined : it.parentName; 200 const name = getTrackName({name: rawName, kind}); 201 const parentTrackId = it.parentId; 202 let groupKey = SCROLLING_TRACK_GROUP; 203 204 if (parentTrackId !== null) { 205 const maybeGroupKey = parentIdToGroupKey.get(parentTrackId); 206 if (maybeGroupKey === undefined) { 207 groupKey = uuidv4(); 208 parentIdToGroupKey.set(parentTrackId, groupKey); 209 210 const parentName = getTrackName({name: rawParentName, kind}); 211 this.addTrackGroupActions.push( 212 Actions.addTrackGroup({ 213 name: parentName, 214 key: groupKey, 215 collapsed: true, 216 }), 217 ); 218 } else { 219 groupKey = maybeGroupKey; 220 } 221 } 222 223 const track: AddTrackArgs = { 224 uri: `perfetto.AsyncSlices#${rawName}.${it.parentId}`, 225 trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, 226 trackGroup: groupKey, 227 name, 228 }; 229 230 this.tracksToAdd.push(track); 231 } 232 } 233 234 async addGpuFreqTracks(engine: Engine): Promise<void> { 235 const numGpus = globals.traceContext.gpuCount; 236 for (let gpu = 0; gpu < numGpus; gpu++) { 237 // Only add a gpu freq track if we have 238 // gpu freq data. 239 const freqExistsResult = await engine.query(` 240 select * 241 from gpu_counter_track 242 join _counter_track_summary using (id) 243 where name = 'gpufreq' and gpu_id = ${gpu} 244 limit 1; 245 `); 246 if (freqExistsResult.numRows() > 0) { 247 this.tracksToAdd.push({ 248 uri: `perfetto.Counter#gpu_freq${gpu}`, 249 name: `Gpu ${gpu} Frequency`, 250 trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, 251 trackGroup: SCROLLING_TRACK_GROUP, 252 }); 253 } 254 } 255 } 256 257 async addCpuFreqLimitCounterTracks(engine: Engine): Promise<void> { 258 const cpuFreqLimitCounterTracksSql = ` 259 select name, id 260 from cpu_counter_track 261 join _counter_track_summary using (id) 262 where name glob "Cpu * Freq Limit" 263 order by name asc 264 `; 265 266 this.addCpuCounterTracks(engine, cpuFreqLimitCounterTracksSql); 267 } 268 269 async addCpuPerfCounterTracks(engine: Engine): Promise<void> { 270 // Perf counter tracks are bound to CPUs, follow the scheduling and 271 // frequency track naming convention ("Cpu N ..."). 272 // Note: we might not have a track for a given cpu if no data was seen from 273 // it. This might look surprising in the UI, but placeholder tracks are 274 // wasteful as there's no way of collapsing global counter tracks at the 275 // moment. 276 const addCpuPerfCounterTracksSql = ` 277 select printf("Cpu %u %s", cpu, name) as name, id 278 from perf_counter_track as pct 279 join _counter_track_summary using (id) 280 order by perf_session_id asc, pct.name asc, cpu asc 281 `; 282 this.addCpuCounterTracks(engine, addCpuPerfCounterTracksSql); 283 } 284 285 async addCpuCounterTracks(engine: Engine, sql: string): Promise<void> { 286 const result = await engine.query(sql); 287 288 const it = result.iter({ 289 name: STR, 290 id: NUM, 291 }); 292 293 for (; it.valid(); it.next()) { 294 const name = it.name; 295 const trackId = it.id; 296 this.tracksToAdd.push({ 297 uri: `perfetto.Counter#cpu${trackId}`, 298 name, 299 trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, 300 trackGroup: SCROLLING_TRACK_GROUP, 301 }); 302 } 303 } 304 305 async groupGlobalIonTracks(): Promise<void> { 306 const ionTracks: AddTrackArgs[] = []; 307 let hasSummary = false; 308 for (const track of this.tracksToAdd) { 309 const isIon = track.name.startsWith(MEM_ION); 310 const isIonCounter = track.name === MEM_ION; 311 const isDmaHeapCounter = track.name === MEM_DMA_COUNTER_NAME; 312 const isDmaBuffferSlices = track.name === MEM_DMA; 313 if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) { 314 ionTracks.push(track); 315 } 316 hasSummary = hasSummary || isIonCounter; 317 hasSummary = hasSummary || isDmaHeapCounter; 318 } 319 320 if (ionTracks.length === 0 || !hasSummary) { 321 return; 322 } 323 324 const groupUuid = uuidv4(); 325 const summaryTrackKey = uuidv4(); 326 let foundSummary = false; 327 328 for (const track of ionTracks) { 329 if ( 330 !foundSummary && 331 [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name) 332 ) { 333 foundSummary = true; 334 track.key = summaryTrackKey; 335 track.trackGroup = undefined; 336 } else { 337 track.trackGroup = groupUuid; 338 } 339 } 340 341 const addGroup = Actions.addTrackGroup({ 342 summaryTrackKey, 343 name: MEM_DMA_COUNTER_NAME, 344 key: groupUuid, 345 collapsed: true, 346 }); 347 this.addTrackGroupActions.push(addGroup); 348 } 349 350 async groupGlobalIostatTracks(tag: string, group: string): Promise<void> { 351 const iostatTracks: AddTrackArgs[] = []; 352 const devMap = new Map<string, string>(); 353 354 for (const track of this.tracksToAdd) { 355 if (track.name.startsWith(tag)) { 356 iostatTracks.push(track); 357 } 358 } 359 360 if (iostatTracks.length === 0) { 361 return; 362 } 363 364 for (const track of iostatTracks) { 365 const name = track.name.split('.', 3); 366 367 if (!devMap.has(name[1])) { 368 devMap.set(name[1], uuidv4()); 369 } 370 track.name = name[2]; 371 track.trackGroup = devMap.get(name[1]); 372 } 373 374 for (const [key, value] of devMap) { 375 const groupName = group + key; 376 const addGroup = Actions.addTrackGroup({ 377 name: groupName, 378 key: value, 379 collapsed: true, 380 }); 381 this.addTrackGroupActions.push(addGroup); 382 } 383 } 384 385 async groupGlobalBuddyInfoTracks(): Promise<void> { 386 const buddyInfoTracks: AddTrackArgs[] = []; 387 const devMap = new Map<string, string>(); 388 389 for (const track of this.tracksToAdd) { 390 if (track.name.startsWith(BUDDY_INFO_TAG)) { 391 buddyInfoTracks.push(track); 392 } 393 } 394 395 if (buddyInfoTracks.length === 0) { 396 return; 397 } 398 399 for (const track of buddyInfoTracks) { 400 const tokens = track.name.split('['); 401 const node = tokens[1].slice(0, -1); 402 const zone = tokens[2].slice(0, -1); 403 const size = tokens[3].slice(0, -1); 404 405 const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone; 406 if (!devMap.has(groupName)) { 407 devMap.set(groupName, uuidv4()); 408 } 409 track.name = 'Chunk size: ' + size; 410 track.trackGroup = devMap.get(groupName); 411 } 412 413 for (const [key, value] of devMap) { 414 const groupName = key; 415 const addGroup = Actions.addTrackGroup({ 416 name: groupName, 417 key: value, 418 collapsed: true, 419 }); 420 this.addTrackGroupActions.push(addGroup); 421 } 422 } 423 424 async groupFrequencyTracks(groupName: string): Promise<void> { 425 let groupUuid = undefined; 426 for (const track of this.tracksToAdd) { 427 // Group all the frequency tracks together (except the CPU and GPU 428 // frequency ones). 429 if ( 430 track.name.endsWith('Frequency') && 431 !track.name.startsWith('Cpu') && 432 !track.name.startsWith('Gpu') 433 ) { 434 if ( 435 track.trackGroup !== undefined && 436 track.trackGroup !== SCROLLING_TRACK_GROUP 437 ) { 438 continue; 439 } 440 if (groupUuid === undefined) { 441 groupUuid = uuidv4(); 442 } 443 track.trackGroup = groupUuid; 444 } 445 } 446 447 if (groupUuid !== undefined) { 448 const addGroup = Actions.addTrackGroup({ 449 name: groupName, 450 key: groupUuid, 451 collapsed: true, 452 }); 453 this.addTrackGroupActions.push(addGroup); 454 } 455 } 456 457 async groupMiscNonAllowlistedTracks(groupName: string): Promise<void> { 458 // List of allowlisted track names. 459 const ALLOWLIST_REGEXES = [ 460 new RegExp('^Cpu .*$', 'i'), 461 new RegExp('^Gpu .*$', 'i'), 462 new RegExp('^Trace Triggers$'), 463 new RegExp('^Android App Startups$'), 464 new RegExp('^Device State.*$'), 465 new RegExp('^Android logs$'), 466 ]; 467 468 let groupUuid = undefined; 469 for (const track of this.tracksToAdd) { 470 if ( 471 track.trackGroup !== undefined && 472 track.trackGroup !== SCROLLING_TRACK_GROUP 473 ) { 474 continue; 475 } 476 let allowlisted = false; 477 for (const regex of ALLOWLIST_REGEXES) { 478 allowlisted = allowlisted || regex.test(track.name); 479 } 480 if (allowlisted) { 481 continue; 482 } 483 if (groupUuid === undefined) { 484 groupUuid = uuidv4(); 485 } 486 track.trackGroup = groupUuid; 487 } 488 489 if (groupUuid !== undefined) { 490 const addGroup = Actions.addTrackGroup({ 491 name: groupName, 492 key: groupUuid, 493 collapsed: true, 494 }); 495 this.addTrackGroupActions.push(addGroup); 496 } 497 } 498 499 async groupTracksByRegex(regex: RegExp, groupName: string): Promise<void> { 500 let groupUuid = undefined; 501 502 for (const track of this.tracksToAdd) { 503 if (regex.test(track.name)) { 504 if ( 505 track.trackGroup !== undefined && 506 track.trackGroup !== SCROLLING_TRACK_GROUP 507 ) { 508 continue; 509 } 510 if (groupUuid === undefined) { 511 groupUuid = uuidv4(); 512 } 513 track.trackGroup = groupUuid; 514 } 515 } 516 517 if (groupUuid !== undefined) { 518 const addGroup = Actions.addTrackGroup({ 519 name: groupName, 520 key: groupUuid, 521 collapsed: true, 522 }); 523 this.addTrackGroupActions.push(addGroup); 524 } 525 } 526 527 async addAnnotationTracks(engine: Engine): Promise<void> { 528 const sliceResult = await engine.query(` 529 select id, name, upid, group_name 530 from annotation_slice_track 531 order by name 532 `); 533 534 const sliceIt = sliceResult.iter({ 535 id: NUM, 536 name: STR, 537 upid: NUM, 538 group_name: STR_NULL, 539 }); 540 541 interface GroupIds { 542 id: string; 543 summaryTrackKey: string; 544 } 545 546 const groupNameToKeys = new Map<string, GroupIds>(); 547 548 for (; sliceIt.valid(); sliceIt.next()) { 549 const id = sliceIt.id; 550 const name = sliceIt.name; 551 const upid = sliceIt.upid; 552 const groupName = sliceIt.group_name; 553 554 let summaryTrackKey = undefined; 555 let trackGroupId = 556 upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid); 557 558 if (groupName) { 559 // If this is the first track encountered for a certain group, 560 // create an id for the group and use this track as the group's 561 // summary track. 562 const groupKeys = groupNameToKeys.get(groupName); 563 if (groupKeys) { 564 trackGroupId = groupKeys.id; 565 } else { 566 trackGroupId = uuidv4(); 567 summaryTrackKey = uuidv4(); 568 groupNameToKeys.set(groupName, { 569 id: trackGroupId, 570 summaryTrackKey, 571 }); 572 } 573 } 574 575 this.tracksToAdd.push({ 576 uri: `perfetto.Annotation#${id}`, 577 key: summaryTrackKey, 578 name, 579 trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, 580 trackGroup: trackGroupId, 581 }); 582 } 583 584 for (const [groupName, groupKeys] of groupNameToKeys) { 585 const addGroup = Actions.addTrackGroup({ 586 summaryTrackKey: groupKeys.summaryTrackKey, 587 name: groupName, 588 key: groupKeys.id, 589 collapsed: true, 590 }); 591 this.addTrackGroupActions.push(addGroup); 592 } 593 594 const counterResult = await engine.query(` 595 SELECT id, name, upid FROM annotation_counter_track 596 `); 597 598 const counterIt = counterResult.iter({ 599 id: NUM, 600 name: STR, 601 upid: NUM, 602 }); 603 604 for (; counterIt.valid(); counterIt.next()) { 605 const id = counterIt.id; 606 const name = counterIt.name; 607 const upid = counterIt.upid; 608 this.tracksToAdd.push({ 609 uri: `perfetto.Annotation#counter${id}`, 610 name, 611 trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, 612 trackGroup: 613 upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid), 614 }); 615 } 616 } 617 618 async addThreadStateTracks(engine: Engine): Promise<void> { 619 const result = await engine.query(` 620 select 621 utid, 622 upid, 623 tid, 624 thread.name as threadName 625 from thread 626 join _sched_summary using (utid) 627 `); 628 629 const it = result.iter({ 630 utid: NUM, 631 upid: NUM_NULL, 632 tid: NUM_NULL, 633 threadName: STR_NULL, 634 }); 635 for (; it.valid(); it.next()) { 636 const utid = it.utid; 637 const tid = it.tid; 638 const upid = it.upid; 639 const threadName = it.threadName; 640 const uuid = this.getUuidUnchecked(utid, upid); 641 if (uuid === undefined) { 642 // If a thread has no scheduling activity (i.e. the sched table has zero 643 // rows for that uid) no track group will be created and we want to skip 644 // the track creation as well. 645 continue; 646 } 647 648 const priority = InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK; 649 const name = getTrackName({ 650 utid, 651 tid, 652 threadName, 653 kind: THREAD_STATE_TRACK_KIND, 654 }); 655 656 this.tracksToAdd.push({ 657 uri: `perfetto.ThreadState#${utid}`, 658 name, 659 trackGroup: uuid, 660 trackSortKey: { 661 utid, 662 priority, 663 }, 664 }); 665 } 666 } 667 668 async addThreadCpuSampleTracks(engine: Engine): Promise<void> { 669 const result = await engine.query(` 670 with thread_cpu_sample as ( 671 select distinct utid 672 from cpu_profile_stack_sample 673 where utid != 0 674 ) 675 select 676 utid, 677 tid, 678 upid, 679 thread.name as threadName 680 from thread_cpu_sample 681 join thread using(utid)`); 682 683 const it = result.iter({ 684 utid: NUM, 685 upid: NUM_NULL, 686 tid: NUM_NULL, 687 threadName: STR_NULL, 688 }); 689 for (; it.valid(); it.next()) { 690 const utid = it.utid; 691 const upid = it.upid; 692 const threadName = it.threadName; 693 const uuid = this.getUuid(utid, upid); 694 this.tracksToAdd.push({ 695 uri: `perfetto.CpuProfile#${utid}`, 696 trackSortKey: { 697 utid, 698 priority: InThreadTrackSortKey.CPU_STACK_SAMPLES_TRACK, 699 }, 700 name: `${threadName} (CPU Stack Samples)`, 701 trackGroup: uuid, 702 }); 703 } 704 } 705 706 async addThreadCounterTracks(engine: Engine): Promise<void> { 707 const result = await engine.query(` 708 select 709 thread_counter_track.name as trackName, 710 utid, 711 upid, 712 tid, 713 thread.name as threadName, 714 thread_counter_track.id as trackId 715 from thread_counter_track 716 join _counter_track_summary using (id) 717 join thread using (utid) 718 where thread_counter_track.name != 'thread_time' 719 `); 720 721 const it = result.iter({ 722 trackName: STR_NULL, 723 utid: NUM, 724 upid: NUM_NULL, 725 tid: NUM_NULL, 726 threadName: STR_NULL, 727 trackId: NUM, 728 }); 729 for (; it.valid(); it.next()) { 730 const utid = it.utid; 731 const tid = it.tid; 732 const upid = it.upid; 733 const trackId = it.trackId; 734 const trackName = it.trackName; 735 const threadName = it.threadName; 736 const uuid = this.getUuid(utid, upid); 737 const name = getTrackName({ 738 name: trackName, 739 utid, 740 tid, 741 kind: COUNTER_TRACK_KIND, 742 threadName, 743 threadTrack: true, 744 }); 745 this.tracksToAdd.push({ 746 uri: `perfetto.Counter#thread${trackId}`, 747 name, 748 trackSortKey: { 749 utid, 750 priority: InThreadTrackSortKey.ORDINARY, 751 }, 752 trackGroup: uuid, 753 }); 754 } 755 } 756 757 async addProcessAsyncSliceTracks(engine: Engine): Promise<void> { 758 const result = await engine.query(` 759 select 760 upid, 761 t.name as trackName, 762 t.track_ids as trackIds, 763 process.name as processName, 764 process.pid as pid 765 from _process_track_summary_by_upid_and_name t 766 join process using(upid) 767 where t.name is null or t.name not glob "* Timeline" 768 `); 769 770 const it = result.iter({ 771 upid: NUM, 772 trackName: STR_NULL, 773 trackIds: STR, 774 processName: STR_NULL, 775 pid: NUM_NULL, 776 }); 777 for (; it.valid(); it.next()) { 778 const upid = it.upid; 779 const trackName = it.trackName; 780 const rawTrackIds = it.trackIds; 781 const processName = it.processName; 782 const pid = it.pid; 783 784 const uuid = this.getUuid(null, upid); 785 const name = getTrackName({ 786 name: trackName, 787 upid, 788 pid, 789 processName, 790 kind: ASYNC_SLICE_TRACK_KIND, 791 }); 792 793 this.tracksToAdd.push({ 794 uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`, 795 name, 796 trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, 797 trackGroup: uuid, 798 }); 799 } 800 } 801 802 async addUserAsyncSliceTracks(engine: Engine): Promise<void> { 803 const result = await engine.query(` 804 with grouped_packages as materialized ( 805 select 806 uid, 807 group_concat(package_name, ',') as package_name, 808 count() as cnt 809 from package_list 810 group by uid 811 ) 812 select 813 t.name as name, 814 t.uid as uid, 815 iif(g.cnt = 1, g.package_name, 'UID ' || g.uid) as packageName 816 from _uid_track_track_summary_by_uid_and_name t 817 left join grouped_packages g using (uid) 818 `); 819 820 const it = result.iter({ 821 name: STR_NULL, 822 uid: NUM_NULL, 823 packageName: STR_NULL, 824 }); 825 826 // Map From [name] -> [uuid, key] 827 const groupMap = new Map<string, string>(); 828 829 for (; it.valid(); it.next()) { 830 if (it.name == null || it.uid == null) { 831 continue; 832 } 833 const rawName = it.name; 834 const uid = it.uid === null ? undefined : it.uid; 835 const userName = it.packageName === null ? `UID ${uid}` : it.packageName; 836 837 const groupUuid = `uid-track-group${rawName}`; 838 if (groupMap.get(rawName) === undefined) { 839 groupMap.set(rawName, groupUuid); 840 } 841 842 this.tracksToAdd.push({ 843 uri: `perfetto.AsyncSlices#${rawName}.${uid}`, 844 name: userName, 845 trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, 846 trackGroup: groupUuid, 847 }); 848 } 849 850 for (const [name, groupUuid] of groupMap) { 851 const addGroup = Actions.addTrackGroup({ 852 name: name, 853 key: groupUuid, 854 collapsed: true, 855 }); 856 this.addTrackGroupActions.push(addGroup); 857 } 858 } 859 860 async addActualFramesTracks(engine: Engine): Promise<void> { 861 const result = await engine.query(` 862 select 863 upid, 864 t.name as trackName, 865 process.name as processName, 866 process.pid as pid 867 from _process_track_summary_by_upid_and_name t 868 join process using(upid) 869 where t.name = "Actual Timeline" 870 `); 871 872 const it = result.iter({ 873 upid: NUM, 874 trackName: STR_NULL, 875 processName: STR_NULL, 876 pid: NUM_NULL, 877 }); 878 for (; it.valid(); it.next()) { 879 const upid = it.upid; 880 const trackName = it.trackName; 881 const processName = it.processName; 882 const pid = it.pid; 883 884 const uuid = this.getUuid(null, upid); 885 const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND; 886 const name = getTrackName({ 887 name: trackName, 888 upid, 889 pid, 890 processName, 891 kind, 892 }); 893 894 this.tracksToAdd.push({ 895 uri: `perfetto.ActualFrames#${upid}`, 896 name, 897 trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK, 898 trackGroup: uuid, 899 }); 900 } 901 } 902 903 async addExpectedFramesTracks(engine: Engine): Promise<void> { 904 const result = await engine.query(` 905 select 906 upid, 907 t.name as trackName, 908 process.name as processName, 909 process.pid as pid 910 from _process_track_summary_by_upid_and_name t 911 join process using(upid) 912 where t.name = "Expected Timeline" 913 `); 914 915 const it = result.iter({ 916 upid: NUM, 917 trackName: STR_NULL, 918 processName: STR_NULL, 919 pid: NUM_NULL, 920 }); 921 922 for (; it.valid(); it.next()) { 923 const upid = it.upid; 924 const trackName = it.trackName; 925 const processName = it.processName; 926 const pid = it.pid; 927 928 const uuid = this.getUuid(null, upid); 929 const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND; 930 const name = getTrackName({ 931 name: trackName, 932 upid, 933 pid, 934 processName, 935 kind, 936 }); 937 938 this.tracksToAdd.push({ 939 uri: `perfetto.ExpectedFrames#${upid}`, 940 name, 941 trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK, 942 trackGroup: uuid, 943 }); 944 } 945 } 946 947 async addThreadSliceTracks(engine: Engine): Promise<void> { 948 const result = await engine.query(` 949 select 950 thread_track.utid as utid, 951 thread_track.id as trackId, 952 thread_track.name as trackName, 953 EXTRACT_ARG(thread_track.source_arg_set_id, 954 'is_root_in_scope') as isDefaultTrackForScope, 955 tid, 956 thread.name as threadName, 957 thread.upid as upid 958 from thread_track 959 join _slice_track_summary using (id) 960 join thread using(utid) 961 `); 962 963 const it = result.iter({ 964 utid: NUM, 965 trackId: NUM, 966 trackName: STR_NULL, 967 isDefaultTrackForScope: NUM_NULL, 968 tid: NUM_NULL, 969 threadName: STR_NULL, 970 upid: NUM_NULL, 971 }); 972 for (; it.valid(); it.next()) { 973 const utid = it.utid; 974 const trackId = it.trackId; 975 const trackName = it.trackName; 976 // Note that !!null === false. 977 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 978 const isDefaultTrackForScope = !!it.isDefaultTrackForScope; 979 const tid = it.tid; 980 const threadName = it.threadName; 981 const upid = it.upid; 982 983 const uuid = this.getUuid(utid, upid); 984 985 const kind = THREAD_SLICE_TRACK_KIND; 986 const name = getTrackName({name: trackName, utid, tid, threadName, kind}); 987 988 this.tracksToAdd.push({ 989 uri: `perfetto.ThreadSlices#${trackId}`, 990 name, 991 trackGroup: uuid, 992 trackSortKey: { 993 utid, 994 priority: isDefaultTrackForScope 995 ? InThreadTrackSortKey.DEFAULT_TRACK 996 : InThreadTrackSortKey.ORDINARY, 997 }, 998 }); 999 } 1000 } 1001 1002 async addProcessCounterTracks(engine: Engine): Promise<void> { 1003 const result = await engine.query(` 1004 select 1005 process_counter_track.id as trackId, 1006 process_counter_track.name as trackName, 1007 upid, 1008 process.pid, 1009 process.name as processName 1010 from process_counter_track 1011 join _counter_track_summary using (id) 1012 join process using(upid); 1013 `); 1014 const it = result.iter({ 1015 trackId: NUM, 1016 trackName: STR_NULL, 1017 upid: NUM, 1018 pid: NUM_NULL, 1019 processName: STR_NULL, 1020 }); 1021 for (let i = 0; it.valid(); ++i, it.next()) { 1022 const pid = it.pid; 1023 const upid = it.upid; 1024 const trackId = it.trackId; 1025 const trackName = it.trackName; 1026 const processName = it.processName; 1027 const uuid = this.getUuid(null, upid); 1028 const name = getTrackName({ 1029 name: trackName, 1030 upid, 1031 pid, 1032 kind: COUNTER_TRACK_KIND, 1033 processName, 1034 }); 1035 this.tracksToAdd.push({ 1036 uri: `perfetto.Counter#process${trackId}`, 1037 name, 1038 trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack( 1039 upid, 1040 trackName || undefined, 1041 ), 1042 trackGroup: uuid, 1043 }); 1044 } 1045 } 1046 1047 async addProcessHeapProfileTracks(engine: Engine): Promise<void> { 1048 const result = await engine.query(` 1049 select upid 1050 from _process_available_info_summary 1051 where allocation_count > 0 or graph_object_count > 0 1052 `); 1053 for (const it = result.iter({upid: NUM}); it.valid(); it.next()) { 1054 const upid = it.upid; 1055 const uuid = this.getUuid(null, upid); 1056 this.tracksToAdd.push({ 1057 uri: `perfetto.HeapProfile#${upid}`, 1058 trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK, 1059 name: `Heap Profile`, 1060 trackGroup: uuid, 1061 }); 1062 } 1063 } 1064 1065 async addProcessPerfSamplesTracks(engine: Engine): Promise<void> { 1066 const result = await engine.query(` 1067 select upid, pid 1068 from _process_available_info_summary 1069 join process using (upid) 1070 where perf_sample_count > 0 1071 `); 1072 for (const it = result.iter({upid: NUM, pid: NUM}); it.valid(); it.next()) { 1073 const upid = it.upid; 1074 const pid = it.pid; 1075 const uuid = this.getUuid(null, upid); 1076 this.tracksToAdd.push({ 1077 uri: `perfetto.PerfSamplesProfile#${upid}`, 1078 trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK, 1079 name: `Callstacks ${pid}`, 1080 trackGroup: uuid, 1081 }); 1082 } 1083 } 1084 1085 getUuidUnchecked(utid: number | null, upid: number | null) { 1086 return upid === null 1087 ? this.utidToUuid.get(utid!) 1088 : this.upidToUuid.get(upid); 1089 } 1090 1091 getUuid(utid: number | null, upid: number | null) { 1092 return assertExists(this.getUuidUnchecked(utid, upid)); 1093 } 1094 1095 getOrCreateUuid(utid: number | null, upid: number | null) { 1096 let uuid = this.getUuidUnchecked(utid, upid); 1097 if (uuid === undefined) { 1098 uuid = uuidv4(); 1099 if (upid === null) { 1100 this.utidToUuid.set(utid!, uuid); 1101 } else { 1102 this.upidToUuid.set(upid, uuid); 1103 } 1104 } 1105 return uuid; 1106 } 1107 1108 setUuidForUpid(upid: number, uuid: string) { 1109 this.upidToUuid.set(upid, uuid); 1110 } 1111 1112 async addKernelThreadGrouping(engine: Engine): Promise<void> { 1113 // Identify kernel threads if this is a linux system trace, and sufficient 1114 // process information is available. Kernel threads are identified by being 1115 // children of kthreadd (always pid 2). 1116 // The query will return the kthreadd process row first, which must exist 1117 // for any other kthreads to be returned by the query. 1118 // TODO(rsavitski): figure out how to handle the idle process (swapper), 1119 // which has pid 0 but appears as a distinct process (with its own comm) on 1120 // each cpu. It'd make sense to exclude its thread state track, but still 1121 // put process-scoped tracks in this group. 1122 const result = await engine.query(` 1123 select 1124 t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd 1125 from 1126 thread t 1127 join process p using (upid) 1128 left join process parent on (p.parent_upid = parent.upid) 1129 join 1130 (select true from metadata m 1131 where (m.name = 'system_name' and m.str_value = 'Linux') 1132 union 1133 select 1 from (select true from sched limit 1)) 1134 where 1135 p.pid = 2 or parent.pid = 2 1136 order by isKthreadd desc 1137 `); 1138 1139 const it = result.iter({ 1140 utid: NUM, 1141 upid: NUM, 1142 }); 1143 1144 // Not applying kernel thread grouping. 1145 if (!it.valid()) { 1146 return; 1147 } 1148 1149 // Create the track group. Use kthreadd's PROCESS_SUMMARY_TRACK for the 1150 // main track. It doesn't summarise the kernel threads within the group, 1151 // but creating a dedicated track type is out of scope at the time of 1152 // writing. 1153 const kthreadGroupUuid = uuidv4(); 1154 const summaryTrackKey = uuidv4(); 1155 this.tracksToAdd.push({ 1156 uri: 'perfetto.ProcessSummary#kernel', 1157 key: summaryTrackKey, 1158 trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK, 1159 name: `Kernel thread summary`, 1160 }); 1161 const addTrackGroup = Actions.addTrackGroup({ 1162 summaryTrackKey, 1163 name: `Kernel threads`, 1164 key: kthreadGroupUuid, 1165 collapsed: true, 1166 }); 1167 this.addTrackGroupActions.push(addTrackGroup); 1168 1169 // Set the group for all kernel threads (including kthreadd itself). 1170 for (; it.valid(); it.next()) { 1171 this.setUuidForUpid(it.upid, kthreadGroupUuid); 1172 } 1173 } 1174 1175 async addProcessTrackGroups(engine: Engine): Promise<void> { 1176 // We want to create groups of tracks in a specific order. 1177 // The tracks should be grouped: 1178 // by upid 1179 // or (if upid is null) by utid 1180 // the groups should be sorted by: 1181 // Chrome-based process rank based on process names (e.g. Browser) 1182 // has a heap profile or not 1183 // total cpu time *for the whole parent process* 1184 // process name 1185 // upid 1186 // thread name 1187 // utid 1188 const result = await engine.query(` 1189 with processGroups as ( 1190 select 1191 upid, 1192 process.pid as pid, 1193 process.name as processName, 1194 sum_running_dur as sumRunningDur, 1195 thread_slice_count + process_slice_count as sliceCount, 1196 perf_sample_count as perfSampleCount, 1197 allocation_count as heapProfileAllocationCount, 1198 graph_object_count as heapGraphObjectCount, 1199 ( 1200 select group_concat(string_value) 1201 from args 1202 where 1203 process.arg_set_id is not null and 1204 arg_set_id = process.arg_set_id and 1205 flat_key = 'chrome.process_label' 1206 ) chromeProcessLabels, 1207 case process.name 1208 when 'Browser' then 3 1209 when 'Gpu' then 2 1210 when 'Renderer' then 1 1211 else 0 1212 end as chromeProcessRank 1213 from _process_available_info_summary 1214 join process using(upid) 1215 ), 1216 threadGroups as ( 1217 select 1218 utid, 1219 tid, 1220 thread.name as threadName, 1221 sum_running_dur as sumRunningDur, 1222 slice_count as sliceCount, 1223 perf_sample_count as perfSampleCount 1224 from _thread_available_info_summary 1225 join thread using (utid) 1226 where upid is null 1227 ) 1228 select * 1229 from ( 1230 select 1231 upid, 1232 null as utid, 1233 pid, 1234 null as tid, 1235 processName, 1236 null as threadName, 1237 sumRunningDur > 0 as hasSched, 1238 heapProfileAllocationCount > 0 1239 or heapGraphObjectCount > 0 as hasHeapInfo, 1240 ifnull(chromeProcessLabels, '') as chromeProcessLabels 1241 from processGroups 1242 order by 1243 chromeProcessRank desc, 1244 heapProfileAllocationCount desc, 1245 heapGraphObjectCount desc, 1246 perfSampleCount desc, 1247 sumRunningDur desc, 1248 sliceCount desc, 1249 processName asc, 1250 upid asc 1251 ) 1252 union all 1253 select * 1254 from ( 1255 select 1256 null, 1257 utid, 1258 null as pid, 1259 tid, 1260 null as processName, 1261 threadName, 1262 sumRunningDur > 0 as hasSched, 1263 0 as hasHeapInfo, 1264 '' as chromeProcessLabels 1265 from threadGroups 1266 order by 1267 perfSampleCount desc, 1268 sumRunningDur desc, 1269 sliceCount desc, 1270 threadName asc, 1271 utid asc 1272 ) 1273 `); 1274 1275 const it = result.iter({ 1276 upid: NUM_NULL, 1277 utid: NUM_NULL, 1278 pid: NUM_NULL, 1279 tid: NUM_NULL, 1280 processName: STR_NULL, 1281 threadName: STR_NULL, 1282 hasSched: NUM_NULL, 1283 hasHeapInfo: NUM_NULL, 1284 chromeProcessLabels: STR, 1285 }); 1286 for (; it.valid(); it.next()) { 1287 const utid = it.utid; 1288 const upid = it.upid; 1289 const pid = it.pid; 1290 const tid = it.tid; 1291 const threadName = it.threadName; 1292 const processName = it.processName; 1293 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 1294 const hasSched = !!it.hasSched; 1295 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 1296 const hasHeapInfo = !!it.hasHeapInfo; 1297 1298 const summaryTrackKey = uuidv4(); 1299 const type = hasSched ? 'schedule' : 'summary'; 1300 const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`; 1301 1302 // If previous groupings (e.g. kernel threads) picked up there tracks, 1303 // don't try to regroup them. 1304 const pUuid = 1305 upid === null ? this.utidToUuid.get(utid!) : this.upidToUuid.get(upid); 1306 if (pUuid !== undefined) { 1307 continue; 1308 } 1309 1310 this.tracksToAdd.push({ 1311 uri, 1312 key: summaryTrackKey, 1313 trackSortKey: hasSched 1314 ? PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK 1315 : PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK, 1316 name: `${upid === null ? tid : pid} summary`, 1317 labels: it.chromeProcessLabels.split(','), 1318 }); 1319 1320 const name = getTrackName({ 1321 utid, 1322 processName, 1323 pid, 1324 threadName, 1325 tid, 1326 upid, 1327 }); 1328 const addTrackGroup = Actions.addTrackGroup({ 1329 summaryTrackKey, 1330 name, 1331 key: this.getOrCreateUuid(utid, upid), 1332 // Perf profiling tracks remain collapsed, otherwise we would have too 1333 // many expanded process tracks for some perf traces, leading to 1334 // jankyness. 1335 collapsed: !hasHeapInfo, 1336 }); 1337 this.addTrackGroupActions.push(addTrackGroup); 1338 } 1339 } 1340 1341 private async computeThreadOrderingMetadata(): Promise<UtidToTrackSortKey> { 1342 const result = await this.engine.query(` 1343 select 1344 utid, 1345 tid, 1346 (select pid from process p where t.upid = p.upid) as pid, 1347 t.name as threadName 1348 from thread t 1349 `); 1350 1351 const it = result.iter({ 1352 utid: NUM, 1353 tid: NUM_NULL, 1354 pid: NUM_NULL, 1355 threadName: STR_NULL, 1356 }); 1357 1358 const threadOrderingMetadata: UtidToTrackSortKey = {}; 1359 for (; it.valid(); it.next()) { 1360 threadOrderingMetadata[it.utid] = { 1361 tid: it.tid === null ? undefined : it.tid, 1362 sortKey: TrackDecider.getThreadSortKey(it.threadName, it.tid, it.pid), 1363 }; 1364 } 1365 return threadOrderingMetadata; 1366 } 1367 1368 addPluginTracks(): void { 1369 const groupNameToUuid = new Map<string, string>(); 1370 const tracks = globals.trackManager.findPotentialTracks(); 1371 1372 for (const info of tracks) { 1373 const groupName = info.groupName; 1374 1375 let groupUuid = SCROLLING_TRACK_GROUP; 1376 if (groupName) { 1377 const uuid = groupNameToUuid.get(groupName); 1378 if (uuid) { 1379 groupUuid = uuid; 1380 } else { 1381 // Add the group 1382 groupUuid = uuidv4(); 1383 const addGroup = Actions.addTrackGroup({ 1384 name: groupName, 1385 key: groupUuid, 1386 collapsed: true, 1387 fixedOrdering: true, 1388 }); 1389 this.addTrackGroupActions.push(addGroup); 1390 1391 // Add group to the map 1392 groupNameToUuid.set(groupName, groupUuid); 1393 } 1394 } 1395 1396 this.tracksToAdd.push({ 1397 uri: info.uri, 1398 name: info.displayName, 1399 // TODO(hjd): Fix how sorting works. Plugins should expose 1400 // 'sort keys' which the user can use to choose a sort order. 1401 trackSortKey: info.sortKey ?? PrimaryTrackSortKey.ORDINARY_TRACK, 1402 trackGroup: groupUuid, 1403 }); 1404 } 1405 } 1406 1407 async addScrollJankPluginTracks(): Promise<void> { 1408 if (ENABLE_SCROLL_JANK_PLUGIN_V2.get()) { 1409 const result = await getScrollJankTracks(this.engine); 1410 this.tracksToAdd = this.tracksToAdd.concat(result.tracks.tracksToAdd); 1411 this.addTrackGroupActions.push(result.addTrackGroup); 1412 } 1413 } 1414 1415 async decideTracks(): Promise<DeferredAction[]> { 1416 { 1417 const result = screenshotDecideTracks(this.engine); 1418 if (result !== null) { 1419 const {tracksToAdd} = await result; 1420 this.tracksToAdd.push(...tracksToAdd); 1421 } 1422 } 1423 1424 // Add first the global tracks that don't require per-process track groups. 1425 await this.addScrollJankPluginTracks(); 1426 await this.addCpuSchedulingTracks(); 1427 await this.addCpuFreqTracks( 1428 this.engine.getProxy('TrackDecider::addCpuFreqTracks'), 1429 ); 1430 await this.addGlobalAsyncTracks( 1431 this.engine.getProxy('TrackDecider::addGlobalAsyncTracks'), 1432 ); 1433 await this.addGpuFreqTracks( 1434 this.engine.getProxy('TrackDecider::addGpuFreqTracks'), 1435 ); 1436 await this.addCpuFreqLimitCounterTracks( 1437 this.engine.getProxy('TrackDecider::addCpuFreqLimitCounterTracks'), 1438 ); 1439 await this.addCpuPerfCounterTracks( 1440 this.engine.getProxy('TrackDecider::addCpuPerfCounterTracks'), 1441 ); 1442 this.addPluginTracks(); 1443 await this.addAnnotationTracks( 1444 this.engine.getProxy('TrackDecider::addAnnotationTracks'), 1445 ); 1446 await this.groupGlobalIonTracks(); 1447 await this.groupGlobalIostatTracks(F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME); 1448 await this.groupGlobalIostatTracks( 1449 F2FS_IOSTAT_LAT_TAG, 1450 F2FS_IOSTAT_LAT_GROUP_NAME, 1451 ); 1452 await this.groupGlobalIostatTracks(DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME); 1453 await this.groupTracksByRegex(UFS_CMD_TAG_REGEX, UFS_CMD_TAG_GROUP); 1454 await this.groupGlobalBuddyInfoTracks(); 1455 await this.groupTracksByRegex(KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP); 1456 await this.groupTracksByRegex(NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP); 1457 await this.groupTracksByRegex( 1458 ENTITY_RESIDENCY_REGEX, 1459 ENTITY_RESIDENCY_GROUP, 1460 ); 1461 await this.groupTracksByRegex(UCLAMP_REGEX, UCLAMP_GROUP); 1462 await this.groupFrequencyTracks(FREQUENCY_GROUP); 1463 await this.groupTracksByRegex(POWER_RAILS_REGEX, POWER_RAILS_GROUP); 1464 await this.groupTracksByRegex(TEMPERATURE_REGEX, TEMPERATURE_GROUP); 1465 await this.groupTracksByRegex(IRQ_REGEX, IRQ_GROUP); 1466 await this.groupTracksByRegex(CHROME_TRACK_REGEX, CHROME_TRACK_GROUP); 1467 await this.groupMiscNonAllowlistedTracks(MISC_GROUP); 1468 1469 // Add user slice tracks before listing the processes. These tracks will 1470 // be listed with their user/package name only, and they will be grouped 1471 // under on their original shared track names. E.g. "GPU Work Period" 1472 await this.addUserAsyncSliceTracks( 1473 this.engine.getProxy('TrackDecider::addUserAsyncSliceTracks'), 1474 ); 1475 1476 // Pre-group all kernel "threads" (actually processes) if this is a linux 1477 // system trace. Below, addProcessTrackGroups will skip them due to an 1478 // existing group uuid, and addThreadStateTracks will fill in the 1479 // per-thread tracks. Quirk: since all threads will appear to be 1480 // TrackKindPriority.MAIN_THREAD, any process-level tracks will end up 1481 // pushed to the bottom of the group in the UI. 1482 await this.addKernelThreadGrouping( 1483 this.engine.getProxy('TrackDecider::addKernelThreadGrouping'), 1484 ); 1485 1486 // Create the per-process track groups. Note that this won't necessarily 1487 // create a track per process. If a process has been completely idle and has 1488 // no sched events, no track group will be emitted. 1489 // Will populate this.addTrackGroupActions 1490 await this.addProcessTrackGroups( 1491 this.engine.getProxy('TrackDecider::addProcessTrackGroups'), 1492 ); 1493 1494 await this.addProcessHeapProfileTracks( 1495 this.engine.getProxy('TrackDecider::addProcessHeapProfileTracks'), 1496 ); 1497 if (PERF_SAMPLE_FLAG.get()) { 1498 await this.addProcessPerfSamplesTracks( 1499 this.engine.getProxy('TrackDecider::addProcessPerfSamplesTracks'), 1500 ); 1501 } 1502 await this.addProcessCounterTracks( 1503 this.engine.getProxy('TrackDecider::addProcessCounterTracks'), 1504 ); 1505 await this.addProcessAsyncSliceTracks( 1506 this.engine.getProxy('TrackDecider::addProcessAsyncSliceTracks'), 1507 ); 1508 await this.addActualFramesTracks( 1509 this.engine.getProxy('TrackDecider::addActualFramesTracks'), 1510 ); 1511 await this.addExpectedFramesTracks( 1512 this.engine.getProxy('TrackDecider::addExpectedFramesTracks'), 1513 ); 1514 await this.addThreadCounterTracks( 1515 this.engine.getProxy('TrackDecider::addThreadCounterTracks'), 1516 ); 1517 await this.addThreadStateTracks( 1518 this.engine.getProxy('TrackDecider::addThreadStateTracks'), 1519 ); 1520 await this.addThreadSliceTracks( 1521 this.engine.getProxy('TrackDecider::addThreadSliceTracks'), 1522 ); 1523 await this.addThreadCpuSampleTracks( 1524 this.engine.getProxy('TrackDecider::addThreadCpuSampleTracks'), 1525 ); 1526 1527 // TODO(hjd): Move into plugin API. 1528 { 1529 const result = scrollJankDecideTracks(this.engine, (utid, upid) => { 1530 return this.getUuid(utid, upid); 1531 }); 1532 if (result !== null) { 1533 const {tracksToAdd} = await result; 1534 this.tracksToAdd.push(...tracksToAdd); 1535 } 1536 } 1537 1538 this.addTrackGroupActions.push( 1539 Actions.addTracks({tracks: this.tracksToAdd}), 1540 ); 1541 1542 const threadOrderingMetadata = await this.computeThreadOrderingMetadata(); 1543 this.addTrackGroupActions.push( 1544 Actions.setUtidToTrackSortKey({threadOrderingMetadata}), 1545 ); 1546 1547 return this.addTrackGroupActions; 1548 } 1549 1550 // Some process counter tracks are tied to specific threads based on their 1551 // name. 1552 private async resolveTrackSortKeyForProcessCounterTrack( 1553 upid: number, 1554 threadName?: string, 1555 ): Promise<TrackSortKey> { 1556 if (threadName !== 'GPU completion') { 1557 return PrimaryTrackSortKey.COUNTER_TRACK; 1558 } 1559 const result = await this.engine.query(` 1560 select utid 1561 from thread 1562 where upid=${upid} and name=${sqliteString(threadName)} 1563 `); 1564 const it = result.iter({ 1565 utid: NUM, 1566 }); 1567 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 1568 for (; it; it.next()) { 1569 return { 1570 utid: it.utid, 1571 priority: InThreadTrackSortKey.THREAD_COUNTER_TRACK, 1572 }; 1573 } 1574 return PrimaryTrackSortKey.COUNTER_TRACK; 1575 } 1576 1577 private static getThreadSortKey( 1578 threadName?: string | null, 1579 tid?: number | null, 1580 pid?: number | null, 1581 ): PrimaryTrackSortKey { 1582 if (pid !== undefined && pid !== null && pid === tid) { 1583 return PrimaryTrackSortKey.MAIN_THREAD; 1584 } 1585 if (threadName === undefined || threadName === null) { 1586 return PrimaryTrackSortKey.ORDINARY_THREAD; 1587 } 1588 1589 // Chrome main threads should always come first within their process. 1590 if ( 1591 threadName === 'CrBrowserMain' || 1592 threadName === 'CrRendererMain' || 1593 threadName === 'CrGpuMain' 1594 ) { 1595 return PrimaryTrackSortKey.MAIN_THREAD; 1596 } 1597 1598 // Chrome IO threads should always come immediately after the main thread. 1599 if ( 1600 threadName === 'Chrome_ChildIOThread' || 1601 threadName === 'Chrome_IOThread' 1602 ) { 1603 return PrimaryTrackSortKey.CHROME_IO_THREAD; 1604 } 1605 1606 // A Chrome process can have only one compositor thread, so we want to put 1607 // it next to other named processes. 1608 if (threadName === 'Compositor' || threadName === 'VizCompositorThread') { 1609 return PrimaryTrackSortKey.CHROME_COMPOSITOR_THREAD; 1610 } 1611 1612 switch (true) { 1613 case /.*RenderThread.*/.test(threadName): 1614 return PrimaryTrackSortKey.RENDER_THREAD; 1615 case /.*GPU completion.*/.test(threadName): 1616 return PrimaryTrackSortKey.GPU_COMPLETION_THREAD; 1617 default: 1618 return PrimaryTrackSortKey.ORDINARY_THREAD; 1619 } 1620 } 1621} 1622