1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16import {duration, time, TimeSpan} from '../base/time'; 17import {Dataset, DatasetSchema} from '../trace_processor/dataset'; 18import {Engine} from '../trace_processor/engine'; 19import {ColumnDef, Sorting, ThreadStateExtra} from './aggregation'; 20import {Track} from './track'; 21import {arrayEquals} from '../base/array_utils'; 22 23export interface ContentWithLoadingFlag { 24 readonly isLoading: boolean; 25 readonly content: m.Children; 26} 27 28export interface AreaSelectionTab { 29 // Unique id for this tab. 30 readonly id: string; 31 32 // A name for this tab. 33 readonly name: string; 34 35 // Defines the sort order of this tab - higher values appear first. 36 readonly priority?: number; 37 38 /** 39 * Called every Mithril render cycle to render the content of the tab. The 40 * returned content will be displayed inside the current selection tab. 41 * 42 * If undefined is returned then the tab handle will be hidden, which gives 43 * the tab the option to dynamically remove itself from the list of tabs if it 44 * has nothing relevant to show. 45 * 46 * The |isLoading| flag is used to avoid flickering. If set to true, we keep 47 * hold of the the previous vnodes, rendering them instead, for up to 50ms 48 * before switching to the new content. This avoids very fast load times 49 * from causing flickering loading screens, which can be somewhat jarring. 50 */ 51 render(selection: AreaSelection): ContentWithLoadingFlag | undefined; 52} 53 54/** 55 * Compare two area selections for equality. Returns true if the selections are 56 * equivalent, false otherwise. 57 */ 58export function areaSelectionsEqual(a: AreaSelection, b: AreaSelection) { 59 if (a.start !== b.start) return false; 60 if (a.end !== b.end) return false; 61 if (!arrayEquals(a.trackUris, b.trackUris)) { 62 return false; 63 } 64 return true; 65} 66 67export interface SelectionManager { 68 readonly selection: Selection; 69 70 /** 71 * Provides a list of registered area selection tabs. 72 */ 73 readonly areaSelectionTabs: ReadonlyArray<AreaSelectionTab>; 74 75 findTimeRangeOfSelection(): TimeSpan | undefined; 76 clear(): void; 77 78 /** 79 * Select a track event. 80 * 81 * @param trackUri - The URI of the track to select. 82 * @param eventId - The value of the events ID column. 83 * @param opts - Additional options. 84 */ 85 selectTrackEvent( 86 trackUri: string, 87 eventId: number, 88 opts?: SelectionOpts, 89 ): void; 90 91 /** 92 * Select a track. 93 * 94 * @param trackUri - The URI for the track to select. 95 * @param opts - Additional options. 96 */ 97 selectTrack(trackUri: string, opts?: SelectionOpts): void; 98 99 /** 100 * Select a track event via a sql table name + id. 101 * 102 * @param sqlTableName - The name of the SQL table to resolve. 103 * @param id - The ID of the event in that table. 104 * @param opts - Additional options. 105 */ 106 selectSqlEvent(sqlTableName: string, id: number, opts?: SelectionOpts): void; 107 108 /** 109 * Create an area selection for the purposes of aggregation. 110 * 111 * @param args - The area to select. 112 * @param opts - Additional options. 113 */ 114 selectArea(args: Area, opts?: SelectionOpts): void; 115 116 scrollToCurrentSelection(): void; 117 118 /** 119 * Register a new tab under the area selection details panel. 120 */ 121 registerAreaSelectionTab(tab: AreaSelectionTab): void; 122} 123 124/** 125 * Aggregator tabs are displayed in descending order of specificity, determined 126 * by the following precedence hierarchy: 127 * 1. Aggregators explicitly defining a `trackKind` string take priority over 128 * those that do not. 129 * 2. Otherwise, aggregators with schemas containing a greater number of keys 130 * (higher specificity) are prioritized over those with fewer keys. 131 * 3. In cases of identical specificity, tabs are ranked based on their 132 * registration order. 133 */ 134export interface AreaSelectionAggregator { 135 readonly id: string; 136 137 /** 138 * If defined, the dataset passed to `createAggregateView` will only contain 139 * tracks with a matching `kind` tag. 140 */ 141 readonly trackKind?: string; 142 143 /** 144 * If defined, the dataset passed to `createAggregateView` will only contain 145 * tracks that export datasets that implement this schema. 146 */ 147 readonly schema?: DatasetSchema; 148 149 /** 150 * Creates a view for the aggregated data corresponding to the selected area. 151 * 152 * The dataset provided will be filtered based on the `trackKind` and `schema` 153 * if these properties are defined. 154 * 155 * @param engine - The query engine used to execute queries. 156 * @param area - The currently selected area to aggregate. 157 * @param dataset - The dataset representing a union of the data in the 158 * selected tracks. 159 */ 160 createAggregateView( 161 engine: Engine, 162 area: AreaSelection, 163 dataset?: Dataset, 164 ): Promise<boolean>; 165 getExtra( 166 engine: Engine, 167 area: AreaSelection, 168 dataset?: Dataset, 169 ): Promise<ThreadStateExtra | void>; 170 getTabName(): string; 171 getDefaultSorting(): Sorting; 172 getColumnDefinitions(): ColumnDef[]; 173} 174 175export type Selection = 176 | TrackEventSelection 177 | TrackSelection 178 | AreaSelection 179 | NoteSelection 180 | EmptySelection; 181 182/** Defines how changes to selection affect the rest of the UI state */ 183export interface SelectionOpts { 184 clearSearch?: boolean; // Default: true. 185 switchToCurrentSelectionTab?: boolean; // Default: true. 186 scrollToSelection?: boolean; // Default: false. 187} 188 189export interface TrackEventSelection extends TrackEventDetails { 190 readonly kind: 'track_event'; 191 readonly trackUri: string; 192 readonly eventId: number; 193} 194 195export interface TrackSelection { 196 readonly kind: 'track'; 197 readonly trackUri: string; 198} 199 200export interface TrackEventDetails { 201 // ts and dur are required by the core, and must be provided. 202 readonly ts: time; 203 204 // Note: dur can be 0 for instant events or -1 for DNF slices. Will be 205 // undefined if this selection has no duration, i.e. profile / counter 206 // samples. 207 readonly dur?: duration; 208 209 // Optional additional information. 210 // TODO(stevegolton): Find an elegant way of moving this information out of 211 // the core. 212 readonly wakeupTs?: time; 213 readonly wakerCpu?: number; 214 readonly utid?: number; 215} 216 217export interface Area { 218 readonly start: time; 219 readonly end: time; 220 // TODO(primiano): this should be ReadonlyArray<> after the pivot table state 221 // doesn't use State/Immer anymore. 222 readonly trackUris: string[]; 223} 224 225export interface AreaSelection extends Area { 226 readonly kind: 'area'; 227 228 // This array contains the resolved Tracks from Area.trackUris. The resolution 229 // is done by SelectionManager whenever a kind='area' selection is performed. 230 readonly tracks: ReadonlyArray<Track>; 231} 232 233export interface NoteSelection { 234 readonly kind: 'note'; 235 readonly id: string; 236} 237 238export interface EmptySelection { 239 readonly kind: 'empty'; 240} 241 242export interface SqlSelectionResolver { 243 readonly sqlTableName: string; 244 readonly callback: ( 245 id: number, 246 sqlTable: string, 247 ) => Promise<{trackUri: string; eventId: number} | undefined>; 248} 249