• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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