aboutsummaryrefslogtreecommitdiff
path: root/ui/src/controller
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/controller')
-rw-r--r--ui/src/controller/aggregation/slice_aggregation_controller.ts4
-rw-r--r--ui/src/controller/app_controller.ts5
-rw-r--r--ui/src/controller/cpu_profile_controller.ts7
-rw-r--r--ui/src/controller/flamegraph_controller.ts690
-rw-r--r--ui/src/controller/flow_events_controller.ts12
-rw-r--r--ui/src/controller/permalink_controller.ts269
-rw-r--r--ui/src/controller/search_controller.ts2
-rw-r--r--ui/src/controller/selection_controller.ts30
-rw-r--r--ui/src/controller/trace_controller.ts99
-rw-r--r--ui/src/controller/track_decider.ts103
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,