Merge "ui: Fix bug where flows were not being shown for frames tracks" into main
diff --git a/Android.bp b/Android.bp
index a7bda88..69c599d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6776,6 +6776,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
@@ -7205,6 +7206,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
@@ -7296,6 +7298,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/clk.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cma.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/compaction.gen.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/cpm_trace.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cpuhp.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cros_ec.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dcvsh.gen.cc",
@@ -7387,6 +7390,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/clk.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/cma.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/compaction.gen.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/cpm_trace.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/cpuhp.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/cros_ec.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/dcvsh.gen.h",
@@ -7474,6 +7478,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
@@ -7564,6 +7569,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/clk.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cma.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/compaction.pb.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/cpm_trace.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pb.cc",
@@ -7654,6 +7660,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/clk.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/cma.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/compaction.pb.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/cpm_trace.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pb.h",
@@ -7741,6 +7748,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
@@ -7832,6 +7840,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/clk.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cma.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/compaction.pbzero.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/cpm_trace.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pbzero.cc",
@@ -7923,6 +7932,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/clk.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/cma.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/compaction.pbzero.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/cpm_trace.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pbzero.h",
@@ -13644,6 +13654,7 @@
"src/trace_processor/perfetto_sql/stdlib/slices/flow.sql",
"src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql",
"src/trace_processor/perfetto_sql/stdlib/slices/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/slices/time_in_state.sql",
"src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql",
"src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql",
"src/trace_processor/perfetto_sql/stdlib/stacks/cpu_profiling.sql",
@@ -15127,6 +15138,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
@@ -16467,6 +16479,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
diff --git a/BUILD b/BUILD
index ca9ec70..66c9ff0 100644
--- a/BUILD
+++ b/BUILD
@@ -3119,6 +3119,7 @@
"src/trace_processor/perfetto_sql/stdlib/slices/flow.sql",
"src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql",
"src/trace_processor/perfetto_sql/stdlib/slices/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/slices/time_in_state.sql",
"src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql",
],
)
@@ -5544,6 +5545,7 @@
"protos/perfetto/trace/ftrace/clk.proto",
"protos/perfetto/trace/ftrace/cma.proto",
"protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpm_trace.proto",
"protos/perfetto/trace/ftrace/cpuhp.proto",
"protos/perfetto/trace/ftrace/cros_ec.proto",
"protos/perfetto/trace/ftrace/dcvsh.proto",
diff --git a/docs/analysis/common-queries.md b/docs/analysis/common-queries.md
index 6675bc0..e69de29 100644
--- a/docs/analysis/common-queries.md
+++ b/docs/analysis/common-queries.md
@@ -1,102 +0,0 @@
-# PerfettoSQL Common Queries
-
-This page acts as a reference guide for queries which often appear when
-performing ad-hoc analysis.
-
-## Computing CPU time for slices
-If collecting traces which including scheduling information (i.e. from ftrace)
-as well as userspace slices (i.e. from atrace), the actual time spent running
-on a CPU for each userspace slice can be computed: this is commonly known as
-the "CPU time" for a slice.
-
-Firstly, setup the views to simplify subsequent queries:
-```
-DROP VIEW IF EXISTS slice_with_utid;
-CREATE VIEW slice_with_utid AS
-SELECT
- ts,
- dur,
- slice.name as slice_name,
- slice.id as slice_id, utid,
- thread.name as thread_name
-FROM slice
-JOIN thread_track ON thread_track.id = slice.track_id
-JOIN thread USING (utid);
-
-DROP TABLE IF EXISTS slice_thread_state_breakdown;
-CREATE VIRTUAL TABLE slice_thread_state_breakdown
-USING SPAN_LEFT_JOIN(
- slice_with_utid PARTITIONED utid,
- thread_state PARTITIONED utid
-);
-```
-
-Then, to compute the CPU time for all slices in the trace:
-```
-SELECT slice_id, slice_name, SUM(dur) AS cpu_time
-FROM slice_thread_state_breakdown
-WHERE state = 'Running'
-GROUP BY slice_id;
-```
-
-You can also compute CPU time for a specific slice:
-```
-SELECT slice_name, SUM(dur) AS cpu_time
-FROM slice_thread_state_breakdown
-WHERE slice_id = <your slice id> AND state = 'Running';
-```
-
-These queries can be varied easily to compute other similar metrics.
-For example to get the time spent "runnable" and in "uninterruptible sleep":
-```
-SELECT
- slice_id,
- slice_name,
- SUM(IIF(state = 'R', dur, 0)) AS runnable_time,
- SUM(IIF(state = 'D', dur, 0)) AS uninterruptible_time
-FROM slice_thread_state_breakdown
-GROUP BY slice_id;
-```
-
-## Computing scheduling time by woken threads
-A given thread might cause other threads to wake up i.e. because work was
-scheduled on them. For a given thread, the amount of time threads it
-woke up ran for can be a good proxy to understand how much work is being
-spawned.
-
-To compute this, the following query can be used:
-```
-SELECT
- SUM((
- SELECT dur FROM sched
- WHERE
- sched.ts > wakee_runnable.ts AND
- wakee_runnable.utid = wakee_runnable.utid
- ORDER BY ts
- LIMIT 1
- )) AS scheduled_dur
-FROM thread AS waker
-JOIN thread_state AS wakee_runnable ON waker.utid = wakee_runnable.waker_utid
-WHERE waker.name = <your waker thread name here>
-```
-
-To do this for all the threads in the trace simultaenously:
-```
-SELECT
- waker_process.name AS process_name,
- waker.name AS thread_name,
- SUM((
- SELECT dur FROM sched
- WHERE
- sched.ts > wakee_runnable.ts AND
- sched.utid = wakee_runnable.utid
- ORDER BY ts
- LIMIT 1
- )) AS scheduled_dur
-FROM thread AS waker
-JOIN process AS waker_process USING (upid)
-JOIN thread_state AS wakee_runnable ON waker.utid = wakee_runnable.waker_utid
-WHERE waker.utid != 0
-GROUP BY 1, 2
-ORDER BY 3 desc
-```
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index 60a6796..cc65f43 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -3,7 +3,8 @@
Perfetto.
## Create a plugin
-The guide below explains how to create a plugin for the Perfetto UI.
+The guide below explains how to create a plugin for the Perfetto UI. You can
+browse the public plugin API [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public).
### Prepare for UI development
First we need to prepare the UI development environment. You will need to use a
@@ -48,6 +49,8 @@
- Navigate to the plugins page:
[localhost:10000/#!/plugins](http://localhost:10000/#!/plugins).
- Ctrl-F for your plugin name and enable it.
+- Enabling/disabling plugins requires a restart of the UI, so refresh the page
+ to start your plugin.
Later you can request for your plugin to be enabled by default. Follow the
[default plugins](#default-plugins) section for this.
@@ -58,81 +61,212 @@
upload your CL to the codereview tool.
- Once uploaded add `stevegolton@google.com` as a reviewer for your CL.
-## Plugin extension points
-Plugins can extend a handful of specific places in the UI. The sections below
-show these extension points and give examples of how they can be used.
+## Plugin Lifecycle
+To demonstrate the plugin's lifecycle, this is a minimal plugin that implements
+the key lifecycle hooks:
-### Commands
-Commands are user issuable shortcuts for actions in the UI. They can be accessed
-via the omnibox.
+```ts
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
-Follow the [create a plugin](#create-a-plugin) to get an initial skeleton for
-your plugin.
-
-To add your first command, add a call to `ctx.registerCommand()` in either your
-`onActivate()` or `onTraceLoad()` hooks. The recommendation is to register
-commands in `onActivate()` by default unless they require something from
-`PluginContextTrace` which is not available on `PluginContext`.
-
-The tradeoff is that commands registered in `onTraceLoad()` are only available
-while a trace is loaded, whereas commands registered in `onActivate()` are
-available all the time the plugin is active.
-
-```typescript
-class MyPlugin implements PerfettoPlugin {
- onActivate(ctx: PluginContext): void {
- ctx.registerCommand(
- {
- id: 'dev.perfetto.ExampleSimpleCommand#LogHelloPlugin',
- name: 'Log "Hello, plugin!"',
- callback: () => console.log('Hello, plugin!'),
- },
- );
+ static onActivate(app: App): void {
+ // Called once on app startup
+ console.log('MyPlugin::onActivate()', app.pluginId);
+ // Note: It's rare that plugins would need this hook as most plugins are
+ // interested in trace details. Thus, this function can usually be omitted.
}
- onTraceLoad(ctx: PluginContextTrace): void {
- ctx.registerCommand(
- {
- id: 'dev.perfetto.ExampleSimpleTraceCommand#LogHelloTrace',
- name: 'Log "Hello, trace!"',
- callback: () => console.log('Hello, trace!'),
- },
- );
+ constructor(trace: Trace) {
+ // Called each time a trace is loaded
+ console.log('MyPlugin::constructor()', trace.traceInfo.traceTitle);
+ }
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ // Called each time a trace is loaded
+ console.log('MyPlugin::onTraceLoad()', trace.traceInfo.traceTitle);
+ // Note this function returns a promise, so any any async calls should be
+ // completed before this promise resolves as the app using this promise for
+ // timing and plugin synchronization.
}
}
```
-Here `id` is a unique string which identifies this command. The `id` should be
-prefixed with the plugin id followed by a `#`. All command `id`s must be unique
-system-wide. `name` is a human readable name for the command, which is shown in
-the command palette. Finally `callback()` is the callback which actually
-performs the action.
+You can run this plugin with devtools to see the log messages in the console,
+which should give you a feel for the plugin lifecycle. Try opening a few traces
+one after another.
-Commands are removed automatically when their context disappears. Commands
-registered with the `PluginContext` are removed when the plugin is deactivated,
-and commands registered with the `PluginContextTrace` are removed when the trace
-is unloaded.
+`onActivate()` runs shortly after Perfetto starts up, before a trace is loaded.
+This is where the you'll configure your plugin's capabilities that aren't trace
+dependent. At this point the plugin's class is not instantiated, so you'll
+notice `onActivate()` hook is a static class member. `onActivate()` is only ever
+called once, regardless of the number of traces loaded.
+
+`onActivate()` is passed an `App` object which the plugin can use to configure
+core capabilities such as commands, sidebar items and pages. Capabilities
+registered on the App interface are persisted throughout the lifetime of the app
+(practically forever until the tab is closed), in contrast to what happens for
+the same methods on the `Trace` object (see below).
+
+The plugin class in instantiated when a trace is loaded (a new plugin instance
+is created for each trace). `onTraceLoad()` is called immediately after the
+class is instantiated, which is where you'll configure your plugin's trace
+dependent capabilities.
+
+`onTraceLoad()` is passed a `Trace` object which the plugin can use to configure
+entities that are scoped to a specific trace, such as tracks and tabs. `Trace`
+is a superset of `App`, so anything you can do with `App` you can also do with
+`Trace`, however, capabilities registered on `Trace` will typically be discarded
+when a new trace is loaded.
+
+A plugin will typically register capabilities with the core and return quickly.
+But these capabilities usually contain objects and callbacks which are called
+into later by the core during the runtime of the app. Most capabilities require
+a `Trace` or an `App` to do anything useful so these are usually bound into the
+capabilities at registration time using JavaScript classes or closures.
+
+```ts
+// Toy example: Code will not compile.
+async onTraceLoad(trace: Trace) {
+ // `trace` is captured in the closure and used later by the app
+ trace.regsterXYZ(() => trace.xyz);
+}
+```
+
+That way, the callback is bound to a specific trace object which and the trace
+object can outlive the runtime of the `onTraceLoad()` function, which is a very
+common pattern in Perfetto plugins.
+
+> Note: Some capabilities can be registered on either the `App` or the `Trace`
+> object (i.e. in `onActivate()` or in `onTraceLoad()`), if in doubt about which
+> one to use, use `onTraceLoad()` as this is more than likely the one you want.
+> Most plugins add tracks and tabs that depend on the trace. You'd usually have
+> to be doing something out of the ordinary if you need to use `onActivate()`.
+
+### Performance
+`onActivate()` and `onTraceLoad()` should generally complete as quickly as
+possible, however sometimes `onTraceLoad()` may need to perform async operations
+on trace processor such as performing queries and/or creating views and tables.
+Thus, `onTraceLoad()` should return a promise (or you can simply make it an
+async function). When this promise resolves it tells the core that the plugin is
+fully initialized.
+
+> Note: It's important that any async operations done in onTraceLoad() are
+> awaited so that all async operations are completed by the time the promise is
+> resolved. This is so that plugins can be properly timed and synchronized.
+
+
+```ts
+// GOOD
+async onTraceLoad(trace: Trace) {
+ await trace.engine.query(...);
+}
+
+// BAD
+async onTraceLoad(trace: Trace) {
+ // Note the missing await!
+ trace.engine.query(...);
+}
+```
+
+## Extension Points
+Plugins can extend functionality of Perfetto by registering capabilities via
+extension points on the `App` or `Trace` objects.
+
+The following sections delve into more detail on each extension point and
+provide examples of how they can be used.
+
+### Commands
+Commands are user issuable shortcuts for actions in the UI. They are invoked via
+the command palette which can be opened by pressing Ctrl+Shift+P (or Cmd+Shift+P
+on Mac), or by typing a '>' into the omnibox.
+
+To add a command, add a call to `registerCommand()` on either your
+`onActivate()` or `onTraceLoad()` hooks. The recommendation is to register
+commands in `onTraceLoad()` by default unless you very specifically want the
+command to be available before a trace has loaded.
+
+Example of a command that doesn't require a trace.
+```ts
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ static onActivate(app: App) {
+ app.commands.registerCommand({
+ id: `${app.pluginId}#SayHello`,
+ name: 'Say hello',
+ callback: () => console.log('Hello, world!'),
+ });
+ }
+}
+```
+
+Example of a command that requires a trace object - in this case the trace
+title.
+```ts
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace) {
+ trace.commands.registerCommand({
+ id: `${trace.pluginId}#LogTraceTitle`,
+ name: 'Log trace title',
+ callback: () => console.log(trace.info.traceTitle),
+ });
+ }
+}
+```
+
+> Notice that the trace object is captured in the closure, so it can be used
+> after the onTraceLoad() function has returned. This is a very common pattern
+> in Perfetto plugins.
+
+Command arguments explained:
+- `id` is a unique string which identifies this command. The `id` should be
+prefixed with the plugin id followed by a `#`. All command `id`s must be unique
+system-wide.
+- `name` is a human readable name for the command, which is shown in the command
+palette.
+- `callback()` is the callback which actually performs the action.
+
+#### Async commands
+It's common that commands will perform async operations in their callbacks. It's
+recommended to use async/await for this rather than `.then().catch()`. The
+easiest way to do this is to make the callback an async function.
+
+```ts
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace) {
+ trace.commands.registerCommand({
+ id: `${trace.pluginId}#QueryTraceProcessor`,
+ name: 'Query trace processor',
+ callback: async () => {
+ const results = await trace.engine.query(...);
+ // use results...
+ },
+ });
+ }
+}
+```
+
+If the callback is async (i.e. it returns a promise), nothing special happens.
+The command is still fire-n-forget as far as the core is concerned.
Examples:
-- [dev.perfetto.ExampleSimpleCommand](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts).
+- [com.example.ExampleSimpleCommand](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/com.example.ExampleSimpleCommand/index.ts).
- [perfetto.CoreCommands](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/core_plugins/commands/index.ts).
-- [dev.perfetto.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExampleState/index.ts).
+- [com.example.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/com.example.ExampleState/index.ts).
-#### Hotkeys
-
-A default hotkey may be provided when registering a command.
+### Hotkeys
+A hotkey may be associated with a command at registration time.
```typescript
-ctx.registerCommand({
- id: 'dev.perfetto.ExampleSimpleCommand#LogHelloWorld',
- name: 'Log "Hello, World!"',
- callback: () => console.log('Hello, World!'),
+ctx.commands.registerCommand({
+ ...
defaultHotkey: 'Shift+H',
});
```
-Even though the hotkey is a string, it's format checked at compile time using
-typescript's [template literal
+Despite the fact that the hotkey is a string, its format is checked at compile
+time using typescript's [template literal
types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html).
See
@@ -140,151 +274,179 @@
for more details on how the hotkey syntax works, and for the available keys and
modifiers.
+Note this is referred to as the 'default' hotkey because we may introduce a
+feature in the future where users can modify their hotkeys, though this doesn't
+exist at the moment.
+
### Tracks
-#### Defining Tracks
-Tracks describe how to render a track and how to respond to mouse interaction.
-However, the interface is a WIP and should be considered unstable. This
-documentation will be added to over the next few months after the design is
-finalised.
+In order to add a new track to the timeline, you'll need to create two entities:
+- A track 'renderer' which controls what the track looks like and how it fetches
+ data from trace processor.
+- A track 'node' controls where the track appears in the workspace.
-#### Reusing Existing Tracks
-Creating tracks from scratch is difficult and the API is currently a WIP, so it
-is strongly recommended to use one of our existing base classes which do a lot
-of the heavy lifting for you. These base classes also provide a more stable
-layer between your track and the (currently unstable) track API.
+Track renderers are powerful but complex, so it's, so it's strongly advised not
+to create your own. Instead, by far the easiest way to get started with tracks
+is to use the `createQuerySliceTrack` and `createQueryCounterTrack` helpers.
-For example, if your track needs to show slices from a given a SQL expression (a
-very common pattern), extend the `NamedSliceTrack` abstract base class and
-implement `getSqlSource()`, which should return a query with the following
-columns:
-
-- `id: INTEGER`: A unique ID for the slice.
-- `ts: INTEGER`: The timestamp of the start of the slice.
-- `dur: INTEGER`: The duration of the slice.
-- `depth: INTEGER`: Integer value defining how deep the slice should be drawn in
- the track, 0 being rendered at the top of the track, and increasing numbers
- being drawn towards the bottom of the track.
-- `name: TEXT`: Text to be rendered on the slice and in the popup.
-
-For example, the following track describes a slice track that displays all
-slices that begin with the letter 'a'.
+Example:
```ts
-class MyTrack extends NamedSliceTrack {
- getSqlSource(): string {
- return `
- SELECT
- id,
- ts,
- dur,
- depth,
- name
- from slice
- where name like 'a%'
- `;
- }
-}
-```
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
-#### Registering Tracks
-Plugins may register tracks with Perfetto using
-`PluginContextTrace.registerTrack()`, usually in their `onTraceLoad` function.
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace) {
+ const title = 'My Track';
+ const uri = `${trace.pluginId}#MyTrack`;
+ const query = 'select * from slice where track_id = 123';
-```ts
-class MyPlugin implements PerfettoPlugin {
- onTraceLoad(ctx: PluginContextTrace): void {
- ctx.registerTrack({
- uri: 'dev.MyPlugin#ExampleTrack',
- displayName: 'My Example Track',
- trackFactory: ({trackKey}) => {
- return new MyTrack({engine: ctx.engine, trackKey});
+ // Create a new track renderer based on a query
+ const track = await createQuerySliceTrack({
+ trace,
+ uri,
+ data: {
+ sqlSource: query,
},
});
+
+ // Register the track renderer with the core
+ trace.tracks.registerTrack({uri, title, track});
+
+ // Create a track node that references the track renderer using its uri
+ const track = new TrackNode({uri, title});
+
+ // Add the track node to the current workspace
+ trace.workspace.addChildInOrder(track);
}
}
```
-#### Default Tracks
-The "default" tracks are a list of tracks that are added to the timeline when a
-fresh trace is loaded (i.e. **not** when loading a trace from a permalink). This
-list is copied into the timeline after the trace has finished loading, at which
-point control is handed over to the user, allowing them add, remove and reorder
-tracks as they please. Thus it only makes sense to add default tracks in your
-plugin's `onTraceLoad` function, as adding a default track later will have no
-effect.
+See [the source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public/lib/tracks/query_slice_track.ts)
+for detailed usage.
+
+You can also add a counter track using `createQueryCounterTrack` which works in
+a similar way.
```ts
-class MyPlugin implements PerfettoPlugin {
- onTraceLoad(ctx: PluginContextTrace): void {
- ctx.registerTrack({
- // ... as above ...
- });
+import {createQueryCounterTrack} from '../../public/lib/tracks/query_counter_track';
- ctx.addDefaultTrack({
- uri: 'dev.MyPlugin#ExampleTrack',
- displayName: 'My Example Track',
- sortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- });
- }
-}
-```
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace) {
+ const title = 'My Counter Track';
+ const uri = `${trace.pluginId}#MyCounterTrack`;
+ const query = 'select * from counter where track_id = 123';
-Registering and adding a default track is such a common pattern that there is a
-shortcut for doing both in one go: `PluginContextTrace.registerStaticTrack()`,
-which saves having to repeat the URI and display name.
-
-```ts
-class MyPlugin implements PerfettoPlugin {
- onTraceLoad(ctx: PluginContextTrace): void {
- ctx.registerStaticTrack({
- uri: 'dev.MyPlugin#ExampleTrack',
- displayName: 'My Example Track',
- trackFactory: ({trackKey}) => {
- return new MyTrack({engine: ctx.engine, trackKey});
- },
- sortKey: PrimaryTrackSortKey.COUNTER_TRACK,
- });
- }
-}
-```
-
-#### Adding Tracks Directly
-Sometimes plugins might want to add a track to the timeline immediately, usually
-as a result of a command or on some other user action such as a button click. We
-can do this using `PluginContext.timeline.addTrack()`.
-
-```ts
-class MyPlugin implements PerfettoPlugin {
- onTraceLoad(ctx: PluginContextTrace): void {
- ctx.registerTrack({
- // ... as above ...
- });
-
- // Register a command that directly adds a new track to the timeline
- ctx.registerCommand({
- id: 'dev.MyPlugin#AddMyTrack',
- name: 'Add my track',
- callback: () => {
- ctx.timeline.addTrack(
- 'dev.MyPlugin#ExampleTrack',
- 'My Example Track'
- );
+ // Create a new track renderer based on a query
+ const track = await createQueryCounterTrack({
+ trace,
+ uri,
+ data: {
+ sqlSource: query,
},
});
+
+ // Register the track renderer with the core
+ trace.tracks.registerTrack({uri, title, track});
+
+ // Create a track node that references the track renderer using its uri
+ const track = new TrackNode({uri, title});
+
+ // Add the track node to the current workspace
+ trace.workspace.addChildInOrder(track);
}
}
```
+See [the source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public/lib/tracks/query_counter_track.ts)
+for detailed usage.
+
+#### Grouping Tracks
+Any track can have children. Just add child nodes any `TrackNode` object using
+its `addChildXYZ()` methods. Nested tracks are rendered as a collapsible tree.
+
+```ts
+const group = new TrackNode({title: 'Group'});
+trace.workspace.addChildInOrder(group);
+group.addChildLast(new TrackNode({title: 'Child Track A'}));
+group.addChildLast(new TrackNode({title: 'Child Track B'}));
+group.addChildLast(new TrackNode({title: 'Child Track C'}));
+```
+
+Tracks nodes with children can be collapsed and expanded manually by the user at
+runtime, or programmatically using their `expand()` and `collapse()` methods. By
+default tracks are collapsed, so to have tracks automatically expanded on
+startup you'll need to call `expand()` after adding the track node.
+
+```ts
+group.expand();
+```
+
+![Nested tracks](../images/ui-plugins/nested_tracks.png)
+
+Summary tracks are behave slightly differently to ordinary tracks. Summary
+tracks:
+- Are rendered with a light blue background when collapsed, dark blue when
+ expanded.
+- Stick to the top of the viewport when scrolling.
+- Area selections made on the track apply to child tracks instead of the summary
+ track itself.
+
+To create a summary track, set the `isSummary: true` option in its initializer
+list at creation time or set its `isSummary` property to true after creation.
+
+```ts
+const group = new TrackNode({title: 'Group', isSummary: true});
+// ~~~ or ~~~
+group.isSummary = true;
+```
+
+![Summary track](../images/ui-plugins/summary_track.png)
+
+Examples
+- [com.example.ExampleNestedTracks](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/com.example.ExampleNestedTracks/index.ts).
+
+#### Track Ordering
+Tracks can be manually reordered using the `addChildXYZ()` functions available on
+the track node api, including `addChildFirst()`, `addChildLast()`,
+`addChildBefore()`, and `addChildAfter()`.
+
+See [the workspace source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public/workspace.ts) for detailed usage.
+
+However, when several plugins add tracks to the same node or the workspace, no
+single plugin has complete control over the sorting of child nodes within this
+node. Thus, the sortOrder property is be used to decentralize the sorting logic
+between plugins.
+
+In order to do this we simply give the track a `sortOrder` and call
+`addChildInOrder()` on the parent node and the track will be placed before the
+first track with a higher `sortOrder` in the list. (i.e. lower `sortOrder`s appear
+higher in the stack).
+
+```ts
+// PluginA
+workspace.addChildInOrder(new TrackNode({title: 'Foo', sortOrder: 10}));
+
+// Plugin B
+workspace.addChildInOrder(new TrackNode({title: 'Bar', sortOrder: -10}));
+```
+
+Now it doesn't matter which order plugin are initialized, track `Bar` will
+appear above track `Foo` (unless reordered later).
+
+If no `sortOrder` is defined, the track assumes a `sortOrder` of 0.
+
+> It is recommended to always use `addChildInOrder()` in plugins when adding
+> tracks to the `workspace`, especially if you want your plugin to be enabled by
+> default, as this will ensure it respects the sortOrder of other plugins.
+
+
### Tabs
Tabs are a useful way to display contextual information about the trace, the
current selection, or to show the results of an operation.
-To register a tab from a plugin, use the `PluginContextTrace.registerTab`
-method.
+To register a tab from a plugin, use the `Trace.registerTab` method.
```ts
-import m from 'mithril';
-import {Tab, Plugin, PluginContext, PluginContextTrace} from '../../public';
-
class MyTab implements Tab {
render(): m.Children {
return m('div', 'Hello from my tab');
@@ -295,11 +457,11 @@
}
}
-class MyPlugin implements PerfettoPlugin {
- onActivate(_: PluginContext): void {}
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- ctx.registerTab({
- uri: 'dev.MyPlugin#MyTab',
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace) {
+ trace.registerTab({
+ uri: `${trace.pluginId}#MyTab`,
content: new MyTab(),
});
}
@@ -320,8 +482,8 @@
Alternatively, tabs may be shown or hidden programmatically using the tabs API.
```ts
-ctx.tabs.showTab('dev.MyPlugin#MyTab');
-ctx.tabs.hideTab('dev.MyPlugin#MyTab');
+trace.tabs.showTab(`${trace.pluginId}#MyTab`);
+trace.tabs.hideTab(`${trace.pluginId}#MyTab`);
```
Tabs have the following properties:
@@ -345,9 +507,9 @@
registering the tab.
```ts
-ctx.registerTab({
+trace.registerTab({
isEphemeral: true,
- uri: 'dev.MyPlugin#MyTab',
+ uri: `${trace.pluginId}#MyTab`,
content: new MyEphemeralTab(),
});
```
@@ -360,13 +522,6 @@
```ts
import m from 'mithril';
import {uuidv4} from '../../base/uuid';
-import {
- Plugin,
- PluginContext,
- PluginContextTrace,
- PluginDescriptor,
- Tab,
-} from '../../public';
class MyNameTab implements Tab {
constructor(private name: string) {}
@@ -378,21 +533,21 @@
}
}
-class MyPlugin implements PerfettoPlugin {
- onActivate(_: PluginContext): void {}
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- ctx.registerCommand({
- id: 'dev.MyPlugin#AddNewEphemeralTab',
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.registerCommand({
+ id: `${trace.pluginId}#AddNewEphemeralTab`,
name: 'Add new ephemeral tab',
- callback: () => handleCommand(ctx),
+ callback: () => handleCommand(trace),
});
}
}
-function handleCommand(ctx: PluginContextTrace): void {
+function handleCommand(trace: Trace): void {
const name = prompt('What is your name');
if (name) {
- const uri = 'dev.MyPlugin#MyName' + uuidv4();
+ const uri = `${trace.pluginId}#MyName${uuidv4()}`;
// This makes the tab available to perfetto
ctx.registerTab({
isEphemeral: true,
@@ -404,11 +559,6 @@
ctx.tabs.showTab(uri);
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.MyPlugin',
- plugin: MyPlugin,
-};
```
### Details Panels & The Current Selection Tab
@@ -422,10 +572,10 @@
For example:
```ts
-class MyPlugin implements PerfettoPlugin {
- onActivate(_: PluginContext): void {}
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- ctx.registerDetailsPanel({
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace) {
+ trace.registerDetailsPanel({
render(selection: Selection) {
if (canHandleSelection(selection)) {
return m('div', 'Details for selection');
@@ -451,6 +601,142 @@
is undefined. This is a limitation of the current approach and will be updated
to a more democratic contribution model in the future.
+### Sidebar Menu Items
+Plugins can add new entries to the sidebar menu which appears on the left hand
+side of the UI. These entries can include:
+- Commands
+- Links
+- Arbitrary Callbacks
+
+#### Commands
+If a command is referenced, the command name and hotkey are displayed on the
+sidebar item.
+```ts
+trace.commands.registerCommand({
+ id: 'sayHi',
+ name: 'Say hi',
+ callback: () => window.alert('hi'),
+ defaultHotkey: 'Shift+H',
+});
+
+trace.sidebar.addMenuItem({
+ commandId: 'sayHi',
+ section: 'support',
+ icon: 'waving_hand',
+});
+```
+
+#### Links
+If an href is present, the sidebar will be used as a link. This can be an
+internal link to a page, or an external link.
+```ts
+trace.sidebar.addMenuItem({
+ section: 'navigation',
+ text: 'Plugins',
+ href: '#!/plugins',
+});
+```
+
+#### Callbacks
+Sidebar items can be instructed to execute arbitrary callbacks when the button
+is clicked.
+```ts
+trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Copy secrets to clipboard',
+ action: () => copyToClipboard('...'),
+});
+```
+
+If the action returns a promise, the sidebar item will show a little spinner
+animation until the promise returns.
+
+```ts
+trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Prepare the data...',
+ action: () => new Promise((r) => setTimeout(r, 1000)),
+});
+```
+Optional params for all types of sidebar items:
+- `icon` - A material design icon to be displayed next to the sidebar menu item.
+ See full list [here](https://fonts.google.com/icons).
+- `tooltip` - Displayed on hover
+- `section` - Where to place the menu item.
+ - `navigation`
+ - `current_trace`
+ - `convert_trace`
+ - `example_traces`
+ - `support`
+- `sortOrder` - The higher the sortOrder the higher the bar.
+
+See the [sidebar source](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public/sidebar.ts)
+for more detailed usage.
+
+### Pages
+Pages are entities that can be routed via the URL args, and whose content take
+up the entire available space to the right of the sidebar and underneath the
+topbar. Examples of pages are the timeline, record page, and query page, just to
+name a few common examples.
+
+E.g.
+```
+http://ui.perfetto.dev/#!/viewer <-- 'viewer' is is the current page.
+```
+
+Pages are added from a plugin by calling the `pages.registerPage` function.
+
+Pages can be trace-less or trace-ful. Trace-less pages are pages that are to be
+displayed when no trace is loaded - i.e. the record page. Trace-ful pages are
+displayed only when a trace is loaded, as they typically require a trace to work
+with.
+
+You'll typically register trace-less pages in your plugin's `onActivate()`
+function and trace-full pages in either `onActivate()` or `onTraceLoad()`. If
+users navigate to a trace-ful page before a trace is loaded the homepage will be
+shown instead.
+
+> Note: You don't need to bind the `Trace` object for pages unlike other
+> extension points, Perfetto will inject a trace object for you.
+
+Pages should be mithril components that accept `PageWithTraceAttrs` for
+trace-ful pages or `PageAttrs` for trace-less pages.
+
+Example of a trace-less page:
+```ts
+import m from 'mithril';
+import {PageAttrs} from '../../public/page';
+
+class MyPage implements m.ClassComponent<PageAttrs> {
+ view(vnode: m.CVnode<PageAttrs>) {
+ return `The trace title is: ${vnode.attrs.trace.traceInfo.traceTitle}`;
+ }
+}
+
+// ~~~ snip ~~~
+
+app.pages.registerPage({route: '/mypage', page: MyPage, traceless: true});
+```
+
+```ts
+import m from 'mithril';
+import {PageWithTraceAttrs} from '../../public/page';
+
+class MyPage implements m.ClassComponent<PageWithTraceAttrs> {
+ view(_vnode_: m.CVnode<PageWithTraceAttrs>) {
+ return 'Hello from my page';
+ }
+}
+
+// ~~~ snip ~~~
+
+app.pages.registerPage({route: '/mypage', page: MyPage});
+```
+
+Examples:
+- [dev.perfetto.ExplorePage](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExplorePage/index.ts).
+
+
### Metric Visualisations
TBD
@@ -504,12 +790,13 @@
}
```
-To access permalink state, call `mountStore()` on your `PluginContextTrace`
+To access permalink state, call `mountStore()` on your `Trace`
object, passing in a migration function.
```typescript
-class MyPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- const store = ctx.mountStore(migrate);
+default export class implements PerfettoPlugin {
+ static readonly id = 'com.example.MyPlugin';
+ async onTraceLoad(trace: Trace): Promise<void> {
+ const store = trace.mountStore(migrate);
}
}
diff --git a/docs/images/ui-plugins/nested_tracks.png b/docs/images/ui-plugins/nested_tracks.png
new file mode 100644
index 0000000..a9e87bc
--- /dev/null
+++ b/docs/images/ui-plugins/nested_tracks.png
Binary files differ
diff --git a/docs/images/ui-plugins/summary_track.png b/docs/images/ui-plugins/summary_track.png
new file mode 100644
index 0000000..96999dc
--- /dev/null
+++ b/docs/images/ui-plugins/summary_track.png
Binary files differ
diff --git a/docs/toc.md b/docs/toc.md
index 01be2af..deaeb5d 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -48,7 +48,6 @@
* [Standard Library](analysis/stdlib-docs.autogen)
* [Syntax](analysis/perfetto-sql-syntax.md)
* [Prelude tables](analysis/sql-tables.autogen)
- * [Common Queries](analysis/common-queries.md)
* [Built-ins](analysis/builtin.md)
* [Analysis at scale](#)
* [Batch Trace Processor](analysis/batch-trace-processor.md)
diff --git a/gn/proto_library.gni b/gn/proto_library.gni
index 6bc1760..af540c2 100644
--- a/gn/proto_library.gni
+++ b/gn/proto_library.gni
@@ -107,6 +107,7 @@
"generator_plugin_options",
"include_dirs",
"proto_data_sources",
+ "proto_deps",
"proto_in_dir",
"proto_out_dir",
"sources",
@@ -153,6 +154,7 @@
"defines",
"generator_plugin_options",
"include_dirs",
+ "proto_deps",
"proto_in_dir",
"proto_out_dir",
"sources",
@@ -205,6 +207,7 @@
"defines",
"extra_configs",
"include_dirs",
+ "proto_deps",
"proto_in_dir",
"proto_out_dir",
"generator_plugin_options",
@@ -286,40 +289,6 @@
# build generators and for generating descriptors.
source_set_target_name =
string_replace(target_name, expansion_token, "source_set")
- group(source_set_target_name) {
- public_deps_ = []
- if (defined(invoker.public_deps)) {
- foreach(dep, invoker.public_deps) {
- # Get the absolute target path
- mapped_dep = string_replace(dep, expansion_token, "source_set")
- public_deps_ += [ mapped_dep ]
- }
- }
-
- deps = []
- if (defined(invoker.deps)) {
- foreach(dep, invoker.deps) {
- mapped_dep = string_replace(dep, expansion_token, "source_set")
- deps += [ mapped_dep ]
- }
- }
- deps += public_deps_
-
- sources = []
- foreach(source, invoker.sources) {
- sources += [ get_path_info(source, "abspath") ]
- }
-
- metadata = {
- proto_library_sources = sources
- proto_import_dirs = import_dirs_
- exports = []
- foreach(dep, public_deps_) {
- exports +=
- [ get_label_info(dep, "dir") + ":" + get_label_info(dep, "name") ]
- }
- }
- }
# This config is necessary for Chrome proto_library build rule to work
# correctly.
@@ -328,6 +297,42 @@
inputs = invoker.sources
}
+ group(source_set_target_name) {
+ # To propagate indirect inputs dependencies to descendant tareget, we use
+ # public_deps and public_configs in this target.
+ public_deps = []
+ exports_ = []
+ if (defined(invoker.public_deps)) {
+ foreach(dep, invoker.public_deps) {
+ # Get the absolute target path
+ mapped_dep = string_replace(dep, expansion_token, "source_set")
+ public_deps += [ mapped_dep ]
+ exports_ += [ get_label_info(mapped_dep, "dir") + ":" +
+ get_label_info(mapped_dep, "name") ]
+ }
+ }
+
+ if (defined(invoker.deps)) {
+ foreach(dep, invoker.deps) {
+ mapped_dep = string_replace(dep, expansion_token, "source_set")
+ public_deps += [ mapped_dep ]
+ }
+ }
+
+ sources = []
+ foreach(source, invoker.sources) {
+ sources += [ get_path_info(source, "abspath") ]
+ }
+
+ public_configs = [ ":${source_set_input_config_name}" ]
+
+ metadata = {
+ proto_library_sources = sources
+ proto_import_dirs = import_dirs_
+ exports = exports_
+ }
+ }
+
# Generate the descriptor if the option is set.
if (defined(invoker.generate_descriptor)) {
target_name_ = string_replace(target_name, expansion_token, "descriptor")
@@ -385,6 +390,7 @@
proto_out_dir = proto_path
generator_plugin_options = "wrapper_namespace=pbzero"
deps = all_deps_
+ proto_deps = [ ":$source_set_target_name" ]
propagate_imports_configs = propagate_imports_configs_
import_dirs = import_dirs_
forward_variables_from(invoker, vars_to_forward)
@@ -395,6 +401,7 @@
proto_out_dir = proto_path
generator_plugin_options = "wrapper_namespace=gen"
deps = all_deps_
+ proto_deps = [ ":$source_set_target_name" ]
propagate_imports_configs = propagate_imports_configs_
import_dirs = import_dirs_
forward_variables_from(invoker, vars_to_forward)
@@ -405,6 +412,7 @@
proto_in_dir = proto_path
proto_out_dir = proto_path
generator_plugin_options = "wrapper_namespace=gen"
+ proto_deps = [ ":$source_set_target_name" ]
deps = all_deps_ + [ ":$cpp_target_name_" ]
propagate_imports_configs = propagate_imports_configs_
import_dirs = import_dirs_
@@ -419,6 +427,7 @@
cc_generator_options = "lite=true:"
propagate_imports_configs = propagate_imports_configs_
import_dirs = import_dirs_
+ proto_deps = [ ":${source_set_target_name}" ]
forward_variables_from(invoker, vars_to_forward)
}
} else {
diff --git a/gn/standalone/proto_library.gni b/gn/standalone/proto_library.gni
index 93ed9c9..ed86c02 100644
--- a/gn/standalone/proto_library.gni
+++ b/gn/standalone/proto_library.gni
@@ -22,6 +22,10 @@
template("proto_library") {
assert(defined(invoker.sources))
+
+ # This is used in chromium build.
+ not_needed(invoker, [ "proto_deps" ])
+
proto_sources = invoker.sources
# All the proto imports should be relative to the project root.
diff --git a/infra/perfetto.dev/BUILD.gn b/infra/perfetto.dev/BUILD.gn
index 25ff547..b81fa3d 100644
--- a/infra/perfetto.dev/BUILD.gn
+++ b/infra/perfetto.dev/BUILD.gn
@@ -358,6 +358,22 @@
mdtargets += [ ":mdfile_${source}" ]
}
+# Files which have been removed/renamed/moved and now have HTTP redirections in
+# src/assets/script.js
+removed_renamed_moved_files = [ "analysis/common-queries.md" ]
+
+foreach(source, removed_renamed_moved_files) {
+ filename = rebase_path(string_replace(source, ".md", ""),
+ rebase_path("../../docs", root_build_dir))
+ md_to_html("mdfile_${source}") {
+ markdown = "src/empty.md"
+ html_template = "src/template_markdown.html"
+ out_html = "docs/${filename}"
+ deps = [ ":gen_toc" ]
+ }
+ mdtargets += [ ":mdfile_${source}" ]
+}
+
group("all_mdfiles") {
deps = mdtargets
}
diff --git a/infra/perfetto.dev/src/assets/script.js b/infra/perfetto.dev/src/assets/script.js
index aa368bc..44adbf6 100644
--- a/infra/perfetto.dev/src/assets/script.js
+++ b/infra/perfetto.dev/src/assets/script.js
@@ -21,26 +21,6 @@
let tocEventHandlersInstalled = false;
let resizeObserver = undefined;
-// Handles redirects from the old docs.perfetto.dev.
-const legacyRedirectMap = {
- '#/contributing': '/docs/contributing/getting-started#community',
- '#/build-instructions': '/docs/contributing/build-instructions',
- '#/testing': '/docs/contributing/testing',
- '#/app-instrumentation': '/docs/instrumentation/tracing-sdk',
- '#/recording-traces': '/docs/instrumentation/tracing-sdk#recording',
- '#/running': '/docs/quickstart/android-tracing',
- '#/long-traces': '/docs/concepts/config#long-traces',
- '#/detached-mode': '/docs/concepts/detached-mode',
- '#/heapprofd': '/docs/data-sources/native-heap-profiler',
- '#/java-hprof': '/docs/data-sources/java-heap-profiler',
- '#/trace-processor': '/docs/analysis/trace-processor',
- '#/analysis': '/docs/analysis/trace-processor#annotations',
- '#/metrics': '/docs/analysis/metrics',
- '#/traceconv': '/docs/quickstart/traceconv',
- '#/clock-sync': '/docs/concepts/clock-sync',
- '#/architecture': '/docs/concepts/service-model',
-};
-
function doAfterLoadEvent(action) {
if (onloadFired) {
return action();
@@ -345,7 +325,40 @@
document.documentElement.style.setProperty('--anim-enabled', '1')
});
+// Handles redirects from the old docs.perfetto.dev.
+const legacyRedirectMap = {
+ '#/contributing': '/docs/contributing/getting-started#community',
+ '#/build-instructions': '/docs/contributing/build-instructions',
+ '#/testing': '/docs/contributing/testing',
+ '#/app-instrumentation': '/docs/instrumentation/tracing-sdk',
+ '#/recording-traces': '/docs/instrumentation/tracing-sdk#recording',
+ '#/running': '/docs/quickstart/android-tracing',
+ '#/long-traces': '/docs/concepts/config#long-traces',
+ '#/detached-mode': '/docs/concepts/detached-mode',
+ '#/heapprofd': '/docs/data-sources/native-heap-profiler',
+ '#/java-hprof': '/docs/data-sources/java-heap-profiler',
+ '#/trace-processor': '/docs/analysis/trace-processor',
+ '#/analysis': '/docs/analysis/trace-processor#annotations',
+ '#/metrics': '/docs/analysis/metrics',
+ '#/traceconv': '/docs/quickstart/traceconv',
+ '#/clock-sync': '/docs/concepts/clock-sync',
+ '#/architecture': '/docs/concepts/service-model',
+};
+
const fragment = location.hash.split('?')[0].replace('.md', '');
if (fragment in legacyRedirectMap) {
location.replace(legacyRedirectMap[fragment]);
-}
\ No newline at end of file
+}
+
+// Pages which have been been removed/renamed/moved and need to be redirected
+// to their new home.
+const redirectMap = {
+ // stdlib docs is not a perfect replacement but is good enough until we write
+ // a proper, Android specific query codelab page.
+ // TODO(lalitm): switch to that page when it's ready.
+ '/docs/analysis/common-queries': '/docs/analysis/stdlib-docs',
+};
+
+if (location.pathname in redirectMap) {
+ location.replace(redirectMap[location.pathname]);
+}
diff --git a/infra/perfetto.dev/src/empty.md b/infra/perfetto.dev/src/empty.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/infra/perfetto.dev/src/empty.md
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 47da583..9ab7eda 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -78,11 +78,13 @@
import "protos/perfetto/metrics/android/wattson_tasks_attribution.proto";
// Trace processor metadata
+// Next id: 17
message TraceMetadata {
reserved 1;
optional int64 trace_duration_ns = 2;
optional string trace_uuid = 3;
optional string android_build_fingerprint = 4;
+ optional string android_device_manufacturer = 16;
optional int64 statsd_triggering_subscription_id = 5;
optional int64 trace_size_bytes = 6;
repeated string trace_trigger = 7;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index a2763d9..275e373 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -3009,11 +3009,13 @@
// Begin of protos/perfetto/metrics/metrics.proto
// Trace processor metadata
+// Next id: 17
message TraceMetadata {
reserved 1;
optional int64 trace_duration_ns = 2;
optional string trace_uuid = 3;
optional string android_build_fingerprint = 4;
+ optional string android_device_manufacturer = 16;
optional int64 statsd_triggering_subscription_id = 5;
optional int64 trace_size_bytes = 6;
repeated string trace_trigger = 7;
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 59148eb..7319dd4 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -28,6 +28,7 @@
"clk.proto",
"cma.proto",
"compaction.proto",
+ "cpm_trace.proto",
"cpuhp.proto",
"cros_ec.proto",
"dcvsh.proto",
diff --git a/protos/perfetto/trace/ftrace/cpm_trace.proto b/protos/perfetto/trace/ftrace/cpm_trace.proto
new file mode 100644
index 0000000..f19f0f8
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/cpm_trace.proto
@@ -0,0 +1,12 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message ParamSetValueCpmFtraceEvent {
+ optional string body = 1;
+ optional uint32 value = 2;
+ optional int64 timestamp = 3;
+}
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index 7858418..b363eb0 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -28,6 +28,7 @@
import "protos/perfetto/trace/ftrace/clk.proto";
import "protos/perfetto/trace/ftrace/cma.proto";
import "protos/perfetto/trace/ftrace/compaction.proto";
+import "protos/perfetto/trace/ftrace/cpm_trace.proto";
import "protos/perfetto/trace/ftrace/cpuhp.proto";
import "protos/perfetto/trace/ftrace/cros_ec.proto";
import "protos/perfetto/trace/ftrace/dcvsh.proto";
@@ -681,5 +682,6 @@
SchedWakeupTaskAttrFtraceEvent sched_wakeup_task_attr = 540;
DevfreqFrequencyFtraceEvent devfreq_frequency = 541;
KprobeEvent kprobe_event = 542;
+ ParamSetValueCpmFtraceEvent param_set_value_cpm = 543;
}
}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index f77915c..5e07a0c 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -7538,6 +7538,16 @@
// End of protos/perfetto/trace/ftrace/compaction.proto
+// Begin of protos/perfetto/trace/ftrace/cpm_trace.proto
+
+message ParamSetValueCpmFtraceEvent {
+ optional string body = 1;
+ optional uint32 value = 2;
+ optional int64 timestamp = 3;
+}
+
+// End of protos/perfetto/trace/ftrace/cpm_trace.proto
+
// Begin of protos/perfetto/trace/ftrace/cpuhp.proto
message CpuhpExitFtraceEvent {
@@ -11413,6 +11423,7 @@
SchedWakeupTaskAttrFtraceEvent sched_wakeup_task_attr = 540;
DevfreqFrequencyFtraceEvent devfreq_frequency = 541;
KprobeEvent kprobe_event = 542;
+ ParamSetValueCpmFtraceEvent param_set_value_cpm = 543;
}
}
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index 6d58286..edb6b95 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -113,7 +113,7 @@
'chrome/util': ['cr'],
'intervals': ['interval'],
'graphs': ['graph'],
- 'slices': ['slice'],
+ 'slices': ['slice', 'thread_slice', 'process_slice'],
'linux': ['cpu', 'memory'],
'stacks': ['cpu_profiling'],
}
@@ -121,8 +121,6 @@
# Allows for nonstandard object names.
OBJECT_NAME_ALLOWLIST = {
'graphs/partition.sql': ['tree_structural_partition_by_group'],
- 'slices/with_context.sql': ['process_slice', 'thread_slice'],
- 'slices/cpu_time.sql': ['thread_slice_cpu_time', 'thread_slice_cpu_cycles']
}
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index a2da25e..728882b 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index dc6d6b9..8c99f49 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -536,3 +536,4 @@
pixel_mm/pixel_mm_kswapd_done
sched/sched_wakeup_task_attr
devfreq/devfreq_frequency
+cpm_trace/param_set_value_cpm
diff --git a/src/trace_processor/importers/common/tracks.h b/src/trace_processor/importers/common/tracks.h
index 3245a6b..ce4fbcd 100644
--- a/src/trace_processor/importers/common/tracks.h
+++ b/src/trace_processor/importers/common/tracks.h
@@ -56,6 +56,7 @@
F(legacy_chrome_global_instants) \
F(linux_device_frequency) \
F(linux_rpm) \
+ F(pixel_cpm_trace) \
F(pkvm_hypervisor) \
F(softirq_counter) \
F(thread) \
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 1d31d7d..53085f9 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
namespace trace_processor {
namespace {
-std::array<FtraceMessageDescriptor, 543> descriptors{{
+std::array<FtraceMessageDescriptor, 544> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -6006,6 +6006,16 @@
{"type", ProtoSchemaType::kInt32},
},
},
+ {
+ "param_set_value_cpm",
+ 3,
+ {
+ {},
+ {"body", ProtoSchemaType::kString},
+ {"value", ProtoSchemaType::kUint32},
+ {"timestamp", ProtoSchemaType::kInt64},
+ },
+ },
}};
} // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 51ab54d..d9b130e 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -71,6 +71,7 @@
#include "protos/perfetto/trace/ftrace/bcl_exynos.pbzero.h"
#include "protos/perfetto/trace/ftrace/binder.pbzero.h"
#include "protos/perfetto/trace/ftrace/cma.pbzero.h"
+#include "protos/perfetto/trace/ftrace/cpm_trace.pbzero.h"
#include "protos/perfetto/trace/ftrace/cpuhp.pbzero.h"
#include "protos/perfetto/trace/ftrace/cros_ec.pbzero.h"
#include "protos/perfetto/trace/ftrace/dcvsh.pbzero.h"
@@ -1372,6 +1373,10 @@
ParseKprobe(ts, pid, fld_bytes);
break;
}
+ case FtraceEvent::kParamSetValueCpmFieldNumber: {
+ ParseParamSetValueCpm(fld_bytes);
+ break;
+ }
default:
break;
}
@@ -3836,4 +3841,16 @@
track_id);
}
+void FtraceParser::ParseParamSetValueCpm(protozero::ConstBytes blob) {
+ protos::pbzero::ParamSetValueCpmFtraceEvent::Decoder event(blob);
+ TrackTracker::DimensionsBuilder dims_builder =
+ context_->track_tracker->CreateDimensionsBuilder();
+ // Store event body which denotes the name of the track.
+ dims_builder.AppendName(context_->storage->InternString(event.body()));
+ TrackId track_id = context_->track_tracker->InternTrack(
+ tracks::pixel_cpm_trace, std::move(dims_builder).Build());
+ context_->event_tracker->PushCounter(static_cast<int64_t>(event.timestamp()),
+ event.value(), track_id);
+}
+
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 335649d..6a08f65 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -317,6 +317,7 @@
void ParseGoogleIccEvent(int64_t timestamp, protozero::ConstBytes);
void ParseGoogleIrmEvent(int64_t timestamp, protozero::ConstBytes);
void ParseDeviceFrequency(int64_t ts, protozero::ConstBytes blob);
+ void ParseParamSetValueCpm(protozero::ConstBytes blob);
TraceProcessorContext* context_;
RssStatTracker rss_stat_tracker_;
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index 5c6c2e3..50a85da 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -43,6 +43,7 @@
#include "src/trace_processor/util/status_macros.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
+#include "protos/perfetto/trace/ftrace/cpm_trace.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
#include "protos/perfetto/trace/ftrace/power.pbzero.h"
@@ -279,6 +280,11 @@
TokenizeFtraceThermalExynosAcpmBulk(cpu, std::move(event),
std::move(state));
return;
+ } else if (PERFETTO_UNLIKELY(event_id ==
+ protos::pbzero::FtraceEvent::
+ kParamSetValueCpmFieldNumber)) {
+ TokenizeFtraceParamSetValueCpm(cpu, std::move(event), std::move(state));
+ return;
}
auto timestamp = context_->clock_tracker->ToTraceTime(
@@ -448,19 +454,12 @@
RefPtr<PacketSequenceStateGeneration> state) {
// Special handling of valid gpu_work_period tracepoint events which contain
// timestamp values for the GPU time period nested inside the event data.
- const uint8_t* data = event.data();
- const size_t length = event.length();
-
- ProtoDecoder decoder(data, length);
- auto ts_field =
- decoder.FindField(protos::pbzero::FtraceEvent::kGpuWorkPeriodFieldNumber);
- if (!ts_field.valid()) {
- context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
- return;
- }
+ auto ts_field = GetFtraceEventField(
+ protos::pbzero::FtraceEvent::kGpuWorkPeriodFieldNumber, event);
+ if (!ts_field.has_value()) return;
protos::pbzero::GpuWorkPeriodFtraceEvent::Decoder gpu_work_event(
- ts_field.data(), ts_field.size());
+ ts_field.value().data(), ts_field.value().size());
if (!gpu_work_event.has_start_time_ns()) {
context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
return;
@@ -490,19 +489,13 @@
RefPtr<PacketSequenceStateGeneration> state) {
// Special handling of valid thermal_exynos_acpm_bulk tracepoint events which
// contains the right timestamp value nested inside the event data.
- const uint8_t* data = event.data();
- const size_t length = event.length();
-
- ProtoDecoder decoder(data, length);
- auto ts_field = decoder.FindField(
- protos::pbzero::FtraceEvent::kThermalExynosAcpmBulkFieldNumber);
- if (!ts_field.valid()) {
- context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
- return;
- }
+ auto ts_field = GetFtraceEventField(
+ protos::pbzero::FtraceEvent::kThermalExynosAcpmBulkFieldNumber, event);
+ if (!ts_field.has_value()) return;
protos::pbzero::ThermalExynosAcpmBulkFtraceEvent::Decoder
- thermal_exynos_acpm_bulk_event(ts_field.data(), ts_field.size());
+ thermal_exynos_acpm_bulk_event(ts_field.value().data(),
+ ts_field.value().size());
if (!thermal_exynos_acpm_bulk_event.has_timestamp()) {
context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
return;
@@ -513,5 +506,42 @@
std::move(state), context_->machine_id());
}
+void FtraceTokenizer::TokenizeFtraceParamSetValueCpm(
+ uint32_t cpu, TraceBlobView event,
+ RefPtr<PacketSequenceStateGeneration> state) {
+ // Special handling of valid param_set_value_cpm tracepoint events which
+ // contains the right timestamp value nested inside the event data.
+ auto ts_field = GetFtraceEventField(
+ protos::pbzero::FtraceEvent::kParamSetValueCpmFieldNumber, event);
+ if (!ts_field.has_value()) return;
+
+ protos::pbzero::ParamSetValueCpmFtraceEvent::Decoder
+ param_set_value_cpm_event(ts_field.value().data(),
+ ts_field.value().size());
+ if (!param_set_value_cpm_event.has_timestamp()) {
+ context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
+ return;
+ }
+ int64_t timestamp =
+ static_cast<int64_t>(param_set_value_cpm_event.timestamp());
+ context_->sorter->PushFtraceEvent(cpu, timestamp, std::move(event),
+ std::move(state), context_->machine_id());
+}
+
+std::optional<protozero::Field> FtraceTokenizer::GetFtraceEventField(
+ uint32_t event_id, const TraceBlobView& event) {
+ // Extract ftrace event field by decoding event trace blob.
+ const uint8_t* data = event.data();
+ const size_t length = event.length();
+
+ ProtoDecoder decoder(data, length);
+ auto ts_field = decoder.FindField(event_id);
+ if (!ts_field.valid()) {
+ context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
+ return std::nullopt;
+ }
+ return ts_field;
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
index 6aff47d..d8780b1 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
@@ -17,6 +17,7 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_TOKENIZER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_TOKENIZER_H_
+#include <optional>
#include <vector>
#include "perfetto/trace_processor/trace_blob_view.h"
@@ -68,6 +69,11 @@
uint32_t cpu,
TraceBlobView event,
RefPtr<PacketSequenceStateGeneration> state);
+ void TokenizeFtraceParamSetValueCpm(
+ uint32_t cpu, TraceBlobView event,
+ RefPtr<PacketSequenceStateGeneration> state);
+ std::optional<protozero::Field> GetFtraceEventField(
+ uint32_t event_id, const TraceBlobView& event);
void DlogWithLimit(const base::Status& status) {
static std::atomic<uint32_t> dlog_count(0);
diff --git a/src/trace_processor/metrics/sql/trace_metadata.sql b/src/trace_processor/metrics/sql/trace_metadata.sql
index 90c112d..1e45573 100644
--- a/src/trace_processor/metrics/sql/trace_metadata.sql
+++ b/src/trace_processor/metrics/sql/trace_metadata.sql
@@ -23,6 +23,9 @@
'android_build_fingerprint', (
SELECT str_value FROM metadata WHERE name = 'android_build_fingerprint'
),
+ 'android_device_manufacturer', (
+ SELECT str_value FROM metadata WHERE name = 'android_device_manufacturer'
+ ),
'statsd_triggering_subscription_id', (
SELECT int_value FROM metadata
WHERE name = 'statsd_triggering_subscription_id'
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql
index b882627..ebc8f8b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql
@@ -19,7 +19,9 @@
-- Step name (ChromeLatencyInfo.step).
step STRING,
-- Input type.
- input_type STRING
+ input_type STRING,
+ -- Start time of the parent Chrome scheduler task (if any) of this step.
+ task_start_time_ts INT
) AS
SELECT
EXTRACT_ARG(thread_slice.arg_set_id, 'chrome_latency_info.trace_id') AS latency_id,
@@ -28,7 +30,8 @@
dur,
utid,
EXTRACT_ARG(thread_slice.arg_set_id, 'chrome_latency_info.step') AS step,
- EXTRACT_ARG(thread_slice.arg_set_id, 'chrome_latency_info.input_type') AS input_type
+ EXTRACT_ARG(thread_slice.arg_set_id, 'chrome_latency_info.input_type') AS input_type,
+ ts - (EXTRACT_ARG(thread_slice.arg_set_id, 'current_task.event_offset_from_task_start_time_us') * 1000) AS task_start_time_ts
FROM
thread_slice
WHERE
@@ -69,7 +72,9 @@
-- Step name (ChromeLatencyInfo.step).
step STRING,
-- Input type.
- input_type STRING
+ input_type STRING,
+ -- Start time of the parent Chrome scheduler task (if any) of this step.
+ task_start_time_ts INT
) AS
SELECT
latency_id,
@@ -78,7 +83,8 @@
dur,
utid,
step,
- chrome_inputs.input_type AS input_type
+ chrome_inputs.input_type AS input_type,
+ task_start_time_ts
FROM
chrome_inputs
LEFT JOIN
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
index e627991..80696a1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
@@ -93,36 +93,58 @@
GROUP BY utid;
-- Time the thread spent each state in a given interval.
+--
+-- This function is only designed to run over a small number of intervals
+-- (10-100 at most). It will be *very slow* for large sets of intervals.
+--
+-- Specifically for any non-trivial subset of thread slices, prefer using
+-- `thread_slice_time_in_state` in the `slices.time_in_state` module for this
+-- purpose instead.
CREATE PERFETTO FUNCTION sched_time_in_state_for_thread_in_interval(
-- The start of the interval.
ts INT,
-- The duration of the interval.
dur INT,
-- The utid of the thread.
- utid INT)
+ utid INT
+)
RETURNS TABLE(
- -- Thread state (from the `thread_state` table).
- -- Use `sched_state_to_human_readable_string` function to get full name.
- state INT,
+ -- The scheduling state (from the `thread_state` table).
+ --
+ -- Use the `sched_state_to_human_readable_string` function in the `sched`
+ -- package to get full name.
+ state STRING,
-- A (posssibly NULL) boolean indicating, if the device was in uninterruptible
-- sleep, if it was an IO sleep.
io_wait BOOL,
- -- Some states can specify the blocked function. Usually NULL.
+ -- If the `state` is uninterruptible sleep, `io_wait` indicates if it was
+ -- an IO sleep. Will be null if `state` is *not* uninterruptible sleep or if
+ -- we cannot tell if it was an IO sleep or not.
+ --
+ -- Only available on Android when
+ -- `sched/sched_blocked_reason` ftrace tracepoint is enabled.
blocked_function INT,
- -- Total time spent with this state, cpu and blocked function.
- dur INT) AS
+ -- The duration of time the threads slice spent for each
+ -- (state, io_wait, blocked_function) tuple.
+ dur INT
+) AS
SELECT
state,
io_wait,
blocked_function,
sum(ii.dur) as dur
FROM thread_state
-JOIN
- (SELECT * FROM _interval_intersect_single!(
+JOIN (
+ SELECT *
+ FROM _interval_intersect_single!(
$ts, $dur,
- (SELECT id, ts, dur
- FROM thread_state
- WHERE utid = $utid AND dur > 0))) ii USING (id)
+ (
+ SELECT id, ts, dur
+ FROM thread_state
+ WHERE utid = $utid AND dur > 0
+ )
+ )
+) ii USING (id)
GROUP BY 1, 2, 3
ORDER BY 4 DESC;
@@ -137,7 +159,7 @@
RETURNS TABLE(
-- Thread state (from the `thread_state` table).
-- Use `sched_state_to_human_readable_string` function to get full name.
- state INT,
+ state STRING,
-- A (posssibly NULL) boolean indicating, if the device was in uninterruptible
-- sleep, if it was an IO sleep.
io_wait BOOL,
diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn
index 2e58615..4d616ea 100644
--- a/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn
@@ -21,6 +21,7 @@
"flow.sql",
"hierarchy.sql",
"slices.sql",
+ "time_in_state.sql",
"with_context.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/slices/time_in_state.sql
new file mode 100644
index 0000000..142a664
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/slices/time_in_state.sql
@@ -0,0 +1,77 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE intervals.intersect;
+INCLUDE PERFETTO MODULE slices.with_context;
+
+-- For each thread slice, returns the sum of the time it spent in various
+-- scheduling states.
+--
+-- Requires scheduling data to be available in the trace.
+CREATE PERFETTO TABLE thread_slice_time_in_state(
+ -- Id of a slice. Alias of `slice.id`.
+ id INT,
+ -- Name of the slice.
+ name STRING,
+ -- Id of the thread the slice is running on. Alias of `thread.id`.
+ utid INT,
+ -- Name of the thread.
+ thread_name STRING,
+ -- Id of the process the slice is running on. Alias of `process.id`.
+ upid INT,
+ -- Name of the process.
+ process_name STRING,
+ -- The scheduling state (from the `thread_state` table).
+ --
+ -- Use the `sched_state_to_human_readable_string` function in the `sched`
+ -- package to get full name.
+ state STRING,
+ -- If the `state` is uninterruptible sleep, `io_wait` indicates if it was
+ -- an IO sleep. Will be null if `state` is *not* uninterruptible sleep or if
+ -- we cannot tell if it was an IO sleep or not.
+ --
+ -- Only available on Android when
+ -- `sched/sched_blocked_reason` ftrace tracepoint is enabled.
+ io_wait BOOL,
+ -- If in uninterruptible sleep (D), the kernel function on which was blocked.
+ -- Only available on userdebug Android builds when
+ -- `sched/sched_blocked_reason` ftrace tracepoint is enabled.
+ blocked_function INT,
+ -- The duration of time the threads slice spent for each
+ -- (state, io_wait, blocked_function) tuple.
+ dur INT
+) AS
+SELECT
+ ii.id_0 AS id,
+ ts.name,
+ ts.utid,
+ ts.thread_name,
+ ts.upid,
+ ts.process_name,
+ tstate.state,
+ tstate.io_wait,
+ tstate.blocked_function,
+ SUM(ii.dur) AS dur
+FROM _interval_intersect!(
+ (
+ (SELECT * FROM thread_slice WHERE utid > 0 AND dur > 0),
+ (SELECT * FROM thread_state WHERE dur > 0)
+ ),
+ (utid)
+) ii
+JOIN thread_slice ts ON ts.id = ii.id_0
+JOIN thread_state tstate ON tstate.id = ii.id_1
+GROUP BY ii.id_0, tstate.state, tstate.io_wait, tstate.blocked_function
+ORDER BY ii.id_0;
diff --git a/src/trace_processor/util/proto_to_args_parser.cc b/src/trace_processor/util/proto_to_args_parser.cc
index 42639ea..da2f15c 100644
--- a/src/trace_processor/util/proto_to_args_parser.cc
+++ b/src/trace_processor/util/proto_to_args_parser.cc
@@ -482,7 +482,11 @@
pool_.descriptors()[*opt_enum_descriptor_idx].FindEnumString(value);
if (!opt_enum_string) {
// Fall back to the integer representation of the field.
- delegate.AddInteger(key_prefix_, value);
+ // We add the string representation of the int value here in order that
+ // EXTRACT_ARG() should return consistent types under error conditions and
+ // that CREATE PERFETTO TABLE AS EXTRACT_ARG(...) should be generally safe
+ // to use.
+ delegate.AddString(key_prefix_, std::to_string(value));
return base::OkStatus();
}
delegate.AddString(
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index b152d02..2cd9b20 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -1294,6 +1294,22 @@
kUnsetFtraceId,
112,
kUnsetSize},
+ {"param_set_value_cpm",
+ "cpm_trace",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "body", 1, ProtoSchemaType::kString,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "value", 2, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "timestamp", 3, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 543,
+ kUnsetSize},
{"cpuhp_exit",
"cpuhp",
{
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpm_trace/param_set_value_cpm/format b/src/traced/probes/ftrace/test/data/synthetic/events/cpm_trace/param_set_value_cpm/format
new file mode 100644
index 0000000..a7a2876
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpm_trace/param_set_value_cpm/format
@@ -0,0 +1,13 @@
+name: param_set_value_cpm
+ID: 1125
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:__data_loc char[] body; offset:8; size:4; signed:0;
+ field:unsigned int value; offset:12; size:4; signed:0;
+ field:long long timestamp; offset:16; size:8; signed:1;
+
+print fmt: "%s state=%u timestamp=%lld", __get_str(body), REC->value, REC->timestamp
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
index bb42920..083354a 100755
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -393,6 +393,36 @@
-2143831735395280246,"GESTURE_SCROLL_UPDATE_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
"""))
+ def test_task_start_time(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.input;
+
+ SELECT
+ latency_id,
+ step,
+ task_start_time_ts
+ FROM chrome_input_pipeline_steps
+ ORDER BY latency_id
+ LIMIT 10;
+ """,
+ # STEP_SEND_INPUT_EVENT_UI does not run in a task,
+ # so its task_start_time_ts will be NULL.
+ out=Csv("""
+ "latency_id","step","task_start_time_ts"
+ -2143831735395280256,"STEP_SEND_INPUT_EVENT_UI","[NULL]"
+ -2143831735395280256,"STEP_HANDLE_INPUT_EVENT_IMPL",1292554143003210
+ -2143831735395280256,"STEP_DID_HANDLE_INPUT_AND_OVERSCROLL",1292554153539210
+ -2143831735395280256,"STEP_GESTURE_EVENT_HANDLED",1292554154651257
+ -2143831735395280254,"STEP_SEND_INPUT_EVENT_UI","[NULL]"
+ -2143831735395280254,"STEP_HANDLE_INPUT_EVENT_IMPL",1292554155188210
+ -2143831735395280254,"STEP_DID_HANDLE_INPUT_AND_OVERSCROLL",1292554164359210
+ -2143831735395280254,"STEP_GESTURE_EVENT_HANDLED",1292554165141257
+ -2143831735395280250,"STEP_SEND_INPUT_EVENT_UI","[NULL]"
+ -2143831735395280250,"STEP_HANDLE_INPUT_EVENT_IMPL",1292554131865210
+ """))
+
def test_chrome_coalesced_inputs(self):
return DiffTestBlueprint(
trace=DataPath('scroll_m131.pftrace'),
diff --git a/test/trace_processor/diff_tests/stdlib/slices/tests.py b/test/trace_processor/diff_tests/stdlib/slices/tests.py
index 625659e..88b430f 100644
--- a/test/trace_processor/diff_tests/stdlib/slices/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/slices/tests.py
@@ -177,3 +177,27 @@
8,46926
9,17865
"""))
+
+ def test_thread_slice_time_in_state(self):
+ return DiffTestBlueprint(
+ trace=DataPath('example_android_trace_30s.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE slices.time_in_state;
+
+ SELECT id, name, state, io_wait, blocked_function, dur
+ FROM thread_slice_time_in_state
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "id","name","state","io_wait","blocked_function","dur"
+ 0,"Deoptimization JIT inline cache","Running","[NULL]","[NULL]",178646
+ 1,"Deoptimization JIT inline cache","Running","[NULL]","[NULL]",119740
+ 2,"Lock contention on thread list lock (owner tid: 0)","Running","[NULL]","[NULL]",58073
+ 3,"Lock contention on thread list lock (owner tid: 0)","Running","[NULL]","[NULL]",98698
+ 3,"Lock contention on thread list lock (owner tid: 0)","S","[NULL]","[NULL]",56302
+ 4,"monitor contention with owner InputReader (1421) at void com.android.server.power.PowerManagerService.acquireWakeLockInternal(android.os.IBinder, int, java.lang.String, java.lang.String, android.os.WorkSource, java.lang.String, int, int)(PowerManagerService.java:1018) waiters=0 blocking from void com.android.server.power.PowerManagerService.handleSandman()(PowerManagerService.java:2280)","Running","[NULL]","[NULL]",121979
+ 4,"monitor contention with owner InputReader (1421) at void com.android.server.power.PowerManagerService.acquireWakeLockInternal(android.os.IBinder, int, java.lang.String, java.lang.String, android.os.WorkSource, java.lang.String, int, int)(PowerManagerService.java:1018) waiters=0 blocking from void com.android.server.power.PowerManagerService.handleSandman()(PowerManagerService.java:2280)","S","[NULL]","[NULL]",51198
+ 5,"monitor contention with owner main (1204) at void com.android.server.am.ActivityManagerService.onWakefulnessChanged(int)(ActivityManagerService.java:7244) waiters=0 blocking from void com.android.server.am.ActivityManagerService$3.handleMessage(android.os.Message)(ActivityManagerService.java:1704)","Running","[NULL]","[NULL]",45000
+ 5,"monitor contention with owner main (1204) at void com.android.server.am.ActivityManagerService.onWakefulnessChanged(int)(ActivityManagerService.java:7244) waiters=0 blocking from void com.android.server.am.ActivityManagerService$3.handleMessage(android.os.Message)(ActivityManagerService.java:1704)","S","[NULL]","[NULL]",20164377
+ 6,"monitor contention with owner main (1204) at void com.android.server.am.ActivityManagerService.onWakefulnessChanged(int)(ActivityManagerService.java:7244) waiters=1 blocking from com.android.server.wm.ActivityTaskManagerInternal$SleepToken com.android.server.am.ActivityTaskManagerService.acquireSleepToken(java.lang.String, int)(ActivityTaskManagerService.java:5048)","Running","[NULL]","[NULL]",35104
+ """))
diff --git a/ui/src/common/gcs_uploader.ts b/ui/src/base/gcs_uploader.ts
similarity index 93%
rename from ui/src/common/gcs_uploader.ts
rename to ui/src/base/gcs_uploader.ts
index d2012ee..b2f2bd5 100644
--- a/ui/src/common/gcs_uploader.ts
+++ b/ui/src/base/gcs_uploader.ts
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer} from '../base/deferred';
-import {Time} from '../base/time';
-import {TraceFileStream} from '../core/trace_stream';
+import {defer} from './deferred';
+import {Time} from './time';
export const BUCKET_NAME = 'perfetto-ui-data';
export const MIME_JSON = 'application/json; charset=utf-8';
@@ -184,13 +183,16 @@
* @returns A hex-encoded string containing the hash of the file.
*/
async function hashFileStreaming(file: Blob): Promise<string> {
- const fileStream = new TraceFileStream(file);
+ const CHUNK_SIZE = 32 * 1024 * 1024; // 32MB
+ const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let chunkDigests = '';
- for (;;) {
- const chunk = await fileStream.readChunk();
- const digest = await crypto.subtle.digest('SHA-1', chunk.data);
+
+ for (let i = 0; i < totalChunks; i++) {
+ const start = i * CHUNK_SIZE;
+ const end = Math.min(start + CHUNK_SIZE, file.size);
+ const chunk = await file.slice(start, end).arrayBuffer();
+ const digest = await crypto.subtle.digest('SHA-1', chunk);
chunkDigests += digestToHex(digest);
- if (chunk.eof) break;
}
return sha1(chunkDigests);
}
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index c30256a..d0ee370 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -22,6 +22,7 @@
// - Be approved by one of Perfetto UI owners.
export const defaultPlugins = [
'com.android.GpuWorkPeriod',
+ 'com.google.PixelCpmTrace',
'com.google.PixelMemory',
'dev.perfetto.AndroidBinderVizPlugin',
'dev.perfetto.AndroidClientServer',
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index dbe4a02..affbe70 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -15,7 +15,7 @@
import m from 'mithril';
import {ErrorDetails} from '../base/logging';
import {EXTENSION_URL} from '../common/recordingV2/recording_utils';
-import {GcsUploader} from '../common/gcs_uploader';
+import {GcsUploader} from '../base/gcs_uploader';
import {RECORDING_V2_FLAG} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
import {VERSION} from '../gen/perfetto_version';
diff --git a/ui/src/frontend/permalink.ts b/ui/src/frontend/permalink.ts
index 73c9a9d..b69916f 100644
--- a/ui/src/frontend/permalink.ts
+++ b/ui/src/frontend/permalink.ts
@@ -24,7 +24,7 @@
MIME_BINARY,
MIME_JSON,
GcsUploader,
-} from '../common/gcs_uploader';
+} from '../base/gcs_uploader';
import {
SERIALIZED_STATE_VERSION,
SerializedAppState,
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index aed42e3..6d7a579 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -52,7 +52,7 @@
import {EtwSettings} from './recording/etw_settings';
import {AppImpl} from '../core/app_impl';
import {RecordingManager} from '../controller/recording_manager';
-import {BUCKET_NAME, GcsUploader, MIME_JSON} from '../common/gcs_uploader';
+import {BUCKET_NAME, GcsUploader, MIME_JSON} from '../base/gcs_uploader';
import {showModal} from '../widgets/modal';
import {CopyableLink} from '../widgets/copyable_link';
diff --git a/ui/src/plugins/com.example.Skeleton/index.ts b/ui/src/plugins/com.example.Skeleton/index.ts
index d737940..5746950 100644
--- a/ui/src/plugins/com.example.Skeleton/index.ts
+++ b/ui/src/plugins/com.example.Skeleton/index.ts
@@ -16,19 +16,12 @@
import {App} from '../../public/app';
import {MetricVisualisation} from '../../public/plugin';
import {PerfettoPlugin} from '../../public/plugin';
-import {createStore, Store} from '../../base/store';
-
-interface State {
- foo: string;
-}
// SKELETON: Rename this class to match your plugin.
export default class implements PerfettoPlugin {
// SKELETON: Update pluginId to match the directory of the plugin.
static readonly id = 'com.example.Skeleton';
- private store: Store<State> = createStore({foo: 'foo'});
-
/**
* This hook is called when the plugin is activated manually, or when the UI
* starts up with this plugin enabled. This is typically before a trace has
@@ -38,8 +31,8 @@
* This hook should be used for adding commands that don't depend on the
* trace.
*/
- static onActivate(_: App): void {
- //
+ static onActivate(app: App): void {
+ console.log('SkeletonPlugin::onActivate()', app.pluginId);
}
/**
@@ -50,26 +43,23 @@
* It should not be used for finding tracks from other plugins as there is no
* guarantee those tracks will have been added yet.
*/
- async onTraceLoad(ctx: Trace): Promise<void> {
- this.store = ctx.mountStore((_: unknown): State => {
- return {foo: 'bar'};
- });
-
- this.store.edit((state) => {
- state.foo = 'baz';
- });
+ async onTraceLoad(trace: Trace): Promise<void> {
+ console.log('SkeletonPlugin::onTraceLoad()', trace.traceInfo.traceTitle);
// This is an example of how to access the pluginArgs pushed by the
// postMessage when deep-linking to the UI.
- if (ctx.openerPluginArgs !== undefined) {
- console.log(`Postmessage args for ${ctx.pluginId}`, ctx.openerPluginArgs);
+ if (trace.openerPluginArgs !== undefined) {
+ console.log(
+ `Postmessage args for ${trace.pluginId}`,
+ trace.openerPluginArgs,
+ );
}
/**
- * This hook is called when the trace has finished loading, and all plugins
- * have returned from their onTraceLoad calls. The UI can be considered
- * 'ready' at this point. All tracks and commands should now be available,
- * and the timeline is ready to use.
+ * The 'traceready' event is fired when the trace has finished loading, and
+ * all plugins have returned from their onTraceLoad calls. The UI can be
+ * considered 'ready' at this point. All tracks and commands should now be
+ * available, and the timeline is ready to use.
*
* This is where any automations should be done - things that you would
* usually do manually after the trace has loaded but you'd like to automate
@@ -94,8 +84,8 @@
* TODO(stevegolton): Update this comment if the semantics of track adding
* changes.
*/
- ctx.addEventListener('traceready', async () => {
- console.log('onTraceReady called');
+ trace.addEventListener('traceready', async () => {
+ console.log('SkeletonPlugin::traceready');
});
}
diff --git a/ui/src/plugins/com.google.PixelCpmTrace/OWNERS b/ui/src/plugins/com.google.PixelCpmTrace/OWNERS
new file mode 100644
index 0000000..9833dcb
--- /dev/null
+++ b/ui/src/plugins/com.google.PixelCpmTrace/OWNERS
@@ -0,0 +1,2 @@
+sashwinbalaji@google.com
+spirani@google.com
diff --git a/ui/src/plugins/com.google.PixelCpmTrace/index.ts b/ui/src/plugins/com.google.PixelCpmTrace/index.ts
new file mode 100644
index 0000000..dcb3934
--- /dev/null
+++ b/ui/src/plugins/com.google.PixelCpmTrace/index.ts
@@ -0,0 +1,74 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {createQueryCounterTrack} from '../../public/lib/tracks/query_counter_track';
+import {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {COUNTER_TRACK_KIND} from '../../public/track_kinds';
+import {TrackNode} from '../../public/workspace';
+import {NUM, STR} from '../../trace_processor/query_result';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.google.PixelCpmTrace';
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const group = new TrackNode({
+ title: 'Central Power Manager',
+ isSummary: true,
+ });
+
+ const {engine} = ctx;
+ const result = await engine.query(`
+ select
+ id AS trackId,
+ extract_arg(dimension_arg_set_id, 'name') AS trackName
+ FROM track
+ WHERE classification = 'pixel_cpm_trace'
+ ORDER BY trackName
+ `);
+
+ const it = result.iter({trackId: NUM, trackName: STR});
+ for (let group_added = false; it.valid(); it.next()) {
+ const {trackId, trackName} = it;
+ const uri = `/cpm_trace_${trackName}`;
+ const track = await createQueryCounterTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource: `
+ select ts, value
+ from counter
+ where track_id = ${trackId}
+ `,
+ columns: ['ts', 'value'],
+ },
+ columns: {ts: 'ts', value: 'value'},
+ });
+ ctx.tracks.registerTrack({
+ uri,
+ title: trackName,
+ tags: {
+ kind: COUNTER_TRACK_KIND,
+ trackIds: [trackId],
+ },
+ track,
+ });
+ group.addChildInOrder(new TrackNode({uri, title: trackName}));
+ if (!group_added) {
+ ctx.workspace.addChildInOrder(group);
+ group_added = true;
+ }
+ }
+ }
+}
diff --git a/ui/src/widgets/flamegraph.ts b/ui/src/widgets/flamegraph.ts
index a7252f5..3c831dd 100644
--- a/ui/src/widgets/flamegraph.ts
+++ b/ui/src/widgets/flamegraph.ts
@@ -966,7 +966,7 @@
}
return addFilter(state, {
kind: 'SHOW_STACK',
- filter: filter.split(': ', 2)[1],
+ filter: filter,
});
}