blob: d7a9e0e497e4845e42d2e6986897c47d0b757654 [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 {assertExists} from '../base/logging';
import {globals} from '../frontend/globals';
import {TrackDescriptor} from '../public';
import {Engine, EngineBase} from '../trace_processor/engine';
import {NUM, STR, STR_NULL} from '../trace_processor/query_result';
import {
ACTUAL_FRAMES_SLICE_TRACK_KIND,
ASYNC_SLICE_TRACK_KIND,
CHROME_EVENT_LATENCY_TRACK_KIND,
CHROME_SCROLL_JANK_TRACK_KIND,
CHROME_TOPLEVEL_SCROLLS_KIND,
COUNTER_TRACK_KIND,
CPU_FREQ_TRACK_KIND,
CPU_PROFILE_TRACK_KIND,
CPU_SLICE_TRACK_KIND,
EXPECTED_FRAMES_SLICE_TRACK_KIND,
HEAP_PROFILE_TRACK_KIND,
PERF_SAMPLES_PROFILE_TRACK_KIND,
SCROLL_JANK_V3_TRACK_KIND,
THREAD_SLICE_TRACK_KIND,
THREAD_STATE_TRACK_KIND,
} from '../core/track_kinds';
import {exists, Optional} from '../base/utils';
import {GroupNode, ContainerNode, TrackNode} from '../public/workspace';
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 BUDDY_INFO_TAG = 'mem.buddyinfo';
const UFS_CMD_TAG_REGEX = new RegExp('^io.ufs.command.tag.*$');
const UFS_CMD_TAG_GROUP = 'io.ufs.command.tags';
// 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';
const UCLAMP_REGEX = new RegExp('^UCLAMP_');
const UCLAMP_GROUP = 'Scheduler Utilization Clamping';
const POWER_RAILS_GROUP = 'Power Rails';
const POWER_RAILS_REGEX = new RegExp('^power.');
const FREQUENCY_GROUP = 'Frequency Scaling';
const TEMPERATURE_REGEX = new RegExp('^.* Temperature$');
const TEMPERATURE_GROUP = 'Temperature';
const IRQ_GROUP = 'IRQs';
const IRQ_REGEX = new RegExp('^(Irq|SoftIrq) Cpu.*');
const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*');
const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
const MISC_GROUP = 'Misc Global Tracks';
export async function decideTracks(engine: EngineBase): Promise<void> {
await new TrackDecider(engine).decideTracks();
}
class TrackDecider {
private engine: EngineBase;
private threadGroups = new Map<number, GroupNode>();
private processGroups = new Map<number, GroupNode>();
constructor(engine: EngineBase) {
this.engine = engine;
}
private groupGlobalIonTracks(): void {
const ionTracks: TrackNode[] = [];
let hasSummary = false;
for (const track of globals.workspace.children) {
if (!(track instanceof TrackNode)) continue;
const isIon = track.displayName.startsWith(MEM_ION);
const isIonCounter = track.displayName === MEM_ION;
const isDmaHeapCounter = track.displayName === MEM_DMA_COUNTER_NAME;
const isDmaBuffferSlices = track.displayName === MEM_DMA;
if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) {
ionTracks.push(track);
}
hasSummary = hasSummary || isIonCounter;
hasSummary = hasSummary || isDmaHeapCounter;
}
if (ionTracks.length === 0 || !hasSummary) {
return;
}
let group: Optional<GroupNode>;
for (const track of ionTracks) {
if (!group && [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.uri)) {
globals.workspace.removeChild(track);
group = new GroupNode(track.displayName);
group.headerTrackUri = track.uri;
globals.workspace.addChild(group);
} else {
group?.addChild(track);
}
}
}
private groupGlobalIostatTracks(tag: string, groupName: string): void {
const devMap = new Map<string, GroupNode>();
for (const track of globals.workspace.children) {
if (track instanceof TrackNode && track.displayName.startsWith(tag)) {
const name = track.displayName.split('.', 3);
const key = name[1];
let parentGroup = devMap.get(key);
if (!parentGroup) {
const group = new GroupNode(groupName);
globals.workspace.addChild(group);
devMap.set(key, group);
parentGroup = group;
}
track.displayName = name[2];
parentGroup.addChild(track);
}
}
}
private groupGlobalBuddyInfoTracks(): void {
const devMap = new Map<string, GroupNode>();
for (const track of globals.workspace.children) {
if (
track instanceof TrackNode &&
track.displayName.startsWith(BUDDY_INFO_TAG)
) {
const tokens = track.uri.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)) {
const group = new GroupNode(groupName);
devMap.set(groupName, group);
globals.workspace.addChild(group);
}
track.displayName = 'Chunk size: ' + size;
const group = devMap.get(groupName)!;
group.addChild(track);
}
}
}
private groupFrequencyTracks(groupName: string): void {
const group = new GroupNode(groupName);
for (const track of globals.workspace.children) {
if (!(track instanceof TrackNode)) continue;
// Group all the frequency tracks together (except the CPU and GPU
// frequency ones).
if (
track.displayName.endsWith('Frequency') &&
!track.displayName.startsWith('Cpu') &&
!track.displayName.startsWith('Gpu')
) {
group.addChild(track);
}
}
if (group.children.length > 0) {
globals.workspace.addChild(group);
}
}
private groupMiscNonAllowlistedTracks(groupName: string): void {
// List of allowlisted track names.
const ALLOWLIST_REGEXES = [
new RegExp('^Cpu .*$', 'i'),
new RegExp('^Gpu .*$', 'i'),
new RegExp('^Trace Triggers$'),
new RegExp('^Android App Startups$'),
new RegExp('^Device State.*$'),
new RegExp('^Android logs$'),
];
const group = new GroupNode(groupName);
for (const track of globals.workspace.children) {
if (!(track instanceof TrackNode)) continue;
let allowlisted = false;
for (const regex of ALLOWLIST_REGEXES) {
allowlisted = allowlisted || regex.test(track.displayName);
}
if (allowlisted) {
continue;
}
group.addChild(track);
}
if (group.children.length > 0) {
globals.workspace.addChild(group);
}
}
private groupTracksByRegex(regex: RegExp, groupName: string): void {
const group = new GroupNode(groupName);
for (const track of globals.workspace.children) {
if (track instanceof TrackNode && regex.test(track.displayName)) {
group.addChild(track);
}
}
if (group.children.length > 0) {
globals.workspace.addChild(group);
}
}
private addAnnotationTracks(tracks: ReadonlyArray<TrackDescriptor>): void {
const annotationTracks = tracks.filter(
({tags}) => tags?.scope === 'annotation',
);
const groups = new Map<string, GroupNode>();
annotationTracks
.filter(({tags}) => tags?.kind === THREAD_SLICE_TRACK_KIND)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
const groupName = td.tags?.groupName;
// We want to try and find a group to put this track in. If groupName is
// defined, create a new group or place in existing one if it already
// exists Otherwise, try upid to see if we can put this in a process
// group
let container: ContainerNode;
if (groupName) {
const existingGroup = groups.get(groupName);
if (!existingGroup) {
const group = new GroupNode(groupName);
group.headerTrackUri = td.uri;
container = group;
groups.set(groupName, group);
globals.workspace.addChild(group);
} else {
container = existingGroup;
}
} else {
const procGroup = this.processGroups.get(upid);
if (upid !== 0 && procGroup) {
container = procGroup;
} else {
container = globals.workspace;
}
}
container.addChild(new TrackNode(td.uri, td.title));
});
annotationTracks
.filter(({tags}) => tags?.kind === COUNTER_TRACK_KIND)
.forEach((td) => {
const upid = td.tags?.upid;
const parent =
(exists(upid) && this.processGroups.get(upid)) || globals.workspace;
parent.addChild(new TrackNode(td.uri, td.title));
});
}
private addThreadStateTracks(tracks: ReadonlyArray<TrackDescriptor>): void {
tracks
.filter(
({tags}) =>
tags?.kind === THREAD_STATE_TRACK_KIND && tags?.utid !== undefined,
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addThreadCpuSampleTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === CPU_PROFILE_TRACK_KIND && tags?.utid !== undefined,
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addThreadCounterTracks(tracks: ReadonlyArray<TrackDescriptor>): void {
tracks
.filter(
({tags}) =>
tags?.kind === COUNTER_TRACK_KIND &&
tags?.utid !== undefined &&
tags?.scope === 'thread',
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addProcessAsyncSliceTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === ASYNC_SLICE_TRACK_KIND &&
tags?.upid !== undefined &&
tags?.scope === 'process',
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
const group = this.getProcGroup(upid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addUserAsyncSliceTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
const groupMap = new Map<string, GroupNode>();
tracks
.filter(
({tags}) =>
tags?.kind === ASYNC_SLICE_TRACK_KIND &&
tags?.scope === 'user' &&
tags?.rawName !== undefined,
)
.forEach((td) => {
const rawName = td.tags?.rawName;
if (typeof rawName === 'string') {
const track = new TrackNode(td.uri, td.title);
const existingGroup = groupMap.get(rawName);
if (existingGroup) {
existingGroup.addChild(track);
} else {
const group = new GroupNode(rawName);
globals.workspace.addChild(group);
groupMap.set(rawName, group);
group.addChild(track);
}
}
});
}
private addActualFramesTracks(tracks: ReadonlyArray<TrackDescriptor>): void {
tracks
.filter(
({tags}) =>
tags?.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND &&
tags?.upid !== undefined,
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
const group = this.getProcGroup(upid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addExpectedFramesTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === EXPECTED_FRAMES_SLICE_TRACK_KIND &&
tags?.upid !== undefined,
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
const group = this.getProcGroup(upid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addThreadSliceTracks(tracks: ReadonlyArray<TrackDescriptor>): void {
tracks
.filter(
({tags}) =>
tags?.kind === THREAD_SLICE_TRACK_KIND && tags?.utid !== undefined,
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
// const upid = td.tags?.upid;
// const isDefaultTrackForScope = Boolean(td.tags?.isDefaultTrackForScope);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addAsyncThreadSliceTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === ASYNC_SLICE_TRACK_KIND &&
tags?.utid !== undefined &&
tags?.scope === 'thread',
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private async addProcessCounterTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): Promise<void> {
const processCounterTracks = tracks.filter(
({tags}) =>
tags?.kind === COUNTER_TRACK_KIND &&
tags?.scope === 'process' &&
tags?.upid !== undefined,
);
for (const td of processCounterTracks) {
const upid = assertExists(td.tags?.upid);
const group = this.getProcGroup(upid);
// const trackNameTag = td.tags?.trackName;
// const trackName =
// typeof trackNameTag === 'string' ? trackNameTag : undefined;
group.addChild(new TrackNode(td.uri, td.title));
}
}
private addProcessHeapProfileTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === HEAP_PROFILE_TRACK_KIND && tags?.upid !== undefined,
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
const group = this.getProcGroup(upid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addProcessPerfSamplesTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
tags.upid !== undefined &&
tags.utid === undefined,
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
const group = this.getProcGroup(upid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private addThreadPerfSamplesTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(
({tags}) =>
tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
tags?.utid !== undefined,
)
.forEach((td) => {
// const upid = td.tags?.upid;
const utid = assertExists(td.tags?.utid);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
private getProcGroup(upid: number): GroupNode {
const group = this.processGroups.get(upid);
if (group) {
return group;
} else {
throw new Error(`Unable to find proc group with upid ${upid}`);
}
}
private getThreadGroup(utid: number): GroupNode {
const group = this.threadGroups.get(utid);
if (group) {
return group;
} else {
throw new Error(`Unable to find thread group with utid ${utid}`);
}
}
private async addKernelThreadGrouping(engine: Engine): 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 group = new GroupNode('Kernel threads');
group.headerTrackUri = '/kernel'; // Summary track
globals.workspace.addChild(group);
// Set the group for all kernel threads (including kthreadd itself).
for (; it.valid(); it.next()) {
const {utid} = it;
const threadGroup = new GroupNode(`Kernel Thread ${utid}`);
threadGroup.headless = true;
group.addChild(threadGroup);
this.threadGroups.set(utid, threadGroup);
}
}
// Adds top level groups for processes and thread that don't belong to a
// process.
private async addProcessGroups(engine: Engine): Promise<void> {
const result = await engine.query(`
with processGroups as (
select
upid,
process.pid as pid,
process.name as processName,
sum_running_dur as sumRunningDur,
thread_slice_count + process_slice_count as sliceCount,
perf_sample_count as perfSampleCount,
allocation_count as heapProfileAllocationCount,
graph_object_count as heapGraphObjectCount,
(
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'
) chromeProcessLabels,
case process.name
when 'Browser' then 3
when 'Gpu' then 2
when 'Renderer' then 1
else 0
end as chromeProcessRank
from _process_available_info_summary
join process using(upid)
),
threadGroups as (
select
utid,
tid,
thread.name as threadName,
sum_running_dur as sumRunningDur,
slice_count as sliceCount,
perf_sample_count as perfSampleCount
from _thread_available_info_summary
join thread using (utid)
where upid is null
)
select *
from (
select
'process' as kind,
upid as uid,
pid as id,
processName as name
from processGroups
order by
chromeProcessRank desc,
heapProfileAllocationCount desc,
heapGraphObjectCount desc,
perfSampleCount desc,
sumRunningDur desc,
sliceCount desc,
processName asc,
upid asc
)
union all
select *
from (
select
'thread' as kind,
utid as uid,
tid as id,
threadName as name
from threadGroups
order by
perfSampleCount desc,
sumRunningDur desc,
sliceCount desc,
threadName asc,
utid asc
)
`);
const it = result.iter({
kind: STR,
uid: NUM,
id: NUM,
name: STR_NULL,
});
for (; it.valid(); it.next()) {
const {kind, uid, id, name} = it;
if (kind === 'process') {
// Ignore kernel process groups
if (this.processGroups.has(uid)) {
continue;
}
function getProcessDisplayName(
processName: string | undefined,
pid: number,
) {
if (processName) {
return `${stripPathFromExecutable(processName)} ${pid}`;
} else {
return `Process ${pid}`;
}
}
const displayName = getProcessDisplayName(name ?? undefined, id);
const group = new GroupNode(displayName);
group.headerTrackUri = `/process_${uid}`; // Summary track URI
globals.workspace.addChild(group);
this.processGroups.set(uid, group);
} else {
// Ignore kernel process groups
if (this.threadGroups.has(uid)) {
continue;
}
function getThreadDisplayName(
threadName: string | undefined,
pid: number,
) {
if (threadName) {
return `${stripPathFromExecutable(threadName)} ${pid}`;
} else {
return `Thread ${pid}`;
}
}
const displayName = getThreadDisplayName(name ?? undefined, id);
const group = new GroupNode(displayName);
group.headerTrackUri = `/thread_${uid}`; // Summary track URI
globals.workspace.addChild(group);
this.threadGroups.set(uid, group);
}
}
}
// Create all the nested & headless thread groups that live inside existing
// process groups.
private async addThreadGroups(engine: Engine): Promise<void> {
const result = await engine.query(`
with threadGroups as (
select
utid,
upid,
tid,
thread.name as threadName,
CASE
WHEN thread.is_main_thread = 1 THEN 10
WHEN thread.name = 'CrBrowserMain' THEN 10
WHEN thread.name = 'CrRendererMain' THEN 10
WHEN thread.name = 'CrGpuMain' THEN 10
WHEN thread.name glob '*RenderThread*' THEN 9
WHEN thread.name glob '*GPU completion*' THEN 8
WHEN thread.name = 'Chrome_ChildIOThread' THEN 7
WHEN thread.name = 'Chrome_IOThread' THEN 7
WHEN thread.name = 'Compositor' THEN 6
WHEN thread.name = 'VizCompositorThread' THEN 6
ELSE 5
END as priority
from _thread_available_info_summary
join thread using (utid)
where upid is not null
)
select *
from (
select
utid,
upid,
tid,
threadName
from threadGroups
order by
priority desc,
tid asc
)
`);
const it = result.iter({
utid: NUM,
tid: NUM,
upid: NUM,
threadName: STR_NULL,
});
for (; it.valid(); it.next()) {
const {utid, tid, upid, threadName} = it;
// Ignore kernel thread groups
if (this.threadGroups.has(utid)) {
continue;
}
const threadGroup = new GroupNode(threadName ?? `Thread ${tid}`);
this.threadGroups.set(utid, threadGroup);
threadGroup.headless = true;
threadGroup.expand();
this.processGroups.get(upid)?.addChild(threadGroup);
}
}
private addPluginTracks(): void {
const groups = new Map<string, GroupNode>();
const tracks = globals.trackManager.getAutoShowTracks();
for (const info of tracks) {
const groupName = info.tags?.groupName;
let container: ContainerNode = globals.workspace;
if (groupName) {
const existingGroup = groups.get(groupName);
if (existingGroup) {
container = existingGroup;
} else {
// Add the group
const group = new GroupNode(groupName);
container = group;
globals.workspace.addChild(group);
groups.set(groupName, group);
}
}
const track = new TrackNode(info.uri, info.title);
container.addChild(track);
}
}
private addScrollJankPluginTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
const group = new GroupNode('Chrome Scroll Jank');
group.expand();
tracks
.filter(({tags}) => tags?.kind === CHROME_TOPLEVEL_SCROLLS_KIND)
.forEach((td) => {
group.addChild(new TrackNode(td.uri, td.title));
});
tracks
.filter(({tags}) => tags?.kind === SCROLL_JANK_V3_TRACK_KIND)
.forEach((td) => {
group.addChild(new TrackNode(td.uri, td.title));
});
tracks
.filter(({tags}) => tags?.kind === CHROME_EVENT_LATENCY_TRACK_KIND)
.forEach((td) => {
group.addChild(new TrackNode(td.uri, td.title));
});
if (group.children.length) {
globals.workspace.addChild(group);
}
}
private addChromeScrollJankTrack(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
tracks
.filter(({tags}) => tags?.kind === CHROME_SCROLL_JANK_TRACK_KIND)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
const group = this.getThreadGroup(utid);
group.addChild(new TrackNode(td.uri, td.title));
});
}
// Add an ordinary track from a track descriptor
private addTrack(track: TrackDescriptor): void {
globals.workspace.addChild(new TrackNode(track.uri, track.title));
}
// Add tracks that match some predicate
private addTracks(
source: ReadonlyArray<TrackDescriptor>,
predicate: (td: TrackDescriptor) => boolean,
): ReadonlyArray<TrackDescriptor> {
const filteredTracks = source.filter(predicate);
filteredTracks.forEach((a) => this.addTrack(a));
return filteredTracks;
}
public async decideTracks(): Promise<void> {
const tracks = globals.trackManager.getAllTracks();
// Add first the global tracks that don't require per-process track groups.
this.addTracks(tracks, ({uri}) => uri === 'screenshots');
this.addTracks(tracks, ({tags}) => tags?.kind === CPU_SLICE_TRACK_KIND);
this.addTracks(tracks, ({tags}) => tags?.kind === CPU_FREQ_TRACK_KIND);
this.addScrollJankPluginTracks(tracks);
this.addTracks(
tracks,
({tags}) =>
tags?.kind === ASYNC_SLICE_TRACK_KIND && tags?.scope === 'global',
);
this.addTracks(
tracks,
({tags}) =>
tags?.kind === COUNTER_TRACK_KIND && tags?.scope === 'gpuFreq',
);
this.addTracks(
tracks,
({tags}) =>
tags?.kind === COUNTER_TRACK_KIND && tags?.scope === 'cpuFreqLimit',
);
this.addTracks(
tracks,
({tags}) =>
tags?.kind === COUNTER_TRACK_KIND && tags?.scope === 'cpuPerf',
);
this.addPluginTracks();
this.addAnnotationTracks(tracks);
this.groupGlobalIonTracks();
this.groupGlobalIostatTracks(F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME);
this.groupGlobalIostatTracks(
F2FS_IOSTAT_LAT_TAG,
F2FS_IOSTAT_LAT_GROUP_NAME,
);
this.groupGlobalIostatTracks(DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME);
this.groupTracksByRegex(UFS_CMD_TAG_REGEX, UFS_CMD_TAG_GROUP);
this.groupGlobalBuddyInfoTracks();
this.groupTracksByRegex(KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP);
this.groupTracksByRegex(NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP);
this.groupTracksByRegex(ENTITY_RESIDENCY_REGEX, ENTITY_RESIDENCY_GROUP);
this.groupTracksByRegex(UCLAMP_REGEX, UCLAMP_GROUP);
this.groupFrequencyTracks(FREQUENCY_GROUP);
this.groupTracksByRegex(POWER_RAILS_REGEX, POWER_RAILS_GROUP);
this.groupTracksByRegex(TEMPERATURE_REGEX, TEMPERATURE_GROUP);
this.groupTracksByRegex(IRQ_REGEX, IRQ_GROUP);
this.groupTracksByRegex(CHROME_TRACK_REGEX, CHROME_TRACK_GROUP);
this.groupMiscNonAllowlistedTracks(MISC_GROUP);
// Add user slice tracks before listing the processes. These tracks will
// be listed with their user/package name only, and they will be grouped
// under on their original shared track names. E.g. "GPU Work Period"
this.addUserAsyncSliceTracks(tracks);
// 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.addProcessGroups(
this.engine.getProxy('TrackDecider::addProcessTrackGroups'),
);
this.addExpectedFramesTracks(tracks);
this.addActualFramesTracks(tracks);
this.addProcessPerfSamplesTracks(tracks);
this.addProcessHeapProfileTracks(tracks);
await this.addThreadGroups(
this.engine.getProxy('TrackDecider::addThreadTrackGroups'),
);
this.addThreadPerfSamplesTracks(tracks);
this.addThreadCpuSampleTracks(tracks);
this.addThreadStateTracks(tracks);
this.addThreadSliceTracks(tracks);
this.addThreadCounterTracks(tracks);
await this.addProcessCounterTracks(tracks);
this.addProcessAsyncSliceTracks(tracks);
this.addAsyncThreadSliceTracks(tracks);
this.addChromeScrollJankTrack(tracks);
// Remove any empty groups
globals.workspace.children.forEach((n) => {
if (n instanceof GroupNode && n.children.length === 0) {
globals.workspace.removeChild(n);
}
});
// Move groups underneath tracks
Array.from(globals.workspace.children)
.sort((a, b) => {
// Define the desired order
const order = [TrackNode, GroupNode];
// Get the index in the order array
const indexA = order.findIndex((type) => a instanceof type);
const indexB = order.findIndex((type) => b instanceof type);
// Sort based on the index in the order array
return indexA - indexB;
})
.forEach((n) => globals.workspace.addChild(n));
// If there is only one group, expand it
const groups = globals.workspace.children;
if (groups.length === 1 && groups[0] instanceof GroupNode) {
groups[0].expand();
}
}
}
function stripPathFromExecutable(path: string) {
if (path[0] === '/') {
return path.split('/').slice(-1)[0];
} else {
return path;
}
}