// 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 {Engine} from '../trace_processor/engine'; import {UntypedEventSet} from '../core/event_set'; export {Engine} 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 startNs: time; readonly endNs: time; readonly durNs: 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: Engine; // 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; // Optional: Track key key?: string; // Optional: Whether the track is pinned isPinned?: boolean; } // 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; }