xref: /aosp_15_r20/external/perfetto/docs/contributing/ui-plugins.md (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker# UI plugins
2*6dbdd20aSAndroid Build Coastguard WorkerThe Perfetto UI can be extended with plugins. These plugins are shipped part of
3*6dbdd20aSAndroid Build Coastguard WorkerPerfetto.
4*6dbdd20aSAndroid Build Coastguard Worker
5*6dbdd20aSAndroid Build Coastguard Worker## Create a plugin
6*6dbdd20aSAndroid Build Coastguard WorkerThe guide below explains how to create a plugin for the Perfetto UI. You can
7*6dbdd20aSAndroid Build Coastguard Workerbrowse the public plugin API [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public).
8*6dbdd20aSAndroid Build Coastguard Worker
9*6dbdd20aSAndroid Build Coastguard Worker### Prepare for UI development
10*6dbdd20aSAndroid Build Coastguard WorkerFirst we need to prepare the UI development environment. You will need to use a
11*6dbdd20aSAndroid Build Coastguard WorkerMacOS or Linux machine. Follow the steps below or see the [Getting
12*6dbdd20aSAndroid Build Coastguard WorkerStarted](./getting-started) guide for more detail.
13*6dbdd20aSAndroid Build Coastguard Worker
14*6dbdd20aSAndroid Build Coastguard Worker```sh
15*6dbdd20aSAndroid Build Coastguard Workergit clone https://android.googlesource.com/platform/external/perfetto/
16*6dbdd20aSAndroid Build Coastguard Workercd perfetto
17*6dbdd20aSAndroid Build Coastguard Worker./tools/install-build-deps --ui
18*6dbdd20aSAndroid Build Coastguard Worker```
19*6dbdd20aSAndroid Build Coastguard Worker
20*6dbdd20aSAndroid Build Coastguard Worker### Copy the plugin skeleton
21*6dbdd20aSAndroid Build Coastguard Worker```sh
22*6dbdd20aSAndroid Build Coastguard Workercp -r ui/src/plugins/com.example.Skeleton ui/src/plugins/<your-plugin-name>
23*6dbdd20aSAndroid Build Coastguard Worker```
24*6dbdd20aSAndroid Build Coastguard WorkerNow edit `ui/src/plugins/<your-plugin-name>/index.ts`. Search for all instances
25*6dbdd20aSAndroid Build Coastguard Workerof `SKELETON: <instruction>` in the file and follow the instructions.
26*6dbdd20aSAndroid Build Coastguard Worker
27*6dbdd20aSAndroid Build Coastguard WorkerNotes on naming:
28*6dbdd20aSAndroid Build Coastguard Worker- Don't name the directory `XyzPlugin` just `Xyz`.
29*6dbdd20aSAndroid Build Coastguard Worker- The `pluginId` and directory name must match.
30*6dbdd20aSAndroid Build Coastguard Worker- Plugins should be prefixed with the reversed components of a domain name you
31*6dbdd20aSAndroid Build Coastguard Worker  control. For example if `example.com` is your domain your plugin should be
32*6dbdd20aSAndroid Build Coastguard Worker  named `com.example.Foo`.
33*6dbdd20aSAndroid Build Coastguard Worker- Core plugins maintained by the Perfetto team should use `dev.perfetto.Foo`.
34*6dbdd20aSAndroid Build Coastguard Worker- Commands should have ids with the pattern `example.com#DoSomething`
35*6dbdd20aSAndroid Build Coastguard Worker- Command's ids should be prefixed with the id of the plugin which provides
36*6dbdd20aSAndroid Build Coastguard Worker  them.
37*6dbdd20aSAndroid Build Coastguard Worker- Command names should have the form "Verb something something", and should be
38*6dbdd20aSAndroid Build Coastguard Worker  in normal sentence case. I.e. don't capitalize the first letter of each word.
39*6dbdd20aSAndroid Build Coastguard Worker  - Good: "Pin janky frame timeline tracks"
40*6dbdd20aSAndroid Build Coastguard Worker  - Bad: "Tracks are Displayed if Janky"
41*6dbdd20aSAndroid Build Coastguard Worker
42*6dbdd20aSAndroid Build Coastguard Worker### Start the dev server
43*6dbdd20aSAndroid Build Coastguard Worker```sh
44*6dbdd20aSAndroid Build Coastguard Worker./ui/run-dev-server
45*6dbdd20aSAndroid Build Coastguard Worker```
46*6dbdd20aSAndroid Build Coastguard WorkerNow navigate to [localhost:10000](http://localhost:10000/)
47*6dbdd20aSAndroid Build Coastguard Worker
48*6dbdd20aSAndroid Build Coastguard Worker### Enable your plugin
49*6dbdd20aSAndroid Build Coastguard Worker- Navigate to the plugins page:
50*6dbdd20aSAndroid Build Coastguard Worker  [localhost:10000/#!/plugins](http://localhost:10000/#!/plugins).
51*6dbdd20aSAndroid Build Coastguard Worker- Ctrl-F for your plugin name and enable it.
52*6dbdd20aSAndroid Build Coastguard Worker- Enabling/disabling plugins requires a restart of the UI, so refresh the page
53*6dbdd20aSAndroid Build Coastguard Worker  to start your plugin.
54*6dbdd20aSAndroid Build Coastguard Worker
55*6dbdd20aSAndroid Build Coastguard WorkerLater you can request for your plugin to be enabled by default. Follow the
56*6dbdd20aSAndroid Build Coastguard Worker[default plugins](#default-plugins) section for this.
57*6dbdd20aSAndroid Build Coastguard Worker
58*6dbdd20aSAndroid Build Coastguard Worker### Upload your plugin for review
59*6dbdd20aSAndroid Build Coastguard Worker- Update `ui/src/plugins/<your-plugin-name>/OWNERS` to include your email.
60*6dbdd20aSAndroid Build Coastguard Worker- Follow the [Contributing](./getting-started#contributing) instructions to
61*6dbdd20aSAndroid Build Coastguard Worker  upload your CL to the codereview tool.
62*6dbdd20aSAndroid Build Coastguard Worker- Once uploaded add `[email protected]` as a reviewer for your CL.
63*6dbdd20aSAndroid Build Coastguard Worker
64*6dbdd20aSAndroid Build Coastguard Worker## Plugin Lifecycle
65*6dbdd20aSAndroid Build Coastguard WorkerTo demonstrate the plugin's lifecycle, this is a minimal plugin that implements
66*6dbdd20aSAndroid Build Coastguard Workerthe key lifecycle hooks:
67*6dbdd20aSAndroid Build Coastguard Worker
68*6dbdd20aSAndroid Build Coastguard Worker```ts
69*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
70*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
71*6dbdd20aSAndroid Build Coastguard Worker
72*6dbdd20aSAndroid Build Coastguard Worker  static onActivate(app: App): void {
73*6dbdd20aSAndroid Build Coastguard Worker    // Called once on app startup
74*6dbdd20aSAndroid Build Coastguard Worker    console.log('MyPlugin::onActivate()', app.pluginId);
75*6dbdd20aSAndroid Build Coastguard Worker    // Note: It's rare that plugins would need this hook as most plugins are
76*6dbdd20aSAndroid Build Coastguard Worker    // interested in trace details. Thus, this function can usually be omitted.
77*6dbdd20aSAndroid Build Coastguard Worker  }
78*6dbdd20aSAndroid Build Coastguard Worker
79*6dbdd20aSAndroid Build Coastguard Worker  constructor(trace: Trace) {
80*6dbdd20aSAndroid Build Coastguard Worker    // Called each time a trace is loaded
81*6dbdd20aSAndroid Build Coastguard Worker    console.log('MyPlugin::constructor()', trace.traceInfo.traceTitle);
82*6dbdd20aSAndroid Build Coastguard Worker  }
83*6dbdd20aSAndroid Build Coastguard Worker
84*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace): Promise<void> {
85*6dbdd20aSAndroid Build Coastguard Worker    // Called each time a trace is loaded
86*6dbdd20aSAndroid Build Coastguard Worker    console.log('MyPlugin::onTraceLoad()', trace.traceInfo.traceTitle);
87*6dbdd20aSAndroid Build Coastguard Worker    // Note this function returns a promise, so any any async calls should be
88*6dbdd20aSAndroid Build Coastguard Worker    // completed before this promise resolves as the app using this promise for
89*6dbdd20aSAndroid Build Coastguard Worker    // timing and plugin synchronization.
90*6dbdd20aSAndroid Build Coastguard Worker  }
91*6dbdd20aSAndroid Build Coastguard Worker}
92*6dbdd20aSAndroid Build Coastguard Worker```
93*6dbdd20aSAndroid Build Coastguard Worker
94*6dbdd20aSAndroid Build Coastguard WorkerYou can run this plugin with devtools to see the log messages in the console,
95*6dbdd20aSAndroid Build Coastguard Workerwhich should give you a feel for the plugin lifecycle. Try opening a few traces
96*6dbdd20aSAndroid Build Coastguard Workerone after another.
97*6dbdd20aSAndroid Build Coastguard Worker
98*6dbdd20aSAndroid Build Coastguard Worker`onActivate()` runs shortly after Perfetto starts up, before a trace is loaded.
99*6dbdd20aSAndroid Build Coastguard WorkerThis is where the you'll configure your plugin's capabilities that aren't trace
100*6dbdd20aSAndroid Build Coastguard Workerdependent. At this point the plugin's class is not instantiated, so you'll
101*6dbdd20aSAndroid Build Coastguard Workernotice `onActivate()` hook is a static class member. `onActivate()` is only ever
102*6dbdd20aSAndroid Build Coastguard Workercalled once, regardless of the number of traces loaded.
103*6dbdd20aSAndroid Build Coastguard Worker
104*6dbdd20aSAndroid Build Coastguard Worker`onActivate()` is passed an `App` object which the plugin can use to configure
105*6dbdd20aSAndroid Build Coastguard Workercore capabilities such as commands, sidebar items and pages. Capabilities
106*6dbdd20aSAndroid Build Coastguard Workerregistered on the App interface are persisted throughout the lifetime of the app
107*6dbdd20aSAndroid Build Coastguard Worker(practically forever until the tab is closed), in contrast to what happens for
108*6dbdd20aSAndroid Build Coastguard Workerthe same methods on the `Trace` object (see below).
109*6dbdd20aSAndroid Build Coastguard Worker
110*6dbdd20aSAndroid Build Coastguard WorkerThe plugin class in instantiated when a trace is loaded (a new plugin instance
111*6dbdd20aSAndroid Build Coastguard Workeris created for each trace). `onTraceLoad()` is called immediately after the
112*6dbdd20aSAndroid Build Coastguard Workerclass is instantiated, which is where you'll configure your plugin's trace
113*6dbdd20aSAndroid Build Coastguard Workerdependent capabilities.
114*6dbdd20aSAndroid Build Coastguard Worker
115*6dbdd20aSAndroid Build Coastguard Worker`onTraceLoad()` is passed a `Trace` object which the plugin can use to configure
116*6dbdd20aSAndroid Build Coastguard Workerentities that are scoped to a specific trace, such as tracks and tabs. `Trace`
117*6dbdd20aSAndroid Build Coastguard Workeris a superset of `App`, so anything you can do with `App` you can also do with
118*6dbdd20aSAndroid Build Coastguard Worker`Trace`, however, capabilities registered on `Trace` will typically be discarded
119*6dbdd20aSAndroid Build Coastguard Workerwhen a new trace is loaded.
120*6dbdd20aSAndroid Build Coastguard Worker
121*6dbdd20aSAndroid Build Coastguard WorkerA plugin will typically register capabilities with the core and return quickly.
122*6dbdd20aSAndroid Build Coastguard WorkerBut these capabilities usually contain objects and callbacks which are called
123*6dbdd20aSAndroid Build Coastguard Workerinto later by the core during the runtime of the app. Most capabilities require
124*6dbdd20aSAndroid Build Coastguard Workera `Trace` or an `App` to do anything useful so these are usually bound into the
125*6dbdd20aSAndroid Build Coastguard Workercapabilities at registration time using JavaScript classes or closures.
126*6dbdd20aSAndroid Build Coastguard Worker
127*6dbdd20aSAndroid Build Coastguard Worker```ts
128*6dbdd20aSAndroid Build Coastguard Worker// Toy example: Code will not compile.
129*6dbdd20aSAndroid Build Coastguard Workerasync onTraceLoad(trace: Trace) {
130*6dbdd20aSAndroid Build Coastguard Worker  // `trace` is captured in the closure and used later by the app
131*6dbdd20aSAndroid Build Coastguard Worker  trace.regsterXYZ(() => trace.xyz);
132*6dbdd20aSAndroid Build Coastguard Worker}
133*6dbdd20aSAndroid Build Coastguard Worker```
134*6dbdd20aSAndroid Build Coastguard Worker
135*6dbdd20aSAndroid Build Coastguard WorkerThat way, the callback is bound to a specific trace object which and the trace
136*6dbdd20aSAndroid Build Coastguard Workerobject can outlive the runtime of the `onTraceLoad()` function, which is a very
137*6dbdd20aSAndroid Build Coastguard Workercommon pattern in Perfetto plugins.
138*6dbdd20aSAndroid Build Coastguard Worker
139*6dbdd20aSAndroid Build Coastguard Worker> Note: Some capabilities can be registered on either the `App` or the `Trace`
140*6dbdd20aSAndroid Build Coastguard Worker> object (i.e. in `onActivate()` or in `onTraceLoad()`), if in doubt about which
141*6dbdd20aSAndroid Build Coastguard Worker> one to use, use `onTraceLoad()` as this is more than likely the one you want.
142*6dbdd20aSAndroid Build Coastguard Worker> Most plugins add tracks and tabs that depend on the trace. You'd usually have
143*6dbdd20aSAndroid Build Coastguard Worker> to be doing something out of the ordinary if you need to use `onActivate()`.
144*6dbdd20aSAndroid Build Coastguard Worker
145*6dbdd20aSAndroid Build Coastguard Worker### Performance
146*6dbdd20aSAndroid Build Coastguard Worker`onActivate()` and `onTraceLoad()` should generally complete as quickly as
147*6dbdd20aSAndroid Build Coastguard Workerpossible, however sometimes `onTraceLoad()` may need to perform async operations
148*6dbdd20aSAndroid Build Coastguard Workeron trace processor such as performing queries and/or creating views and tables.
149*6dbdd20aSAndroid Build Coastguard WorkerThus, `onTraceLoad()` should return a promise (or you can simply make it an
150*6dbdd20aSAndroid Build Coastguard Workerasync function). When this promise resolves it tells the core that the plugin is
151*6dbdd20aSAndroid Build Coastguard Workerfully initialized.
152*6dbdd20aSAndroid Build Coastguard Worker
153*6dbdd20aSAndroid Build Coastguard Worker> Note: It's important that any async operations done in onTraceLoad() are
154*6dbdd20aSAndroid Build Coastguard Worker> awaited so that all async operations are completed by the time the promise is
155*6dbdd20aSAndroid Build Coastguard Worker> resolved. This is so that plugins can be properly timed and synchronized.
156*6dbdd20aSAndroid Build Coastguard Worker
157*6dbdd20aSAndroid Build Coastguard Worker
158*6dbdd20aSAndroid Build Coastguard Worker```ts
159*6dbdd20aSAndroid Build Coastguard Worker// GOOD
160*6dbdd20aSAndroid Build Coastguard Workerasync onTraceLoad(trace: Trace) {
161*6dbdd20aSAndroid Build Coastguard Worker  await trace.engine.query(...);
162*6dbdd20aSAndroid Build Coastguard Worker}
163*6dbdd20aSAndroid Build Coastguard Worker
164*6dbdd20aSAndroid Build Coastguard Worker// BAD
165*6dbdd20aSAndroid Build Coastguard Workerasync onTraceLoad(trace: Trace) {
166*6dbdd20aSAndroid Build Coastguard Worker  // Note the missing await!
167*6dbdd20aSAndroid Build Coastguard Worker  trace.engine.query(...);
168*6dbdd20aSAndroid Build Coastguard Worker}
169*6dbdd20aSAndroid Build Coastguard Worker```
170*6dbdd20aSAndroid Build Coastguard Worker
171*6dbdd20aSAndroid Build Coastguard Worker## Extension Points
172*6dbdd20aSAndroid Build Coastguard WorkerPlugins can extend functionality of Perfetto by registering capabilities via
173*6dbdd20aSAndroid Build Coastguard Workerextension points on the `App` or `Trace` objects.
174*6dbdd20aSAndroid Build Coastguard Worker
175*6dbdd20aSAndroid Build Coastguard WorkerThe following sections delve into more detail on each extension point and
176*6dbdd20aSAndroid Build Coastguard Workerprovide examples of how they can be used.
177*6dbdd20aSAndroid Build Coastguard Worker
178*6dbdd20aSAndroid Build Coastguard Worker### Commands
179*6dbdd20aSAndroid Build Coastguard WorkerCommands are user issuable shortcuts for actions in the UI. They are invoked via
180*6dbdd20aSAndroid Build Coastguard Workerthe command palette which can be opened by pressing Ctrl+Shift+P (or Cmd+Shift+P
181*6dbdd20aSAndroid Build Coastguard Workeron Mac), or by typing a '>' into the omnibox.
182*6dbdd20aSAndroid Build Coastguard Worker
183*6dbdd20aSAndroid Build Coastguard WorkerTo add a command, add a call to `registerCommand()` on either your
184*6dbdd20aSAndroid Build Coastguard Worker`onActivate()` or `onTraceLoad()` hooks. The recommendation is to register
185*6dbdd20aSAndroid Build Coastguard Workercommands in `onTraceLoad()` by default unless you very specifically want the
186*6dbdd20aSAndroid Build Coastguard Workercommand to be available before a trace has loaded.
187*6dbdd20aSAndroid Build Coastguard Worker
188*6dbdd20aSAndroid Build Coastguard WorkerExample of a command that doesn't require a trace.
189*6dbdd20aSAndroid Build Coastguard Worker```ts
190*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
191*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
192*6dbdd20aSAndroid Build Coastguard Worker  static onActivate(app: App) {
193*6dbdd20aSAndroid Build Coastguard Worker    app.commands.registerCommand({
194*6dbdd20aSAndroid Build Coastguard Worker      id: `${app.pluginId}#SayHello`,
195*6dbdd20aSAndroid Build Coastguard Worker      name: 'Say hello',
196*6dbdd20aSAndroid Build Coastguard Worker      callback: () => console.log('Hello, world!'),
197*6dbdd20aSAndroid Build Coastguard Worker    });
198*6dbdd20aSAndroid Build Coastguard Worker  }
199*6dbdd20aSAndroid Build Coastguard Worker}
200*6dbdd20aSAndroid Build Coastguard Worker```
201*6dbdd20aSAndroid Build Coastguard Worker
202*6dbdd20aSAndroid Build Coastguard WorkerExample of a command that requires a trace object - in this case the trace
203*6dbdd20aSAndroid Build Coastguard Workertitle.
204*6dbdd20aSAndroid Build Coastguard Worker```ts
205*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
206*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
207*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
208*6dbdd20aSAndroid Build Coastguard Worker    trace.commands.registerCommand({
209*6dbdd20aSAndroid Build Coastguard Worker      id: `${trace.pluginId}#LogTraceTitle`,
210*6dbdd20aSAndroid Build Coastguard Worker      name: 'Log trace title',
211*6dbdd20aSAndroid Build Coastguard Worker      callback: () => console.log(trace.info.traceTitle),
212*6dbdd20aSAndroid Build Coastguard Worker    });
213*6dbdd20aSAndroid Build Coastguard Worker  }
214*6dbdd20aSAndroid Build Coastguard Worker}
215*6dbdd20aSAndroid Build Coastguard Worker```
216*6dbdd20aSAndroid Build Coastguard Worker
217*6dbdd20aSAndroid Build Coastguard Worker> Notice that the trace object is captured in the closure, so it can be used
218*6dbdd20aSAndroid Build Coastguard Worker> after the onTraceLoad() function has returned. This is a very common pattern
219*6dbdd20aSAndroid Build Coastguard Worker> in Perfetto plugins.
220*6dbdd20aSAndroid Build Coastguard Worker
221*6dbdd20aSAndroid Build Coastguard WorkerCommand arguments explained:
222*6dbdd20aSAndroid Build Coastguard Worker- `id` is a unique string which identifies this command. The `id` should be
223*6dbdd20aSAndroid Build Coastguard Workerprefixed with the plugin id followed by a `#`. All command `id`s must be unique
224*6dbdd20aSAndroid Build Coastguard Workersystem-wide.
225*6dbdd20aSAndroid Build Coastguard Worker- `name` is a human readable name for the command, which is shown in the command
226*6dbdd20aSAndroid Build Coastguard Workerpalette.
227*6dbdd20aSAndroid Build Coastguard Worker- `callback()` is the callback which actually performs the action.
228*6dbdd20aSAndroid Build Coastguard Worker
229*6dbdd20aSAndroid Build Coastguard Worker#### Async commands
230*6dbdd20aSAndroid Build Coastguard WorkerIt's common that commands will perform async operations in their callbacks. It's
231*6dbdd20aSAndroid Build Coastguard Workerrecommended to use async/await for this rather than `.then().catch()`. The
232*6dbdd20aSAndroid Build Coastguard Workereasiest way to do this is to make the callback an async function.
233*6dbdd20aSAndroid Build Coastguard Worker
234*6dbdd20aSAndroid Build Coastguard Worker```ts
235*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
236*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
237*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
238*6dbdd20aSAndroid Build Coastguard Worker    trace.commands.registerCommand({
239*6dbdd20aSAndroid Build Coastguard Worker      id: `${trace.pluginId}#QueryTraceProcessor`,
240*6dbdd20aSAndroid Build Coastguard Worker      name: 'Query trace processor',
241*6dbdd20aSAndroid Build Coastguard Worker      callback: async () => {
242*6dbdd20aSAndroid Build Coastguard Worker        const results = await trace.engine.query(...);
243*6dbdd20aSAndroid Build Coastguard Worker        // use results...
244*6dbdd20aSAndroid Build Coastguard Worker      },
245*6dbdd20aSAndroid Build Coastguard Worker    });
246*6dbdd20aSAndroid Build Coastguard Worker  }
247*6dbdd20aSAndroid Build Coastguard Worker}
248*6dbdd20aSAndroid Build Coastguard Worker```
249*6dbdd20aSAndroid Build Coastguard Worker
250*6dbdd20aSAndroid Build Coastguard WorkerIf the callback is async (i.e. it returns a promise), nothing special happens.
251*6dbdd20aSAndroid Build Coastguard WorkerThe command is still fire-n-forget as far as the core is concerned.
252*6dbdd20aSAndroid Build Coastguard Worker
253*6dbdd20aSAndroid Build Coastguard WorkerExamples:
254*6dbdd20aSAndroid Build Coastguard Worker- [com.example.ExampleSimpleCommand](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/com.example.ExampleSimpleCommand/index.ts).
255*6dbdd20aSAndroid Build Coastguard Worker- [perfetto.CoreCommands](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/core_plugins/commands/index.ts).
256*6dbdd20aSAndroid Build Coastguard Worker- [com.example.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/com.example.ExampleState/index.ts).
257*6dbdd20aSAndroid Build Coastguard Worker
258*6dbdd20aSAndroid Build Coastguard Worker### Hotkeys
259*6dbdd20aSAndroid Build Coastguard WorkerA hotkey may be associated with a command at registration time.
260*6dbdd20aSAndroid Build Coastguard Worker
261*6dbdd20aSAndroid Build Coastguard Worker```typescript
262*6dbdd20aSAndroid Build Coastguard Workerctx.commands.registerCommand({
263*6dbdd20aSAndroid Build Coastguard Worker  ...
264*6dbdd20aSAndroid Build Coastguard Worker  defaultHotkey: 'Shift+H',
265*6dbdd20aSAndroid Build Coastguard Worker});
266*6dbdd20aSAndroid Build Coastguard Worker```
267*6dbdd20aSAndroid Build Coastguard Worker
268*6dbdd20aSAndroid Build Coastguard WorkerDespite the fact that the hotkey is a string, its format is checked at compile
269*6dbdd20aSAndroid Build Coastguard Workertime using typescript's [template literal
270*6dbdd20aSAndroid Build Coastguard Workertypes](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html).
271*6dbdd20aSAndroid Build Coastguard Worker
272*6dbdd20aSAndroid Build Coastguard WorkerSee
273*6dbdd20aSAndroid Build Coastguard Worker[hotkey.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/hotkeys.ts)
274*6dbdd20aSAndroid Build Coastguard Workerfor more details on how the hotkey syntax works, and for the available keys and
275*6dbdd20aSAndroid Build Coastguard Workermodifiers.
276*6dbdd20aSAndroid Build Coastguard Worker
277*6dbdd20aSAndroid Build Coastguard WorkerNote this is referred to as the 'default' hotkey because we may introduce a
278*6dbdd20aSAndroid Build Coastguard Workerfeature in the future where users can modify their hotkeys, though this doesn't
279*6dbdd20aSAndroid Build Coastguard Workerexist at the moment.
280*6dbdd20aSAndroid Build Coastguard Worker
281*6dbdd20aSAndroid Build Coastguard Worker### Tracks
282*6dbdd20aSAndroid Build Coastguard WorkerIn order to add a new track to the timeline, you'll need to create two entities:
283*6dbdd20aSAndroid Build Coastguard Worker- A track 'renderer' which controls what the track looks like and how it fetches
284*6dbdd20aSAndroid Build Coastguard Worker  data from trace processor.
285*6dbdd20aSAndroid Build Coastguard Worker- A track 'node' controls where the track appears in the workspace.
286*6dbdd20aSAndroid Build Coastguard Worker
287*6dbdd20aSAndroid Build Coastguard WorkerTrack renderers are powerful but complex, so it's, so it's strongly advised not
288*6dbdd20aSAndroid Build Coastguard Workerto create your own. Instead, by far the easiest way to get started with tracks
289*6dbdd20aSAndroid Build Coastguard Workeris to use the `createQuerySliceTrack` and `createQueryCounterTrack` helpers.
290*6dbdd20aSAndroid Build Coastguard Worker
291*6dbdd20aSAndroid Build Coastguard WorkerExample:
292*6dbdd20aSAndroid Build Coastguard Worker```ts
293*6dbdd20aSAndroid Build Coastguard Workerimport {createQuerySliceTrack} from '../../components/tracks/query_slice_track';
294*6dbdd20aSAndroid Build Coastguard Worker
295*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
296*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
297*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
298*6dbdd20aSAndroid Build Coastguard Worker    const title = 'My Track';
299*6dbdd20aSAndroid Build Coastguard Worker    const uri = `${trace.pluginId}#MyTrack`;
300*6dbdd20aSAndroid Build Coastguard Worker    const query = 'select * from slice where track_id = 123';
301*6dbdd20aSAndroid Build Coastguard Worker
302*6dbdd20aSAndroid Build Coastguard Worker    // Create a new track renderer based on a query
303*6dbdd20aSAndroid Build Coastguard Worker    const track = await createQuerySliceTrack({
304*6dbdd20aSAndroid Build Coastguard Worker      trace,
305*6dbdd20aSAndroid Build Coastguard Worker      uri,
306*6dbdd20aSAndroid Build Coastguard Worker      data: {
307*6dbdd20aSAndroid Build Coastguard Worker        sqlSource: query,
308*6dbdd20aSAndroid Build Coastguard Worker      },
309*6dbdd20aSAndroid Build Coastguard Worker    });
310*6dbdd20aSAndroid Build Coastguard Worker
311*6dbdd20aSAndroid Build Coastguard Worker    // Register the track renderer with the core
312*6dbdd20aSAndroid Build Coastguard Worker    trace.tracks.registerTrack({uri, title, track});
313*6dbdd20aSAndroid Build Coastguard Worker
314*6dbdd20aSAndroid Build Coastguard Worker    // Create a track node that references the track renderer using its uri
315*6dbdd20aSAndroid Build Coastguard Worker    const trackNode = new TrackNode({uri, title});
316*6dbdd20aSAndroid Build Coastguard Worker
317*6dbdd20aSAndroid Build Coastguard Worker    // Add the track node to the current workspace
318*6dbdd20aSAndroid Build Coastguard Worker    trace.workspace.addChildInOrder(trackNode);
319*6dbdd20aSAndroid Build Coastguard Worker  }
320*6dbdd20aSAndroid Build Coastguard Worker}
321*6dbdd20aSAndroid Build Coastguard Worker```
322*6dbdd20aSAndroid Build Coastguard Worker
323*6dbdd20aSAndroid Build Coastguard WorkerSee [the source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/components/tracks/query_slice_track.ts)
324*6dbdd20aSAndroid Build Coastguard Workerfor detailed usage.
325*6dbdd20aSAndroid Build Coastguard Worker
326*6dbdd20aSAndroid Build Coastguard WorkerYou can also add a counter track using `createQueryCounterTrack` which works in
327*6dbdd20aSAndroid Build Coastguard Workera similar way.
328*6dbdd20aSAndroid Build Coastguard Worker
329*6dbdd20aSAndroid Build Coastguard Worker```ts
330*6dbdd20aSAndroid Build Coastguard Workerimport {createQueryCounterTrack} from '../../components/tracks/query_counter_track';
331*6dbdd20aSAndroid Build Coastguard Worker
332*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
333*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
334*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
335*6dbdd20aSAndroid Build Coastguard Worker    const title = 'My Counter Track';
336*6dbdd20aSAndroid Build Coastguard Worker    const uri = `${trace.pluginId}#MyCounterTrack`;
337*6dbdd20aSAndroid Build Coastguard Worker    const query = 'select * from counter where track_id = 123';
338*6dbdd20aSAndroid Build Coastguard Worker
339*6dbdd20aSAndroid Build Coastguard Worker    // Create a new track renderer based on a query
340*6dbdd20aSAndroid Build Coastguard Worker    const track = await createQueryCounterTrack({
341*6dbdd20aSAndroid Build Coastguard Worker      trace,
342*6dbdd20aSAndroid Build Coastguard Worker      uri,
343*6dbdd20aSAndroid Build Coastguard Worker      data: {
344*6dbdd20aSAndroid Build Coastguard Worker        sqlSource: query,
345*6dbdd20aSAndroid Build Coastguard Worker      },
346*6dbdd20aSAndroid Build Coastguard Worker    });
347*6dbdd20aSAndroid Build Coastguard Worker
348*6dbdd20aSAndroid Build Coastguard Worker    // Register the track renderer with the core
349*6dbdd20aSAndroid Build Coastguard Worker    trace.tracks.registerTrack({uri, title, track});
350*6dbdd20aSAndroid Build Coastguard Worker
351*6dbdd20aSAndroid Build Coastguard Worker    // Create a track node that references the track renderer using its uri
352*6dbdd20aSAndroid Build Coastguard Worker    const trackNode = new TrackNode({uri, title});
353*6dbdd20aSAndroid Build Coastguard Worker
354*6dbdd20aSAndroid Build Coastguard Worker    // Add the track node to the current workspace
355*6dbdd20aSAndroid Build Coastguard Worker    trace.workspace.addChildInOrder(trackNode);
356*6dbdd20aSAndroid Build Coastguard Worker  }
357*6dbdd20aSAndroid Build Coastguard Worker}
358*6dbdd20aSAndroid Build Coastguard Worker```
359*6dbdd20aSAndroid Build Coastguard Worker
360*6dbdd20aSAndroid Build Coastguard WorkerSee [the source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/components/tracks/query_counter_track.ts)
361*6dbdd20aSAndroid Build Coastguard Workerfor detailed usage.
362*6dbdd20aSAndroid Build Coastguard Worker
363*6dbdd20aSAndroid Build Coastguard Worker#### Grouping Tracks
364*6dbdd20aSAndroid Build Coastguard WorkerAny track can have children. Just add child nodes any `TrackNode` object using
365*6dbdd20aSAndroid Build Coastguard Workerits `addChildXYZ()` methods. Nested tracks are rendered as a collapsible tree.
366*6dbdd20aSAndroid Build Coastguard Worker
367*6dbdd20aSAndroid Build Coastguard Worker```ts
368*6dbdd20aSAndroid Build Coastguard Workerconst group = new TrackNode({title: 'Group'});
369*6dbdd20aSAndroid Build Coastguard Workertrace.workspace.addChildInOrder(group);
370*6dbdd20aSAndroid Build Coastguard Workergroup.addChildLast(new TrackNode({title: 'Child Track A'}));
371*6dbdd20aSAndroid Build Coastguard Workergroup.addChildLast(new TrackNode({title: 'Child Track B'}));
372*6dbdd20aSAndroid Build Coastguard Workergroup.addChildLast(new TrackNode({title: 'Child Track C'}));
373*6dbdd20aSAndroid Build Coastguard Worker```
374*6dbdd20aSAndroid Build Coastguard Worker
375*6dbdd20aSAndroid Build Coastguard WorkerTracks nodes with children can be collapsed and expanded manually by the user at
376*6dbdd20aSAndroid Build Coastguard Workerruntime, or programmatically using their `expand()` and `collapse()` methods. By
377*6dbdd20aSAndroid Build Coastguard Workerdefault tracks are collapsed, so to have tracks automatically expanded on
378*6dbdd20aSAndroid Build Coastguard Workerstartup you'll need to call `expand()` after adding the track node.
379*6dbdd20aSAndroid Build Coastguard Worker
380*6dbdd20aSAndroid Build Coastguard Worker```ts
381*6dbdd20aSAndroid Build Coastguard Workergroup.expand();
382*6dbdd20aSAndroid Build Coastguard Worker```
383*6dbdd20aSAndroid Build Coastguard Worker
384*6dbdd20aSAndroid Build Coastguard Worker![Nested tracks](../images/ui-plugins/nested_tracks.png)
385*6dbdd20aSAndroid Build Coastguard Worker
386*6dbdd20aSAndroid Build Coastguard WorkerSummary tracks are behave slightly differently to ordinary tracks. Summary
387*6dbdd20aSAndroid Build Coastguard Workertracks:
388*6dbdd20aSAndroid Build Coastguard Worker- Are rendered with a light blue background when collapsed, dark blue when
389*6dbdd20aSAndroid Build Coastguard Worker  expanded.
390*6dbdd20aSAndroid Build Coastguard Worker- Stick to the top of the viewport when scrolling.
391*6dbdd20aSAndroid Build Coastguard Worker- Area selections made on the track apply to child tracks instead of the summary
392*6dbdd20aSAndroid Build Coastguard Worker  track itself.
393*6dbdd20aSAndroid Build Coastguard Worker
394*6dbdd20aSAndroid Build Coastguard WorkerTo create a summary track, set the `isSummary: true` option in its initializer
395*6dbdd20aSAndroid Build Coastguard Workerlist at creation time or set its `isSummary` property to true after creation.
396*6dbdd20aSAndroid Build Coastguard Worker
397*6dbdd20aSAndroid Build Coastguard Worker```ts
398*6dbdd20aSAndroid Build Coastguard Workerconst group = new TrackNode({title: 'Group', isSummary: true});
399*6dbdd20aSAndroid Build Coastguard Worker// ~~~ or ~~~
400*6dbdd20aSAndroid Build Coastguard Workergroup.isSummary = true;
401*6dbdd20aSAndroid Build Coastguard Worker```
402*6dbdd20aSAndroid Build Coastguard Worker
403*6dbdd20aSAndroid Build Coastguard Worker![Summary track](../images/ui-plugins/summary_track.png)
404*6dbdd20aSAndroid Build Coastguard Worker
405*6dbdd20aSAndroid Build Coastguard WorkerExamples
406*6dbdd20aSAndroid Build Coastguard Worker- [com.example.ExampleNestedTracks](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/com.example.ExampleNestedTracks/index.ts).
407*6dbdd20aSAndroid Build Coastguard Worker
408*6dbdd20aSAndroid Build Coastguard Worker#### Track Ordering
409*6dbdd20aSAndroid Build Coastguard WorkerTracks can be manually reordered using the `addChildXYZ()` functions available on
410*6dbdd20aSAndroid Build Coastguard Workerthe track node api, including `addChildFirst()`, `addChildLast()`,
411*6dbdd20aSAndroid Build Coastguard Worker`addChildBefore()`, and `addChildAfter()`.
412*6dbdd20aSAndroid Build Coastguard Worker
413*6dbdd20aSAndroid Build Coastguard WorkerSee [the workspace source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public/workspace.ts) for detailed usage.
414*6dbdd20aSAndroid Build Coastguard Worker
415*6dbdd20aSAndroid Build Coastguard WorkerHowever, when several plugins add tracks to the same node or the workspace, no
416*6dbdd20aSAndroid Build Coastguard Workersingle plugin has complete control over the sorting of child nodes within this
417*6dbdd20aSAndroid Build Coastguard Workernode. Thus, the sortOrder property is be used to decentralize the sorting logic
418*6dbdd20aSAndroid Build Coastguard Workerbetween plugins.
419*6dbdd20aSAndroid Build Coastguard Worker
420*6dbdd20aSAndroid Build Coastguard WorkerIn order to do this we simply give the track a `sortOrder` and call
421*6dbdd20aSAndroid Build Coastguard Worker`addChildInOrder()` on the parent node and the track will be placed before the
422*6dbdd20aSAndroid Build Coastguard Workerfirst track with a higher `sortOrder` in the list. (i.e. lower `sortOrder`s appear
423*6dbdd20aSAndroid Build Coastguard Workerhigher in the stack).
424*6dbdd20aSAndroid Build Coastguard Worker
425*6dbdd20aSAndroid Build Coastguard Worker```ts
426*6dbdd20aSAndroid Build Coastguard Worker// PluginA
427*6dbdd20aSAndroid Build Coastguard Workerworkspace.addChildInOrder(new TrackNode({title: 'Foo', sortOrder: 10}));
428*6dbdd20aSAndroid Build Coastguard Worker
429*6dbdd20aSAndroid Build Coastguard Worker// Plugin B
430*6dbdd20aSAndroid Build Coastguard Workerworkspace.addChildInOrder(new TrackNode({title: 'Bar', sortOrder: -10}));
431*6dbdd20aSAndroid Build Coastguard Worker```
432*6dbdd20aSAndroid Build Coastguard Worker
433*6dbdd20aSAndroid Build Coastguard WorkerNow it doesn't matter which order plugin are initialized, track `Bar` will
434*6dbdd20aSAndroid Build Coastguard Workerappear above track `Foo` (unless reordered later).
435*6dbdd20aSAndroid Build Coastguard Worker
436*6dbdd20aSAndroid Build Coastguard WorkerIf no `sortOrder` is defined, the track assumes a `sortOrder` of 0.
437*6dbdd20aSAndroid Build Coastguard Worker
438*6dbdd20aSAndroid Build Coastguard Worker> It is recommended to always use `addChildInOrder()` in plugins when adding
439*6dbdd20aSAndroid Build Coastguard Worker> tracks to the `workspace`, especially if you want your plugin to be enabled by
440*6dbdd20aSAndroid Build Coastguard Worker> default, as this will ensure it respects the sortOrder of other plugins.
441*6dbdd20aSAndroid Build Coastguard Worker
442*6dbdd20aSAndroid Build Coastguard Worker
443*6dbdd20aSAndroid Build Coastguard Worker### Tabs
444*6dbdd20aSAndroid Build Coastguard WorkerTabs are a useful way to display contextual information about the trace, the
445*6dbdd20aSAndroid Build Coastguard Workercurrent selection, or to show the results of an operation.
446*6dbdd20aSAndroid Build Coastguard Worker
447*6dbdd20aSAndroid Build Coastguard WorkerTo register a tab from a plugin, use the `Trace.registerTab` method.
448*6dbdd20aSAndroid Build Coastguard Worker
449*6dbdd20aSAndroid Build Coastguard Worker```ts
450*6dbdd20aSAndroid Build Coastguard Workerclass MyTab implements Tab {
451*6dbdd20aSAndroid Build Coastguard Worker  render(): m.Children {
452*6dbdd20aSAndroid Build Coastguard Worker    return m('div', 'Hello from my tab');
453*6dbdd20aSAndroid Build Coastguard Worker  }
454*6dbdd20aSAndroid Build Coastguard Worker
455*6dbdd20aSAndroid Build Coastguard Worker  getTitle(): string {
456*6dbdd20aSAndroid Build Coastguard Worker    return 'My Tab';
457*6dbdd20aSAndroid Build Coastguard Worker  }
458*6dbdd20aSAndroid Build Coastguard Worker}
459*6dbdd20aSAndroid Build Coastguard Worker
460*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
461*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
462*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
463*6dbdd20aSAndroid Build Coastguard Worker    trace.registerTab({
464*6dbdd20aSAndroid Build Coastguard Worker      uri: `${trace.pluginId}#MyTab`,
465*6dbdd20aSAndroid Build Coastguard Worker      content: new MyTab(),
466*6dbdd20aSAndroid Build Coastguard Worker    });
467*6dbdd20aSAndroid Build Coastguard Worker  }
468*6dbdd20aSAndroid Build Coastguard Worker}
469*6dbdd20aSAndroid Build Coastguard Worker```
470*6dbdd20aSAndroid Build Coastguard Worker
471*6dbdd20aSAndroid Build Coastguard WorkerYou'll need to pass in a tab-like object, something that implements the `Tab`
472*6dbdd20aSAndroid Build Coastguard Workerinterface. Tabs only need to define their title and a render function which
473*6dbdd20aSAndroid Build Coastguard Workerspecifies how to render the tab.
474*6dbdd20aSAndroid Build Coastguard Worker
475*6dbdd20aSAndroid Build Coastguard WorkerRegistered tabs don't appear immediately - we need to show it first. All
476*6dbdd20aSAndroid Build Coastguard Workerregistered tabs are displayed in the tab dropdown menu, and can be shown or
477*6dbdd20aSAndroid Build Coastguard Workerhidden by clicking on the entries in the drop down menu.
478*6dbdd20aSAndroid Build Coastguard Worker
479*6dbdd20aSAndroid Build Coastguard WorkerTabs can also be hidden by clicking the little x in the top right of their
480*6dbdd20aSAndroid Build Coastguard Workerhandle.
481*6dbdd20aSAndroid Build Coastguard Worker
482*6dbdd20aSAndroid Build Coastguard WorkerAlternatively, tabs may be shown or hidden programmatically using the tabs API.
483*6dbdd20aSAndroid Build Coastguard Worker
484*6dbdd20aSAndroid Build Coastguard Worker```ts
485*6dbdd20aSAndroid Build Coastguard Workertrace.tabs.showTab(`${trace.pluginId}#MyTab`);
486*6dbdd20aSAndroid Build Coastguard Workertrace.tabs.hideTab(`${trace.pluginId}#MyTab`);
487*6dbdd20aSAndroid Build Coastguard Worker```
488*6dbdd20aSAndroid Build Coastguard Worker
489*6dbdd20aSAndroid Build Coastguard WorkerTabs have the following properties:
490*6dbdd20aSAndroid Build Coastguard Worker- Each tab has a unique URI.
491*6dbdd20aSAndroid Build Coastguard Worker- Only once instance of the tab may be open at a time. Calling showTab multiple
492*6dbdd20aSAndroid Build Coastguard Worker  times with the same URI will only activate the tab, not add a new instance of
493*6dbdd20aSAndroid Build Coastguard Worker  the tab to the tab bar.
494*6dbdd20aSAndroid Build Coastguard Worker
495*6dbdd20aSAndroid Build Coastguard Worker#### Ephemeral Tabs
496*6dbdd20aSAndroid Build Coastguard Worker
497*6dbdd20aSAndroid Build Coastguard WorkerBy default, tabs are registered as 'permanent' tabs. These tabs have the
498*6dbdd20aSAndroid Build Coastguard Workerfollowing additional properties:
499*6dbdd20aSAndroid Build Coastguard Worker- They appear in the tab dropdown.
500*6dbdd20aSAndroid Build Coastguard Worker- They remain once closed. The plugin controls the lifetime of the tab object.
501*6dbdd20aSAndroid Build Coastguard Worker
502*6dbdd20aSAndroid Build Coastguard WorkerEphemeral tabs, by contrast, have the following properties:
503*6dbdd20aSAndroid Build Coastguard Worker- They do not appear in the tab dropdown.
504*6dbdd20aSAndroid Build Coastguard Worker- When they are hidden, they will be automatically unregistered.
505*6dbdd20aSAndroid Build Coastguard Worker
506*6dbdd20aSAndroid Build Coastguard WorkerEphemeral tabs can be registered by setting the `isEphemeral` flag when
507*6dbdd20aSAndroid Build Coastguard Workerregistering the tab.
508*6dbdd20aSAndroid Build Coastguard Worker
509*6dbdd20aSAndroid Build Coastguard Worker```ts
510*6dbdd20aSAndroid Build Coastguard Workertrace.registerTab({
511*6dbdd20aSAndroid Build Coastguard Worker  isEphemeral: true,
512*6dbdd20aSAndroid Build Coastguard Worker  uri: `${trace.pluginId}#MyTab`,
513*6dbdd20aSAndroid Build Coastguard Worker  content: new MyEphemeralTab(),
514*6dbdd20aSAndroid Build Coastguard Worker});
515*6dbdd20aSAndroid Build Coastguard Worker```
516*6dbdd20aSAndroid Build Coastguard Worker
517*6dbdd20aSAndroid Build Coastguard WorkerEphemeral tabs are usually added as a result of some user action, such as
518*6dbdd20aSAndroid Build Coastguard Workerrunning a command. Thus, it's common pattern to register a tab and show the tab
519*6dbdd20aSAndroid Build Coastguard Workersimultaneously.
520*6dbdd20aSAndroid Build Coastguard Worker
521*6dbdd20aSAndroid Build Coastguard WorkerMotivating example:
522*6dbdd20aSAndroid Build Coastguard Worker```ts
523*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
524*6dbdd20aSAndroid Build Coastguard Workerimport {uuidv4} from '../../base/uuid';
525*6dbdd20aSAndroid Build Coastguard Worker
526*6dbdd20aSAndroid Build Coastguard Workerclass MyNameTab implements Tab {
527*6dbdd20aSAndroid Build Coastguard Worker  constructor(private name: string) {}
528*6dbdd20aSAndroid Build Coastguard Worker  render(): m.Children {
529*6dbdd20aSAndroid Build Coastguard Worker    return m('h1', `Hello, ${this.name}!`);
530*6dbdd20aSAndroid Build Coastguard Worker  }
531*6dbdd20aSAndroid Build Coastguard Worker  getTitle(): string {
532*6dbdd20aSAndroid Build Coastguard Worker    return 'My Name Tab';
533*6dbdd20aSAndroid Build Coastguard Worker  }
534*6dbdd20aSAndroid Build Coastguard Worker}
535*6dbdd20aSAndroid Build Coastguard Worker
536*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
537*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
538*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace): Promise<void> {
539*6dbdd20aSAndroid Build Coastguard Worker    trace.registerCommand({
540*6dbdd20aSAndroid Build Coastguard Worker      id: `${trace.pluginId}#AddNewEphemeralTab`,
541*6dbdd20aSAndroid Build Coastguard Worker      name: 'Add new ephemeral tab',
542*6dbdd20aSAndroid Build Coastguard Worker      callback: () => handleCommand(trace),
543*6dbdd20aSAndroid Build Coastguard Worker    });
544*6dbdd20aSAndroid Build Coastguard Worker  }
545*6dbdd20aSAndroid Build Coastguard Worker}
546*6dbdd20aSAndroid Build Coastguard Worker
547*6dbdd20aSAndroid Build Coastguard Workerfunction handleCommand(trace: Trace): void {
548*6dbdd20aSAndroid Build Coastguard Worker  const name = prompt('What is your name');
549*6dbdd20aSAndroid Build Coastguard Worker  if (name) {
550*6dbdd20aSAndroid Build Coastguard Worker    const uri = `${trace.pluginId}#MyName${uuidv4()}`;
551*6dbdd20aSAndroid Build Coastguard Worker    // This makes the tab available to perfetto
552*6dbdd20aSAndroid Build Coastguard Worker    ctx.registerTab({
553*6dbdd20aSAndroid Build Coastguard Worker      isEphemeral: true,
554*6dbdd20aSAndroid Build Coastguard Worker      uri,
555*6dbdd20aSAndroid Build Coastguard Worker      content: new MyNameTab(name),
556*6dbdd20aSAndroid Build Coastguard Worker    });
557*6dbdd20aSAndroid Build Coastguard Worker
558*6dbdd20aSAndroid Build Coastguard Worker    // This opens the tab in the tab bar
559*6dbdd20aSAndroid Build Coastguard Worker    ctx.tabs.showTab(uri);
560*6dbdd20aSAndroid Build Coastguard Worker  }
561*6dbdd20aSAndroid Build Coastguard Worker}
562*6dbdd20aSAndroid Build Coastguard Worker```
563*6dbdd20aSAndroid Build Coastguard Worker
564*6dbdd20aSAndroid Build Coastguard Worker### Details Panels & The Current Selection Tab
565*6dbdd20aSAndroid Build Coastguard WorkerThe "Current Selection" tab is a special tab that cannot be hidden. It remains
566*6dbdd20aSAndroid Build Coastguard Workerpermanently in the left-most tab position in the tab bar. Its purpose is to
567*6dbdd20aSAndroid Build Coastguard Workerdisplay details about the current selection.
568*6dbdd20aSAndroid Build Coastguard Worker
569*6dbdd20aSAndroid Build Coastguard WorkerPlugins may register interest in providing content for this tab using the
570*6dbdd20aSAndroid Build Coastguard Worker`PluginContentTrace.registerDetailsPanel()` method.
571*6dbdd20aSAndroid Build Coastguard Worker
572*6dbdd20aSAndroid Build Coastguard WorkerFor example:
573*6dbdd20aSAndroid Build Coastguard Worker
574*6dbdd20aSAndroid Build Coastguard Worker```ts
575*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
576*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
577*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
578*6dbdd20aSAndroid Build Coastguard Worker    trace.registerDetailsPanel({
579*6dbdd20aSAndroid Build Coastguard Worker      render(selection: Selection) {
580*6dbdd20aSAndroid Build Coastguard Worker        if (canHandleSelection(selection)) {
581*6dbdd20aSAndroid Build Coastguard Worker          return m('div', 'Details for selection');
582*6dbdd20aSAndroid Build Coastguard Worker        } else {
583*6dbdd20aSAndroid Build Coastguard Worker          return undefined;
584*6dbdd20aSAndroid Build Coastguard Worker        }
585*6dbdd20aSAndroid Build Coastguard Worker      }
586*6dbdd20aSAndroid Build Coastguard Worker    });
587*6dbdd20aSAndroid Build Coastguard Worker  }
588*6dbdd20aSAndroid Build Coastguard Worker}
589*6dbdd20aSAndroid Build Coastguard Worker```
590*6dbdd20aSAndroid Build Coastguard Worker
591*6dbdd20aSAndroid Build Coastguard WorkerThis function takes an object that implements the `DetailsPanel` interface,
592*6dbdd20aSAndroid Build Coastguard Workerwhich only requires a render function to be implemented that takes the current
593*6dbdd20aSAndroid Build Coastguard Workerselection object and returns either mithril vnodes or a falsy value.
594*6dbdd20aSAndroid Build Coastguard Worker
595*6dbdd20aSAndroid Build Coastguard WorkerEvery render cycle, render is called on all registered details panels, and the
596*6dbdd20aSAndroid Build Coastguard Workerfirst registered panel to return a truthy value will be used.
597*6dbdd20aSAndroid Build Coastguard Worker
598*6dbdd20aSAndroid Build Coastguard WorkerCurrently the winning details panel takes complete control over this tab. Also,
599*6dbdd20aSAndroid Build Coastguard Workerthe order that these panels are called in is not defined, so if we have multiple
600*6dbdd20aSAndroid Build Coastguard Workerdetails panels competing for the same selection, the one that actually shows up
601*6dbdd20aSAndroid Build Coastguard Workeris undefined. This is a limitation of the current approach and will be updated
602*6dbdd20aSAndroid Build Coastguard Workerto a more democratic contribution model in the future.
603*6dbdd20aSAndroid Build Coastguard Worker
604*6dbdd20aSAndroid Build Coastguard Worker### Sidebar Menu Items
605*6dbdd20aSAndroid Build Coastguard WorkerPlugins can add new entries to the sidebar menu which appears on the left hand
606*6dbdd20aSAndroid Build Coastguard Workerside of the UI. These entries can include:
607*6dbdd20aSAndroid Build Coastguard Worker- Commands
608*6dbdd20aSAndroid Build Coastguard Worker- Links
609*6dbdd20aSAndroid Build Coastguard Worker- Arbitrary Callbacks
610*6dbdd20aSAndroid Build Coastguard Worker
611*6dbdd20aSAndroid Build Coastguard Worker#### Commands
612*6dbdd20aSAndroid Build Coastguard WorkerIf a command is referenced, the command name and hotkey are displayed on the
613*6dbdd20aSAndroid Build Coastguard Workersidebar item.
614*6dbdd20aSAndroid Build Coastguard Worker```ts
615*6dbdd20aSAndroid Build Coastguard Workertrace.commands.registerCommand({
616*6dbdd20aSAndroid Build Coastguard Worker  id: 'sayHi',
617*6dbdd20aSAndroid Build Coastguard Worker  name: 'Say hi',
618*6dbdd20aSAndroid Build Coastguard Worker  callback: () => window.alert('hi'),
619*6dbdd20aSAndroid Build Coastguard Worker  defaultHotkey: 'Shift+H',
620*6dbdd20aSAndroid Build Coastguard Worker});
621*6dbdd20aSAndroid Build Coastguard Worker
622*6dbdd20aSAndroid Build Coastguard Workertrace.sidebar.addMenuItem({
623*6dbdd20aSAndroid Build Coastguard Worker  commandId: 'sayHi',
624*6dbdd20aSAndroid Build Coastguard Worker  section: 'support',
625*6dbdd20aSAndroid Build Coastguard Worker  icon: 'waving_hand',
626*6dbdd20aSAndroid Build Coastguard Worker});
627*6dbdd20aSAndroid Build Coastguard Worker```
628*6dbdd20aSAndroid Build Coastguard Worker
629*6dbdd20aSAndroid Build Coastguard Worker#### Links
630*6dbdd20aSAndroid Build Coastguard WorkerIf an href is present, the sidebar will be used as a link. This can be an
631*6dbdd20aSAndroid Build Coastguard Workerinternal link to a page, or an external link.
632*6dbdd20aSAndroid Build Coastguard Worker```ts
633*6dbdd20aSAndroid Build Coastguard Workertrace.sidebar.addMenuItem({
634*6dbdd20aSAndroid Build Coastguard Worker  section: 'navigation',
635*6dbdd20aSAndroid Build Coastguard Worker  text: 'Plugins',
636*6dbdd20aSAndroid Build Coastguard Worker  href: '#!/plugins',
637*6dbdd20aSAndroid Build Coastguard Worker});
638*6dbdd20aSAndroid Build Coastguard Worker```
639*6dbdd20aSAndroid Build Coastguard Worker
640*6dbdd20aSAndroid Build Coastguard Worker#### Callbacks
641*6dbdd20aSAndroid Build Coastguard WorkerSidebar items can be instructed to execute arbitrary callbacks when the button
642*6dbdd20aSAndroid Build Coastguard Workeris clicked.
643*6dbdd20aSAndroid Build Coastguard Worker```ts
644*6dbdd20aSAndroid Build Coastguard Workertrace.sidebar.addMenuItem({
645*6dbdd20aSAndroid Build Coastguard Worker  section: 'current_trace',
646*6dbdd20aSAndroid Build Coastguard Worker  text: 'Copy secrets to clipboard',
647*6dbdd20aSAndroid Build Coastguard Worker  action: () => copyToClipboard('...'),
648*6dbdd20aSAndroid Build Coastguard Worker});
649*6dbdd20aSAndroid Build Coastguard Worker```
650*6dbdd20aSAndroid Build Coastguard Worker
651*6dbdd20aSAndroid Build Coastguard WorkerIf the action returns a promise, the sidebar item will show a little spinner
652*6dbdd20aSAndroid Build Coastguard Workeranimation until the promise returns.
653*6dbdd20aSAndroid Build Coastguard Worker
654*6dbdd20aSAndroid Build Coastguard Worker```ts
655*6dbdd20aSAndroid Build Coastguard Workertrace.sidebar.addMenuItem({
656*6dbdd20aSAndroid Build Coastguard Worker  section: 'current_trace',
657*6dbdd20aSAndroid Build Coastguard Worker  text: 'Prepare the data...',
658*6dbdd20aSAndroid Build Coastguard Worker  action: () => new Promise((r) => setTimeout(r, 1000)),
659*6dbdd20aSAndroid Build Coastguard Worker});
660*6dbdd20aSAndroid Build Coastguard Worker```
661*6dbdd20aSAndroid Build Coastguard WorkerOptional params for all types of sidebar items:
662*6dbdd20aSAndroid Build Coastguard Worker- `icon` - A material design icon to be displayed next to the sidebar menu item.
663*6dbdd20aSAndroid Build Coastguard Worker  See full list [here](https://fonts.google.com/icons).
664*6dbdd20aSAndroid Build Coastguard Worker- `tooltip` - Displayed on hover
665*6dbdd20aSAndroid Build Coastguard Worker- `section` - Where to place the menu item.
666*6dbdd20aSAndroid Build Coastguard Worker  - `navigation`
667*6dbdd20aSAndroid Build Coastguard Worker  - `current_trace`
668*6dbdd20aSAndroid Build Coastguard Worker  - `convert_trace`
669*6dbdd20aSAndroid Build Coastguard Worker  - `example_traces`
670*6dbdd20aSAndroid Build Coastguard Worker  - `support`
671*6dbdd20aSAndroid Build Coastguard Worker- `sortOrder` - The higher the sortOrder the higher the bar.
672*6dbdd20aSAndroid Build Coastguard Worker
673*6dbdd20aSAndroid Build Coastguard WorkerSee the [sidebar source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public/sidebar.ts)
674*6dbdd20aSAndroid Build Coastguard Workerfor more detailed usage.
675*6dbdd20aSAndroid Build Coastguard Worker
676*6dbdd20aSAndroid Build Coastguard Worker### Pages
677*6dbdd20aSAndroid Build Coastguard WorkerPages are entities that can be routed via the URL args, and whose content take
678*6dbdd20aSAndroid Build Coastguard Workerup the entire available space to the right of the sidebar and underneath the
679*6dbdd20aSAndroid Build Coastguard Workertopbar. Examples of pages are the timeline, record page, and query page, just to
680*6dbdd20aSAndroid Build Coastguard Workername a few common examples.
681*6dbdd20aSAndroid Build Coastguard Worker
682*6dbdd20aSAndroid Build Coastguard WorkerE.g.
683*6dbdd20aSAndroid Build Coastguard Worker```
684*6dbdd20aSAndroid Build Coastguard Workerhttp://ui.perfetto.dev/#!/viewer <-- 'viewer' is is the current page.
685*6dbdd20aSAndroid Build Coastguard Worker```
686*6dbdd20aSAndroid Build Coastguard Worker
687*6dbdd20aSAndroid Build Coastguard WorkerPages are added from a plugin by calling the `pages.registerPage` function.
688*6dbdd20aSAndroid Build Coastguard Worker
689*6dbdd20aSAndroid Build Coastguard WorkerPages can be trace-less or trace-ful. Trace-less pages are pages that are to be
690*6dbdd20aSAndroid Build Coastguard Workerdisplayed when no trace is loaded - i.e. the record page. Trace-ful pages are
691*6dbdd20aSAndroid Build Coastguard Workerdisplayed only when a trace is loaded, as they typically require a trace to work
692*6dbdd20aSAndroid Build Coastguard Workerwith.
693*6dbdd20aSAndroid Build Coastguard Worker
694*6dbdd20aSAndroid Build Coastguard WorkerYou'll typically register trace-less pages in your plugin's `onActivate()`
695*6dbdd20aSAndroid Build Coastguard Workerfunction and trace-full pages in either `onActivate()` or `onTraceLoad()`. If
696*6dbdd20aSAndroid Build Coastguard Workerusers navigate to a trace-ful page before a trace is loaded the homepage will be
697*6dbdd20aSAndroid Build Coastguard Workershown instead.
698*6dbdd20aSAndroid Build Coastguard Worker
699*6dbdd20aSAndroid Build Coastguard Worker> Note: You don't need to bind the `Trace` object for pages unlike other
700*6dbdd20aSAndroid Build Coastguard Worker> extension points, Perfetto will inject a trace object for you.
701*6dbdd20aSAndroid Build Coastguard Worker
702*6dbdd20aSAndroid Build Coastguard WorkerPages should be mithril components that accept `PageWithTraceAttrs` for
703*6dbdd20aSAndroid Build Coastguard Workertrace-ful pages or `PageAttrs` for trace-less pages.
704*6dbdd20aSAndroid Build Coastguard Worker
705*6dbdd20aSAndroid Build Coastguard WorkerExample of a trace-less page:
706*6dbdd20aSAndroid Build Coastguard Worker```ts
707*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
708*6dbdd20aSAndroid Build Coastguard Workerimport {PageAttrs} from '../../public/page';
709*6dbdd20aSAndroid Build Coastguard Worker
710*6dbdd20aSAndroid Build Coastguard Workerclass MyPage implements m.ClassComponent<PageAttrs> {
711*6dbdd20aSAndroid Build Coastguard Worker  view(vnode: m.CVnode<PageAttrs>) {
712*6dbdd20aSAndroid Build Coastguard Worker    return `The trace title is: ${vnode.attrs.trace.traceInfo.traceTitle}`;
713*6dbdd20aSAndroid Build Coastguard Worker  }
714*6dbdd20aSAndroid Build Coastguard Worker}
715*6dbdd20aSAndroid Build Coastguard Worker
716*6dbdd20aSAndroid Build Coastguard Worker// ~~~ snip ~~~
717*6dbdd20aSAndroid Build Coastguard Worker
718*6dbdd20aSAndroid Build Coastguard Workerapp.pages.registerPage({route: '/mypage', page: MyPage, traceless: true});
719*6dbdd20aSAndroid Build Coastguard Worker```
720*6dbdd20aSAndroid Build Coastguard Worker
721*6dbdd20aSAndroid Build Coastguard Worker```ts
722*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
723*6dbdd20aSAndroid Build Coastguard Workerimport {PageWithTraceAttrs} from '../../public/page';
724*6dbdd20aSAndroid Build Coastguard Worker
725*6dbdd20aSAndroid Build Coastguard Workerclass MyPage implements m.ClassComponent<PageWithTraceAttrs> {
726*6dbdd20aSAndroid Build Coastguard Worker  view(_vnode_: m.CVnode<PageWithTraceAttrs>) {
727*6dbdd20aSAndroid Build Coastguard Worker    return 'Hello from my page';
728*6dbdd20aSAndroid Build Coastguard Worker  }
729*6dbdd20aSAndroid Build Coastguard Worker}
730*6dbdd20aSAndroid Build Coastguard Worker
731*6dbdd20aSAndroid Build Coastguard Worker// ~~~ snip ~~~
732*6dbdd20aSAndroid Build Coastguard Worker
733*6dbdd20aSAndroid Build Coastguard Workerapp.pages.registerPage({route: '/mypage', page: MyPage});
734*6dbdd20aSAndroid Build Coastguard Worker```
735*6dbdd20aSAndroid Build Coastguard Worker
736*6dbdd20aSAndroid Build Coastguard WorkerExamples:
737*6dbdd20aSAndroid Build Coastguard Worker- [dev.perfetto.ExplorePage](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExplorePage/index.ts).
738*6dbdd20aSAndroid Build Coastguard Worker
739*6dbdd20aSAndroid Build Coastguard Worker
740*6dbdd20aSAndroid Build Coastguard Worker### Metric Visualisations
741*6dbdd20aSAndroid Build Coastguard WorkerTBD
742*6dbdd20aSAndroid Build Coastguard Worker
743*6dbdd20aSAndroid Build Coastguard WorkerExamples:
744*6dbdd20aSAndroid Build Coastguard Worker- [dev.perfetto.AndroidBinderViz](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts).
745*6dbdd20aSAndroid Build Coastguard Worker
746*6dbdd20aSAndroid Build Coastguard Worker### State
747*6dbdd20aSAndroid Build Coastguard WorkerNOTE: It is important to consider version skew when using persistent state.
748*6dbdd20aSAndroid Build Coastguard Worker
749*6dbdd20aSAndroid Build Coastguard WorkerPlugins can persist information into permalinks. This allows plugins to
750*6dbdd20aSAndroid Build Coastguard Workergracefully handle permalinking and is an opt-in - not automatic - mechanism.
751*6dbdd20aSAndroid Build Coastguard Worker
752*6dbdd20aSAndroid Build Coastguard WorkerPersistent plugin state works using a `Store<T>` where `T` is some JSON
753*6dbdd20aSAndroid Build Coastguard Workerserializable object. `Store` is implemented
754*6dbdd20aSAndroid Build Coastguard Worker[here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/store.ts).
755*6dbdd20aSAndroid Build Coastguard Worker`Store` allows for reading and writing `T`. Reading:
756*6dbdd20aSAndroid Build Coastguard Worker```typescript
757*6dbdd20aSAndroid Build Coastguard Workerinterface Foo {
758*6dbdd20aSAndroid Build Coastguard Worker  bar: string;
759*6dbdd20aSAndroid Build Coastguard Worker}
760*6dbdd20aSAndroid Build Coastguard Worker
761*6dbdd20aSAndroid Build Coastguard Workerconst store: Store<Foo> = getFooStoreSomehow();
762*6dbdd20aSAndroid Build Coastguard Worker
763*6dbdd20aSAndroid Build Coastguard Worker// store.state is immutable and must not be edited.
764*6dbdd20aSAndroid Build Coastguard Workerconst foo = store.state.foo;
765*6dbdd20aSAndroid Build Coastguard Workerconst bar = foo.bar;
766*6dbdd20aSAndroid Build Coastguard Worker
767*6dbdd20aSAndroid Build Coastguard Workerconsole.log(bar);
768*6dbdd20aSAndroid Build Coastguard Worker```
769*6dbdd20aSAndroid Build Coastguard Worker
770*6dbdd20aSAndroid Build Coastguard WorkerWriting:
771*6dbdd20aSAndroid Build Coastguard Worker```typescript
772*6dbdd20aSAndroid Build Coastguard Workerinterface Foo {
773*6dbdd20aSAndroid Build Coastguard Worker  bar: string;
774*6dbdd20aSAndroid Build Coastguard Worker}
775*6dbdd20aSAndroid Build Coastguard Worker
776*6dbdd20aSAndroid Build Coastguard Workerconst store: Store<Foo> = getFooStoreSomehow();
777*6dbdd20aSAndroid Build Coastguard Worker
778*6dbdd20aSAndroid Build Coastguard Workerstore.edit((draft) => {
779*6dbdd20aSAndroid Build Coastguard Worker  draft.foo.bar = 'Hello, world!';
780*6dbdd20aSAndroid Build Coastguard Worker});
781*6dbdd20aSAndroid Build Coastguard Worker
782*6dbdd20aSAndroid Build Coastguard Workerconsole.log(store.state.foo.bar);
783*6dbdd20aSAndroid Build Coastguard Worker// > Hello, world!
784*6dbdd20aSAndroid Build Coastguard Worker```
785*6dbdd20aSAndroid Build Coastguard Worker
786*6dbdd20aSAndroid Build Coastguard WorkerFirst define an interface for your specific plugin state.
787*6dbdd20aSAndroid Build Coastguard Worker```typescript
788*6dbdd20aSAndroid Build Coastguard Workerinterface MyState {
789*6dbdd20aSAndroid Build Coastguard Worker  favouriteSlices: MySliceInfo[];
790*6dbdd20aSAndroid Build Coastguard Worker}
791*6dbdd20aSAndroid Build Coastguard Worker```
792*6dbdd20aSAndroid Build Coastguard Worker
793*6dbdd20aSAndroid Build Coastguard WorkerTo access permalink state, call `mountStore()` on your `Trace`
794*6dbdd20aSAndroid Build Coastguard Workerobject, passing in a migration function.
795*6dbdd20aSAndroid Build Coastguard Worker```typescript
796*6dbdd20aSAndroid Build Coastguard Workerdefault export class implements PerfettoPlugin {
797*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'com.example.MyPlugin';
798*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace): Promise<void> {
799*6dbdd20aSAndroid Build Coastguard Worker    const store = trace.mountStore(migrate);
800*6dbdd20aSAndroid Build Coastguard Worker  }
801*6dbdd20aSAndroid Build Coastguard Worker}
802*6dbdd20aSAndroid Build Coastguard Worker
803*6dbdd20aSAndroid Build Coastguard Workerfunction migrate(initialState: unknown): MyState {
804*6dbdd20aSAndroid Build Coastguard Worker  // ...
805*6dbdd20aSAndroid Build Coastguard Worker}
806*6dbdd20aSAndroid Build Coastguard Worker```
807*6dbdd20aSAndroid Build Coastguard Worker
808*6dbdd20aSAndroid Build Coastguard WorkerWhen it comes to migration, there are two cases to consider:
809*6dbdd20aSAndroid Build Coastguard Worker- Loading a new trace
810*6dbdd20aSAndroid Build Coastguard Worker- Loading from a permalink
811*6dbdd20aSAndroid Build Coastguard Worker
812*6dbdd20aSAndroid Build Coastguard WorkerIn case of a new trace, your migration function is called with `undefined`. In
813*6dbdd20aSAndroid Build Coastguard Workerthis case you should return a default version of `MyState`:
814*6dbdd20aSAndroid Build Coastguard Worker```typescript
815*6dbdd20aSAndroid Build Coastguard Workerconst DEFAULT = {favouriteSlices: []};
816*6dbdd20aSAndroid Build Coastguard Worker
817*6dbdd20aSAndroid Build Coastguard Workerfunction migrate(initialState: unknown): MyState {
818*6dbdd20aSAndroid Build Coastguard Worker  if (initialState === undefined) {
819*6dbdd20aSAndroid Build Coastguard Worker    // Return default version of MyState.
820*6dbdd20aSAndroid Build Coastguard Worker    return DEFAULT;
821*6dbdd20aSAndroid Build Coastguard Worker  } else {
822*6dbdd20aSAndroid Build Coastguard Worker    // Migrate old version here.
823*6dbdd20aSAndroid Build Coastguard Worker  }
824*6dbdd20aSAndroid Build Coastguard Worker}
825*6dbdd20aSAndroid Build Coastguard Worker```
826*6dbdd20aSAndroid Build Coastguard Worker
827*6dbdd20aSAndroid Build Coastguard WorkerIn the permalink case, your migration function is called with the state of the
828*6dbdd20aSAndroid Build Coastguard Workerplugin store at the time the permalink was generated. This may be from an older
829*6dbdd20aSAndroid Build Coastguard Workeror newer version of the plugin.
830*6dbdd20aSAndroid Build Coastguard Worker
831*6dbdd20aSAndroid Build Coastguard Worker**Plugins must not make assumptions about the contents of `initialState`!**
832*6dbdd20aSAndroid Build Coastguard Worker
833*6dbdd20aSAndroid Build Coastguard WorkerIn this case you need to carefully validate the state object. This could be
834*6dbdd20aSAndroid Build Coastguard Workerachieved in several ways, none of which are particularly straight forward. State
835*6dbdd20aSAndroid Build Coastguard Workermigration is difficult!
836*6dbdd20aSAndroid Build Coastguard Worker
837*6dbdd20aSAndroid Build Coastguard WorkerOne brute force way would be to use a version number.
838*6dbdd20aSAndroid Build Coastguard Worker
839*6dbdd20aSAndroid Build Coastguard Worker```typescript
840*6dbdd20aSAndroid Build Coastguard Workerinterface MyState {
841*6dbdd20aSAndroid Build Coastguard Worker  version: number;
842*6dbdd20aSAndroid Build Coastguard Worker  favouriteSlices: MySliceInfo[];
843*6dbdd20aSAndroid Build Coastguard Worker}
844*6dbdd20aSAndroid Build Coastguard Worker
845*6dbdd20aSAndroid Build Coastguard Workerconst VERSION = 3;
846*6dbdd20aSAndroid Build Coastguard Workerconst DEFAULT = {favouriteSlices: []};
847*6dbdd20aSAndroid Build Coastguard Worker
848*6dbdd20aSAndroid Build Coastguard Workerfunction migrate(initialState: unknown): MyState {
849*6dbdd20aSAndroid Build Coastguard Worker  if (initialState && (initialState as {version: any}).version === VERSION) {
850*6dbdd20aSAndroid Build Coastguard Worker    // Version number checks out, assume the structure is correct.
851*6dbdd20aSAndroid Build Coastguard Worker    return initialState as State;
852*6dbdd20aSAndroid Build Coastguard Worker  } else {
853*6dbdd20aSAndroid Build Coastguard Worker    // Null, undefined, or bad version number - return default value.
854*6dbdd20aSAndroid Build Coastguard Worker    return DEFAULT;
855*6dbdd20aSAndroid Build Coastguard Worker  }
856*6dbdd20aSAndroid Build Coastguard Worker}
857*6dbdd20aSAndroid Build Coastguard Worker```
858*6dbdd20aSAndroid Build Coastguard Worker
859*6dbdd20aSAndroid Build Coastguard WorkerYou'll need to remember to update your version number when making changes!
860*6dbdd20aSAndroid Build Coastguard WorkerMigration should be unit-tested to ensure compatibility.
861*6dbdd20aSAndroid Build Coastguard Worker
862*6dbdd20aSAndroid Build Coastguard WorkerExamples:
863*6dbdd20aSAndroid Build Coastguard Worker- [dev.perfetto.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExampleState/index.ts).
864*6dbdd20aSAndroid Build Coastguard Worker
865*6dbdd20aSAndroid Build Coastguard Worker## Guide to the plugin API
866*6dbdd20aSAndroid Build Coastguard WorkerThe plugin interfaces are defined in
867*6dbdd20aSAndroid Build Coastguard Worker[ui/src/public/](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public).
868*6dbdd20aSAndroid Build Coastguard Worker
869*6dbdd20aSAndroid Build Coastguard Worker## Default plugins
870*6dbdd20aSAndroid Build Coastguard WorkerSome plugins are enabled by default. These plugins are held to a higher quality
871*6dbdd20aSAndroid Build Coastguard Workerthan non-default plugins since changes to those plugins effect all users of the
872*6dbdd20aSAndroid Build Coastguard WorkerUI. The list of default plugins is specified at
873*6dbdd20aSAndroid Build Coastguard Worker[ui/src/core/default_plugins.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/core/default_plugins.ts).
874*6dbdd20aSAndroid Build Coastguard Worker
875*6dbdd20aSAndroid Build Coastguard Worker## Misc notes
876*6dbdd20aSAndroid Build Coastguard Worker- Plugins must be licensed under
877*6dbdd20aSAndroid Build Coastguard Worker  [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) the same as all other
878*6dbdd20aSAndroid Build Coastguard Worker  code in the repository.
879*6dbdd20aSAndroid Build Coastguard Worker- Plugins are the responsibility of the OWNERS of that plugin to maintain, not
880*6dbdd20aSAndroid Build Coastguard Worker  the responsibility of the Perfetto team. All efforts will be made to keep the
881*6dbdd20aSAndroid Build Coastguard Worker  plugin API stable and existing plugins working however plugins that remain
882*6dbdd20aSAndroid Build Coastguard Worker  unmaintained for long periods of time will be disabled and ultimately deleted.
883*6dbdd20aSAndroid Build Coastguard Worker
884