• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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