Add docs a lot of public interfaces Change-Id: I875fc21c5a7a5e1e3b359cbb2b50a98e789cc7a8
diff --git a/python/tools/check_ratchet.py b/python/tools/check_ratchet.py index 8ea5a0d..3395f27 100755 --- a/python/tools/check_ratchet.py +++ b/python/tools/check_ratchet.py
@@ -36,7 +36,7 @@ from dataclasses import dataclass -EXPECTED_ANY_COUNT = 34 +EXPECTED_ANY_COUNT = 32 EXPECTED_RUN_METRIC_COUNT = 4 ROOT_DIR = os.path.dirname(
diff --git a/ui/src/plugins/com.android.AndroidNetwork/index.ts b/ui/src/plugins/com.android.AndroidNetwork/index.ts index 10e5c43..9dcc7dd 100644 --- a/ui/src/plugins/com.android.AndroidNetwork/index.ts +++ b/ui/src/plugins/com.android.AndroidNetwork/index.ts
@@ -43,19 +43,21 @@ ctx.commands.registerCommand({ id: 'com.android.AddBatteryEventsTrack', name: 'Add track: battery events', - callback: async (track) => { - if (track === undefined) { - track = await ctx.omnibox.prompt('Battery Track'); - if (track === undefined) return; - } + callback: async (rawTrackName) => { + const trackName = await (async function () { + if (typeof rawTrackName === 'string') return rawTrackName; + return await ctx.omnibox.prompt('Battery Track'); + })(); + + if (!trackName) return; await ctx.engine.query(`SELECT IMPORT('android.battery_stats');`); await this.addSimpleTrack( ctx, - track, + trackName, `(SELECT * FROM android_battery_stats_event_slices - WHERE track_name = "${track}")`, + WHERE track_name = "${trackName}")`, ['ts', 'dur', 'str_value', 'int_value'], ); }, @@ -64,16 +66,23 @@ ctx.commands.registerCommand({ id: 'com.android.AddNetworkActivityTrack', name: 'Add track: network activity', - callback: async (groupby, filter, trackName) => { - if (groupby === undefined) { - groupby = await ctx.omnibox.prompt('Group by', 'package_name'); - if (groupby === undefined) return; - } + callback: async (rawGroupby, rawFilter, rawTrackName) => { + const groupby = await (async function () { + if (typeof rawGroupby === 'string') return rawGroupby; + return await ctx.omnibox.prompt('Group by', 'package_name'); + })(); - if (filter === undefined) { - filter = await ctx.omnibox.prompt('Filter', 'TRUE'); - if (filter === undefined) return; - } + const filter = await (async function () { + if (typeof rawFilter === 'string') return rawFilter; + return await ctx.omnibox.prompt('Group by', 'package_name'); + })(); + + if (!groupby || !filter) return; + + const trackName = await (async function () { + if (typeof rawTrackName === 'string') return rawTrackName; + return 'Network Activity'; + })(); const suffix = new Date().getTime(); await ctx.engine.query(` @@ -91,7 +100,7 @@ const groupCols = groupby.replaceAll(' ', '').split(','); await this.addSimpleTrack( ctx, - trackName ?? 'Network Activity', + trackName, `android_network_activity_${suffix}`, ['ts', 'dur', ...groupCols, 'packet_length', 'packet_count'], );
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts index 37132e9..a21f812 100644 --- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts +++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
@@ -24,7 +24,7 @@ metricsFromTableOrSubquery, QueryFlamegraph, } from '../../components/query_flamegraph'; -import {MinimapRow} from '../../public/minimap'; +import {MinimapCell, MinimapRow} from '../../public/minimap'; import {PerfettoPlugin} from '../../public/plugin'; import {AreaSelection, areaSelectionsEqual} from '../../public/selection'; import {Trace} from '../../public/trace'; @@ -460,7 +460,7 @@ upid; `); - const slicesData = new Map<number, MinimapRow>(); + const slicesData = new Map<number, MinimapCell[]>(); const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM}); for (; it.valid(); it.next()) { const bucket = it.bucket;
diff --git a/ui/src/public/analytics.ts b/ui/src/public/analytics.ts index 653b1ea..066903f 100644 --- a/ui/src/public/analytics.ts +++ b/ui/src/public/analytics.ts
@@ -16,8 +16,33 @@ export type TraceCategories = 'Trace Actions' | 'Record Trace' | 'User Actions'; +/** + * Logs analytics events and errors. + * + * Use this to track user actions, trace operations, and error conditions. + * Events are categorized (e.g., 'Trace Actions', 'User Actions') and can + * be used to understand how users interact with your plugin. + */ export interface Analytics { + /** + * Logs a generic analytics event. + * + * @param category The category of the event (e.g., 'Trace Actions'). + * @param event The name of the event (e.g., 'Save trace'). + */ logEvent(category: TraceCategories | null, event: string): void; + + /** + * Logs an error event. + * + * @param err The error details to log. + */ logError(err: ErrorDetails): void; + + /** + * Checks if analytics is enabled. + * + * @returns `true` if analytics is enabled, `false` otherwise. + */ isEnabled(): boolean; }
diff --git a/ui/src/public/command.ts b/ui/src/public/command.ts index abf884f..c1aa105 100644 --- a/ui/src/public/command.ts +++ b/ui/src/public/command.ts
@@ -14,29 +14,87 @@ import {Hotkey} from '../base/hotkeys'; +/** + * Manages the registration and execution of commands. + * + * Commands are user-invokable actions that can be triggered via hotkeys, + * the command palette, or programmatically. Use this to register plugin + * commands that users can execute. + */ export interface CommandManager { + /** + * Registers a new command. + * + * The command is uniquely identified by its `id`. If a command with the same + * `id` is already registered, this method will throw an error. + * + * @param command The command to register. + */ registerCommand(command: Command): void; + /** + * Checks if a command with the given `id` is registered. + * + * @param commandId The unique identifier of the command. + * @returns `true` if the command is registered, `false` otherwise. + */ hasCommand(commandId: string): boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - runCommand(id: string, ...args: any[]): any; + /** + * Executes a command by its `id`. + * + * Any additional arguments are passed to the command's `callback` function. + * + * @param id The unique identifier of the command to run. + * @param args A list of arguments to pass to the command's callback. + * @returns The result of the command's callback, if any. + */ + runCommand(id: string, ...args: unknown[]): unknown; } +/** + * Represents a command that can be executed within the application. + * + * A command is a self-contained unit of work that can be invoked by the user + * or programmatically. It includes a unique identifier, a human-readable name, + * a callback function to execute, and an optional default hotkey. + */ 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; + /** + * A unique identifier for this command. + * + * This `id` is used to register, unregister, and execute the command. It is + * recommended to use a namespace to avoid collisions (e.g., + * `myPlugin.myCommand`). + */ + readonly id: string; + + /** + * A human-friendly name for this command. + * + * This name is displayed to the user in the command palette and other UI + * elements. It should be concise and descriptive. + */ + readonly name: string; + + /** + * The function to call when the command is invoked. + * + * This function receives any arguments passed to `runCommand` and can return + * a value. + */ + callback(...args: unknown[]): unknown; + + /** + * The default hotkey for this command. + * + * This is just the default and may be changed by the user in the settings. + * See `hotkeys.ts` for guidance on hotkey syntax. + * + * @example + * - 'P' + * - 'Shift+P' + * - '!Mod+Shift+P' + */ + readonly defaultHotkey?: Hotkey; }
diff --git a/ui/src/public/feature_flag.ts b/ui/src/public/feature_flag.ts index c82d38a..f076f3f 100644 --- a/ui/src/public/feature_flag.ts +++ b/ui/src/public/feature_flag.ts
@@ -12,53 +12,141 @@ // See the License for the specific language governing permissions and // limitations under the License. +/** + * Manages feature flags for experimental or togglable features. + * + * Feature flags allow plugins to expose experimental functionality that + * users can enable/disable. Flags are persisted across sessions and can be + * configured on the flags page. + */ export interface FeatureFlagManager { + /** + * Registers a new feature flag. + * + * @param settings The settings for the new feature flag. + * @returns The registered feature flag. + */ register(settings: FlagSettings): Flag; } +/** + * Settings for defining a new feature flag. + */ export interface FlagSettings { - id: string; - defaultValue: boolean; - description: string; - name?: string; - devOnly?: boolean; -} - -export interface Flag { - // A unique identifier for this flag ("magicSorting") + /** + * A unique identifier for this flag (e.g., "magicSorting"). + */ readonly id: string; - // The name of the flag the user sees ("New track sorting algorithm") - readonly name: string; - - // A longer description which is displayed to the user. - // "Sort tracks using an embedded tfLite model based on your expression - // while waiting for the trace to load." - readonly description: string; - - // Whether the flag defaults to true or false. - // If !flag.isOverridden() then flag.get() === flag.defaultValue + /** + * The default value of the flag (true or false). + * + * If `flag.isOverridden()` is false, then `flag.get()` will return + * `flag.defaultValue`. + */ readonly defaultValue: boolean; - // Get the current value of the flag. + /** + * A longer description which is displayed to the user. + * + * Example: "Sort tracks using an embedded tfLite model based on your + * expression while waiting for the trace to load." + */ + readonly description: string; + + /** + * The name of the flag the user sees (e.g., "New track sorting algorithm"). + * If omitted, the `id` will be used as the name. + */ + readonly name?: string; + + /** + * If true, this flag will only be visible and configurable in development + * builds of the Perfetto UI. + */ + readonly devOnly?: boolean; +} + +/** + * Represents a feature flag that can be enabled or disabled. + */ +export interface Flag { + /** + * A unique identifier for this flag (e.g., "magicSorting"). + */ + readonly id: string; + + /** + * The name of the flag the user sees (e.g., "New track sorting algorithm"). + */ + readonly name: string; + + /** + * A longer description which is displayed to the user. + * + * Example: "Sort tracks using an embedded tfLite model based on your + * expression while waiting for the trace to load." + */ + readonly description: string; + + /** + * Whether the flag defaults to true or false. + * + * If `!flag.isOverridden()`, then `flag.get()` will return + * `flag.defaultValue`. + */ + readonly defaultValue: boolean; + + /** + * Get the current value of the flag. + * + * @returns The current boolean value of the flag. + */ get(): boolean; - // Override the flag and persist the new value. + /** + * Override the flag and persist the new value. + * + * This will change the flag's value for the current and future sessions. + * + * @param value The new boolean value for the flag. + */ set(value: boolean): void; - // If the flag has been overridden. - // Note: A flag can be overridden to its default value. + /** + * Checks if the flag has been explicitly overridden by the user. + * + * Note: A flag can be overridden to its default value. + * + * @returns `true` if the flag's value has been explicitly set, `false` + * otherwise. + */ isOverridden(): boolean; - // Reset the flag to its default setting. + /** + * Reset the flag to its default setting. + * + * This will remove any user override and revert the flag to its + * `defaultValue`. + */ reset(): void; - // Get the current state of the flag. + /** + * Get the current state of the flag's override status. + * + * @returns The {@link OverrideState} of the flag. + */ overriddenState(): OverrideState; } +/** + * Represents the override state of a feature flag. + */ export enum OverrideState { + /** The flag is currently using its default value. */ DEFAULT = 'DEFAULT', + /** The flag has been overridden to `true`. */ TRUE = 'OVERRIDE_TRUE', + /** The flag has been overridden to `false`. */ FALSE = 'OVERRIDE_FALSE', }
diff --git a/ui/src/public/minimap.ts b/ui/src/public/minimap.ts index f78c3de..d4bcf54 100644 --- a/ui/src/public/minimap.ts +++ b/ui/src/public/minimap.ts
@@ -15,22 +15,61 @@ import {HighPrecisionTimeSpan} from '../base/high_precision_time_span'; import {duration, time} from '../base/time'; +/** + * Represents a single cell in the minimap, containing data for a specific time + * range. + */ export interface MinimapCell { + /** + * The start timestamp of the cell. + */ readonly ts: time; + + /** + * The duration of the cell. + */ readonly dur: duration; + + /** + * The load value for the cell, typically a normalized value between 0 and 1. + */ readonly load: number; } -export type MinimapRow = MinimapCell[]; +/** + * Represents a row of data in the minimap. + */ +export type MinimapRow = readonly MinimapCell[]; +/** + * Provides content for the minimap. + */ export interface MinimapContentProvider { + /** + * The priority of this content provider. Higher priority providers are + * preferred. + */ readonly priority: number; + + /** + * Gets the data for the minimap for a given time span and resolution. + * @param timeSpan The time span for which to provide data. + * @param resolution The resolution at which to provide the data. + * @returns A promise that resolves to an array of minimap rows. + */ getData( timeSpan: HighPrecisionTimeSpan, resolution: duration, ): Promise<MinimapRow[]>; } +/** + * Manages content providers for the minimap. + */ export interface MinimapManager { - registerContentProvider(x: MinimapContentProvider): void; + /** + * Registers a new content provider for the minimap. + * @param provider The content provider to register. + */ + registerContentProvider(provider: MinimapContentProvider): void; }
diff --git a/ui/src/public/note.ts b/ui/src/public/note.ts index 20b462a..08276ce 100644 --- a/ui/src/public/note.ts +++ b/ui/src/public/note.ts
@@ -14,45 +14,127 @@ import {time} from '../base/time'; +/** + * Manages notes and span notes on the timeline. + * + * Notes are flags on the timeline marker, while span notes represent flagged + * ranges. + */ export interface NoteManager { + /** + * Retrieves a note or span note by its ID. + * @param id The unique identifier of the note. + * @returns The note or span note if found, or `undefined`. + */ getNote(id: string): Note | SpanNote | undefined; - // Adds a note (a flag on the timeline marker). Returns the id. + /** + * Adds a new note (a flag on the timeline marker). + * @param args The arguments for adding the note. + * @returns The unique ID of the newly added note. + */ addNote(args: AddNoteArgs): string; - // Adds a span note (a flagged range). Returns the id. + /** + * Adds a new span note (a flagged range). + * @param args The arguments for adding the span note. + * @returns The unique ID of the newly added span note. + */ addSpanNote(args: AddSpanNoteArgs): string; } +/** + * Arguments for adding a new note. + */ export interface AddNoteArgs { + /** + * The timestamp of the note. + */ readonly timestamp: time; - readonly color?: string; // Default: randomColor(). - readonly text?: string; // Default: ''. - // The id is optional. If present, allows overriding a previosly created note. - // If not present it will be auto-assigned with a montonic counter. + /** + * The color of the note. If not provided, a random color will be assigned. + */ + readonly color?: string; + /** + * The text content of the note. If not provided, an empty string will be used. + */ + readonly text?: string; + /** + * The unique ID of the note. If provided, it allows overriding a previously + * created note. If not present, an ID will be auto-assigned with a monotonic + * counter. + */ readonly id?: string; } +/** + * Represents a note (a flag on the timeline marker). + */ export interface Note extends AddNoteArgs { + /** + * The type of the note, always 'DEFAULT' for a regular note. + */ readonly noteType: 'DEFAULT'; + /** + * The unique ID of the note. + */ readonly id: string; + /** + * The color of the note. + */ readonly color: string; + /** + * The text content of the note. + */ readonly text: string; } +/** + * Arguments for adding a new span note. + */ export interface AddSpanNoteArgs { + /** + * The start timestamp of the span note. + */ readonly start: time; + /** + * The end timestamp of the span note. + */ readonly end: time; - readonly color?: string; // Default: randomColor(). - readonly text?: string; // Default: ''. - // The id is optional. If present, allows overriding a previosly created note. - // If not present it will be auto-assigned with a montonic counter. + /** + * The color of the span note. If not provided, a random color will be assigned. + */ + readonly color?: string; + /** + * The text content of the span note. If not provided, an empty string will be used. + */ + readonly text?: string; + /** + * The unique ID of the span note. If provided, it allows overriding a previously + * created span note. If not present, an ID will be auto-assigned with a monotonic + * counter. + */ readonly id?: string; } +/** + * Represents a span note (a flagged range). + */ export interface SpanNote extends AddSpanNoteArgs { + /** + * The type of the note, always 'SPAN' for a span note. + */ readonly noteType: 'SPAN'; + /** + * The unique ID of the span note. + */ readonly id: string; + /** + * The color of the span note. + */ readonly color: string; + /** + * The text content of the span note. + */ readonly text: string; }
diff --git a/ui/src/public/omnibox.ts b/ui/src/public/omnibox.ts index daa2a83..cfd2733 100644 --- a/ui/src/public/omnibox.ts +++ b/ui/src/public/omnibox.ts
@@ -12,6 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +/** + * Provides non-modal user prompts integrated into the UI. + * + * The omnibox can prompt users for free-form text input or selection from a + * list of choices. Think of it as window.prompt() but non-blocking and + * better integrated with the UI. Returns a promise that resolves to the + * user's input or undefined if dismissed. + */ export interface OmniboxManager { /** * Turns the omnibox into an interactive prompt for the user. Think of @@ -70,7 +78,23 @@ prompt<T>(text: string, choices: PromptChoices<T>): Promise<T | undefined>; } +/** + * Represents a set of choices for the omnibox prompt. + * + * This interface allows for providing complex objects as choices, with a + * custom function to extract the display name for each choice. + * @template T The type of the values in the choices list. + */ export interface PromptChoices<T> { - values: ReadonlyArray<T>; - getName: (x: T) => string; + /** + * An array of values that the user can choose from. + */ + readonly values: ReadonlyArray<T>; + + /** + * A function that returns the display name for a given choice value. + * @param x The choice value. + * @returns The string representation of the choice. + */ + getName(x: T): string; }
diff --git a/ui/src/public/page.ts b/ui/src/public/page.ts index 82a5bb9..30b5c6e 100644 --- a/ui/src/public/page.ts +++ b/ui/src/public/page.ts
@@ -15,24 +15,46 @@ import m from 'mithril'; /** - * Allows to register custom page endpoints that response to given routes, e.g. - * /viewer, /record etc. + * Manages custom page registration and routing. + * + * Use this to register pages that respond to specific routes (e.g., + * '/settings', '/query'). Pages are automatically unregistered when the + * trace is closed or the plugin is unloaded. */ export interface PageManager { /** - * Example usage: - * registerPage({route: '/foo', page: FooPage}) - * class FooPage implements m.ClassComponent<PageWithTrace> { - * view({attrs}: m.CVnode<PageWithTrace>) { - * return m('div', ... - * onclick: () => attrs.trace.timeline.zoom(...); - * ) - * } + * Registers a new custom page handler. + * + * The page handler defines the route it responds to and the content to + * render. Returns a `Disposable` that can be used to unregister the page. + * + * @param pageHandler The page handler to register. + * @returns A `Disposable` to unregister the page. + * + * @example + * ```ts + * // Example usage: + * registerPage({route: '/foo', render: (subpage) => m(FooPage, {subpage})}) + * + * class FooPage implements m.ClassComponent<{subpage?: string}> { + * view({attrs}: m.CVnode<{subpage?: string}>) { + * return m('div', + * m('h1', `Foo Page ${attrs.subpage ? `(${attrs.subpage})` : ''}`), + * m('button', {onclick: () => console.log('Button clicked')}, 'Click me') + * ); * } + * } + * ``` */ registerPage(pageHandler: PageHandler): Disposable; } +/** + * Defines a handler for a custom page. + * + * A page handler specifies the route it responds to and provides a render + * function to display its content. + */ export interface PageHandler { /** * The route path this page handler responds to (e.g., '/', '/viewer'). @@ -50,7 +72,8 @@ * Renders the page content. * Called during each Mithril render cycle. * - * @param subpage Optional subpage path segment after the main route + * @param subpage Optional subpage path segment after the main route. + * @returns The Mithril children to render for the page. */ - readonly render: (subpage: string | undefined) => m.Children; + render(subpage: string | undefined): m.Children; }
diff --git a/ui/src/public/plugin.ts b/ui/src/public/plugin.ts index 0891433..a693431 100644 --- a/ui/src/public/plugin.ts +++ b/ui/src/public/plugin.ts
@@ -24,13 +24,65 @@ * * On trace load, the core will create a new class instance by calling new on * this constructor and then call its onTraceLoad() function. + * @template T The type of the plugin instance. */ export interface PerfettoPluginStatic<T extends PerfettoPlugin> { + /** + * A unique identifier for the plugin. + * + * This ID is used to identify the plugin within the Perfetto UI. It is + * recommended to use a reverse domain name style (e.g., + * `dev.perfetto.MyPlugin`). + */ readonly id: string; + + /** + * An optional human-readable description of the plugin. + * + * This description may be displayed in the UI to provide more information + * about the plugin's functionality. + */ readonly description?: string; + + /** + * An optional list of other plugins that this plugin depends on. + * + * The Perfetto UI ensures that all dependencies are loaded before activating + * this plugin. + */ readonly dependencies?: ReadonlyArray<PerfettoPluginStatic<PerfettoPlugin>>; + + /** + * Called when the plugin is activated, before a trace has been loaded. + * + * This method is suitable for registering commands, sidebar items, or pages + * that are not dependent on a loaded trace. + * + * @param app The {@link App} instance, providing access to app-wide + * functionality. + * @param args The initial route arguments when the app was loaded. + */ onActivate?(app: App, args: RouteArgs): void; + + /** + * Returns a list of metric visualisations provided by this plugin. + * + * Metric visualisations are used to display data from trace metrics in a + * graphical format. + * + * @returns An array of {@link MetricVisualisation} objects. + */ metricVisualisations?(): MetricVisualisation[]; + + /** + * The constructor for the plugin's trace-scoped instance. + * + * This constructor is called when a trace is loaded, creating a new instance + * of the plugin that is scoped to the loaded trace. + * + * @param trace The {@link Trace} instance, providing access to trace-scoped + * functionality. + */ new (trace: Trace): T; } @@ -39,43 +91,98 @@ * is created from the class constructor above at trace load time. */ export interface PerfettoPlugin { + /** + * Called when a trace is loaded. + * + * This method is suitable for performing trace-specific initialization, such + * as querying trace data or registering trace-scoped UI elements. + * + * @param ctx The {@link Trace} instance, providing access to trace-scoped + * functionality. + * @param args Optional arguments passed to the plugin during trace loading. + */ onTraceLoad?(ctx: Trace, args?: {[key: string]: unknown}): Promise<void>; } +/** + * Represents a metric visualisation provided by a plugin. + * + * Metric visualisations are used to display data from trace metrics in a + * graphical format, typically using Vega or Vega-Lite specifications. + */ export interface MetricVisualisation { - // The name of the metric e.g. 'android_camera' + /** + * The name of the metric (e.g., 'android_camera'). + * + * This name corresponds to a metric generated by the trace processor. + */ 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) + /** + * A Vega or Vega-Lite visualisation specification. + * + * The data from the metric, extracted by the `path` property, will be + * exposed as a datasource named "metric" within the Vega(-Lite) spec. + */ 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. + /** + * A path index into the metric data. + * + * This path specifies how to extract the relevant data from the metric's + * protobuf output. For example, if the metric returns: + * ```protobuf + * { + * foo { + * bar { + * baz: { name: "a" } + * baz: { name: "b" } + * baz: { name: "c" } + * } + * } + * } + * ``` + * This becomes the following JSON: + * ```json + * { "foo": { "bar": { "baz": [ + * {"name": "a"}, + * {"name": "b"}, + * {"name": "c"}, + * ]}}} + * ``` + * And given `path = ["foo", "bar", "baz"]`, the extracted data + * `[ {"name": "a"}, {"name": "b"}, {"name": "c"} ]` is passed to the + * Vega(-Lite) visualisation. + */ path: string[]; } +/** + * Manages registered plugins and their instances. + * + * Use this to get instances of other plugins (for inter-plugin + * communication) or access metric visualizations. Note that plugin instances + * are only available after a trace has been loaded. + */ export interface PluginManager { + /** + * Retrieves an instance of a registered plugin. + * + * This method can only be called after a trace has been loaded, as plugin + * instances are trace-scoped. + * + * @param plugin The static plugin class (constructor) of the plugin to + * retrieve. + * @returns The trace-scoped instance of the requested plugin. + * @template T The type of the plugin instance. + */ getPlugin<T extends PerfettoPlugin>(plugin: PerfettoPluginStatic<T>): T; + + /** + * Returns a list of all metric visualisations provided by all registered + * plugins. + * + * @returns An array of {@link MetricVisualisation} objects. + */ metricVisualisations(): MetricVisualisation[]; }
diff --git a/ui/src/public/search.ts b/ui/src/public/search.ts index f0db9a6..839572c 100644 --- a/ui/src/public/search.ts +++ b/ui/src/public/search.ts
@@ -15,17 +15,41 @@ import {time} from '../base/time'; import {Track} from './track'; +/** + * Defines the possible sources for a search result. + */ export type SearchSource = 'cpu' | 'log' | 'slice' | 'track' | 'event'; +/** + * Represents a single search result. + */ export interface SearchResult { - eventId: number; - ts: time; - trackUri: string; - source: SearchSource; + /** + * The ID of the event found. + */ + readonly eventId: number; + /** + * The timestamp of the event. + */ + readonly ts: time; + /** + * The URI of the track where the event is located. + */ + readonly trackUri: string; + /** + * The source of the search result. + */ + readonly source: SearchSource; } +/** + * A callback function type for handling search result steps. + */ export type ResultStepEventHandler = (r: SearchResult) => void; +/** + * Represents a filter expression used for searching. + */ export interface FilterExpression { /** * A SQL WHERE clause that filters the events in the selected tracks. The @@ -40,6 +64,9 @@ readonly join?: string; } +/** + * Defines a provider for search functionality. + */ export interface SearchProvider { /** * A human-readable name for this search provider. This is not currently used @@ -49,7 +76,7 @@ /** * Returns a set of tracks that this provider is interested in. - * @param tracks - A list of all tracks we want to search inside. + * @param tracks A list of all tracks we want to search inside. * @returns A subset of tracks that this provider is interested in. */ selectTracks(tracks: ReadonlyArray<Track>): ReadonlyArray<Track>; @@ -61,13 +88,20 @@ * This function is async because it may need to query some data using the * search term before it can return a filter expression. * - * @param searchTerm - The raw search term entered by the user. + * @param searchTerm The raw search term entered by the user. * @returns A promise that resolves to a FilterExpression that is compiled into - * the resulting SQL query. If undefined, this provider will not be used. + * the resulting SQL query. If `undefined`, this provider will not be used. */ getSearchFilter(searchTerm: string): Promise<FilterExpression | undefined>; } +/** + * Manages the registration of search providers. + */ export interface SearchManager { + /** + * Registers a new search provider. + * @param provider The search provider to register. + */ registerSearchProvider(provider: SearchProvider): void; }
diff --git a/ui/src/public/selection.ts b/ui/src/public/selection.ts index cd0bdef..f468894 100644 --- a/ui/src/public/selection.ts +++ b/ui/src/public/selection.ts
@@ -17,51 +17,223 @@ import {duration, time, TimeSpan} from '../base/time'; import {Track} from './track'; +/** + * Represents content that may be in a loading state. + */ export interface ContentWithLoadingFlag { + /** + * Indicates whether the content is currently loading. + */ readonly isLoading: boolean; + /** + * The actual content to be displayed. + */ readonly content: m.Children; } +/** + * Defines a tab within the area selection details panel. + */ export interface AreaSelectionTab { - // Unique id for this tab. + /** + * Unique ID for this tab. + */ readonly id: string; - // A name for this tab. + /** + * A human-readable name for this tab. + */ readonly name: string; - // Defines the sort order of this tab - higher values appear first. + /** + * Defines the sort order of this tab - higher values appear first. + */ readonly priority?: number; /** * Called every Mithril render cycle to render the content of the tab. The * returned content will be displayed inside the current selection tab. * - * If undefined is returned then the tab handle will be hidden, which gives + * If `undefined` is returned then the tab handle will be hidden, which gives * the tab the option to dynamically remove itself from the list of tabs if it * has nothing relevant to show. * - * The |isLoading| flag is used to avoid flickering. If set to true, we keep - * hold of the the previous vnodes, rendering them instead, for up to 50ms + * The `isLoading` flag is used to avoid flickering. If set to `true`, we keep + * hold of the previous vnodes, rendering them instead, for up to 50ms * before switching to the new content. This avoids very fast load times * from causing flickering loading screens, which can be somewhat jarring. + * @param selection The current area selection. + * @returns The content to render, or `undefined` if the tab should be hidden. */ render(selection: AreaSelection): ContentWithLoadingFlag | undefined; } /** - * Compare two area selections for equality. Returns true if the selections are - * equivalent, false otherwise. + * Represents the different types of selections that can be made in the UI. */ -export function areaSelectionsEqual(a: AreaSelection, b: AreaSelection) { - if (a.start !== b.start) return false; - if (a.end !== b.end) return false; - if (!arrayEquals(a.trackUris, b.trackUris)) { - return false; - } - return true; +export type Selection = + | TrackEventSelection + | TrackSelection + | AreaSelection + | NoteSelection + | EmptySelection; + +/** + * Defines how changes to selection affect the rest of the UI state. + */ +export interface SelectionOpts { + /** + * If `true`, clears the search input. Defaults to `true`. + */ + readonly clearSearch?: boolean; + /** + * If `true`, switches to the tab relevant to the current selection. Defaults + * to `true`. + */ + readonly switchToCurrentSelectionTab?: boolean; + /** + * If `true`, scrolls the timeline to reveal the selection. Defaults to `false`. + */ + readonly scrollToSelection?: boolean; } +/** + * Represents a selection of a specific track event. + */ +export interface TrackEventSelection extends TrackEventDetails { + /** + * The kind of selection, always 'track_event'. + */ + readonly kind: 'track_event'; + /** + * The URI of the track where the event is located. + */ + readonly trackUri: string; + /** + * The ID of the selected event. + */ + readonly eventId: number; +} + +/** + * Represents a selection of an entire track. + */ +export interface TrackSelection { + /** + * The kind of selection, always 'track'. + */ + readonly kind: 'track'; + /** + * The URI of the selected track. + */ + readonly trackUri: string; +} + +/** + * Details about a track event. + */ +export interface TrackEventDetails { + /** + * The timestamp of the event. Required by the core. + */ + readonly ts: time; + + /** + * The duration of the event. Can be 0 for instant events or -1 for DNF + * slices. Will be `undefined` if this selection has no duration (e.g., + * profile/counter samples). + */ + readonly dur?: duration; +} + +/** + * Defines an area on the timeline. + */ +export interface Area { + /** + * The start timestamp of the area. + */ + readonly start: time; + /** + * The end timestamp of the area. + */ + readonly end: time; + /** + * An array of URIs of the tracks included in the area. + */ + readonly trackUris: ReadonlyArray<string>; +} + +/** + * Represents a selection of an area on the timeline. + */ +export interface AreaSelection extends Area { + /** + * The kind of selection, always 'area'. + */ + readonly kind: 'area'; + + /** + * This array contains the resolved Tracks from `Area.trackUris`. The + * resolution is done by `SelectionManager` whenever a `kind='area'` selection + * is performed. + */ + readonly tracks: ReadonlyArray<Track>; +} + +/** + * Represents a selection of a note. + */ +export interface NoteSelection { + /** + * The kind of selection, always 'note'. + */ + readonly kind: 'note'; + /** + * The ID of the selected note. + */ + readonly id: string; +} + +/** + * Represents an empty selection. + */ +export interface EmptySelection { + /** + * The kind of selection, always 'empty'. + */ + readonly kind: 'empty'; +} + +/** + * Resolves SQL events to track events. + */ +export interface SqlSelectionResolver { + /** + * The name of the SQL table to resolve. + */ + readonly sqlTableName: string; + /** + * A callback function that resolves an event ID from a SQL table to a track + * URI and event ID. + * @param id The ID of the event in the SQL table. + * @param sqlTable The name of the SQL table. + * @returns A promise that resolves to an object containing `trackUri` and + * `eventId`, or `undefined` if not found. + */ + callback( + id: number, + sqlTable: string, + ): Promise<{readonly trackUri: string; readonly eventId: number} | undefined>; +} + +/** + * Manages the current selection state in the UI. + */ export interface SelectionManager { + /** + * The current selection. + */ readonly selection: Selection; /** @@ -70,16 +242,16 @@ readonly areaSelectionTabs: ReadonlyArray<AreaSelectionTab>; /** - * Clears the current selection, selects nothing. + * Clears the current selection, selecting nothing. */ clearSelection(): void; /** - * Select a track event. + * Selects a track event. * - * @param trackUri - The URI of the track to select. - * @param eventId - The value of the events ID column. - * @param opts - Additional options. + * @param trackUri The URI of the track to select. + * @param eventId The value of the event's ID column. + * @param opts Additional options for the selection. */ selectTrackEvent( trackUri: string, @@ -88,43 +260,47 @@ ): void; /** - * Select a track. + * Selects a track. * - * @param trackUri - The URI for the track to select. - * @param opts - Additional options. + * @param trackUri The URI for the track to select. + * @param opts Additional options for the selection. */ selectTrack(trackUri: string, opts?: SelectionOpts): void; /** - * Resolves events via a sql table name + ids. + * Resolves events via a SQL table name and IDs. * - * @param sqlTableName - The name of the SQL table to resolve. - * @param ids - The IDs of the events in that table. + * @param sqlTableName The name of the SQL table to resolve. + * @param ids The IDs of the events in that table. + * @returns A promise that resolves to an array of objects containing eventId + * and trackUri. */ resolveSqlEvents( sqlTableName: string, ids: ReadonlyArray<number>, - ): Promise<ReadonlyArray<{eventId: number; trackUri: string}>>; + ): Promise< + ReadonlyArray<{readonly eventId: number; readonly trackUri: string}> + >; /** - * Select a track event via a sql table name + id. + * Selects a track event via a SQL table name and ID. * - * @param sqlTableName - The name of the SQL table to resolve. - * @param id - The ID of the event in that table. - * @param opts - Additional options. + * @param sqlTableName The name of the SQL table to resolve. + * @param id The ID of the event in that table. + * @param opts Additional options for the selection. */ selectSqlEvent(sqlTableName: string, id: number, opts?: SelectionOpts): void; /** - * Create an area selection for the purposes of aggregation. + * Creates an area selection for the purposes of aggregation. * - * @param args - The area to select. - * @param opts - Additional options. + * @param args The area to select. + * @param opts Additional options for the selection. */ selectArea(args: Area, opts?: SelectionOpts): void; /** - * Scroll the timeline horizontally and vertically to reveal the currently + * Scrolls the timeline horizontally and vertically to reveal the currently * selected entity. */ scrollToSelection(): void; @@ -133,78 +309,32 @@ * Returns the smallest time span that contains the currently selected entity. * * @returns The time span, if a timeline entity is selected, otherwise - * undefined. + * `undefined`. */ getTimeSpanOfSelection(): TimeSpan | undefined; /** - * Register a new tab under the area selection details panel. + * Registers a new tab under the area selection details panel. + * @param tab The area selection tab to register. */ registerAreaSelectionTab(tab: AreaSelectionTab): void; } -export type Selection = - | TrackEventSelection - | TrackSelection - | AreaSelection - | NoteSelection - | EmptySelection; - -/** Defines how changes to selection affect the rest of the UI state */ -export interface SelectionOpts { - clearSearch?: boolean; // Default: true. - switchToCurrentSelectionTab?: boolean; // Default: true. - scrollToSelection?: boolean; // Default: false. -} - -export interface TrackEventSelection extends TrackEventDetails { - readonly kind: 'track_event'; - readonly trackUri: string; - readonly eventId: number; -} - -export interface TrackSelection { - readonly kind: 'track'; - readonly trackUri: string; -} - -export interface TrackEventDetails { - // ts and dur are required by the core, and must be provided. - readonly ts: time; - - // Note: dur can be 0 for instant events or -1 for DNF slices. Will be - // undefined if this selection has no duration, i.e. profile / counter - // samples. - readonly dur?: duration; -} - -export interface Area { - readonly start: time; - readonly end: time; - readonly trackUris: ReadonlyArray<string>; -} - -export interface AreaSelection extends Area { - readonly kind: 'area'; - - // This array contains the resolved Tracks from Area.trackUris. The resolution - // is done by SelectionManager whenever a kind='area' selection is performed. - readonly tracks: ReadonlyArray<Track>; -} - -export interface NoteSelection { - readonly kind: 'note'; - readonly id: string; -} - -export interface EmptySelection { - readonly kind: 'empty'; -} - -export interface SqlSelectionResolver { - readonly sqlTableName: string; - readonly callback: ( - id: number, - sqlTable: string, - ) => Promise<{trackUri: string; eventId: number} | undefined>; +/** + * Compare two area selections for equality. Returns true if the selections are + * equivalent, false otherwise. + * @param a The first area selection. + * @param b The second area selection. + * @returns `true` if the selections are equal, `false` otherwise. + */ +export function areaSelectionsEqual( + a: AreaSelection, + b: AreaSelection, +): boolean { + if (a.start !== b.start) return false; + if (a.end !== b.end) return false; + if (!arrayEquals(a.trackUris, b.trackUris)) { + return false; + } + return true; }
diff --git a/ui/src/public/settings.ts b/ui/src/public/settings.ts index 592e0a9..5ceb8bb 100644 --- a/ui/src/public/settings.ts +++ b/ui/src/public/settings.ts
@@ -21,6 +21,12 @@ import {z} from 'zod'; import m from 'mithril'; +/** + * A function type for rendering a custom UI for a setting. + * @template T The type of the setting's value. + * @param setting The setting instance to render. + * @returns Mithril children to be rendered. + */ export type SettingRenderer<T> = (setting: Setting<T>) => m.Children; /** @@ -29,24 +35,44 @@ * @template T The type of the setting's value. */ export interface SettingDescriptor<T> { - // A unique identifier for the setting. Used as the storage key. + /** + * A unique identifier for the setting. Used as the storage key. + */ readonly id: string; - // A human-readable name for the setting, used on the settings page. + + /** + * A human-readable name for the setting, used on the settings page. + */ readonly name: string; - // A detailed description of what the setting does, used on the settings page. + + /** + * A detailed description of what the setting does, used on the settings page. + */ readonly description: string; - // The Zod schema used for validating the setting's value, and defining the - // structure and type of this setting. + + /** + * The Zod schema used for validating the setting's value, and defining the + * structure and type of this setting. + */ readonly schema: z.ZodType<T>; - // The default value of the setting if the setting is absent from the - // underlying storage. + + /** + * The default value of the setting if the setting is absent from the + * underlying storage. + */ readonly defaultValue: T; - // If true, the user will be prompted to reload the the page when this setting - // is changed. + + /** + * If true, the user will be prompted to reload the the page when this setting + * is changed. + */ readonly requiresReload?: boolean; - // An optional render function for customizing the UI of this setting in the - // settings page. Required for settings that are move complex than a primitive - // type, such as objects or arrays. + + /** + * An optional render function for customizing the UI of this setting in the + * settings page. Required for settings that are move complex than a primitive + * type, such as objects or arrays. + */ readonly render?: SettingRenderer<T>; } @@ -57,22 +83,41 @@ * @template T The type of the setting's value. */ export interface Setting<T> extends SettingDescriptor<T>, Disposable { - // Returns true if this settings is currently set to the default value. + /** + * Returns true if this settings is currently set to the default value. + */ readonly isDefault: boolean; - // Get the current value of the setting. + + /** + * Get the current value of the setting. + * @returns The current value of the setting. + */ get(): T; - // Set the value of the setting. This will also update the underlying storage. + + /** + * Set the value of the setting. This will also update the underlying storage. + * @param value The new value for the setting. + */ set(value: T): void; - // Resets back to default. + + /** + * Resets back to default. + */ reset(): void; } /** * Manages the registration and retrieval of application settings. + * + * Settings are stored in local storage and can be configured on the settings + * page. They support validation via Zod schemas, custom rendering, and can + * optionally require an app reload when changed. */ export interface SettingsManager { /** * Registers a new setting. + * @template T The type of the setting's value. + * @param setting The descriptor for the setting to register. * @returns A handle used to interact with the setting. */ register<T>(setting: SettingDescriptor<T>): Setting<T>; @@ -82,6 +127,7 @@ resetAll(): void; /** * Retrieves a list of all currently registered settings. + * @returns A read-only array of all registered settings. */ getAllSettings(): ReadonlyArray<Setting<unknown>>; /** @@ -93,7 +139,9 @@ /** * Get the a setting by its ID. + * @template T The expected type of the setting's value. * @param id The unique identifier of the setting. + * @returns The setting instance if found, or `undefined` otherwise. */ get<T>(id: string): Setting<T> | undefined; }
diff --git a/ui/src/public/sidebar.ts b/ui/src/public/sidebar.ts index 980766f..85a5795 100644 --- a/ui/src/public/sidebar.ts +++ b/ui/src/public/sidebar.ts
@@ -4,7 +4,7 @@ // 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 +// http://www.apache.org/licenses/LICENSE-20.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -42,43 +42,107 @@ export type SidebarSections = keyof typeof SIDEBAR_SECTIONS; +/** + * Manages the sidebar menu items and visibility. + * + * The sidebar is organized into sections (navigation, settings, support, + * etc.). Use this to add menu entries that can navigate to pages, execute + * actions, or trigger commands. Menu items are automatically removed when + * the trace is closed or the plugin is unloaded. + */ export interface SidebarManager { + /** + * Whether the sidebar is currently enabled. + * + * When the sidebar is disabled, menu items cannot be added, and its + * visibility cannot be toggled. + */ readonly enabled: boolean; /** * Adds a new menu item to the sidebar. - * All entries must map to a command. This will allow the shortcut and + * + * All entries must map to a command, which allows the shortcut and * optional shortcut to be displayed on the UI. + * + * @param menuItem The menu item to add. */ addMenuItem(menuItem: SidebarMenuItem): void; /** * Gets the current visibility of the sidebar. + * + * @returns `true` if the sidebar is visible, `false` otherwise. */ get visible(): boolean; /** - * Toggles the visibility of the sidebar. Can only be called when - * `sidebarEnabled` returns `ENABLED`. + * Toggles the visibility of the sidebar. + * + * This method can only be called when `enabled` is `true`. */ toggleVisibility(): void; } +/** + * Represents a single menu item in the sidebar. + * + * A sidebar menu item can either navigate to a URL, execute an action, or + * trigger a command. + */ export type SidebarMenuItem = { + /** + * The section of the sidebar where this menu item will be placed. + * + * Must be one of the predefined `SIDEBAR_SECTIONS`. + */ readonly section: SidebarSections; + + /** + * An optional number to control the sort order of menu items within a + * section. + * + * Lower numbers appear before higher numbers. If omitted, items are sorted + * by their `text` property. + */ readonly sortOrder?: number; - // The properties below can be mutated by passing a callback rather than a - // direct value. The callback is invoked on every render frame, keep it cheap. - // readonly text: string | (() => string); + /** + * An optional icon to display next to the menu item. + * + * Can be a string (e.g., 'settings') or a function that returns a string. + * The function is invoked on every render frame, so keep it cheap. + */ readonly icon?: string | (() => string); + + /** + * An optional tooltip to display when hovering over the menu item. + * + * Can be a string or a function that returns a string. The function is + * invoked on every render frame, so keep it cheap. + */ readonly tooltip?: string | (() => string); + + /** + * An optional CSS class to apply to the menu item. + * + * Can be a string (without trailing '.') or a function that returns a string. + * The function is invoked on every render frame, so keep it cheap. + */ readonly cssClass?: string | (() => string); // Without trailing '.'. - // If false or omitted the item works normally. - // If true the item is striken through and the action/href will be a no-op. - // If a string, the item acts as disabled and clicking on it shows a popup - // that shows the returned text (the string has "disabled reason" semantic); + /** + * Controls the disabled state of the menu item. + * + * - If `false` or omitted, the item works normally. + * - If `true`, the item is struck through, and its action/href will be a + * no-op. + * - If a `string`, the item acts as disabled, and clicking on it shows a + * popup with the returned text (which has "disabled reason" semantics). + * + * Can be a string, boolean, or a function that returns either. The function + * is invoked on every render frame, so keep it cheap. + */ readonly disabled?: string | boolean | (() => string | boolean); // One of the three following arguments must be specified. @@ -104,7 +168,7 @@ * a promise, a spinner will be drawn next to the sidebar entry until the * promise resolves. */ - readonly action: () => unknown | Promise<unknown>; + action(): unknown | Promise<unknown>; /** Optional. If omitted href = '#'. */ readonly href?: string;
diff --git a/ui/src/public/statusbar.ts b/ui/src/public/statusbar.ts index 6854be2..4ad6399 100644 --- a/ui/src/public/statusbar.ts +++ b/ui/src/public/statusbar.ts
@@ -15,17 +15,42 @@ import m from 'mithril'; import {Intent} from '../widgets/common'; +/** + * Represents an item to be displayed in the status bar. + */ export interface StatusbarItem { - readonly renderItem: () => { + /** + * A function that returns the properties for rendering the status bar item. + * @returns An object with label, optional icon, optional intent, and an + * optional click handler. + */ + renderItem(): { readonly label: string; readonly icon?: string; readonly intent?: Intent; - readonly onclick?: (event: MouseEvent) => void; + onclick?(event: MouseEvent): void; }; - readonly popupContent?: () => m.Children; + + /** + * An optional function that returns the content to be displayed in a popup + * when the status bar item is clicked. + * @returns The Mithril children to render in the popup. + */ + popupContent?(): m.Children; } +/** + * Manages items in the status bar. + */ export interface StatusbarManager { + /** + * A read-only array of all currently registered status bar items. + */ readonly statusBarItems: ReadonlyArray<StatusbarItem>; + + /** + * Registers a new item to be displayed in the status bar. + * @param item The status bar item to register. + */ registerItem(item: StatusbarItem): void; }
diff --git a/ui/src/public/tab.ts b/ui/src/public/tab.ts index 97d7065..6032404 100644 --- a/ui/src/public/tab.ts +++ b/ui/src/public/tab.ts
@@ -14,22 +14,86 @@ import m from 'mithril'; +/** + * Manages the registration, display, and hiding of tabs within the UI. + * + * Tabs provide a way to organize different views or functionalities within + * the application, allowing users to switch between them easily. + */ export interface TabManager { + /** + * Registers a new tab with the TabManager. + * + * @param tab The descriptor for the tab to register. + */ registerTab(tab: TabDescriptor): void; + + /** + * Displays the tab associated with the given URI. + * + * If the tab is not currently visible, it will be brought to the foreground. + * @param uri The unique URI of the tab to show. + */ showTab(uri: string): void; + + /** + * Hides the tab associated with the given URI. + * + * @param uri The unique URI of the tab to hide. + */ hideTab(uri: string): void; + + /** + * Adds a tab to the list of default tabs. + * + * Default tabs are automatically opened when the application starts or + * when a new trace is loaded. + * @param uri The unique URI of the tab to add as a default. + */ addDefaultTab(uri: string): void; } +/** + * Represents the content and title of a tab. + */ export interface Tab { + /** + * Renders the content of the tab. + * @returns The Mithril children to render for the tab's content. + */ render(): m.Children; + + /** + * Gets the title of the tab. + * @returns The human-readable title of the tab. + */ getTitle(): string; } +/** + * Describes a tab to be registered with the TabManager. + */ export interface TabDescriptor { - uri: string; // TODO(stevegolton): Maybe optional for ephemeral tabs. - content: Tab; - isEphemeral?: boolean; // Defaults false + /** + * The unique URI for this tab. + * TODO(stevegolton): Maybe optional for ephemeral tabs. + */ + readonly uri: string; + /** + * The content of the tab, including its render function and title. + */ + readonly content: Tab; + /** + * If true, this tab is ephemeral and may be closed automatically under + * certain conditions (e.g., when a trace is closed). Defaults to `false`. + */ + readonly isEphemeral?: boolean; + /** + * An optional callback function that is invoked when the tab is hidden. + */ onHide?(): void; + /** + * An optional callback function that is invoked when the tab is shown. + */ onShow?(): void; }
diff --git a/ui/src/public/timeline.ts b/ui/src/public/timeline.ts index 58507f9..dc45eb7 100644 --- a/ui/src/public/timeline.ts +++ b/ui/src/public/timeline.ts
@@ -16,56 +16,130 @@ import {time} from '../base/time'; import {Setting} from './settings'; +/** + * Defines the various formats for displaying timestamps in the UI. + */ export enum TimestampFormat { + /** Displays time as a timecode (e.g., HH:MM:SS.mmm). */ Timecode = 'timecode', + /** Displays raw trace nanoseconds. */ TraceNs = 'traceNs', + /** Displays raw trace nanoseconds, formatted according to locale. */ TraceNsLocale = 'traceNsLocale', + /** Displays time in seconds. */ Seconds = 'seconds', + /** Displays time in milliseconds. */ Milliseconds = 'milliseconds', + /** Displays time in microseconds. */ Microseconds = 'microseconds', + /** Displays time in UTC format. */ UTC = 'utc', + /** Displays time in a custom timezone. */ CustomTimezone = 'customTimezone', + /** Displays time in the trace's timezone. */ TraceTz = 'traceTz', } +/** + * Defines the precision for displaying durations in the UI. + */ export enum DurationPrecision { + /** Displays full precision for durations. */ Full = 'full', + /** Displays human-readable durations (e.g., 1h 2m 3s). */ HumanReadable = 'human_readable', } +/** + * Manages the interactive timeline and viewport. + * + * The timeline allows users to navigate through the trace, zoom in and out, + * and interact with various time-based elements. + */ export interface Timeline { - // Bring a timestamp into view. + /** + * Brings a specific timestamp into the current viewport. + * @param ts The timestamp to pan to. + */ panToTimestamp(ts: time): void; - // Move the viewport. + /** + * Sets the start and end times of the current viewport. + * @param start The start timestamp of the viewport. + * @param end The end timestamp of the viewport. + */ setViewportTime(start: time, end: time): void; - // A span representing the current viewport location. + /** + * A span representing the current visible time range in the viewport. + */ readonly visibleWindow: HighPrecisionTimeSpan; - // Render a vertical line on the timeline at this timestamp. + /** + * The timestamp where the hover cursor is currently located on the timeline. + * Setting this value will render a vertical line at the specified timestamp. + */ hoverCursorTimestamp: time | undefined; + /** + * The timestamp of a hovered note on the timeline. + * Setting this value will highlight the corresponding note. + */ hoveredNoteTimestamp: time | undefined; + + /** + * The ID of the currently highlighted slice. + * Setting this value will highlight the corresponding slice in the UI. + */ highlightedSliceId: number | undefined; + /** + * The UTID (Unique Thread ID) of the currently hovered thread. + */ hoveredUtid: number | undefined; + + /** + * The PID (Process ID) of the currently hovered process. + */ hoveredPid: bigint | undefined; - // This value defines the time of the origin of the time axis in trace time. - // Depending on the timestamp format setting, this value can change: - // E.g. - // - Raw - origin = 0 - // - Seconds - origin = trace.start. - // - Realtime - origin = midnight before trace.start. + /** + * Gets the time of the origin of the time axis in trace time. + * + * Depending on the timestamp format setting, this value can change: + * - Raw: origin = 0 + * - Seconds: origin = trace.start + * - Realtime: origin = midnight before trace.start + * + * @returns The origin timestamp of the time axis. + */ getTimeAxisOrigin(): time; - // Get a time in the current domain as specified by timestampOffset. + /** + * Converts a timestamp to a time in the current domain, as specified by + * the timestamp offset. + * @param ts The timestamp to convert. + * @returns The timestamp in the current domain. + */ toDomainTime(ts: time): time; - // These control how timestamps and durations are formatted throughout the UI + /** + * Controls how timestamps are formatted throughout the UI. + */ timestampFormat: TimestampFormat; + + /** + * Controls how durations are formatted throughout the UI. + */ durationPrecision: DurationPrecision; + + /** + * The custom timezone offset in minutes. + */ customTimezoneOffset: number; + + /** + * The setting for overriding the default timezone. + */ timezoneOverride: Setting<string>; }
diff --git a/ui/src/public/trace.ts b/ui/src/public/trace.ts index 80865f2..58a5039 100644 --- a/ui/src/public/trace.ts +++ b/ui/src/public/trace.ts
@@ -32,7 +32,7 @@ // Lists all the possible event listeners using the key as the event name and // the type as the type of the callback. export interface EventListeners { - traceready: () => Promise<void> | void; + traceready(): Promise<void> | void; } /**
diff --git a/ui/src/public/trace_info.ts b/ui/src/public/trace_info.ts index 4e20a90..71b7c38 100644 --- a/ui/src/public/trace_info.ts +++ b/ui/src/public/trace_info.ts
@@ -4,7 +4,7 @@ // 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 +// http://www.apache.org/licenses/LICENSE-20.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -14,46 +14,80 @@ import {time} from '../base/time'; +/** + * Provides information about the currently loaded trace. + */ export interface TraceInfo { - readonly traceTitle: string; // File name and size of the current trace. - readonly traceUrl: string; // URL of the Trace. + /** + * The title of the trace, typically the file name and size. + */ + readonly traceTitle: string; + /** + * The URL from which the trace was loaded, if applicable. + */ + readonly traceUrl: string; + /** + * The start timestamp of the trace. + */ readonly start: time; + /** + * The end timestamp of the trace. + */ readonly end: time; - // This is the ts value at the time of the Unix epoch. - // Normally some large negative value, because the unix epoch is normally in - // the past compared to ts=0. + /** + * The timestamp value at the time of the Unix epoch. + * + * Normally some large negative value, because the Unix epoch is typically in + * the past compared to `ts=0`. + */ readonly unixOffset: time; - // Represents the reported timezone in minutes from UTC. + /** + * The reported timezone offset in minutes from UTC. + */ readonly tzOffMin: number; - // The number of import/analysis errors present in the `stats` table. + /** + * The number of import/analysis errors present in the `stats` table. + */ readonly importErrors: number; - // The trace type inferred by TraceProcessor (e.g. 'proto', 'json, ...). - // See TraceTypeToString() in src/trace_processor/util/trace_type.cc for - // all the available types. + /** + * The trace type inferred by TraceProcessor (e.g., 'proto', 'json', ...). + * + * See `TraceTypeToString()` in `src/trace_processor/util/trace_type.cc` for + * all available types. + */ readonly traceType?: string; - // True if the trace contains any ftrace data (sched or other ftrace events). + /** + * `true` if the trace contains any ftrace data (sched or other ftrace events). + */ readonly hasFtrace: boolean; - // The UUID of the trace. This is generated by TraceProcessor by either - // looking at the TraceUuid packet emitted by traced or, as a fallback, by - // hashing the first KB of the trace. This can be an empty string in rare - // cases (e.g., opening an empty trace). + /** + * The UUID of the trace. This is generated by TraceProcessor by either + * looking at the `TraceUuid` packet emitted by `traced` or, as a fallback, by + * hashing the first KB of the trace. This can be an empty string in rare + * cases (e.g., opening an empty trace). + */ readonly uuid: string; - // Wheteher the current trace has been successfully stored into cache storage. + /** + * Whether the current trace has been successfully stored into cache storage. + */ readonly cached: boolean; - // Returns true if the current trace can be downloaded via getTraceFile(). - // The trace isn't downloadable in the following cases: - // - It comes from a source (e.g. HTTP+RPC) that doesn't support re-download - // due to technical limitations. - // - Download is disabled because the trace was pushed via postMessage and - // the caller has asked to disable downloads. + /** + * Returns `true` if the current trace can be downloaded via `getTraceFile()`. + * + * The trace isn't downloadable in the following cases: + * - It comes from a source (e.g., HTTP+RPC) that doesn't support re-download + * due to technical limitations. + * - Download is disabled because the trace was pushed via `postMessage` and + * the caller has asked to disable downloads. + */ readonly downloadable: boolean; }
diff --git a/ui/src/public/track.ts b/ui/src/public/track.ts index 58ac999..c15416f 100644 --- a/ui/src/public/track.ts +++ b/ui/src/public/track.ts
@@ -25,52 +25,99 @@ import {CanvasColors} from './canvas_colors'; import {z} from 'zod'; +/** + * Defines criteria for filtering tracks. + */ export interface TrackFilterCriteria { + /** + * The human-readable name of the filter criteria. + */ readonly name: string; - // Run on each node to work out whether it satisfies the selected filter - // option. + /** + * A predicate function that determines whether a track satisfies the selected + * filter option. + * @param track The track node to evaluate. + * @param filterOption The selected filter option string. + * @returns `true` if the track satisfies the filter, `false` otherwise. + */ readonly predicate: (track: TrackNode, filterOption: string) => boolean; - // The list of possible filter options. - readonly options: ReadonlyArray<{key: string; label: string}>; + /** + * The list of possible filter options for this criteria. + */ + readonly options: ReadonlyArray<{ + readonly key: string; + readonly label: string; + }>; } +/** + * Manages the registration and discovery of tracks. + */ export interface TrackManager { /** - * Register a new track against a unique key known as a URI. The track is not - * shown by default and callers need to either manually add it to a - * Workspace or use registerTrackAndShowOnTraceLoad() below. + * Registers a new track with a unique URI. + * + * The track is not shown by default; callers need to either manually add it + * to a Workspace or use `registerTrackAndShowOnTraceLoad()` (if available) + * to display it. + * @param track The track to register. */ registerTrack(track: Track): void; + /** + * Finds a track that satisfies the given predicate. + * @param predicate A function that returns `true` for the desired track. + * @returns The first track that satisfies the predicate, or `undefined` if + * none is found. + */ findTrack( predicate: (track: Track) => boolean | undefined, ): Track | undefined; + /** + * Retrieves all currently registered tracks. + * @returns An array of all registered tracks. + */ getAllTracks(): Track[]; + /** + * Retrieves a track by its unique URI. + * @param uri The unique URI of the track. + * @returns The track if found, or `undefined`. + */ getTrack(uri: string): Track | undefined; /** - * Register a track filter criteria, which can be used by end users to control - * the list of tracks they see in workspaces. These criteria can provide more - * power to the user compared to e.g. purely filtering by name. + * Registers a track filter criteria, which can be used by end users to + * control the list of tracks they see in workspaces. + * + * These criteria can provide more power to the user compared to purely + * filtering by name. + * @param filter The track filter criteria to register. */ registerTrackFilterCriteria(filter: TrackFilterCriteria): void; /** - * Register a timeline overlay renderer. + * Registers a timeline overlay renderer. * * Overlays are rendered on top of all tracks in the timeline view and can be * used to draw annotations that span multiple tracks, such as flow arrows or * vertical lines marking specific events. + * @param overlay The overlay to register. */ registerOverlay(overlay: Overlay): void; } +/** + * Contextual information about the track passed to track lifecycle hooks & + * render hooks. + */ export interface TrackContext { - // This track's URI, used for making selections et al. + /** + * This track's URI, used for making selections and other operations. + */ readonly trackUri: string; } @@ -116,30 +163,46 @@ readonly colors: CanvasColors; } -// A definition of a track, including a renderer implementation and metadata. +/** + * A definition of a track, including a renderer implementation and metadata. + */ export interface Track { - // A unique identifier for this track. + /** + * A unique identifier for this track. + */ readonly uri: string; - // Describes how to render the track. + /** + * Describes how to render the track. + */ readonly renderer: TrackRenderer; - // Optional: A human readable description of the track. This can be a simple - // string or a render function that returns Mithril vnodes. + /** + * Optional: A human readable description of the track. This can be a simple + * string or a render function that returns Mithril vnodes. + */ readonly description?: string | (() => m.Children); - // Optional: Human readable subtitle. Sometimes displayed if there is room. + /** + * Optional: Human readable subtitle. Sometimes displayed if there is room. + */ readonly subtitle?: string; - // Optional: A list of tags which provide additional metadata about the track. - // Used mainly for legacy purposes that predate dataset. + /** + * Optional: A list of tags which provide additional metadata about the track. + * Used mainly for legacy purposes that predate dataset. + */ readonly tags?: TrackTags; - // Optional: A list of strings which are displayed as "chips" in the track - // shell. + /** + * Optional: A list of strings which are displayed as "chips" in the track + * shell. + */ readonly chips?: ReadonlyArray<string>; - // Filled in by the core. + /** + * Filled in by the core. + */ readonly pluginId?: string; } @@ -148,12 +211,12 @@ */ export interface TrackMouseEvent { /** - * X coordinate of the mouse event w.r.t. the top-left of the track. + * X coordinate of the mouse event with respect to the top-left of the track. */ readonly x: number; /** - * Y coordinate of the mouse event w.r.t the top-left of the track. + * Y coordinate of the mouse event with respect to the top-left of the track. */ readonly y: number; @@ -172,35 +235,51 @@ * * A lot of the fields in this interface are currently unused, but they will be * used in the future when track serialization is implemented. + * @template T The type of the setting's value. */ export interface TrackSettingDescriptor<T> { - // A unique identifier for this setting. Will be used to store the serialized - // value for this setting. Currently unused. + /** + * A unique identifier for this setting. Will be used to store the serialized + * value for this setting. Currently unused. + */ readonly id: string; - // A human readable name for this setting. This is displayed in the settings - // menu unless overridden. + /** + * A human readable name for this setting. This is displayed in the settings + * menu unless overridden. + */ readonly name: string; - // A human readable description for this setting. Currently unused, but good - // practice to require this in order to document what a setting does and is - // used for. + /** + * A human readable description for this setting. Currently unused, but good + * practice to require this in order to document what a setting does and is + * used for. + */ readonly description: string; - // A Zod schema describing the setting's value type which is used to infer the - // automatic settings menu type and options, and will be used for - // serialization and deserialization. + /** + * A Zod schema describing the setting's value type which is used to infer the + * automatic settings menu type and options, and will be used for + * serialization and deserialization. + */ readonly schema: z.ZodType<T>; - // The default value for this setting. This will be used to render a 'reset' - // button in the render menu, and possibly as a fallback if parsing fails when - // we add serialization. Currently unused. + /** + * The default value for this setting. This will be used to render a 'reset' + * button in the render menu, and possibly as a fallback if parsing fails when + * we add serialization. Currently unused. + */ readonly defaultValue: T; - // An optional function used to render a control for this setting. This - // describes what the control looks like in the settings menu on the track and - // also the bulk settings menu when multiple tracks are selected. If omitted, - // a control will be automatically generated based on the schema and name. + /** + * An optional function used to render a control for this setting. This + * describes what the control looks like in the settings menu on the track and + * also the bulk settings menu when multiple tracks are selected. If omitted, + * a control will be automatically generated based on the schema and name. + * @param setter A function to set the value of the setting. + * @param values An array of current values for the setting (for bulk editing). + * @returns Mithril children to render the control. + */ render?(setter: (value: T) => void, values: ReadonlyArray<T>): m.Children; } @@ -208,13 +287,28 @@ * A setting that can be changed by the user that affects how the track is * rendered or behaves. References a TrackSettingDescriptor which describes the * setting's metadata and how to render a control for it. + * @template T The type of the setting's value. */ export interface TrackSetting<T> { + /** + * The descriptor for this track setting. + */ readonly descriptor: TrackSettingDescriptor<T>; - getValue: () => T; + /** + * Gets the current value of the setting. + * @returns The current value of the setting. + */ + getValue(): T; + /** + * Sets the value of the setting. + * @param newValue The new value for the setting. + */ setValue(newValue: T): void; } +/** + * Defines the rendering logic and lifecycle hooks for a track. + */ export interface TrackRenderer { /** * Describes which root table the events on this track come from. This is @@ -251,6 +345,7 @@ * * Note: On the first render cycle, both onCreate and onUpdate are called one * after another. + * @param ctx The track context. */ onCreate?(ctx: TrackContext): Promise<void>; @@ -260,6 +355,7 @@ * The track should inspect things like the visible window, track size, and * resolution to work out whether any data needs to be reloaded based on these * properties and perform a reload. + * @param ctx The track render context. */ onUpdate?(ctx: TrackRenderContext): Promise<void>; @@ -272,48 +368,93 @@ /** * Required method used to render the track's content to the canvas, called * synchronously on every render cycle. + * @param ctx The track render context. */ render(ctx: TrackRenderContext): void; + + /** + * Optional: Called when a full redraw of the track is required. + */ onFullRedraw?(): void; /** * Return the vertical bounds (top & bottom) of a slice were it to be rendered * at a specific depth, given the slice height and padding/spacing that this * track uses. + * @param depth The depth of the slice. + * @returns The vertical bounds of the slice, or `undefined`. */ getSliceVerticalBounds?(depth: number): VerticalBounds | undefined; + + /** + * Optional: Returns the height of the track. + * @returns The height of the track in pixels. + */ getHeight?(): number; + + /** + * Optional: Returns Mithril children for buttons to be displayed in the track shell. + * @returns Mithril children representing the track shell buttons. + */ getTrackShellButtons?(): m.Children; + + /** + * Optional: Called when the mouse moves over the track. + * @param event The track mouse event. + */ onMouseMove?(event: TrackMouseEvent): void; + + /** + * Optional: Called when the mouse is clicked on the track. + * @param event The track mouse event. + * @returns `true` if the click was handled, `false` otherwise. + */ onMouseClick?(event: TrackMouseEvent): boolean; + + /** + * Optional: Called when the mouse leaves the track area. + */ onMouseOut?(): void; /** * Optional: Returns a dataset that represents the events displayed on this * track. + * @returns The source dataset for the track, or `undefined`. */ getDataset?(): SourceDataset | undefined; /** * Optional: Get details of a track event given by eventId on this track. + * @param eventId The ID of the track event. + * @returns A promise that resolves to the track event details, or `undefined`. */ getSelectionDetails?(eventId: number): Promise<TrackEventDetails | undefined>; - // Optional: A factory that returns a details panel object for a given track - // event selection. This is called each time the selection is changed (and the - // selection is relevant to this track). + /** + * Optional: A factory that returns a details panel object for a given track + * event selection. This is called each time the selection is changed (and the + * selection is relevant to this track). + * @param sel The track event selection. + * @returns The track event details panel, or `undefined`. + */ detailsPanel?(sel: TrackEventSelection): TrackEventDetailsPanel | undefined; - // Optional: Returns tooltip content if available. If the return value is - // falsy, no tooltip is rendered. + /** + * Optional: Returns tooltip content if available. If the return value is + * falsy, no tooltip is rendered. + * @returns Mithril children for the tooltip content, or `undefined`. + */ renderTooltip?(): m.Children; } -// 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. +/** + * An set of key/value pairs describing a given track. These are used for + * selecting tracks to pin/unpin, displaying "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]: @@ -325,72 +466,147 @@ | ReadonlyArray<number>; }; +/** + * Well-known track tags used for various subsystems. + */ interface WellKnownTrackTags { - // The track "kinds", are by various subsystems e.g. aggregation controllers - // in order to select tracks to operate on. A good analogy is how CSS - // selectors can match elements using their class list. - kinds: ReadonlyArray<string>; + /** + * The track "kinds", are by various subsystems e.g. aggregation controllers + * in order to select tracks to operate on. A good analogy is how CSS + * selectors can match elements using their class list. + */ + readonly kinds: ReadonlyArray<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: ReadonlyArray<number>; + /** + * 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. + */ + readonly trackIds: ReadonlyArray<number>; - // Optional: The CPU number associated with this track. - cpu: number; + /** + * Optional: The CPU number associated with this track. + */ + readonly cpu: number; - // Optional: The UTID associated with this track. - utid: number; + /** + * Optional: The UTID associated with this track. + */ + readonly utid: number; - // Optional: The UPID associated with this track. - upid: number; + /** + * Optional: The UPID associated with this track. + */ + readonly upid: number; - // Track type, used for filtering - type: string; + /** + * Track type, used for filtering. + */ + readonly type: string; } +/** + * Represents a single slice on a track. + */ export interface Slice { - // These properties are updated only once per query result when the Slice - // object is created and don't change afterwards. + /** + * The unique ID of the slice. + * These properties are updated only once per query result when the Slice + * object is created and don't change afterwards. + */ readonly id: number; + /** + * The start timestamp of the slice in nanoseconds. + */ readonly startNs: time; + /** + * The end timestamp of the slice in nanoseconds. + */ readonly endNs: time; + /** + * The duration of the slice in nanoseconds. + */ readonly durNs: duration; + /** + * The timestamp of the slice. + */ readonly ts: time; + /** + * The count associated with the slice. + */ readonly count: number; + /** + * The duration of the slice. + */ readonly dur: duration; + /** + * The depth of the slice in its track. + */ readonly depth: number; + /** + * Flags associated with the slice. + */ 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 + /** + * 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. + /** + * The title of the slice. These can be changed by the Impl. + */ title?: string; + /** + * The subtitle of the slice. + */ subTitle: string; + /** + * The color scheme used for the slice. + */ colorScheme: ColorScheme; + /** + * Whether the slice is currently highlighted. + */ isHighlighted: boolean; } /** - * Contains a track and it's top and bottom coordinates in the timeline. + * Contains a track and its top and bottom coordinates in the timeline. */ export interface TrackBounds { + /** + * The track node. + */ readonly node: TrackNode; + /** + * The vertical bounds of the track. + */ readonly verticalBounds: VerticalBounds; } +/** + * Defines a timeline overlay renderer. + */ export interface Overlay { + /** + * Renders the overlay on top of the tracks. + * @param ctx The 2D rendering context of the canvas. + * @param timescale The time scale used for translating between pixels and time. + * @param size The dimensions of the canvas in pixels. + * @param tracks A read-only array of track bounds. + * @param theme The canvas colors for the current theme. + */ render( ctx: CanvasRenderingContext2D, timescale: TimeScale,
diff --git a/ui/src/public/workspace.ts b/ui/src/public/workspace.ts index a8987c7..ed506cb 100644 --- a/ui/src/public/workspace.ts +++ b/ui/src/public/workspace.ts
@@ -15,11 +15,29 @@ import {assertTrue} from '../base/logging'; import {errResult, okResult, Result} from '../base/result'; +/** + * Manages workspaces, allowing for creation, switching, and access to the + * current workspace. + */ export interface WorkspaceManager { - // This is the same of ctx.workspace, exposed for consistency also here. + /** + * The currently active workspace. This is the same as `ctx.workspace`. + */ readonly currentWorkspace: Workspace; + /** + * A read-only array of all available workspaces. + */ readonly all: ReadonlyArray<Workspace>; + /** + * Creates a new, empty workspace. + * @param displayName The human-readable name for the new workspace. + * @returns The newly created workspace. + */ createEmptyWorkspace(displayName: string): Workspace; + /** + * Switches the currently active workspace. + * @param workspace The workspace to switch to. + */ switchWorkspace(workspace: Workspace): void; } @@ -37,80 +55,100 @@ * * TODO(stevegolton): We could possibly move this into its own module and use it * everywhere where session-unique ids are required. + * @returns A session-unique ID. */ function createSessionUniqueId(): string { return (sessionUniqueIdCounter++).toString(); } /** - * Describes generic parent track node functionality - i.e. any entity that can - * contain child TrackNodes, providing methods to add, remove, and access child - * nodes. - * - * This class is abstract because, while it can technically be instantiated on - * its own (no abstract methods/properties), it can't and shouldn't be - * instantiated anywhere in practice - all APIs require either a TrackNode or a - * Workspace. - * - * Thus, it serves two purposes: - * 1. Avoiding duplication between Workspace and TrackNode, which is an internal - * implementation detail of this module. - * 2. Providing a typescript interface for a generic TrackNode container class, - * which otherwise you might have to achieve using `Workspace | TrackNode` - * which is uglier. - * - * If you find yourself using this as a Javascript class in external code, e.g. - * `instance of TrackNodeContainer`, you're probably doing something wrong. + * Arguments for creating a new `TrackNode`. */ - export interface TrackNodeArgs { - name: string; - uri: string; - headless: boolean; - sortOrder: number; - collapsed: boolean; - isSummary: boolean; - removable: boolean; + /** + * The human-readable name for this track, displayed in the track shell. + */ + readonly name: string; + /** + * The URI of the track content to display. + */ + readonly uri: string; + /** + * If `true`, the header for this track will not be shown, and its children + * will be displayed un-nested. + */ + readonly headless: boolean; + /** + * An optional sort order, which workspaces may use for sorting. Lower numbers + * appear first. + */ + readonly sortOrder: number; + /** + * If `true`, the track will be initially collapsed. + */ + readonly collapsed: boolean; + /** + * If `true`, this track will be used as a summary for its children. + */ + readonly isSummary: boolean; + /** + * If `true`, this node will be removable by the user. + */ + readonly removable: boolean; } /** * A base class for any node with children (i.e. a group or a workspace). */ export class TrackNode { - // Immutable unique (within the workspace) ID of this track node. Used for - // efficiently retrieving this node object from a workspace. Note: This is - // different to |uri| which is used to reference a track to render on the - // track. If this means nothing to you, don't bother using it. + /** + * Immutable unique (within the workspace) ID of this track node. Used for + * efficiently retrieving this node object from a workspace. Note: This is + * different to `uri` which is used to reference a track to render on the + * track. If this means nothing to you, don't bother using it. + */ public readonly id: string; - // A human readable string for this track - displayed in the track shell. - // TODO(stevegolton): Make this optional, so that if we implement a string for - // this track then we can implement it here as well. + /** + * A human readable string for this track - displayed in the track shell. + * TODO(stevegolton): Make this optional, so that if we implement a string for + * this track then we can implement it here as well. + */ public name: string; - // The URI of the track content to display here. + /** + * The URI of the track content to display here. + */ public uri?: string; - // Optional sort order, which workspaces may or may not take advantage of for - // sorting when displaying the workspace. Lower numbers appear first. + /** + * Optional sort order, which workspaces may or may not take advantage of for + * sorting when displaying the workspace. Lower numbers appear first. + */ public sortOrder?: number; - // Don't show the header at all for this track, just show its un-nested - // children. This is helpful to group together tracks that logically belong to - // the same group (e.g. all ftrace cpu tracks) and ease the job of - // sorting/grouping plugins. + /** + * Don't show the header at all for this track, just show its un-nested + * children. This is helpful to group together tracks that logically belong to + * the same group (e.g. all ftrace cpu tracks) and ease the job of + * sorting/grouping plugins. + */ public headless: boolean; - // If true, this track is to be used as a summary for its children. When the - // group is expanded the track will become sticky to the top of the viewport - // to provide context for the tracks within, and the content of this track - // shall be omitted. It will also be squashed down to a smaller height to save - // vertical space. + /** + * If true, this track is to be used as a summary for its children. When the + * group is expanded the track will become sticky to the top of the viewport + * to provide context for the tracks within, and the content of this track + * shall be omitted. It will also be squashed down to a smaller height to save + * vertical space. + */ public isSummary: boolean; - // If true, this node will be removable by the user. It will show a little - // close button in the track shell which the user can press to remove the - // track from the workspace. + /** + * If true, this node will be removable by the user. It will show a little + * close button in the track shell which the user can press to remove the + * track from the workspace. + */ public removable: boolean; protected _collapsed = true; @@ -120,6 +158,9 @@ private _parent?: TrackNode; public _workspace?: Workspace; + /** + * The parent of this track node, or `undefined` if it is a root node. + */ get parent(): TrackNode | undefined { return this._parent; } @@ -146,7 +187,8 @@ } /** - * Remove this track from it's parent & unpin from the workspace if pinned. + * Removes this track from its parent and unpins it from the workspace if + * pinned. */ remove(): void { this.workspace?.unpinTrack(this); @@ -154,7 +196,7 @@ } /** - * Add this track to the list of pinned tracks in its parent workspace. + * Adds this track to the list of pinned tracks in its parent workspace. * * Has no effect if this track is not added to a workspace. */ @@ -163,7 +205,7 @@ } /** - * Remove this track from the list of pinned tracks in its parent workspace. + * Removes this track from the list of pinned tracks in its parent workspace. * * Has no effect if this track is not added to a workspace. */ @@ -172,23 +214,24 @@ } /** - * Returns true if this node is added to a workspace as is in the pinned track - * list of that workspace. + * Returns `true` if this node is added to a workspace and is in the pinned + * track list of that workspace. */ get isPinned(): boolean { return Boolean(this.workspace?.hasPinnedTrack(this)); } /** - * Find the closest visible ancestor TrackNode. + * Finds the closest visible ancestor TrackNode. * - * Given the path from the root workspace to this node, find the fist one, + * Given the path from the root workspace to this node, find the first one, * starting from the root, which is collapsed. This will be, from the user's * point of view, the closest ancestor of this node. * - * Returns undefined if this node is actually visible. + * Returns `undefined` if this node is actually visible. * * TODO(stevegolton): Should it return itself in this case? + * @returns The closest visible ancestor, or `this` if the node is visible. */ findClosestVisibleAncestor(): TrackNode { // Build a path from the root workspace to this node @@ -206,7 +249,7 @@ } /** - * Expand all ancestor nodes. + * Expands all ancestor nodes to make this node visible. */ reveal(): void { let parent = this.parent; @@ -217,8 +260,9 @@ } /** - * Get all ancestors of this node from root to immediate parent. - * Returns an empty array if this node has no parent. + * Gets all ancestors of this node from root to immediate parent. + * @returns An array of ancestor nodes, or an empty array if this node has no + * parent. */ getAncestors(): TrackNode[] { const ancestors: TrackNode[] = []; @@ -231,7 +275,7 @@ } /** - * Find this node's root node - this may be a workspace or another node. + * Finds this node's root node - this may be a workspace or another node. */ get rootNode(): TrackNode { let node: TrackNode = this; @@ -242,21 +286,21 @@ } /** - * Find this node's workspace if it is attached to one. + * Finds this node's workspace if it is attached to one. */ get workspace(): Workspace | undefined { return this.rootNode._workspace; } /** - * Mark this node as un-collapsed, indicating its children should be rendered. + * Marks this node as un-collapsed, indicating its children should be rendered. */ expand(): void { this._collapsed = false; } /** - * Mark this node as collapsed, indicating its children should not be + * Marks this node as collapsed, indicating its children should not be * rendered. */ collapse(): void { @@ -264,14 +308,15 @@ } /** - * Toggle the collapsed state. + * Toggles the collapsed state. */ toggleCollapsed(): void { this._collapsed = !this._collapsed; } /** - * Whether this node is collapsed, indicating its children should be rendered. + * Whether this node is collapsed, indicating its children should not be + * rendered. */ get collapsed(): boolean { return this._collapsed; @@ -287,7 +332,7 @@ /** * Returns the list of titles representing the full path from the root node to - * the current node. This path consists only of node titles, workspaces are + * the current node. This path consists only of node titles; workspaces are * omitted. */ get fullPath(): ReadonlyArray<string> { @@ -304,7 +349,7 @@ } /** - * True if this node has children, false otherwise. + * `true` if this node has children, `false` otherwise. */ get hasChildren(): boolean { return this._children.length > 0; @@ -318,14 +363,15 @@ } /** - * Inserts a new child node considering it's sortOrder. + * Inserts a new child node considering its `sortOrder`. * - * The child will be added before the first child whose |sortOrder| is greater + * The child will be added before the first child whose `sortOrder` is greater * than the child node's sort order, or at the end if one does not exist. If - * |sortOrder| is omitted on either node in the comparison it is assumed to be + * `sortOrder` is omitted on either node in the comparison it is assumed to be * 0. * - * @param child - The child node to add. + * @param child The child node to add. + * @returns A `Result` indicating success or failure. */ addChildInOrder(child: TrackNode): Result { const insertPoint = this._children.find( @@ -339,9 +385,10 @@ } /** - * Add a new child node at the start of the list of children. + * Adds a new child node at the end of the list of children. * * @param child The new child node to add. + * @returns A `Result` indicating success or failure. */ addChildLast(child: TrackNode): Result { const result = this.adopt(child); @@ -351,9 +398,10 @@ } /** - * Add a new child node at the end of the list of children. + * Adds a new child node at the start of the list of children. * * @param child The child node to add. + * @returns A `Result` indicating success or failure. */ addChildFirst(child: TrackNode): Result { const result = this.adopt(child); @@ -363,11 +411,12 @@ } /** - * Add a new child node before an existing child node. + * Adds a new child node before an existing child node. * * @param child The child node to add. * @param referenceNode An existing child node. The new node will be added - * before this node. + * before this node. + * @returns A `Result` indicating success or failure. */ addChildBefore(child: TrackNode, referenceNode: TrackNode): Result { // Nodes are the same, nothing to do. @@ -385,11 +434,12 @@ } /** - * Add a new child node after an existing child node. + * Adds a new child node after an existing child node. * * @param child The child node to add. * @param referenceNode An existing child node. The new node will be added - * after this node. + * after this node. + * @returns A `Result` indicating success or failure. */ addChildAfter(child: TrackNode, referenceNode: TrackNode): Result { // Nodes are the same, nothing to do. @@ -407,7 +457,7 @@ } /** - * Remove a child node from this node. + * Removes a child node from this node. * * @param child The child node to remove. */ @@ -419,9 +469,9 @@ } /** - * The flattened list of all descendent nodes in depth first order. + * The flattened list of all descendent nodes in depth-first order. * - * Use flatTracksUnordered if you don't care about track order, as it's more + * Use `flatTracks` if you don't care about track order, as it's more * efficient. */ get flatTracksOrdered(): ReadonlyArray<TrackNode> { @@ -445,7 +495,7 @@ } /** - * Remove all children from this node. + * Removes all children from this node. */ clear(): void { this._children = []; @@ -453,24 +503,24 @@ } /** - * Get a track node by its id. + * Gets a track node by its ID. * - * Node: This is an O(1) operation. + * Note: This is an O(1) operation. * - * @param id The id of the node we want to find. - * @returns The node or undefined if no such node exists. + * @param id The ID of the node we want to find. + * @returns The node or `undefined` if no such node exists. */ getTrackById(id: string): TrackNode | undefined { return this.tracksById.get(id); } /** - * Get a track node via its URI. + * Gets a track node via its URI. * - * Node: This is an O(1) operation. + * Note: This is an O(1) operation. * - * @param uri The uri of the track to find. - * @returns The node or undefined if no such node exists with this URI. + * @param uri The URI of the track to find. + * @returns The node or `undefined` if no such node exists with this URI. */ getTrackByUri(uri: string): TrackNode | undefined { return this.tracksByUri.get(uri); @@ -479,8 +529,8 @@ /** * Creates a copy of this node with a new ID. * - * @param deep - If true, children are copied too. - * @returns - A copy of this node. + * @param deep If `true`, children are copied too. + * @returns A copy of this node. */ clone(deep = false): TrackNode { const cloned = new TrackNode({...this, id: undefined}); @@ -552,14 +602,31 @@ * Defines a workspace containing a track tree and a pinned area. */ export class Workspace { + /** + * The human-readable title of the workspace. + */ public title = '<untitled-workspace>'; + /** + * The unique ID of the workspace. + */ public readonly id: string; + /** + * Whether the workspace is user-editable. + */ public userEditable: boolean = true; - // Dummy node to contain the pinned tracks + /** + * A dummy node to contain the pinned tracks. + */ public readonly pinnedTracksNode = new TrackNode(); + /** + * The root node for the main track tree. + */ public readonly tracks = new TrackNode(); + /** + * A read-only array of pinned tracks in this workspace. + */ get pinnedTracks(): ReadonlyArray<TrackNode> { return this.pinnedTracksNode.children; } @@ -575,7 +642,7 @@ } /** - * Reset the entire workspace including the pinned tracks. + * Resets the entire workspace including the pinned tracks. */ clear(): void { this.pinnedTracksNode.clear(); @@ -584,6 +651,7 @@ /** * Adds a track node to this workspace's pinned area. + * @param track The track to pin. */ pinTrack(track: TrackNode): void { // Make a lightweight clone of this track - just the uri and the title. @@ -597,6 +665,7 @@ /** * Removes a track node from this workspace's pinned area. + * @param track The track to unpin. */ unpinTrack(track: TrackNode): void { const foundNode = this.pinnedTracksNode.children.find( @@ -608,31 +677,33 @@ } /** - * Check if this workspace has a pinned track with the same URI as |track|. + * Checks if this workspace has a pinned track with the same URI as `track`. + * @param track The track to check. + * @returns `true` if a matching pinned track is found, `false` otherwise. */ hasPinnedTrack(track: TrackNode): boolean { return this.pinnedTracksNode.flatTracks.some((p) => p.uri === track.uri); } /** - * Get a track node via its URI. + * Gets a track node via its URI. * - * Node: This is an O(1) operation. + * Note: This is an O(1) operation. * - * @param uri The uri of the track to find. - * @returns The node or undefined if no such node exists with this URI. + * @param uri The URI of the track to find. + * @returns The node or `undefined` if no such node exists with this URI. */ getTrackByUri(uri: string): TrackNode | undefined { return this.tracks.flatTracks.find((t) => t.uri === uri); } /** - * Get a track node by its id. + * Gets a track node by its ID. * - * Node: This is an O(1) operation. + * Note: This is an O(1) operation. * - * @param id The id of the node we want to find. - * @returns The node or undefined if no such node exists. + * @param id The ID of the node we want to find. + * @returns The node or `undefined` if no such node exists. */ getTrackById(id: string): TrackNode | undefined { return ( @@ -648,61 +719,66 @@ } /** - * Inserts a new child node considering it's sortOrder. + * Inserts a new child node considering its `sortOrder`. * - * The child will be added before the first child whose |sortOrder| is greater + * The child will be added before the first child whose `sortOrder` is greater * than the child node's sort order, or at the end if one does not exist. If - * |sortOrder| is omitted on either node in the comparison it is assumed to be + * `sortOrder` is omitted on either node in the comparison it is assumed to be * 0. * - * @param child - The child node to add. + * @param child The child node to add. + * @returns A `Result` indicating success or failure. */ addChildInOrder(child: TrackNode): Result { return this.tracks.addChildInOrder(child); } /** - * Add a new child node at the start of the list of children. + * Adds a new child node at the end of the list of children. * * @param child The new child node to add. + * @returns A `Result` indicating success or failure. */ addChildLast(child: TrackNode): Result { return this.tracks.addChildLast(child); } /** - * Add a new child node at the end of the list of children. + * Adds a new child node at the start of the list of children. * * @param child The child node to add. + * @returns A `Result` indicating success or failure. */ addChildFirst(child: TrackNode): Result { return this.tracks.addChildFirst(child); } /** - * Add a new child node before an existing child node. + * Adds a new child node before an existing child node. * * @param child The child node to add. * @param referenceNode An existing child node. The new node will be added - * before this node. + * before this node. + * @returns A `Result` indicating success or failure. */ addChildBefore(child: TrackNode, referenceNode: TrackNode): Result { return this.tracks.addChildBefore(child, referenceNode); } /** - * Add a new child node after an existing child node. + * Adds a new child node after an existing child node. * * @param child The child node to add. * @param referenceNode An existing child node. The new node will be added - * after this node. + * after this node. + * @returns A `Result` indicating success or failure. */ addChildAfter(child: TrackNode, referenceNode: TrackNode): Result { return this.tracks.addChildAfter(child, referenceNode); } /** - * Remove a child node from this node. + * Removes a child node from this node. * * @param child The child node to remove. */ @@ -711,19 +787,19 @@ } /** - * The flattened list of all descendent nodes in depth first order. + * The flattened list of all descendent nodes in depth-first order. * - * Use flatTracksUnordered if you don't care about track order, as it's more + * Use `flatTracks` if you don't care about track order, as it's more * efficient. */ - get flatTracksOrdered() { + get flatTracksOrdered(): ReadonlyArray<TrackNode> { return this.tracks.flatTracksOrdered; } /** * The flattened list of all descendent nodes in no particular order. */ - get flatTracks() { + get flatTracks(): ReadonlyArray<TrackNode> { return this.tracks.flatTracks; } }