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 * as uuidv4 from 'uuid/v4'; 16import {assertExists} from '../base/logging'; 17 18import { 19 Actions, 20 AddTrackArgs, 21 DeferredAction, 22} from '../common/actions'; 23import {Engine} from '../common/engine'; 24import { 25 iter, 26 NUM, 27 NUM_NULL, 28 slowlyCountRows, 29 STR, 30 STR_NULL, 31} from '../common/query_iterator'; 32import {SCROLLING_TRACK_GROUP, TrackKindPriority} from '../common/state'; 33import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames/common'; 34import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log/common'; 35import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/common'; 36import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common'; 37import {COUNTER_TRACK_KIND} from '../tracks/counter/common'; 38import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq/common'; 39import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile/common'; 40import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common'; 41import { 42 EXPECTED_FRAMES_SLICE_TRACK_KIND 43} from '../tracks/expected_frames/common'; 44import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common'; 45import { 46 PROCESS_SCHEDULING_TRACK_KIND 47} from '../tracks/process_scheduling/common'; 48import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common'; 49import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common'; 50 51const MEM_DMA_COUNTER_NAME = 'mem.dma_heap'; 52const MEM_DMA = 'mem.dma_buffer'; 53const MEM_ION = 'mem.ion'; 54 55export async function decideTracks( 56 engineId: string, engine: Engine): Promise<DeferredAction[]> { 57 return (new TrackDecider(engineId, engine)).decideTracks(); 58} 59 60class TrackDecider { 61 private engineId: string; 62 private engine: Engine; 63 private upidToUuid = new Map<number, string>(); 64 private utidToUuid = new Map<number, string>(); 65 private tracksToAdd: AddTrackArgs[] = []; 66 private addTrackGroupActions: DeferredAction[] = []; 67 68 constructor(engineId: string, engine: Engine) { 69 this.engineId = engineId; 70 this.engine = engine; 71 } 72 73 static getTrackName(args: Partial<{ 74 name: string | null, 75 utid: number, 76 processName: string|null, 77 pid: number|null, 78 threadName: string|null, 79 tid: number|null, 80 upid: number|null, 81 kind: string, 82 threadTrack: boolean 83 }>) { 84 const { 85 name, 86 upid, 87 utid, 88 processName, 89 threadName, 90 pid, 91 tid, 92 kind, 93 threadTrack 94 } = args; 95 96 const hasName = name !== undefined && name !== null && name !== '[NULL]'; 97 const hasUpid = upid !== undefined && upid !== null; 98 const hasUtid = utid !== undefined && utid !== null; 99 const hasProcessName = processName !== undefined && processName !== null; 100 const hasThreadName = threadName !== undefined && threadName !== null; 101 const hasTid = tid !== undefined && tid !== null; 102 const hasPid = pid !== undefined && pid !== null; 103 const hasKind = kind !== undefined; 104 const isThreadTrack = threadTrack !== undefined && threadTrack; 105 106 // If we don't have any useful information (better than 107 // upid/utid) we show the track kind to help with tracking 108 // down where this is coming from. 109 const kindSuffix = hasKind ? ` (${kind})` : ''; 110 111 if (isThreadTrack && hasName && hasTid) { 112 return `${name} (${tid})`; 113 } else if (hasName) { 114 return `${name}`; 115 } else if (hasUpid && hasPid && hasProcessName) { 116 return `${processName} ${pid}`; 117 } else if (hasUpid && hasPid) { 118 return `Process ${pid}`; 119 } else if (hasThreadName && hasTid) { 120 return `${threadName} ${tid}`; 121 } else if (hasTid) { 122 return `Thread ${tid}`; 123 } else if (hasUpid) { 124 return `upid: ${upid}${kindSuffix}`; 125 } else if (hasUtid) { 126 return `utid: ${utid}${kindSuffix}`; 127 } else if (hasKind) { 128 return `Unnamed ${kind}`; 129 } 130 return 'Unknown'; 131 } 132 133 async addCpuSchedulingTracks(): Promise<void> { 134 const cpus = await this.engine.getCpus(); 135 for (const cpu of cpus) { 136 this.tracksToAdd.push({ 137 engineId: this.engineId, 138 kind: CPU_SLICE_TRACK_KIND, 139 trackKindPriority: TrackKindPriority.ORDINARY, 140 name: `Cpu ${cpu}`, 141 trackGroup: SCROLLING_TRACK_GROUP, 142 config: { 143 cpu, 144 } 145 }); 146 } 147 } 148 149 async addCpuFreqTracks(): Promise<void> { 150 const cpus = await this.engine.getCpus(); 151 152 const maxCpuFreq = await this.engine.query(` 153 select max(value) 154 from counter c 155 inner join cpu_counter_track t on c.track_id = t.id 156 where name = 'cpufreq'; 157 `); 158 159 for (const cpu of cpus) { 160 // Only add a cpu freq track if we have 161 // cpu freq data. 162 // TODO(hjd): Find a way to display cpu idle 163 // events even if there are no cpu freq events. 164 const cpuFreqIdle = await this.engine.query(` 165 select 166 id as cpu_freq_id, 167 ( 168 select id 169 from cpu_counter_track 170 where name = 'cpuidle' 171 and cpu = ${cpu} 172 limit 1 173 ) as cpu_idle_id 174 from cpu_counter_track 175 where name = 'cpufreq' and cpu = ${cpu} 176 limit 1; 177 `); 178 if (slowlyCountRows(cpuFreqIdle) > 0) { 179 const freqTrackId = +cpuFreqIdle.columns[0].longValues![0]; 180 181 const idleTrackExists: boolean = !cpuFreqIdle.columns[1].isNulls![0]; 182 const idleTrackId = idleTrackExists ? 183 +cpuFreqIdle.columns[1].longValues![0] : 184 undefined; 185 186 this.tracksToAdd.push({ 187 engineId: this.engineId, 188 kind: CPU_FREQ_TRACK_KIND, 189 trackKindPriority: TrackKindPriority.ORDINARY, 190 name: `Cpu ${cpu} Frequency`, 191 trackGroup: SCROLLING_TRACK_GROUP, 192 config: { 193 cpu, 194 maximumValue: +maxCpuFreq.columns[0].doubleValues![0], 195 freqTrackId, 196 idleTrackId, 197 } 198 }); 199 } 200 } 201 } 202 203 async addGlobalAsyncTracks(): Promise<void> { 204 const rawGlobalAsyncTracks = await this.engine.query(` 205 SELECT 206 t.name, 207 t.track_ids, 208 MAX(experimental_slice_layout.layout_depth) as max_depth 209 FROM ( 210 SELECT name, GROUP_CONCAT(track.id) AS track_ids 211 FROM track 212 WHERE track.type = "track" 213 GROUP BY name 214 ) AS t CROSS JOIN experimental_slice_layout 215 WHERE t.track_ids = experimental_slice_layout.filter_track_ids 216 GROUP BY t.track_ids; 217 `); 218 for (let i = 0; i < slowlyCountRows(rawGlobalAsyncTracks); i++) { 219 const name = rawGlobalAsyncTracks.columns[0].isNulls![i] ? 220 undefined : 221 rawGlobalAsyncTracks.columns[0].stringValues![i]; 222 const rawTrackIds = rawGlobalAsyncTracks.columns[1].stringValues![i]; 223 const trackIds = rawTrackIds.split(',').map(v => Number(v)); 224 const maxDepth = +rawGlobalAsyncTracks.columns[2].longValues![i]; 225 const kind = ASYNC_SLICE_TRACK_KIND; 226 const track = { 227 engineId: this.engineId, 228 kind, 229 trackKindPriority: TrackDecider.inferTrackKindPriority(name), 230 trackGroup: SCROLLING_TRACK_GROUP, 231 name: TrackDecider.getTrackName({name, kind}), 232 config: { 233 maxDepth, 234 trackIds, 235 }, 236 }; 237 this.tracksToAdd.push(track); 238 } 239 } 240 241 async addGpuFreqTracks(): Promise<void> { 242 const numGpus = await this.engine.getNumberOfGpus(); 243 const maxGpuFreq = await this.engine.query(` 244 select max(value) 245 from counter c 246 inner join gpu_counter_track t on c.track_id = t.id 247 where name = 'gpufreq'; 248 `); 249 250 for (let gpu = 0; gpu < numGpus; gpu++) { 251 // Only add a gpu freq track if we have 252 // gpu freq data. 253 const freqExists = await this.engine.query(` 254 select id 255 from gpu_counter_track 256 where name = 'gpufreq' and gpu_id = ${gpu} 257 limit 1; 258 `); 259 if (slowlyCountRows(freqExists) > 0) { 260 this.tracksToAdd.push({ 261 engineId: this.engineId, 262 kind: COUNTER_TRACK_KIND, 263 name: `Gpu ${gpu} Frequency`, 264 trackKindPriority: TrackKindPriority.ORDINARY, 265 trackGroup: SCROLLING_TRACK_GROUP, 266 config: { 267 trackId: +freqExists.columns[0].longValues![0], 268 maximumValue: +maxGpuFreq.columns[0].doubleValues![0], 269 } 270 }); 271 } 272 } 273 } 274 275 async addGlobalCounterTracks(): Promise<void> { 276 // Add global or GPU counter tracks that are not bound to any pid/tid. 277 const globalCounters = await this.engine.query(` 278 select name, id 279 from counter_track 280 where type = 'counter_track' 281 union 282 select name, id 283 from gpu_counter_track 284 where name != 'gpufreq' 285 `); 286 for (let i = 0; i < slowlyCountRows(globalCounters); i++) { 287 const name = globalCounters.columns[0].stringValues![i]; 288 const trackId = +globalCounters.columns[1].longValues![i]; 289 this.tracksToAdd.push({ 290 engineId: this.engineId, 291 kind: COUNTER_TRACK_KIND, 292 name, 293 trackKindPriority: TrackDecider.inferTrackKindPriority(name), 294 trackGroup: SCROLLING_TRACK_GROUP, 295 config: { 296 name, 297 trackId, 298 } 299 }); 300 } 301 } 302 303 async groupGlobalIonTracks(): Promise<void> { 304 const ionTracks: AddTrackArgs[] = []; 305 let hasSummary = false; 306 for (const track of this.tracksToAdd) { 307 const isIon = track.name.startsWith(MEM_ION); 308 const isIonCounter = track.name === MEM_ION; 309 const isDmaHeapCounter = track.name === MEM_DMA_COUNTER_NAME; 310 const isDmaBuffferSlices = track.name === MEM_DMA; 311 if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) { 312 ionTracks.push(track); 313 } 314 hasSummary = hasSummary || isIonCounter; 315 hasSummary = hasSummary || isDmaHeapCounter; 316 } 317 318 if (ionTracks.length === 0 || !hasSummary) { 319 return; 320 } 321 322 const id = uuidv4(); 323 const summaryTrackId = uuidv4(); 324 let foundSummary = false; 325 326 for (const track of ionTracks) { 327 if (!foundSummary && 328 [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name)) { 329 foundSummary = true; 330 track.id = summaryTrackId; 331 track.trackGroup = undefined; 332 } else { 333 track.trackGroup = id; 334 } 335 } 336 337 const addGroup = Actions.addTrackGroup({ 338 engineId: this.engineId, 339 summaryTrackId, 340 name: MEM_DMA_COUNTER_NAME, 341 id, 342 collapsed: true, 343 }); 344 this.addTrackGroupActions.push(addGroup); 345 } 346 347 async addLogsTrack(): Promise<void> { 348 const logCount = 349 await this.engine.query(`select count(1) from android_logs`); 350 if (logCount.columns[0].longValues![0] > 0) { 351 this.tracksToAdd.push({ 352 engineId: this.engineId, 353 kind: ANDROID_LOGS_TRACK_KIND, 354 name: 'Android logs', 355 trackKindPriority: TrackKindPriority.ORDINARY, 356 trackGroup: SCROLLING_TRACK_GROUP, 357 config: {} 358 }); 359 } 360 } 361 362 async addAnnotationTracks(): Promise<void> { 363 const annotationSliceRows = await this.engine.query(` 364 SELECT id, name, upid FROM annotation_slice_track`); 365 for (let i = 0; i < slowlyCountRows(annotationSliceRows); i++) { 366 const id = annotationSliceRows.columns[0].longValues![i]; 367 const name = annotationSliceRows.columns[1].stringValues![i]; 368 const upid = annotationSliceRows.columns[2].longValues![i]; 369 this.tracksToAdd.push({ 370 engineId: this.engineId, 371 kind: SLICE_TRACK_KIND, 372 name, 373 trackKindPriority: TrackDecider.inferTrackKindPriority(name), 374 trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : 375 this.upidToUuid.get(upid), 376 config: { 377 maxDepth: 0, 378 namespace: 'annotation', 379 trackId: id, 380 }, 381 }); 382 } 383 384 const annotationCounterRows = await this.engine.query(` 385 SELECT id, name, upid, min_value, max_value 386 FROM annotation_counter_track`); 387 for (let i = 0; i < slowlyCountRows(annotationCounterRows); i++) { 388 const id = annotationCounterRows.columns[0].longValues![i]; 389 const name = annotationCounterRows.columns[1].stringValues![i]; 390 const upid = annotationCounterRows.columns[2].longValues![i]; 391 const minimumValue = annotationCounterRows.columns[3].isNulls![i] ? 392 undefined : 393 annotationCounterRows.columns[3].doubleValues![i]; 394 const maximumValue = annotationCounterRows.columns[4].isNulls![i] ? 395 undefined : 396 annotationCounterRows.columns[4].doubleValues![i]; 397 this.tracksToAdd.push({ 398 engineId: this.engineId, 399 kind: 'CounterTrack', 400 name, 401 trackKindPriority: TrackDecider.inferTrackKindPriority(name), 402 trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : 403 this.upidToUuid.get(upid), 404 config: { 405 name, 406 namespace: 'annotation', 407 trackId: id, 408 minimumValue, 409 maximumValue, 410 } 411 }); 412 } 413 } 414 415 async addThreadStateTracks(): Promise<void> { 416 const query = await this.engine.query(` 417 select 418 utid, 419 tid, 420 upid, 421 pid, 422 thread.name as threadName 423 from 424 thread_state 425 left join thread using(utid) 426 left join process using(upid) 427 where utid != 0 428 group by utid`); 429 430 const it = iter( 431 { 432 utid: NUM, 433 upid: NUM_NULL, 434 tid: NUM_NULL, 435 pid: NUM_NULL, 436 threadName: STR_NULL, 437 }, 438 query); 439 for (let i = 0; it.valid(); ++i, it.next()) { 440 const row = it.row; 441 const utid = row.utid; 442 const tid = row.tid; 443 const upid = row.upid; 444 const pid = row.pid; 445 const threadName = row.threadName; 446 const uuid = this.getUuidUnchecked(utid, upid); 447 if (uuid === undefined) { 448 // If a thread has no scheduling activity (i.e. the sched table has zero 449 // rows for that uid) no track group will be created and we want to skip 450 // the track creation as well. 451 continue; 452 } 453 const kind = THREAD_STATE_TRACK_KIND; 454 this.tracksToAdd.push({ 455 engineId: this.engineId, 456 kind, 457 name: TrackDecider.getTrackName({utid, tid, threadName, kind}), 458 trackGroup: uuid, 459 trackKindPriority: 460 TrackDecider.inferTrackKindPriority(threadName, tid, pid), 461 config: {utid, tid} 462 }); 463 } 464 } 465 466 async addThreadCpuSampleTracks(): Promise<void> { 467 const query = await this.engine.query(` 468 select 469 utid, 470 tid, 471 upid, 472 thread.name as threadName 473 from 474 thread 475 join (select utid 476 from cpu_profile_stack_sample group by utid 477 ) using(utid) 478 left join process using(upid) 479 where utid != 0 480 group by utid`); 481 482 const it = iter( 483 { 484 utid: NUM, 485 upid: NUM_NULL, 486 tid: NUM_NULL, 487 threadName: STR_NULL, 488 }, 489 query); 490 for (let i = 0; it.valid(); ++i, it.next()) { 491 const row = it.row; 492 const utid = row.utid; 493 const upid = row.upid; 494 const threadName = row.threadName; 495 const uuid = this.getUuid(utid, upid); 496 this.tracksToAdd.push({ 497 engineId: this.engineId, 498 kind: CPU_PROFILE_TRACK_KIND, 499 // TODO(hjd): The threadName can be null, use instead. 500 trackKindPriority: TrackDecider.inferTrackKindPriority(threadName), 501 name: `${threadName} (CPU Stack Samples)`, 502 trackGroup: uuid, 503 config: {utid}, 504 }); 505 } 506 } 507 508 async addThreadCounterTracks(): Promise<void> { 509 const query = await this.engine.query(` 510 select 511 thread_counter_track.name as trackName, 512 utid, 513 upid, 514 tid, 515 thread.name as threadName, 516 thread_counter_track.id as trackId, 517 thread.start_ts as startTs, 518 thread.end_ts as endTs 519 from thread_counter_track 520 join thread using(utid) 521 left join process using(upid) 522 where thread_counter_track.name not in ('time_in_state', 'thread_time') 523 `); 524 525 const it = iter( 526 { 527 trackName: STR_NULL, 528 utid: NUM, 529 upid: NUM_NULL, 530 tid: NUM_NULL, 531 threadName: STR_NULL, 532 startTs: NUM_NULL, 533 trackId: NUM, 534 endTs: NUM_NULL, 535 }, 536 query); 537 for (let i = 0; it.valid(); ++i, it.next()) { 538 const row = it.row; 539 const utid = row.utid; 540 const tid = row.tid; 541 const upid = row.upid; 542 const trackId = row.trackId; 543 const trackName = row.trackName; 544 const threadName = row.threadName; 545 const uuid = this.getUuid(utid, upid); 546 const startTs = row.startTs === null ? undefined : row.startTs; 547 const endTs = row.endTs === null ? undefined : row.endTs; 548 const kind = COUNTER_TRACK_KIND; 549 const name = TrackDecider.getTrackName( 550 {name: trackName, utid, tid, kind, threadName, threadTrack: true}); 551 this.tracksToAdd.push({ 552 engineId: this.engineId, 553 kind, 554 name, 555 trackKindPriority: TrackDecider.inferTrackKindPriority(threadName), 556 trackGroup: uuid, 557 config: {name, trackId, startTs, endTs, tid} 558 }); 559 } 560 } 561 562 async addProcessAsyncSliceTracks(): Promise<void> { 563 const query = await this.engine.query(` 564 select 565 process_track.upid as upid, 566 process_track.name as trackName, 567 group_concat(process_track.id) as trackIds, 568 process.name as processName, 569 process.pid as pid 570 from process_track 571 left join process using(upid) 572 where process_track.name not like "% Timeline" 573 group by 574 process_track.upid, 575 process_track.name 576 `); 577 578 const it = iter( 579 { 580 upid: NUM, 581 trackName: STR_NULL, 582 trackIds: STR, 583 processName: STR_NULL, 584 pid: NUM_NULL, 585 }, 586 query); 587 for (let i = 0; it.valid(); ++i, it.next()) { 588 const row = it.row; 589 const upid = row.upid; 590 const trackName = row.trackName; 591 const rawTrackIds = row.trackIds; 592 const trackIds = rawTrackIds.split(',').map(v => Number(v)); 593 const processName = row.processName; 594 const pid = row.pid; 595 596 const uuid = this.getUuid(0, upid); 597 598 // TODO(hjd): 1+N queries are bad in the track_decider 599 const depthResult = await this.engine.query(` 600 SELECT MAX(layout_depth) as max_depth 601 FROM experimental_slice_layout('${rawTrackIds}'); 602 `); 603 const maxDepth = +depthResult.columns[0].longValues![0]; 604 605 const kind = ASYNC_SLICE_TRACK_KIND; 606 const name = TrackDecider.getTrackName( 607 {name: trackName, upid, pid, processName, kind}); 608 this.tracksToAdd.push({ 609 engineId: this.engineId, 610 kind, 611 name, 612 trackKindPriority: TrackDecider.inferTrackKindPriority(name), 613 trackGroup: uuid, 614 config: { 615 trackIds, 616 maxDepth, 617 } 618 }); 619 } 620 } 621 622 async addActualFramesTracks(): Promise<void> { 623 const query = await this.engine.query(` 624 select 625 upid, 626 trackName, 627 trackIds, 628 process.name as processName, 629 process.pid as pid 630 from ( 631 select 632 process_track.upid as upid, 633 process_track.name as trackName, 634 group_concat(process_track.id) as trackIds 635 from process_track 636 where process_track.name like "Actual Timeline" 637 group by 638 process_track.upid, 639 process_track.name 640 ) left join process using(upid) 641 `); 642 643 const it = iter( 644 { 645 upid: NUM, 646 trackName: STR_NULL, 647 trackIds: STR, 648 processName: STR_NULL, 649 pid: NUM_NULL, 650 }, 651 query); 652 for (let i = 0; it.valid(); ++i, it.next()) { 653 const row = it.row; 654 const upid = row.upid; 655 const trackName = row.trackName; 656 const rawTrackIds = row.trackIds; 657 const trackIds = rawTrackIds.split(',').map(v => Number(v)); 658 const processName = row.processName; 659 const pid = row.pid; 660 661 const uuid = this.getUuid(0, upid); 662 663 // TODO(hjd): 1+N queries are bad in the track_decider 664 const depthResult = await this.engine.query(` 665 SELECT MAX(layout_depth) as max_depth 666 FROM experimental_slice_layout('${rawTrackIds}'); 667 `); 668 const maxDepth = +depthResult.columns[0].longValues![0]; 669 670 const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND; 671 const name = TrackDecider.getTrackName( 672 {name: trackName, upid, pid, processName, kind}); 673 this.tracksToAdd.push({ 674 engineId: this.engineId, 675 kind, 676 name, 677 trackKindPriority: TrackDecider.inferTrackKindPriority(trackName), 678 trackGroup: uuid, 679 config: { 680 trackIds, 681 maxDepth, 682 } 683 }); 684 } 685 } 686 687 async addExpectedFramesTracks(): Promise<void> { 688 const query = await this.engine.query(` 689 select 690 upid, 691 trackName, 692 trackIds, 693 process.name as processName, 694 process.pid as pid 695 from ( 696 select 697 process_track.upid as upid, 698 process_track.name as trackName, 699 group_concat(process_track.id) as trackIds 700 from process_track 701 where process_track.name like "Expected Timeline" 702 group by 703 process_track.upid, 704 process_track.name 705 ) left join process using(upid) 706 `); 707 708 const it = iter( 709 { 710 upid: NUM, 711 trackName: STR_NULL, 712 trackIds: STR, 713 processName: STR_NULL, 714 pid: NUM_NULL, 715 }, 716 query); 717 for (let i = 0; it.valid(); ++i, it.next()) { 718 const row = it.row; 719 const upid = row.upid; 720 const trackName = row.trackName; 721 const rawTrackIds = row.trackIds; 722 const trackIds = rawTrackIds.split(',').map(v => Number(v)); 723 const processName = row.processName; 724 const pid = row.pid; 725 726 const uuid = this.getUuid(0, upid); 727 728 // TODO(hjd): 1+N queries are bad in the track_decider 729 const depthResult = await this.engine.query(` 730 SELECT MAX(layout_depth) as max_depth 731 FROM experimental_slice_layout('${rawTrackIds}'); 732 `); 733 const maxDepth = +depthResult.columns[0].longValues![0]; 734 735 const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND; 736 const name = TrackDecider.getTrackName( 737 {name: trackName, upid, pid, processName, kind}); 738 this.tracksToAdd.push({ 739 engineId: this.engineId, 740 kind, 741 name, 742 trackKindPriority: TrackDecider.inferTrackKindPriority(trackName), 743 trackGroup: uuid, 744 config: { 745 trackIds, 746 maxDepth, 747 } 748 }); 749 } 750 } 751 752 async addThreadSliceTracks(): Promise<void> { 753 const query = await this.engine.query(` 754 select 755 thread_track.utid as utid, 756 thread_track.id as trackId, 757 thread_track.name as trackName, 758 tid, 759 thread.name as threadName, 760 max(depth) as maxDepth, 761 process.upid as upid, 762 process.pid as pid 763 from slice 764 join thread_track on slice.track_id = thread_track.id 765 join thread using(utid) 766 left join process using(upid) 767 group by thread_track.id 768 `); 769 770 const it = iter( 771 { 772 utid: NUM, 773 trackId: NUM, 774 trackName: STR_NULL, 775 tid: NUM_NULL, 776 threadName: STR_NULL, 777 maxDepth: NUM, 778 upid: NUM_NULL, 779 pid: NUM_NULL, 780 }, 781 query); 782 for (let i = 0; it.valid(); ++i, it.next()) { 783 const row = it.row; 784 const utid = row.utid; 785 const trackId = row.trackId; 786 const trackName = row.trackName; 787 const tid = row.tid; 788 const threadName = row.threadName; 789 const upid = row.upid; 790 const pid = row.pid; 791 const maxDepth = row.maxDepth; 792 const trackKindPriority = 793 TrackDecider.inferTrackKindPriority(threadName, tid, pid); 794 795 const uuid = this.getUuid(utid, upid); 796 797 const kind = SLICE_TRACK_KIND; 798 const name = TrackDecider.getTrackName( 799 {name: trackName, utid, tid, threadName, kind}); 800 this.tracksToAdd.push({ 801 engineId: this.engineId, 802 kind, 803 name, 804 trackGroup: uuid, 805 trackKindPriority, 806 config: {trackId, maxDepth, tid} 807 }); 808 } 809 } 810 811 async addProcessCounterTracks(): Promise<void> { 812 const query = await this.engine.query(` 813 select 814 process_counter_track.id as trackId, 815 process_counter_track.name as trackName, 816 upid, 817 process.pid, 818 process.name as processName, 819 process.start_ts as startTs, 820 process.end_ts as endTs 821 from process_counter_track 822 join process using(upid); 823 `); 824 const it = iter( 825 { 826 trackId: NUM, 827 trackName: STR_NULL, 828 upid: NUM, 829 pid: NUM_NULL, 830 processName: STR_NULL, 831 startTs: NUM_NULL, 832 endTs: NUM_NULL, 833 }, 834 query); 835 for (let i = 0; it.valid(); ++i, it.next()) { 836 const row = it.row; 837 const pid = row.pid; 838 const upid = row.upid; 839 const trackId = row.trackId; 840 const trackName = row.trackName; 841 const processName = row.processName; 842 const uuid = this.getUuid(0, upid); 843 const startTs = row.startTs === null ? undefined : row.startTs; 844 const endTs = row.endTs === null ? undefined : row.endTs; 845 const kind = COUNTER_TRACK_KIND; 846 const name = TrackDecider.getTrackName( 847 {name: trackName, upid, pid, kind, processName}); 848 this.tracksToAdd.push({ 849 engineId: this.engineId, 850 kind, 851 name, 852 trackKindPriority: TrackDecider.inferTrackKindPriority(trackName), 853 trackGroup: uuid, 854 config: { 855 name, 856 trackId, 857 startTs, 858 endTs, 859 } 860 }); 861 } 862 } 863 864 async addProcessHeapProfileTracks(): Promise<void> { 865 const query = await this.engine.query(` 866 select distinct(upid) from heap_profile_allocation 867 union 868 select distinct(upid) from heap_graph_object 869 `); 870 const it = iter({upid: NUM}, query); 871 for (let i = 0; it.valid(); ++i, it.next()) { 872 const upid = it.row.upid; 873 const uuid = this.getUuid(0, upid); 874 this.tracksToAdd.push({ 875 engineId: this.engineId, 876 kind: HEAP_PROFILE_TRACK_KIND, 877 trackKindPriority: TrackKindPriority.ORDINARY, 878 name: `Heap Profile`, 879 trackGroup: uuid, 880 config: {upid} 881 }); 882 } 883 } 884 885 getUuidUnchecked(utid: number, upid: number|null) { 886 return upid === null ? this.utidToUuid.get(utid) : 887 this.upidToUuid.get(upid); 888 } 889 890 getUuid(utid: number, upid: number|null) { 891 return assertExists(this.getUuidUnchecked(utid, upid)); 892 } 893 894 getOrCreateUuid(utid: number, upid: number|null) { 895 let uuid = this.getUuidUnchecked(utid, upid); 896 if (uuid === undefined) { 897 uuid = uuidv4(); 898 if (upid === null) { 899 this.utidToUuid.set(utid, uuid); 900 } else { 901 this.upidToUuid.set(upid, uuid); 902 } 903 } 904 return uuid; 905 } 906 907 async addProcessTrackGroups(): Promise<void> { 908 // We want to create groups of tracks in a specific order. 909 // The tracks should be grouped: 910 // by upid 911 // or (if upid is null) by utid 912 // the groups should be sorted by: 913 // has a heap profile or not 914 // total cpu time *for the whole parent process* 915 // upid 916 // utid 917 const query = await this.engine.query(` 918 select 919 the_tracks.upid, 920 the_tracks.utid, 921 total_dur as hasSched, 922 hasHeapProfiles, 923 process.pid as pid, 924 thread.tid as tid, 925 process.name as processName, 926 thread.name as threadName 927 from ( 928 select upid, 0 as utid from process_track 929 union 930 select upid, 0 as utid from process_counter_track 931 union 932 select upid, utid from thread_counter_track join thread using(utid) 933 union 934 select upid, utid from thread_track join thread using(utid) 935 union 936 select upid, utid from sched join thread using(utid) group by utid 937 union 938 select upid, utid from ( 939 select distinct(utid) from cpu_profile_stack_sample 940 ) join thread using(utid) 941 union 942 select distinct(upid) as upid, 0 as utid from heap_profile_allocation 943 union 944 select distinct(upid) as upid, 0 as utid from heap_graph_object 945 ) the_tracks 946 left join (select upid, sum(dur) as total_dur 947 from sched join thread using(utid) 948 group by upid 949 ) using(upid) 950 left join (select upid, sum(value) as total_cycles 951 from android_thread_time_in_state_event 952 group by upid 953 ) using(upid) 954 left join ( 955 select 956 distinct(upid) as upid, 957 true as hasHeapProfiles 958 from heap_profile_allocation 959 union 960 select 961 distinct(upid) as upid, 962 true as hasHeapProfiles 963 from heap_graph_object 964 ) using (upid) 965 left join thread using(utid) 966 left join process using(upid) 967 order by 968 hasHeapProfiles desc, 969 total_dur desc, 970 total_cycles desc, 971 the_tracks.upid, 972 the_tracks.utid; 973 `); 974 975 const it = iter( 976 { 977 utid: NUM, 978 upid: NUM_NULL, 979 tid: NUM_NULL, 980 pid: NUM_NULL, 981 threadName: STR_NULL, 982 processName: STR_NULL, 983 hasSched: NUM_NULL, 984 hasHeapProfiles: NUM_NULL, 985 }, 986 query); 987 for (let i = 0; it.valid(); ++i, it.next()) { 988 const row = it.row; 989 const utid = row.utid; 990 const tid = row.tid; 991 const upid = row.upid; 992 const pid = row.pid; 993 const threadName = row.threadName; 994 const processName = row.processName; 995 const hasSched = !!row.hasSched; 996 const hasHeapProfiles = !!row.hasHeapProfiles; 997 998 // Group by upid if present else by utid. 999 let pUuid = 1000 upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid); 1001 // These should only happen once for each track group. 1002 if (pUuid === undefined) { 1003 pUuid = this.getOrCreateUuid(utid, upid); 1004 const summaryTrackId = uuidv4(); 1005 1006 const pidForColor = pid || tid || upid || utid || 0; 1007 const kind = 1008 hasSched ? PROCESS_SCHEDULING_TRACK_KIND : PROCESS_SUMMARY_TRACK; 1009 1010 this.tracksToAdd.push({ 1011 id: summaryTrackId, 1012 engineId: this.engineId, 1013 kind, 1014 trackKindPriority: TrackDecider.inferTrackKindPriority(threadName), 1015 name: `${upid === null ? tid : pid} summary`, 1016 config: {pidForColor, upid, utid, tid}, 1017 }); 1018 1019 const name = TrackDecider.getTrackName( 1020 {utid, processName, pid, threadName, tid, upid}); 1021 const addTrackGroup = Actions.addTrackGroup({ 1022 engineId: this.engineId, 1023 summaryTrackId, 1024 name, 1025 id: pUuid, 1026 collapsed: !hasHeapProfiles, 1027 }); 1028 1029 this.addTrackGroupActions.push(addTrackGroup); 1030 } 1031 } 1032 } 1033 1034 async decideTracks(): Promise<DeferredAction[]> { 1035 // Add first the global tracks that don't require per-process track groups. 1036 await this.addCpuSchedulingTracks(); 1037 await this.addCpuFreqTracks(); 1038 await this.addGlobalAsyncTracks(); 1039 await this.addGpuFreqTracks(); 1040 await this.addGlobalCounterTracks(); 1041 await this.groupGlobalIonTracks(); 1042 1043 // Create the per-process track groups. Note that this won't necessarily 1044 // create a track per process. If a process has been completely idle and has 1045 // no sched events, no track group will be emitted. 1046 // Will populate this.addTrackGroupActions 1047 await this.addProcessTrackGroups(); 1048 1049 await this.addProcessHeapProfileTracks(); 1050 await this.addProcessCounterTracks(); 1051 await this.addProcessAsyncSliceTracks(); 1052 await this.addActualFramesTracks(); 1053 await this.addExpectedFramesTracks(); 1054 await this.addThreadCounterTracks(); 1055 await this.addThreadStateTracks(); 1056 await this.addThreadSliceTracks(); 1057 await this.addThreadCpuSampleTracks(); 1058 await this.addLogsTrack(); 1059 await this.addAnnotationTracks(); 1060 1061 this.addTrackGroupActions.push( 1062 Actions.addTracks({tracks: this.tracksToAdd})); 1063 return this.addTrackGroupActions; 1064 } 1065 1066 private static inferTrackKindPriority( 1067 threadName?: string|null, tid?: number|null, 1068 pid?: number|null): TrackKindPriority { 1069 if (pid !== undefined && pid !== null && pid === tid) { 1070 return TrackKindPriority.MAIN_THREAD; 1071 } 1072 if (threadName === undefined || threadName === null) { 1073 return TrackKindPriority.ORDINARY; 1074 } 1075 1076 switch (true) { 1077 case /.*RenderThread.*/.test(threadName): 1078 return TrackKindPriority.RENDER_THREAD; 1079 case /.*GPU completion.*/.test(threadName): 1080 return TrackKindPriority.GPU_COMPLETION; 1081 default: 1082 return TrackKindPriority.ORDINARY; 1083 } 1084 } 1085} 1086