blob: d9ad58bff51aafa6234cf8043bd4dec41214c682 [file] [log] [blame]
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {v4 as uuidv4} from 'uuid';
import {assertExists} from '../base/logging';
import {sqliteString} from '../base/string_utils';
import {
Actions,
AddTrackArgs,
DeferredAction,
} from '../common/actions';
import {Engine, EngineProxy} from '../common/engine';
import {featureFlags, PERF_SAMPLE_FLAG} from '../common/feature_flags';
import {pluginManager} from '../common/plugins';
import {
LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../common/query_result';
import {
InThreadTrackSortKey,
PrimaryTrackSortKey,
SCROLLING_TRACK_GROUP,
TrackSortKey,
UtidToTrackSortKey,
} from '../common/state';
import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log';
import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices';
import {
decideTracks as scrollJankDecideTracks,
} from '../tracks/chrome_scroll_jank';
import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
import {COUNTER_TRACK_KIND, CounterScaleOptions} from '../tracks/counter';
import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq';
import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile';
import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices';
import {
EXPECTED_FRAMES_SLICE_TRACK_KIND,
} from '../tracks/expected_frames';
import {FTRACE_RAW_TRACK_KIND} from '../tracks/ftrace';
import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile';
import {NULL_TRACK_KIND} from '../tracks/null_track';
import {
PERF_SAMPLES_PROFILE_TRACK_KIND,
} from '../tracks/perf_samples_profile';
import {
PROCESS_SCHEDULING_TRACK_KIND,
} from '../tracks/process_scheduling';
import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary';
import {
ENABLE_SCROLL_JANK_PLUGIN_V2, getScrollJankTracks,
INPUT_LATENCY_TRACK,
} from '../tracks/scroll_jank';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
import {THREAD_STATE_TRACK_V2_KIND} from '../tracks/thread_state_v2';
const TRACKS_V2_FLAG = featureFlags.register({
id: 'tracksV2.1',
name: 'Tracks V2',
description: 'Show tracks built on top of the Track V2 API.',
defaultValue: false,
});
const TRACKS_V2_COMPARE_FLAG = featureFlags.register({
id: 'tracksV2Compare',
name: 'Tracks V2: Also show V1 tracks',
description:
'Show V1 tracks side by side with V2 tracks. Does nothing if TracksV2 is not enabled.',
defaultValue: false,
});
function showV2(): boolean {
return TRACKS_V2_FLAG.get();
}
function showV1(): boolean {
return !showV2() || (showV2() && TRACKS_V2_COMPARE_FLAG.get());
}
const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
const MEM_DMA = 'mem.dma_buffer';
const MEM_ION = 'mem.ion';
const F2FS_IOSTAT_TAG = 'f2fs_iostat.';
const F2FS_IOSTAT_GROUP_NAME = 'f2fs_iostat';
const F2FS_IOSTAT_LAT_TAG = 'f2fs_iostat_latency.';
const F2FS_IOSTAT_LAT_GROUP_NAME = 'f2fs_iostat_latency';
const DISK_IOSTAT_TAG = 'diskstat.';
const DISK_IOSTAT_GROUP_NAME = 'diskstat';
const UFS_CMD_TAG = 'io.ufs.command.tag';
const UFS_CMD_TAG_GROUP_NAME = 'io.ufs.command.tags';
const BUDDY_INFO_TAG = 'mem.buddyinfo';
// NB: Userspace wakelocks start with "WakeLock" not "Wakelock".
const KERNEL_WAKELOCK_REGEX = new RegExp('^Wakelock.*$');
const KERNEL_WAKELOCK_GROUP = 'Kernel wakelocks';
const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$');
const NETWORK_TRACK_GROUP = 'Networking';
const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:');
const ENTITY_RESIDENCY_GROUP = 'Entity residency';
// Sets the default 'scale' for counter tracks. If the regex matches
// then the paired mode is used. Entries are in priority order so the
// first match wins.
const COUNTER_REGEX: [RegExp, CounterScaleOptions][] = [
// Power counters make more sense in rate mode since you're typically
// interested in the slope of the graph rather than the absolute
// value.
[new RegExp('^power\..*$'), 'RATE'],
// Same for network counters.
[NETWORK_TRACK_REGEX, 'RATE'],
// Entity residency
[ENTITY_RESIDENCY_REGEX, 'RATE'],
];
function getCounterScale(name: string): CounterScaleOptions|undefined {
for (const [re, scale] of COUNTER_REGEX) {
if (name.match(re)) {
return scale;
}
}
return undefined;
}
export async function decideTracks(
engineId: string, engine: Engine): Promise<DeferredAction[]> {
return (new TrackDecider(engineId, engine)).decideTracks();
}
class TrackDecider {
private engineId: string;
private engine: Engine;
private upidToUuid = new Map<number, string>();
private utidToUuid = new Map<number, string>();
private tracksToAdd: AddTrackArgs[] = [];
private addTrackGroupActions: DeferredAction[] = [];
constructor(engineId: string, engine: Engine) {
this.engineId = engineId;
this.engine = engine;
}
static getTrackName(args: Partial<{
name: string | null,
utid: number,
processName: string|null,
pid: number|null,
threadName: string|null,
tid: number|null,
upid: number|null,
kind: string,
threadTrack: boolean
}>) {
const {
name,
upid,
utid,
processName,
threadName,
pid,
tid,
kind,
threadTrack,
} = args;
const hasName = name !== undefined && name !== null && name !== '[NULL]';
const hasUpid = upid !== undefined && upid !== null;
const hasUtid = utid !== undefined && utid !== null;
const hasProcessName = processName !== undefined && processName !== null;
const hasThreadName = threadName !== undefined && threadName !== null;
const hasTid = tid !== undefined && tid !== null;
const hasPid = pid !== undefined && pid !== null;
const hasKind = kind !== undefined;
const isThreadTrack = threadTrack !== undefined && threadTrack;
// If we don't have any useful information (better than
// upid/utid) we show the track kind to help with tracking
// down where this is coming from.
const kindSuffix = hasKind ? ` (${kind})` : '';
if (isThreadTrack && hasName && hasTid) {
return `${name} (${tid})`;
} else if (hasName) {
return `${name}`;
} else if (hasUpid && hasPid && hasProcessName) {
return `${processName} ${pid}`;
} else if (hasUpid && hasPid) {
return `Process ${pid}`;
} else if (hasThreadName && hasTid) {
return `${threadName} ${tid}`;
} else if (hasTid) {
return `Thread ${tid}`;
} else if (hasUpid) {
return `upid: ${upid}${kindSuffix}`;
} else if (hasUtid) {
return `utid: ${utid}${kindSuffix}`;
} else if (hasKind) {
return `Unnamed ${kind}`;
}
return 'Unknown';
}
async guessCpuSizes(): Promise<Map<number, string>> {
const cpuToSize = new Map<number, string>();
await this.engine.query(`
SELECT IMPORT('common.cpus');
`);
const result = await this.engine.query(`
SELECT cpu, GUESS_CPU_SIZE(cpu) as size FROM cpu_counter_track;
`);
const it = result.iter({
cpu: NUM,
size: STR_NULL,
});
for (; it.valid(); it.next()) {
const size = it.size;
if (size !== null) {
cpuToSize.set(it.cpu, size);
}
}
return cpuToSize;
}
async addCpuSchedulingTracks(): Promise<void> {
const cpus = await this.engine.getCpus();
const cpuToSize = await this.guessCpuSizes();
for (const cpu of cpus) {
const size = cpuToSize.get(cpu);
const name = size === undefined ? `Cpu ${cpu}` : `Cpu ${cpu} (${size})`;
this.tracksToAdd.push({
engineId: this.engineId,
kind: CPU_SLICE_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
name,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
cpu,
},
});
}
}
async addScrollJankTracks(engine: Engine): Promise<void> {
const scrollJankTracks = getScrollJankTracks(engine);
const scrollJankTracksResult = await scrollJankTracks;
const originalLength = this.tracksToAdd.length;
this.tracksToAdd.length += scrollJankTracksResult.tracksToAdd.length;
for (let i = 0; i < scrollJankTracksResult.tracksToAdd.length; ++i) {
this.tracksToAdd[i + originalLength] =
scrollJankTracksResult.tracksToAdd[i];
}
}
async addCpuFreqTracks(engine: EngineProxy): Promise<void> {
const cpus = await this.engine.getCpus();
const maxCpuFreqResult = await engine.query(`
select ifnull(max(value), 0) as freq
from counter c
inner join cpu_counter_track t on c.track_id = t.id
where name = 'cpufreq';
`);
const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq;
for (const cpu of cpus) {
// Only add a cpu freq track if we have
// cpu freq data.
// TODO(hjd): Find a way to display cpu idle
// events even if there are no cpu freq events.
const cpuFreqIdleResult = await engine.query(`
select
id as cpuFreqId,
(
select id
from cpu_counter_track
where name = 'cpuidle'
and cpu = ${cpu}
limit 1
) as cpuIdleId
from cpu_counter_track
where name = 'cpufreq' and cpu = ${cpu}
limit 1;
`);
if (cpuFreqIdleResult.numRows() > 0) {
const row = cpuFreqIdleResult.firstRow({
cpuFreqId: NUM,
cpuIdleId: NUM_NULL,
});
const freqTrackId = row.cpuFreqId;
const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId;
this.tracksToAdd.push({
engineId: this.engineId,
kind: CPU_FREQ_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
name: `Cpu ${cpu} Frequency`,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
cpu,
maximumValue: maxCpuFreq,
freqTrackId,
idleTrackId,
},
});
}
}
}
async addGlobalAsyncTracks(engine: EngineProxy): Promise<void> {
const rawGlobalAsyncTracks = await engine.query(`
with tracks_with_slices as materialized (
select distinct track_id
from slice
),
global_tracks as (
select
track.parent_id as parent_id,
track.id as track_id,
track.name as name
from track
join tracks_with_slices on tracks_with_slices.track_id = track.id
where
track.type = "track"
or track.type = "gpu_track"
or track.type = "cpu_track"
),
global_tracks_grouped as (
select
parent_id,
name,
group_concat(track_id) as trackIds,
count(track_id) as trackCount
from global_tracks track
group by parent_id, name
)
select
t.parent_id as parentId,
p.name as parentName,
t.name as name,
t.trackIds as trackIds,
max_layout_depth(t.trackCount, t.trackIds) as maxDepth
from global_tracks_grouped AS t
left join track p on (t.parent_id = p.id)
order by p.name, t.name;
`);
const it = rawGlobalAsyncTracks.iter({
name: STR_NULL,
parentName: STR_NULL,
parentId: NUM_NULL,
trackIds: STR,
maxDepth: NUM,
});
const parentIdToGroupId = new Map<number, string>();
let scrollJankRendered = false;
for (; it.valid(); it.next()) {
const kind = ASYNC_SLICE_TRACK_KIND;
const rawName = it.name === null ? undefined : it.name;
const rawParentName = it.parentName === null ? undefined : it.parentName;
const name = TrackDecider.getTrackName({name: rawName, kind});
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
const parentTrackId = it.parentId;
const maxDepth = it.maxDepth;
let trackGroup = SCROLLING_TRACK_GROUP;
if (parentTrackId !== null) {
const groupId = parentIdToGroupId.get(parentTrackId);
if (groupId === undefined) {
trackGroup = uuidv4();
parentIdToGroupId.set(parentTrackId, trackGroup);
const parentName =
TrackDecider.getTrackName({name: rawParentName, kind});
const summaryTrackId = uuidv4();
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind: NULL_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
trackGroup: undefined,
name: parentName,
config: {},
});
this.addTrackGroupActions.push(Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: parentName,
id: trackGroup,
collapsed: true,
}));
} else {
trackGroup = groupId;
}
}
if (ENABLE_SCROLL_JANK_PLUGIN_V2.get() && !scrollJankRendered &&
name.includes(INPUT_LATENCY_TRACK)) {
// This ensures that the scroll jank tracks render above the tracks
// for GestureScrollUpdate.
await this.addScrollJankTracks(this.engine);
scrollJankRendered = true;
}
const track = {
engineId: this.engineId,
kind,
trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
trackGroup,
name,
config: {
maxDepth,
trackIds,
},
};
this.tracksToAdd.push(track);
}
}
async addGpuFreqTracks(engine: EngineProxy): Promise<void> {
const numGpus = await this.engine.getNumberOfGpus();
const maxGpuFreqResult = await engine.query(`
select ifnull(max(value), 0) as maximumValue
from counter c
inner join gpu_counter_track t on c.track_id = t.id
where name = 'gpufreq';
`);
const maximumValue =
maxGpuFreqResult.firstRow({maximumValue: NUM}).maximumValue;
for (let gpu = 0; gpu < numGpus; gpu++) {
// Only add a gpu freq track if we have
// gpu freq data.
const freqExistsResult = await engine.query(`
select id
from gpu_counter_track
where name = 'gpufreq' and gpu_id = ${gpu}
limit 1;
`);
if (freqExistsResult.numRows() > 0) {
const trackId = freqExistsResult.firstRow({id: NUM}).id;
this.tracksToAdd.push({
engineId: this.engineId,
kind: COUNTER_TRACK_KIND,
name: `Gpu ${gpu} Frequency`,
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
trackId,
maximumValue,
},
});
}
}
}
async addCpuPerfCounterTracks(engine: EngineProxy): Promise<void> {
// Perf counter tracks are bound to CPUs, follow the scheduling and
// frequency track naming convention ("Cpu N ...").
// Note: we might not have a track for a given cpu if no data was seen from
// it. This might look surprising in the UI, but placeholder tracks are
// wasteful as there's no way of collapsing global counter tracks at the
// moment.
const result = await engine.query(`
select printf("Cpu %u %s", cpu, name) as name, id
from perf_counter_track as pct
order by perf_session_id asc, pct.name asc, cpu asc
`);
const it = result.iter({
name: STR,
id: NUM,
});
for (; it.valid(); it.next()) {
const name = it.name;
const trackId = it.id;
this.tracksToAdd.push({
engineId: this.engineId,
kind: COUNTER_TRACK_KIND,
name,
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
name,
trackId,
scale: getCounterScale(name),
},
});
}
}
async groupGlobalIonTracks(): Promise<void> {
const ionTracks: AddTrackArgs[] = [];
let hasSummary = false;
for (const track of this.tracksToAdd) {
const isIon = track.name.startsWith(MEM_ION);
const isIonCounter = track.name === MEM_ION;
const isDmaHeapCounter = track.name === MEM_DMA_COUNTER_NAME;
const isDmaBuffferSlices = track.name === MEM_DMA;
if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) {
ionTracks.push(track);
}
hasSummary = hasSummary || isIonCounter;
hasSummary = hasSummary || isDmaHeapCounter;
}
if (ionTracks.length === 0 || !hasSummary) {
return;
}
const id = uuidv4();
const summaryTrackId = uuidv4();
let foundSummary = false;
for (const track of ionTracks) {
if (!foundSummary &&
[MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name)) {
foundSummary = true;
track.id = summaryTrackId;
track.trackGroup = undefined;
} else {
track.trackGroup = id;
}
}
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: MEM_DMA_COUNTER_NAME,
id,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
}
async groupGlobalIostatTracks(tag: string, group: string): Promise<void> {
const iostatTracks: AddTrackArgs[] = [];
const devMap = new Map<string, string>();
for (const track of this.tracksToAdd) {
if (track.name.startsWith(tag)) {
iostatTracks.push(track);
}
}
if (iostatTracks.length === 0) {
return;
}
for (const track of iostatTracks) {
const name = track.name.split('.', 3);
if (!devMap.has(name[1])) {
devMap.set(name[1], uuidv4());
}
track.name = name[2];
track.trackGroup = devMap.get(name[1]);
}
for (const [key, value] of devMap) {
const groupName = group + key;
const summaryTrackId = uuidv4();
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind: NULL_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
name: groupName,
trackGroup: undefined,
config: {},
});
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: groupName,
id: value,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
}
}
async groupGlobalUfsCmdTagTracks(tag: string, group: string): Promise<void> {
const ufsCmdTagTracks: AddTrackArgs[] = [];
for (const track of this.tracksToAdd) {
if (track.name.startsWith(tag)) {
ufsCmdTagTracks.push(track);
}
}
if (ufsCmdTagTracks.length === 0) {
return;
}
const id = uuidv4();
const summaryTrackId = uuidv4();
ufsCmdTagTracks[0].id = summaryTrackId;
for (const track of ufsCmdTagTracks) {
track.trackGroup = id;
}
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: group,
id,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
}
async groupGlobalBuddyInfoTracks(): Promise<void> {
const buddyInfoTracks: AddTrackArgs[] = [];
const devMap = new Map<string, string>();
for (const track of this.tracksToAdd) {
if (track.name.startsWith(BUDDY_INFO_TAG)) {
buddyInfoTracks.push(track);
}
}
if (buddyInfoTracks.length === 0) {
return;
}
for (const track of buddyInfoTracks) {
const tokens = track.name.split('[');
const node = tokens[1].slice(0, -1);
const zone = tokens[2].slice(0, -1);
const size = tokens[3].slice(0, -1);
const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone;
if (!devMap.has(groupName)) {
devMap.set(groupName, uuidv4());
}
track.name = 'Size: ' + size;
track.trackGroup = devMap.get(groupName);
}
for (const [key, value] of devMap) {
const groupName = key;
const summaryTrackId = uuidv4();
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind: NULL_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
name: groupName,
trackGroup: undefined,
config: {},
});
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: groupName,
id: value,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
}
}
async groupTracksByRegex(regex: RegExp, groupName: string): Promise<void> {
let groupUuid = undefined;
for (const track of this.tracksToAdd) {
if (regex.test(track.name)) {
if (groupUuid === undefined) {
groupUuid = uuidv4();
}
track.trackGroup = groupUuid;
}
}
if (groupUuid !== undefined) {
const summaryTrackId = uuidv4();
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind: NULL_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
name: groupName,
trackGroup: undefined,
config: {},
});
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: groupName,
id: groupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
}
}
applyDefaultCounterScale(): void {
for (const track of this.tracksToAdd) {
if (track.kind === COUNTER_TRACK_KIND) {
const scaleConfig = {
scale: getCounterScale(track.name),
};
track.config = Object.assign({}, track.config, scaleConfig);
}
}
}
async addLogsTrack(engine: EngineProxy): Promise<void> {
const result =
await engine.query(`select count(1) as cnt from android_logs`);
const count = result.firstRow({cnt: NUM}).cnt;
if (count > 0) {
this.tracksToAdd.push({
engineId: this.engineId,
kind: ANDROID_LOGS_TRACK_KIND,
name: 'Android logs',
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
config: {},
});
}
}
async addFtraceTrack(engine: EngineProxy): Promise<void> {
const query = 'select distinct cpu from ftrace_event';
const result = await engine.query(query);
const it = result.iter({cpu: NUM});
let groupUuid = undefined;
let summaryTrackId = undefined;
// use the first one as the summary track
for (let row = 0; it.valid(); it.next(), row++) {
if (groupUuid === undefined) {
groupUuid = 'ftrace-track-group';
summaryTrackId = uuidv4();
this.tracksToAdd.push({
engineId: this.engineId,
kind: NULL_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
name: `Ftrace Events`,
trackGroup: undefined,
config: {},
id: summaryTrackId,
});
}
this.tracksToAdd.push({
engineId: this.engineId,
kind: FTRACE_RAW_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
name: `Ftrace Events Cpu ${it.cpu}`,
trackGroup: groupUuid,
config: {cpu: it.cpu},
});
}
if (groupUuid !== undefined && summaryTrackId !== undefined) {
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
name: 'Ftrace Events',
id: groupUuid,
collapsed: true,
summaryTrackId,
});
this.addTrackGroupActions.push(addGroup);
}
}
async addAnnotationTracks(engine: EngineProxy): Promise<void> {
const sliceResult = await engine.query(`
select id, name, upid, group_name
from annotation_slice_track
order by name
`);
const sliceIt = sliceResult.iter({
id: NUM,
name: STR,
upid: NUM,
group_name: STR_NULL,
});
interface GroupIds {
id: string;
summaryTrackId: string;
}
const groupNameToIds = new Map<string, GroupIds>();
for (; sliceIt.valid(); sliceIt.next()) {
const id = sliceIt.id;
const name = sliceIt.name;
const upid = sliceIt.upid;
const groupName = sliceIt.group_name;
let summaryTrackId = undefined;
let trackGroupId =
upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid);
if (groupName) {
// If this is the first track encountered for a certain group,
// create an id for the group and use this track as the group's
// summary track.
const groupIds = groupNameToIds.get(groupName);
if (groupIds) {
trackGroupId = groupIds.id;
} else {
trackGroupId = uuidv4();
summaryTrackId = uuidv4();
groupNameToIds.set(groupName, {
id: trackGroupId,
summaryTrackId,
});
}
}
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind: SLICE_TRACK_KIND,
name,
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
trackGroup: trackGroupId,
config: {
maxDepth: 0,
namespace: 'annotation',
trackId: id,
},
});
}
for (const [groupName, groupIds] of groupNameToIds) {
const addGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId: groupIds.summaryTrackId,
name: groupName,
id: groupIds.id,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
}
const counterResult = await engine.query(`
SELECT
id,
name,
upid,
min_value as minValue,
max_value as maxValue
FROM annotation_counter_track`);
const counterIt = counterResult.iter({
id: NUM,
name: STR,
upid: NUM,
minValue: NUM_NULL,
maxValue: NUM_NULL,
});
for (; counterIt.valid(); counterIt.next()) {
const id = counterIt.id;
const name = counterIt.name;
const upid = counterIt.upid;
const minimumValue =
counterIt.minValue === null ? undefined : counterIt.minValue;
const maximumValue =
counterIt.maxValue === null ? undefined : counterIt.maxValue;
this.tracksToAdd.push({
engineId: this.engineId,
kind: 'CounterTrack',
name,
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP :
this.upidToUuid.get(upid),
config: {
name,
namespace: 'annotation',
trackId: id,
minimumValue,
maximumValue,
},
});
}
}
async addThreadStateTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select
utid,
tid,
upid,
pid,
thread.name as threadName
from
thread_state
left join thread using(utid)
left join process using(upid)
where utid != 0
group by utid`);
const it = result.iter({
utid: NUM,
upid: NUM_NULL,
tid: NUM_NULL,
pid: NUM_NULL,
threadName: STR_NULL,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
const tid = it.tid;
const upid = it.upid;
const threadName = it.threadName;
const uuid = this.getUuidUnchecked(utid, upid);
if (uuid === undefined) {
// If a thread has no scheduling activity (i.e. the sched table has zero
// rows for that uid) no track group will be created and we want to skip
// the track creation as well.
continue;
}
const priority = InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK;
if (showV1()) {
const kind = THREAD_STATE_TRACK_KIND;
this.tracksToAdd.push({
engineId: this.engineId,
kind: THREAD_STATE_TRACK_KIND,
name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
trackGroup: uuid,
trackSortKey: {
utid,
priority,
},
config: {utid, tid},
});
}
if (showV2()) {
const kind = THREAD_STATE_TRACK_V2_KIND;
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
trackGroup: uuid,
trackSortKey: {
utid,
priority,
},
config: {utid, tid},
});
}
}
}
async addThreadCpuSampleTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select
utid,
tid,
upid,
thread.name as threadName
from
thread
join (select utid
from cpu_profile_stack_sample group by utid
) using(utid)
left join process using(upid)
where utid != 0
group by utid`);
const it = result.iter({
utid: NUM,
upid: NUM_NULL,
tid: NUM_NULL,
threadName: STR_NULL,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
const upid = it.upid;
const threadName = it.threadName;
const uuid = this.getUuid(utid, upid);
this.tracksToAdd.push({
engineId: this.engineId,
kind: CPU_PROFILE_TRACK_KIND,
trackSortKey: {
utid,
priority: InThreadTrackSortKey.CPU_STACK_SAMPLES_TRACK,
},
name: `${threadName} (CPU Stack Samples)`,
trackGroup: uuid,
config: {utid},
});
}
}
async addThreadCounterTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select
thread_counter_track.name as trackName,
utid,
upid,
tid,
thread.name as threadName,
thread_counter_track.id as trackId,
thread.start_ts as startTs,
thread.end_ts as endTs
from thread_counter_track
join thread using(utid)
left join process using(upid)
where thread_counter_track.name != 'thread_time'
`);
const it = result.iter({
trackName: STR_NULL,
utid: NUM,
upid: NUM_NULL,
tid: NUM_NULL,
threadName: STR_NULL,
startTs: LONG_NULL,
trackId: NUM,
endTs: LONG_NULL,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
const tid = it.tid;
const upid = it.upid;
const trackId = it.trackId;
const trackName = it.trackName;
const threadName = it.threadName;
const uuid = this.getUuid(utid, upid);
const startTs = it.startTs === null ? undefined : it.startTs;
const endTs = it.endTs === null ? undefined : it.endTs;
const kind = COUNTER_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, utid, tid, kind, threadName, threadTrack: true});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name,
trackSortKey: {
utid,
priority: InThreadTrackSortKey.ORDINARY,
},
trackGroup: uuid,
config: {
name,
trackId,
startTs,
endTs,
tid,
},
});
}
}
async addProcessAsyncSliceTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
with process_async_tracks as materialized (
select
process_track.upid as upid,
process_track.name as trackName,
process.name as processName,
process.pid as pid,
group_concat(process_track.id) as trackIds,
count(1) as trackCount
from process_track
left join process using(upid)
where
process_track.name is null or
process_track.name not like "% Timeline"
group by
process_track.upid,
process_track.name
)
select
t.*,
max_layout_depth(t.trackCount, t.trackIds) as maxDepth
from process_async_tracks t;
`);
const it = result.iter({
upid: NUM,
trackName: STR_NULL,
trackIds: STR,
processName: STR_NULL,
pid: NUM_NULL,
maxDepth: NUM,
});
for (; it.valid(); it.next()) {
const upid = it.upid;
const trackName = it.trackName;
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
const processName = it.processName;
const pid = it.pid;
const maxDepth = it.maxDepth;
const uuid = this.getUuid(0, upid);
const kind = ASYNC_SLICE_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, upid, pid, processName, kind});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name,
trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
trackGroup: uuid,
config: {
trackIds,
maxDepth,
},
});
}
}
async addActualFramesTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
with process_async_tracks as materialized (
select
process_track.upid as upid,
process_track.name as trackName,
process.name as processName,
process.pid as pid,
group_concat(process_track.id) as trackIds,
count(1) as trackCount
from process_track
left join process using(upid)
where process_track.name = "Actual Timeline"
group by
process_track.upid,
process_track.name
)
select
t.*,
max_layout_depth(t.trackCount, t.trackIds) as maxDepth
from process_async_tracks t;
`);
const it = result.iter({
upid: NUM,
trackName: STR_NULL,
trackIds: STR,
processName: STR_NULL,
pid: NUM_NULL,
maxDepth: NUM,
});
for (; it.valid(); it.next()) {
const upid = it.upid;
const trackName = it.trackName;
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
const processName = it.processName;
const pid = it.pid;
const maxDepth = it.maxDepth;
const uuid = this.getUuid(0, upid);
const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, upid, pid, processName, kind});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name,
trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
trackGroup: uuid,
config: {
trackIds,
maxDepth,
},
});
}
}
async addExpectedFramesTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
with process_async_tracks as materialized (
select
process_track.upid as upid,
process_track.name as trackName,
process.name as processName,
process.pid as pid,
group_concat(process_track.id) as trackIds,
count(1) as trackCount
from process_track
left join process using(upid)
where process_track.name = "Expected Timeline"
group by
process_track.upid,
process_track.name
)
select
t.*,
max_layout_depth(t.trackCount, t.trackIds) as maxDepth
from process_async_tracks t;
`);
const it = result.iter({
upid: NUM,
trackName: STR_NULL,
trackIds: STR,
processName: STR_NULL,
pid: NUM_NULL,
maxDepth: NUM,
});
for (; it.valid(); it.next()) {
const upid = it.upid;
const trackName = it.trackName;
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
const processName = it.processName;
const pid = it.pid;
const maxDepth = it.maxDepth;
const uuid = this.getUuid(0, upid);
const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, upid, pid, processName, kind});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name,
trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
trackGroup: uuid,
config: {
trackIds,
maxDepth,
},
});
}
}
async addThreadSliceTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select
thread_track.utid as utid,
thread_track.id as trackId,
thread_track.name as trackName,
EXTRACT_ARG(thread_track.source_arg_set_id,
'is_root_in_scope') as isDefaultTrackForScope,
tid,
thread.name as threadName,
max(slice.depth) as maxDepth,
process.upid as upid
from slice
join thread_track on slice.track_id = thread_track.id
join thread using(utid)
left join process using(upid)
group by thread_track.id
`);
const it = result.iter({
utid: NUM,
trackId: NUM,
trackName: STR_NULL,
isDefaultTrackForScope: NUM_NULL,
tid: NUM_NULL,
threadName: STR_NULL,
maxDepth: NUM,
upid: NUM_NULL,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
const trackId = it.trackId;
const trackName = it.trackName;
// Note that !!null === false.
const isDefaultTrackForScope = !!it.isDefaultTrackForScope;
const tid = it.tid;
const threadName = it.threadName;
const upid = it.upid;
const maxDepth = it.maxDepth;
const uuid = this.getUuid(utid, upid);
const kind = SLICE_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, utid, tid, threadName, kind});
if (showV1()) {
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name,
trackGroup: uuid,
trackSortKey: {
utid,
priority: isDefaultTrackForScope ?
InThreadTrackSortKey.DEFAULT_TRACK :
InThreadTrackSortKey.ORDINARY,
},
config: {
trackId,
maxDepth,
tid,
},
});
}
if (showV2()) {
this.tracksToAdd.push({
engineId: this.engineId,
kind: 'GenericSliceTrack',
name,
trackGroup: uuid,
trackSortKey: {
utid,
priority: isDefaultTrackForScope ?
InThreadTrackSortKey.DEFAULT_TRACK :
InThreadTrackSortKey.ORDINARY,
},
config: {sqlTrackId: trackId},
});
}
}
}
async addProcessCounterTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select
process_counter_track.id as trackId,
process_counter_track.name as trackName,
upid,
process.pid,
process.name as processName,
process.start_ts as startTs,
process.end_ts as endTs
from process_counter_track
join process using(upid);
`);
const it = result.iter({
trackId: NUM,
trackName: STR_NULL,
upid: NUM,
pid: NUM_NULL,
processName: STR_NULL,
startTs: LONG_NULL,
endTs: LONG_NULL,
});
for (let i = 0; it.valid(); ++i, it.next()) {
const pid = it.pid;
const upid = it.upid;
const trackId = it.trackId;
const trackName = it.trackName;
const processName = it.processName;
const uuid = this.getUuid(0, upid);
const startTs = it.startTs === null ? undefined : it.startTs;
const endTs = it.endTs === null ? undefined : it.endTs;
const kind = COUNTER_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, upid, pid, kind, processName});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
name,
trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack(
upid, trackName || undefined),
trackGroup: uuid,
config: {
name,
trackId,
startTs,
endTs,
},
});
}
}
async addProcessHeapProfileTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select distinct(upid) from heap_profile_allocation
union
select distinct(upid) from heap_graph_object
`);
for (const it = result.iter({upid: NUM}); it.valid(); it.next()) {
const upid = it.upid;
const uuid = this.getUuid(0, upid);
this.tracksToAdd.push({
engineId: this.engineId,
kind: HEAP_PROFILE_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
name: `Heap Profile`,
trackGroup: uuid,
config: {upid},
});
}
}
async addProcessPerfSamplesTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
select distinct upid, pid
from perf_sample join thread using (utid) join process using (upid)
where callsite_id is not null
`);
for (const it = result.iter({upid: NUM, pid: NUM}); it.valid(); it.next()) {
const upid = it.upid;
const pid = it.pid;
const uuid = this.getUuid(0, upid);
this.tracksToAdd.push({
engineId: this.engineId,
kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK,
name: `Callstacks ${pid}`,
trackGroup: uuid,
config: {upid},
});
}
}
getUuidUnchecked(utid: number, upid: number|null) {
return upid === null ? this.utidToUuid.get(utid) :
this.upidToUuid.get(upid);
}
getUuid(utid: number, upid: number|null) {
return assertExists(this.getUuidUnchecked(utid, upid));
}
getOrCreateUuid(utid: number, upid: number|null) {
let uuid = this.getUuidUnchecked(utid, upid);
if (uuid === undefined) {
uuid = uuidv4();
if (upid === null) {
this.utidToUuid.set(utid, uuid);
} else {
this.upidToUuid.set(upid, uuid);
}
}
return uuid;
}
setUuidForUpid(upid: number, uuid: string) {
this.upidToUuid.set(upid, uuid);
}
async addKernelThreadGrouping(engine: EngineProxy): Promise<void> {
// Identify kernel threads if this is a linux system trace, and sufficient
// process information is available. Kernel threads are identified by being
// children of kthreadd (always pid 2).
// The query will return the kthreadd process row first, which must exist
// for any other kthreads to be returned by the query.
// TODO(rsavitski): figure out how to handle the idle process (swapper),
// which has pid 0 but appears as a distinct process (with its own comm) on
// each cpu. It'd make sense to exclude its thread state track, but still
// put process-scoped tracks in this group.
const result = await engine.query(`
select
t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd
from
thread t
join process p using (upid)
left join process parent on (p.parent_upid = parent.upid)
join
(select true from metadata m
where (m.name = 'system_name' and m.str_value = 'Linux')
union
select 1 from (select true from sched limit 1))
where
p.pid = 2 or parent.pid = 2
order by isKthreadd desc
`);
const it = result.iter({
utid: NUM,
upid: NUM,
});
// Not applying kernel thread grouping.
if (!it.valid()) {
return;
}
// Create the track group. Use kthreadd's PROCESS_SUMMARY_TRACK for the
// main track. It doesn't summarise the kernel threads within the group,
// but creating a dedicated track type is out of scope at the time of
// writing.
const kthreadGroupUuid = uuidv4();
const summaryTrackId = uuidv4();
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind: PROCESS_SUMMARY_TRACK,
trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
name: `Kernel thread summary`,
config: {pidForColor: 2, upid: it.upid, utid: it.utid},
});
const addTrackGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name: `Kernel threads`,
id: kthreadGroupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addTrackGroup);
// Set the group for all kernel threads (including kthreadd itself).
for (; it.valid(); it.next()) {
this.setUuidForUpid(it.upid, kthreadGroupUuid);
}
}
async addProcessTrackGroups(engine: EngineProxy): Promise<void> {
// We want to create groups of tracks in a specific order.
// The tracks should be grouped:
// by upid
// or (if upid is null) by utid
// the groups should be sorted by:
// Chrome-based process rank based on process names (e.g. Browser)
// has a heap profile or not
// total cpu time *for the whole parent process*
// process name
// upid
// thread name
// utid
const result = await engine.query(`
select
the_tracks.upid,
the_tracks.utid,
total_dur as hasSched,
hasHeapProfiles,
process.pid as pid,
thread.tid as tid,
process.name as processName,
thread.name as threadName,
ifnull((
select group_concat(string_value)
from args
where
process.arg_set_id is not null and
arg_set_id = process.arg_set_id and
flat_key = 'chrome.process_label'
), '') AS chromeProcessLabels,
(case process.name
when 'Browser' then 3
when 'Gpu' then 2
when 'Renderer' then 1
else 0
end) as chromeProcessRank
from (
select upid, 0 as utid from process_track
union
select upid, 0 as utid from process_counter_track
union
select upid, utid from thread_counter_track join thread using(utid)
union
select upid, utid from thread_track join thread using(utid)
union
select upid, utid from sched join thread using(utid) group by utid
union
select upid, 0 as utid from (
select distinct upid
from perf_sample join thread using (utid) join process using (upid)
where callsite_id is not null)
union
select upid, utid from (
select distinct(utid) from cpu_profile_stack_sample
) join thread using(utid)
union
select distinct(upid) as upid, 0 as utid from heap_profile_allocation
union
select distinct(upid) as upid, 0 as utid from heap_graph_object
) the_tracks
left join (
select upid, sum(thread_total_dur) as total_dur
from (
select utid, sum(dur) as thread_total_dur
from sched where dur != -1 and utid != 0
group by utid
)
join thread using (utid)
group by upid
) using(upid)
left join (
select
distinct(upid) as upid,
true as hasHeapProfiles
from heap_profile_allocation
union
select
distinct(upid) as upid,
true as hasHeapProfiles
from heap_graph_object
) using (upid)
left join (
select
thread.upid as upid,
sum(cnt) as perfSampleCount
from (
select utid, count(*) as cnt
from perf_sample where callsite_id is not null
group by utid
) join thread using (utid)
group by thread.upid
) using (upid)
left join (
select
process.upid as upid,
sum(cnt) as sliceCount
from (select track_id, count(*) as cnt from slice group by track_id)
left join thread_track on track_id = thread_track.id
left join thread on thread_track.utid = thread.utid
left join process_track on track_id = process_track.id
join process on process.upid = thread.upid
or process_track.upid = process.upid
where process.upid is not null
group by process.upid
) using (upid)
left join thread using(utid)
left join process using(upid)
order by
chromeProcessRank desc,
hasHeapProfiles desc,
perfSampleCount desc,
total_dur desc,
sliceCount desc,
processName asc nulls last,
the_tracks.upid asc nulls last,
threadName asc nulls last,
the_tracks.utid asc nulls last;
`);
const it = result.iter({
utid: NUM,
upid: NUM_NULL,
tid: NUM_NULL,
pid: NUM_NULL,
threadName: STR_NULL,
processName: STR_NULL,
hasSched: NUM_NULL,
hasHeapProfiles: NUM_NULL,
chromeProcessLabels: STR,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
const tid = it.tid;
const upid = it.upid;
const pid = it.pid;
const threadName = it.threadName;
const processName = it.processName;
const hasSched = !!it.hasSched;
const hasHeapProfiles = !!it.hasHeapProfiles;
// Group by upid if present else by utid.
let pUuid =
upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid);
// These should only happen once for each track group.
if (pUuid === undefined) {
pUuid = this.getOrCreateUuid(utid, upid);
const summaryTrackId = uuidv4();
const pidForColor = pid || tid || upid || utid || 0;
const kind =
hasSched ? PROCESS_SCHEDULING_TRACK_KIND : PROCESS_SUMMARY_TRACK;
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
kind,
trackSortKey: hasSched ?
PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK :
PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
name: `${upid === null ? tid : pid} summary`,
config: {pidForColor, upid, utid, tid},
labels: it.chromeProcessLabels.split(','),
});
const name = TrackDecider.getTrackName(
{utid, processName, pid, threadName, tid, upid});
const addTrackGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name,
id: pUuid,
// Perf profiling tracks remain collapsed, otherwise we would have too
// many expanded process tracks for some perf traces, leading to
// jankyness.
collapsed: !hasHeapProfiles,
});
this.addTrackGroupActions.push(addTrackGroup);
}
}
}
private async computeThreadOrderingMetadata(): Promise<UtidToTrackSortKey> {
const result = await this.engine.query(`
select
utid,
tid,
pid,
thread.name as threadName
from thread
left join process using(upid)`);
const it = result.iter({
utid: NUM,
tid: NUM_NULL,
pid: NUM_NULL,
threadName: STR_NULL,
});
const threadOrderingMetadata: UtidToTrackSortKey = {};
for (; it.valid(); it.next()) {
threadOrderingMetadata[it.utid] = {
tid: it.tid === null ? undefined : it.tid,
sortKey: TrackDecider.getThreadSortKey(it.threadName, it.tid, it.pid),
};
}
return threadOrderingMetadata;
}
private async defineMaxLayoutDepthSqlFunction(): Promise<void> {
await this.engine.query(`
select create_function(
'max_layout_depth(track_count INT, track_ids STRING)',
'INT',
'
select ifnull(iif(
$track_count = 1,
(
select max(depth)
from slice
where track_id = cast($track_ids AS int)
),
(
select max(layout_depth)
from experimental_slice_layout($track_ids)
)
), 0);
'
);
`);
}
async addPluginTracks(): Promise<void> {
const promises = pluginManager.findPotentialTracks(this.engine);
const groups = await Promise.all(promises);
for (const infos of groups) {
for (const info of infos) {
this.tracksToAdd.push({
engineId: this.engineId,
kind: info.trackKind,
name: info.name,
// TODO(hjd): Fix how sorting works. Plugins should expose
// 'sort keys' which the user can use to choose a sort order.
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
config: info.config,
});
}
}
}
async decideTracks(): Promise<DeferredAction[]> {
await this.defineMaxLayoutDepthSqlFunction();
// Add first the global tracks that don't require per-process track groups.
await this.addCpuSchedulingTracks();
await this.addFtraceTrack(
this.engine.getProxy('TrackDecider::addFtraceTrack'));
await this.addCpuFreqTracks(
this.engine.getProxy('TrackDecider::addCpuFreqTracks'));
await this.addGlobalAsyncTracks(
this.engine.getProxy('TrackDecider::addGlobalAsyncTracks'));
await this.addGpuFreqTracks(
this.engine.getProxy('TrackDecider::addGpuFreqTracks'));
await this.addCpuPerfCounterTracks(
this.engine.getProxy('TrackDecider::addCpuPerfCounterTracks'));
await this.addPluginTracks();
await this.addAnnotationTracks(
this.engine.getProxy('TrackDecider::addAnnotationTracks'));
await this.groupGlobalIonTracks();
await this.groupGlobalIostatTracks(F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME);
await this.groupGlobalIostatTracks(
F2FS_IOSTAT_LAT_TAG, F2FS_IOSTAT_LAT_GROUP_NAME);
await this.groupGlobalIostatTracks(DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME);
await this.groupGlobalUfsCmdTagTracks(UFS_CMD_TAG, UFS_CMD_TAG_GROUP_NAME);
await this.groupGlobalBuddyInfoTracks();
await this.groupTracksByRegex(KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP);
await this.groupTracksByRegex(NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP);
await this.groupTracksByRegex(
ENTITY_RESIDENCY_REGEX, ENTITY_RESIDENCY_GROUP);
// Pre-group all kernel "threads" (actually processes) if this is a linux
// system trace. Below, addProcessTrackGroups will skip them due to an
// existing group uuid, and addThreadStateTracks will fill in the
// per-thread tracks. Quirk: since all threads will appear to be
// TrackKindPriority.MAIN_THREAD, any process-level tracks will end up
// pushed to the bottom of the group in the UI.
await this.addKernelThreadGrouping(
this.engine.getProxy('TrackDecider::addKernelThreadGrouping'));
// Create the per-process track groups. Note that this won't necessarily
// create a track per process. If a process has been completely idle and has
// no sched events, no track group will be emitted.
// Will populate this.addTrackGroupActions
await this.addProcessTrackGroups(
this.engine.getProxy('TrackDecider::addProcessTrackGroups'));
await this.addProcessHeapProfileTracks(
this.engine.getProxy('TrackDecider::addProcessHeapProfileTracks'));
if (PERF_SAMPLE_FLAG.get()) {
await this.addProcessPerfSamplesTracks(
this.engine.getProxy('TrackDecider::addProcessPerfSamplesTracks'));
}
await this.addProcessCounterTracks(
this.engine.getProxy('TrackDecider::addProcessCounterTracks'));
await this.addProcessAsyncSliceTracks(
this.engine.getProxy('TrackDecider::addProcessAsyncSliceTrack'));
await this.addActualFramesTracks(
this.engine.getProxy('TrackDecider::addActualFramesTracks'));
await this.addExpectedFramesTracks(
this.engine.getProxy('TrackDecider::addExpectedFramesTracks'));
await this.addThreadCounterTracks(
this.engine.getProxy('TrackDecider::addThreadCounterTracks'));
await this.addThreadStateTracks(
this.engine.getProxy('TrackDecider::addThreadStateTracks'));
await this.addThreadSliceTracks(
this.engine.getProxy('TrackDecider::addThreadSliceTracks'));
await this.addThreadCpuSampleTracks(
this.engine.getProxy('TrackDecider::addThreadCpuSampleTracks'));
await this.addLogsTrack(this.engine.getProxy('TrackDecider::addLogsTrack'));
// TODO(hjd): Move into plugin API.
{
const result = scrollJankDecideTracks(this.engine, (utid, upid) => {
return this.getUuid(utid, upid);
});
if (result !== null) {
const {tracksToAdd} = await result;
this.tracksToAdd.push(...tracksToAdd);
}
}
this.addTrackGroupActions.push(
Actions.addTracks({tracks: this.tracksToAdd}));
const threadOrderingMetadata = await this.computeThreadOrderingMetadata();
this.addTrackGroupActions.push(
Actions.setUtidToTrackSortKey({threadOrderingMetadata}));
this.applyDefaultCounterScale();
return this.addTrackGroupActions;
}
// Some process counter tracks are tied to specific threads based on their
// name.
private async resolveTrackSortKeyForProcessCounterTrack(
upid: number, threadName?: string): Promise<TrackSortKey> {
if (threadName !== 'GPU completion') {
return PrimaryTrackSortKey.COUNTER_TRACK;
}
const result = await this.engine.query(`
select utid
from thread
where upid=${upid} and name=${sqliteString(threadName)}
`);
const it = result.iter({
utid: NUM,
});
for (; it; it.next()) {
return {
utid: it.utid,
priority: InThreadTrackSortKey.THREAD_COUNTER_TRACK,
};
}
return PrimaryTrackSortKey.COUNTER_TRACK;
}
private static getThreadSortKey(
threadName?: string|null, tid?: number|null,
pid?: number|null): PrimaryTrackSortKey {
if (pid !== undefined && pid !== null && pid === tid) {
return PrimaryTrackSortKey.MAIN_THREAD;
}
if (threadName === undefined || threadName === null) {
return PrimaryTrackSortKey.ORDINARY_THREAD;
}
// Chrome main threads should always come first within their process.
if (threadName === 'CrBrowserMain' || threadName === 'CrRendererMain' ||
threadName === 'CrGpuMain') {
return PrimaryTrackSortKey.MAIN_THREAD;
}
// Chrome IO threads should always come immediately after the main thread.
if (threadName === 'Chrome_ChildIOThread' ||
threadName === 'Chrome_IOThread') {
return PrimaryTrackSortKey.CHROME_IO_THREAD;
}
// A Chrome process can have only one compositor thread, so we want to put
// it next to other named processes.
if (threadName === 'Compositor' || threadName === 'VizCompositorThread') {
return PrimaryTrackSortKey.CHROME_COMPOSITOR_THREAD;
}
switch (true) {
case /.*RenderThread.*/.test(threadName):
return PrimaryTrackSortKey.RENDER_THREAD;
case /.*GPU completion.*/.test(threadName):
return PrimaryTrackSortKey.GPU_COMPLETION_THREAD;
default:
return PrimaryTrackSortKey.ORDINARY_THREAD;
}
}
}