blob: ee73ced9a866224b7f0499565c5920fe508e65cc [file] [log] [blame] [edit]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
import {duration, time} from '../base/time';
import {Size2D, VerticalBounds} from '../base/geom';
import {TimeScale} from '../base/time_scale';
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
import {ColorScheme} from '../base/color_scheme';
import {TrackEventDetailsPanel} from './details_panel';
import {TrackEventDetails, TrackEventSelection} from './selection';
import {SourceDataset} from '../trace_processor/dataset';
import {TrackNode} from './workspace';
import {CanvasColors} from './canvas_colors';
import {z} from 'zod';
/**
* Represents a snap point for the snap-to-boundaries feature.
* When dragging selection boundaries, the cursor can snap to these points
* to enable precise measurement of time intervals.
*/
export interface SnapPoint {
/** The timestamp to snap to */
time: time;
}
export interface TrackFilterCriteria {
readonly name: string;
// Run on each node to work out whether it satisfies the selected filter
// option.
readonly predicate: (track: TrackNode, filterOption: string) => boolean;
// The list of possible filter options.
readonly options: ReadonlyArray<{key: string; label: string}>;
}
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.
*/
registerTrack(track: Track): void;
findTrack(
predicate: (track: Track) => boolean | undefined,
): Track | undefined;
getAllTracks(): Track[];
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.
*/
registerTrackFilterCriteria(filter: TrackFilterCriteria): void;
/**
* Register 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.
*/
registerOverlay(overlay: Overlay): void;
}
export interface TrackContext {
// This track's URI, used for making selections et al.
readonly trackUri: string;
}
/**
* Contextual information about the track passed to track lifecycle hooks &
* render hooks with additional information about the timeline/canvas.
*/
export interface TrackRenderContext extends TrackContext {
/**
* The time span of the visible window.
*/
readonly visibleWindow: HighPrecisionTimeSpan;
/**
* The dimensions of the track on the canvas in pixels.
*/
readonly size: Size2D;
/**
* Suggested data resolution.
*
* This number is the number of time units that corresponds to 1 pixel on the
* screen, rounded down to the nearest power of 2. The minimum value is 1.
*
* It's up to the track whether it would like to use this resolution or
* calculate their own based on the timespan and the track dimensions.
*/
readonly resolution: duration;
/**
* Canvas context used for rendering.
*/
readonly ctx: CanvasRenderingContext2D;
/**
* A time scale used for translating between pixels and time.
*/
readonly timescale: TimeScale;
/**
* Semantic colors which can vary depending on the current theme.
*/
readonly colors: CanvasColors;
}
// A definition of a track, including a renderer implementation and metadata.
export interface Track {
// A unique identifier for this track.
readonly uri: string;
// 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.
readonly description?: string | (() => m.Children);
// 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.
readonly tags?: TrackTags;
// Optional: A list of strings which are displayed as "chips" in the track
// shell.
readonly chips?: ReadonlyArray<string>;
// Filled in by the core.
readonly pluginId?: string;
}
/**
* Contextual information passed to mouse events.
*/
export interface TrackMouseEvent {
/**
* X coordinate of the mouse event w.r.t. the top-left of the track.
*/
readonly x: number;
/**
* Y coordinate of the mouse event w.r.t the top-left of the track.
*/
readonly y: number;
/**
* A time scale used for translating between pixels and time.
*/
readonly timescale: TimeScale;
}
/**
* A descriptor for a track setting, which describes the setting's metadata and
* how to render a control for it. This is separate from the track setting
* itself because multiple tracks (which will need different TrackSetting
* instances) can share the same descriptor, which would make them editable in
* bulk.
*
* A lot of the fields in this interface are currently unused, but they will be
* used in the future when track serialization is implemented.
*/
export interface TrackSettingDescriptor<T> {
// 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.
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.
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.
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.
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.
render?(setter: (value: T) => void, values: ReadonlyArray<T>): m.Children;
}
/**
* 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.
*/
export interface TrackSetting<T> {
readonly descriptor: TrackSettingDescriptor<T>;
getValue: () => T;
setValue(newValue: T): void;
}
export interface TrackRenderer {
/**
* Describes which root table the events on this track come from. This is
* mainly for use by flows (before they get refactored to be more generic) and
* will be used by the SQL table resolver mechanism along with dataset.
* TODO(stevegolton): Maybe move this onto dataset directly?
*/
readonly rootTableName?: string;
/**
* An optional list of settings that can be modified externally to affect how
* the track is rendered or behaves.
*
* These settings are user-editable and are shown in the track settings menu.
* They can also be modified in bulk if multiple tracks share the same setting
* descriptor.
*
* Note: The core does not yet provide a mechanism to persist these settings
* to permalinks.
*/
readonly settings?: ReadonlyArray<TrackSetting<unknown>>;
/**
* Optional lifecycle hook called on the first render cycle. Should be used to
* create any required resources.
*
* These lifecycle hooks are asynchronous, but they are run synchronously,
* meaning that perfetto will wait for each one to complete before calling the
* next one, so the user doesn't have to serialize these calls manually.
*
* Exactly when this hook is called is left purposely undefined. The only
* guarantee is that it will be called exactly once before the first call to
* onUpdate().
*
* Note: On the first render cycle, both onCreate and onUpdate are called one
* after another.
*/
onCreate?(ctx: TrackContext): Promise<void>;
/**
* Optional lifecycle hook called on every render cycle.
*
* 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.
*/
onUpdate?(ctx: TrackRenderContext): Promise<void>;
/**
* Optional lifecycle hook called when the track is no longer visible. Should
* be used to clear up any resources.
*/
onDestroy?(): Promise<void>;
/**
* Required method used to render the track's content to the canvas, called
* synchronously on every render cycle.
*/
render(ctx: TrackRenderContext): void;
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.
*/
getSliceVerticalBounds?(depth: number): VerticalBounds | undefined;
getHeight?(): number;
getTrackShellButtons?(): m.Children;
onMouseMove?(event: TrackMouseEvent): void;
onMouseClick?(event: TrackMouseEvent): boolean;
onMouseOut?(): void;
/**
* Optional: Returns a dataset that represents the events displayed on this
* track.
*/
getDataset?(): SourceDataset | undefined;
/**
* Optional: Get details of a track event given by eventId on this track.
*/
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).
detailsPanel?(sel: TrackEventSelection): TrackEventDetailsPanel | undefined;
// Optional: Returns tooltip content if available. If the return value is
// falsy, no tooltip is rendered.
renderTooltip?(): m.Children;
/**
* Optional: Find the nearest snap point to the given time.
* Returns undefined if no snap point is within threshold.
*
* This method is used by the snap-to-boundaries feature to enable precise
* measurement of time intervals. When dragging selection boundaries, the
* cursor can snap to these points.
*
* @param targetTime - Target time to snap from
* @param thresholdPx - Maximum pixel distance to snap
* @param timescale - For converting between time and pixels
* @returns The nearest snap point, or undefined if none within threshold
*/
getSnapPoint?(
targetTime: time,
thresholdPx: number,
timescale: TimeScale,
): SnapPoint | undefined;
}
// An set of key/value pairs describing a given track. These are used for
// selecting tracks to pin/unpin, diplsaying "chips" in the track shell, and
// (in future) the sorting and grouping of tracks.
// We define a handful of well known fields, and the rest are arbitrary key-
// value pairs.
export type TrackTags = Partial<WellKnownTrackTags> & {
// There may be arbitrary other key/value pairs.
[key: string]:
| undefined
| string
| number
| boolean
| ReadonlyArray<string>
| ReadonlyArray<number>;
};
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>;
// 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: The CPU number associated with this track.
cpu: number;
// Optional: The UTID associated with this track.
utid: number;
// Optional: The UPID associated with this track.
upid: number;
// Track type, used for filtering
type: string;
}
export interface Slice {
// These properties are updated only once per query result when the Slice
// object is created and don't change afterwards.
readonly id: number;
readonly startNs: time;
readonly endNs: time;
readonly durNs: duration;
readonly ts: time;
readonly count: number;
readonly dur: duration;
readonly depth: number;
readonly flags: number;
// Each slice can represent some extra numerical information by rendering a
// portion of the slice with a lighter tint.
// |fillRatio\ describes the ratio of the normal area to the tinted area
// width of the slice, normalized between 0.0 -> 1.0.
// 0.0 means the whole slice is tinted.
// 1.0 means none of the slice is tinted.
// E.g. If |fillRatio| = 0.65 the slice will be rendered like this:
// [############|*******]
// ^------------^-------^
// Normal Light
readonly fillRatio: number;
// These can be changed by the Impl.
title?: string;
subTitle: string;
colorScheme: ColorScheme;
isHighlighted: boolean;
}
/**
* Contains a track and it's top and bottom coordinates in the timeline.
*/
export interface TrackBounds {
readonly node: TrackNode;
readonly verticalBounds: VerticalBounds;
}
export interface Overlay {
render(
ctx: CanvasRenderingContext2D,
timescale: TimeScale,
size: Size2D,
tracks: ReadonlyArray<TrackBounds>,
theme: CanvasColors,
): void;
}