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 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 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