| // 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 { |
| InThreadTrackSortKey, |
| SCROLLING_TRACK_GROUP, |
| TrackSortKey, |
| UtidToTrackSortKey, |
| } from '../common/state'; |
| import {globals} from '../frontend/globals'; |
| import {PERF_SAMPLE_FLAG} from '../core/feature_flags'; |
| import {PrimaryTrackSortKey} from '../public'; |
| import {getTrackName} from '../public/utils'; |
| import {Engine, EngineProxy} from '../trace_processor/engine'; |
| import {NUM, NUM_NULL, STR, STR_NULL} from '../trace_processor/query_result'; |
| import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices'; |
| import { |
| ENABLE_SCROLL_JANK_PLUGIN_V2, |
| getScrollJankTracks, |
| } from '../tracks/chrome_scroll_jank'; |
| import {decideTracks as scrollJankDecideTracks} from '../tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track'; |
| import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; |
| import {COUNTER_TRACK_KIND} from '../tracks/counter'; |
| import { |
| ACTUAL_FRAMES_SLICE_TRACK_KIND, |
| EXPECTED_FRAMES_SLICE_TRACK_KIND, |
| } from '../tracks/frames'; |
| import {decideTracks as screenshotDecideTracks} from '../tracks/screenshots'; |
| import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state'; |
| |
| 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: Engine): Promise<DeferredAction[]> { |
| return new TrackDecider(engine).decideTracks(); |
| } |
| |
| class TrackDecider { |
| private engine: Engine; |
| private upidToUuid = new Map<number, string>(); |
| private utidToUuid = new Map<number, string>(); |
| private tracksToAdd: AddTrackArgs[] = []; |
| private addTrackGroupActions: DeferredAction[] = []; |
| |
| constructor(engine: Engine) { |
| this.engine = engine; |
| } |
| |
| async guessCpuSizes(): Promise<Map<number, string>> { |
| const cpuToSize = new Map<number, string>(); |
| await this.engine.query(` |
| INCLUDE PERFETTO MODULE cpu.size; |
| `); |
| const result = await this.engine.query(` |
| SELECT cpu, cpu_guess_core_type(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({ |
| uri: `perfetto.CpuSlices#cpu${cpu}`, |
| trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, |
| name, |
| trackGroup: SCROLLING_TRACK_GROUP, |
| }); |
| } |
| } |
| |
| async addCpuFreqTracks(engine: EngineProxy): Promise<void> { |
| const cpus = await this.engine.getCpus(); |
| |
| 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) { |
| this.tracksToAdd.push({ |
| uri: `perfetto.CpuFreq#${cpu}`, |
| trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, |
| name: `Cpu ${cpu} Frequency`, |
| trackGroup: SCROLLING_TRACK_GROUP, |
| }); |
| } |
| } |
| } |
| |
| async addGlobalAsyncTracks(engine: EngineProxy): Promise<void> { |
| const rawGlobalAsyncTracks = await engine.query(` |
| with global_tracks_grouped as ( |
| select distinct t.parent_id, t.name |
| from track t |
| join _slice_track_summary using (id) |
| where t.type in ('track', 'gpu_track', 'cpu_track') |
| ) |
| select |
| t.name as name, |
| t.parent_id as parentId, |
| p.name as parentName |
| 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, |
| parentId: NUM_NULL, |
| parentName: STR_NULL, |
| }); |
| |
| const parentIdToGroupId = new Map<number, string>(); |
| 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 = getTrackName({name: rawName, kind}); |
| const parentTrackId = it.parentId; |
| let trackGroup = SCROLLING_TRACK_GROUP; |
| |
| if (parentTrackId !== null) { |
| const groupId = parentIdToGroupId.get(parentTrackId); |
| if (groupId === undefined) { |
| trackGroup = uuidv4(); |
| parentIdToGroupId.set(parentTrackId, trackGroup); |
| |
| const parentName = getTrackName({name: rawParentName, kind}); |
| this.addTrackGroupActions.push( |
| Actions.addTrackGroup({ |
| name: parentName, |
| id: trackGroup, |
| collapsed: true, |
| }), |
| ); |
| } else { |
| trackGroup = groupId; |
| } |
| } |
| |
| const track: AddTrackArgs = { |
| uri: `perfetto.AsyncSlices#${rawName}.${it.parentId}`, |
| trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, |
| trackGroup, |
| name, |
| }; |
| |
| this.tracksToAdd.push(track); |
| } |
| } |
| |
| async addGpuFreqTracks(engine: EngineProxy): Promise<void> { |
| const numGpus = await this.engine.getNumberOfGpus(); |
| 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 * |
| from gpu_counter_track |
| where name = 'gpufreq' and gpu_id = ${gpu} |
| limit 1; |
| `); |
| if (freqExistsResult.numRows() > 0) { |
| this.tracksToAdd.push({ |
| uri: `perfetto.Counter#gpu_freq${gpu}`, |
| name: `Gpu ${gpu} Frequency`, |
| trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, |
| trackGroup: SCROLLING_TRACK_GROUP, |
| }); |
| } |
| } |
| } |
| |
| async addCpuFreqLimitCounterTracks(engine: EngineProxy): Promise<void> { |
| const cpuFreqLimitCounterTracksSql = ` |
| select name, id |
| from cpu_counter_track |
| where name glob "Cpu * Freq Limit" |
| order by name asc |
| `; |
| |
| this.addCpuCounterTracks(engine, cpuFreqLimitCounterTracksSql); |
| } |
| |
| 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 addCpuPerfCounterTracksSql = ` |
| 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 |
| `; |
| this.addCpuCounterTracks(engine, addCpuPerfCounterTracksSql); |
| } |
| |
| async addCpuCounterTracks(engine: EngineProxy, sql: string): Promise<void> { |
| const result = await engine.query(sql); |
| |
| const it = result.iter({ |
| name: STR, |
| id: NUM, |
| }); |
| |
| for (; it.valid(); it.next()) { |
| const name = it.name; |
| const trackId = it.id; |
| this.tracksToAdd.push({ |
| uri: `perfetto.Counter#cpu${trackId}`, |
| name, |
| trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, |
| trackGroup: SCROLLING_TRACK_GROUP, |
| }); |
| } |
| } |
| |
| 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 summaryTrackKey = uuidv4(); |
| let foundSummary = false; |
| |
| for (const track of ionTracks) { |
| if ( |
| !foundSummary && |
| [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name) |
| ) { |
| foundSummary = true; |
| track.key = summaryTrackKey; |
| track.trackGroup = undefined; |
| } else { |
| track.trackGroup = id; |
| } |
| } |
| |
| const addGroup = Actions.addTrackGroup({ |
| summaryTrackKey, |
| 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 addGroup = Actions.addTrackGroup({ |
| name: groupName, |
| id: value, |
| 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 = 'Chunk size: ' + size; |
| track.trackGroup = devMap.get(groupName); |
| } |
| |
| for (const [key, value] of devMap) { |
| const groupName = key; |
| const addGroup = Actions.addTrackGroup({ |
| name: groupName, |
| id: value, |
| collapsed: true, |
| }); |
| this.addTrackGroupActions.push(addGroup); |
| } |
| } |
| |
| async groupFrequencyTracks(groupName: string): Promise<void> { |
| let groupUuid = undefined; |
| for (const track of this.tracksToAdd) { |
| // Group all the frequency tracks together (except the CPU and GPU |
| // frequency ones). |
| if ( |
| track.name.endsWith('Frequency') && |
| !track.name.startsWith('Cpu') && |
| !track.name.startsWith('Gpu') |
| ) { |
| if ( |
| track.trackGroup !== undefined && |
| track.trackGroup !== SCROLLING_TRACK_GROUP |
| ) { |
| continue; |
| } |
| if (groupUuid === undefined) { |
| groupUuid = uuidv4(); |
| } |
| track.trackGroup = groupUuid; |
| } |
| } |
| |
| if (groupUuid !== undefined) { |
| const addGroup = Actions.addTrackGroup({ |
| name: groupName, |
| id: groupUuid, |
| collapsed: true, |
| }); |
| this.addTrackGroupActions.push(addGroup); |
| } |
| } |
| |
| async groupMiscNonAllowlistedTracks(groupName: string): Promise<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$'), |
| ]; |
| |
| let groupUuid = undefined; |
| for (const track of this.tracksToAdd) { |
| if ( |
| track.trackGroup !== undefined && |
| track.trackGroup !== SCROLLING_TRACK_GROUP |
| ) { |
| continue; |
| } |
| let allowlisted = false; |
| for (const regex of ALLOWLIST_REGEXES) { |
| allowlisted = allowlisted || regex.test(track.name); |
| } |
| if (allowlisted) { |
| continue; |
| } |
| if (groupUuid === undefined) { |
| groupUuid = uuidv4(); |
| } |
| track.trackGroup = groupUuid; |
| } |
| |
| if (groupUuid !== undefined) { |
| const addGroup = Actions.addTrackGroup({ |
| name: groupName, |
| id: groupUuid, |
| 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 ( |
| track.trackGroup !== undefined && |
| track.trackGroup !== SCROLLING_TRACK_GROUP |
| ) { |
| continue; |
| } |
| if (groupUuid === undefined) { |
| groupUuid = uuidv4(); |
| } |
| track.trackGroup = groupUuid; |
| } |
| } |
| |
| if (groupUuid !== undefined) { |
| const addGroup = Actions.addTrackGroup({ |
| name: groupName, |
| id: groupUuid, |
| collapsed: true, |
| }); |
| 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; |
| summaryTrackKey: 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 summaryTrackKey = 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(); |
| summaryTrackKey = uuidv4(); |
| groupNameToIds.set(groupName, { |
| id: trackGroupId, |
| summaryTrackKey, |
| }); |
| } |
| } |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.Annotation#${id}`, |
| key: summaryTrackKey, |
| name, |
| trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, |
| trackGroup: trackGroupId, |
| }); |
| } |
| |
| for (const [groupName, groupIds] of groupNameToIds) { |
| const addGroup = Actions.addTrackGroup({ |
| summaryTrackKey: groupIds.summaryTrackKey, |
| name: groupName, |
| id: groupIds.id, |
| collapsed: true, |
| }); |
| this.addTrackGroupActions.push(addGroup); |
| } |
| |
| const counterResult = await engine.query(` |
| SELECT id, name, upid FROM annotation_counter_track |
| `); |
| |
| const counterIt = counterResult.iter({ |
| id: NUM, |
| name: STR, |
| upid: NUM, |
| }); |
| |
| for (; counterIt.valid(); counterIt.next()) { |
| const id = counterIt.id; |
| const name = counterIt.name; |
| const upid = counterIt.upid; |
| this.tracksToAdd.push({ |
| uri: `perfetto.Annotation#counter${id}`, |
| name, |
| trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, |
| trackGroup: |
| upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid), |
| }); |
| } |
| } |
| |
| async addThreadStateTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select |
| utid, |
| upid, |
| tid, |
| thread.name as threadName |
| from thread |
| join _sched_summary using (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 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; |
| const name = getTrackName({ |
| utid, |
| tid, |
| threadName, |
| kind: THREAD_STATE_TRACK_KIND, |
| }); |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.ThreadState#${utid}`, |
| name, |
| trackGroup: uuid, |
| trackSortKey: { |
| utid, |
| priority, |
| }, |
| }); |
| } |
| } |
| |
| async addThreadCpuSampleTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| with thread_cpu_sample as ( |
| select distinct utid |
| from cpu_profile_stack_sample |
| where utid != 0 |
| ) |
| select |
| utid, |
| tid, |
| upid, |
| thread.name as threadName |
| from thread_cpu_sample |
| join thread using(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({ |
| uri: `perfetto.CpuProfile#${utid}`, |
| trackSortKey: { |
| utid, |
| priority: InThreadTrackSortKey.CPU_STACK_SAMPLES_TRACK, |
| }, |
| name: `${threadName} (CPU Stack Samples)`, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| 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 |
| from thread_counter_track |
| join thread using(utid) |
| 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, |
| trackId: NUM, |
| }); |
| 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 name = getTrackName({ |
| name: trackName, |
| utid, |
| tid, |
| kind: COUNTER_TRACK_KIND, |
| threadName, |
| threadTrack: true, |
| }); |
| this.tracksToAdd.push({ |
| uri: `perfetto.Counter#thread${trackId}`, |
| name, |
| trackSortKey: { |
| utid, |
| priority: InThreadTrackSortKey.ORDINARY, |
| }, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| async addProcessAsyncSliceTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select |
| upid, |
| t.name as trackName, |
| t.track_ids as trackIds, |
| process.name as processName, |
| process.pid as pid |
| from _process_track_summary_by_upid_and_name t |
| join process using(upid) |
| where t.name is null or t.name not glob "* Timeline" |
| `); |
| |
| const it = result.iter({ |
| upid: NUM, |
| trackName: STR_NULL, |
| trackIds: STR, |
| processName: STR_NULL, |
| pid: NUM_NULL, |
| }); |
| for (; it.valid(); it.next()) { |
| const upid = it.upid; |
| const trackName = it.trackName; |
| const rawTrackIds = it.trackIds; |
| const processName = it.processName; |
| const pid = it.pid; |
| |
| const uuid = this.getUuid(null, upid); |
| const name = getTrackName({ |
| name: trackName, |
| upid, |
| pid, |
| processName, |
| kind: ASYNC_SLICE_TRACK_KIND, |
| }); |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`, |
| name, |
| trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| async addUserAsyncSliceTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select |
| t.name as name, |
| t.uid as uid, |
| package_list.package_name as packageName |
| from _uid_track_track_summary_by_uid_and_name t |
| join package_list using (uid) |
| `); |
| |
| const it = result.iter({ |
| name: STR_NULL, |
| uid: NUM_NULL, |
| packageName: STR_NULL, |
| }); |
| |
| // Map From [name] -> [uuid, key] |
| const groupMap = new Map<string, string>(); |
| |
| for (; it.valid(); it.next()) { |
| if (it.name == null || it.uid == null) { |
| continue; |
| } |
| const rawName = it.name; |
| const uid = it.uid === null ? undefined : it.uid; |
| const userName = it.packageName === null ? `UID: ${uid}` : it.packageName; |
| |
| const groupUuid = `uid-track-group${rawName}`; |
| if (groupMap.get(rawName) === undefined) { |
| groupMap.set(rawName, groupUuid); |
| } |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.AsyncSlices#${rawName}.${uid}`, |
| name: userName, |
| trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, |
| trackGroup: groupUuid, |
| }); |
| } |
| |
| for (const [name, groupUuid] of groupMap) { |
| const addGroup = Actions.addTrackGroup({ |
| name: name, |
| id: groupUuid, |
| collapsed: true, |
| }); |
| this.addTrackGroupActions.push(addGroup); |
| } |
| } |
| |
| async addActualFramesTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select |
| upid, |
| t.name as trackName, |
| process.name as processName, |
| process.pid as pid |
| from _process_track_summary_by_upid_and_name t |
| join process using(upid) |
| where t.name = "Actual Timeline" |
| `); |
| |
| const it = result.iter({ |
| upid: NUM, |
| trackName: STR_NULL, |
| processName: STR_NULL, |
| pid: NUM_NULL, |
| }); |
| for (; it.valid(); it.next()) { |
| const upid = it.upid; |
| const trackName = it.trackName; |
| const processName = it.processName; |
| const pid = it.pid; |
| |
| const uuid = this.getUuid(null, upid); |
| const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND; |
| const name = getTrackName({ |
| name: trackName, |
| upid, |
| pid, |
| processName, |
| kind, |
| }); |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.ActualFrames#${upid}`, |
| name, |
| trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| async addExpectedFramesTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select |
| upid, |
| t.name as trackName, |
| process.name as processName, |
| process.pid as pid |
| from _process_track_summary_by_upid_and_name t |
| join process using(upid) |
| where t.name = "Expected Timeline" |
| `); |
| |
| const it = result.iter({ |
| upid: NUM, |
| trackName: STR_NULL, |
| processName: STR_NULL, |
| pid: NUM_NULL, |
| }); |
| |
| for (; it.valid(); it.next()) { |
| const upid = it.upid; |
| const trackName = it.trackName; |
| const processName = it.processName; |
| const pid = it.pid; |
| |
| const uuid = this.getUuid(null, upid); |
| const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND; |
| const name = getTrackName({ |
| name: trackName, |
| upid, |
| pid, |
| processName, |
| kind, |
| }); |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.ExpectedFrames#${upid}`, |
| name, |
| trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| 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, |
| thread.upid as upid |
| from thread_track |
| join _slice_track_summary using (id) |
| join thread using(utid) |
| `); |
| |
| const it = result.iter({ |
| utid: NUM, |
| trackId: NUM, |
| trackName: STR_NULL, |
| isDefaultTrackForScope: NUM_NULL, |
| tid: NUM_NULL, |
| threadName: STR_NULL, |
| upid: NUM_NULL, |
| }); |
| for (; it.valid(); it.next()) { |
| const utid = it.utid; |
| const trackId = it.trackId; |
| const trackName = it.trackName; |
| // Note that !!null === false. |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| const isDefaultTrackForScope = !!it.isDefaultTrackForScope; |
| const tid = it.tid; |
| const threadName = it.threadName; |
| const upid = it.upid; |
| |
| const uuid = this.getUuid(utid, upid); |
| |
| const kind = SLICE_TRACK_KIND; |
| const name = getTrackName({name: trackName, utid, tid, threadName, kind}); |
| |
| this.tracksToAdd.push({ |
| uri: `perfetto.ChromeSlices#${trackId}`, |
| name, |
| trackGroup: uuid, |
| trackSortKey: { |
| utid, |
| priority: isDefaultTrackForScope |
| ? InThreadTrackSortKey.DEFAULT_TRACK |
| : InThreadTrackSortKey.ORDINARY, |
| }, |
| }); |
| } |
| } |
| |
| 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 |
| 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, |
| }); |
| 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(null, upid); |
| const name = getTrackName({ |
| name: trackName, |
| upid, |
| pid, |
| kind: COUNTER_TRACK_KIND, |
| processName, |
| }); |
| this.tracksToAdd.push({ |
| uri: `perfetto.Counter#process${trackId}`, |
| name, |
| trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack( |
| upid, |
| trackName || undefined, |
| ), |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| async addProcessHeapProfileTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select upid |
| from _process_available_info_summary |
| where allocation_count > 0 or graph_object_count > 0 |
| `); |
| for (const it = result.iter({upid: NUM}); it.valid(); it.next()) { |
| const upid = it.upid; |
| const uuid = this.getUuid(null, upid); |
| this.tracksToAdd.push({ |
| uri: `perfetto.HeapProfile#${upid}`, |
| trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK, |
| name: `Heap Profile`, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| async addProcessPerfSamplesTracks(engine: EngineProxy): Promise<void> { |
| const result = await engine.query(` |
| select upid, pid |
| from _process_available_info_summary |
| join process using (upid) |
| where perf_sample_count > 0 |
| `); |
| 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(null, upid); |
| this.tracksToAdd.push({ |
| uri: `perfetto.PerfSamplesProfile#${upid}`, |
| trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK, |
| name: `Callstacks ${pid}`, |
| trackGroup: uuid, |
| }); |
| } |
| } |
| |
| getUuidUnchecked(utid: number | null, upid: number | null) { |
| return upid === null |
| ? this.utidToUuid.get(utid!) |
| : this.upidToUuid.get(upid); |
| } |
| |
| getUuid(utid: number | null, upid: number | null) { |
| return assertExists(this.getUuidUnchecked(utid, upid)); |
| } |
| |
| getOrCreateUuid(utid: number | null, 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 summaryTrackKey = uuidv4(); |
| this.tracksToAdd.push({ |
| uri: 'perfetto.ProcessSummary#kernel', |
| key: summaryTrackKey, |
| trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK, |
| name: `Kernel thread summary`, |
| }); |
| const addTrackGroup = Actions.addTrackGroup({ |
| summaryTrackKey, |
| 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(` |
| 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 |
| upid, |
| null as utid, |
| pid, |
| null as tid, |
| processName, |
| null as threadName, |
| sumRunningDur > 0 as hasSched, |
| heapProfileAllocationCount > 0 |
| or heapGraphObjectCount > 0 as hasHeapInfo, |
| ifnull(chromeProcessLabels, '') as chromeProcessLabels |
| 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 |
| null, |
| utid, |
| null as pid, |
| tid, |
| null as processName, |
| threadName, |
| sumRunningDur > 0 as hasSched, |
| 0 as hasHeapInfo, |
| '' as chromeProcessLabels |
| from threadGroups |
| order by |
| perfSampleCount desc, |
| sumRunningDur desc, |
| sliceCount desc, |
| threadName asc, |
| utid asc |
| ) |
| `); |
| |
| const it = result.iter({ |
| upid: NUM_NULL, |
| utid: NUM_NULL, |
| pid: NUM_NULL, |
| tid: NUM_NULL, |
| processName: STR_NULL, |
| threadName: STR_NULL, |
| hasSched: NUM_NULL, |
| hasHeapInfo: NUM_NULL, |
| chromeProcessLabels: STR, |
| }); |
| for (; it.valid(); it.next()) { |
| const utid = it.utid; |
| const upid = it.upid; |
| const pid = it.pid; |
| const tid = it.tid; |
| const threadName = it.threadName; |
| const processName = it.processName; |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| const hasSched = !!it.hasSched; |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| const hasHeapInfo = !!it.hasHeapInfo; |
| |
| const summaryTrackKey = uuidv4(); |
| const type = hasSched ? 'schedule' : 'summary'; |
| const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`; |
| |
| // If previous groupings (e.g. kernel threads) picked up there tracks, |
| // don't try to regroup them. |
| const pUuid = |
| upid === null ? this.utidToUuid.get(utid!) : this.upidToUuid.get(upid); |
| if (pUuid !== undefined) { |
| continue; |
| } |
| |
| this.tracksToAdd.push({ |
| uri, |
| key: summaryTrackKey, |
| trackSortKey: hasSched |
| ? PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK |
| : PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK, |
| name: `${upid === null ? tid : pid} summary`, |
| labels: it.chromeProcessLabels.split(','), |
| }); |
| |
| const name = getTrackName({ |
| utid, |
| processName, |
| pid, |
| threadName, |
| tid, |
| upid, |
| }); |
| const addTrackGroup = Actions.addTrackGroup({ |
| summaryTrackKey, |
| name, |
| id: this.getOrCreateUuid(utid, upid), |
| // Perf profiling tracks remain collapsed, otherwise we would have too |
| // many expanded process tracks for some perf traces, leading to |
| // jankyness. |
| collapsed: !hasHeapInfo, |
| }); |
| this.addTrackGroupActions.push(addTrackGroup); |
| } |
| } |
| |
| private async computeThreadOrderingMetadata(): Promise<UtidToTrackSortKey> { |
| const result = await this.engine.query(` |
| select |
| utid, |
| tid, |
| (select pid from process p where t.upid = p.upid) as pid, |
| t.name as threadName |
| from thread t`); |
| |
| 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; |
| } |
| |
| addPluginTracks(): void { |
| const groupNameToUuid = new Map<string, string>(); |
| const tracks = globals.trackManager.findPotentialTracks(); |
| |
| for (const info of tracks) { |
| const groupName = info.groupName; |
| |
| let groupUuid = SCROLLING_TRACK_GROUP; |
| if (groupName) { |
| const uuid = groupNameToUuid.get(groupName); |
| if (uuid) { |
| groupUuid = uuid; |
| } else { |
| // Add the group |
| groupUuid = uuidv4(); |
| const addGroup = Actions.addTrackGroup({ |
| name: groupName, |
| id: groupUuid, |
| collapsed: true, |
| fixedOrdering: true, |
| }); |
| this.addTrackGroupActions.push(addGroup); |
| |
| // Add group to the map |
| groupNameToUuid.set(groupName, groupUuid); |
| } |
| } |
| |
| this.tracksToAdd.push({ |
| uri: info.uri, |
| name: info.displayName, |
| // TODO(hjd): Fix how sorting works. Plugins should expose |
| // 'sort keys' which the user can use to choose a sort order. |
| trackSortKey: info.sortKey ?? PrimaryTrackSortKey.ORDINARY_TRACK, |
| trackGroup: groupUuid, |
| params: info.params, |
| }); |
| } |
| } |
| |
| async addScrollJankPluginTracks(): Promise<void> { |
| if (ENABLE_SCROLL_JANK_PLUGIN_V2.get()) { |
| const result = await getScrollJankTracks(this.engine); |
| this.tracksToAdd = this.tracksToAdd.concat(result.tracks.tracksToAdd); |
| this.addTrackGroupActions.push(result.addTrackGroup); |
| } |
| } |
| |
| async decideTracks(): Promise<DeferredAction[]> { |
| { |
| const result = screenshotDecideTracks(this.engine); |
| if (result !== null) { |
| const {tracksToAdd} = await result; |
| this.tracksToAdd.push(...tracksToAdd); |
| } |
| } |
| |
| // Add first the global tracks that don't require per-process track groups. |
| await this.addScrollJankPluginTracks(); |
| await this.addCpuSchedulingTracks(); |
| 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.addCpuFreqLimitCounterTracks( |
| this.engine.getProxy('TrackDecider::addCpuFreqLimitCounterTracks'), |
| ); |
| await this.addCpuPerfCounterTracks( |
| this.engine.getProxy('TrackDecider::addCpuPerfCounterTracks'), |
| ); |
| 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.groupTracksByRegex(UFS_CMD_TAG_REGEX, UFS_CMD_TAG_GROUP); |
| 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, |
| ); |
| await this.groupTracksByRegex(UCLAMP_REGEX, UCLAMP_GROUP); |
| await this.groupFrequencyTracks(FREQUENCY_GROUP); |
| await this.groupTracksByRegex(POWER_RAILS_REGEX, POWER_RAILS_GROUP); |
| await this.groupTracksByRegex(TEMPERATURE_REGEX, TEMPERATURE_GROUP); |
| await this.groupTracksByRegex(IRQ_REGEX, IRQ_GROUP); |
| await this.groupTracksByRegex(CHROME_TRACK_REGEX, CHROME_TRACK_GROUP); |
| await 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" |
| await this.addUserAsyncSliceTracks( |
| this.engine.getProxy('TrackDecider::addUserAsyncSliceTracks'), |
| ); |
| |
| // 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::addProcessAsyncSliceTracks'), |
| ); |
| 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'), |
| ); |
| |
| // 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}), |
| ); |
| |
| 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, |
| }); |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| 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; |
| } |
| } |
| } |