blob: 1ae68b0b9872aa11bdcecb385c15d10c6e732509 [file] [log] [blame]
// Copyright (C) 2022 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 m from 'mithril';
import {Hotkey} from '../base/hotkeys';
import {duration, Span, time} from '../base/time';
import {EngineProxy} from '../common/engine';
import {TrackControllerFactory} from '../controller/track_controller';
import {Store} from '../frontend/store';
import {PxSpan, TimeScale} from '../frontend/time_scale';
import {SliceRect, TrackCreator} from '../frontend/track';
import {TrackButtonAttrs} from '../frontend/track_panel';
export {EngineProxy} from '../common/engine';
export {
LONG,
LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../common/query_result';
export {Store} from '../frontend/store';
// An imperative API for plugins to change the UI.
export interface Viewer {
// Control of the sidebar.
sidebar: {
// Show the sidebar.
show(): void;
// Hide the sidebar.
hide(): void;
// Returns true if the sidebar is visble.
isVisible(): boolean;
}
// Tracks
tracks: {
pin(predicate: TrackPredicate): void;
unpin(predicate: TrackPredicate): void;
}
// Control over the bottom details pane.
tabs: {
// Creates a new tab running the provided query.
openQuery(query: string, title: string): void;
}
commands: {run(name: string, ...args: any[]): void;}
}
export interface Command {
// A unique id for this command.
id: string;
// A human-friendly name for this command.
name: string;
// Callback is called when the command is invoked.
callback: (...args: any[]) => void;
// Default hotkey for this command.
// Note: this is just the default and may be changed by the user.
// Examples:
// - 'P'
// - 'Shift+P'
// - '!Mod+Shift+P'
// See hotkeys.ts for guidance on hotkey syntax.
defaultHotkey?: Hotkey;
}
export interface MetricVisualisation {
// The name of the metric e.g. 'android_camera'
metric: string;
// A vega or vega-lite visualisation spec.
// The data from the metric under path will be exposed as a
// datasource named "metric" in Vega(-Lite)
spec: string;
// A path index into the metric.
// For example if the metric returns the folowing protobuf:
// {
// foo {
// bar {
// baz: { name: "a" }
// baz: { name: "b" }
// baz: { name: "c" }
// }
// }
// }
// That becomes the following json:
// { "foo": { "bar": { "baz": [
// {"name": "a"},
// {"name": "b"},
// {"name": "c"},
// ]}}}
// And given path = ["foo", "bar", "baz"]
// We extract:
// [ {"name": "a"}, {"name": "b"}, {"name": "c"} ]
// And pass that to the vega(-lite) visualisation.
path: string[];
}
export interface MetricVisualisation {
// The name of the metric e.g. 'android_camera'
metric: string;
// A vega or vega-lite visualisation spec.
// The data from the metric under path will be exposed as a
// datasource named "metric" in Vega(-Lite)
spec: string;
// A path index into the metric.
// For example if the metric returns the folowing protobuf:
// {
// foo {
// bar {
// baz: { name: "a" }
// baz: { name: "b" }
// baz: { name: "c" }
// }
// }
// }
// That becomes the following json:
// { "foo": { "bar": { "baz": [
// {"name": "a"},
// {"name": "b"},
// {"name": "c"},
// ]}}}
// And given path = ["foo", "bar", "baz"]
// We extract:
// [ {"name": "a"}, {"name": "b"}, {"name": "c"} ]
// And pass that to the vega(-lite) visualisation.
path: string[];
}
// This interface defines a context for a plugin, which is an object passed to
// most hooks within the plugin. It should be used to interact with Perfetto.
export interface PluginContext {
readonly viewer: Viewer;
// DEPRECATED. In prior versions of the UI tracks were split into a
// 'TrackController' and a 'Track'. In more recent versions of the UI
// the functionality of |TrackController| has been merged into Track so
// |TrackController|s are not necessary in new code.
registerTrackController(track: TrackControllerFactory): void;
// Register a track factory. The core UI invokes |TrackCreator| to
// construct tracks discovered by invoking |TrackProvider|s.
// The split between 'construction' and 'discovery' allows
// plugins to reuse common tracks for new data. For example: the
// dev.perfetto.AndroidGpu plugin could register a TrackProvider
// which returns GPU counter tracks. The counter track factory itself
// could be registered in dev.perfetto.CounterTrack - a whole
// different plugin.
registerTrack(track: TrackCreator): void;
// Add a command.
addCommand(command: Command): void;
}
export type Migrate<State> = (init: unknown) => State;
export interface TrackContext {
// The ID of this track instance.
trackInstanceId: string;
// Creates a new store overlaying the track instance's state object.
// A migrate function must be passed to convert any existing state to a
// compatible format.
// When opening a fresh trace, the value of |init| will be undefined, and
// state should be updated to an appropriate default value.
// When loading a permalink, the value of |init| will be whatever was saved
// when the permalink was shared, which might be from an old version of this
// track.
mountStore<State>(migrate: Migrate<State>): Store<State>;
}
// TODO(stevegolton): Rename `Track` to `BaseTrack` (or similar) and rename this
// interface to `Track`.
export interface TrackLike {
onCreate(): Promise<void>;
render(ctx: CanvasRenderingContext2D): void;
onFullRedraw(): void;
getSliceRect(
visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
|undefined;
getHeight(): number;
getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>>;
getContextMenu(): m.Vnode<any>|null;
onMouseMove(position: {x: number, y: number}): void;
onMouseClick(position: {x: number, y: number}): boolean;
onMouseOut(): void;
onDestroy(): Promise<void>;
}
export interface PluginTrackInfo {
// A unique identifier for the track. This must be unique within all tracks.
uri: string;
// A human friendly name for this track. Used when displaying the list of
// tracks to the user. E.g. when adding a new track to the workspace.
displayName: string;
// A factory function returning the track object.
trackFactory: (ctx: TrackContext) => TrackLike;
// The track "kind" Uued by various subsystems e.g. aggregation controllers.
// This is where "XXX_TRACK_KIND" values should be placed.
// TODO(stevegolton): This will be deprecated once we handle group selections
// in a more generic way - i.e. EventSet.
kind: string;
// An optional list of track IDs represented by this trace.
// This list is used for participation in track indexing by track ID.
// This index is used by various subsystems to find links between tracks based
// on the track IDs used by trace processor.
trackIds?: number[];
// Optional: The CPU number associated with this track.
cpu?: number;
// Optional: A list of tags used for sorting, grouping and "chips".
tags?: TrackTags;
}
// Tracks within track groups (usually corresponding to processes) are sorted.
// As we want to group all tracks related to a given thread together, we use
// two keys:
// - Primary key corresponds to a priority of a track block (all tracks related
// to a given thread or a single track if it's not thread-associated).
// - Secondary key corresponds to a priority of a given thread-associated track
// within its thread track block.
// Each track will have a sort key, which either a primary sort key
// (for non-thread tracks) or a tid and secondary sort key (mapping of tid to
// primary sort key is done independently).
export enum PrimaryTrackSortKey {
DEBUG_SLICE_TRACK,
NULL_TRACK,
PROCESS_SCHEDULING_TRACK,
PROCESS_SUMMARY_TRACK,
EXPECTED_FRAMES_SLICE_TRACK,
ACTUAL_FRAMES_SLICE_TRACK,
PERF_SAMPLES_PROFILE_TRACK,
HEAP_PROFILE_TRACK,
MAIN_THREAD,
RENDER_THREAD,
GPU_COMPLETION_THREAD,
CHROME_IO_THREAD,
CHROME_COMPOSITOR_THREAD,
ORDINARY_THREAD,
COUNTER_TRACK,
ASYNC_SLICE_TRACK,
ORDINARY_TRACK,
}
// Similar to PluginContext but with additional properties to operate on the
// currently loaded trace. Passed to trace-relevant hooks instead of
// PluginContext.
export interface TracePluginContext<T = undefined> extends PluginContext {
readonly engine: EngineProxy;
readonly store: Store<T>;
// Add a new track from this plugin. The track is just made available here,
// it's not automatically shown until it's added to a workspace.
addTrack(trackDetails: PluginTrackInfo): void;
// Suggest a track be added to the workspace on a fresh trace load.
// Supersedes `findPotentialTracks()` which has been removed.
// Note: this API will be deprecated soon.
suggestTrack(trackInfo: TrackInfo): void;
}
export interface BasePlugin<State> {
// Lifecycle methods.
onActivate(ctx: PluginContext): void;
onTraceLoad?(ctx: TracePluginContext<State>): Promise<void>;
onTraceUnload?(ctx: TracePluginContext<State>): Promise<void>;
onDeactivate?(ctx: PluginContext): void;
// Extension points.
metricVisualisations?(ctx: PluginContext): MetricVisualisation[];
}
export interface StatefulPlugin<State> extends BasePlugin<State> {
// Function to migrate the persistent state.
migrate(initialState: unknown): State;
}
// Generic interface all plugins must implement.
// If a state type is passed, the plugin must implement migrate(). Otherwise if
// the state type is omitted, migrate need not be defined.
export type Plugin<State = undefined> =
State extends undefined ? BasePlugin<State>: StatefulPlugin<State>;
// This interface defines what a plugin factory should look like.
// This can be defined in the plugin class definition by defining a constructor
// and the relevant static methods:
// E.g.
// class MyPlugin implements TracePlugin<MyState> {
// migrate(initialState: unknown): MyState {...}
// constructor(store: Store<MyState>, engine: EngineProxy) {...}
// ... methods from the TracePlugin interface go here ...
// }
// ... which can then be passed around by class i.e. MyPlugin
export interface PluginClass<T> {
// Instantiate the plugin.
new(): Plugin<T>;
}
export interface TrackInfo {
// A human readable name for this specific track. It will normally be
// displayed on the left-hand-side of the track.
name: string;
// Used to define default sort order for new traces.
// Note: sortKey will be deprecated soon in favour of tags.
sortKey: PrimaryTrackSortKey;
// URI of the suggested track.
uri: string;
}
// A predicate for selecting a groups of tracks.
export type TrackPredicate = (info: TrackTags) => boolean;
interface WellKnownTrackTags {
// A human readable name for this specific track.
name: string;
// Controls whether to show the "metric" chip.
metric: boolean;
// Controls whether to show the "debuggable" chip.
debuggable: boolean;
}
// An set of key/value pairs describing a given track. These are used for
// selecting tracks to pin/unpin, diplsaying "chips" in the track shell, and
// (in future) the sorting and grouping of tracks.
// We define a handful of well known fields, and the rest are arbitrary key-
// value pairs.
export type TrackTags = Partial<WellKnownTrackTags>&{
// There may be arbitrary other key/value pairs.
[key: string]: string|number|boolean|undefined;
}
// Plugins can be passed as class refs, factory functions, or concrete plugin
// implementations.
export type PluginFactory<T> = PluginClass<T>|Plugin<T>|(() => Plugin<T>);
export interface PluginInfo<T = undefined> {
// A unique string for your plugin. To ensure the name is unique you
// may wish to use a URL with reversed components in the manner of
// Java package names.
pluginId: string;
// The plugin factory used to instantiate the plugin object, or if this is
// an actual plugin implementation, it's just used as-is.
plugin: PluginFactory<T>;
}