diff options
Diffstat (limited to 'ui/src/controller')
-rw-r--r-- | ui/src/controller/aggregation/slice_aggregation_controller.ts | 4 | ||||
-rw-r--r-- | ui/src/controller/app_controller.ts | 5 | ||||
-rw-r--r-- | ui/src/controller/cpu_profile_controller.ts | 7 | ||||
-rw-r--r-- | ui/src/controller/flamegraph_controller.ts | 690 | ||||
-rw-r--r-- | ui/src/controller/flow_events_controller.ts | 12 | ||||
-rw-r--r-- | ui/src/controller/permalink_controller.ts | 269 | ||||
-rw-r--r-- | ui/src/controller/search_controller.ts | 2 | ||||
-rw-r--r-- | ui/src/controller/selection_controller.ts | 30 | ||||
-rw-r--r-- | ui/src/controller/trace_controller.ts | 99 | ||||
-rw-r--r-- | ui/src/controller/track_decider.ts | 103 |
10 files changed, 165 insertions, 1056 deletions
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts index 89ba573bb..89cdfb1ab 100644 --- a/ui/src/controller/aggregation/slice_aggregation_controller.ts +++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts @@ -17,7 +17,7 @@ import {Area, Sorting} from '../../common/state'; import {globals} from '../../frontend/globals'; import {Engine} from '../../trace_processor/engine'; import {ASYNC_SLICE_TRACK_KIND} from '../../core_plugins/async_slices'; -import {SLICE_TRACK_KIND} from '../../core_plugins/chrome_slices/chrome_slice_track'; +import {THREAD_SLICE_TRACK_KIND} from '../../core_plugins/thread_slice/thread_slice_track'; import {AggregationController} from './aggregation_controller'; @@ -28,7 +28,7 @@ export function getSelectedTrackKeys(area: Area): number[] { // Track will be undefined for track groups. if (track?.uri !== undefined) { const trackInfo = globals.trackManager.resolveTrackInfo(track.uri); - if (trackInfo?.kind === SLICE_TRACK_KIND) { + if (trackInfo?.kind === THREAD_SLICE_TRACK_KIND) { trackInfo.trackIds && selectedTrackKeys.push(...trackInfo.trackIds); } if (trackInfo?.kind === ASYNC_SLICE_TRACK_KIND) { diff --git a/ui/src/controller/app_controller.ts b/ui/src/controller/app_controller.ts index 4c8ad85bf..7d4d7cd9c 100644 --- a/ui/src/controller/app_controller.ts +++ b/ui/src/controller/app_controller.ts @@ -16,7 +16,6 @@ import {RECORDING_V2_FLAG} from '../core/feature_flags'; import {globals} from '../frontend/globals'; import {Child, Controller, ControllerInitializerAny} from './controller'; -import {PermalinkController} from './permalink_controller'; import {RecordController} from './record_controller'; import {TraceController} from './trace_controller'; @@ -40,9 +39,7 @@ export class AppController extends Controller<'main'> { // - An internal promise of a nested controller being resolved and manually // re-triggering the controllers. run() { - const childControllers: ControllerInitializerAny[] = [ - Child('permalink', PermalinkController, {}), - ]; + const childControllers: ControllerInitializerAny[] = []; if (!RECORDING_V2_FLAG.get()) { childControllers.push( Child('record', RecordController, {extensionPort: this.extensionPort}), diff --git a/ui/src/controller/cpu_profile_controller.ts b/ui/src/controller/cpu_profile_controller.ts index fd410bf2c..38d8cbfa7 100644 --- a/ui/src/controller/cpu_profile_controller.ts +++ b/ui/src/controller/cpu_profile_controller.ts @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { - CallsiteInfo, - CpuProfileSampleSelection, - getLegacySelection, -} from '../common/state'; +import {CallsiteInfo} from '../common/flamegraph_util'; +import {CpuProfileSampleSelection, getLegacySelection} from '../common/state'; import {CpuProfileDetails, globals} from '../frontend/globals'; import {publishCpuProfileDetails} from '../frontend/publish'; import {Engine} from '../trace_processor/engine'; diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts deleted file mode 100644 index 14436f771..000000000 --- a/ui/src/controller/flamegraph_controller.ts +++ /dev/null @@ -1,690 +0,0 @@ -// Copyright (C) 2019 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 {Duration, time} from '../base/time'; -import {exists} from '../base/utils'; -import {Actions} from '../common/actions'; -import { - defaultViewingOption, - expandCallsites, - findRootSize, - mergeCallsites, -} from '../common/flamegraph_util'; -import { - CallsiteInfo, - FlamegraphState, - FlamegraphStateViewingOption, - isHeapGraphDominatorTreeViewingOption, - ProfileType, -} from '../common/state'; -import {FlamegraphDetails, globals} from '../frontend/globals'; -import {publishFlamegraphDetails} from '../frontend/publish'; -import {Engine} from '../trace_processor/engine'; -import {NUM, STR} from '../trace_processor/query_result'; -import {PERF_SAMPLES_PROFILE_TRACK_KIND} from '../core_plugins/perf_samples_profile'; - -import {AreaSelectionHandler} from './area_selection_handler'; -import {Controller} from './controller'; - -export function profileType(s: string): ProfileType { - if (isProfileType(s)) { - return s; - } - if (s.startsWith('heap_profile')) { - return ProfileType.HEAP_PROFILE; - } - throw new Error('Unknown type ${s}'); -} - -function isProfileType(s: string): s is ProfileType { - return Object.values(ProfileType).includes(s as ProfileType); -} - -function getFlamegraphType(type: ProfileType) { - switch (type) { - case ProfileType.HEAP_PROFILE: - case ProfileType.MIXED_HEAP_PROFILE: - case ProfileType.NATIVE_HEAP_PROFILE: - case ProfileType.JAVA_HEAP_SAMPLES: - return 'native'; - case ProfileType.JAVA_HEAP_GRAPH: - return 'graph'; - case ProfileType.PERF_SAMPLE: - return 'perf'; - default: - const exhaustiveCheck: never = type; - throw new Error(`Unhandled case: ${exhaustiveCheck}`); - } -} - -export interface FlamegraphControllerArgs { - engine: Engine; -} -const MIN_PIXEL_DISPLAYED = 1; - -class TablesCache { - private engine: Engine; - private cache: Map<string, string>; - private prefix: string; - private tableId: number; - private cacheSizeLimit: number; - - constructor(engine: Engine, prefix: string) { - this.engine = engine; - this.cache = new Map<string, string>(); - this.prefix = prefix; - this.tableId = 0; - this.cacheSizeLimit = 10; - } - - async getTableName(query: string): Promise<string> { - let tableName = this.cache.get(query); - if (tableName === undefined) { - // TODO(hjd): This should be LRU. - if (this.cache.size > this.cacheSizeLimit) { - for (const name of this.cache.values()) { - await this.engine.query(`drop table ${name}`); - } - this.cache.clear(); - } - tableName = `${this.prefix}_${this.tableId++}`; - await this.engine.query( - `create temp table if not exists ${tableName} as ${query}`, - ); - this.cache.set(query, tableName); - } - return tableName; - } - - hasQuery(query: string): boolean { - return this.cache.get(query) !== undefined; - } -} - -export class FlamegraphController extends Controller<'main'> { - private flamegraphDatasets: Map<string, CallsiteInfo[]> = new Map(); - private lastSelectedFlamegraphState?: FlamegraphState; - private requestingData = false; - private queuedRequest = false; - private flamegraphDetails: FlamegraphDetails = {}; - private areaSelectionHandler: AreaSelectionHandler; - private cache: TablesCache; - - constructor(private args: FlamegraphControllerArgs) { - super('main'); - this.cache = new TablesCache(args.engine, 'grouped_callsites'); - this.areaSelectionHandler = new AreaSelectionHandler(); - } - - run() { - const [hasAreaChanged, area] = this.areaSelectionHandler.getAreaChange(); - if (hasAreaChanged) { - const upids = []; - if (!area) { - this.checkCompletionAndPublishFlamegraph({ - ...globals.flamegraphDetails, - isInAreaSelection: false, - }); - return; - } - for (const trackId of area.tracks) { - const track = globals.state.tracks[trackId]; - if (track?.uri) { - const trackInfo = globals.trackManager.resolveTrackInfo(track.uri); - if (trackInfo?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND) { - exists(trackInfo.upid) && upids.push(trackInfo.upid); - } - } - } - if (upids.length === 0) { - this.checkCompletionAndPublishFlamegraph({ - ...globals.flamegraphDetails, - isInAreaSelection: false, - }); - return; - } - globals.dispatch( - Actions.openFlamegraph({ - upids, - start: area.start, - end: area.end, - type: ProfileType.PERF_SAMPLE, - viewingOption: defaultViewingOption(ProfileType.PERF_SAMPLE), - }), - ); - } - const selection = globals.state.currentFlamegraphState; - if (!selection || !this.shouldRequestData(selection)) { - return; - } - if (this.requestingData) { - this.queuedRequest = true; - return; - } - this.requestingData = true; - - this.assembleFlamegraphDetails(selection, area !== undefined); - } - - private async assembleFlamegraphDetails( - selection: FlamegraphState, - isInAreaSelection: boolean, - ) { - const selectedFlamegraphState = {...selection}; - const flamegraphMetadata = await this.getFlamegraphMetadata( - selection.type, - selectedFlamegraphState.start, - selectedFlamegraphState.end, - selectedFlamegraphState.upids, - ); - if (flamegraphMetadata !== undefined) { - Object.assign(this.flamegraphDetails, flamegraphMetadata); - } - - // TODO(hjd): Clean this up. - if ( - this.lastSelectedFlamegraphState && - this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex - ) { - this.flamegraphDatasets.clear(); - } - - this.lastSelectedFlamegraphState = {...selection}; - - const expandedCallsite = - selectedFlamegraphState.expandedCallsiteByViewingOption[ - selectedFlamegraphState.viewingOption - ]; - const expandedId = expandedCallsite ? expandedCallsite.id : -1; - const rootSize = expandedCallsite?.totalSize; - - const key = `${selectedFlamegraphState.upids};${selectedFlamegraphState.start};${selectedFlamegraphState.end}`; - - try { - const flamegraphData = await this.getFlamegraphData( - key, - /* eslint-disable @typescript-eslint/strict-boolean-expressions */ - selectedFlamegraphState.viewingOption /* eslint-enable */ - ? selectedFlamegraphState.viewingOption - : defaultViewingOption(selectedFlamegraphState.type), - selection.start, - selection.end, - selectedFlamegraphState.upids, - selectedFlamegraphState.type, - selectedFlamegraphState.focusRegex, - ); - if ( - flamegraphData !== undefined && - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - selection && - selection.kind === selectedFlamegraphState.kind && - selection.start === selectedFlamegraphState.start && - selection.end === selectedFlamegraphState.end - ) { - const expandedFlamegraphData = expandCallsites( - flamegraphData, - expandedId, - ); - this.prepareAndMergeCallsites( - expandedFlamegraphData, - this.lastSelectedFlamegraphState.viewingOption, - isInAreaSelection, - rootSize, - expandedCallsite, - ); - } - } finally { - this.requestingData = false; - if (this.queuedRequest) { - this.queuedRequest = false; - this.run(); - } - } - } - - private shouldRequestData(selection: FlamegraphState) { - return ( - selection.kind === 'FLAMEGRAPH_STATE' && - (this.lastSelectedFlamegraphState === undefined || - this.lastSelectedFlamegraphState.start !== selection.start || - this.lastSelectedFlamegraphState.end !== selection.end || - this.lastSelectedFlamegraphState.type !== selection.type || - !FlamegraphController.areArraysEqual( - this.lastSelectedFlamegraphState.upids, - selection.upids, - ) || - this.lastSelectedFlamegraphState.viewingOption !== - selection.viewingOption || - this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex || - this.lastSelectedFlamegraphState.expandedCallsiteByViewingOption[ - selection.viewingOption - ] !== - selection.expandedCallsiteByViewingOption[selection.viewingOption]) - ); - } - - private prepareAndMergeCallsites( - flamegraphData: CallsiteInfo[], - viewingOption: FlamegraphStateViewingOption, - isInAreaSelection: boolean, - rootSize?: number, - expandedCallsite?: CallsiteInfo, - ) { - this.flamegraphDetails.flamegraph = mergeCallsites( - flamegraphData, - this.getMinSizeDisplayed(flamegraphData, rootSize), - ); - this.flamegraphDetails.expandedCallsite = expandedCallsite; - this.flamegraphDetails.viewingOption = viewingOption; - this.flamegraphDetails.isInAreaSelection = isInAreaSelection; - this.checkCompletionAndPublishFlamegraph(this.flamegraphDetails); - } - - private async checkCompletionAndPublishFlamegraph( - flamegraphDetails: FlamegraphDetails, - ) { - flamegraphDetails.graphIncomplete = - ( - await this.args.engine.query(`select value from stats - where severity = 'error' and name = 'heap_graph_non_finalized_graph'`) - ).firstRow({value: NUM}).value > 0; - flamegraphDetails.graphLoading = false; - publishFlamegraphDetails(flamegraphDetails); - } - - async getFlamegraphData( - baseKey: string, - viewingOption: FlamegraphStateViewingOption, - start: time, - end: time, - upids: number[], - type: ProfileType, - focusRegex: string, - ): Promise<CallsiteInfo[]> { - let currentData: CallsiteInfo[]; - const key = `${baseKey}-${viewingOption}`; - if (this.flamegraphDatasets.has(key)) { - currentData = this.flamegraphDatasets.get(key)!; - } else { - publishFlamegraphDetails({ - ...globals.flamegraphDetails, - graphLoading: true, - }); - // Collecting data for drawing flamegraph for selected profile. - // Data needs to be in following format: - // id, name, parent_id, depth, total_size - const tableName = await this.prepareViewsAndTables( - start, - end, - upids, - type, - focusRegex, - viewingOption, - ); - currentData = await this.getFlamegraphDataFromTables( - tableName, - viewingOption, - focusRegex, - ); - this.flamegraphDatasets.set(key, currentData); - } - return currentData; - } - - async getFlamegraphDataFromTables( - tableName: string, - viewingOption: FlamegraphStateViewingOption, - focusRegex: string, - ) { - let orderBy = ''; - let totalColumnName: - | 'cumulativeSize' - | 'cumulativeAllocSize' - | 'cumulativeCount' - | 'cumulativeAllocCount' = 'cumulativeSize'; - let selfColumnName: 'size' | 'count' = 'size'; - // TODO(fmayer): Improve performance so this is no longer necessary. - // Alternatively consider collapsing frames of the same label. - const maxDepth = 100; - switch (viewingOption) { - case FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY: - orderBy = `where cumulative_alloc_size > 0 and depth < ${maxDepth} order by depth, parent_id, - cumulative_alloc_size desc, name`; - totalColumnName = 'cumulativeAllocSize'; - selfColumnName = 'size'; - break; - case FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY: - orderBy = `where cumulative_count > 0 and depth < ${maxDepth} order by depth, parent_id, - cumulative_count desc, name`; - totalColumnName = 'cumulativeCount'; - selfColumnName = 'count'; - break; - case FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY: - orderBy = `where cumulative_alloc_count > 0 and depth < ${maxDepth} order by depth, parent_id, - cumulative_alloc_count desc, name`; - totalColumnName = 'cumulativeAllocCount'; - selfColumnName = 'count'; - break; - case FlamegraphStateViewingOption.PERF_SAMPLES_KEY: - case FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY: - orderBy = `where cumulative_size > 0 and depth < ${maxDepth} order by depth, parent_id, - cumulative_size desc, name`; - totalColumnName = 'cumulativeSize'; - selfColumnName = 'size'; - break; - case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY: - orderBy = `where depth < ${maxDepth} order by depth, - cumulativeCount desc, name`; - totalColumnName = 'cumulativeCount'; - selfColumnName = 'count'; - break; - case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY: - orderBy = `where depth < ${maxDepth} order by depth, - cumulativeSize desc, name`; - totalColumnName = 'cumulativeSize'; - selfColumnName = 'size'; - break; - default: - const exhaustiveCheck: never = viewingOption; - throw new Error(`Unhandled case: ${exhaustiveCheck}`); - break; - } - - const callsites = await this.args.engine.query(` - SELECT - id as hash, - IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name, - IFNULL(parent_id, -1) as parentHash, - depth, - cumulative_size as cumulativeSize, - cumulative_alloc_size as cumulativeAllocSize, - cumulative_count as cumulativeCount, - cumulative_alloc_count as cumulativeAllocCount, - map_name as mapping, - size, - count, - IFNULL(source_file, '') as sourceFile, - IFNULL(line_number, -1) as lineNumber - from ${tableName} ${orderBy}`); - - const flamegraphData: CallsiteInfo[] = []; - const hashToindex: Map<number, number> = new Map(); - const it = callsites.iter({ - hash: NUM, - name: STR, - parentHash: NUM, - depth: NUM, - cumulativeSize: NUM, - cumulativeAllocSize: NUM, - cumulativeCount: NUM, - cumulativeAllocCount: NUM, - mapping: STR, - sourceFile: STR, - lineNumber: NUM, - size: NUM, - count: NUM, - }); - for (let i = 0; it.valid(); ++i, it.next()) { - const hash = it.hash; - let name = it.name; - const parentHash = it.parentHash; - const depth = it.depth; - const totalSize = it[totalColumnName]; - const selfSize = it[selfColumnName]; - const mapping = it.mapping; - const highlighted = - focusRegex !== '' && - name.toLocaleLowerCase().includes(focusRegex.toLocaleLowerCase()); - const parentId = hashToindex.has(+parentHash) - ? hashToindex.get(+parentHash)! - : -1; - - let location: string | undefined; - if (/[a-zA-Z]/i.test(it.sourceFile)) { - location = it.sourceFile; - if (it.lineNumber !== -1) { - location += `:${it.lineNumber}`; - } - } - - if (depth === maxDepth - 1) { - name += ' [tree truncated]'; - } - // Instead of hash, we will store index of callsite in this original array - // as an id of callsite. That way, we have quicker access to parent and it - // will stay unique: - hashToindex.set(hash, i); - - flamegraphData.push({ - id: i, - totalSize, - depth, - parentId, - name, - selfSize, - mapping, - merged: false, - highlighted, - location, - }); - } - return flamegraphData; - } - - private async prepareViewsAndTables( - start: time, - end: time, - upids: number[], - type: ProfileType, - focusRegex: string, - viewingOption: FlamegraphStateViewingOption, - ): Promise<string> { - const flamegraphType = getFlamegraphType(type); - if (type === ProfileType.PERF_SAMPLE) { - let upid: string; - let upidGroup: string; - if (upids.length > 1) { - upid = `NULL`; - upidGroup = `'${FlamegraphController.serializeUpidGroup(upids)}'`; - } else { - upid = `${upids[0]}`; - upidGroup = `NULL`; - } - return this.cache.getTableName( - `select id, name, map_name, parent_id, depth, cumulative_size, - cumulative_alloc_size, cumulative_count, cumulative_alloc_count, - size, alloc_size, count, alloc_count, source_file, line_number - from experimental_flamegraph( - '${flamegraphType}', - NULL, - '>=${start},<=${end}', - ${upid}, - ${upidGroup}, - '${focusRegex}' - )`, - ); - } - if ( - type === ProfileType.JAVA_HEAP_GRAPH && - isHeapGraphDominatorTreeViewingOption(viewingOption) - ) { - return this.cache.getTableName( - await this.loadHeapGraphDominatorTreeQuery(upids[0], end), - ); - } - return this.cache.getTableName( - `select id, name, map_name, parent_id, depth, cumulative_size, - cumulative_alloc_size, cumulative_count, cumulative_alloc_count, - size, alloc_size, count, alloc_count, source_file, line_number - from experimental_flamegraph( - '${flamegraphType}', - ${end}, - NULL, - ${upids[0]}, - NULL, - '${focusRegex}' - )`, - ); - } - - private async loadHeapGraphDominatorTreeQuery(upid: number, timestamp: time) { - const outputTableName = `heap_graph_type_dominated_${upid}_${timestamp}`; - const outputQuery = `SELECT * FROM ${outputTableName}`; - if (this.cache.hasQuery(outputQuery)) { - return outputQuery; - } - - this.args.engine.query(` - INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree; - - -- heap graph dominator tree with objects as nodes and all relavant - -- object self stats and dominated stats - CREATE PERFETTO TABLE _heap_graph_object_dominated AS - SELECT - node.id, - node.idom_id, - node.dominated_obj_count, - node.dominated_size_bytes + node.dominated_native_size_bytes AS dominated_size, - node.depth, - obj.type_id, - obj.root_type, - obj.self_size + obj.native_size AS self_size - FROM memory_heap_graph_dominator_tree node - JOIN heap_graph_object obj USING(id) - WHERE obj.upid = ${upid} AND obj.graph_sample_ts = ${timestamp} - -- required to accelerate the recursive cte below - ORDER BY idom_id; - - -- calculate for each object node in the dominator tree the - -- HASH(path of type_id's from the super root to the object) - CREATE PERFETTO TABLE _dominator_tree_path_hash AS - WITH RECURSIVE _tree_visitor(id, path_hash) AS ( - SELECT - id, - HASH( - CAST(type_id AS TEXT) || '-' || IFNULL(root_type, '') - ) AS path_hash - FROM _heap_graph_object_dominated - WHERE depth = 1 - UNION ALL - SELECT - child.id, - HASH(CAST(parent.path_hash AS TEXT) || '/' || CAST(type_id AS TEXT)) AS path_hash - FROM _heap_graph_object_dominated child - JOIN _tree_visitor parent ON child.idom_id = parent.id - ) - SELECT * from _tree_visitor - ORDER BY id; - - -- merge object nodes with the same path into one "class type node", so the - -- end result is a tree where nodes are identified by their types and the - -- dominator relationships are preserved. - CREATE PERFETTO TABLE ${outputTableName} AS - SELECT - map.path_hash as id, - COALESCE(cls.deobfuscated_name, cls.name, '[NULL]') || IIF( - node.root_type IS NOT NULL, - ' [' || node.root_type || ']', '' - ) AS name, - IFNULL(parent_map.path_hash, -1) AS parent_id, - node.depth - 1 AS depth, - sum(dominated_size) AS cumulative_size, - -1 AS cumulative_alloc_size, - sum(dominated_obj_count) AS cumulative_count, - -1 AS cumulative_alloc_count, - '' as map_name, - '' as source_file, - -1 as line_number, - sum(self_size) AS size, - count(*) AS count - FROM _heap_graph_object_dominated node - JOIN _dominator_tree_path_hash map USING(id) - LEFT JOIN _dominator_tree_path_hash parent_map ON node.idom_id = parent_map.id - JOIN heap_graph_class cls ON node.type_id = cls.id - GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number; - - -- These are intermediates and not needed - DROP TABLE _heap_graph_object_dominated; - DROP TABLE _dominator_tree_path_hash; - `); - - return outputQuery; - } - - getMinSizeDisplayed( - flamegraphData: CallsiteInfo[], - rootSize?: number, - ): number { - const timeState = globals.state.frontendLocalState.visibleState; - const dur = globals.stateVisibleTime().duration; - // TODO(stevegolton): Does this actually do what we want??? - let width = Duration.toSeconds(dur / timeState.resolution); - // TODO(168048193): Remove screen size hack: - width = Math.max(width, 800); - if (rootSize === undefined) { - rootSize = findRootSize(flamegraphData); - } - return (MIN_PIXEL_DISPLAYED * rootSize) / width; - } - - async getFlamegraphMetadata( - type: ProfileType, - start: time, - end: time, - upids: number[], - ): Promise<FlamegraphDetails | undefined> { - // Don't do anything if selection of the marker stayed the same. - if ( - this.lastSelectedFlamegraphState !== undefined && - this.lastSelectedFlamegraphState.start === start && - this.lastSelectedFlamegraphState.end === end && - FlamegraphController.areArraysEqual( - this.lastSelectedFlamegraphState.upids, - upids, - ) - ) { - return undefined; - } - - // Collecting data for more information about profile, such as: - // total memory allocated, memory that is allocated and not freed. - const upidGroup = FlamegraphController.serializeUpidGroup(upids); - - const result = await this.args.engine.query( - `select pid from process where upid in (${upidGroup})`, - ); - const it = result.iter({pid: NUM}); - const pids = []; - for (let i = 0; it.valid(); ++i, it.next()) { - pids.push(it.pid); - } - return {start, dur: end - start, pids, upids, type}; - } - - private static areArraysEqual(a: number[], b: number[]) { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - return true; - } - - private static serializeUpidGroup(upids: number[]) { - return new Array(upids).join(); - } -} diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts index aa40c6ea0..3ffedf3ce 100644 --- a/ui/src/controller/flow_events_controller.ts +++ b/ui/src/controller/flow_events_controller.ts @@ -20,7 +20,7 @@ import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish'; import {asSliceSqlId} from '../frontend/sql_types'; import {Engine} from '../trace_processor/engine'; import {LONG, NUM, STR_NULL} from '../trace_processor/query_result'; -import {SLICE_TRACK_KIND} from '../core_plugins/chrome_slices/chrome_slice_track'; +import {THREAD_SLICE_TRACK_KIND} from '../core_plugins/thread_slice/thread_slice_track'; import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../core_plugins/frames'; import {Controller} from './controller'; @@ -41,7 +41,7 @@ const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({ export class FlowEventsController extends Controller<'main'> { private lastSelectedSliceId?: number; private lastSelectedArea?: Area; - private lastSelectedKind: 'CHROME_SLICE' | 'AREA' | 'NONE' = 'NONE'; + private lastSelectedKind: 'SLICE' | 'AREA' | 'NONE' = 'NONE'; constructor(private args: FlowEventsControllerArgs) { super('main'); @@ -306,13 +306,13 @@ export class FlowEventsController extends Controller<'main'> { sliceSelected(sliceId: number) { if ( - this.lastSelectedKind === 'CHROME_SLICE' && + this.lastSelectedKind === 'SLICE' && this.lastSelectedSliceId === sliceId ) { return; } this.lastSelectedSliceId = sliceId; - this.lastSelectedKind = 'CHROME_SLICE'; + this.lastSelectedKind = 'SLICE'; const connectedFlows = SHOW_INDIRECT_PRECEDING_FLOWS_FLAG.get() ? `( @@ -386,7 +386,7 @@ export class FlowEventsController extends Controller<'main'> { const trackInfo = globals.trackManager.resolveTrackInfo(track.uri); const kind = trackInfo?.kind; if ( - kind === SLICE_TRACK_KIND || + kind === THREAD_SLICE_TRACK_KIND || kind === ACTUAL_FRAMES_SLICE_TRACK_KIND ) { if (trackInfo?.trackIds) { @@ -453,7 +453,7 @@ export class FlowEventsController extends Controller<'main'> { // TODO(b/155483804): This is a hack as annotation slices don't contain // flows. We should tidy this up when fixing this bug. - if (selection.kind === 'CHROME_SLICE' && selection.table !== 'annotation') { + if (selection.kind === 'SLICE' && selection.table !== 'annotation') { this.sliceSelected(selection.id); } else { publishConnectedFlows([]); diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts deleted file mode 100644 index 1380ebfc0..000000000 --- a/ui/src/controller/permalink_controller.ts +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (C) 2018 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 {produce} from 'immer'; - -import {assertExists} from '../base/logging'; -import {runValidator} from '../base/validators'; -import {Actions} from '../common/actions'; -import {ConversionJobStatus} from '../common/conversion_jobs'; -import { - createEmptyNonSerializableState, - createEmptyState, -} from '../common/empty_state'; -import {EngineConfig, ObjectById, State, STATE_VERSION} from '../common/state'; -import { - BUCKET_NAME, - buggyToSha256, - deserializeStateObject, - saveState, - toSha256, - TraceGcsUploader, -} from '../common/upload_utils'; -import {globals} from '../frontend/globals'; -import {publishConversionJobStatusUpdate} from '../frontend/publish'; -import {Router} from '../frontend/router'; - -import {Controller} from './controller'; -import {RecordConfig, recordConfigValidator} from './record_config_types'; -import {showModal} from '../widgets/modal'; - -interface MultiEngineState { - currentEngineId?: string; - engines: ObjectById<EngineConfig>; -} - -function isMultiEngineState( - state: State | MultiEngineState, -): state is MultiEngineState { - if ((state as MultiEngineState).engines !== undefined) { - return true; - } - return false; -} - -export class PermalinkController extends Controller<'main'> { - private lastRequestId?: string; - constructor() { - super('main'); - } - - run() { - if ( - globals.state.permalink.requestId === undefined || - globals.state.permalink.requestId === this.lastRequestId - ) { - return; - } - const requestId = assertExists(globals.state.permalink.requestId); - this.lastRequestId = requestId; - - // if the |hash| is not set, this is a request to create a permalink. - if (globals.state.permalink.hash === undefined) { - const isRecordingConfig = assertExists( - globals.state.permalink.isRecordingConfig, - ); - - const jobName = 'create_permalink'; - publishConversionJobStatusUpdate({ - jobName, - jobStatus: ConversionJobStatus.InProgress, - }); - - PermalinkController.createPermalink(isRecordingConfig) - .then((hash) => { - globals.dispatch(Actions.setPermalink({requestId, hash})); - }) - .finally(() => { - publishConversionJobStatusUpdate({ - jobName, - jobStatus: ConversionJobStatus.NotRunning, - }); - }); - return; - } - - // Otherwise, this is a request to load the permalink. - PermalinkController.loadState(globals.state.permalink.hash).then( - (stateOrConfig) => { - if (PermalinkController.isRecordConfig(stateOrConfig)) { - // This permalink state only contains a RecordConfig. Show the - // recording page with the config, but keep other state as-is. - const validConfig = runValidator( - recordConfigValidator, - stateOrConfig as unknown, - ).result; - globals.dispatch(Actions.setRecordConfig({config: validConfig})); - Router.navigate('#!/record'); - return; - } - globals.dispatch(Actions.setState({newState: stateOrConfig})); - this.lastRequestId = stateOrConfig.permalink.requestId; - }, - ); - } - - private static upgradeState(state: State): State { - if (state.engine !== undefined && state.engine.source.type !== 'URL') { - // All permalink traces should be modified to have a source.type=URL - // pointing to the uploaded trace. Due to a bug in some older version - // of the UI (b/327049372), an upload failure can end up with a state that - // has type=FILE but a null file object. If this happens, invalidate the - // trace and show a message. - showModal({ - title: 'Cannot load trace permalink', - content: m( - 'div', - 'The permalink stored on the server is corrupted ' + - 'and cannot be loaded.', - ), - }); - return createEmptyState(); - } - - if (state.version !== STATE_VERSION) { - const newState = createEmptyState(); - // Old permalinks from state versions prior to version 24 - // have multiple engines of which only one is identified as the - // current engine via currentEngineId. Handle this case: - if (isMultiEngineState(state)) { - const engineId = state.currentEngineId; - if (engineId !== undefined) { - newState.engine = state.engines[engineId]; - } - } else { - newState.engine = state.engine; - } - - if (newState.engine !== undefined) { - newState.engine.ready = false; - } - const message = - `Unable to parse old state version. Discarding state ` + - `and loading trace.`; - console.warn(message); - PermalinkController.updateStatus(message); - return newState; - } else { - // Loaded state is presumed to be compatible with the State type - // definition in the app. However, a non-serializable part has to be - // recreated. - state.nonSerializableState = createEmptyNonSerializableState(); - } - return state; - } - - private static isRecordConfig( - stateOrConfig: State | RecordConfig, - ): stateOrConfig is RecordConfig { - const mode = (stateOrConfig as {mode?: string}).mode; - return ( - mode !== undefined && - ['STOP_WHEN_FULL', 'RING_BUFFER', 'LONG_TRACE'].includes(mode) - ); - } - - private static async createPermalink( - isRecordingConfig: boolean, - ): Promise<string> { - let uploadState: State | RecordConfig = globals.state; - - if (isRecordingConfig) { - uploadState = globals.state.recordConfig; - } else { - const engine = assertExists(globals.getCurrentEngine()); - let dataToUpload: File | ArrayBuffer | undefined = undefined; - let traceName = `trace ${engine.id}`; - if (engine.source.type === 'FILE') { - dataToUpload = engine.source.file; - traceName = dataToUpload.name; - } else if (engine.source.type === 'ARRAY_BUFFER') { - dataToUpload = engine.source.buffer; - } else if (engine.source.type !== 'URL') { - throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`); - } - - if (dataToUpload !== undefined) { - PermalinkController.updateStatus(`Uploading ${traceName}`); - const uploader = new TraceGcsUploader(dataToUpload, () => { - switch (uploader.state) { - case 'UPLOADING': - const statusTxt = `Uploading ${uploader.getEtaString()}`; - PermalinkController.updateStatus(statusTxt); - break; - case 'UPLOADED': - // Convert state to use URLs and remove permalink. - const url = uploader.uploadedUrl; - uploadState = produce(globals.state, (draft) => { - assertExists(draft.engine).source = {type: 'URL', url}; - draft.permalink = {}; - }); - break; - case 'ERROR': - PermalinkController.updateStatus( - `Upload failed ${uploader.error}`, - ); - break; - } // switch (state) - }); // onProgress - await uploader.waitForCompletion(); - } - } - - // Upload state. - PermalinkController.updateStatus(`Creating permalink...`); - const hash = await saveState(uploadState); - PermalinkController.updateStatus(`Permalink ready`); - return hash; - } - - private static async loadState(id: string): Promise<State | RecordConfig> { - const url = `https://storage.googleapis.com/${BUCKET_NAME}/${id}`; - const response = await fetch(url); - if (!response.ok) { - throw new Error( - `Could not fetch permalink.\n` + - `Are you sure the id (${id}) is correct?\n` + - `URL: ${url}`, - ); - } - const text = await response.text(); - const stateHash = await toSha256(text); - const state = deserializeStateObject<State>(text); - if (stateHash !== id) { - // Old permalinks incorrectly dropped some digits from the - // hexdigest of the SHA256. We don't want to invalidate those - // links so we also compute the old string and try that here - // also. - const buggyStateHash = await buggyToSha256(text); - if (buggyStateHash !== id) { - throw new Error(`State hash does not match ${id} vs. ${stateHash}`); - } - } - if (!this.isRecordConfig(state)) { - return this.upgradeState(state); - } - return state; - } - - private static updateStatus(msg: string): void { - // TODO(hjd): Unify loading updates. - globals.dispatch( - Actions.updateStatus({ - msg, - timestamp: Date.now() / 1000, - }), - ); - } -} diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts index ba2d35463..bd6a3b133 100644 --- a/ui/src/controller/search_controller.ts +++ b/ui/src/controller/search_controller.ts @@ -163,7 +163,7 @@ export class SearchController extends Controller<'main'> { utids.push(it.utid); } - const cpus = await this.engine.getCpus(); + const cpus = globals.traceContext.cpus; const maxCpu = Math.max(...cpus, -1); const res = await this.query(` diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts index 1b6dd0fe1..40252739b 100644 --- a/ui/src/controller/selection_controller.ts +++ b/ui/src/controller/selection_controller.ts @@ -15,7 +15,11 @@ import {assertTrue} from '../base/logging'; import {Time, time} from '../base/time'; import {Args, ArgValue} from '../common/arg_types'; -import {ChromeSliceSelection, getLegacySelection} from '../common/state'; +import { + SelectionKind, + ThreadSliceSelection, + getLegacySelection, +} from '../common/state'; import { CounterDetails, globals, @@ -37,7 +41,7 @@ import { STR_NULL, timeFromSql, } from '../trace_processor/query_result'; -import {SLICE_TRACK_KIND} from '../core_plugins/chrome_slices/chrome_slice_track'; +import {THREAD_SLICE_TRACK_KIND} from '../core_plugins/thread_slice/thread_slice_track'; import {Controller} from './controller'; @@ -71,10 +75,10 @@ export class SelectionController extends Controller<'main'> { const selection = getLegacySelection(globals.state); if (!selection || selection.kind === 'AREA') return; - const selectWithId = [ + const selectWithId: SelectionKind[] = [ 'SLICE', 'COUNTER', - 'CHROME_SLICE', + 'SCHED_SLICE', 'HEAP_PROFILE', 'THREAD_STATE', ]; @@ -107,16 +111,16 @@ export class SelectionController extends Controller<'main'> { publishCounterDetails(results); } }); - } else if (selection.kind === 'SLICE') { - this.sliceDetails(selectedId as number); + } else if (selection.kind === 'SCHED_SLICE') { + this.schedSliceDetails(selectedId as number); } else if (selection.kind === 'THREAD_STATE') { this.threadStateDetails(selection.id); - } else if (selection.kind === 'CHROME_SLICE') { - this.chromeSliceDetails(selection); + } else if (selection.kind === 'SLICE') { + this.sliceDetails(selection); } } - async chromeSliceDetails(selection: ChromeSliceSelection) { + async sliceDetails(selection: ThreadSliceSelection) { const selectedId = selection.id; const table = selection.table; @@ -306,7 +310,7 @@ export class SelectionController extends Controller<'main'> { if (name === 'destination slice id' && !isNaN(Number(value))) { const destTrackId = await this.getDestTrackId(value); args.set('Destination Slice', { - kind: 'SLICE', + kind: 'SCHED_SLICE', trackId: destTrackId, sliceId: Number(value), rawValue: value, @@ -328,7 +332,7 @@ export class SelectionController extends Controller<'main'> { let trackKey = ''; for (const track of Object.values(globals.state.tracks)) { const trackInfo = globals.trackManager.resolveTrackInfo(track.uri); - if (trackInfo?.kind === SLICE_TRACK_KIND) { + if (trackInfo?.kind === THREAD_SLICE_TRACK_KIND) { const trackIds = trackInfo?.trackIds; if (trackIds && trackIds.length > 0 && trackIds[0] === trackId) { trackKey = track.key; @@ -366,7 +370,7 @@ export class SelectionController extends Controller<'main'> { } } - async sliceDetails(id: number) { + async schedSliceDetails(id: number) { const sqlQuery = `SELECT sched.ts, sched.dur, @@ -441,7 +445,7 @@ export class SelectionController extends Controller<'main'> { IFNULL(value, 0) as value FROM counter WHERE ts < ${ts} and track_id = ${trackId}`); const previousValue = previous.firstRow({value: NUM}).value; - const endTs = rightTs !== -1n ? rightTs : globals.traceTime.end; + const endTs = rightTs !== -1n ? rightTs : globals.traceContext.end; const delta = value - previousValue; const duration = endTs - ts; const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId); diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts index 445bd4a8c..ffa062ef2 100644 --- a/ui/src/controller/trace_controller.ts +++ b/ui/src/controller/trace_controller.ts @@ -29,11 +29,11 @@ import {pluginManager} from '../common/plugins'; import {EngineMode, PendingDeeplinkState, ProfileType} from '../common/state'; import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../core/feature_flags'; import { - defaultTraceTime, + defaultTraceContext, globals, QuantizedLoad, ThreadDesc, - TraceTime, + TraceContext, } from '../frontend/globals'; import { clearOverviewData, @@ -41,7 +41,7 @@ import { publishMetricError, publishOverviewData, publishThreads, - publishTraceDetails, + publishTraceContext, } from '../frontend/publish'; import {addQueryResultsTab} from '../frontend/query_result_tab'; import {Router} from '../frontend/router'; @@ -74,11 +74,6 @@ import { CpuProfileControllerArgs, } from './cpu_profile_controller'; import { - FlamegraphController, - FlamegraphControllerArgs, - profileType, -} from './flamegraph_controller'; -import { FlowEventsController, FlowEventsControllerArgs, } from './flow_events_controller'; @@ -100,6 +95,7 @@ import { TraceStream, } from '../core/trace_stream'; import {decideTracks} from './track_decider'; +import {FlamegraphCache, profileType} from '../frontend/flamegraph_panel'; type States = 'init' | 'loading_trace' | 'ready'; @@ -281,10 +277,6 @@ export class TraceController extends Controller<States> { Child('cpuProfile', CpuProfileController, cpuProfileArgs), ); - const flamegraphArgs: FlamegraphControllerArgs = {engine}; - childControllers.push( - Child('flamegraph', FlamegraphController, flamegraphArgs), - ); childControllers.push( Child('cpu_aggregation', CpuAggregationController, { engine, @@ -351,6 +343,10 @@ export class TraceController extends Controller<States> { onDestroy() { pluginManager.onTraceClose(); globals.engines.delete(this.engineId); + + // Invalidate the flamegraph cache. + // TODO(stevegolton): migrate this to the new system when it's ready. + globals.areaFlamegraphCache = new FlamegraphCache('area'); } private async loadTrace(): Promise<EngineMode> { @@ -452,7 +448,7 @@ export class TraceController extends Controller<States> { const traceUuid = await this.cacheCurrentTrace(); const traceDetails = await getTraceTimeDetails(this.engine); - publishTraceDetails(traceDetails); + publishTraceContext(traceDetails); const shownJsonWarning = window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) !== null; @@ -677,7 +673,7 @@ export class TraceController extends Controller<States> { } globals.setLegacySelection( { - kind: 'CHROME_SLICE', + kind: 'SLICE', id: row.id, trackKey, table: 'slice', @@ -1025,6 +1021,9 @@ export class TraceController extends Controller<States> { this.updateStatus('Creating slice summaries'); await engine.query(`include perfetto module viz.summary.slices;`); + this.updateStatus('Creating counter summaries'); + await engine.query(`include perfetto module viz.summary.counters;`); + this.updateStatus('Creating thread summaries'); await engine.query(`include perfetto module viz.summary.threads;`); @@ -1100,8 +1099,8 @@ async function computeVisibleTime( // if we have non-default visible state, update the visible time to it const previousVisibleState = globals.stateVisibleTime(); const defaultTraceSpan = new TimeSpan( - defaultTraceTime.start, - defaultTraceTime.end, + defaultTraceContext.start, + defaultTraceContext.end, ); if ( !( @@ -1122,7 +1121,7 @@ async function computeVisibleTime( let visibleEnd = traceEnd; // compare start and end with metadata computed by the trace processor - const mdTime = await engine.getTracingMetadataTimeBounds(); + const mdTime = await getTracingMetadataTimeBounds(engine); // make sure the bounds hold if (Time.max(visibleStart, mdTime.start) < Time.min(visibleEnd, mdTime.end)) { visibleStart = Time.max(visibleStart, mdTime.start); @@ -1145,8 +1144,8 @@ async function computeVisibleTime( return HighPrecisionTimeSpan.fromTime(visibleStart, visibleEnd); } -async function getTraceTimeDetails(engine: EngineBase): Promise<TraceTime> { - const traceTime = await engine.getTraceTimeBounds(); +async function getTraceTimeDetails(engine: Engine): Promise<TraceContext> { + const traceTime = await getTraceTimeBounds(engine); // Find the first REALTIME or REALTIME_COARSE clock snapshot. // Prioritize REALTIME over REALTIME_COARSE. @@ -1216,5 +1215,67 @@ async function getTraceTimeDetails(engine: EngineBase): Promise<TraceTime> { realtimeOffset, utcOffset, traceTzOffset, + cpus: await getCpus(engine), + gpuCount: await getNumberOfGpus(engine), }; } + +async function getTraceTimeBounds( + engine: Engine, +): Promise<Span<time, duration>> { + const result = await engine.query( + `select start_ts as startTs, end_ts as endTs from trace_bounds`, + ); + const bounds = result.firstRow({ + startTs: LONG, + endTs: LONG, + }); + return new TimeSpan(Time.fromRaw(bounds.startTs), Time.fromRaw(bounds.endTs)); +} + +// TODO(hjd): When streaming must invalidate this somehow. +async function getCpus(engine: Engine): Promise<number[]> { + const cpus = []; + const queryRes = await engine.query( + 'select distinct(cpu) as cpu from sched order by cpu;', + ); + for (const it = queryRes.iter({cpu: NUM}); it.valid(); it.next()) { + cpus.push(it.cpu); + } + return cpus; +} + +async function getNumberOfGpus(engine: Engine): Promise<number> { + const result = await engine.query(` + select count(distinct(gpu_id)) as gpuCount + from gpu_counter_track + where name = 'gpufreq'; + `); + return result.firstRow({gpuCount: NUM}).gpuCount; +} + +async function getTracingMetadataTimeBounds( + engine: Engine, +): Promise<Span<time, duration>> { + const queryRes = await engine.query(`select + name, + int_value as intValue + from metadata + where name = 'tracing_started_ns' or name = 'tracing_disabled_ns' + or name = 'all_data_source_started_ns'`); + let startBound = Time.MIN; + let endBound = Time.MAX; + const it = queryRes.iter({name: STR, intValue: LONG_NULL}); + for (; it.valid(); it.next()) { + const columnName = it.name; + const timestamp = it.intValue; + if (timestamp === null) continue; + if (columnName === 'tracing_disabled_ns') { + endBound = Time.min(endBound, Time.fromRaw(timestamp)); + } else { + startBound = Time.max(startBound, Time.fromRaw(timestamp)); + } + } + + return new TimeSpan(startBound, endBound); +} diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts index e1b907c90..36fb251c8 100644 --- a/ui/src/controller/track_decider.ts +++ b/ui/src/controller/track_decider.ts @@ -42,7 +42,7 @@ import { } from '../core_plugins/frames'; import {decideTracks as screenshotDecideTracks} from '../core_plugins/screenshots'; import {THREAD_STATE_TRACK_KIND} from '../core_plugins/thread_state'; -import {SLICE_TRACK_KIND} from '../core_plugins/chrome_slices/chrome_slice_track'; +import {THREAD_SLICE_TRACK_KIND} from '../core_plugins/thread_slice/thread_slice_track'; const MEM_DMA_COUNTER_NAME = 'mem.dma_heap'; const MEM_DMA = 'mem.dma_buffer'; @@ -96,10 +96,12 @@ class TrackDecider { async guessCpuSizes(): Promise<Map<number, string>> { const cpuToSize = new Map<number, string>(); await this.engine.query(` - INCLUDE PERFETTO MODULE cpu.size; + include perfetto module cpu.size; `); const result = await this.engine.query(` - SELECT cpu, cpu_guess_core_type(cpu) as size FROM cpu_counter_track; + select cpu, cpu_guess_core_type(cpu) as size + from cpu_counter_track + join _counter_track_summary using (id); `); const it = result.iter({ @@ -118,7 +120,7 @@ class TrackDecider { } async addCpuSchedulingTracks(): Promise<void> { - const cpus = await this.engine.getCpus(); + const cpus = globals.traceContext.cpus; const cpuToSize = await this.guessCpuSizes(); for (const cpu of cpus) { @@ -134,7 +136,7 @@ class TrackDecider { } async addCpuFreqTracks(engine: Engine): Promise<void> { - const cpus = await this.engine.getCpus(); + const cpus = globals.traceContext.cpus; for (const cpu of cpus) { // Only add a cpu freq track if we have @@ -152,6 +154,7 @@ class TrackDecider { limit 1 ) as cpuIdleId from cpu_counter_track + join _counter_track_summary using (id) where name = 'cpufreq' and cpu = ${cpu} limit 1; `); @@ -189,38 +192,38 @@ class TrackDecider { parentName: STR_NULL, }); - const parentIdToGroupId = new Map<number, string>(); + const parentIdToGroupKey = 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; + let groupKey = SCROLLING_TRACK_GROUP; if (parentTrackId !== null) { - const groupId = parentIdToGroupId.get(parentTrackId); - if (groupId === undefined) { - trackGroup = uuidv4(); - parentIdToGroupId.set(parentTrackId, trackGroup); + const maybeGroupKey = parentIdToGroupKey.get(parentTrackId); + if (maybeGroupKey === undefined) { + groupKey = uuidv4(); + parentIdToGroupKey.set(parentTrackId, groupKey); const parentName = getTrackName({name: rawParentName, kind}); this.addTrackGroupActions.push( Actions.addTrackGroup({ name: parentName, - id: trackGroup, + key: groupKey, collapsed: true, }), ); } else { - trackGroup = groupId; + groupKey = maybeGroupKey; } } const track: AddTrackArgs = { uri: `perfetto.AsyncSlices#${rawName}.${it.parentId}`, trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, - trackGroup, + trackGroup: groupKey, name, }; @@ -229,13 +232,14 @@ class TrackDecider { } async addGpuFreqTracks(engine: Engine): Promise<void> { - const numGpus = await this.engine.getNumberOfGpus(); + const numGpus = globals.traceContext.gpuCount; 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 + join _counter_track_summary using (id) where name = 'gpufreq' and gpu_id = ${gpu} limit 1; `); @@ -254,6 +258,7 @@ class TrackDecider { const cpuFreqLimitCounterTracksSql = ` select name, id from cpu_counter_track + join _counter_track_summary using (id) where name glob "Cpu * Freq Limit" order by name asc `; @@ -271,6 +276,7 @@ class TrackDecider { const addCpuPerfCounterTracksSql = ` select printf("Cpu %u %s", cpu, name) as name, id from perf_counter_track as pct + join _counter_track_summary using (id) order by perf_session_id asc, pct.name asc, cpu asc `; this.addCpuCounterTracks(engine, addCpuPerfCounterTracksSql); @@ -315,7 +321,7 @@ class TrackDecider { return; } - const id = uuidv4(); + const groupUuid = uuidv4(); const summaryTrackKey = uuidv4(); let foundSummary = false; @@ -328,14 +334,14 @@ class TrackDecider { track.key = summaryTrackKey; track.trackGroup = undefined; } else { - track.trackGroup = id; + track.trackGroup = groupUuid; } } const addGroup = Actions.addTrackGroup({ summaryTrackKey, name: MEM_DMA_COUNTER_NAME, - id, + key: groupUuid, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -369,7 +375,7 @@ class TrackDecider { const groupName = group + key; const addGroup = Actions.addTrackGroup({ name: groupName, - id: value, + key: value, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -408,7 +414,7 @@ class TrackDecider { const groupName = key; const addGroup = Actions.addTrackGroup({ name: groupName, - id: value, + key: value, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -441,7 +447,7 @@ class TrackDecider { if (groupUuid !== undefined) { const addGroup = Actions.addTrackGroup({ name: groupName, - id: groupUuid, + key: groupUuid, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -483,7 +489,7 @@ class TrackDecider { if (groupUuid !== undefined) { const addGroup = Actions.addTrackGroup({ name: groupName, - id: groupUuid, + key: groupUuid, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -511,7 +517,7 @@ class TrackDecider { if (groupUuid !== undefined) { const addGroup = Actions.addTrackGroup({ name: groupName, - id: groupUuid, + key: groupUuid, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -537,7 +543,7 @@ class TrackDecider { summaryTrackKey: string; } - const groupNameToIds = new Map<string, GroupIds>(); + const groupNameToKeys = new Map<string, GroupIds>(); for (; sliceIt.valid(); sliceIt.next()) { const id = sliceIt.id; @@ -553,13 +559,13 @@ class TrackDecider { // 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; + const groupKeys = groupNameToKeys.get(groupName); + if (groupKeys) { + trackGroupId = groupKeys.id; } else { trackGroupId = uuidv4(); summaryTrackKey = uuidv4(); - groupNameToIds.set(groupName, { + groupNameToKeys.set(groupName, { id: trackGroupId, summaryTrackKey, }); @@ -575,11 +581,11 @@ class TrackDecider { }); } - for (const [groupName, groupIds] of groupNameToIds) { + for (const [groupName, groupKeys] of groupNameToKeys) { const addGroup = Actions.addTrackGroup({ - summaryTrackKey: groupIds.summaryTrackKey, + summaryTrackKey: groupKeys.summaryTrackKey, name: groupName, - id: groupIds.id, + key: groupKeys.id, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -707,7 +713,8 @@ class TrackDecider { thread.name as threadName, thread_counter_track.id as trackId from thread_counter_track - join thread using(utid) + join _counter_track_summary using (id) + join thread using (utid) where thread_counter_track.name != 'thread_time' `); @@ -843,7 +850,7 @@ class TrackDecider { for (const [name, groupUuid] of groupMap) { const addGroup = Actions.addTrackGroup({ name: name, - id: groupUuid, + key: groupUuid, collapsed: true, }); this.addTrackGroupActions.push(addGroup); @@ -975,11 +982,11 @@ class TrackDecider { const uuid = this.getUuid(utid, upid); - const kind = SLICE_TRACK_KIND; + const kind = THREAD_SLICE_TRACK_KIND; const name = getTrackName({name: trackName, utid, tid, threadName, kind}); this.tracksToAdd.push({ - uri: `perfetto.ChromeSlices#${trackId}`, + uri: `perfetto.ThreadSlices#${trackId}`, name, trackGroup: uuid, trackSortKey: { @@ -1001,6 +1008,7 @@ class TrackDecider { process.pid, process.name as processName from process_counter_track + join _counter_track_summary using (id) join process using(upid); `); const it = result.iter({ @@ -1153,7 +1161,7 @@ class TrackDecider { const addTrackGroup = Actions.addTrackGroup({ summaryTrackKey, name: `Kernel threads`, - id: kthreadGroupUuid, + key: kthreadGroupUuid, collapsed: true, }); this.addTrackGroupActions.push(addTrackGroup); @@ -1320,7 +1328,7 @@ class TrackDecider { const addTrackGroup = Actions.addTrackGroup({ summaryTrackKey, name, - id: this.getOrCreateUuid(utid, upid), + key: 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. @@ -1332,12 +1340,13 @@ class TrackDecider { 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`); + 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, @@ -1373,7 +1382,7 @@ class TrackDecider { groupUuid = uuidv4(); const addGroup = Actions.addTrackGroup({ name: groupName, - id: groupUuid, + key: groupUuid, collapsed: true, fixedOrdering: true, }); @@ -1549,9 +1558,9 @@ class TrackDecider { return PrimaryTrackSortKey.COUNTER_TRACK; } const result = await this.engine.query(` - select utid - from thread - where upid=${upid} and name=${sqliteString(threadName)} + select utid + from thread + where upid=${upid} and name=${sqliteString(threadName)} `); const it = result.iter({ utid: NUM, |