• 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 * 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