blob: a8d29499f1df8d8a36163b39b2a64e3d3796601a [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 {Span, duration, time} from '../base/time';
import {Migrate, Store} from '../base/store';
import {ColorScheme} from '../core/colorizer';
import {LegacySelection} from '../common/state';
import {PanelSize} from '../frontend/panel';
import {EngineProxy} from '../trace_processor/engine';
import {UntypedEventSet} from '../core/event_set';
export {EngineProxy} from '../trace_processor/engine';
export {
LONG,
LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../trace_processor/query_result';
export {BottomTabToSCSAdapter} from './utils';
export {createStore, Migrate, Store} from '../base/store';
// This is a temporary fix until this is available in the plugin API.
export {
createDebugSliceTrackActions,
addDebugSliceTrack,
} from '../frontend/debug_tracks';
export interface Slice {
// These properties are updated only once per query result when the Slice
// object is created and don't change afterwards.
readonly id: number;
readonly startNsQ: time;
readonly endNsQ: time;
readonly durNsQ: duration;
readonly ts: time;
readonly dur: duration;
readonly depth: number;
readonly flags: number;
// Each slice can represent some extra numerical information by rendering a
// portion of the slice with a lighter tint.
// |fillRatio\ describes the ratio of the normal area to the tinted area
// width of the slice, normalized between 0.0 -> 1.0.
// 0.0 means the whole slice is tinted.
// 1.0 means none of the slice is tinted.
// E.g. If |fillRatio| = 0.65 the slice will be rendered like this:
// [############|*******]
// ^------------^-------^
// Normal Light
readonly fillRatio: number;
// These can be changed by the Impl.
title: string;
subTitle: string;
colorScheme: ColorScheme;
isHighlighted: boolean;
}
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.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (...args: any[]) => any;
// 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[];
}
// 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 {
// The unique ID for this plugin.
readonly pluginId: string;
// Register command against this plugin context.
registerCommand(command: Command): void;
// Run a command, optionally passing some args.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
runCommand(id: string, ...args: any[]): any;
// Control of the sidebar.
sidebar: {
// Show the sidebar.
show(): void;
// Hide the sidebar.
hide(): void;
// Returns true if the sidebar is visible.
isVisible(): boolean;
};
}
export interface TrackContext {
// This track's key, used for making selections et al.
trackKey: string;
// Set of params passed in when the track was created.
params: unknown;
// 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>;
}
export interface SliceRect {
left: number;
width: number;
top: number;
height: number;
visible: boolean;
}
export interface Track {
/**
* Optional: Called once before onUpdate is first called.
*
* If this function returns a Promise, this promise is awaited before onUpdate
* or onDestroy is called. Any calls made to these functions in the meantime
* will be queued up and the hook will be called later once onCreate returns.
*
* Exactly when this hook is called is left purposely undefined. The only
* guarantee is that it will be called once before onUpdate is first called.
*
* @param ctx Our track context object.
*/
onCreate?(ctx: TrackContext): Promise<void> | void;
/**
* Optional: Called every render cycle while the track is visible, just before
* render().
* If this function returns a Promise, this promise is awaited before another
* onUpdate is called or onDestroy is called.
*/
onUpdate?(): Promise<void> | void;
/**
* Optional: Called when the track is no longer visible. Should be used to
* clean up resources.
* This function can return nothing or a promise. The promise is currently
* ignored.
*/
onDestroy?(): Promise<void> | void;
render(ctx: CanvasRenderingContext2D, size: PanelSize): void;
onFullRedraw?(): void;
getSliceRect?(tStart: time, tEnd: time, depth: number): SliceRect | undefined;
getHeight(): number;
getTrackShellButtons?(): m.Children;
onMouseMove?(position: {x: number; y: number}): void;
onMouseClick?(position: {x: number; y: number}): boolean;
onMouseOut?(): void;
/**
* Optional: Get the event set that represents this track's data.
*/
getEventSet?(): UntypedEventSet;
}
// A definition of a track, including a renderer implementation and metadata.
export interface TrackDescriptor {
// A unique identifier for this track.
uri: string;
// A factory function returning a new track instance.
trackFactory: (ctx: TrackContext) => Track;
// The track "kind", used 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;
// 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: The UTID associated with this track.
utid?: number;
// Optional: The UPID associated with this track.
upid?: number;
// Optional: A list of tags used for sorting, grouping and "chips".
tags?: TrackTags;
// Placeholder - presently unused.
displayName?: string;
}
// 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_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,
}
export interface SliceTrackColNames {
ts: string;
name: string;
dur: string;
}
export interface DebugSliceTrackArgs {
// Title of the track. If omitted a placeholder name will be chosen instead.
trackName?: string;
// Mapping definitions of the 'ts', 'dur', and 'name' columns.
// By default, columns called ts, dur and name will be used.
// If dur is assigned the value '0', all slices shall be instant events.
columnMapping?: Partial<SliceTrackColNames>;
// Any extra columns to be used as args.
args?: string[];
// Optional renaming of columns.
columns?: string[];
}
export interface CounterTrackColNames {
ts: string;
value: string;
}
export interface DebugCounterTrackArgs {
// Title of the track. If omitted a placeholder name will be chosen instead.
trackName?: string;
// Mapping definitions of the ts and value columns.
columnMapping?: Partial<CounterTrackColNames>;
}
export interface Tab {
render(): m.Children;
getTitle(): string;
}
export interface TabDescriptor {
uri: string; // TODO(stevegolton): Maybe optional for ephemeral tabs.
content: Tab;
isEphemeral?: boolean; // Defaults false
onHide?(): void;
onShow?(): void;
}
export interface DetailsPanel {
render(selection: LegacySelection): m.Children;
isLoading?(): boolean;
}
// Similar to PluginContext but with additional methods to operate on the
// currently loaded trace. Passed to trace-relevant hooks on a plugin instead of
// PluginContext.
export interface PluginContextTrace extends PluginContext {
readonly engine: EngineProxy;
// Control over the main timeline.
timeline: {
// Add a new track to the scrolling track section, returning the newly
// created track key.
addTrack(uri: string, displayName: string, params?: unknown): string;
// Remove a single track from the timeline.
removeTrack(key: string): void;
// Pin a single track.
pinTrack(key: string): void;
// Unpin a single track.
unpinTrack(key: string): void;
// Pin all tracks that match a predicate.
pinTracksByPredicate(predicate: TrackPredicate): void;
// Unpin all tracks that match a predicate.
unpinTracksByPredicate(predicate: TrackPredicate): void;
// Remove all tracks that match a predicate.
removeTracksByPredicate(predicate: TrackPredicate): void;
// Expand all groups that match a predicate.
expandGroupsByPredicate(predicate: GroupPredicate): void;
// Collapse all groups that match a predicate.
collapseGroupsByPredicate(predicate: GroupPredicate): void;
// Retrieve a list of tracks on the timeline.
tracks: TrackRef[];
// Bring a timestamp into view.
panToTimestamp(ts: time): void;
// Move the viewport
setViewportTime(start: time, end: time): void;
// A span representing the current viewport location
readonly viewport: Span<time, duration>;
};
// Control over the bottom details pane.
tabs: {
// Creates a new tab running the provided query.
openQuery(query: string, title: string): void;
// Add a tab to the tab bar (if not already) and focus it.
showTab(uri: string): void;
// Remove a tab from the tab bar.
hideTab(uri: string): void;
};
// Register a new track against a unique key known as a URI.
// Once a track is registered it can be referenced multiple times on the
// timeline with different params to allow customising each instance.
registerTrack(trackDesc: TrackDescriptor): void;
// Add a new entry to the pool of default tracks. Default tracks are a list
// of track references that describe the list of tracks that should be added
// to the main timeline on startup.
// Default tracks are only used when a trace is first loaded, not when
// loading from a permalink, where the existing list of tracks from the
// shared state is used instead.
addDefaultTrack(track: TrackRef): void;
// Simultaneously register a track and add it as a default track in one go.
// This is simply a helper which calls registerTrack() and addDefaultTrack()
// with the same URI.
registerStaticTrack(track: TrackDescriptor & TrackRef): void;
// Register a new tab for this plugin. Will be unregistered when the plugin
// is deactivated or when the trace is unloaded.
registerTab(tab: TabDescriptor): void;
// Suggest that a tab should be shown immediately.
addDefaultTab(uri: string): void;
// Register a hook into the current selection tab rendering logic that allows
// customization of the current selection tab content.
registerDetailsPanel(sel: DetailsPanel): void;
// Create a store mounted over the top of this plugin's persistent state.
mountStore<T>(migrate: Migrate<T>): Store<T>;
trace: {
// A span representing the start and end time of the trace
readonly span: Span<time, duration>;
};
}
export interface Plugin {
// Lifecycle methods.
onActivate?(ctx: PluginContext): void;
onTraceLoad?(ctx: PluginContextTrace): Promise<void>;
onTraceUnload?(ctx: PluginContextTrace): Promise<void>;
onDeactivate?(ctx: PluginContext): void;
// Extension points.
metricVisualisations?(ctx: PluginContext): MetricVisualisation[];
}
// 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 {
// Instantiate the plugin.
new (): Plugin;
}
// Describes a reference to a registered track.
export interface TrackRef {
// URI of the registered track.
uri: string;
// A human readable name for this track - displayed in the track shell.
displayName: string;
// Optional: An opaque object used to customize this instance of the track.
params?: unknown;
// Optional: Used to define default sort order for new traces.
// Note: This will be deprecated soon in favour of tags & sort rules.
sortKey?: PrimaryTrackSortKey;
// Optional: Add tracks to a group with this name.
groupName?: string;
}
// A predicate for selecting a subset of tracks.
export type TrackPredicate = (info: TrackTags) => boolean;
// Describes a reference to a group of tracks.
export interface GroupRef {
// A human readable name for this track group.
displayName: string;
// True if the track is open else false.
collapsed: boolean;
}
// A predicate for selecting a subset of groups.
export type GroupPredicate = (info: GroupRef) => 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 class refs or concrete plugin implementations.
export type PluginFactory = PluginClass | Plugin;
export interface PluginDescriptor {
// 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;
}