1// Copyright (C) 2022 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'; 16 17import {Hotkey} from '../base/hotkeys'; 18import {Span, duration, time} from '../base/time'; 19import {Migrate, Store} from '../base/store'; 20import {ColorScheme} from '../core/colorizer'; 21import {LegacySelection, PrimaryTrackSortKey, Selection} from '../common/state'; 22import {PanelSize} from '../frontend/panel'; 23import {Engine} from '../trace_processor/engine'; 24import {UntypedEventSet} from '../core/event_set'; 25import {TraceContext} from '../frontend/globals'; 26import {PromptOption} from '../frontend/omnibox_manager'; 27import {Optional} from '../base/utils'; 28 29export {Engine} from '../trace_processor/engine'; 30export { 31 LONG, 32 LONG_NULL, 33 NUM, 34 NUM_NULL, 35 STR, 36 STR_NULL, 37} from '../trace_processor/query_result'; 38export {BottomTabToSCSAdapter} from './utils'; 39export {createStore, Migrate, Store} from '../base/store'; 40export {PromptOption} from '../frontend/omnibox_manager'; 41export {PrimaryTrackSortKey} from '../common/state'; 42 43export {addDebugSliceTrack} from '../frontend/debug_tracks/debug_tracks'; 44export * from '../core/track_kinds'; 45 46export interface Slice { 47 // These properties are updated only once per query result when the Slice 48 // object is created and don't change afterwards. 49 readonly id: number; 50 readonly startNs: time; 51 readonly endNs: time; 52 readonly durNs: duration; 53 readonly ts: time; 54 readonly dur: duration; 55 readonly depth: number; 56 readonly flags: number; 57 58 // Each slice can represent some extra numerical information by rendering a 59 // portion of the slice with a lighter tint. 60 // |fillRatio\ describes the ratio of the normal area to the tinted area 61 // width of the slice, normalized between 0.0 -> 1.0. 62 // 0.0 means the whole slice is tinted. 63 // 1.0 means none of the slice is tinted. 64 // E.g. If |fillRatio| = 0.65 the slice will be rendered like this: 65 // [############|*******] 66 // ^------------^-------^ 67 // Normal Light 68 readonly fillRatio: number; 69 70 // These can be changed by the Impl. 71 title: string; 72 subTitle: string; 73 colorScheme: ColorScheme; 74 isHighlighted: boolean; 75} 76 77export interface Command { 78 // A unique id for this command. 79 id: string; 80 // A human-friendly name for this command. 81 name: string; 82 // Callback is called when the command is invoked. 83 // eslint-disable-next-line @typescript-eslint/no-explicit-any 84 callback: (...args: any[]) => any; 85 // Default hotkey for this command. 86 // Note: this is just the default and may be changed by the user. 87 // Examples: 88 // - 'P' 89 // - 'Shift+P' 90 // - '!Mod+Shift+P' 91 // See hotkeys.ts for guidance on hotkey syntax. 92 defaultHotkey?: Hotkey; 93} 94 95export interface MetricVisualisation { 96 // The name of the metric e.g. 'android_camera' 97 metric: string; 98 99 // A vega or vega-lite visualisation spec. 100 // The data from the metric under path will be exposed as a 101 // datasource named "metric" in Vega(-Lite) 102 spec: string; 103 104 // A path index into the metric. 105 // For example if the metric returns the folowing protobuf: 106 // { 107 // foo { 108 // bar { 109 // baz: { name: "a" } 110 // baz: { name: "b" } 111 // baz: { name: "c" } 112 // } 113 // } 114 // } 115 // That becomes the following json: 116 // { "foo": { "bar": { "baz": [ 117 // {"name": "a"}, 118 // {"name": "b"}, 119 // {"name": "c"}, 120 // ]}}} 121 // And given path = ["foo", "bar", "baz"] 122 // We extract: 123 // [ {"name": "a"}, {"name": "b"}, {"name": "c"} ] 124 // And pass that to the vega(-lite) visualisation. 125 path: string[]; 126} 127 128// This interface defines a context for a plugin, which is an object passed to 129// most hooks within the plugin. It should be used to interact with Perfetto. 130export interface PluginContext { 131 // The unique ID for this plugin. 132 readonly pluginId: string; 133 134 // Register command against this plugin context. 135 registerCommand(command: Command): void; 136 137 // Run a command, optionally passing some args. 138 // eslint-disable-next-line @typescript-eslint/no-explicit-any 139 runCommand(id: string, ...args: any[]): any; 140 141 // Control of the sidebar. 142 sidebar: { 143 // Show the sidebar. 144 show(): void; 145 146 // Hide the sidebar. 147 hide(): void; 148 149 // Returns true if the sidebar is visible. 150 isVisible(): boolean; 151 }; 152} 153 154export interface TrackContext { 155 // This track's key, used for making selections et al. 156 trackKey: string; 157} 158 159export interface SliceRect { 160 left: number; 161 width: number; 162 top: number; 163 height: number; 164 visible: boolean; 165} 166 167export interface Track { 168 /** 169 * Optional: Called once before onUpdate is first called. 170 * 171 * If this function returns a Promise, this promise is awaited before onUpdate 172 * or onDestroy is called. Any calls made to these functions in the meantime 173 * will be queued up and the hook will be called later once onCreate returns. 174 * 175 * Exactly when this hook is called is left purposely undefined. The only 176 * guarantee is that it will be called once before onUpdate is first called. 177 * 178 * @param ctx Our track context object. 179 */ 180 onCreate?(ctx: TrackContext): Promise<void> | void; 181 182 /** 183 * Optional: Called every render cycle while the track is visible, just before 184 * render(). 185 * If this function returns a Promise, this promise is awaited before another 186 * onUpdate is called or onDestroy is called. 187 */ 188 onUpdate?(): Promise<void> | void; 189 190 /** 191 * Optional: Called when the track is no longer visible. Should be used to 192 * clean up resources. 193 * This function can return nothing or a promise. The promise is currently 194 * ignored. 195 */ 196 onDestroy?(): Promise<void> | void; 197 198 render(ctx: CanvasRenderingContext2D, size: PanelSize): void; 199 onFullRedraw?(): void; 200 getSliceRect?(tStart: time, tEnd: time, depth: number): SliceRect | undefined; 201 getHeight(): number; 202 getTrackShellButtons?(): m.Children; 203 onMouseMove?(position: {x: number; y: number}): void; 204 onMouseClick?(position: {x: number; y: number}): boolean; 205 onMouseOut?(): void; 206 207 /** 208 * Optional: Get the event set that represents this track's data. 209 */ 210 getEventSet?(): UntypedEventSet; 211} 212 213// A definition of a track, including a renderer implementation and metadata. 214export interface TrackDescriptor { 215 // A unique identifier for this track. 216 uri: string; 217 218 // A factory function returning a new track instance. 219 trackFactory: (ctx: TrackContext) => Track; 220 221 // The track "kind", used by various subsystems e.g. aggregation controllers. 222 // This is where "XXX_TRACK_KIND" values should be placed. 223 // TODO(stevegolton): This will be deprecated once we handle group selections 224 // in a more generic way - i.e. EventSet. 225 kind?: string; 226 227 // Optional: list of track IDs represented by this trace. 228 // This list is used for participation in track indexing by track ID. 229 // This index is used by various subsystems to find links between tracks based 230 // on the track IDs used by trace processor. 231 trackIds?: number[]; 232 233 // Optional: The CPU number associated with this track. 234 cpu?: number; 235 236 // Optional: The UTID associated with this track. 237 utid?: number; 238 239 // Optional: The UPID associated with this track. 240 upid?: number; 241 242 // Optional: A list of tags used for sorting, grouping and "chips". 243 tags?: TrackTags; 244 245 // Placeholder - presently unused. 246 displayName?: string; 247 248 // Optional: method to look up the start and duration of an event on this track 249 getEventBounds?: (id: number) => Promise<Optional<{ts: time; dur: duration}>>; 250 251 // Optional: A details panel to use when this track is selected. 252 detailsPanel?: TrackSelectionDetailsPanel; 253} 254 255export interface SliceTrackColNames { 256 ts: string; 257 name: string; 258 dur: string; 259} 260 261export interface DebugSliceTrackArgs { 262 // Title of the track. If omitted a placeholder name will be chosen instead. 263 trackName?: string; 264 265 // Mapping definitions of the 'ts', 'dur', and 'name' columns. 266 // By default, columns called ts, dur and name will be used. 267 // If dur is assigned the value '0', all slices shall be instant events. 268 columnMapping?: Partial<SliceTrackColNames>; 269 270 // Any extra columns to be used as args. 271 args?: string[]; 272 273 // Optional renaming of columns. 274 columns?: string[]; 275} 276 277export interface CounterTrackColNames { 278 ts: string; 279 value: string; 280} 281 282export interface DebugCounterTrackArgs { 283 // Title of the track. If omitted a placeholder name will be chosen instead. 284 trackName?: string; 285 286 // Mapping definitions of the ts and value columns. 287 columnMapping?: Partial<CounterTrackColNames>; 288} 289 290export interface Tab { 291 render(): m.Children; 292 getTitle(): string; 293} 294 295export interface TabDescriptor { 296 uri: string; // TODO(stevegolton): Maybe optional for ephemeral tabs. 297 content: Tab; 298 isEphemeral?: boolean; // Defaults false 299 onHide?(): void; 300 onShow?(): void; 301} 302 303export interface LegacyDetailsPanel { 304 render(selection: LegacySelection): m.Children; 305 isLoading?(): boolean; 306} 307 308export interface DetailsPanel { 309 render(selection: Selection): m.Children; 310 isLoading?(): boolean; 311} 312 313export interface TrackSelectionDetailsPanel { 314 render(id: number): m.Children; 315 isLoading?(): boolean; 316} 317 318// Similar to PluginContext but with additional methods to operate on the 319// currently loaded trace. Passed to trace-relevant hooks on a plugin instead of 320// PluginContext. 321export interface PluginContextTrace extends PluginContext { 322 readonly engine: Engine; 323 324 // Control over the main timeline. 325 timeline: { 326 // Add a new track to the scrolling track section, returning the newly 327 // created track key. 328 addTrack(uri: string, displayName: string, params?: unknown): string; 329 330 // Remove a single track from the timeline. 331 removeTrack(key: string): void; 332 333 // Pin a single track. 334 pinTrack(key: string): void; 335 336 // Unpin a single track. 337 unpinTrack(key: string): void; 338 339 // Pin all tracks that match a predicate. 340 pinTracksByPredicate(predicate: TrackPredicate): void; 341 342 // Unpin all tracks that match a predicate. 343 unpinTracksByPredicate(predicate: TrackPredicate): void; 344 345 // Remove all tracks that match a predicate. 346 removeTracksByPredicate(predicate: TrackPredicate): void; 347 348 // Expand all groups that match a predicate. 349 expandGroupsByPredicate(predicate: GroupPredicate): void; 350 351 // Collapse all groups that match a predicate. 352 collapseGroupsByPredicate(predicate: GroupPredicate): void; 353 354 // Retrieve a list of tracks on the timeline. 355 tracks: TrackRef[]; 356 357 // Bring a timestamp into view. 358 panToTimestamp(ts: time): void; 359 360 // Move the viewport 361 setViewportTime(start: time, end: time): void; 362 363 // A span representing the current viewport location 364 readonly viewport: Span<time, duration>; 365 }; 366 367 // Control over the bottom details pane. 368 tabs: { 369 // Creates a new tab running the provided query. 370 openQuery(query: string, title: string): void; 371 372 // Add a tab to the tab bar (if not already) and focus it. 373 showTab(uri: string): void; 374 375 // Remove a tab from the tab bar. 376 hideTab(uri: string): void; 377 }; 378 379 // Register a new track against a unique key known as a URI. 380 // Once a track is registered it can be referenced multiple times on the 381 // timeline with different params to allow customising each instance. 382 registerTrack(trackDesc: TrackDescriptor): void; 383 384 // Add a new entry to the pool of default tracks. Default tracks are a list 385 // of track references that describe the list of tracks that should be added 386 // to the main timeline on startup. 387 // Default tracks are only used when a trace is first loaded, not when 388 // loading from a permalink, where the existing list of tracks from the 389 // shared state is used instead. 390 addDefaultTrack(track: TrackRef): void; 391 392 // Simultaneously register a track and add it as a default track in one go. 393 // This is simply a helper which calls registerTrack() and addDefaultTrack() 394 // with the same URI. 395 registerStaticTrack(track: TrackDescriptor & TrackRef): void; 396 397 // Register a new tab for this plugin. Will be unregistered when the plugin 398 // is deactivated or when the trace is unloaded. 399 registerTab(tab: TabDescriptor): void; 400 401 // Suggest that a tab should be shown immediately. 402 addDefaultTab(uri: string): void; 403 404 // Register a hook into the current selection tab rendering logic that allows 405 // customization of the current selection tab content. 406 registerDetailsPanel(sel: LegacyDetailsPanel): void; 407 408 // Create a store mounted over the top of this plugin's persistent state. 409 mountStore<T>(migrate: Migrate<T>): Store<T>; 410 411 trace: TraceContext; 412 413 // When the trace is opened via postMessage deep-linking, returns the sub-set 414 // of postMessageData.pluginArgs[pluginId] for the current plugin. If not 415 // present returns undefined. 416 readonly openerPluginArgs?: {[key: string]: unknown}; 417 418 prompt(text: string, options?: PromptOption[]): Promise<string>; 419} 420 421export interface Plugin { 422 // Lifecycle methods. 423 onActivate?(ctx: PluginContext): void; 424 onTraceLoad?(ctx: PluginContextTrace): Promise<void>; 425 onTraceUnload?(ctx: PluginContextTrace): Promise<void>; 426 onDeactivate?(ctx: PluginContext): void; 427 428 // Extension points. 429 metricVisualisations?(ctx: PluginContext): MetricVisualisation[]; 430} 431 432// This interface defines what a plugin factory should look like. 433// This can be defined in the plugin class definition by defining a constructor 434// and the relevant static methods: 435// E.g. 436// class MyPlugin implements TracePlugin<MyState> { 437// migrate(initialState: unknown): MyState {...} 438// constructor(store: Store<MyState>, engine: EngineProxy) {...} 439// ... methods from the TracePlugin interface go here ... 440// } 441// ... which can then be passed around by class i.e. MyPlugin 442export interface PluginClass { 443 // Instantiate the plugin. 444 new (): Plugin; 445} 446 447// Describes a reference to a registered track. 448export interface TrackRef { 449 // URI of the registered track. 450 uri: string; 451 452 // A human readable name for this track - displayed in the track shell. 453 displayName: string; 454 455 // Optional: Used to define default sort order for new traces. 456 // Note: This will be deprecated soon in favour of tags & sort rules. 457 sortKey?: PrimaryTrackSortKey; 458 459 // Optional: Add tracks to a group with this name. 460 groupName?: string; 461 462 // Optional: Track key 463 key?: string; 464 465 // Optional: Whether the track is pinned 466 isPinned?: boolean; 467} 468 469// A predicate for selecting a subset of tracks. 470export type TrackPredicate = (info: TrackTags) => boolean; 471 472// Describes a reference to a group of tracks. 473export interface GroupRef { 474 // A human readable name for this track group. 475 displayName: string; 476 477 // True if the track is open else false. 478 collapsed: boolean; 479} 480 481// A predicate for selecting a subset of groups. 482export type GroupPredicate = (info: GroupRef) => boolean; 483 484interface WellKnownTrackTags { 485 // A human readable name for this specific track. 486 name: string; 487 488 // Controls whether to show the "metric" chip. 489 metric: boolean; 490 491 // Controls whether to show the "debuggable" chip. 492 debuggable: boolean; 493 494 // Groupname of the track 495 groupName: string; 496} 497 498// An set of key/value pairs describing a given track. These are used for 499// selecting tracks to pin/unpin, diplsaying "chips" in the track shell, and 500// (in future) the sorting and grouping of tracks. 501// We define a handful of well known fields, and the rest are arbitrary key- 502// value pairs. 503export type TrackTags = Partial<WellKnownTrackTags> & { 504 // There may be arbitrary other key/value pairs. 505 [key: string]: string | number | boolean | undefined; 506}; 507 508// Plugins can be class refs or concrete plugin implementations. 509export type PluginFactory = PluginClass | Plugin; 510 511export interface PluginDescriptor { 512 // A unique string for your plugin. To ensure the name is unique you 513 // may wish to use a URL with reversed components in the manner of 514 // Java package names. 515 pluginId: string; 516 517 // The plugin factory used to instantiate the plugin object, or if this is 518 // an actual plugin implementation, it's just used as-is. 519 plugin: PluginFactory; 520} 521