Merge "Add dim bounds proto to perfetto" into main
diff --git a/Android.bp b/Android.bp
index b820aef..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",
@@ -13599,19 +13609,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql",
"src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql",
"src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/args.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/counters.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/metadata.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/slices.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
"src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql",
"src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql",
"src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql",
"src/trace_processor/perfetto_sql/stdlib/graphs/dominator_tree.sql",
@@ -13656,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",
@@ -15139,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",
@@ -16479,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 a23b86c..d4f1f1b 100644
--- a/BUILD
+++ b/BUILD
@@ -502,6 +502,72 @@
linkstatic = True,
)
+# GN target: //src/traceconv:libpprofbuilder
+perfetto_cc_library(
+ name = "libpprofbuilder",
+ srcs = [
+ ":src_profiling_deobfuscator",
+ ":src_profiling_symbolizer_symbolize_database",
+ ":src_profiling_symbolizer_symbolizer",
+ ":src_trace_processor_util_build_id",
+ ":src_traceconv_pprofbuilder",
+ ":src_traceconv_utils",
+ ],
+ hdrs = [
+ ":include_perfetto_base_base",
+ ":include_perfetto_ext_base_base",
+ ":include_perfetto_profiling_pprof_builder",
+ ":include_perfetto_protozero_protozero",
+ ":include_perfetto_public_abi_base",
+ ":include_perfetto_public_base",
+ ":include_perfetto_public_protozero",
+ ":include_perfetto_trace_processor_basic_types",
+ ":include_perfetto_trace_processor_storage",
+ ":include_perfetto_trace_processor_trace_processor",
+ ],
+ visibility = PERFETTO_CONFIG.public_visibility,
+ deps = [
+ ":protos_perfetto_common_zero",
+ ":protos_perfetto_config_android_zero",
+ ":protos_perfetto_config_ftrace_zero",
+ ":protos_perfetto_config_gpu_zero",
+ ":protos_perfetto_config_inode_file_zero",
+ ":protos_perfetto_config_interceptors_zero",
+ ":protos_perfetto_config_power_zero",
+ ":protos_perfetto_config_process_stats_zero",
+ ":protos_perfetto_config_profiling_zero",
+ ":protos_perfetto_config_statsd_zero",
+ ":protos_perfetto_config_sys_stats_zero",
+ ":protos_perfetto_config_system_info_zero",
+ ":protos_perfetto_config_track_event_zero",
+ ":protos_perfetto_config_zero",
+ ":protos_perfetto_trace_android_winscope_common_zero",
+ ":protos_perfetto_trace_android_winscope_regular_zero",
+ ":protos_perfetto_trace_android_zero",
+ ":protos_perfetto_trace_chrome_zero",
+ ":protos_perfetto_trace_etw_zero",
+ ":protos_perfetto_trace_filesystem_zero",
+ ":protos_perfetto_trace_ftrace_zero",
+ ":protos_perfetto_trace_gpu_zero",
+ ":protos_perfetto_trace_interned_data_zero",
+ ":protos_perfetto_trace_minimal_zero",
+ ":protos_perfetto_trace_non_minimal_zero",
+ ":protos_perfetto_trace_perfetto_zero",
+ ":protos_perfetto_trace_power_zero",
+ ":protos_perfetto_trace_profiling_zero",
+ ":protos_perfetto_trace_ps_zero",
+ ":protos_perfetto_trace_statsd_zero",
+ ":protos_perfetto_trace_sys_stats_zero",
+ ":protos_perfetto_trace_system_info_zero",
+ ":protos_perfetto_trace_track_event_zero",
+ ":protos_perfetto_trace_translation_zero",
+ ":protos_third_party_pprof_zero",
+ ":protozero",
+ ":src_trace_processor_containers_containers",
+ ] + PERFETTO_CONFIG.deps.zlib,
+ linkstatic = True,
+)
+
# GN target: //test:client_api_example
perfetto_cc_binary(
name = "client_api_example",
@@ -2974,19 +3040,6 @@
srcs = glob(["src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql"]),
)
-# GN target: //src/trace_processor/perfetto_sql/stdlib/common:common
-perfetto_filegroup(
- name = "src_trace_processor_perfetto_sql_stdlib_common_common",
- srcs = [
- "src/trace_processor/perfetto_sql/stdlib/common/args.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/counters.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/metadata.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/slices.sql",
- "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
- ],
-)
-
# GN target: //src/trace_processor/perfetto_sql/stdlib/counters:counters
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_counters_counters",
@@ -2995,19 +3048,6 @@
],
)
-# GN target: //src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common:common
-perfetto_filegroup(
- name = "src_trace_processor_perfetto_sql_stdlib_deprecated_v42_common_common",
- srcs = [
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql",
- "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql",
- ],
-)
-
# GN target: //src/trace_processor/perfetto_sql/stdlib/export:export
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_export_export",
@@ -3145,6 +3185,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",
],
)
@@ -3242,9 +3283,7 @@
":src_trace_processor_perfetto_sql_stdlib_android_winscope_winscope",
":src_trace_processor_perfetto_sql_stdlib_callstacks_callstacks",
":src_trace_processor_perfetto_sql_stdlib_chrome_chrome_sql",
- ":src_trace_processor_perfetto_sql_stdlib_common_common",
":src_trace_processor_perfetto_sql_stdlib_counters_counters",
- ":src_trace_processor_perfetto_sql_stdlib_deprecated_v42_common_common",
":src_trace_processor_perfetto_sql_stdlib_export_export",
":src_trace_processor_perfetto_sql_stdlib_graphs_graphs",
":src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
@@ -5572,6 +5611,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",
@@ -6890,74 +6930,6 @@
PERFETTO_CONFIG.deps.demangle_wrapper,
)
-# GN target: //src/traceconv:libpprofbuilder
-perfetto_cc_library(
- name = "libpprofbuilder",
- srcs = [
- ":src_profiling_deobfuscator",
- ":src_profiling_symbolizer_symbolize_database",
- ":src_profiling_symbolizer_symbolizer",
- ":src_trace_processor_util_build_id",
- ":src_traceconv_pprofbuilder",
- ":src_traceconv_utils",
- ],
- hdrs = [
- ":include_perfetto_base_base",
- ":include_perfetto_ext_base_base",
- ":include_perfetto_profiling_pprof_builder",
- ":include_perfetto_protozero_protozero",
- ":include_perfetto_public_abi_base",
- ":include_perfetto_public_base",
- ":include_perfetto_public_protozero",
- ":include_perfetto_trace_processor_basic_types",
- ":include_perfetto_trace_processor_storage",
- ":include_perfetto_trace_processor_trace_processor",
- ],
- visibility = [
- "//visibility:public",
- ],
- deps = [
- ":protos_perfetto_common_zero",
- ":protos_perfetto_config_android_zero",
- ":protos_perfetto_config_ftrace_zero",
- ":protos_perfetto_config_gpu_zero",
- ":protos_perfetto_config_inode_file_zero",
- ":protos_perfetto_config_interceptors_zero",
- ":protos_perfetto_config_power_zero",
- ":protos_perfetto_config_process_stats_zero",
- ":protos_perfetto_config_profiling_zero",
- ":protos_perfetto_config_statsd_zero",
- ":protos_perfetto_config_sys_stats_zero",
- ":protos_perfetto_config_system_info_zero",
- ":protos_perfetto_config_track_event_zero",
- ":protos_perfetto_config_zero",
- ":protos_perfetto_trace_android_winscope_common_zero",
- ":protos_perfetto_trace_android_winscope_regular_zero",
- ":protos_perfetto_trace_android_zero",
- ":protos_perfetto_trace_chrome_zero",
- ":protos_perfetto_trace_etw_zero",
- ":protos_perfetto_trace_filesystem_zero",
- ":protos_perfetto_trace_ftrace_zero",
- ":protos_perfetto_trace_gpu_zero",
- ":protos_perfetto_trace_interned_data_zero",
- ":protos_perfetto_trace_minimal_zero",
- ":protos_perfetto_trace_non_minimal_zero",
- ":protos_perfetto_trace_perfetto_zero",
- ":protos_perfetto_trace_power_zero",
- ":protos_perfetto_trace_profiling_zero",
- ":protos_perfetto_trace_ps_zero",
- ":protos_perfetto_trace_statsd_zero",
- ":protos_perfetto_trace_sys_stats_zero",
- ":protos_perfetto_trace_system_info_zero",
- ":protos_perfetto_trace_track_event_zero",
- ":protos_perfetto_trace_translation_zero",
- ":protos_third_party_pprof_zero",
- ":protozero",
- ":src_trace_processor_containers_containers",
- ] + PERFETTO_CONFIG.deps.zlib,
- linkstatic = True,
-)
-
# GN target: //src/traceconv:traceconv
perfetto_cc_binary(
name = "traceconv",
diff --git a/CHANGELOG b/CHANGELOG
index 60f31dc..2a33ef5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -24,6 +24,10 @@
inserted directly into it.
* Removed the `uid_track` table. It was redundant as no data was
inserted directly into it.
+ * Removed `common` package. It has been deprecated since `v42` and most
+ functionality has been moved into other packages. Notably, the time
+ conversion functions can be found in `time.conversion` module, and
+ `thread_slice` is available with `slices.with_context`.
Trace Processor:
*
UI:
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/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index 1de0b47..86c206c 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -303,8 +303,8 @@
// sorted by the duration in descending order.
// By checking out the top slices/threads, developers can identify specific
// slices or threads for further investigation.
- repeated TraceSliceSection trace_slice_sections = 7;
- repeated TraceThreadSection trace_thread_sections = 8;
+ optional TraceSliceSectionInfo trace_slice_sections = 7;
+ optional TraceThreadSectionInfo trace_thread_sections = 8;
// Details specific for a reason.
optional string additional_info = 9;
@@ -355,6 +355,13 @@
optional uint32 thread_tid = 6;
}
+ // Information for the SliceSections
+ message TraceSliceSectionInfo {
+ repeated TraceSliceSection slice_section = 1;
+ optional int64 start_timestamp = 2;
+ optional int64 end_timestamp = 3;
+ }
+
// Contains information for a section of a thread.
message TraceThreadSection {
optional int64 start_timestamp = 1;
@@ -371,6 +378,13 @@
optional uint32 thread_tid = 6;
}
+ // Information for the ThreadSections
+ message TraceThreadSectionInfo {
+ repeated TraceThreadSection thread_section = 1;
+ optional int64 start_timestamp = 2;
+ optional int64 end_timestamp = 3;
+ }
+
// Next id: 26
message Startup {
// Random id uniquely identifying an app startup in this trace.
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..e2fc3a3 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -2541,8 +2541,8 @@
// sorted by the duration in descending order.
// By checking out the top slices/threads, developers can identify specific
// slices or threads for further investigation.
- repeated TraceSliceSection trace_slice_sections = 7;
- repeated TraceThreadSection trace_thread_sections = 8;
+ optional TraceSliceSectionInfo trace_slice_sections = 7;
+ optional TraceThreadSectionInfo trace_thread_sections = 8;
// Details specific for a reason.
optional string additional_info = 9;
@@ -2593,6 +2593,13 @@
optional uint32 thread_tid = 6;
}
+ // Information for the SliceSections
+ message TraceSliceSectionInfo {
+ repeated TraceSliceSection slice_section = 1;
+ optional int64 start_timestamp = 2;
+ optional int64 end_timestamp = 3;
+ }
+
// Contains information for a section of a thread.
message TraceThreadSection {
optional int64 start_timestamp = 1;
@@ -2609,6 +2616,13 @@
optional uint32 thread_tid = 6;
}
+ // Information for the ThreadSections
+ message TraceThreadSectionInfo {
+ repeated TraceThreadSection thread_section = 1;
+ optional int64 start_timestamp = 2;
+ optional int64 end_timestamp = 3;
+ }
+
// Next id: 26
message Startup {
// Random id uniquely identifying an app startup in this trace.
@@ -3009,11 +3023,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..ed15b47 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index 2695450..37c1a95 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -115,15 +115,9 @@
(['/public/lib/colorizer'], '/core/feature_flags'),
# TODO(primiano): Record page-related technical debt.
- ('/frontend/record*', '/controller/*'),
- ('/frontend/permalink', '/controller/*'),
- ('/common/*', '/controller/record_config_types'),
- ('/controller/index', '/common/recordingV2/target_factories/index'),
- ('/common/recordingV2/*', '/controller/*'),
- ('/controller/record_controller*', '*'),
- ('/controller/adb_*', '*'),
- ('/chrome_extension/chrome_tracing_controller', '/controller/*'),
- ('/chrome_extension/chrome_tracing_controller', '/core/trace_config_utils'),
+ ('/plugins/dev.perfetto.RecordTrace/*', '/frontend/globals'),
+ ('/chrome_extension/chrome_tracing_controller',
+ '/plugins/dev.perfetto.RecordTrace/*'),
# TODO(primiano): query-table tech debt.
(
@@ -150,9 +144,6 @@
# Bigtrace deps.
('/bigtrace/*', ['/base/*', '/widgets/*', '/trace_processor/*']),
- # TODO(primiano): rationalize recordingv2. RecordingV2 is a mess of subdirs.
- ('/common/recordingV2/*', '/common/recordingV2/*'),
-
# TODO(primiano): misc tech debt.
('/public/lib/extensions', '/frontend/*'),
('/bigtrace/index', ['/core/live_reload', '/core/raf_scheduler']),
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/android/jank/frames.sql b/src/trace_processor/metrics/sql/android/jank/frames.sql
index 8f72e96..481bb4b 100644
--- a/src/trace_processor/metrics/sql/android/jank/frames.sql
+++ b/src/trace_processor/metrics/sql/android/jank/frames.sql
@@ -122,30 +122,55 @@
-- the commit/composite slices on the main thread.
DROP TABLE IF EXISTS android_jank_cuj_sf_frame;
CREATE PERFETTO TABLE android_jank_cuj_sf_frame AS
+WITH android_jank_cuj_timeline_sf_frame AS (
+ SELECT DISTINCT
+ cuj_id,
+ CAST(timeline.name AS INTEGER) AS vsync,
+ timeline.display_frame_token
+ FROM android_jank_cuj_vsync_boundary boundary
+ JOIN actual_frame_timeline_slice timeline
+ ON
+ boundary.upid = timeline.upid
+ AND CAST(timeline.name AS INTEGER) >= vsync_min
+ AND CAST(timeline.name AS INTEGER) <= vsync_max
+ WHERE
+ boundary.layer_id IS NULL
+ OR (
+ timeline.layer_name GLOB '*#*'
+ AND boundary.layer_id = CAST(STR_SPLIT(timeline.layer_name, '#', 1) AS INTEGER))
+),
+android_jank_cuj_sf_frame_base AS (
+ SELECT DISTINCT
+ boundary.cuj_id,
+ boundary.vsync,
+ boundary.ts,
+ boundary.ts_main_thread_start,
+ boundary.ts_end,
+ boundary.dur,
+ actual_timeline.jank_tag = 'Self Jank' AS sf_missed,
+ NULL AS app_missed, -- for simplicity align schema with android_jank_cuj_frame
+ jank_tag,
+ jank_type,
+ prediction_type,
+ present_type,
+ gpu_composition,
+ -- In case expected timeline is missing, as a fallback we use the typical frame deadline
+ -- for 60Hz.
+ -- See similar expression in android_jank_cuj_frame_timeline.
+ COALESCE(expected_timeline.dur, 16600000) AS dur_expected
+ FROM android_jank_cuj_sf_main_thread_frame_boundary boundary
+ JOIN android_jank_cuj_sf_process sf_process
+ JOIN actual_frame_timeline_slice actual_timeline
+ ON actual_timeline.upid = sf_process.upid
+ AND boundary.vsync = CAST(actual_timeline.name AS INTEGER)
+ JOIN android_jank_cuj_timeline_sf_frame ft
+ ON CAST(actual_timeline.name AS INTEGER) = ft.display_frame_token
+ AND boundary.cuj_id = ft.cuj_id
+ LEFT JOIN expected_frame_timeline_slice expected_timeline
+ ON expected_timeline.upid = actual_timeline.upid
+ AND expected_timeline.name = actual_timeline.name
+)
SELECT
- cuj_id,
- ROW_NUMBER() OVER (PARTITION BY cuj_id ORDER BY vsync ASC) AS frame_number,
- vsync,
- boundary.ts,
- boundary.ts_main_thread_start,
- boundary.ts_end,
- boundary.dur,
- actual_timeline.jank_tag = 'Self Jank' AS sf_missed,
- NULL AS app_missed, -- for simplicity align schema with android_jank_cuj_frame
- jank_tag,
- jank_type,
- prediction_type,
- present_type,
- gpu_composition,
- -- In case expected timeline is missing, as a fallback we use the typical frame deadline
- -- for 60Hz.
- -- See similar expression in android_jank_cuj_frame_timeline.
- COALESCE(expected_timeline.dur, 16600000) AS dur_expected
-FROM android_jank_cuj_sf_main_thread_frame_boundary boundary
-JOIN android_jank_cuj_sf_process sf_process
-JOIN actual_frame_timeline_slice actual_timeline
- ON actual_timeline.upid = sf_process.upid
- AND boundary.vsync = CAST(actual_timeline.name AS INTEGER)
-LEFT JOIN expected_frame_timeline_slice expected_timeline
- ON expected_timeline.upid = actual_timeline.upid
- AND expected_timeline.name = actual_timeline.name;
+ *,
+ ROW_NUMBER() OVER (PARTITION BY cuj_id ORDER BY vsync ASC) AS frame_number
+FROM android_jank_cuj_sf_frame_base;
diff --git a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
index 9c43318..536dea7 100644
--- a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
+++ b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
@@ -67,6 +67,7 @@
-- Ignore child slice e.g. "Choreographer#doFrame - resynced to 1234 in 20.0ms"
AND slice.name not GLOB '*resynced*'
AND slice.dur > 0
+ AND vsync > 0
AND (vsync >= begin_vsync OR begin_vsync is NULL)
AND (vsync <= end_vsync OR end_vsync is NULL)
-- In some malformed traces we see nested doFrame slices.
diff --git a/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql b/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql
index 2d5b646..8a4a7dd 100644
--- a/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql
+++ b/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql
@@ -46,10 +46,13 @@
CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_in_runnable_state(
startup_id LONG, num_threads INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection(
- 'start_timestamp', ts, 'end_timestamp', ts + dur,
- 'thread_tid', tid, 'process_pid', pid,
- 'thread_name', thread_name))
+ SELECT AndroidStartupMetric_TraceThreadSectionInfo(
+ 'start_timestamp', MIN(ts),
+ 'end_timestamp', MAX(ts + dur),
+ 'thread_section', RepeatedField(AndroidStartupMetric_TraceThreadSection(
+ 'start_timestamp', ts, 'end_timestamp', ts + dur,
+ 'thread_tid', tid, 'process_pid', pid,
+ 'thread_name', thread_name)))
FROM (
SELECT p.pid, ts, dur, thread.tid, thread_name
FROM launch_threads_by_thread_state l, android_startup_processes p
@@ -62,10 +65,13 @@
CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_and_state(
startup_id LONG, state STRING, num_threads INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection(
- 'start_timestamp', ts, 'end_timestamp', ts + dur,
- 'thread_tid', tid, 'process_pid', pid,
- 'thread_name', thread_name))
+ SELECT AndroidStartupMetric_TraceThreadSectionInfo(
+ 'start_timestamp', MIN(ts),
+ 'end_timestamp', MAX(ts + dur),
+ 'thread_section', RepeatedField(AndroidStartupMetric_TraceThreadSection(
+ 'start_timestamp', ts, 'end_timestamp', ts + dur,
+ 'thread_tid', tid, 'process_pid', pid,
+ 'thread_name', thread_name)))
FROM (
SELECT p.pid, ts, dur, thread.tid, thread_name
FROM launch_threads_by_thread_state l, android_startup_processes p
@@ -78,10 +84,13 @@
CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_state_and_io_wait(
startup_id INT, state STRING, io_wait BOOL, num_threads INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection(
- 'start_timestamp', ts, 'end_timestamp', ts + dur,
- 'thread_tid', tid, 'process_pid', pid,
- 'thread_name', thread_name))
+ SELECT AndroidStartupMetric_TraceThreadSectionInfo(
+ 'start_timestamp', MIN(ts),
+ 'end_timestamp', MAX(ts + dur),
+ 'thread_section', RepeatedField(AndroidStartupMetric_TraceThreadSection(
+ 'start_timestamp', ts, 'end_timestamp', ts + dur,
+ 'thread_tid', tid, 'process_pid', pid,
+ 'thread_name', thread_name)))
FROM (
SELECT p.pid, ts, dur, thread.tid, thread_name
FROM launch_threads_by_thread_state l, android_startup_processes p
@@ -95,10 +104,13 @@
CREATE OR REPLACE PERFETTO FUNCTION get_thread_time_for_launch_state_and_thread(
startup_id INT, state STRING, thread_name STRING, num_threads INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection(
- 'start_timestamp', ts, 'end_timestamp', ts + dur,
- 'thread_tid', tid, 'process_pid', pid,
- 'thread_name', thread_name))
+ SELECT AndroidStartupMetric_TraceThreadSectionInfo(
+ 'start_timestamp', MIN(ts),
+ 'end_timestamp', MAX(ts + dur),
+ 'thread_section', RepeatedField(AndroidStartupMetric_TraceThreadSection(
+ 'start_timestamp', ts, 'end_timestamp', ts + dur,
+ 'thread_tid', tid, 'process_pid', pid,
+ 'thread_name', thread_name)))
FROM (
SELECT p.pid, ts, dur, thread.tid, thread_name
FROM launch_threads_by_thread_state l, android_startup_processes p
@@ -111,13 +123,16 @@
CREATE OR REPLACE PERFETTO FUNCTION get_missing_baseline_profile_for_launch(
startup_id LONG, pkg_name STRING)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', pid,
- 'start_timestamp', slice_ts,
- 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id,
- 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', pid,
+ 'start_timestamp', slice_ts,
+ 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id,
+ 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME($startup_id,
@@ -135,13 +150,16 @@
CREATE OR REPLACE PERFETTO FUNCTION get_run_from_apk(startup_id LONG)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', pid,
- 'start_timestamp', slice_ts,
- 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id,
- 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', pid,
+ 'start_timestamp', slice_ts,
+ 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id,
+ 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
FROM android_thread_slices_for_all_startups l, android_startup_processes p
@@ -157,13 +175,16 @@
CREATE OR REPLACE PERFETTO FUNCTION get_unlock_running_during_launch_slice(startup_id LONG,
pid INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', $pid,
- 'start_timestamp', slice_ts,
- 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id,
- 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', $pid,
+ 'start_timestamp', slice_ts,
+ 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id,
+ 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT tid, slice.ts as slice_ts, slice.dur as slice_dur,
slice.id as slice_id, slice.name as slice_name
@@ -180,13 +201,16 @@
CREATE OR REPLACE PERFETTO FUNCTION get_gc_activity(startup_id LONG, num_slices INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', pid,
- 'start_timestamp', slice_ts,
- 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id,
- 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', pid,
+ 'start_timestamp', slice_ts,
+ 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id,
+ 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
FROM android_thread_slices_for_all_startups slice, android_startup_processes p
@@ -204,13 +228,16 @@
CREATE OR REPLACE PERFETTO FUNCTION get_dur_on_main_thread_for_startup_and_slice(
startup_id LONG, slice_name STRING, num_slices INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', pid,
- 'start_timestamp', slice_ts,
- 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id,
- 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', pid,
+ 'start_timestamp', slice_ts,
+ 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id,
+ 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
FROM android_thread_slices_for_all_startups l,
@@ -223,11 +250,14 @@
CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_binder_transactions_blocked(
startup_id LONG, threshold DOUBLE, num_slices INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', pid,
- 'start_timestamp', slice_ts, 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id, 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', pid,
+ 'start_timestamp', slice_ts, 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id, 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT pid, request.tid as tid, request.slice_ts as slice_ts, request.slice_dur as slice_dur,
request.id as slice_id, request.slice_name as slice_name
@@ -253,11 +283,14 @@
CREATE OR REPLACE PERFETTO FUNCTION get_slices_concurrent_to_launch(
startup_id INT, slice_glob STRING, num_slices INT, pid INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', $pid,
- 'start_timestamp', ts, 'end_timestamp', ts + dur,
- 'slice_id', id, 'slice_name', name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', $pid,
+ 'start_timestamp', ts, 'end_timestamp', ts + dur,
+ 'slice_id', id, 'slice_name', name)),
+ 'start_timestamp', MIN(ts),
+ 'end_timestamp', MAX(ts + dur))
FROM (
SELECT thread.tid, s.ts as ts, dur, s.id, s.name FROM slice s
JOIN thread_track t ON s.track_id = t.id
@@ -275,11 +308,14 @@
CREATE OR REPLACE PERFETTO FUNCTION get_slices_for_startup_and_slice_name(
startup_id INT, slice_name STRING, num_slices INT, pid int)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- 'thread_tid', tid,
- 'process_pid', $pid,
- 'start_timestamp', slice_ts, 'end_timestamp', slice_ts + slice_dur,
- 'slice_id', slice_id, 'slice_name', slice_name))
+ SELECT AndroidStartupMetric_TraceSliceSectionInfo(
+ 'slice_section', RepeatedField(AndroidStartupMetric_TraceSliceSection(
+ 'thread_tid', tid,
+ 'process_pid', $pid,
+ 'start_timestamp', slice_ts, 'end_timestamp', slice_ts + slice_dur,
+ 'slice_id', slice_id, 'slice_name', slice_name)),
+ 'start_timestamp', MIN(slice_ts),
+ 'end_timestamp', MAX(slice_ts + slice_dur))
FROM (
SELECT tid, slice_ts, slice_dur, slice_id, slice_name
FROM android_thread_slices_for_all_startups
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql b/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql
index 788ff3b..3545234 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql
@@ -55,22 +55,6 @@
ELSE "unknown" END)
ELSE "regular" END AS delay_type;
--- Checks if slice has a descendant with provided name.
-CREATE OR REPLACE PERFETTO FUNCTION _has_descendant_slice_with_name(
- -- Id of the slice to check descendants of.
- id INT,
- -- Name of potential descendant slice.
- descendant_name STRING
-)
--- Whether `descendant_name` is a name of an descendant slice.
-RETURNS BOOL AS
-SELECT EXISTS(
- SELECT 1
- FROM descendant_slice($id)
- WHERE name = $descendant_name
- LIMIT 1
-);
-
-- Get all EventLatency events for scroll updates to use their
-- flows later on to decide how much time we waited from queueing the event
-- until we started processing it.
@@ -87,10 +71,16 @@
{{slice_table_name}} AS s JOIN args USING(arg_set_id)
WHERE
NAME = "EventLatency"
- AND (args.string_value GLOB "*GESTURE_SCROLL_UPDATE"
- OR args.string_value = "GESTURE_SCROLL_END")
- AND _has_descendant_slice_with_name(
- s.id, "SubmitCompositorFrameToPresentationCompositorFrame")
+ AND EXISTS(
+ SELECT 1
+ FROM descendant_slice(s.id)
+ WHERE name = "SubmitCompositorFrameToPresentationCompositorFrame"
+ LIMIT 1
+ )
+ AND (
+ args.string_value GLOB "*GESTURE_SCROLL_UPDATE"
+ OR args.string_value = "GESTURE_SCROLL_END"
+ )
ORDER BY trace_id;
-- Get all chrome_latency_info_for_gesture_slices where trace_ids are not -1,
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql b/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql
index 94d2794..62d84af 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql
@@ -21,7 +21,22 @@
-- The numbers mentioned above are estimates in the ideal case scenario.
INCLUDE PERFETTO MODULE chrome.scroll_jank.utils;
-INCLUDE PERFETTO MODULE common.slices;
+
+-- Checks if slice has a descendant with provided name.
+CREATE OR REPLACE PERFETTO FUNCTION _has_descendant_slice_with_name(
+ -- Id of the slice to check descendants of.
+ id INT,
+ -- Name of potential descendant slice.
+ descendant_name STRING
+)
+-- Whether `descendant_name` is a name of an descendant slice.
+RETURNS BOOL AS
+SELECT EXISTS(
+ SELECT 1
+ FROM descendant_slice($id)
+ WHERE name = $descendant_name
+ LIMIT 1
+);
-- Grab all GestureScrollUpdate slices.
DROP VIEW IF EXISTS chrome_all_scroll_updates;
@@ -29,7 +44,7 @@
SELECT
S.id,
chrome_get_most_recent_scroll_begin_id(ts) AS scroll_id,
- has_descendant_slice_with_name(S.id, "SubmitCompositorFrameToPresentationCompositorFrame")
+ _has_descendant_slice_with_name(S.id, "SubmitCompositorFrameToPresentationCompositorFrame")
AS is_presented,
ts,
dur,
diff --git a/src/trace_processor/metrics/sql/common/parent_slice.sql b/src/trace_processor/metrics/sql/common/parent_slice.sql
index d5c6f24..5478d93 100644
--- a/src/trace_processor/metrics/sql/common/parent_slice.sql
+++ b/src/trace_processor/metrics/sql/common/parent_slice.sql
@@ -13,5 +13,3 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-
-INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
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/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 21216b6..36bd391 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -704,8 +704,18 @@
}
std::string package_name = sql_modules::GetPackageName(key);
+
auto* package = FindPackage(package_name);
if (!package) {
+ if (package_name == "common") {
+ return base::ErrStatus(
+ "INCLUDE: Package `common` has been removed and most of the "
+ "functionality has been moved to other packages. Check "
+ "`slices.with_context` for replacement for `common.slices` and "
+ "`time.conversion` for replacement for `common.timestamps`. The "
+ "documentation for Perfetto standard library can be found at "
+ "https://perfetto.dev/docs/analysis/stdlib-docs.");
+ }
return base::ErrStatus("INCLUDE: Package '%s' not found", key.c_str());
}
return IncludePackageImpl(*package, key, parser);
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index bf51d9e..adaeb73 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -22,9 +22,7 @@
"android",
"callstacks",
"chrome:chrome_sql",
- "common",
"counters",
- "deprecated/v42/common",
"export",
"graphs",
"intervals",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql b/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql
index 90697fa..66d2d01 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql
@@ -39,6 +39,8 @@
OR $name GLOB 'NotificationStackScrollLayout#onMeasure'
OR $name GLOB 'ExpNotRow#*'
OR $name GLOB 'GC: Wait For*'
+ OR $name GLOB 'Recomposer:*'
+ OR $name GLOB 'Compose:*'
OR (
-- Some top level handler slices
$depth = 0
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql
index 62b1d88..0d704db 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql
@@ -2,8 +2,6 @@
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.
-INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
-
-- Finds the start timestamp for a given slice's descendant with a given name.
-- If there are multiple descendants with a given name, the function will return
-- the first one, so it's most useful when working with a timeline broken down
@@ -42,6 +40,22 @@
WHERE s.name GLOB $child_name
LIMIT 1;
+-- Checks if slice has a descendant with provided name.
+CREATE PERFETTO FUNCTION _has_descendant_slice_with_name(
+ -- Id of the slice to check descendants of.
+ id INT,
+ -- Name of potential descendant slice.
+ descendant_name STRING
+)
+-- Whether `descendant_name` is a name of an descendant slice.
+RETURNS BOOL AS
+SELECT EXISTS(
+ SELECT 1
+ FROM descendant_slice($id)
+ WHERE name = $descendant_name
+ LIMIT 1
+);
+
-- Returns the presentation timestamp for a given EventLatency slice.
-- This is either the end of
-- SwapEndToPresentationCompositorFrame (if it exists),
@@ -101,7 +115,7 @@
slice.ts,
slice.dur,
EXTRACT_arg(arg_set_id, 'event_latency.event_latency_id') AS scroll_update_id,
- has_descendant_slice_with_name(
+ _has_descendant_slice_with_name(
slice.id,
'SubmitCompositorFrameToPresentationCompositorFrame')
AS is_presented,
@@ -142,7 +156,7 @@
event_type GLOB '*GESTURE_SCROLL*'
-- Pinches are only relevant if the frame was presented.
OR (event_type GLOB '*GESTURE_PINCH_UPDATE'
- AND has_descendant_slice_with_name(
+ AND _has_descendant_slice_with_name(
id,
'SubmitCompositorFrameToPresentationCompositorFrame')
)
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/graphics_pipeline.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/graphics_pipeline.sql
index 909619c..eae5bd5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/graphics_pipeline.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/graphics_pipeline.sql
@@ -2,7 +2,7 @@
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.
-INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
+INCLUDE PERFETTO MODULE slices.with_context;
-- `Graphics.Pipeline` steps corresponding to work done by a Viz client to
-- produce a frame (i.e. before surface aggregation). Covers steps:
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/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
deleted file mode 100644
index 73db0a7..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2022 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("../../../../../gn/perfetto_sql.gni")
-
-perfetto_sql_source_set("common") {
- sources = [
- "args.sql",
- "counters.sql",
- "metadata.sql",
- "percentiles.sql",
- "slices.sql",
- "timestamps.sql",
- ]
-}
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/OWNERS b/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
deleted file mode 100644
index 0a16b3f..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-set noparent
-
-# Please prefer sending to one of the following people
-mayzner@google.com
-lalitm@google.com
-
-# For emergency reviews
-primiano@google.com
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/args.sql b/src/trace_processor/perfetto_sql/stdlib/common/args.sql
deleted file mode 100644
index 3d1e793..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/args.sql
+++ /dev/null
@@ -1,20 +0,0 @@
---
--- Copyright 2023 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.
-
--- No new changes allowed. Will be removed after v45 of Perfetto.
---
--- We decided to move away from the generalised `common` module and migrate the
--- most useful functionality into specialised modules.
-INCLUDE PERFETTO MODULE deprecated.v42.common.args;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/counters.sql b/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
deleted file mode 100644
index f0c1ce6..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
+++ /dev/null
@@ -1,21 +0,0 @@
---
--- Copyright 2023 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.
-
--- No new changes allowed. Will be removed after v45 of Perfetto.
---
--- We decided to move away from the generalised `common` module and migrate the
--- most useful functionality into specialised modules.
-INCLUDE PERFETTO MODULE deprecated.v42.common.args;
-INCLUDE PERFETTO MODULE deprecated.v42.common.counters;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql b/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql
deleted file mode 100644
index bd1a0fd..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql
+++ /dev/null
@@ -1,21 +0,0 @@
---
--- Copyright 2022 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.
-
--- No new changes allowed. Will be removed after v45 of Perfetto.
---
--- We decided to move away from the generalised `common` module and migrate the
--- most useful functionality into specialised modules.
-INCLUDE PERFETTO MODULE deprecated.v42.common.args;
-INCLUDE PERFETTO MODULE deprecated.v42.common.metadata;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
deleted file mode 100644
index 525c95c..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
+++ /dev/null
@@ -1,21 +0,0 @@
---
--- Copyright 2023 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.
-
--- No new changes allowed. Will be removed after v45 of Perfetto.
---
--- We decided to move away from the generalised `common` module and migrate the
--- most useful functionality into specialised modules.
-INCLUDE PERFETTO MODULE deprecated.v42.common.args;
-INCLUDE PERFETTO MODULE deprecated.v42.common.percentiles;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/slices.sql b/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
deleted file mode 100644
index d5d70c9..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
+++ /dev/null
@@ -1,21 +0,0 @@
---
--- Copyright 2022 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.
-
--- No new changes allowed. Will be removed after v45 of Perfetto.
---
--- We decided to move away from the generalised `common` module and migrate the
--- most useful functionality into specialised modules.
-INCLUDE PERFETTO MODULE deprecated.v42.common.args;
-INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql b/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
deleted file mode 100644
index 8f91d3b..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
+++ /dev/null
@@ -1,21 +0,0 @@
---
--- Copyright 2022 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.
-
--- No new changes allowed. Will be removed after v45 of Perfetto.
---
--- We decided to move away from the generalised `common` module and migrate the
--- most useful functionality into specialised modules.
-INCLUDE PERFETTO MODULE deprecated.v42.common.args;
-INCLUDE PERFETTO MODULE deprecated.v42.common.timestamps;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn
deleted file mode 100644
index a99b51b..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2022 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("../../../../../../../gn/perfetto_sql.gni")
-
-perfetto_sql_source_set("common") {
- sources = [
- "args.sql",
- "counters.sql",
- "metadata.sql",
- "percentiles.sql",
- "slices.sql",
- "timestamps.sql",
- ]
-}
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql
deleted file mode 100644
index df0615a..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql
+++ /dev/null
@@ -1,31 +0,0 @@
---
--- Copyright 2023 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.
-
--- Returns the formatted value of a given argument.
--- Similar to EXTRACT_ARG, but instead of returning the raw value, it returns
--- the value formatted according to the 'value_type' column (e.g. for booleans,
--- EXTRACT_ARG will return 0 or 1, while FORMATTED_ARG will return 'true' or
--- 'false').
-CREATE PERFETTO FUNCTION formatted_arg(
- -- Id of the arg set.
- arg_set_id INT,
- -- Key of the argument.
- arg_key STRING
-)
--- Formatted value of the argument.
-RETURNS STRING AS
-SELECT display_value
-FROM args
-WHERE arg_set_id = $arg_set_id AND key = $arg_key;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql
deleted file mode 100644
index 7923c52..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql
+++ /dev/null
@@ -1,101 +0,0 @@
---
--- Copyright 2023 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 deprecated.v42.common.timestamps;
-
--- Timestamp of first counter value in a counter.
-CREATE PERFETTO FUNCTION earliest_timestamp_for_counter_track(
- -- Id of a counter track with a counter.
- counter_track_id INT)
--- Timestamp of first counter value. Null if doesn't exist.
-RETURNS LONG AS
-SELECT MIN(ts) FROM counter WHERE counter.track_id = $counter_track_id;
-
--- Counter values with details of counter track with calculated duration of each counter value.
--- Duration is calculated as time from counter to the next counter.
-CREATE PERFETTO FUNCTION counter_with_dur_for_track(
- -- Id of track counter track.
- counter_track_id INT)
-RETURNS TABLE(
- -- Timestamp of the counter value.
- ts LONG,
- -- Duration of the counter value.
- dur LONG,
- -- Counter value.
- value DOUBLE,
- -- Id of the counter track.
- track_id INT,
- -- Name of the counter track.
- track_name STRING,
- -- Counter track set id.
- track_arg_set_id INT,
- -- Counter arg set id.
- arg_set_id INT
-) AS
-SELECT
- ts,
- LEAD(ts, 1, trace_end()) OVER(ORDER BY ts) - ts AS dur,
- value,
- track.id AS track_id,
- track.name AS track_name,
- track.source_arg_set_id AS track_arg_set_id,
- counter.arg_set_id AS arg_set_id
-FROM counter
-JOIN counter_track track ON track.id = counter.track_id
-WHERE track.id = $counter_track_id;
-
--- COUNTER_WITH_DUR_FOR_TRACK but in a specified time.
--- Does calculation over the table ends - creates an artificial counter value at
--- the start if needed and chops the duration of the last timestamps in range.
-CREATE PERFETTO FUNCTION counter_for_time_range(
- -- Id of track counter track.
- counter_track_id INT,
- -- Timestamp of the timerange start.
- -- Can be earlier than the first counter value.
- start_ts LONG,
- -- Timestamp of the timerange end.
- end_ts LONG)
-RETURNS TABLE(
- -- Timestamp of the counter value.
- ts LONG,
- -- Duration of the counter value.
- dur LONG,
- -- Counter value.
- value DOUBLE,
- -- If of the counter track.
- track_id INT,
- -- Name of the counter track.
- track_name STRING,
- -- Counter track set id.
- track_arg_set_id INT,
- -- Counter arg set id.
- arg_set_id INT
-) AS
-SELECT
- IIF(ts < $start_ts, $start_ts, ts) AS ts,
- IIF(
- ts < $start_ts,
- dur - ($start_ts - ts),
- IIF(ts + dur > $end_ts, $end_ts - ts, dur)) AS dur,
- value,
- track_id,
- track_name,
- track_arg_set_id,
- arg_set_id
-FROM counter_with_dur_for_track($counter_track_id)
-WHERE TRUE
- AND ts + dur >= $start_ts
- AND ts < $end_ts
-ORDER BY ts ASC;
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql
deleted file mode 100644
index e667477..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql
+++ /dev/null
@@ -1,22 +0,0 @@
---
--- Copyright 2022 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.
-
--- Extracts an int value with the given name from the metadata table.
-CREATE PERFETTO FUNCTION extract_int_metadata(
- -- The name of the metadata entry.
- name STRING)
--- int_value for the given name. NULL if there's no such entry.
-RETURNS LONG AS
-SELECT int_value FROM metadata WHERE name = ($name);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql
deleted file mode 100644
index e807a78..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql
+++ /dev/null
@@ -1,169 +0,0 @@
---
--- Copyright 2023 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 deprecated.v42.common.counters;
-INCLUDE PERFETTO MODULE deprecated.v42.common.timestamps;
-
-CREATE PERFETTO FUNCTION _number_generator(upper_limit INT)
-RETURNS TABLE(num INT) AS
-WITH nums AS
- (SELECT 1 num UNION SELECT num + 1
- from NUMS
- WHERE num < $upper_limit)
-SELECT num FROM nums;
-
-CREATE PERFETTO FUNCTION _earliest_timestamp_for_counter_track(
- -- Id of a counter track with a counter.
- counter_track_id INT)
--- Timestamp of first counter value. Null if doesn't exist.
-RETURNS LONG AS
-SELECT MIN(ts) FROM counter WHERE counter.track_id = $counter_track_id;
-
--- COUNTER_WITH_DUR_FOR_TRACK but in a specified time.
--- Does calculation over the table ends - creates an artificial counter value at
--- the start if needed and chops the duration of the last timestamps in range.
-CREATE PERFETTO FUNCTION _counter_for_time_range(
- -- Id of track counter track.
- counter_track_id INT,
- -- Timestamp of the timerange start.
- -- Can be earlier than the first counter value.
- start_ts LONG,
- -- Timestamp of the timerange end.
- end_ts LONG)
-RETURNS TABLE(
- -- Timestamp of the counter value.
- ts LONG,
- -- Duration of the counter value.
- dur LONG,
- -- Counter value.
- value DOUBLE,
- -- If of the counter track.
- track_id INT,
- -- Name of the counter track.
- track_name STRING,
- -- Counter track set id.
- track_arg_set_id INT,
- -- Counter arg set id.
- arg_set_id INT
-) AS
-SELECT
- IIF(ts < $start_ts, $start_ts, ts) AS ts,
- IIF(
- ts < $start_ts,
- dur - ($start_ts - ts),
- IIF(ts + dur > $end_ts, $end_ts - ts, dur)) AS dur,
- value,
- track_id,
- track_name,
- track_arg_set_id,
- arg_set_id
-FROM counter_with_dur_for_track($counter_track_id)
-WHERE TRUE
- AND ts + dur >= $start_ts
- AND ts < $end_ts
-ORDER BY ts ASC;
-
---
--- Get durations for percentile
---
-
--- All percentiles (range 1-100) for counter track ID in a given time range.
---
--- Percentiles are calculated by:
--- 1. Dividing the sum of duration in time range for each value in the counter
--- by duration of the counter in range. This gives us `percentile_for)value` (DOUBLE).
--- 2. Fetching each percentile by taking floor of each `percentile_for_value`, grouping by
--- resulting `percentile` and MIN from value for each grouping. As we are rounding down,
--- taking MIN assures most reliable data.
--- 3. Filling the possible gaps in percentiles by getting the minimal value from higher
--- percentiles for each gap.
-CREATE PERFETTO FUNCTION counter_percentiles_for_time_range(
- -- Id of the counter track.
- counter_track_id INT,
- -- Timestamp of start of time range.
- start_ts LONG,
- -- Timestamp of end of time range.
- end_ts LONG)
-RETURNS TABLE(
- -- All of the numbers from 1 to 100.
- percentile INT,
- -- Value for the percentile.
- value DOUBLE
-) AS
-WITH percentiles_for_value AS (
- SELECT
- value,
- (CAST(SUM(dur) OVER(ORDER BY value ASC) AS DOUBLE) /
- ($end_ts - MAX($start_ts, _earliest_timestamp_for_counter_track($counter_track_id)))) * 100
- AS percentile_for_value
- FROM _COUNTER_FOR_TIME_RANGE($counter_track_id, $start_ts, $end_ts)
- ORDER BY value ASC
-),
-with_gaps AS (
- SELECT
- CAST(percentile_for_value AS INT) AS percentile,
- MIN(value) AS value
- FROM percentiles_for_value
- GROUP BY percentile
- ORDER BY percentile ASC)
-SELECT
- num AS percentile,
- IFNULL(value, MIN(value) OVER (ORDER BY percentile DESC)) AS value
-FROM _NUMBER_GENERATOR(100) AS nums
-LEFT JOIN with_gaps ON with_gaps.percentile = nums.num
-ORDER BY percentile DESC;
-
--- All percentiles (range 1-100) for counter track ID.
-CREATE PERFETTO FUNCTION counter_percentiles_for_track(
- -- Id of the counter track.
- counter_track_id INT)
-RETURNS TABLE(
- -- All of the numbers from 1 to 100.
- percentile INT,
- -- Value for the percentile.
- value DOUBLE
-) AS
-SELECT *
-FROM counter_percentiles_for_time_range(
- $counter_track_id, trace_start(), trace_end());
-
--- Value for specific percentile (range 1-100) for counter track ID in time range.
-CREATE PERFETTO FUNCTION counter_track_percentile_for_time(
- -- Id of the counter track.
- counter_track_id INT,
- -- Any of the numbers from 1 to 100.
- percentile INT,
- -- Timestamp of start of time range.
- start_ts LONG,
- -- Timestamp of end of time range.
- end_ts LONG)
--- Value for the percentile.
-RETURNS DOUBLE AS
-SELECT value
-FROM counter_percentiles_for_time_range($counter_track_id, $start_ts, $end_ts)
-WHERE percentile = $percentile;
-
--- Value for specific percentile (range 1-100) for counter track ID.
-CREATE PERFETTO FUNCTION counter_track_percentile(
- -- Id of the counter track.
- counter_track_id INT,
- -- Any of the numbers from 1 to 100.
- percentile INT)
--- Value for the percentile.
-RETURNS DOUBLE AS
-SELECT counter_track_percentile_for_time($counter_track_id,
- $percentile,
- trace_start(),
- trace_end());
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql
deleted file mode 100644
index 05b6b21..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql
+++ /dev/null
@@ -1,133 +0,0 @@
---
--- Copyright 2022 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 slices.with_context;
-
--- Checks if slice has an ancestor with provided name.
-CREATE PERFETTO FUNCTION has_parent_slice_with_name(
- -- Id of the slice to check parents of.
- id INT,
- -- Name of potential ancestor slice.
- parent_name STRING)
--- Whether `parent_name` is a name of an ancestor slice.
-RETURNS BOOL AS
-SELECT EXISTS(
- SELECT 1
- FROM ancestor_slice($id)
- WHERE name = $parent_name
- LIMIT 1
-);
-
--- Checks if slice has a descendant with provided name.
-CREATE PERFETTO FUNCTION has_descendant_slice_with_name(
- -- Id of the slice to check descendants of.
- id INT,
- -- Name of potential descendant slice.
- descendant_name STRING
-)
--- Whether `descendant_name` is a name of an descendant slice.
-RETURNS BOOL AS
-SELECT EXISTS(
- SELECT 1
- FROM descendant_slice($id)
- WHERE name = $descendant_name
- LIMIT 1
-);
-
--- Finds the end timestamp for a given slice's descendant with a given name.
--- If there are multiple descendants with a given name, the function will return the
--- first one, so it's most useful when working with a timeline broken down into phases,
--- where each subphase can happen only once.
-CREATE PERFETTO FUNCTION descendant_slice_end(
- -- Id of the parent slice.
- parent_id INT,
- -- Name of the child with the desired end TS.
- child_name STRING
-)
--- End timestamp of the child or NULL if it doesn't exist.
-RETURNS INT AS
-SELECT
- CASE WHEN s.dur
- IS NOT -1 THEN s.ts + s.dur
- ELSE NULL
- END
-FROM descendant_slice($parent_id) s
-WHERE s.name = $child_name
-LIMIT 1;
-
--- Finds all slices with a direct parent with the given parent_id.
-CREATE PERFETTO FUNCTION direct_children_slice(
- -- Id of the parent slice.
- parent_id LONG)
-RETURNS TABLE(
- -- Alias for `slice.id`.
- id LONG,
- -- Alias for `slice.type`.
- type STRING,
- -- Alias for `slice.ts`.
- ts LONG,
- -- Alias for `slice.dur`.
- dur LONG,
- -- Alias for `slice.category`.
- category LONG,
- -- Alias for `slice.name`.
- name STRING,
- -- Alias for `slice.track_id`.
- track_id LONG,
- -- Alias for `slice.depth`.
- depth LONG,
- -- Alias for `slice.parent_id`.
- parent_id LONG,
- -- Alias for `slice.arg_set_id`.
- arg_set_id LONG,
- -- Alias for `slice.thread_ts`.
- thread_ts LONG,
- -- Alias for `slice.thread_dur`.
- thread_dur LONG
-) AS
-SELECT
- slice.id,
- slice.type,
- slice.ts,
- slice.dur,
- slice.category,
- slice.name,
- slice.track_id,
- slice.depth,
- slice.parent_id,
- slice.arg_set_id,
- slice.thread_ts,
- slice.thread_dur
-FROM slice
-WHERE parent_id = $parent_id;
-
--- Given a slice id, returns the name of the slice.
-CREATE PERFETTO FUNCTION slice_name_from_id(
- -- The slice id which we need the name for.
- id LONG
-)
--- The name of slice with the given id.
-RETURNS STRING AS
-SELECT
- name
-FROM slice
-WHERE $id = id;
-
-CREATE PERFETTO FUNCTION slice_count(
- -- Name of the slices to counted.
- slice_glob STRING)
--- Number of slices with the name.
-RETURNS INT AS
-SELECT COUNT(1) FROM slice WHERE name GLOB $slice_glob;
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql
deleted file mode 100644
index bff333f..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql
+++ /dev/null
@@ -1,72 +0,0 @@
---
--- Copyright 2022 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 time.conversion;
-
-CREATE PERFETTO FUNCTION is_spans_overlapping(
- ts1 LONG,
- ts_end1 LONG,
- ts2 LONG,
- ts_end2 LONG)
-RETURNS BOOL AS
-SELECT (IIF($ts1 < $ts2, $ts2, $ts1)
- < IIF($ts_end1 < $ts_end2, $ts_end1, $ts_end2));
-
-CREATE PERFETTO FUNCTION spans_overlapping_dur(
- ts1 LONG,
- dur1 LONG,
- ts2 LONG,
- dur2 LONG
-)
-RETURNS INT AS
-SELECT
- CASE
- WHEN $dur1 = -1 OR $dur2 = -1 THEN 0
- WHEN $ts1 + $dur1 < $ts2 OR $ts2 + $dur2 < $ts1 THEN 0
- WHEN ($ts1 >= $ts2) AND ($ts1 + $dur1 <= $ts2 + $dur2) THEN $dur1
- WHEN ($ts1 < $ts2) AND ($ts1 + $dur1 < $ts2 + $dur2) THEN $ts1 + $dur1 - $ts2
- WHEN ($ts1 > $ts2) AND ($ts1 + $dur1 > $ts2 + $dur2) THEN $ts2 + $dur2 - $ts1
- ELSE $dur2
- END;
-
--- Renames
-
-CREATE PERFETTO FUNCTION ns(nanos INT)
-RETURNS INT AS
-SELECT time_from_ns($nanos);
-
-CREATE PERFETTO FUNCTION us(micros INT)
-RETURNS INT AS
-SELECT time_from_us($micros);
-
-CREATE PERFETTO FUNCTION ms(millis INT)
-RETURNS INT AS
-SELECT time_from_ms($millis);
-
-CREATE PERFETTO FUNCTION seconds(seconds INT)
-RETURNS INT AS
-SELECT time_from_s($seconds);
-
-CREATE PERFETTO FUNCTION minutes(minutes INT)
-RETURNS INT AS
-SELECT time_from_min($minutes);
-
-CREATE PERFETTO FUNCTION hours(hours INT)
-RETURNS INT AS
-SELECT time_from_hours($hours);
-
-CREATE PERFETTO FUNCTION days(days INT)
-RETURNS INT AS
-SELECT time_from_days($days);
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql
index e7081ee..50331e6 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql
@@ -37,6 +37,24 @@
FROM intervals_overlap_count!(runnable, ts, dur)
ORDER BY ts;
+-- The count of threads in uninterruptible sleep over time.
+CREATE PERFETTO TABLE sched_uninterruptible_sleep_thread_count(
+ -- Timestamp when the thread count changed to the current value.
+ ts INT,
+ -- Number of threads in uninterrutible sleep, covering the range from this timestamp to the
+ -- next row's timestamp.
+ uninterruptible_sleep_thread_count INT
+) AS
+WITH
+uninterruptible_sleep AS (
+ SELECT ts, dur FROM thread_state
+ where state = 'D'
+)
+SELECT
+ ts, value as uninterruptible_sleep_thread_count
+FROM intervals_overlap_count!(uninterruptible_sleep, ts, dur)
+ORDER BY ts;
+
-- The count of active CPUs over time.
CREATE PERFETTO TABLE sched_active_cpu_count(
-- Timestamp when the number of active CPU changed.
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/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 8397d8d..b9d4c47 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -501,7 +501,7 @@
for (int repeat = 0; repeat < 3; repeat++) {
ASSERT_EQ(RestoreInitialTables(), 0u);
{
- auto it = Query("INCLUDE PERFETTO MODULE common.timestamps;");
+ auto it = Query("INCLUDE PERFETTO MODULE time.conversion;");
it.Next();
ASSERT_TRUE(it.Status().ok());
}
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/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc
index 60cce1f..c4a6fc7 100644
--- a/test/cmdline_integrationtest.cc
+++ b/test/cmdline_integrationtest.cc
@@ -47,6 +47,7 @@
using ::testing::ContainsRegex;
using ::testing::Each;
+using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::HasSubstr;
@@ -99,6 +100,52 @@
return trace_config;
}
+// For the regular tests.
+TraceConfig CreateTraceConfigForTest(uint32_t test_msg_count = 11,
+ uint32_t test_msg_size = 32) {
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(1024);
+ auto* ds_config = trace_config.add_data_sources()->mutable_config();
+ ds_config->set_name("android.perfetto.FakeProducer");
+ ds_config->mutable_for_testing()->set_message_count(test_msg_count);
+ ds_config->mutable_for_testing()->set_message_size(test_msg_size);
+ return trace_config;
+}
+
+void ExpectTraceContainsTestMessages(const protos::gen::Trace& trace,
+ uint32_t count) {
+ ssize_t actual_test_packets_count = std::count_if(
+ trace.packet().begin(), trace.packet().end(),
+ [](const protos::gen::TracePacket& tp) { return tp.has_for_testing(); });
+ EXPECT_EQ(count, static_cast<uint32_t>(actual_test_packets_count));
+}
+
+void ExpectTraceContainsTestMessagesWithSize(const protos::gen::Trace& trace,
+ uint32_t message_size) {
+ for (const auto& packet : trace.packet()) {
+ if (packet.has_for_testing()) {
+ EXPECT_EQ(message_size, packet.for_testing().str().size());
+ }
+ }
+}
+
+void ExpectTraceContainsConfigWithTriggerMode(
+ const protos::gen::Trace& trace,
+ protos::gen::TraceConfig::TriggerConfig::TriggerMode trigger_mode) {
+ // GTest three level nested Property matcher is hard to read, so we use
+ // 'find_if' with lambda to ensure the trace config properly includes the
+ // trigger mode we set.
+ auto found =
+ std::find_if(trace.packet().begin(), trace.packet().end(),
+ [trigger_mode](const protos::gen::TracePacket& tp) {
+ return tp.has_trace_config() &&
+ tp.trace_config().trigger_config().trigger_mode() ==
+ trigger_mode;
+ });
+ EXPECT_NE(found, trace.packet().end())
+ << "Trace config doesn't include expected trigger mode.";
+}
+
class ScopedFileRemove {
public:
explicit ScopedFileRemove(const std::string& path) : path_(path) {}
@@ -106,6 +153,27 @@
std::string path_;
};
+bool ParseNotEmptyTraceFromFile(const std::string& trace_path,
+ protos::gen::Trace& out) {
+ std::string trace_str;
+ if (!base::ReadFile(trace_path, &trace_str))
+ return false;
+ if (trace_str.empty())
+ return false;
+ return out.ParseFromString(trace_str);
+}
+
+std::vector<std::string> GetReceivedTriggerNames(
+ const protos::gen::Trace& trace) {
+ std::vector<std::string> triggers;
+ for (const protos::gen::TracePacket& packet : trace.packet()) {
+ if (packet.has_trigger()) {
+ triggers.push_back(packet.trigger().trigger_name());
+ }
+ }
+ return triggers;
+}
+
class PerfettoCmdlineTest : public ::testing::Test {
public:
void StartServiceIfRequiredNoNewExecsAfterThis() {
@@ -190,11 +258,8 @@
// Read the trace written in the fixed location
// (/data/misc/perfetto-traces/ on Android, /tmp/ on Linux/Mac) and make
// sure it has the right contents.
- std::string trace_str;
- base::ReadFile(trace_path, &trace_str);
- ASSERT_FALSE(trace_str.empty());
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str));
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(trace_path, trace));
uint32_t test_packets = 0;
for (const auto& p : trace.packet())
test_packets += p.has_for_testing() ? 1 : 0;
@@ -212,6 +277,11 @@
std::string stderr_;
base::TestTaskRunner task_runner_;
+ // We use these two constants to set test data payload parameters and assert
+ // it was correctly written to the trace.
+ static constexpr size_t kTestMessageCount = 11;
+ static constexpr size_t kTestMessageSize = 32;
+
private:
bool exec_allowed_ = true;
TestHelper test_helper_{&task_runner_};
@@ -350,15 +420,8 @@
}
TEST_F(PerfettoCmdlineTest, StartTracingTrigger) {
- // See |message_count| and |message_size| in the TraceConfig above.
- constexpr size_t kMessageCount = 11;
- constexpr size_t kMessageSize = 32;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::START_TRACING);
@@ -404,53 +467,25 @@
test_helper().WaitForProducerSetup();
EXPECT_EQ(0, trigger_proc.Run(&stderr_));
- // Wait for the producer to start, and then write out 11 packets.
+ // Wait for the producer to start, and then write out some test packets.
test_helper().WaitForProducerEnabled();
auto on_data_written = task_runner_.CreateCheckpoint("data_written");
fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written");
background_trace.join();
- std::string trace_str;
- base::ReadFile(path, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str));
- size_t for_testing_packets = 0;
- size_t trigger_packets = 0;
- size_t trace_config_packets = 0;
- for (const auto& packet : trace.packet()) {
- if (packet.has_trace_config()) {
- // Ensure the trace config properly includes the trigger mode we set.
- auto kStartTrig = protos::gen::TraceConfig::TriggerConfig::START_TRACING;
- EXPECT_EQ(kStartTrig,
- packet.trace_config().trigger_config().trigger_mode());
- ++trace_config_packets;
- } else if (packet.has_trigger()) {
- // validate that the triggers are properly added to the trace.
- EXPECT_EQ("trigger_name", packet.trigger().trigger_name());
- ++trigger_packets;
- } else if (packet.has_for_testing()) {
- // Make sure that the data size is correctly set based on what we
- // requested.
- EXPECT_EQ(kMessageSize, packet.for_testing().str().size());
- ++for_testing_packets;
- }
- }
- EXPECT_EQ(trace_config_packets, 1u);
- EXPECT_EQ(trigger_packets, 1u);
- EXPECT_EQ(for_testing_packets, kMessageCount);
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path, trace));
+ ExpectTraceContainsConfigWithTriggerMode(
+ trace, protos::gen::TraceConfig::TriggerConfig::START_TRACING);
+ EXPECT_THAT(GetReceivedTriggerNames(trace), ElementsAre("trigger_name"));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
}
TEST_F(PerfettoCmdlineTest, StopTracingTrigger) {
- // See |message_count| and |message_size| in the TraceConfig above.
- constexpr size_t kMessageCount = 11;
- constexpr size_t kMessageSize = 32;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
@@ -497,8 +532,8 @@
});
test_helper().WaitForProducerEnabled();
- // Wait for the producer to start, and then write out 11 packets, before the
- // trace actually starts (the trigger is seen).
+ // Wait for the producer to start, and then write out some test packets,
+ // before the trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
@@ -507,56 +542,23 @@
background_trace.join();
- std::string trace_str;
- base::ReadFile(path, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str));
- bool seen_first_trigger = false;
- size_t for_testing_packets = 0;
- size_t trigger_packets = 0;
- size_t trace_config_packets = 0;
- for (const auto& packet : trace.packet()) {
- if (packet.has_trace_config()) {
- // Ensure the trace config properly includes the trigger mode we set.
- auto kStopTrig = protos::gen::TraceConfig::TriggerConfig::STOP_TRACING;
- EXPECT_EQ(kStopTrig,
- packet.trace_config().trigger_config().trigger_mode());
- ++trace_config_packets;
- } else if (packet.has_trigger()) {
- // validate that the triggers are properly added to the trace.
- if (!seen_first_trigger) {
- EXPECT_EQ("trigger_name", packet.trigger().trigger_name());
- seen_first_trigger = true;
- } else {
- EXPECT_EQ("trigger_name_3", packet.trigger().trigger_name());
- }
- ++trigger_packets;
- } else if (packet.has_for_testing()) {
- // Make sure that the data size is correctly set based on what we
- // requested.
- EXPECT_EQ(kMessageSize, packet.for_testing().str().size());
- ++for_testing_packets;
- }
- }
- EXPECT_EQ(trace_config_packets, 1u);
- EXPECT_EQ(trigger_packets, 2u);
- EXPECT_EQ(for_testing_packets, kMessageCount);
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path, trace));
+ ExpectTraceContainsConfigWithTriggerMode(
+ trace, protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
+ EXPECT_THAT(GetReceivedTriggerNames(trace),
+ ElementsAre("trigger_name", "trigger_name_3"));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
}
// Dropbox on the commandline client only works on android builds. So disable
// this test on all other builds.
TEST_F(PerfettoCmdlineTest, AndroidOnly(NoDataNoFileWithoutTrigger)) {
- // See |message_count| and |message_size| in the TraceConfig above.
- constexpr size_t kMessageCount = 11;
- constexpr size_t kMessageSize = 32;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* incident_config = trace_config.mutable_incident_report_config();
incident_config->set_destination_package("foo.bar.baz");
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
@@ -601,15 +603,8 @@
}
TEST_F(PerfettoCmdlineTest, StopTracingTriggerFromConfig) {
- // See |message_count| and |message_size| in the TraceConfig above.
- constexpr size_t kMessageCount = 11;
- constexpr size_t kMessageSize = 32;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
@@ -666,8 +661,8 @@
});
test_helper().WaitForProducerEnabled();
- // Wait for the producer to start, and then write out 11 packets, before the
- // trace actually starts (the trigger is seen).
+ // Wait for the producer to start, and then write out some test packets,
+ // before the trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
@@ -676,44 +671,20 @@
background_trace.join();
- std::string trace_str;
- base::ReadFile(path, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str));
- EXPECT_LT(static_cast<int>(kMessageCount), trace.packet_size());
- bool seen_first_trigger = false;
- for (const auto& packet : trace.packet()) {
- if (packet.has_trace_config()) {
- // Ensure the trace config properly includes the trigger mode we set.
- auto kStopTrig = protos::gen::TraceConfig::TriggerConfig::STOP_TRACING;
- EXPECT_EQ(kStopTrig,
- packet.trace_config().trigger_config().trigger_mode());
- } else if (packet.has_trigger()) {
- // validate that the triggers are properly added to the trace.
- if (!seen_first_trigger) {
- EXPECT_EQ("trigger_name", packet.trigger().trigger_name());
- seen_first_trigger = true;
- } else {
- EXPECT_EQ("trigger_name_3", packet.trigger().trigger_name());
- }
- } else if (packet.has_for_testing()) {
- // Make sure that the data size is correctly set based on what we
- // requested.
- EXPECT_EQ(kMessageSize, packet.for_testing().str().size());
- }
- }
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path, trace));
+ EXPECT_LT(static_cast<int>(kTestMessageCount), trace.packet_size());
+ ExpectTraceContainsConfigWithTriggerMode(
+ trace, protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
+ EXPECT_THAT(GetReceivedTriggerNames(trace),
+ ElementsAre("trigger_name", "trigger_name_3"));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
}
TEST_F(PerfettoCmdlineTest, TriggerFromConfigStopsFileOpening) {
- // See |message_count| and |message_size| in the TraceConfig above.
- constexpr size_t kMessageCount = 11;
- constexpr size_t kMessageSize = 32;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
@@ -772,15 +743,8 @@
}
TEST_F(PerfettoCmdlineTest, AndroidOnly(CmdTriggerWithUploadFlag)) {
- // See |message_count| and |message_size| in the TraceConfig above.
- constexpr size_t kMessageCount = 2;
- constexpr size_t kMessageSize = 2;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::STOP_TRACING);
@@ -831,8 +795,8 @@
});
test_helper().WaitForProducerEnabled();
- // Wait for the producer to start, and then write out 11 packets, before the
- // trace actually starts (the trigger is seen).
+ // Wait for the producer to start, and then write out some test packets,
+ // before the trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
@@ -841,11 +805,11 @@
background_trace.join();
- std::string trace_str;
- base::ReadFile(path, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str));
- EXPECT_LT(static_cast<int>(kMessageCount), trace.packet_size());
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path, trace));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
+ EXPECT_LT(static_cast<int>(kTestMessageCount), trace.packet_size());
EXPECT_THAT(trace.packet(),
Contains(Property(&protos::gen::TracePacket::trigger,
Property(&protos::gen::Trigger::trigger_name,
@@ -853,14 +817,8 @@
}
TEST_F(PerfettoCmdlineTest, TriggerCloneSnapshot) {
- constexpr size_t kMessageCount = 2;
- constexpr size_t kMessageSize = 2;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
auto* trigger_cfg = trace_config.mutable_trigger_config();
trigger_cfg->set_trigger_mode(
protos::gen::TraceConfig::TriggerConfig::CLONE_SNAPSHOT);
@@ -910,8 +868,8 @@
});
test_helper().WaitForProducerEnabled();
- // Wait for the producer to start, and then write out 11 packets, before the
- // trace actually starts (the trigger is seen).
+ // Wait for the producer to start, and then write out some test packets,
+ // before the trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
@@ -931,11 +889,11 @@
perfetto_proc.SendSigterm();
background_trace.join();
- std::string trace_str;
- base::ReadFile(snapshot_path, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str));
- EXPECT_LT(static_cast<int>(kMessageCount), trace.packet_size());
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(snapshot_path, trace));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
+ EXPECT_LT(static_cast<int>(kTestMessageCount), trace.packet_size());
EXPECT_THAT(trace.packet(),
Contains(Property(&protos::gen::TracePacket::trigger,
Property(&protos::gen::Trigger::trigger_name,
@@ -961,14 +919,9 @@
}
TEST_F(PerfettoCmdlineTest, CloneByName) {
- constexpr size_t kMessageCount = 2;
- protos::gen::TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(1024);
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
trace_config.set_unique_session_name("my_unique_session_name");
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(kMessageCount);
- ds_config->mutable_for_testing()->set_message_size(2);
// We have to construct all the processes we want to fork before we start the
// service with |StartServiceIfRequired()|. this is because it is unsafe
@@ -1026,26 +979,18 @@
EXPECT_EQ(0, perfetto_proc_clone_2.Run(&stderr_)) << "stderr: " << stderr_;
EXPECT_FALSE(base::FileExists(path_cloned_2));
- std::string cloned_trace_str;
- base::ReadFile(path_cloned, &cloned_trace_str);
protos::gen::Trace cloned_trace;
- ASSERT_TRUE(cloned_trace.ParseFromString(cloned_trace_str));
- ssize_t cloned_num_test_packets = std::count_if(
- cloned_trace.packet().begin(), cloned_trace.packet().end(),
- [](const protos::gen::TracePacket& tp) { return tp.has_for_testing(); });
- EXPECT_EQ(cloned_num_test_packets, static_cast<ssize_t>(kMessageCount));
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path_cloned, cloned_trace));
+ ExpectTraceContainsTestMessages(cloned_trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(cloned_trace, kTestMessageSize);
perfetto_proc.SendSigterm();
background_trace.join();
- std::string trace_str;
- base::ReadFile(path, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(cloned_trace_str));
- ssize_t num_test_packets = std::count_if(
- trace.packet().begin(), trace.packet().end(),
- [](const protos::gen::TracePacket& tp) { return tp.has_for_testing(); });
- EXPECT_EQ(num_test_packets, static_cast<ssize_t>(kMessageCount));
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path, trace));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
}
// Regression test for b/279753347: --save-for-bugreport would create an empty
@@ -1185,10 +1130,8 @@
auto check_trace = [&](std::string fname, int expected_score) {
std::string fpath = GetBugreportTraceDir() + "/" + fname;
ASSERT_TRUE(base::FileExists(fpath)) << fpath;
- std::string trace_str;
- base::ReadFile(fpath, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str)) << fpath;
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(fpath, trace)) << fpath;
EXPECT_THAT(
trace.packet(),
Contains(Property(&protos::gen::TracePacket::trace_config,
@@ -1211,8 +1154,9 @@
auto remove_on_exit = base::OnScopeExit(remove_br_files);
const uint32_t kMsgCount = 10000;
+ const uint32_t kMsgSize = 1024;
TraceConfig cfg = CreateTraceConfigForBugreportTest(
- /*score=*/1, /*add_filter=*/false, kMsgCount, /*msg_size=*/1024);
+ /*score=*/1, /*add_filter=*/false, kMsgCount, kMsgSize);
auto session_name = "bugreport_test_" +
std::to_string(base::GetWallTimeNs().count() % 1000000);
@@ -1263,14 +1207,10 @@
std::string fpath = GetBugreportTraceDir() + "/systrace.pftrace";
ASSERT_TRUE(base::FileExists(fpath)) << fpath;
- std::string trace_str;
- base::ReadFile(fpath, &trace_str);
protos::gen::Trace trace;
- ASSERT_TRUE(trace.ParseFromString(trace_str)) << fpath;
- ssize_t num_test_packets = std::count_if(
- trace.packet().begin(), trace.packet().end(),
- [](const protos::gen::TracePacket& tp) { return tp.has_for_testing(); });
- EXPECT_EQ(num_test_packets, static_cast<ssize_t>(kMsgCount));
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(fpath, trace)) << fpath;
+ ExpectTraceContainsTestMessages(trace, kMsgCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kMsgSize);
}
} // namespace perfetto
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 92108de..69c1bc7 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -117,8 +117,6 @@
from diff_tests.stdlib.android.startups_tests import Startups
from diff_tests.stdlib.android.tests import AndroidStdlib
from diff_tests.stdlib.chrome.chrome_stdlib_testsuites import CHROME_STDLIB_TESTSUITES
-from diff_tests.stdlib.common.tests import StdlibCommon
-from diff_tests.stdlib.common.tests import StdlibCommon
from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
from diff_tests.stdlib.export.tests import ExportTests
@@ -326,7 +324,6 @@
*Pkvm(index_path, 'stdlib/pkvm', 'Pkvm').fetch(),
*PreludeSlices(index_path, 'stdlib/prelude', 'PreludeSlices').fetch(),
*StdlibSmoke(index_path, 'stdlib', 'StdlibSmoke').fetch(),
- *StdlibCommon(index_path, 'stdlib/common', 'StdlibCommon').fetch(),
*Slices(index_path, 'stdlib/slices', 'Slices').fetch(),
*SpanJoinLeftJoin(index_path, 'stdlib/span_join',
'SpanJoinLeftJoin').fetch(),
@@ -335,7 +332,6 @@
*SpanJoinRegression(index_path, 'stdlib/span_join',
'SpanJoinRegression').fetch(),
*SpanJoinSmoke(index_path, 'stdlib/span_join', 'SpanJoinSmoke').fetch(),
- *StdlibCommon(index_path, 'stdlib/common', 'StdlibCommon').fetch(),
*StdlibIntervals(index_path, 'stdlib/intervals',
'StdlibIntervals').fetch(),
*IntervalsIntersect(index_path, 'stdlib/intervals',
diff --git a/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
index 597cdcd..1ce67ab 100644
--- a/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
@@ -492,7 +492,7 @@
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
SELECT
- HAS_DESCENDANT_SLICE_WITH_NAME(
+ _HAS_DESCENDANT_SLICE_WITH_NAME(
(SELECT id from slice where dur = 60156000),
'SwapEndToPresentationCompositorFrame') AS has_descendant;
""",
@@ -510,7 +510,7 @@
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
SELECT
- HAS_DESCENDANT_SLICE_WITH_NAME(
+ _HAS_DESCENDANT_SLICE_WITH_NAME(
(SELECT id from slice where dur = 77247000),
'SwapEndToPresentationCompositorFrame') AS has_descendant;
""",
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup.out b/test/trace_processor/diff_tests/metrics/startup/android_startup.out
index 42368b3..ec2b075 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup.out
@@ -78,11 +78,15 @@
}
launch_dur: 108
trace_thread_sections {
+ thread_section {
+ start_timestamp: 130
+ end_timestamp: 210
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 130
end_timestamp: 210
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
startup_type: "warm"
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
index 2d6070c..8b53348 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
@@ -148,12 +148,16 @@
}
launch_dur: 999999900
trace_slice_sections {
+ slice_section {
+ start_timestamp: 340
+ end_timestamp: 390
+ slice_id: 20
+ slice_name: "CollectorTransition mark sweep GC"
+ process_pid: 3
+ thread_tid: 5
+ }
start_timestamp: 340
end_timestamp: 390
- slice_id: 20
- slice_name: "CollectorTransition mark sweep GC"
- process_pid: 3
- thread_tid: 5
}
}
slow_start_reason_with_details {
@@ -171,20 +175,24 @@
}
launch_dur: 999999900
trace_slice_sections {
- start_timestamp: 170
- end_timestamp: 500000000
- slice_id: 9
- slice_name: "OpenDexFilesFromOat(something else)"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
+ slice_section {
+ start_timestamp: 170
+ end_timestamp: 500000000
+ slice_id: 9
+ slice_name: "OpenDexFilesFromOat(something else)"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 150
+ end_timestamp: 165
+ slice_id: 5
+ slice_name: "OpenDexFilesFromOat(something)"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 150
- end_timestamp: 165
- slice_id: 5
- slice_name: "OpenDexFilesFromOat(something)"
- process_pid: 3
- thread_tid: 3
+ end_timestamp: 500000000
}
}
slow_start_reason_with_details {
@@ -200,12 +208,16 @@
}
launch_dur: 999999900
trace_slice_sections {
+ slice_section {
+ start_timestamp: 10000000
+ end_timestamp: 50000000
+ slice_id: 21
+ slice_name: "binder transaction"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 10000000
end_timestamp: 50000000
- slice_id: 21
- slice_name: "binder transaction"
- process_pid: 3
- thread_tid: 3
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
index a98505f..a4e1074 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
@@ -107,12 +107,16 @@
}
launch_dur: 999999900000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 340000000000
+ end_timestamp: 390000000000
+ slice_id: 91
+ slice_name: "CollectorTransition mark sweep GC"
+ process_pid: 3
+ thread_tid: 5
+ }
start_timestamp: 340000000000
end_timestamp: 390000000000
- slice_id: 91
- slice_name: "CollectorTransition mark sweep GC"
- process_pid: 3
- thread_tid: 5
}
}
slow_start_reason_with_details {
@@ -129,25 +133,29 @@
}
launch_dur: 999999900000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 155000000000
+ end_timestamp: 165000000000
+ thread_name: "Jit thread pool"
+ process_pid: 3
+ thread_tid: 4
+ }
+ thread_section {
+ start_timestamp: 170000000000
+ end_timestamp: 175000000000
+ thread_name: "Jit thread pool"
+ process_pid: 3
+ thread_tid: 4
+ }
+ thread_section {
+ start_timestamp: 185000000000
+ end_timestamp: 190000000000
+ thread_name: "Jit thread pool"
+ process_pid: 3
+ thread_tid: 4
+ }
start_timestamp: 155000000000
- end_timestamp: 165000000000
- thread_name: "Jit thread pool"
- thread_tid: 4
- process_pid: 3
- }
- trace_thread_sections {
- start_timestamp: 170000000000
- end_timestamp: 175000000000
- thread_name: "Jit thread pool"
- thread_tid: 4
- process_pid: 3
- }
- trace_thread_sections {
- start_timestamp: 185000000000
end_timestamp: 190000000000
- thread_name: "Jit thread pool"
- thread_tid: 4
- process_pid: 3
}
}
slow_start_reason_with_details {
@@ -164,28 +172,32 @@
}
launch_dur: 999999900000000000
trace_slice_sections {
- start_timestamp: 200000000000
- end_timestamp: 210000000000
- slice_id: 84
- slice_name: "JIT compiling nothing"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
+ slice_section {
+ start_timestamp: 200000000000
+ end_timestamp: 210000000000
+ slice_id: 84
+ slice_name: "JIT compiling nothing"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 100000000000
+ end_timestamp: 101000000000
+ slice_id: 9
+ slice_name: "JIT compiling something"
+ process_pid: 3
+ thread_tid: 4
+ }
+ slice_section {
+ start_timestamp: 101000000000
+ end_timestamp: 102000000000
+ slice_id: 10
+ slice_name: "JIT compiling something"
+ process_pid: 3
+ thread_tid: 4
+ }
start_timestamp: 100000000000
- end_timestamp: 101000000000
- slice_id: 9
- slice_name: "JIT compiling something"
- process_pid: 3
- thread_tid: 4
- }
- trace_slice_sections {
- start_timestamp: 101000000000
- end_timestamp: 102000000000
- slice_id: 10
- slice_name: "JIT compiling something"
- process_pid: 3
- thread_tid: 4
+ end_timestamp: 210000000000
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
index 2405d7e..04b8915 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
@@ -128,12 +128,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 204000000000
+ end_timestamp: 205000000000
+ slice_id: 13
+ slice_name: "location=/system/framework/oat/arm/com.google.android.calendar.odex status=up-to-date filter=speed reason=install-dm"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 204000000000
end_timestamp: 205000000000
- slice_id: 13
- slice_name: "location=/system/framework/oat/arm/com.google.android.calendar.odex status=up-to-date filter=speed reason=install-dm"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -149,12 +153,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 200000000000
+ end_timestamp: 202000000000
+ slice_id: 12
+ slice_name: "location=error status=io-error-no-oat filter=run-from-apk reason=unknown"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 200000000000
end_timestamp: 202000000000
- slice_id: 12
- slice_name: "location=error status=io-error-no-oat filter=run-from-apk reason=unknown"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -171,12 +179,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 185000000000
+ end_timestamp: 187000000000
+ slice_id: 4
+ slice_name: "bindApplication"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 185000000000
end_timestamp: 187000000000
- slice_id: 4
- slice_name: "bindApplication"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -193,20 +205,24 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 188000000000
+ end_timestamp: 189000000000
+ slice_id: 6
+ slice_name: "inflate"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 191000000000
+ end_timestamp: 192000000000
+ slice_id: 8
+ slice_name: "inflate"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 188000000000
- end_timestamp: 189000000000
- slice_id: 6
- slice_name: "inflate"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
- start_timestamp: 191000000000
end_timestamp: 192000000000
- slice_id: 8
- slice_name: "inflate"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -223,12 +239,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 188000000000
+ end_timestamp: 189000000000
+ slice_id: 7
+ slice_name: "ResourcesManager#getResources"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 188000000000
end_timestamp: 189000000000
- slice_id: 7
- slice_name: "ResourcesManager#getResources"
- thread_tid: 3
- process_pid: 3
}
}
slow_start_reason_with_details {
@@ -245,11 +265,15 @@
}
launch_dur: 108000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 205000000000
+ end_timestamp: 210000000000
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 205000000000
end_timestamp: 210000000000
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
startup_type: "cold"
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
index 3dbb2aa..f2c7123 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
@@ -127,12 +127,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 200000000000
+ end_timestamp: 202000000000
+ slice_id: 12
+ slice_name: "location=error status=io-error-no-oat filter=run-from-apk reason=unknown"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 200000000000
end_timestamp: 202000000000
- slice_id: 12
- slice_name: "location=error status=io-error-no-oat filter=run-from-apk reason=unknown"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -149,12 +153,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 185000000000
+ end_timestamp: 195000000000
+ slice_id: 4
+ slice_name: "bindApplication"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 185000000000
end_timestamp: 195000000000
- slice_id: 4
- slice_name: "bindApplication"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -171,20 +179,24 @@
}
launch_dur: 108000000000
trace_slice_sections {
- start_timestamp: 190000000000
- end_timestamp: 192000000000
- slice_id: 8
- slice_name: "inflate"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
+ slice_section {
+ start_timestamp: 190000000000
+ end_timestamp: 192000000000
+ slice_id: 8
+ slice_name: "inflate"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 188000000000
+ end_timestamp: 189000000000
+ slice_id: 7
+ slice_name: "inflate"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 188000000000
- end_timestamp: 189000000000
- slice_id: 7
- slice_name: "inflate"
- process_pid: 3
- thread_tid: 3
+ end_timestamp: 192000000000
}
}
slow_start_reason_with_details {
@@ -201,12 +213,16 @@
}
launch_dur: 108000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 187000000000
+ end_timestamp: 192000000000
+ slice_id: 5
+ slice_name: "ResourcesManager#getResources"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 187000000000
end_timestamp: 192000000000
- slice_id: 5
- slice_name: "ResourcesManager#getResources"
- thread_tid: 3
- process_pid: 3
}
}
slow_start_reason_with_details {
@@ -223,11 +239,15 @@
}
launch_dur: 108000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 205000000000
+ end_timestamp: 210000000000
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 205000000000
end_timestamp: 210000000000
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
startup_type: "cold"
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
index 4913852..6cb97e0 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
@@ -46,25 +46,29 @@
}
launch_dur: 100
trace_slice_sections {
+ slice_section {
+ start_timestamp: 105
+ end_timestamp: 106
+ slice_id: 6
+ slice_name: "Broadcast dispatched from android (2005:system/1000) x"
+ thread_tid: 1
+ }
+ slice_section {
+ start_timestamp: 106
+ end_timestamp: 107
+ slice_id: 8
+ slice_name: "Broadcast dispatched from android (2005:system/1000) x"
+ thread_tid: 1
+ }
+ slice_section {
+ start_timestamp: 107
+ end_timestamp: 108
+ slice_id: 10
+ slice_name: "Broadcast dispatched from android (2005:system/1000) x"
+ thread_tid: 1
+ }
start_timestamp: 105
- end_timestamp: 106
- slice_id: 6
- slice_name: "Broadcast dispatched from android (2005:system/1000) x"
- thread_tid: 1
- }
- trace_slice_sections {
- start_timestamp: 106
- end_timestamp: 107
- slice_id: 8
- slice_name: "Broadcast dispatched from android (2005:system/1000) x"
- thread_tid: 1
- }
- trace_slice_sections {
- start_timestamp: 107
end_timestamp: 108
- slice_id: 10
- slice_name: "Broadcast dispatched from android (2005:system/1000) x"
- thread_tid: 1
}
}
slow_start_reason_with_details {
@@ -81,25 +85,29 @@
}
launch_dur: 100
trace_slice_sections {
+ slice_section {
+ start_timestamp: 100
+ end_timestamp: 101
+ slice_id: 1
+ slice_name: "broadcastReceiveReg: x"
+ thread_tid: 2
+ }
+ slice_section {
+ start_timestamp: 101
+ end_timestamp: 102
+ slice_id: 2
+ slice_name: "broadcastReceiveReg: x"
+ thread_tid: 2
+ }
+ slice_section {
+ start_timestamp: 102
+ end_timestamp: 103
+ slice_id: 3
+ slice_name: "broadcastReceiveReg: x"
+ thread_tid: 2
+ }
start_timestamp: 100
- end_timestamp: 101
- slice_id: 1
- slice_name: "broadcastReceiveReg: x"
- thread_tid: 2
- }
- trace_slice_sections {
- start_timestamp: 101
- end_timestamp: 102
- slice_id: 2
- slice_name: "broadcastReceiveReg: x"
- thread_tid: 2
- }
- trace_slice_sections {
- start_timestamp: 102
end_timestamp: 103
- slice_id: 3
- slice_name: "broadcastReceiveReg: x"
- thread_tid: 2
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
index dabbb5f..f64dceb 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
@@ -82,12 +82,16 @@
}
launch_dur: 100000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 112000000000
+ end_timestamp: 115000000000
+ slice_id: 1
+ slice_name: "bindApplication"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 112000000000
end_timestamp: 115000000000
- slice_id: 1
- slice_name: "bindApplication"
- process_pid: 3
- thread_tid: 3
}
}
slow_start_reason_with_details {
@@ -105,28 +109,32 @@
}
launch_dur: 100000000000
trace_slice_sections {
- start_timestamp: 140000000000
- end_timestamp: 157000000000
- slice_id: 5
- slice_name: "Lock contention on a monitor lock (owner tid: 2)"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
+ slice_section {
+ start_timestamp: 140000000000
+ end_timestamp: 157000000000
+ slice_id: 5
+ slice_name: "Lock contention on a monitor lock (owner tid: 2)"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 120000000000
+ end_timestamp: 130000000000
+ slice_id: 4
+ slice_name: "Lock contention on thread list lock (owner tid: 2)"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 155000000000
+ end_timestamp: 160000000000
+ slice_id: 6
+ slice_name: "Lock contention on a monitor lock (owner tid: 3)"
+ process_pid: 3
+ thread_tid: 4
+ }
start_timestamp: 120000000000
- end_timestamp: 130000000000
- slice_id: 4
- slice_name: "Lock contention on thread list lock (owner tid: 2)"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
- start_timestamp: 155000000000
end_timestamp: 160000000000
- slice_id: 6
- slice_name: "Lock contention on a monitor lock (owner tid: 3)"
- process_pid: 3
- thread_tid: 4
}
}
slow_start_reason_with_details {
@@ -144,20 +152,24 @@
}
launch_dur: 100000000000
trace_slice_sections {
+ slice_section {
+ start_timestamp: 140000000000
+ end_timestamp: 157000000000
+ slice_id: 5
+ slice_name: "Lock contention on a monitor lock (owner tid: 2)"
+ process_pid: 3
+ thread_tid: 3
+ }
+ slice_section {
+ start_timestamp: 155000000000
+ end_timestamp: 160000000000
+ slice_id: 6
+ slice_name: "Lock contention on a monitor lock (owner tid: 3)"
+ process_pid: 3
+ thread_tid: 4
+ }
start_timestamp: 140000000000
- end_timestamp: 157000000000
- slice_id: 5
- slice_name: "Lock contention on a monitor lock (owner tid: 2)"
- process_pid: 3
- thread_tid: 3
- }
- trace_slice_sections {
- start_timestamp: 155000000000
end_timestamp: 160000000000
- slice_id: 6
- slice_name: "Lock contention on a monitor lock (owner tid: 3)"
- process_pid: 3
- thread_tid: 4
}
}
startup_type: "cold"
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
index ded275e..17d426d 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
@@ -77,11 +77,15 @@
}
launch_dur: 7
trace_thread_sections {
+ thread_section {
+ start_timestamp: 103
+ end_timestamp: 107
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 103
end_timestamp: 107
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
}
@@ -164,11 +168,15 @@
}
launch_dur: 7
trace_thread_sections {
+ thread_section {
+ start_timestamp: 203
+ end_timestamp: 207
+ thread_name: "com.google.android.calendar"
+ process_pid: 4
+ thread_tid: 4
+ }
start_timestamp: 203
end_timestamp: 207
- thread_name: "com.google.android.calendar"
- thread_tid: 4
- process_pid: 4
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
index 942037e..0ae5b29 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
@@ -81,11 +81,15 @@
}
launch_dur: 108000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 130000000000
+ end_timestamp: 210000000000
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 130000000000
end_timestamp: 210000000000
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
slow_start_reason_with_details {
@@ -102,11 +106,15 @@
}
launch_dur: 108000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 120000000000
+ end_timestamp: 125000000000
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 120000000000
end_timestamp: 125000000000
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
slow_start_reason_with_details {
@@ -123,11 +131,15 @@
}
launch_dur: 108000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 125000000000
+ end_timestamp: 130000000000
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 125000000000
end_timestamp: 130000000000
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
slow_start_reason_with_details {
@@ -144,11 +156,15 @@
}
launch_dur: 108000000000
trace_thread_sections {
+ thread_section {
+ start_timestamp: 130000000000
+ end_timestamp: 210000000000
+ thread_name: "com.google.android.calendar"
+ process_pid: 3
+ thread_tid: 3
+ }
start_timestamp: 130000000000
end_timestamp: 210000000000
- thread_name: "com.google.android.calendar"
- thread_tid: 3
- process_pid: 3
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
index 3f52432..1f2000d 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
@@ -44,11 +44,15 @@
}
launch_dur: 100
trace_slice_sections {
+ slice_section {
+ start_timestamp: 130
+ end_timestamp: 133
+ slice_id: 1
+ slice_name: "KeyguardUpdateMonitor#onAuthenticationSucceeded"
+ thread_tid: 2
+ }
start_timestamp: 130
end_timestamp: 133
- slice_id: 1
- slice_name: "KeyguardUpdateMonitor#onAuthenticationSucceeded"
- thread_tid: 2
}
}
}
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/common/tests.py b/test/trace_processor/diff_tests/stdlib/common/tests.py
deleted file mode 100644
index d103320..0000000
--- a/test/trace_processor/diff_tests/stdlib/common/tests.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2023 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 a
-#
-# 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.
-
-from python.generators.diff_tests.testing import Path, DataPath, Metric
-from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
-from python.generators.diff_tests.testing import TestSuite
-
-
-class StdlibCommon(TestSuite):
-
- def test_spans_overlapping_dur_intersect_edge(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(0, 2, 1, 2) AS dur
- """,
- out=Csv("""
- "dur"
- 1
- """))
-
- def test_spans_overlapping_dur_intersect_edge_reversed(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(1, 2, 0, 2) AS dur
- """,
- out=Csv("""
- "dur"
- 1
- """))
-
- def test_spans_overlapping_dur_intersect_all(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(0, 3, 1, 1) AS dur
- """,
- out=Csv("""
- "dur"
- 1
- """))
-
- def test_spans_overlapping_dur_intersect_all_reversed(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(1, 1, 0, 3) AS dur
- """,
- out=Csv("""
- "dur"
- 1
- """))
-
- def test_spans_overlapping_dur_no_intersect(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(0, 1, 2, 1) AS dur
- """,
- out=Csv("""
- "dur"
- 0
- """))
-
- def test_spans_overlapping_dur_no_intersect_reversed(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(2, 1, 0, 1) AS dur
- """,
- out=Csv("""
- "dur"
- 0
- """))
-
- def test_spans_overlapping_dur_negative_dur(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(0, -1, 0, 1) AS dur
- """,
- out=Csv("""
- "dur"
- 0
- """))
-
- def test_spans_overlapping_dur_negative_dur_reversed(self):
- return DiffTestBlueprint(
- trace=TextProto(r"""
-
- """),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT SPANS_OVERLAPPING_DUR(0, 1, 0, -1) AS dur
- """,
- out=Csv("""
- "dur"
- 0
- """))
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/test/trace_processor/diff_tests/stdlib/timestamps/tests.py b/test/trace_processor/diff_tests/stdlib/timestamps/tests.py
index a029ce5..d5cc34b 100644
--- a/test/trace_processor/diff_tests/stdlib/timestamps/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/timestamps/tests.py
@@ -22,86 +22,60 @@
class Timestamps(TestSuite):
- def test_ns(self):
+ def test_to_time(self):
return DiffTestBlueprint(
trace=TextProto(""),
query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT ns(4) as result;
+ INCLUDE PERFETTO MODULE time.conversion;
+
+ WITH data(unit, time) AS (
+ VALUES
+ ('ns', time_to_ns(cast_int!(1e14))),
+ ('us', time_to_us(cast_int!(1e14))),
+ ('ms', time_to_ms(cast_int!(1e14))),
+ ('s', time_to_s(cast_int!(1e14))),
+ ('min', time_to_min(cast_int!(1e14))),
+ ('h', time_to_hours(cast_int!(1e14))),
+ ('days', time_to_days(cast_int!(1e14)))
+ )
+ SELECT * FROM data
""",
out=Csv("""
- "result"
- 4
+ "unit","time"
+ "ns",100000000000000
+ "us",100000000000
+ "ms",100000000
+ "s",100000
+ "min",1666
+ "h",27
+ "days",1
"""))
- def test_us(self):
+ def test_from_time(self):
return DiffTestBlueprint(
trace=TextProto(""),
query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT us(4) as result;
- """,
- out=Csv("""
- "result"
- 4000
- """))
+ INCLUDE PERFETTO MODULE time.conversion;
- def test_ms(self):
- return DiffTestBlueprint(
- trace=TextProto(""),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT ms(4) as result;
+ WITH data(unit, time) AS (
+ VALUES
+ ('ns', time_from_ns(1)),
+ ('us', time_from_us(1)),
+ ('ms', time_from_ms(1)),
+ ('s', time_from_s(1)),
+ ('min', time_from_min(1)),
+ ('h', time_from_hours(1)),
+ ('days', time_from_days(1))
+ )
+ SELECT * FROM data
""",
out=Csv("""
- "result"
- 4000000
- """))
-
- def test_seconds(self):
- return DiffTestBlueprint(
- trace=TextProto(""),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT seconds(4) as result;
- """,
- out=Csv("""
- "result"
- 4000000000
- """))
-
- def test_minutes(self):
- return DiffTestBlueprint(
- trace=TextProto(""),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT minutes(1) as result;
- """,
- out=Csv("""
- "result"
- 60000000000
- """))
-
- def test_hours(self):
- return DiffTestBlueprint(
- trace=TextProto(""),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT hours(1) as result;
- """,
- out=Csv("""
- "result"
- 3600000000000
- """))
-
- def test_days(self):
- return DiffTestBlueprint(
- trace=TextProto(""),
- query="""
- INCLUDE PERFETTO MODULE common.timestamps;
- SELECT days(1) as result;
- """,
- out=Csv("""
- "result"
- 86400000000000
- """))
+ "unit","time"
+ "ns",1
+ "us",1000
+ "ms",1000000
+ "s",1000000000
+ "min",60000000000
+ "h",3600000000000
+ "days",86400000000000
+ """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/syntax/include_tests.py b/test/trace_processor/diff_tests/syntax/include_tests.py
index 6c4d88a..6316ae6 100644
--- a/test/trace_processor/diff_tests/syntax/include_tests.py
+++ b/test/trace_processor/diff_tests/syntax/include_tests.py
@@ -23,121 +23,40 @@
def test_import(self):
return DiffTestBlueprint(
- trace=TextProto(r"""
- packet {
- ftrace_events {
- cpu: 1
- event {
- timestamp: 1000
- pid: 1
- print {
- buf: "C|1000|battery_stats.data_conn|13\n"
- }
- }
- event {
- timestamp: 4000
- pid: 1
- print {
- buf: "C|1000|battery_stats.data_conn|20\n"
- }
- }
- event {
- timestamp: 1000
- pid: 1
- print {
- buf: "C|1000|battery_stats.audio|1\n"
- }
- }
- }
- }
- """),
+ trace=TextProto(''),
query="""
- SELECT IMPORT('common.timestamps');
+ SELECT IMPORT('time.conversion');
- SELECT TRACE_START();
+ SELECT 1 AS x;
""",
out=Csv("""
- "TRACE_START()"
- 1000
+ "x"
+ 1
"""))
def test_include_perfetto_module(self):
return DiffTestBlueprint(
- trace=TextProto(r"""
- packet {
- ftrace_events {
- cpu: 1
- event {
- timestamp: 1000
- pid: 1
- print {
- buf: "C|1000|battery_stats.data_conn|13\n"
- }
- }
- event {
- timestamp: 4000
- pid: 1
- print {
- buf: "C|1000|battery_stats.data_conn|20\n"
- }
- }
- event {
- timestamp: 1000
- pid: 1
- print {
- buf: "C|1000|battery_stats.audio|1\n"
- }
- }
- }
- }
- """),
+ trace=TextProto(''),
query="""
- INCLUDE PERFETTO MODULE common.timestamps;
+ INCLUDE PERFETTO MODULE time.conversion;
- SELECT TRACE_START();
+ SELECT time_to_ns(1) AS x
""",
out=Csv("""
- "TRACE_START()"
- 1000
+ "x"
+ 1
"""))
def test_include_and_import(self):
return DiffTestBlueprint(
- trace=TextProto(r"""
- packet {
- ftrace_events {
- cpu: 1
- event {
- timestamp: 1000
- pid: 1
- print {
- buf: "C|1000|battery_stats.data_conn|13\n"
- }
- }
- event {
- timestamp: 4000
- pid: 1
- print {
- buf: "C|1000|battery_stats.data_conn|20\n"
- }
- }
- event {
- timestamp: 1000
- pid: 1
- print {
- buf: "C|1000|battery_stats.audio|1\n"
- }
- }
- }
- }
- """),
+ trace=TextProto(''),
query="""
- SELECT IMPORT('common.timestamps');
- INCLUDE PERFETTO MODULE common.timestamps;
+ SELECT IMPORT('time.conversion');
+ INCLUDE PERFETTO MODULE time.conversion;
- SELECT TRACE_START();
+ SELECT 1 AS x
""",
out=Csv("""
- "TRACE_START()"
- 1000
+ "x"
+ 1
"""))
diff --git a/tools/check_sql_modules.py b/tools/check_sql_modules.py
index f787143..ed8ddc3 100755
--- a/tools/check_sql_modules.py
+++ b/tools/check_sql_modules.py
@@ -106,7 +106,10 @@
if (include_package == "common"):
errors.append(
- "Common module has been deprecated in the standard library.")
+ "Common module has been deprecated in the standard library. "
+ "Please check `slices.with_context` for a replacement for "
+ "`common.slices` and `time.conversion` for replacement for "
+ "`common.timestamps`")
if (package != "viz" and include_package == "viz"):
errors.append("No modules can depend on 'viz' outside 'viz' package.")
diff --git a/tools/gen_bazel b/tools/gen_bazel
index a30f5ca..a5776ba 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -83,12 +83,12 @@
'//src/trace_processor:trace_processor_shell',
'//src/trace_processor:trace_processor',
'//src/traceconv:traceconv',
- '//src/traceconv:libpprofbuilder',
]
# These targets will be exported with visibility only to our allowlist.
allowlist_public_targets = [
'//src/shared_lib:libperfetto_c',
+ '//src/traceconv:libpprofbuilder',
]
# These targets are required by internal build rules but don't need to be
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/base/mithril_utils.ts b/ui/src/base/mithril_utils.ts
index b450f64..9b0615d 100644
--- a/ui/src/base/mithril_utils.ts
+++ b/ui/src/base/mithril_utils.ts
@@ -52,3 +52,36 @@
);
},
};
+
+/**
+ * Utility function to pre-bind some mithril attrs of a component, and leave
+ * the others unbound and passed at run-time.
+ * Example use case: the Page API Passes to the registered page a PageAttrs,
+ * which is {subpage:string}. Imagine you write a MyPage component that takes
+ * some extra input attrs (e.g. the App object) and you want to bind them
+ * onActivate(). The results looks like this:
+ *
+ * interface MyPageAttrs extends PageAttrs { app: App; }
+ *
+ * class MyPage extends m.classComponent<MyPageAttrs> {... view() {...} }
+ *
+ * onActivate(app: App) {
+ * pages.register(... bindMithrilApps(MyPage, {app: app});
+ * }
+ *
+ * The return value of bindMithrilApps is a mithril component that takes in
+ * input only a {subpage: string} and passes down to MyPage the combination
+ * of pre-bound and runtime attrs, that is {subpage, app}.
+ */
+export function bindMithrilAttrs<BaseAttrs, Attrs>(
+ component: m.ComponentTypes<Attrs>,
+ boundArgs: Omit<Attrs, keyof BaseAttrs>,
+): m.Component<BaseAttrs> {
+ return {
+ view(vnode: m.Vnode<BaseAttrs>) {
+ const attrs = {...vnode.attrs, ...boundArgs} as Attrs;
+ const emptyAttrs: m.CommonAttributes<Attrs, {}> = {}; // Keep tsc happy.
+ return m<Attrs, {}>(component, {...attrs, ...emptyAttrs});
+ },
+ };
+}
diff --git a/ui/src/chrome_extension/chrome_tracing_controller.ts b/ui/src/chrome_extension/chrome_tracing_controller.ts
index 916fca9..de15873 100644
--- a/ui/src/chrome_extension/chrome_tracing_controller.ts
+++ b/ui/src/chrome_extension/chrome_tracing_controller.ts
@@ -21,13 +21,13 @@
ConsumerPortResponse,
GetTraceStatsResponse,
ReadBuffersResponse,
-} from '../controller/consumer_port_types';
-import {RpcConsumerPort} from '../controller/record_controller_interfaces';
+} from '../plugins/dev.perfetto.RecordTrace/consumer_port_types';
+import {RpcConsumerPort} from '../plugins/dev.perfetto.RecordTrace/record_controller_interfaces';
import {
browserSupportsPerfettoConfig,
extractTraceConfig,
hasSystemDataSourceConfig,
-} from '../core/trace_config_utils';
+} from '../plugins/dev.perfetto.RecordTrace/trace_config_utils';
import {ITraceStats, TraceConfig} from '../protos';
import {DevToolsSocket} from './devtools_socket';
diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts
deleted file mode 100644
index cc10366..0000000
--- a/ui/src/common/constants.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (C) 2021 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.
-
-export const TRACE_SUFFIX = '.perfetto-trace';
diff --git a/ui/src/common/recordingV2/recording_error_handling.ts b/ui/src/common/recordingV2/recording_error_handling.ts
deleted file mode 100644
index ffec467..0000000
--- a/ui/src/common/recordingV2/recording_error_handling.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (C) 2022 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 {getErrorMessage} from '../../base/errors';
-import {
- showAllowUSBDebugging,
- showConnectionLostError,
- showExtensionNotInstalled,
- showFailedToPushBinary,
- showIssueParsingTheTracedResponse,
- showNoDeviceSelected,
- showWebsocketConnectionIssue,
- showWebUSBErrorV2,
-} from '../../frontend/error_dialog';
-import {OnMessageCallback} from './recording_interfaces_v2';
-import {
- ALLOW_USB_DEBUGGING,
- BINARY_PUSH_FAILURE,
- BINARY_PUSH_UNKNOWN_RESPONSE,
- EXTENSION_NOT_INSTALLED,
- NO_DEVICE_SELECTED,
- PARSING_UNABLE_TO_DECODE_METHOD,
- PARSING_UNKNWON_REQUEST_ID,
- PARSING_UNRECOGNIZED_MESSAGE,
- PARSING_UNRECOGNIZED_PORT,
- WEBSOCKET_UNABLE_TO_CONNECT,
-} from './recording_utils';
-
-// The pattern for handling recording error can have the following nesting in
-// case of errors:
-// A. wrapRecordingError -> wraps a promise
-// B. onFailure -> has user defined logic and calls showRecordingModal
-// C. showRecordingModal -> shows UX for a given error; this is not called
-// directly by wrapRecordingError, because we want the caller (such as the
-// UI) to dictate the UX
-
-// This method takes a promise and a callback to be execute in case the promise
-// fails. It then awaits the promise and executes the callback in case of
-// failure. In the recording code it is used to wrap:
-// 1. Acessing the WebUSB API.
-// 2. Methods returning promises which can be rejected. For instance:
-// a) When the user clicks 'Add a new device' but then doesn't select a valid
-// device.
-// b) When the user starts a tracing session, but cancels it before they
-// authorize the session on the device.
-export async function wrapRecordingError<T>(
- promise: Promise<T>,
- onFailure: OnMessageCallback,
-): Promise<T | undefined> {
- try {
- return await promise;
- } catch (e) {
- // Sometimes the message is wrapped in an Error object, sometimes not, so
- // we make sure we transform it into a string.
- const errorMessage = getErrorMessage(e);
- onFailure(errorMessage);
- return undefined;
- }
-}
-
-// Shows a modal for every known type of error which can arise during recording.
-// In this way, errors occuring at different levels of the recording process
-// can be handled in a central location.
-export function showRecordingModal(message: string): void {
- if (
- [
- 'Unable to claim interface.',
- 'The specified endpoint is not part of a claimed and selected ' +
- 'alternate interface.',
- // thrown when calling the 'reset' method on a WebUSB device.
- 'Unable to reset the device.',
- ].some((partOfMessage) => message.includes(partOfMessage))
- ) {
- showWebUSBErrorV2();
- } else if (
- [
- 'A transfer error has occurred.',
- 'The device was disconnected.',
- 'The transfer was cancelled.',
- ].some((partOfMessage) => message.includes(partOfMessage)) ||
- isDeviceDisconnectedError(message)
- ) {
- showConnectionLostError();
- } else if (message === ALLOW_USB_DEBUGGING) {
- showAllowUSBDebugging();
- } else if (
- isMessageComposedOf(message, [
- BINARY_PUSH_FAILURE,
- BINARY_PUSH_UNKNOWN_RESPONSE,
- ])
- ) {
- showFailedToPushBinary(message.substring(message.indexOf(':') + 1));
- } else if (message === NO_DEVICE_SELECTED) {
- showNoDeviceSelected();
- } else if (WEBSOCKET_UNABLE_TO_CONNECT === message) {
- showWebsocketConnectionIssue(message);
- } else if (message === EXTENSION_NOT_INSTALLED) {
- showExtensionNotInstalled();
- } else if (
- isMessageComposedOf(message, [
- PARSING_UNKNWON_REQUEST_ID,
- PARSING_UNABLE_TO_DECODE_METHOD,
- PARSING_UNRECOGNIZED_PORT,
- PARSING_UNRECOGNIZED_MESSAGE,
- ])
- ) {
- showIssueParsingTheTracedResponse(message);
- } else {
- throw new Error(`${message}`);
- }
-}
-
-function isDeviceDisconnectedError(message: string) {
- return (
- message.includes('Device with serial') &&
- message.includes('was disconnected.')
- );
-}
-
-function isMessageComposedOf(message: string, issues: string[]) {
- for (const issue of issues) {
- if (message.includes(issue)) {
- return true;
- }
- }
- return false;
-}
-
-// Exception thrown by the Recording logic.
-export class RecordingError extends Error {}
diff --git a/ui/src/core/app_impl.ts b/ui/src/core/app_impl.ts
index ab9718a..3336045 100644
--- a/ui/src/core/app_impl.ts
+++ b/ui/src/core/app_impl.ts
@@ -34,6 +34,8 @@
import {PageHandler} from '../public/page';
import {setPerfHooks} from './perf';
import {ServiceWorkerController} from '../frontend/service_worker_controller';
+import {FeatureFlagManager, FlagSettings} from '../public/feature_flag';
+import {featureFlags} from './feature_flags';
// The args that frontend/index.ts passes when calling AppImpl.initialize().
// This is to deal with injections that would otherwise cause circular deps.
@@ -77,19 +79,31 @@
// The currently open trace.
currentTrace?: TraceContext;
+ private static _instance: AppContext;
+
+ static initialize(initArgs: AppInitArgs): AppContext {
+ assertTrue(AppContext._instance === undefined);
+ return (AppContext._instance = new AppContext(initArgs));
+ }
+
+ static get instance(): AppContext {
+ return assertExists(AppContext._instance);
+ }
+
// This constructor is invoked only once, when frontend/index.ts invokes
// AppMainImpl.initialize().
- constructor(initArgs: AppInitArgs) {
+ private constructor(initArgs: AppInitArgs) {
this.initArgs = initArgs;
this.initialRouteArgs = initArgs.initialRouteArgs;
- this.sidebarMgr = new SidebarManagerImpl({
- sidebarEnabled: !this.initialRouteArgs.hideSidebar,
- });
this.serviceWorkerController = new ServiceWorkerController();
this.embeddedMode = this.initialRouteArgs.mode === 'embedded';
this.testingMode =
self.location !== undefined &&
self.location.search.indexOf('testing=1') >= 0;
+ this.sidebarMgr = new SidebarManagerImpl({
+ disabled: this.embeddedMode,
+ hidden: this.initialRouteArgs.hideSidebar,
+ });
this.analytics = initAnalytics(this.testingMode, this.embeddedMode);
this.pluginMgr = new PluginManagerImpl({
forkForPlugin: (pluginId) => this.forPlugin(pluginId),
@@ -141,19 +155,16 @@
private readonly appCtx: AppContext;
private readonly pageMgrProxy: PageManagerImpl;
+ // Invoked by frontend/index.ts.
+ static initialize(args: AppInitArgs) {
+ AppContext.initialize(args).forPlugin(CORE_PLUGIN_ID);
+ }
+
// Gets access to the one instance that the core can use. Note that this is
// NOT the only instance, as other AppImpl instance will be created for each
// plugin.
- private static _instance: AppImpl;
-
- // Invoked by frontend/index.ts.
- static initialize(args: AppInitArgs) {
- assertTrue(AppImpl._instance === undefined);
- AppImpl._instance = new AppContext(args).forPlugin(CORE_PLUGIN_ID);
- }
-
static get instance(): AppImpl {
- return assertExists(AppImpl._instance);
+ return AppContext.instance.forPlugin(CORE_PLUGIN_ID);
}
// Only called by AppContext.forPlugin().
@@ -171,6 +182,10 @@
});
}
+ forPlugin(pluginId: string): AppImpl {
+ return this.appCtx.forPlugin(pluginId);
+ }
+
get commands(): CommandManagerImpl {
return this.appCtx.commandMgr;
}
@@ -211,6 +226,12 @@
return this.appCtx.initialRouteArgs;
}
+ get featureFlags(): FeatureFlagManager {
+ return {
+ register: (settings: FlagSettings) => featureFlags.register(settings),
+ };
+ }
+
openTraceFromFile(file: File): void {
this.openTrace({type: 'FILE', file});
}
@@ -228,7 +249,6 @@
}
private async openTrace(src: TraceSource) {
- assertTrue(this.pluginId === CORE_PLUGIN_ID);
this.appCtx.closeCurrentTrace();
this.appCtx.isLoadingTrace = true;
try {
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index c30256a..4791e8e 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',
@@ -55,6 +56,7 @@
'dev.perfetto.ProcessSummary',
'dev.perfetto.ProcessThreadGroups',
'dev.perfetto.QueryPage',
+ 'dev.perfetto.RecordTrace',
'dev.perfetto.RestorePinnedTrack',
'dev.perfetto.Sched',
'dev.perfetto.Screenshots',
diff --git a/ui/src/core/feature_flags.ts b/ui/src/core/feature_flags.ts
index 4e745b9..4e60a61 100644
--- a/ui/src/core/feature_flags.ts
+++ b/ui/src/core/feature_flags.ts
@@ -16,20 +16,7 @@
// ~everywhere and the are "statically" initialized (i.e. files construct Flags
// at import time) if this file starts importing anything we will quickly run
// into issues with initialization order which will be a pain.
-
-interface FlagSettings {
- id: string;
- defaultValue: boolean;
- description: string;
- name?: string;
- devOnly?: boolean;
-}
-
-export enum OverrideState {
- DEFAULT = 'DEFAULT',
- TRUE = 'OVERRIDE_TRUE',
- FALSE = 'OVERRIDE_FALSE',
-}
+import {Flag, FlagSettings, OverrideState} from '../public/feature_flag';
export interface FlagStore {
load(): object;
@@ -135,39 +122,6 @@
}
}
-export interface Flag {
- // A unique identifier for this flag ("magicSorting")
- readonly id: string;
-
- // The name of the flag the user sees ("New track sorting algorithm")
- readonly name: string;
-
- // A longer description which is displayed to the user.
- // "Sort tracks using an embedded tfLite model based on your expression
- // while waiting for the trace to load."
- readonly description: string;
-
- // Whether the flag defaults to true or false.
- // If !flag.isOverridden() then flag.get() === flag.defaultValue
- readonly defaultValue: boolean;
-
- // Get the current value of the flag.
- get(): boolean;
-
- // Override the flag and persist the new value.
- set(value: boolean): void;
-
- // If the flag has been overridden.
- // Note: A flag can be overridden to its default value.
- isOverridden(): boolean;
-
- // Reset the flag to its default setting.
- reset(): void;
-
- // Get the current state of the flag.
- overriddenState(): OverrideState;
-}
-
class FlagImpl implements Flag {
registry: Flags;
state: OverrideState;
@@ -248,10 +202,3 @@
export const FlagsForTesting = Flags;
export const featureFlags = new Flags(new LocalStorageStore());
-
-export const RECORDING_V2_FLAG = featureFlags.register({
- id: 'recordingv2',
- name: 'Recording V2',
- description: 'Record using V2 interface',
- defaultValue: false,
-});
diff --git a/ui/src/core/plugin_manager.ts b/ui/src/core/plugin_manager.ts
index 55b5f4a..f444618 100644
--- a/ui/src/core/plugin_manager.ts
+++ b/ui/src/core/plugin_manager.ts
@@ -22,7 +22,8 @@
} from '../public/plugin';
import {Trace} from '../public/trace';
import {defaultPlugins} from './default_plugins';
-import {featureFlags, Flag} from './feature_flags';
+import {featureFlags} from './feature_flags';
+import {Flag} from '../public/feature_flag';
import {TraceImpl} from './trace_impl';
// The pseudo plugin id used for the core instance of AppImpl.
diff --git a/ui/src/core/sidebar_manager.ts b/ui/src/core/sidebar_manager.ts
index 11c12dd..9de9b90 100644
--- a/ui/src/core/sidebar_manager.ts
+++ b/ui/src/core/sidebar_manager.ts
@@ -27,9 +27,9 @@
readonly menuItems = new Registry<SidebarMenuItemInternal>((m) => m.id);
- constructor(args: {sidebarEnabled: boolean}) {
- this.enabled = args.sidebarEnabled;
- this._visible = args.sidebarEnabled;
+ constructor(args: {disabled?: boolean; hidden?: boolean}) {
+ this.enabled = !args.disabled;
+ this._visible = !args.hidden;
}
addMenuItem(item: SidebarMenuItem): Disposable {
diff --git a/ui/src/core/trace_impl.ts b/ui/src/core/trace_impl.ts
index 01e0070..2ae2d16 100644
--- a/ui/src/core/trace_impl.ts
+++ b/ui/src/core/trace_impl.ts
@@ -46,6 +46,10 @@
import {PageHandler, PageManager} from '../public/page';
import {createProxy} from '../base/utils';
import {PageManagerImpl} from './page_manager';
+import {FeatureFlagManager, FlagSettings} from '../public/feature_flag';
+import {featureFlags} from './feature_flags';
+import {SerializedAppState} from './state_serialization_schema';
+import {PostedTrace} from './trace_source';
/**
* Handles the per-trace state of the UI
@@ -407,6 +411,12 @@
return this.appImpl.initialRouteArgs;
}
+ get featureFlags(): FeatureFlagManager {
+ return {
+ register: (settings: FlagSettings) => featureFlags.register(settings),
+ };
+ }
+
scheduleFullRedraw(): void {
this.appImpl.scheduleFullRedraw();
}
@@ -415,6 +425,18 @@
this.appImpl.navigate(newHash);
}
+ openTraceFromFile(file: File): void {
+ this.appImpl.openTraceFromFile(file);
+ }
+
+ openTraceFromUrl(url: string, serializedAppState?: SerializedAppState) {
+ this.appImpl.openTraceFromUrl(url, serializedAppState);
+ }
+
+ openTraceFromBuffer(args: PostedTrace): void {
+ this.appImpl.openTraceFromBuffer(args);
+ }
+
addEventListener<T extends keyof EventListeners>(
event: T,
callback: EventListeners[T],
diff --git a/ui/src/core_plugins/chrome_scroll_jank/index.ts b/ui/src/core_plugins/chrome_scroll_jank/index.ts
index 732a5d6..19a0c70 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/index.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/index.ts
@@ -21,7 +21,8 @@
import {TopLevelScrollTrack} from './scroll_track';
import {ScrollJankCauseMap} from './scroll_jank_cause_map';
import {TrackNode} from '../../public/workspace';
-import {featureFlags, OverrideState} from '../../core/feature_flags';
+import {featureFlags} from '../../core/feature_flags';
+import {OverrideState} from '../../public/feature_flag';
// Before plugins were a thing, this plugin was enabled using a feature flag.
// However, nowadays, plugins themselves can be selectively enabled and
diff --git a/ui/src/core_plugins/flags_page/flags_page.ts b/ui/src/core_plugins/flags_page/flags_page.ts
index 4f275c9..d8cfdf6 100644
--- a/ui/src/core_plugins/flags_page/flags_page.ts
+++ b/ui/src/core_plugins/flags_page/flags_page.ts
@@ -14,7 +14,8 @@
import m from 'mithril';
import {channelChanged, getNextChannel, setChannel} from '../../core/channels';
-import {featureFlags, Flag, OverrideState} from '../../core/feature_flags';
+import {featureFlags} from '../../core/feature_flags';
+import {Flag, OverrideState} from '../../public/feature_flag';
import {raf} from '../../core/raf_scheduler';
import {PageAttrs} from '../../public/page';
import {Router} from '../../core/router';
diff --git a/ui/src/frontend/clipboard.ts b/ui/src/frontend/clipboard.ts
deleted file mode 100644
index 8d0b06c..0000000
--- a/ui/src/frontend/clipboard.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2018 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 {copyToClipboard} from '../base/clipboard';
-import {AppImpl} from '../core/app_impl';
-
-export function onClickCopy(url: string) {
- return (e: Event) => {
- e.preventDefault();
- copyToClipboard(url);
- AppImpl.instance.omnibox.showStatusMessage(
- 'Link copied into the clipboard',
- );
- };
-}
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index dbe4a02..99d4157 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -14,9 +14,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 {RECORDING_V2_FLAG} from '../core/feature_flags';
+import {GcsUploader} from '../base/gcs_uploader';
import {raf} from '../core/raf_scheduler';
import {VERSION} from '../gen/perfetto_version';
import {getCurrentModalKey, showModal} from '../widgets/modal';
@@ -47,22 +45,20 @@
return;
}
- if (!RECORDING_V2_FLAG.get()) {
- if (err.message.includes('Unable to claim interface')) {
- showWebUSBError();
- timeLastReport = now;
- return;
- }
+ if (err.message.includes('Unable to claim interface')) {
+ showWebUSBError();
+ timeLastReport = now;
+ return;
+ }
- if (
- err.message.includes('A transfer error has occurred') ||
- err.message.includes('The device was disconnected') ||
- err.message.includes('The transfer was cancelled')
- ) {
- showConnectionLostError();
- timeLastReport = now;
- return;
- }
+ if (
+ err.message.includes('A transfer error has occurred') ||
+ err.message.includes('The device was disconnected') ||
+ err.message.includes('The transfer was cancelled')
+ ) {
+ showConnectionLostError();
+ timeLastReport = now;
+ return;
}
if (err.message.includes('(ERR:fmt)')) {
@@ -356,135 +352,6 @@
});
}
-export function showWebUSBErrorV2() {
- showModal({
- title: 'A WebUSB error occurred',
- content: m(
- 'div',
- m(
- 'span',
- `Is adb already running on the host? Run this command and
- try again.`,
- ),
- m('br'),
- m('.modal-bash', '> adb kill-server'),
- m('br'),
- // The statement below covers the following edge case:
- // 1. 'adb server' is running on the device.
- // 2. The user selects the new Android target, so we try to fetch the
- // OS version and do QSS.
- // 3. The error modal is shown.
- // 4. The user runs 'adb kill-server'.
- // At this point we don't have a trigger to try fetching the OS version
- // + QSS again. Therefore, the user will need to refresh the page.
- m(
- 'span',
- "If after running 'adb kill-server', you don't see " +
- "a 'Start Recording' button on the page and you don't see " +
- "'Allow USB debugging' on the device, " +
- 'you will need to reload this page.',
- ),
- m('br'),
- m('br'),
- m('span', 'For details see '),
- m('a', {href: 'http://b/159048331', target: '_blank'}, 'b/159048331'),
- ),
- });
-}
-
-export function showConnectionLostError(): void {
- showModal({
- title: 'Connection with the ADB device lost',
- content: m(
- 'div',
- m('span', `Please connect the device again to restart the recording.`),
- m('br'),
- ),
- });
-}
-
-export function showAllowUSBDebugging(): void {
- showModal({
- title: 'Could not connect to the device',
- content: m(
- 'div',
- m('span', 'Please allow USB debugging on the device.'),
- m('br'),
- ),
- });
-}
-
-export function showNoDeviceSelected(): void {
- showModal({
- title: 'No device was selected for recording',
- content: m(
- 'div',
- m(
- 'span',
- `If you want to connect to an ADB device,
- please select it from the list.`,
- ),
- m('br'),
- ),
- });
-}
-
-export function showExtensionNotInstalled(): void {
- showModal({
- title: 'Perfetto Chrome extension not installed',
- content: m(
- 'div',
- m(
- '.note',
- `To trace Chrome from the Perfetto UI, you need to install our `,
- m('a', {href: EXTENSION_URL, target: '_blank'}, 'Chrome extension'),
- ' and then reload this page.',
- ),
- m('br'),
- ),
- });
-}
-
-export function showWebsocketConnectionIssue(message: string): void {
- showModal({
- title: 'Unable to connect to the device via websocket',
- content: m(
- 'div',
- m('div', 'trace_processor_shell --httpd is unreachable or crashed.'),
- m('pre', message),
- ),
- });
-}
-
-export function showIssueParsingTheTracedResponse(message: string): void {
- showModal({
- title:
- 'A problem was encountered while connecting to' +
- ' the Perfetto tracing service',
- content: m('div', m('span', message), m('br')),
- });
-}
-
-export function showFailedToPushBinary(message: string): void {
- showModal({
- title: 'Failed to push a binary to the device',
- content: m(
- 'div',
- m(
- 'span',
- 'This can happen if your Android device has an OS version lower ' +
- 'than Q. Perfetto tried to push the latest version of its ' +
- 'embedded binary but failed.',
- ),
- m('br'),
- m('br'),
- m('span', 'Error message:'),
- m('br'),
- m('span', message),
- ),
- });
-}
-
function showRpcSequencingError() {
showModal({
title: 'A TraceProcessor RPC error occurred',
@@ -534,3 +401,25 @@
],
});
}
+
+function showWebsocketConnectionIssue(message: string): void {
+ showModal({
+ title: 'Unable to connect to the device via websocket',
+ content: m(
+ 'div',
+ m('div', 'trace_processor_shell --httpd is unreachable or crashed.'),
+ m('pre', message),
+ ),
+ });
+}
+
+function showConnectionLostError(): void {
+ showModal({
+ title: 'Connection with the ADB device lost',
+ content: m(
+ 'div',
+ m('span', `Please connect the device again to restart the recording.`),
+ m('br'),
+ ),
+ });
+}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 198f08e..67aa000 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -20,7 +20,7 @@
import m from 'mithril';
import {defer} from '../base/deferred';
import {addErrorHandler, reportError} from '../base/logging';
-import {RECORDING_V2_FLAG, featureFlags} from '../core/feature_flags';
+import {featureFlags} from '../core/feature_flags';
import {initLiveReload} from '../core/live_reload';
import {raf} from '../core/raf_scheduler';
import {initWasm} from '../trace_processor/wasm_engine_proxy';
@@ -33,8 +33,6 @@
import {globals} from './globals';
import {HomePage} from './home_page';
import {postMessageHandler} from './post_message_handler';
-import {RecordPage} from './record_page';
-import {RecordPageV2} from './record_page_v2';
import {Route, Router} from '../core/router';
import {CheckHttpRpcConnection} from './rpc_http_dialog';
import {maybeOpenTraceFromRoute} from './trace_url_handler';
@@ -224,8 +222,6 @@
const pages = AppImpl.instance.pages;
const traceless = true;
pages.registerPage({route: '/', traceless, page: HomePage});
- const recordPage = RECORDING_V2_FLAG.get() ? RecordPageV2 : RecordPage;
- pages.registerPage({route: '/record', traceless, page: recordPage});
pages.registerPage({route: '/viewer', page: ViewerPage});
const router = new Router();
router.onRouteChanged = routeChange;
diff --git a/ui/src/frontend/permalink.ts b/ui/src/frontend/permalink.ts
index 5ab7059..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,
@@ -32,9 +32,7 @@
import {z} from 'zod';
import {showModal} from '../widgets/modal';
import {AppImpl} from '../core/app_impl';
-import {Router} from '../core/router';
-import {onClickCopy} from './clipboard';
-import {RecordingManager} from '../controller/recording_manager';
+import {CopyableLink} from '../widgets/copyable_link';
// Permalink serialization has two layers:
// 1. Serialization of the app state (state_serialization.ts):
@@ -57,69 +55,54 @@
// 1. parseAppState() does further semantic checks (e.g. version checking).
// 2. We want to still load the traceUrl even if the app state is invalid.
appState: z.any().optional(),
-
- // This is for the very unusual case of clicking on "Share settings" in the
- // recording page. In this case there is no trace or app state. We just
- // create a permalink with the recording state.
- recordingOpts: z.any().optional(),
});
type PermalinkState = z.infer<typeof PERMALINK_SCHEMA>;
-export interface PermalinkOptions {
- mode: 'APP_STATE' | 'RECORDING_OPTS';
-}
-
-export async function createPermalink(opts: PermalinkOptions): Promise<void> {
- const hash = await createPermalinkInternal(opts);
+export async function createPermalink(): Promise<void> {
+ const hash = await createPermalinkInternal();
showPermalinkDialog(hash);
}
// Returns the file name, not the full url (i.e. the name of the GCS object).
-async function createPermalinkInternal(
- opts: PermalinkOptions,
-): Promise<string> {
+async function createPermalinkInternal(): Promise<string> {
const permalinkData: PermalinkState = {};
- if (opts.mode === 'RECORDING_OPTS') {
- permalinkData.recordingOpts = RecordingManager.instance.state.recordConfig;
- } else if (opts.mode === 'APP_STATE') {
- // Check if we need to upload the trace file, before serializing the app
- // state.
- let alreadyUploadedUrl = '';
- const trace = assertExists(AppImpl.instance.trace);
- const traceSource = trace.traceInfo.source;
- let dataToUpload: File | ArrayBuffer | undefined = undefined;
- let traceName = trace.traceInfo.traceTitle || 'trace';
- if (traceSource.type === 'FILE') {
- dataToUpload = traceSource.file;
- traceName = dataToUpload.name;
- } else if (traceSource.type === 'ARRAY_BUFFER') {
- dataToUpload = traceSource.buffer;
- } else if (traceSource.type === 'URL') {
- alreadyUploadedUrl = traceSource.url;
- } else {
- throw new Error(`Cannot share trace ${JSON.stringify(traceSource)}`);
- }
-
- // Upload the trace file, unless it's already uploaded (type == 'URL').
- // Internally TraceGcsUploader will skip the upload if an object with the
- // same hash exists already.
- if (alreadyUploadedUrl) {
- permalinkData.traceUrl = alreadyUploadedUrl;
- } else if (dataToUpload !== undefined) {
- updateStatus(`Uploading ${traceName}`);
- const uploader: GcsUploader = new GcsUploader(dataToUpload, {
- mimeType: MIME_BINARY,
- onProgress: () => reportUpdateProgress(uploader),
- });
- await uploader.waitForCompletion();
- permalinkData.traceUrl = uploader.uploadedUrl;
- }
-
- permalinkData.appState = serializeAppState(trace);
+ // Check if we need to upload the trace file, before serializing the app
+ // state.
+ let alreadyUploadedUrl = '';
+ const trace = assertExists(AppImpl.instance.trace);
+ const traceSource = trace.traceInfo.source;
+ let dataToUpload: File | ArrayBuffer | undefined = undefined;
+ let traceName = trace.traceInfo.traceTitle || 'trace';
+ if (traceSource.type === 'FILE') {
+ dataToUpload = traceSource.file;
+ traceName = dataToUpload.name;
+ } else if (traceSource.type === 'ARRAY_BUFFER') {
+ dataToUpload = traceSource.buffer;
+ } else if (traceSource.type === 'URL') {
+ alreadyUploadedUrl = traceSource.url;
+ } else {
+ throw new Error(`Cannot share trace ${JSON.stringify(traceSource)}`);
}
+ // Upload the trace file, unless it's already uploaded (type == 'URL').
+ // Internally TraceGcsUploader will skip the upload if an object with the
+ // same hash exists already.
+ if (alreadyUploadedUrl) {
+ permalinkData.traceUrl = alreadyUploadedUrl;
+ } else if (dataToUpload !== undefined) {
+ updateStatus(`Uploading ${traceName}`);
+ const uploader: GcsUploader = new GcsUploader(dataToUpload, {
+ mimeType: MIME_BINARY,
+ onProgress: () => reportUpdateProgress(uploader),
+ });
+ await uploader.waitForCompletion();
+ permalinkData.traceUrl = uploader.uploadedUrl;
+ }
+
+ permalinkData.appState = serializeAppState(trace);
+
// Serialize the permalink with the app state (or recording state) and upload.
updateStatus(`Creating permalink...`);
const permalinkJson = JsonSerialize(permalinkData);
@@ -166,13 +149,6 @@
}
}
- if (permalink.recordingOpts !== undefined) {
- // This permalink state only contains a RecordConfig. Show the
- // recording page with the config, but keep other state as-is.
- RecordingManager.instance.setRecordConfig(permalink.recordingOpts);
- Router.navigate('#!/record');
- return;
- }
let serializedAppState: SerializedAppState | undefined = undefined;
if (permalink.appState !== undefined) {
// This is the most common case where the permalink contains the app state
@@ -266,15 +242,8 @@
}
function showPermalinkDialog(hash: string) {
- const url = `${self.location.origin}/#!/?s=${hash}`;
- const linkProps = {title: 'Click to copy the URL', onclick: onClickCopy(url)};
showModal({
title: 'Permalink',
- content: m(
- 'div',
- m(`a[href=${url}]`, linkProps, url),
- m('br'),
- m('i', 'Click on the URL to copy it into the clipboard'),
- ),
+ content: m(CopyableLink, {url: `${self.location.origin}/#!/?s=${hash}`}),
});
}
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 9f71bcc..8ece2d3 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
import {getCurrentChannel} from '../core/channels';
-import {TRACE_SUFFIX} from '../common/constants';
+import {TRACE_SUFFIX} from '../public/trace';
import {
disableMetatracingAndGetTrace,
enableMetatracing,
@@ -554,13 +554,6 @@
// TODO(primiano): The Open file / Open with legacy entries are registered by
// the 'perfetto.CoreCommands' plugins. Make things consistent.
app.sidebar.addMenuItem({
- section: 'navigation',
- text: 'Record new trace',
- href: '#!/record',
- icon: 'fiber_smart_record',
- sortOrder: 2,
- });
- app.sidebar.addMenuItem({
section: 'support',
text: 'Keyboard shortcuts',
action: toggleHelp,
diff --git a/ui/src/frontend/sql_table_tab.ts b/ui/src/frontend/sql_table_tab.ts
index 40f2012..b803148 100644
--- a/ui/src/frontend/sql_table_tab.ts
+++ b/ui/src/frontend/sql_table_tab.ts
@@ -28,6 +28,12 @@
import {MenuItem, PopupMenu2} from '../widgets/menu';
import {addEphemeralTab} from '../common/add_ephemeral_tab';
import {Tab} from '../public/tab';
+import {addChartTab} from './widgets/charts/chart_tab';
+import {
+ ChartOption,
+ createChartConfigFromSqlTableState,
+} from './widgets/charts/chart';
+import {AddChartMenuItem} from './widgets/charts/add_chart_menu';
export interface AddSqlTableTabParams {
table: SqlTableDescription;
@@ -122,6 +128,16 @@
},
m(SqlTable, {
state: this.state,
+ addColumnMenuItems: (column, columnAlias) =>
+ m(AddChartMenuItem, {
+ chartConfig: createChartConfigFromSqlTableState(
+ column,
+ columnAlias,
+ this.state,
+ ),
+ chartOptions: [ChartOption.HISTOGRAM],
+ addChart: (chart) => addChartTab(chart),
+ }),
}),
);
}
diff --git a/ui/src/frontend/trace_share_utils.ts b/ui/src/frontend/trace_share_utils.ts
index 418fe51..cf8f185 100644
--- a/ui/src/frontend/trace_share_utils.ts
+++ b/ui/src/frontend/trace_share_utils.ts
@@ -16,11 +16,11 @@
import {TraceUrlSource} from '../core/trace_source';
import {createPermalink} from './permalink';
import {showModal} from '../widgets/modal';
-import {onClickCopy} from './clipboard';
import {globals} from './globals';
import {AppImpl} from '../core/app_impl';
import {Trace} from '../public/trace';
import {TraceImpl} from '../core/trace_impl';
+import {CopyableLink} from '../widgets/copyable_link';
export function isShareable(trace: Trace) {
return globals.isInternalUser && trace.traceInfo.downloadable;
@@ -43,7 +43,7 @@
if (traceUrl) {
msg.push(m('p', 'By using the URL below you can open this trace again.'));
msg.push(m('p', 'Clicking will copy the URL into the clipboard.'));
- msg.push(createTraceLink(traceUrl, traceUrl));
+ msg.push(m(CopyableLink, {url: traceUrl}));
}
showModal({
@@ -61,19 +61,6 @@
);
if (result) {
AppImpl.instance.analytics.logEvent('Trace Actions', 'Create permalink');
- return await createPermalink({mode: 'APP_STATE'});
+ return await createPermalink();
}
}
-
-export function createTraceLink(title: string, url: string) {
- if (url === '') {
- return m('a.trace-file-name', title);
- }
- const linkProps = {
- href: url,
- title: 'Click to copy the URL',
- target: '_blank',
- onclick: onClickCopy(url),
- };
- return m('a.trace-file-name', linkProps, title);
-}
diff --git a/ui/src/frontend/widgets/charts/add_chart_menu.ts b/ui/src/frontend/widgets/charts/add_chart_menu.ts
new file mode 100644
index 0000000..cb3bb17
--- /dev/null
+++ b/ui/src/frontend/widgets/charts/add_chart_menu.ts
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {MenuItem} from '../../../widgets/menu';
+import {Icons} from '../../../base/semantic_icons';
+import {Chart, ChartConfig, ChartOption, toTitleCase} from './chart';
+
+interface AddChartMenuItemAttrs {
+ readonly chartConfig: ChartConfig;
+ readonly chartOptions: Array<ChartOption>;
+ readonly addChart: (chart: Chart) => void;
+}
+
+export class AddChartMenuItem
+ implements m.ClassComponent<AddChartMenuItemAttrs>
+{
+ private renderAddChartOptions(
+ config: ChartConfig,
+ chartOptions: Array<ChartOption>,
+ addChart: (chart: Chart) => void,
+ ): m.Children {
+ return chartOptions.map((option) => {
+ return m(MenuItem, {
+ label: toTitleCase(option),
+ onclick: () => addChart({option, config}),
+ });
+ });
+ }
+
+ view({attrs}: m.Vnode<AddChartMenuItemAttrs>) {
+ return m(
+ MenuItem,
+ {label: 'Add chart', icon: Icons.Chart},
+ this.renderAddChartOptions(
+ attrs.chartConfig,
+ attrs.chartOptions,
+ attrs.addChart,
+ ),
+ );
+ }
+}
diff --git a/ui/src/frontend/widgets/charts/chart.ts b/ui/src/frontend/widgets/charts/chart.ts
index 1a78c41..124a52e 100644
--- a/ui/src/frontend/widgets/charts/chart.ts
+++ b/ui/src/frontend/widgets/charts/chart.ts
@@ -11,9 +11,13 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+import m from 'mithril';
import {Row} from '../../../trace_processor/query_result';
import {Engine} from '../../../trace_processor/engine';
-import {TableColumn, TableColumnSet} from '../sql/table/column';
+import {Filter, TableColumn, TableColumnSet} from '../sql/table/column';
+import {Histogram} from './histogram/histogram';
+import {SqlTableState} from '../sql/table/state';
+import {columnTitle} from '../sql/table/table';
export interface VegaLiteChartSpec {
$schema: string;
@@ -41,6 +45,26 @@
};
}
+// Holds the various chart types and human readable string
+export enum ChartOption {
+ HISTOGRAM = 'histogram',
+}
+
+export interface ChartConfig {
+ readonly engine: Engine;
+ readonly columnTitle: string; // Human readable column name (ex: Duration)
+ readonly sqlColumn: string[]; // SQL column name (ex: dur)
+ readonly filters?: Filter[]; // Filters applied to SQL table
+ readonly tableDisplay?: string; // Human readable table name (ex: slices)
+ readonly query: string; // SQL query for the underlying data
+ readonly aggregationType?: 'nominal' | 'quantitative'; // Aggregation type.
+}
+
+export interface Chart {
+ readonly option: ChartOption;
+ readonly config: ChartConfig;
+}
+
export interface ChartData {
readonly rows: Row[];
readonly error?: string;
@@ -65,3 +89,32 @@
return words.join(' ');
}
+
+// renderChartComponent will take a chart option and config and map
+// to the corresponding chart class component.
+export function renderChartComponent(chart: Chart) {
+ switch (chart.option) {
+ case ChartOption.HISTOGRAM:
+ return m(Histogram, chart.config);
+ default:
+ return;
+ }
+}
+
+export function createChartConfigFromSqlTableState(
+ column: TableColumn,
+ columnAlias: string,
+ sqlTableState: SqlTableState,
+) {
+ return {
+ engine: sqlTableState.trace.engine,
+ columnTitle: columnTitle(column),
+ sqlColumn: [columnAlias],
+ filters: sqlTableState?.getFilters(),
+ tableDisplay: sqlTableState.config.displayName ?? sqlTableState.config.name,
+ query: sqlTableState.getSqlQuery(
+ Object.fromEntries([[columnAlias, column.primaryColumn()]]),
+ ),
+ aggregationType: column.aggregation?.().dataType,
+ };
+}
diff --git a/ui/src/frontend/widgets/charts/chart_tab.ts b/ui/src/frontend/widgets/charts/chart_tab.ts
new file mode 100644
index 0000000..6d802e6
--- /dev/null
+++ b/ui/src/frontend/widgets/charts/chart_tab.ts
@@ -0,0 +1,54 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {DetailsShell} from '../../../widgets/details_shell';
+import {filterTitle} from '../sql/table/column';
+import {addEphemeralTab} from '../../../common/add_ephemeral_tab';
+import {Tab} from '../../../public/tab';
+import {Chart, renderChartComponent, toTitleCase} from './chart';
+
+export function addChartTab(chart: Chart): void {
+ addEphemeralTab('histogramTab', new ChartTab(chart));
+}
+
+export class ChartTab implements Tab {
+ constructor(private readonly chart: Chart) {}
+
+ render() {
+ return m(
+ DetailsShell,
+ {
+ title: this.getTitle(),
+ description: this.getDescription(),
+ },
+ renderChartComponent(this.chart),
+ );
+ }
+
+ getTitle(): string {
+ return `${toTitleCase(this.chart.config.columnTitle)} Histogram`;
+ }
+
+ private getDescription(): string {
+ let desc = `Count distribution for ${this.chart.config.tableDisplay ?? ''} table`;
+
+ if (this.chart.config.filters && this.chart.config.filters.length > 0) {
+ desc += ' where ';
+ desc += this.chart.config.filters.map((f) => filterTitle(f)).join(', ');
+ }
+
+ return desc;
+ }
+}
diff --git a/ui/src/frontend/widgets/charts/histogram/histogram.ts b/ui/src/frontend/widgets/charts/histogram/histogram.ts
index e4dd0d0..38f4a65 100644
--- a/ui/src/frontend/widgets/charts/histogram/histogram.ts
+++ b/ui/src/frontend/widgets/charts/histogram/histogram.ts
@@ -15,25 +15,14 @@
import m from 'mithril';
import {stringifyJsonWithBigints} from '../../../../base/json_utils';
import {VegaView} from '../../../../widgets/vega_view';
-import {Filter} from '../../../widgets/sql/table/column';
import {HistogramState} from './state';
import {Spinner} from '../../../../widgets/spinner';
-import {Engine} from '../../../../trace_processor/engine';
+import {ChartConfig} from '../chart';
-export interface HistogramConfig {
- engine: Engine;
- columnTitle: string; // Human readable column name (ex: Duration)
- sqlColumn: string[]; // SQL column name (ex: dur)
- filters?: Filter[]; // Filters applied to SQL table
- tableDisplay?: string; // Human readable table name (ex: slices)
- query: string; // SQL query for the underlying data
- aggregationType?: 'nominal' | 'quantitative'; // Aggregation type.
-}
-
-export class Histogram implements m.ClassComponent<HistogramConfig> {
+export class Histogram implements m.ClassComponent<ChartConfig> {
private readonly state: HistogramState;
- constructor({attrs}: m.Vnode<HistogramConfig>) {
+ constructor({attrs}: m.Vnode<ChartConfig>) {
this.state = new HistogramState(
attrs.engine,
attrs.query,
diff --git a/ui/src/frontend/widgets/charts/histogram/tab.ts b/ui/src/frontend/widgets/charts/histogram/tab.ts
deleted file mode 100644
index 096245e..0000000
--- a/ui/src/frontend/widgets/charts/histogram/tab.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import m from 'mithril';
-import {DetailsShell} from '../../../../widgets/details_shell';
-import {filterTitle} from '../../../widgets/sql/table/column';
-import {addEphemeralTab} from '../../../../common/add_ephemeral_tab';
-import {Tab} from '../../../../public/tab';
-import {Histogram, HistogramConfig} from './histogram';
-import {toTitleCase} from '../chart';
-
-export function addHistogramTab(config: HistogramConfig): void {
- addEphemeralTab('histogramTab', new HistogramTab(config));
-}
-
-export class HistogramTab implements Tab {
- constructor(private readonly config: HistogramConfig) {}
-
- render() {
- return m(
- DetailsShell,
- {
- title: this.getTitle(),
- description: this.getDescription(),
- },
- m(Histogram, this.config),
- );
- }
-
- getTitle(): string {
- return `${toTitleCase(this.config.columnTitle)} Histogram`;
- }
-
- private getDescription(): string {
- let desc = `Count distribution for ${this.config.tableDisplay ?? ''} table`;
-
- if (this.config.filters && this.config.filters.length > 0) {
- desc += ' where ';
- desc += this.config.filters.map((f) => filterTitle(f)).join(', ');
- }
-
- return desc;
- }
-}
diff --git a/ui/src/frontend/widgets/sql/table/table.ts b/ui/src/frontend/widgets/sql/table/table.ts
index acd5ffe..761a32c 100644
--- a/ui/src/frontend/widgets/sql/table/table.ts
+++ b/ui/src/frontend/widgets/sql/table/table.ts
@@ -40,14 +40,20 @@
import {SqlTableState} from './state';
import {SqlTableDescription} from './table_description';
import {Intent} from '../../../../widgets/common';
-import {addHistogramTab} from '../../charts/histogram/tab';
import {Form} from '../../../../widgets/form';
import {TextInput} from '../../../../widgets/text_input';
export interface SqlTableConfig {
readonly state: SqlTableState;
+ // For additional menu items to add to the column header menus
+ readonly addColumnMenuItems?: (
+ column: TableColumn,
+ columnAlias: string,
+ ) => m.Children;
}
+type AdditionalColumnMenuItems = Record<string, m.Children>;
+
function renderCell(
column: TableColumn,
row: Row,
@@ -65,7 +71,7 @@
return column.renderCell(sqlValue, getTableManager(state), additionalValues);
}
-function columnTitle(column: TableColumn): string {
+export function columnTitle(column: TableColumn): string {
if (column.getTitle !== undefined) {
const title = column.getTitle();
if (title !== undefined) return title;
@@ -274,7 +280,11 @@
);
}
- renderColumnHeader(column: TableColumn, index: number) {
+ renderColumnHeader(
+ column: TableColumn,
+ index: number,
+ additionalColumnHeaderMenuItems?: m.Children,
+ ) {
const sorted = this.state.isSortedBy(column);
const icon =
sorted === 'ASC'
@@ -327,27 +337,7 @@
{label: 'Add filter', icon: Icons.Filter},
this.renderColumnFilterOptions(column),
),
- m(MenuItem, {
- label: 'Create histogram',
- icon: Icons.Chart,
- onclick: () => {
- const columnAlias =
- this.state.getCurrentRequest().columns[
- sqlColumnId(column.primaryColumn())
- ];
- addHistogramTab({
- engine: this.state.trace.engine,
- sqlColumn: [columnAlias],
- columnTitle: columnTitle(column),
- filters: this.state.getFilters(),
- tableDisplay: this.table.displayName ?? this.table.name,
- query: this.state.getSqlQuery(
- Object.fromEntries([[columnAlias, column.primaryColumn()]]),
- ),
- aggregationType: column.aggregation?.().dataType,
- });
- },
- }),
+ additionalColumnHeaderMenuItems,
// Menu items before divider apply to selected column
m(MenuDivider),
// Menu items after divider apply to entire table
@@ -355,13 +345,49 @@
);
}
- view() {
+ getAdditionalColumnMenuItems(
+ addColumnMenuItems?: (
+ column: TableColumn,
+ columnAlias: string,
+ ) => m.Children,
+ ) {
+ if (addColumnMenuItems === undefined) return;
+
+ const additionalColumnMenuItems: AdditionalColumnMenuItems = {};
+ this.state.getSelectedColumns().forEach((column) => {
+ const columnAlias =
+ this.state.getCurrentRequest().columns[
+ sqlColumnId(column.primaryColumn())
+ ];
+
+ additionalColumnMenuItems[columnAlias] = addColumnMenuItems(
+ column,
+ columnAlias,
+ );
+ });
+
+ return additionalColumnMenuItems;
+ }
+
+ view({attrs}: m.Vnode<SqlTableConfig>) {
const rows = this.state.getDisplayedRows();
+ const additionalColumnMenuItems = this.getAdditionalColumnMenuItems(
+ attrs.addColumnMenuItems,
+ );
const columns = this.state.getSelectedColumns();
const columnDescriptors = columns.map((column, i) => {
return {
- title: this.renderColumnHeader(column, i),
+ title: this.renderColumnHeader(
+ column,
+ i,
+ additionalColumnMenuItems &&
+ additionalColumnMenuItems[
+ this.state.getCurrentRequest().columns[
+ sqlColumnId(column.primaryColumn())
+ ]
+ ],
+ ),
render: (row: Row) => renderCell(column, row, this.state),
};
});
diff --git a/ui/src/frontend/widgets/thread_state.ts b/ui/src/frontend/widgets/thread_state.ts
index d6c0f7b..b7cfd69 100644
--- a/ui/src/frontend/widgets/thread_state.ts
+++ b/ui/src/frontend/widgets/thread_state.ts
@@ -58,6 +58,6 @@
if (state.thread === undefined) return null;
return m(ThreadStateRef, {
- id: state.threadStateSqlId,
+ id: state.id,
});
}
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/plugins/dev.perfetto.ExplorePage/explore_page.ts b/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
index 5b1d0d4..1c60d4f 100644
--- a/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
@@ -33,8 +33,15 @@
import {Button} from '../../widgets/button';
import {Icons} from '../../base/semantic_icons';
import {DetailsShell} from '../../widgets/details_shell';
+import {
+ Chart,
+ ChartOption,
+ createChartConfigFromSqlTableState,
+ renderChartComponent,
+} from '../../frontend/widgets/charts/chart';
+import {AddChartMenuItem} from '../../frontend/widgets/charts/add_chart_menu';
-interface ExplorePageState {
+interface ExploreTableState {
sqlTableState?: SqlTableState;
selectedTable?: ExplorableTable;
}
@@ -46,13 +53,12 @@
}
export class ExplorePage implements m.ClassComponent<PageWithTraceAttrs> {
- private readonly state: ExplorePageState;
+ private readonly state: ExploreTableState;
+ private readonly charts: Chart[];
constructor() {
- this.state = {
- sqlTableState: undefined,
- selectedTable: undefined,
- };
+ this.charts = [];
+ this.state = {};
}
// Show menu with standard library tables
@@ -115,7 +121,7 @@
this.state.selectedTable = table;
- const sqlTableState = new SqlTableState(
+ this.state.sqlTableState = new SqlTableState(
trace,
{
name: table.name,
@@ -123,7 +129,6 @@
},
{imports: [table.module]},
);
- this.state.sqlTableState = sqlTableState;
},
});
});
@@ -162,6 +167,16 @@
},
m(SqlTable, {
state: sqlTableState,
+ addColumnMenuItems: (column, columnAlias) =>
+ m(AddChartMenuItem, {
+ chartConfig: createChartConfigFromSqlTableState(
+ column,
+ columnAlias,
+ sqlTableState,
+ ),
+ chartOptions: [ChartOption.HISTOGRAM],
+ addChart: (chart) => this.charts.push(chart),
+ }),
}),
);
}
@@ -170,6 +185,7 @@
return m(
'.explore-page',
m(Menu, this.renderSelectableTablesMenuItems(attrs.trace)),
+ this.charts.map((chart) => renderChartComponent(chart)),
this.state.selectedTable && this.renderSqlTable(),
);
}
diff --git a/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
index 03be4f2..d75dd77 100644
--- a/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
@@ -20,6 +20,7 @@
import {STR_NULL} from '../../trace_processor/query_result';
import {Slice} from '../../public/track';
import {Trace} from '../../public/trace';
+import {TrackEventDetails} from '../../public/selection';
// color named and defined based on Material Design color palettes
// 500 colors indicate a timeline slice is not a partial jank (not a jank or
@@ -90,6 +91,17 @@
colorScheme: getColorSchemeForJank(row.jankTag, row.jankSeverityType),
};
}
+
+ override async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const baseDetails = await super.getSelectionDetails(id);
+ if (!baseDetails) return undefined;
+ return {
+ ...baseDetails,
+ tableName: 'slice',
+ };
+ }
}
function getColorSchemeForJank(
diff --git a/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
index bd41be4..ff04311 100644
--- a/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
@@ -22,6 +22,7 @@
import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout';
import {Slice} from '../../public/track';
import {Trace} from '../../public/trace';
+import {TrackEventDetails} from '../../public/selection';
const GREEN = makeColorScheme(new HSLColor('#4CAF50')); // Green 500
@@ -61,4 +62,15 @@
getRowSpec(): NamedRow {
return NAMED_ROW;
}
+
+ override async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const baseDetails = await super.getSelectionDetails(id);
+ if (!baseDetails) return undefined;
+ return {
+ ...baseDetails,
+ tableName: 'slice',
+ };
+ }
}
diff --git a/ui/src/controller/adb.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb.ts
similarity index 98%
rename from ui/src/controller/adb.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb.ts
index e188ea7..5197d23 100644
--- a/ui/src/controller/adb.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb.ts
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists} from '../base/logging';
-import {isString} from '../base/object_utils';
-import {utf8Decode, utf8Encode} from '../base/string_utils';
+import {assertExists} from '../../base/logging';
+import {isString} from '../../base/object_utils';
+import {utf8Decode, utf8Encode} from '../../base/string_utils';
import {Adb, AdbMsg, AdbStream, CmdType} from './adb_interfaces';
export const VERSION_WITH_CHECKSUM = 0x01000000;
diff --git a/ui/src/controller/adb_base_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_base_controller.ts
similarity index 96%
rename from ui/src/controller/adb_base_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_base_controller.ts
index 2a72a33..c447df5 100644
--- a/ui/src/controller/adb_base_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_base_controller.ts
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {exists} from '../base/utils';
-import {RecordingState, RecordingTarget, isAdbTarget} from '../common/state';
+import {exists} from '../../base/utils';
+import {RecordingState, RecordingTarget, isAdbTarget} from './state';
import {
extractDurationFromTraceConfig,
extractTraceConfig,
-} from '../core/trace_config_utils';
+} from './trace_config_utils';
import {Adb} from './adb_interfaces';
import {ReadBuffersResponse} from './consumer_port_types';
import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
diff --git a/ui/src/controller/adb_interfaces.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_interfaces.ts
similarity index 100%
rename from ui/src/controller/adb_interfaces.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_interfaces.ts
diff --git a/ui/src/controller/adb_jsdomtest.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_jsdomtest.ts
similarity index 97%
rename from ui/src/controller/adb_jsdomtest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_jsdomtest.ts
index 9f51a97..1d228a5 100644
--- a/ui/src/controller/adb_jsdomtest.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_jsdomtest.ts
@@ -19,7 +19,7 @@
DEFAULT_MAX_PAYLOAD_BYTES,
VERSION_WITH_CHECKSUM,
} from './adb';
-import {utf8Encode} from '../base/string_utils';
+import {utf8Encode} from '../../base/string_utils';
test('startAuthentication', async () => {
const adb = new AdbOverWebUsb();
diff --git a/ui/src/controller/adb_record_controller_jsdomtest.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_record_controller_jsdomtest.ts
similarity index 95%
rename from ui/src/controller/adb_record_controller_jsdomtest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_record_controller_jsdomtest.ts
index e404397..6078a59 100644
--- a/ui/src/controller/adb_record_controller_jsdomtest.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_record_controller_jsdomtest.ts
@@ -13,12 +13,12 @@
// limitations under the License.
import {dingus} from 'dingusjs';
-import {utf8Encode} from '../base/string_utils';
-import {EnableTracingRequest, TraceConfig} from '../protos';
+import {utf8Encode} from '../../base/string_utils';
+import {EnableTracingRequest, TraceConfig} from '../../protos';
import {AdbStream, MockAdb, MockAdbStream} from './adb_interfaces';
import {AdbConsumerPort} from './adb_shell_controller';
import {Consumer} from './record_controller_interfaces';
-import {createEmptyState} from '../common/empty_state';
+import {createEmptyState} from './empty_state';
function generateMockConsumer(): Consumer {
return {
diff --git a/ui/src/controller/adb_shell_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_shell_controller.ts
similarity index 96%
rename from ui/src/controller/adb_shell_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_shell_controller.ts
index 5d1d156..623dc5d 100644
--- a/ui/src/controller/adb_shell_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_shell_controller.ts
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {base64Encode, utf8Decode} from '../base/string_utils';
-import {RecordingState} from '../common/state';
-import {extractTraceConfig} from '../core/trace_config_utils';
+import {base64Encode, utf8Decode} from '../../base/string_utils';
+import {RecordingState} from './state';
+import {extractTraceConfig} from './trace_config_utils';
import {AdbBaseConsumerPort, AdbConnectionState} from './adb_base_controller';
import {Adb, AdbStream} from './adb_interfaces';
import {ReadBuffersResponse} from './consumer_port_types';
diff --git a/ui/src/controller/adb_socket_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_socket_controller.ts
similarity index 98%
rename from ui/src/controller/adb_socket_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_socket_controller.ts
index 715be0d..a676747 100644
--- a/ui/src/controller/adb_socket_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_socket_controller.ts
@@ -20,14 +20,14 @@
GetTraceStatsResponse,
IPCFrame,
ReadBuffersResponse,
-} from '../protos';
+} from '../../protos';
import {AdbBaseConsumerPort, AdbConnectionState} from './adb_base_controller';
import {Adb, AdbStream} from './adb_interfaces';
import {isReadBuffersResponse} from './consumer_port_types';
import {Consumer} from './record_controller_interfaces';
-import {exists} from '../base/utils';
-import {assertTrue} from '../base/logging';
-import {RecordingState} from '../common/state';
+import {exists} from '../../base/utils';
+import {assertTrue} from '../../base/logging';
+import {RecordingState} from './state';
enum SocketState {
DISCONNECTED,
diff --git a/ui/src/frontend/recording/advanced_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/advanced_settings.ts
similarity index 97%
rename from ui/src/frontend/recording/advanced_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/advanced_settings.ts
index d762338..35e6fe2 100644
--- a/ui/src/frontend/recording/advanced_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/advanced_settings.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Dropdown, Probe, Slider, Textarea, Toggle} from '../record_widgets';
+import {Dropdown, Probe, Slider, Textarea, Toggle} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
const FTRACE_CATEGORIES = new Map<string, string>();
diff --git a/ui/src/frontend/recording/android_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/android_settings.ts
similarity index 98%
rename from ui/src/frontend/recording/android_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/android_settings.ts
index 4154fa7..7c0d741 100644
--- a/ui/src/frontend/recording/android_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/android_settings.ts
@@ -14,9 +14,9 @@
import m from 'mithril';
import {AtomId, DataSourceDescriptor} from '../../protos';
-import {Dropdown, Probe, Slider, Textarea, Toggle} from '../record_widgets';
+import {Dropdown, Probe, Slider, Textarea, Toggle} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
-import {RecordConfig} from '../../controller/record_config_types';
+import {RecordConfig} from './record_config_types';
const PUSH_ATOM_IDS = new Map<string, string>();
const PULL_ATOM_IDS = new Map<string, string>();
diff --git a/ui/src/controller/chrome_proxy_record_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/chrome_proxy_record_controller.ts
similarity index 96%
rename from ui/src/controller/chrome_proxy_record_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/chrome_proxy_record_controller.ts
index d1e1b63..ef0b999 100644
--- a/ui/src/controller/chrome_proxy_record_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/chrome_proxy_record_controller.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {binaryDecode, binaryEncode} from '../base/string_utils';
-import {TRACE_SUFFIX} from '../common/constants';
+import {binaryDecode, binaryEncode} from '../../base/string_utils';
+import {TRACE_SUFFIX} from '../../public/trace';
import {
ConsumerPortResponse,
hasProperty,
diff --git a/ui/src/frontend/recording/chrome_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/chrome_settings.ts
similarity index 97%
rename from ui/src/frontend/recording/chrome_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/chrome_settings.ts
index 8ac14cd..fd09d82 100644
--- a/ui/src/frontend/recording/chrome_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/chrome_settings.ts
@@ -13,19 +13,19 @@
// limitations under the License.
import m from 'mithril';
-import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
+import {DataSource} from './recordingV2/recording_interfaces_v2';
import {
RecordingState,
getBuiltinChromeCategoryList,
isChromeTarget,
-} from '../../common/state';
+} from './state';
import {
MultiSelect,
MultiSelectDiff,
Option as MultiSelectOption,
} from '../../widgets/multiselect';
import {Section} from '../../widgets/section';
-import {CategoryGetter, CompactProbe, Toggle} from '../record_widgets';
+import {CategoryGetter, CompactProbe, Toggle} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
function extractChromeCategories(
diff --git a/ui/src/controller/consumer_port_types.ts b/ui/src/plugins/dev.perfetto.RecordTrace/consumer_port_types.ts
similarity index 98%
rename from ui/src/controller/consumer_port_types.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/consumer_port_types.ts
index 973205f..732e9e8 100644
--- a/ui/src/controller/consumer_port_types.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/consumer_port_types.ts
@@ -18,7 +18,7 @@
IFreeBuffersResponse,
IGetTraceStatsResponse,
IReadBuffersResponse,
-} from '../protos';
+} from '../../protos';
export interface Typed {
type: string;
diff --git a/ui/src/frontend/recording/cpu_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/cpu_settings.ts
similarity index 97%
rename from ui/src/frontend/recording/cpu_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/cpu_settings.ts
index ba12267..06b2713 100644
--- a/ui/src/frontend/recording/cpu_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/cpu_settings.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Probe, Slider} from '../record_widgets';
+import {Probe, Slider} from './record_widgets';
import {POLL_INTERVAL_MS, RecordingSectionAttrs} from './recording_sections';
export class CpuSettings implements m.ClassComponent<RecordingSectionAttrs> {
diff --git a/ui/src/common/empty_state.ts b/ui/src/plugins/dev.perfetto.RecordTrace/empty_state.ts
similarity index 92%
rename from ui/src/common/empty_state.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/empty_state.ts
index c356b00..bfafe3f 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/empty_state.ts
@@ -12,10 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {
- autosaveConfigStore,
- recordTargetStore,
-} from '../frontend/record_config';
+import {autosaveConfigStore, recordTargetStore} from './record_config';
import {RecordingState} from './state';
export function createEmptyState(): RecordingState {
diff --git a/ui/src/frontend/recording/etw_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/etw_settings.ts
similarity index 96%
rename from ui/src/frontend/recording/etw_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/etw_settings.ts
index 70c1ab1..eefb8ac 100644
--- a/ui/src/frontend/recording/etw_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/etw_settings.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Probe} from '../record_widgets';
+import {Probe} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
export class EtwSettings implements m.ClassComponent<RecordingSectionAttrs> {
diff --git a/ui/src/frontend/recording/gpu_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/gpu_settings.ts
similarity index 97%
rename from ui/src/frontend/recording/gpu_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/gpu_settings.ts
index 745af99..1040f75 100644
--- a/ui/src/frontend/recording/gpu_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/gpu_settings.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Probe} from '../record_widgets';
+import {Probe} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
export class GpuSettings implements m.ClassComponent<RecordingSectionAttrs> {
diff --git a/ui/src/plugins/dev.perfetto.RecordTrace/index.ts b/ui/src/plugins/dev.perfetto.RecordTrace/index.ts
new file mode 100644
index 0000000..e0c5a1f
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/index.ts
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {RecordPage} from './record_page';
+import {RecordPageV2} from './record_page_v2';
+import {App} from '../../public/app';
+import {PerfettoPlugin} from '../../public/plugin';
+import {RecordingPageController} from './recordingV2/recording_page_controller';
+import {RecordingManager} from './recording_manager';
+import {PageAttrs} from '../../public/page';
+import {bindMithrilAttrs} from '../../base/mithril_utils';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.RecordTrace';
+
+ static onActivate(app: App) {
+ app.sidebar.addMenuItem({
+ section: 'navigation',
+ text: 'Record new trace',
+ href: '#!/record',
+ icon: 'fiber_smart_record',
+ sortOrder: 2,
+ });
+
+ const RECORDING_V2_FLAG = app.featureFlags.register({
+ id: 'recordingv2',
+ name: 'Recording V2',
+ description: 'Record using V2 interface',
+ defaultValue: false,
+ });
+ const useRecordingV2 = RECORDING_V2_FLAG.get();
+
+ const recMgr = new RecordingManager(app, useRecordingV2);
+ let page: m.ClassComponent<PageAttrs>;
+ if (useRecordingV2) {
+ const recCtl = new RecordingPageController(app, recMgr);
+ recCtl.initFactories();
+ page = bindMithrilAttrs(RecordPageV2, {app, recCtl, recMgr});
+ } else {
+ page = bindMithrilAttrs(RecordPage, {app, recMgr});
+ }
+ app.pages.registerPage({route: '/record', traceless: true, page});
+ }
+}
diff --git a/ui/src/frontend/recording/linux_perf_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/linux_perf_settings.ts
similarity index 96%
rename from ui/src/frontend/recording/linux_perf_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/linux_perf_settings.ts
index 7f9c6d4..a0fcf9f 100644
--- a/ui/src/frontend/recording/linux_perf_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/linux_perf_settings.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Probe, Slider, Textarea} from '../record_widgets';
+import {Probe, Slider, Textarea} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
const PLACEHOLDER_TEXT = `Filters for processes to profile, one per line e.g.:
diff --git a/ui/src/frontend/recording/memory_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/memory_settings.ts
similarity index 98%
rename from ui/src/frontend/recording/memory_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/memory_settings.ts
index ccfa9ef..231306f 100644
--- a/ui/src/frontend/recording/memory_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/memory_settings.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
import {MeminfoCounters, VmstatCounters} from '../../protos';
-import {Dropdown, Probe, Slider, Textarea, Toggle} from '../record_widgets';
+import {Dropdown, Probe, Slider, Textarea, Toggle} from './record_widgets';
import {POLL_INTERVAL_MS, RecordingSectionAttrs} from './recording_sections';
class HeapSettings implements m.ClassComponent<RecordingSectionAttrs> {
diff --git a/ui/src/frontend/recording/power_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/power_settings.ts
similarity index 93%
rename from ui/src/frontend/recording/power_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/power_settings.ts
index be2f6ed..bf88217 100644
--- a/ui/src/frontend/recording/power_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/power_settings.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import m from 'mithril';
-import {globals} from '../globals';
-import {Probe, Slider} from '../record_widgets';
+import {globals} from '../../frontend/globals';
+import {Probe, Slider} from './record_widgets';
import {POLL_INTERVAL_MS, RecordingSectionAttrs} from './recording_sections';
export class PowerSettings implements m.ClassComponent<RecordingSectionAttrs> {
@@ -34,6 +34,7 @@
m('span', ')'),
),
];
+ // TODO(primiano): figure out a better story for isInternalUser.
if (globals.isInternalUser) {
descr.push(
m(
diff --git a/ui/src/frontend/record_config.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_config.ts
similarity index 97%
rename from ui/src/frontend/record_config.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_config.ts
index f3bf3c5..ae41d9c 100644
--- a/ui/src/frontend/record_config.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_config.ts
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {exists} from '../base/utils';
-import {getDefaultRecordingTargets, RecordingTarget} from '../common/state';
+import {exists} from '../../base/utils';
+import {getDefaultRecordingTargets, RecordingTarget} from './state';
import {
createEmptyRecordConfig,
NamedRecordConfig,
NAMED_RECORD_CONFIG_SCHEMA,
RecordConfig,
RECORD_CONFIG_SCHEMA,
-} from '../controller/record_config_types';
+} from './record_config_types';
const LOCAL_STORAGE_RECORD_CONFIGS_KEY = 'recordConfigs';
const LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY = 'autosaveConfig';
diff --git a/ui/src/controller/record_config_types.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_config_types.ts
similarity index 100%
rename from ui/src/controller/record_config_types.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_config_types.ts
diff --git a/ui/src/controller/record_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller.ts
similarity index 94%
rename from ui/src/controller/record_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_controller.ts
index 844a7a4..8062650 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller.ts
@@ -13,19 +13,19 @@
// limitations under the License.
import {Message, Method, rpc, RPCImplCallback} from 'protobufjs';
-import {isString} from '../base/object_utils';
-import {base64Encode} from '../base/string_utils';
-import {TRACE_SUFFIX} from '../common/constants';
-import {genTraceConfig} from '../common/recordingV2/recording_config_utils';
-import {TargetInfo} from '../common/recordingV2/recording_interfaces_v2';
+import {isString} from '../../base/object_utils';
+import {base64Encode} from '../../base/string_utils';
+import {TRACE_SUFFIX} from '../../public/trace';
+import {genTraceConfig} from './recordingV2/recording_config_utils';
+import {TargetInfo} from './recordingV2/recording_interfaces_v2';
import {
AdbRecordingTarget,
isAdbTarget,
isChromeTarget,
isWindowsTarget,
RecordingTarget,
-} from '../common/state';
-import {ConsumerPort, TraceConfig} from '../protos';
+} from './state';
+import {ConsumerPort, TraceConfig} from '../../protos';
import {AdbOverWebUsb} from './adb';
import {AdbConsumerPort} from './adb_shell_controller';
import {AdbSocketConsumerPort} from './adb_socket_controller';
@@ -41,9 +41,9 @@
} from './consumer_port_types';
import {RecordConfig} from './record_config_types';
import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
-import {AppImpl} from '../core/app_impl';
import {RecordingManager} from './recording_manager';
-import {raf} from '../core/raf_scheduler';
+import {scheduleFullRedraw} from '../../widgets/raf';
+import {App} from '../../public/app';
type RPCImplMethod = Method | rpc.ServiceMethod<Message<{}>, Message<{}>>;
@@ -189,6 +189,7 @@
}
export class RecordController implements Consumer {
+ private app: App;
private recMgr: RecordingManager;
private config: RecordConfig | null = null;
private readonly extensionPort: MessagePort;
@@ -206,7 +207,8 @@
// char, it is the 'targetOS'
private controllerPromises = new Map<string, Promise<RpcConsumerPort>>();
- constructor(recMgr: RecordingManager, extensionPort: MessagePort) {
+ constructor(app: App, recMgr: RecordingManager, extensionPort: MessagePort) {
+ this.app = app;
this.recMgr = recMgr;
this.consumerPort = ConsumerPort.create(this.rpcImpl.bind(this));
this.extensionPort = extensionPort;
@@ -219,7 +221,7 @@
refreshOnStateChange() {
// TODO(eseckler): Use ConsumerPort's QueryServiceState instead
// of posting a custom extension message to retrieve the category list.
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
if (this.state.fetchChromeCategories && !this.fetchedCategories) {
this.fetchedCategories = true;
if (this.state.extensionInstalled) {
@@ -227,12 +229,7 @@
}
this.recMgr.setFetchChromeCategories(false);
}
- if (
- this.state.recordConfig === this.config &&
- this.state.recordingInProgress === this.recordingInProgress
- ) {
- return;
- }
+
this.config = this.state.recordConfig;
const configProto = genConfigProto(this.config, this.state.recordingTarget);
@@ -325,7 +322,7 @@
return;
}
const trace = this.generateTrace();
- AppImpl.instance.openTraceFromBuffer({
+ this.app.openTraceFromBuffer({
title: 'Recorded trace',
buffer: trace.buffer,
fileName: `recorded_trace${this.recordedTraceSuffix}`,
diff --git a/ui/src/controller/record_controller_interfaces.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller_interfaces.ts
similarity index 97%
rename from ui/src/controller/record_controller_interfaces.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_controller_interfaces.ts
index e9662fd..f29940a 100644
--- a/ui/src/controller/record_controller_interfaces.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller_interfaces.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {TRACE_SUFFIX} from '../common/constants';
+import {TRACE_SUFFIX} from '../../public/trace';
import {ConsumerPortResponse} from './consumer_port_types';
export type ErrorCallback = (_: string) => void;
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller_jsdomtest.ts
similarity index 99%
rename from ui/src/controller/record_controller_jsdomtest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_controller_jsdomtest.ts
index 442e4b8..1035369 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller_jsdomtest.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists} from '../base/logging';
-import {TraceConfig} from '../protos';
+import {assertExists} from '../../base/logging';
+import {TraceConfig} from '../../protos';
import {createEmptyRecordConfig} from './record_config_types';
import {genConfigProto, toPbtxt} from './record_controller';
diff --git a/ui/src/frontend/record_page.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_page.ts
similarity index 84%
rename from ui/src/frontend/record_page.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_page.ts
index bb161b0..021a5db 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_page.ts
@@ -26,38 +26,32 @@
LoadedConfig,
MAX_TIME,
RecordingTarget,
-} from '../common/state';
-import {AdbOverWebUsb} from '../controller/adb';
-import {RecordConfig} from '../controller/record_config_types';
-import {featureFlags} from '../core/feature_flags';
-import {raf} from '../core/raf_scheduler';
-import {PageAttrs} from '../public/page';
+} from './state';
+import {AdbOverWebUsb} from './adb';
+import {RECORD_CONFIG_SCHEMA, RecordConfig} from './record_config_types';
+import {PageAttrs} from '../../public/page';
import {
autosaveConfigStore,
recordConfigStore,
recordTargetStore,
} from './record_config';
import {CodeSnippet} from './record_widgets';
-import {AdvancedSettings} from './recording/advanced_settings';
-import {AndroidSettings} from './recording/android_settings';
-import {ChromeSettings} from './recording/chrome_settings';
-import {CpuSettings} from './recording/cpu_settings';
-import {GpuSettings} from './recording/gpu_settings';
-import {LinuxPerfSettings} from './recording/linux_perf_settings';
-import {MemorySettings} from './recording/memory_settings';
-import {PowerSettings} from './recording/power_settings';
-import {RecordingSettings} from './recording/recording_settings';
-import {EtwSettings} from './recording/etw_settings';
-import {createPermalink} from './permalink';
-import {AppImpl} from '../core/app_impl';
-import {RecordingManager} from '../controller/recording_manager';
-
-export const PERSIST_CONFIG_FLAG = featureFlags.register({
- id: 'persistConfigsUI',
- name: 'Config persistence UI',
- description: 'Show experimental config persistence UI on the record page.',
- defaultValue: true,
-});
+import {AdvancedSettings} from './advanced_settings';
+import {AndroidSettings} from './android_settings';
+import {ChromeSettings} from './chrome_settings';
+import {CpuSettings} from './cpu_settings';
+import {GpuSettings} from './gpu_settings';
+import {LinuxPerfSettings} from './linux_perf_settings';
+import {MemorySettings} from './memory_settings';
+import {PowerSettings} from './power_settings';
+import {RecordingSettings} from './recording_settings';
+import {EtwSettings} from './etw_settings';
+import {RecordingManager} from './recording_manager';
+import {scheduleFullRedraw} from '../../widgets/raf';
+import {App} from '../../public/app';
+import {GcsUploader, BUCKET_NAME, MIME_JSON} from '../../base/gcs_uploader';
+import {showModal} from '../../widgets/modal';
+import {CopyableLink} from '../../widgets/copyable_link';
export const RECORDING_SECTIONS = [
'buffers',
@@ -158,22 +152,20 @@
recMgr.setRecordingTarget(recordingTarget);
recordTargetStore.save(target);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
function Instructions(recMgr: RecordingManager, cssClass: string) {
return m(
`.record-section.instructions${cssClass}`,
m('header', 'Recording command'),
- PERSIST_CONFIG_FLAG.get()
- ? m(
- 'button.permalinkconfig',
- {
- onclick: () => createPermalink({mode: 'RECORDING_OPTS'}),
- },
- 'Share recording settings',
- )
- : null,
+ m(
+ 'button.permalinkconfig',
+ {
+ onclick: () => uploadRecordingConfig(recMgr.state.recordConfig),
+ },
+ 'Share recording settings',
+ ),
RecordingSnippet(recMgr),
BufferUsageProgressBar(recMgr),
m('.buttons', StopCancelButtons(recMgr)),
@@ -203,7 +195,7 @@
disabled: loadedConfigEqual(configType, recMgr.state.lastLoadedConfig),
onclick: () => {
recMgr.setRecordConfig(config, configType);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
},
m('i.material-icons', 'file_upload'),
@@ -249,7 +241,7 @@
type: 'NAMED',
name: item.title,
});
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
},
},
@@ -262,7 +254,7 @@
title: 'Remove configuration',
onclick: () => {
recordConfigStore.delete(item.key);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
},
m('i.material-icons', 'delete'),
@@ -297,7 +289,7 @@
placeholder: 'Title for config',
oninput() {
ConfigTitleState.setTitle(this.value);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
}),
m(
@@ -313,7 +305,7 @@
recMgr.state.recordConfig,
ConfigTitleState.getTitle(),
);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
ConfigTitleState.clearTitle();
},
},
@@ -331,7 +323,7 @@
)
) {
recMgr.clearRecordConfig();
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
},
},
@@ -578,7 +570,7 @@
function onStartRecordingPressed(recMgr: RecordingManager) {
location.href = '#!/record/instructions';
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
autosaveConfigStore.save(recMgr.state.recordConfig);
const target = recMgr.state.recordingTarget;
@@ -587,7 +579,7 @@
isChromeTarget(target) ||
isWindowsTarget(target)
) {
- AppImpl.instance.analytics.logEvent(
+ recMgr.app.analytics.logEvent(
'Record Trace',
`Record trace (${target.os})`,
);
@@ -747,7 +739,7 @@
'.record-menu',
{
class: recInProgress ? 'disabled' : '',
- onclick: () => raf.scheduleFullRedraw(),
+ onclick: () => scheduleFullRedraw(),
},
m('header', 'Trace config'),
m(
@@ -770,22 +762,20 @@
m('.sub', 'Manually record trace'),
),
),
- PERSIST_CONFIG_FLAG.get()
- ? m(
- 'a[href="#!/record/config"]',
- {
- onclick: () => {
- recordConfigStore.reloadFromLocalStorage();
- },
- },
- m(
- `li${routePage === 'config' ? '.active' : ''}`,
- m('i.material-icons', 'save'),
- m('.title', 'Saved configs'),
- m('.sub', 'Manage local configs'),
- ),
- )
- : null,
+ m(
+ 'a[href="#!/record/config"]',
+ {
+ onclick: () => {
+ recordConfigStore.reloadFromLocalStorage();
+ },
+ },
+ m(
+ `li${routePage === 'config' ? '.active' : ''}`,
+ m('i.material-icons', 'save'),
+ m('.title', 'Saved configs'),
+ m('.sub', 'Manage local configs'),
+ ),
+ ),
),
m('header', 'Probes'),
m('ul', probes),
@@ -796,10 +786,36 @@
return routePage === section ? '.active' : '';
}
-export class RecordPage implements m.ClassComponent<PageAttrs> {
- private readonly recMgr = RecordingManager.instance;
+export interface RecordPageAttrs extends PageAttrs {
+ app: App;
+ recMgr: RecordingManager;
+}
- view({attrs}: m.CVnode<PageAttrs>) {
+export class RecordPage implements m.ClassComponent<RecordPageAttrs> {
+ private readonly recMgr: RecordingManager;
+ private lastSubpage: string | undefined = undefined;
+
+ constructor({attrs}: m.CVnode<RecordPageAttrs>) {
+ this.recMgr = attrs.recMgr;
+ }
+
+ oninit({attrs}: m.CVnode<RecordPageAttrs>) {
+ this.lastSubpage = attrs.subpage;
+ if (attrs.subpage !== undefined && attrs.subpage.startsWith('/share/')) {
+ const hash = attrs.subpage.substring(7);
+ loadRecordConfig(this.recMgr, hash);
+ attrs.app.navigate('#!/record/instructions');
+ }
+ }
+
+ view({attrs}: m.CVnode<RecordPageAttrs>) {
+ if (attrs.subpage !== this.lastSubpage) {
+ this.lastSubpage = attrs.subpage;
+ // TODO(primiano): this is a hack necesasry to retrigger the generation of
+ // the record cmdline. Refactor this code once record v1 vs v2 is gone.
+ this.recMgr.setRecordConfig(this.recMgr.state.recordConfig);
+ }
+
const pages: m.Children = [];
// we need to remove the `/` character from the route
let routePage = attrs.subpage ? attrs.subpage.substr(1) : '';
@@ -862,3 +878,35 @@
);
}
}
+
+export async function uploadRecordingConfig(recordConfig: RecordConfig) {
+ const json = JSON.stringify(recordConfig);
+ const uploader: GcsUploader = new GcsUploader(json, {
+ mimeType: MIME_JSON,
+ });
+ await uploader.waitForCompletion();
+ const hash = uploader.uploadedFileName;
+ const url = `${self.location.origin}/#!/record/share/${hash}`;
+ showModal({
+ title: 'Shareable record settings',
+ content: m(CopyableLink, {url}),
+ });
+}
+
+export async function loadRecordConfig(recMgr: RecordingManager, hash: string) {
+ const url = `https://storage.googleapis.com/${BUCKET_NAME}/${hash}`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ showModal({title: 'Load failed', content: `Could not fetch ${url}`});
+ return;
+ }
+ const text = await response.text();
+ const json = JSON.parse(text);
+ const res = RECORD_CONFIG_SCHEMA.safeParse(json);
+ if (!res.success) {
+ throw new Error(
+ 'Failed to deserialize record settings ' + res.error.toString(),
+ );
+ }
+ recMgr.setRecordConfig(res.data);
+}
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_page_v2.ts
similarity index 76%
rename from ui/src/frontend/record_page_v2.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_page_v2.ts
index 5fbeac0..3559332 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_page_v2.ts
@@ -14,60 +14,52 @@
import m from 'mithril';
import {Attributes} from 'mithril';
-import {assertExists} from '../base/logging';
-import {RecordingConfigUtils} from '../common/recordingV2/recording_config_utils';
+import {assertExists} from '../../base/logging';
+import {RecordingConfigUtils} from './recordingV2/recording_config_utils';
import {
ChromeTargetInfo,
RecordingTargetV2,
TargetInfo,
-} from '../common/recordingV2/recording_interfaces_v2';
+} from './recordingV2/recording_interfaces_v2';
import {
RecordingPageController,
RecordingState,
-} from '../common/recordingV2/recording_page_controller';
-import {
- EXTENSION_NAME,
- EXTENSION_URL,
-} from '../common/recordingV2/recording_utils';
-import {targetFactoryRegistry} from '../common/recordingV2/target_factory_registry';
-import {raf} from '../core/raf_scheduler';
-import {PageAttrs} from '../public/page';
+} from './recordingV2/recording_page_controller';
+import {EXTENSION_NAME, EXTENSION_URL} from './recordingV2/recording_utils';
+import {targetFactoryRegistry} from './recordingV2/target_factory_registry';
+import {PageAttrs} from '../../public/page';
import {recordConfigStore} from './record_config';
import {
Configurations,
+ loadRecordConfig,
maybeGetActiveCss,
- PERSIST_CONFIG_FLAG,
RECORDING_SECTIONS,
+ uploadRecordingConfig,
} from './record_page';
import {CodeSnippet} from './record_widgets';
-import {AdvancedSettings} from './recording/advanced_settings';
-import {AndroidSettings} from './recording/android_settings';
-import {ChromeSettings} from './recording/chrome_settings';
-import {CpuSettings} from './recording/cpu_settings';
-import {EtwSettings} from './recording/etw_settings';
-import {GpuSettings} from './recording/gpu_settings';
-import {LinuxPerfSettings} from './recording/linux_perf_settings';
-import {MemorySettings} from './recording/memory_settings';
-import {PowerSettings} from './recording/power_settings';
-import {RecordingSettings} from './recording/recording_settings';
-import {FORCE_RESET_MESSAGE} from './recording/recording_ui_utils';
-import {showAddNewTargetModal} from './recording/reset_target_modal';
-import {createPermalink} from './permalink';
-import {RecordingManager} from '../controller/recording_manager';
-import {RecordConfig} from '../controller/record_config_types';
+import {AdvancedSettings} from './advanced_settings';
+import {AndroidSettings} from './android_settings';
+import {ChromeSettings} from './chrome_settings';
+import {CpuSettings} from './cpu_settings';
+import {EtwSettings} from './etw_settings';
+import {GpuSettings} from './gpu_settings';
+import {LinuxPerfSettings} from './linux_perf_settings';
+import {MemorySettings} from './memory_settings';
+import {PowerSettings} from './power_settings';
+import {RecordingSettings} from './recording_settings';
+import {FORCE_RESET_MESSAGE} from './recording_ui_utils';
+import {showAddNewTargetModal} from './reset_target_modal';
+import {RecordingManager} from './recording_manager';
+import {RecordConfig} from './record_config_types';
+import {App} from '../../public/app';
+import {scheduleFullRedraw} from '../../widgets/raf';
const START_RECORDING_MESSAGE = 'Start Recording';
// TODO(primiano): this is needs to be rewritten, but then i'm going to rewrite
// the whole record_page_v2 so not worth cleaning up now.
-let _controller: RecordingPageController;
-function controller(): RecordingPageController {
- if (_controller === undefined) {
- _controller = new RecordingPageController(RecordingManager.instance);
- }
- return _controller;
-}
-const recordConfigUtils = new RecordingConfigUtils();
+let controller: RecordingPageController;
+let recordConfigUtils: RecordingConfigUtils;
// Options for displaying a target selection menu.
export interface TargetSelectionOptions {
@@ -106,13 +98,13 @@
function RecordingPlatformSelection() {
// Don't show the platform selector while we are recording a trace.
- if (controller().getState() >= RecordingState.RECORDING) return undefined;
+ if (controller.getState() >= RecordingState.RECORDING) return undefined;
return m(
'.target',
m(
'.chip',
- {onclick: () => showAddNewTargetModal(controller())},
+ {onclick: () => showAddNewTargetModal(controller)},
m('button', 'Add new recording target'),
m('i.material-icons', 'add'),
),
@@ -121,13 +113,13 @@
}
export function targetSelection(): m.Vnode | undefined {
- if (!controller().shouldShowTargetSelection()) {
+ if (!controller.shouldShowTargetSelection()) {
return undefined;
}
const targets: RecordingTargetV2[] = targetFactoryRegistry.listTargets();
const targetNames = [];
- const targetInfo = controller().getTargetInfo();
+ const targetInfo = controller.getTargetInfo();
if (!targetInfo) {
targetNames.push(m('option', 'PLEASE_SELECT_TARGET'));
}
@@ -149,7 +141,7 @@
{
selectedIndex,
onchange: (e: Event) => {
- controller().onTargetSelection((e.target as HTMLSelectElement).value);
+ controller.onTargetSelection((e.target as HTMLSelectElement).value);
},
onupdate: (select) => {
// Work around mithril bug
@@ -178,24 +170,22 @@
}
function Instructions(recCfg: RecordConfig, cssClass: string) {
- if (controller().getState() < RecordingState.TARGET_SELECTED) {
+ if (controller.getState() < RecordingState.TARGET_SELECTED) {
return undefined;
}
// We will have a valid target at this step because we checked the state.
- const targetInfo = assertExists(controller().getTargetInfo());
+ const targetInfo = assertExists(controller.getTargetInfo());
return m(
`.record-section.instructions${cssClass}`,
m('header', 'Recording command'),
- PERSIST_CONFIG_FLAG.get()
- ? m(
- 'button.permalinkconfig',
- {
- onclick: () => createPermalink({mode: 'RECORDING_OPTS'}),
- },
- 'Share recording settings',
- )
- : null,
+ m(
+ 'button.permalinkconfig',
+ {
+ onclick: () => uploadRecordingConfig(recCfg),
+ },
+ 'Share recording settings',
+ ),
RecordingSnippet(recCfg, targetInfo),
BufferUsageProgressBar(),
m('.buttons', StopCancelButtons()),
@@ -204,13 +194,13 @@
function BufferUsageProgressBar() {
// Show the Buffer Usage bar only after we start recording a trace.
- if (controller().getState() !== RecordingState.RECORDING) {
+ if (controller.getState() !== RecordingState.RECORDING) {
return undefined;
}
- controller().fetchBufferUsage();
+ controller.fetchBufferUsage();
- const bufferUsage = controller().getBufferUsagePercentage();
+ const bufferUsage = controller.getBufferUsagePercentage();
// Buffer usage is not available yet on Android.
if (bufferUsage === 0) return undefined;
@@ -222,11 +212,11 @@
}
function RecordingNotes(recCfg: RecordConfig) {
- if (controller().getState() !== RecordingState.TARGET_INFO_DISPLAYED) {
+ if (controller.getState() !== RecordingState.TARGET_INFO_DISPLAYED) {
return undefined;
}
// We will have a valid target at this step because we checked the state.
- const targetInfo = assertExists(controller().getTargetInfo());
+ const targetInfo = assertExists(controller.getTargetInfo());
const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing';
const cmdlineUrl =
@@ -321,7 +311,7 @@
function RecordingSnippet(recCfg: RecordConfig, targetInfo: TargetInfo) {
// We don't need commands to start tracing on chrome
if (isChromeTargetInfo(targetInfo)) {
- if (controller().getState() > RecordingState.AUTH_P2) {
+ if (controller.getState() > RecordingState.AUTH_P2) {
// If the UI has started tracing, don't display a message guiding the user
// to start recording.
return undefined;
@@ -373,14 +363,14 @@
function RecordingButton(recCfg: RecordConfig) {
if (
- controller().getState() !== RecordingState.TARGET_INFO_DISPLAYED ||
- !controller().canCreateTracingSession()
+ controller.getState() !== RecordingState.TARGET_INFO_DISPLAYED ||
+ !controller.canCreateTracingSession()
) {
return undefined;
}
// We know we have a target because we checked the state.
- const targetInfo = assertExists(controller().getTargetInfo());
+ const targetInfo = assertExists(controller.getTargetInfo());
const hasDataSources = recordConfigUtils.fetchLatestRecordCommand(
recCfg,
targetInfo,
@@ -395,7 +385,7 @@
'button',
{
class: 'selected',
- onclick: () => controller().onStartRecordingPressed(),
+ onclick: () => controller.onStartRecordingPressed(),
},
START_RECORDING_MESSAGE,
),
@@ -404,21 +394,17 @@
function StopCancelButtons() {
// Show the Stop/Cancel buttons only while we are recording a trace.
- if (!controller().shouldShowStopCancelButtons()) {
+ if (!controller.shouldShowStopCancelButtons()) {
return undefined;
}
const stop = m(
`button.selected`,
- {onclick: () => controller().onStop()},
+ {onclick: () => controller.onStop()},
'Stop',
);
- const cancel = m(
- `button`,
- {onclick: () => controller().onCancel()},
- 'Cancel',
- );
+ const cancel = m(`button`, {onclick: () => controller.onCancel()}, 'Cancel');
return [stop, cancel];
}
@@ -508,7 +494,7 @@
// We only display the probes when we have a valid target, so it's not
// possible for the target to be undefined here.
- const targetType = assertExists(controller().getTargetInfo()).targetType;
+ const targetType = assertExists(controller.getTargetInfo()).targetType;
const probes = [];
if (targetType === 'LINUX') {
probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe);
@@ -533,10 +519,10 @@
'.record-menu',
{
class:
- controller().getState() > RecordingState.TARGET_INFO_DISPLAYED
+ controller.getState() > RecordingState.TARGET_INFO_DISPLAYED
? 'disabled'
: '',
- onclick: () => raf.scheduleFullRedraw(),
+ onclick: () => scheduleFullRedraw(),
},
m('header', 'Trace config'),
m(
@@ -559,22 +545,20 @@
m('.sub', 'Manually record trace'),
),
),
- PERSIST_CONFIG_FLAG.get()
- ? m(
- 'a[href="#!/record/config"]',
- {
- onclick: () => {
- recordConfigStore.reloadFromLocalStorage();
- },
- },
- m(
- `li${routePage === 'config' ? '.active' : ''}`,
- m('i.material-icons', 'save'),
- m('.title', 'Saved configs'),
- m('.sub', 'Manage local configs'),
- ),
- )
- : null,
+ m(
+ 'a[href="#!/record/config"]',
+ {
+ onclick: () => {
+ recordConfigStore.reloadFromLocalStorage();
+ },
+ },
+ m(
+ `li${routePage === 'config' ? '.active' : ''}`,
+ m('i.material-icons', 'save'),
+ m('.title', 'Saved configs'),
+ m('.sub', 'Manage local configs'),
+ ),
+ ),
),
m('header', 'Probes'),
m('ul', probes),
@@ -584,10 +568,10 @@
function getRecordContainer(recMgr: RecordingManager, subpage?: string) {
const recCfg = recMgr.state.recordConfig;
const components: m.Children[] = [RecordHeader(recMgr)];
- if (controller().getState() === RecordingState.NO_TARGET) {
+ if (controller.getState() === RecordingState.NO_TARGET) {
components.push(m('.full-centered', 'Please connect a valid target.'));
return m('.record-container', components);
- } else if (controller().getState() <= RecordingState.ASK_TO_FORCE_P1) {
+ } else if (controller.getState() <= RecordingState.ASK_TO_FORCE_P1) {
components.push(
m(
'.full-centered',
@@ -597,13 +581,13 @@
),
);
return m('.record-container', components);
- } else if (controller().getState() === RecordingState.AUTH_P1) {
+ } else if (controller.getState() === RecordingState.AUTH_P1) {
components.push(
m('.full-centered', 'Please allow USB debugging on the device.'),
);
return m('.record-container', components);
} else if (
- controller().getState() === RecordingState.WAITING_FOR_TRACE_DISPLAY
+ controller.getState() === RecordingState.WAITING_FOR_TRACE_DISPLAY
) {
components.push(
m('.full-centered', 'Waiting for the trace to be collected.'),
@@ -645,7 +629,7 @@
for (const [section, component] of settingsSections.entries()) {
pages.push(
m(component, {
- dataSources: controller().getTargetInfo()?.dataSources || [],
+ dataSources: controller.getTargetInfo()?.dataSources || [],
cssClass: maybeGetActiveCss(routePage, section),
recState: recMgr.state,
}),
@@ -656,20 +640,43 @@
return m('.record-container', components);
}
-export class RecordPageV2 implements m.ClassComponent<PageAttrs> {
- private readonly recMgr = RecordingManager.instance;
+export interface RecordPageV2Attrs extends PageAttrs {
+ app: App;
+ recCtl: RecordingPageController;
+ recMgr: RecordingManager;
+}
- oninit(): void {
- controller().initFactories();
+export class RecordPageV2 implements m.ClassComponent<RecordPageV2Attrs> {
+ private lastSubpage: string | undefined = undefined;
+
+ constructor({attrs}: m.CVnode<RecordPageV2Attrs>) {
+ controller ??= attrs.recCtl;
+ recordConfigUtils ??= new RecordingConfigUtils();
}
- view({attrs}: m.CVnode<PageAttrs>) {
+ oninit({attrs}: m.CVnode<RecordPageV2Attrs>) {
+ this.lastSubpage = attrs.subpage;
+ if (attrs.subpage !== undefined && attrs.subpage.startsWith('/share/')) {
+ const hash = attrs.subpage.substring(7);
+ loadRecordConfig(attrs.recMgr, hash);
+ attrs.app.navigate('#!/record/instructions');
+ }
+ }
+
+ view({attrs}: m.CVnode<RecordPageV2Attrs>) {
+ if (attrs.subpage !== this.lastSubpage) {
+ this.lastSubpage = attrs.subpage;
+ // TODO(primiano): this is a hack necesasry to retrigger the generation of
+ // the record cmdline. Refactor this code once record v1 vs v2 is gone.
+ attrs.recMgr.setRecordConfig(attrs.recMgr.state.recordConfig);
+ }
+
return m(
'.record-page',
- controller().getState() > RecordingState.TARGET_INFO_DISPLAYED
+ controller.getState() > RecordingState.TARGET_INFO_DISPLAYED
? m('.hider')
: [],
- getRecordContainer(this.recMgr, attrs.subpage),
+ getRecordContainer(attrs.recMgr, attrs.subpage),
);
}
}
diff --git a/ui/src/frontend/record_widgets.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_widgets.ts
similarity index 96%
rename from ui/src/frontend/record_widgets.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_widgets.ts
index 90f3c3d..325237b 100644
--- a/ui/src/frontend/record_widgets.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_widgets.ts
@@ -13,11 +13,11 @@
// limitations under the License.
import m from 'mithril';
-import {copyToClipboard} from '../base/clipboard';
-import {assertExists} from '../base/logging';
-import {RecordConfig} from '../controller/record_config_types';
-import {raf} from '../core/raf_scheduler';
-import {assetSrc} from '../base/assets';
+import {copyToClipboard} from '../../base/clipboard';
+import {assertExists} from '../../base/logging';
+import {RecordConfig} from './record_config_types';
+import {assetSrc} from '../../base/assets';
+import {scheduleFullRedraw} from '../../widgets/raf';
export declare type Setter<T> = (cfg: RecordConfig, val: T) => void;
export declare type Getter<T> = (cfg: RecordConfig) => T;
@@ -63,7 +63,7 @@
view({attrs, children}: m.CVnode<ProbeAttrs>) {
const onToggle = (enabled: boolean) => {
attrs.setEnabled(attrs.recCfg, enabled);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
};
const enabled = attrs.isEnabled(attrs.recCfg);
@@ -130,7 +130,7 @@
view({attrs}: m.CVnode<ToggleAttrs>) {
const onToggle = (enabled: boolean) => {
attrs.setEnabled(attrs.recCfg, enabled);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
};
const enabled = attrs.isEnabled(attrs.recCfg);
@@ -175,7 +175,7 @@
export class Slider implements m.ClassComponent<SliderAttrs> {
onValueChange(attrs: SliderAttrs, newVal: number) {
attrs.set(attrs.recCfg, newVal);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
onTimeValueChange(attrs: SliderAttrs, hms: string) {
@@ -276,7 +276,7 @@
selKeys.push(item.value);
}
attrs.set(attrs.recCfg, selKeys);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
view({attrs}: m.CVnode<DropdownAttrs>) {
@@ -326,7 +326,7 @@
export class Textarea implements m.ClassComponent<TextareaAttrs> {
onChange(attrs: TextareaAttrs, dom: HTMLTextAreaElement) {
attrs.set(attrs.recCfg, dom.value);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
view({attrs}: m.CVnode<TextareaAttrs>) {
@@ -400,7 +400,7 @@
if (!enabled && index !== -1) {
values.splice(index, 1);
}
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
view({attrs}: m.CVnode<CategoriesCheckboxListParams>) {
diff --git a/ui/src/common/recordingV2/adb_connection_impl.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_impl.ts
similarity index 94%
rename from ui/src/common/recordingV2/adb_connection_impl.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_impl.ts
index 99ef224..33e0dc1 100644
--- a/ui/src/common/recordingV2/adb_connection_impl.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_impl.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer} from '../../base/deferred';
-import {ArrayBufferBuilder} from '../../base/array_buffer_builder';
+import {defer} from '../../../base/deferred';
+import {ArrayBufferBuilder} from '../../../base/array_buffer_builder';
import {AdbFileHandler} from './adb_file_handler';
import {
AdbConnection,
@@ -21,7 +21,7 @@
OnDisconnectCallback,
OnMessageCallback,
} from './recording_interfaces_v2';
-import {utf8Decode} from '../../base/string_utils';
+import {utf8Decode} from '../../../base/string_utils';
export abstract class AdbConnectionImpl implements AdbConnection {
// onStatus and onDisconnect are set to callbacks passed from the caller.
diff --git a/ui/src/common/recordingV2/adb_connection_over_websocket.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_over_websocket.ts
similarity index 98%
rename from ui/src/common/recordingV2/adb_connection_over_websocket.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_over_websocket.ts
index 160b257..9c9d139 100644
--- a/ui/src/common/recordingV2/adb_connection_over_websocket.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_over_websocket.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer, Deferred} from '../../base/deferred';
-import {utf8Decode} from '../../base/string_utils';
+import {defer, Deferred} from '../../../base/deferred';
+import {utf8Decode} from '../../../base/string_utils';
import {AdbConnectionImpl} from './adb_connection_impl';
import {RecordingError} from './recording_error_handling';
import {
diff --git a/ui/src/common/recordingV2/adb_connection_over_webusb.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_over_webusb.ts
similarity index 98%
rename from ui/src/common/recordingV2/adb_connection_over_webusb.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_over_webusb.ts
index 713d8b3..715d366 100644
--- a/ui/src/common/recordingV2/adb_connection_over_webusb.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_connection_over_webusb.ts
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer, Deferred} from '../../base/deferred';
-import {assertExists, assertFalse, assertTrue} from '../../base/logging';
-import {isString} from '../../base/object_utils';
-import {utf8Decode, utf8Encode} from '../../base/string_utils';
-import {CmdType} from '../../controller/adb_interfaces';
+import {defer, Deferred} from '../../../base/deferred';
+import {assertExists, assertFalse, assertTrue} from '../../../base/logging';
+import {isString} from '../../../base/object_utils';
+import {utf8Decode, utf8Encode} from '../../../base/string_utils';
+import {CmdType} from '../adb_interfaces';
import {AdbConnectionImpl} from './adb_connection_impl';
import {AdbKeyManager, maybeStoreKey} from './auth/adb_key_manager';
import {RecordingError, wrapRecordingError} from './recording_error_handling';
diff --git a/ui/src/common/recordingV2/adb_file_handler.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_file_handler.ts
similarity index 94%
rename from ui/src/common/recordingV2/adb_file_handler.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_file_handler.ts
index 1016fe7..078726f 100644
--- a/ui/src/common/recordingV2/adb_file_handler.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/adb_file_handler.ts
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer, Deferred} from '../../base/deferred';
-import {assertFalse} from '../../base/logging';
-import {ArrayBufferBuilder} from '../../base/array_buffer_builder';
+import {defer, Deferred} from '../../../base/deferred';
+import {assertFalse} from '../../../base/logging';
+import {ArrayBufferBuilder} from '../../../base/array_buffer_builder';
import {RecordingError} from './recording_error_handling';
import {ByteStream} from './recording_interfaces_v2';
import {
BINARY_PUSH_FAILURE,
BINARY_PUSH_UNKNOWN_RESPONSE,
} from './recording_utils';
-import {utf8Decode} from '../../base/string_utils';
+import {utf8Decode} from '../../../base/string_utils';
// https://cs.android.com/android/platform/superproject/+/main:packages/
// modules/adb/file_sync_protocol.h;l=144
diff --git a/ui/src/common/recordingV2/auth/adb_auth.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/adb_auth.ts
similarity index 97%
rename from ui/src/common/recordingV2/auth/adb_auth.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/adb_auth.ts
index aec8752..7ed275e 100644
--- a/ui/src/common/recordingV2/auth/adb_auth.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/adb_auth.ts
@@ -13,12 +13,12 @@
// limitations under the License.
import {BigInteger, RSAKey} from 'jsbn-rsa';
-import {assertExists, assertTrue} from '../../../base/logging';
+import {assertExists, assertTrue} from '../../../../base/logging';
import {
base64Decode,
base64Encode,
hexEncode,
-} from '../../../base/string_utils';
+} from '../../../../base/string_utils';
import {RecordingError} from '../recording_error_handling';
const WORD_SIZE = 4;
diff --git a/ui/src/common/recordingV2/auth/adb_key_manager.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/adb_key_manager.ts
similarity index 98%
rename from ui/src/common/recordingV2/auth/adb_key_manager.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/adb_key_manager.ts
index 53e233f..0ce297b 100644
--- a/ui/src/common/recordingV2/auth/adb_key_manager.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/adb_key_manager.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assetSrc} from '../../../base/assets';
+import {assetSrc} from '../../../../base/assets';
import {AdbKey} from './adb_auth';
function isPasswordCredential(
diff --git a/ui/src/common/recordingV2/auth/credentials_interfaces.d.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/credentials_interfaces.d.ts
similarity index 100%
rename from ui/src/common/recordingV2/auth/credentials_interfaces.d.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/auth/credentials_interfaces.d.ts
diff --git a/ui/src/common/recordingV2/chrome_traced_tracing_session.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/chrome_traced_tracing_session.ts
similarity index 95%
rename from ui/src/common/recordingV2/chrome_traced_tracing_session.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/chrome_traced_tracing_session.ts
index f8ecd03..9461190 100644
--- a/ui/src/common/recordingV2/chrome_traced_tracing_session.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/chrome_traced_tracing_session.ts
@@ -12,28 +12,28 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer, Deferred} from '../../base/deferred';
-import {assertExists, assertTrue} from '../../base/logging';
-import {binaryDecode, binaryEncode} from '../../base/string_utils';
+import {defer, Deferred} from '../../../base/deferred';
+import {assertExists, assertTrue} from '../../../base/logging';
+import {binaryDecode, binaryEncode} from '../../../base/string_utils';
import {
ChromeExtensionMessage,
isChromeExtensionError,
isChromeExtensionStatus,
isGetCategoriesResponse,
-} from '../../controller/chrome_proxy_record_controller';
+} from '../chrome_proxy_record_controller';
import {
isDisableTracingResponse,
isEnableTracingResponse,
isFreeBuffersResponse,
isGetTraceStatsResponse,
isReadBuffersResponse,
-} from '../../controller/consumer_port_types';
+} from '../consumer_port_types';
import {
EnableTracingRequest,
IBufferStats,
ISlice,
TraceConfig,
-} from '../../protos';
+} from '../../../protos';
import {RecordingError} from './recording_error_handling';
import {
TracingSession,
diff --git a/ui/src/common/recordingV2/host_os_byte_stream.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/host_os_byte_stream.ts
similarity index 97%
rename from ui/src/common/recordingV2/host_os_byte_stream.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/host_os_byte_stream.ts
index 3c43630..a03b791 100644
--- a/ui/src/common/recordingV2/host_os_byte_stream.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/host_os_byte_stream.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer} from '../../base/deferred';
+import {defer} from '../../../base/deferred';
import {
ByteStream,
OnStreamCloseCallback,
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils.ts
similarity index 98%
rename from ui/src/common/recordingV2/recording_config_utils.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils.ts
index bc3262e..e4eca50 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils.ts
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {isString} from '../../base/object_utils';
-import {base64Encode} from '../../base/string_utils';
-import {exists} from '../../base/utils';
-import {RecordConfig} from '../../controller/record_config_types';
+import {isString} from '../../../base/object_utils';
+import {base64Encode} from '../../../base/string_utils';
+import {exists} from '../../../base/utils';
+import {RecordConfig} from '../record_config_types';
import {
AndroidLogConfig,
AndroidLogId,
@@ -41,7 +41,7 @@
TraceConfig,
TrackEventConfig,
VmstatCounters,
-} from '../../protos';
+} from '../../../protos';
import {TargetInfo} from './recording_interfaces_v2';
import PerfClock = PerfEvents.PerfClock;
import Timebase = PerfEvents.Timebase;
@@ -463,7 +463,7 @@
if (uiCfg.androidStatsdPushedAtoms.length > 0) {
ds.config.statsdTracingConfig.pushAtomId =
- uiCfg.androidStatsdPushedAtoms.map((atom) => atom as any as AtomId);
+ uiCfg.androidStatsdPushedAtoms.map((atom) => atom as unknown as AtomId);
}
const needPulledAtomConfig =
@@ -471,7 +471,7 @@
uiCfg.androidStatsdPulledAtoms.length > 0;
if (needPulledAtomConfig) {
- let pullAtomConfig = new StatsdPullAtomConfig();
+ const pullAtomConfig = new StatsdPullAtomConfig();
if (uiCfg.androidStatsdRawPulledAtoms.length > 0) {
for (const line of uiCfg.androidStatsdRawPulledAtoms.split('\n')) {
if (line.trim().length > 0) {
@@ -479,8 +479,9 @@
}
}
}
- pullAtomConfig.pullAtomId =
- uiCfg.androidStatsdPulledAtoms.map((atom) => atom as any as AtomId);
+ pullAtomConfig.pullAtomId = uiCfg.androidStatsdPulledAtoms.map(
+ (atom) => atom as unknown as AtomId,
+ );
pullAtomConfig.pullFrequencyMs =
uiCfg.androidStatsdPulledAtomPullFrequencyMs;
if (uiCfg.androidStatsdPulledAtomPackages.length > 0) {
diff --git a/ui/src/common/recordingV2/recording_config_utils_unittest.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils_unittest.ts
similarity index 96%
rename from ui/src/common/recordingV2/recording_config_utils_unittest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils_unittest.ts
index 67ac112..dd96a69 100644
--- a/ui/src/common/recordingV2/recording_config_utils_unittest.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils_unittest.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {createEmptyRecordConfig} from '../../controller/record_config_types';
+import {createEmptyRecordConfig} from '../record_config_types';
import {genTraceConfig} from './recording_config_utils';
import {AndroidTargetInfo} from './recording_interfaces_v2';
diff --git a/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_error_handling.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_error_handling.ts
new file mode 100644
index 0000000..ba86e65
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_error_handling.ts
@@ -0,0 +1,263 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {getErrorMessage} from '../../../base/errors';
+import {showModal} from '../../../widgets/modal';
+import {OnMessageCallback} from './recording_interfaces_v2';
+import {
+ ALLOW_USB_DEBUGGING,
+ BINARY_PUSH_FAILURE,
+ BINARY_PUSH_UNKNOWN_RESPONSE,
+ EXTENSION_NOT_INSTALLED,
+ EXTENSION_URL,
+ NO_DEVICE_SELECTED,
+ PARSING_UNABLE_TO_DECODE_METHOD,
+ PARSING_UNKNWON_REQUEST_ID,
+ PARSING_UNRECOGNIZED_MESSAGE,
+ PARSING_UNRECOGNIZED_PORT,
+ WEBSOCKET_UNABLE_TO_CONNECT,
+} from './recording_utils';
+
+// The pattern for handling recording error can have the following nesting in
+// case of errors:
+// A. wrapRecordingError -> wraps a promise
+// B. onFailure -> has user defined logic and calls showRecordingModal
+// C. showRecordingModal -> shows UX for a given error; this is not called
+// directly by wrapRecordingError, because we want the caller (such as the
+// UI) to dictate the UX
+
+// This method takes a promise and a callback to be execute in case the promise
+// fails. It then awaits the promise and executes the callback in case of
+// failure. In the recording code it is used to wrap:
+// 1. Acessing the WebUSB API.
+// 2. Methods returning promises which can be rejected. For instance:
+// a) When the user clicks 'Add a new device' but then doesn't select a valid
+// device.
+// b) When the user starts a tracing session, but cancels it before they
+// authorize the session on the device.
+export async function wrapRecordingError<T>(
+ promise: Promise<T>,
+ onFailure: OnMessageCallback,
+): Promise<T | undefined> {
+ try {
+ return await promise;
+ } catch (e) {
+ // Sometimes the message is wrapped in an Error object, sometimes not, so
+ // we make sure we transform it into a string.
+ const errorMessage = getErrorMessage(e);
+ onFailure(errorMessage);
+ return undefined;
+ }
+}
+
+// Shows a modal for every known type of error which can arise during recording.
+// In this way, errors occuring at different levels of the recording process
+// can be handled in a central location.
+export function showRecordingModal(message: string): void {
+ if (
+ [
+ 'Unable to claim interface.',
+ 'The specified endpoint is not part of a claimed and selected ' +
+ 'alternate interface.',
+ // thrown when calling the 'reset' method on a WebUSB device.
+ 'Unable to reset the device.',
+ ].some((partOfMessage) => message.includes(partOfMessage))
+ ) {
+ showWebUSBErrorV2();
+ } else if (
+ [
+ 'A transfer error has occurred.',
+ 'The device was disconnected.',
+ 'The transfer was cancelled.',
+ ].some((partOfMessage) => message.includes(partOfMessage)) ||
+ isDeviceDisconnectedError(message)
+ ) {
+ showConnectionLostError();
+ } else if (message === ALLOW_USB_DEBUGGING) {
+ showAllowUSBDebugging();
+ } else if (
+ isMessageComposedOf(message, [
+ BINARY_PUSH_FAILURE,
+ BINARY_PUSH_UNKNOWN_RESPONSE,
+ ])
+ ) {
+ showFailedToPushBinary(message.substring(message.indexOf(':') + 1));
+ } else if (message === NO_DEVICE_SELECTED) {
+ showNoDeviceSelected();
+ } else if (WEBSOCKET_UNABLE_TO_CONNECT === message) {
+ showWebsocketConnectionIssue(message);
+ } else if (message === EXTENSION_NOT_INSTALLED) {
+ showExtensionNotInstalled();
+ } else if (
+ isMessageComposedOf(message, [
+ PARSING_UNKNWON_REQUEST_ID,
+ PARSING_UNABLE_TO_DECODE_METHOD,
+ PARSING_UNRECOGNIZED_PORT,
+ PARSING_UNRECOGNIZED_MESSAGE,
+ ])
+ ) {
+ showIssueParsingTheTracedResponse(message);
+ } else {
+ throw new Error(`${message}`);
+ }
+}
+
+function isDeviceDisconnectedError(message: string) {
+ return (
+ message.includes('Device with serial') &&
+ message.includes('was disconnected.')
+ );
+}
+
+function isMessageComposedOf(message: string, issues: string[]) {
+ for (const issue of issues) {
+ if (message.includes(issue)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Exception thrown by the Recording logic.
+export class RecordingError extends Error {}
+
+function showWebUSBErrorV2() {
+ showModal({
+ title: 'A WebUSB error occurred',
+ content: m(
+ 'div',
+ m(
+ 'span',
+ `Is adb already running on the host? Run this command and
+ try again.`,
+ ),
+ m('br'),
+ m('.modal-bash', '> adb kill-server'),
+ m('br'),
+ // The statement below covers the following edge case:
+ // 1. 'adb server' is running on the device.
+ // 2. The user selects the new Android target, so we try to fetch the
+ // OS version and do QSS.
+ // 3. The error modal is shown.
+ // 4. The user runs 'adb kill-server'.
+ // At this point we don't have a trigger to try fetching the OS version
+ // + QSS again. Therefore, the user will need to refresh the page.
+ m(
+ 'span',
+ "If after running 'adb kill-server', you don't see " +
+ "a 'Start Recording' button on the page and you don't see " +
+ "'Allow USB debugging' on the device, " +
+ 'you will need to reload this page.',
+ ),
+ m('br'),
+ m('br'),
+ m('span', 'For details see '),
+ m('a', {href: 'http://b/159048331', target: '_blank'}, 'b/159048331'),
+ ),
+ });
+}
+
+function showConnectionLostError(): void {
+ showModal({
+ title: 'Connection with the ADB device lost',
+ content: m(
+ 'div',
+ m('span', `Please connect the device again to restart the recording.`),
+ m('br'),
+ ),
+ });
+}
+
+function showAllowUSBDebugging(): void {
+ showModal({
+ title: 'Could not connect to the device',
+ content: m(
+ 'div',
+ m('span', 'Please allow USB debugging on the device.'),
+ m('br'),
+ ),
+ });
+}
+
+function showNoDeviceSelected(): void {
+ showModal({
+ title: 'No device was selected for recording',
+ content: m(
+ 'div',
+ m(
+ 'span',
+ `If you want to connect to an ADB device,
+ please select it from the list.`,
+ ),
+ m('br'),
+ ),
+ });
+}
+
+function showExtensionNotInstalled(): void {
+ showModal({
+ title: 'Perfetto Chrome extension not installed',
+ content: m(
+ 'div',
+ m(
+ '.note',
+ `To trace Chrome from the Perfetto UI, you need to install our `,
+ m('a', {href: EXTENSION_URL, target: '_blank'}, 'Chrome extension'),
+ ' and then reload this page.',
+ ),
+ m('br'),
+ ),
+ });
+}
+
+function showIssueParsingTheTracedResponse(message: string): void {
+ showModal({
+ title:
+ 'A problem was encountered while connecting to' +
+ ' the Perfetto tracing service',
+ content: m('div', m('span', message), m('br')),
+ });
+}
+
+function showFailedToPushBinary(message: string): void {
+ showModal({
+ title: 'Failed to push a binary to the device',
+ content: m(
+ 'div',
+ m(
+ 'span',
+ 'This can happen if your Android device has an OS version lower ' +
+ 'than Q. Perfetto tried to push the latest version of its ' +
+ 'embedded binary but failed.',
+ ),
+ m('br'),
+ m('br'),
+ m('span', 'Error message:'),
+ m('br'),
+ m('span', message),
+ ),
+ });
+}
+
+function showWebsocketConnectionIssue(message: string): void {
+ showModal({
+ title: 'Unable to connect to the device via websocket',
+ content: m(
+ 'div',
+ m('div', 'trace_processor_shell --httpd is unreachable or crashed.'),
+ m('pre', message),
+ ),
+ });
+}
diff --git a/ui/src/common/recordingV2/recording_interfaces_v2.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_interfaces_v2.ts
similarity index 99%
rename from ui/src/common/recordingV2/recording_interfaces_v2.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_interfaces_v2.ts
index 954a145..c8a030e 100644
--- a/ui/src/common/recordingV2/recording_interfaces_v2.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_interfaces_v2.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {TraceConfig} from '../../protos';
+import {TraceConfig} from '../../../protos';
// TargetFactory connects, disconnects and keeps track of targets.
// There is one factory for AndroidWebusb, AndroidWebsocket, Chrome etc.
diff --git a/ui/src/common/recordingV2/recording_page_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_page_controller.ts
similarity index 94%
rename from ui/src/common/recordingV2/recording_page_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_page_controller.ts
index de568aa..76617d5 100644
--- a/ui/src/common/recordingV2/recording_page_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_page_controller.ts
@@ -12,19 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists, assertTrue} from '../../base/logging';
-import {currentDateHourAndMinute} from '../../base/time';
-import {RecordingManager} from '../../controller/recording_manager';
-import {AppImpl} from '../../core/app_impl';
-import {raf} from '../../core/raf_scheduler';
-import {autosaveConfigStore} from '../../frontend/record_config';
+import {assertExists, assertTrue} from '../../../base/logging';
+import {currentDateHourAndMinute} from '../../../base/time';
+import {RecordingManager} from '../recording_manager';
+import {autosaveConfigStore} from '../record_config';
import {
DEFAULT_ADB_WEBSOCKET_URL,
DEFAULT_TRACED_WEBSOCKET_URL,
-} from '../../frontend/recording/recording_ui_utils';
-import {couldNotClaimInterface} from '../../frontend/recording/reset_interface_modal';
-import {TraceConfig} from '../../protos';
-import {TRACE_SUFFIX} from '../constants';
+} from '../recording_ui_utils';
+import {couldNotClaimInterface} from '../reset_interface_modal';
+import {TraceConfig} from '../../../protos';
+import {TRACE_SUFFIX} from '../../../public/trace';
import {genTraceConfig} from './recording_config_utils';
import {RecordingError, showRecordingModal} from './recording_error_handling';
import {
@@ -47,6 +45,8 @@
HostOsTargetFactory,
} from './target_factories/host_os_target_factory';
import {targetFactoryRegistry} from './target_factory_registry';
+import {scheduleFullRedraw} from '../../../widgets/raf';
+import {App} from '../../../public/app';
// The recording page can be in any of these states. It can transition between
// states:
@@ -250,6 +250,7 @@
// Keeps track of the state the Ui is in. Has methods which are executed on
// user actions such as starting/stopping/cancelling a tracing session.
export class RecordingPageController {
+ private app: App;
private recMgr: RecordingManager;
// State of the recording page. This is set by user actions and/or automatic
@@ -267,7 +268,8 @@
// transitions don't override one another in async functions.
private stateGeneration = 0;
- constructor(recMgr: RecordingManager) {
+ constructor(app: App, recMgr: RecordingManager) {
+ this.app = app;
this.recMgr = recMgr;
}
@@ -296,7 +298,7 @@
}
this.setState(state);
this.recMgr.setRecordingStatus(undefined);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
maybeClearRecordingState(tracingSessionWrapper: TracingSessionWrapper): void {
@@ -312,7 +314,7 @@
if (this.tracingSessionWrapper !== tracingSessionWrapper) {
return;
}
- AppImpl.instance.openTraceFromBuffer({
+ this.app.openTraceFromBuffer({
title: 'Recorded trace',
buffer: trace.buffer,
fileName: `trace_${currentDateHourAndMinute()}${TRACE_SUFFIX}`,
@@ -390,11 +392,11 @@
if (!this.target) {
this.setState(RecordingState.NO_TARGET);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
return;
}
this.setState(RecordingState.TARGET_SELECTED);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
this.tracingSessionWrapper = this.createTracingSessionWrapper(this.target);
this.tracingSessionWrapper.fetchTargetInfo();
@@ -431,7 +433,7 @@
const target = this.getTarget();
const targetInfo = target.getInfo();
- AppImpl.instance.analytics.logEvent(
+ this.app.analytics.logEvent(
'Record Trace',
`Record trace (${targetInfo.targetType})`,
);
@@ -484,7 +486,7 @@
// We redraw if:
// 1. We received a correct buffer usage value.
// 2. We receive a RecordingError.
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
initFactories() {
@@ -531,7 +533,7 @@
// If the change happens for an existing target, the controller keeps the
// currently selected target in focus.
if (this.target && allTargets.includes(this.target)) {
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
return;
}
// If the change happens to a new target or the controller does not have a
@@ -552,7 +554,7 @@
this.recMgr.setRecordingStatus(undefined);
// Redrawing because this method has changed the RecordingState, which will
// affect the display of the record_page.
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
private setState(state: RecordingState) {
diff --git a/ui/src/common/recordingV2/recording_utils.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_utils.ts
similarity index 100%
rename from ui/src/common/recordingV2/recording_utils.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_utils.ts
diff --git a/ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_websocket_target_factory.ts
similarity index 96%
rename from ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_websocket_target_factory.ts
index 21097eb..03cda1f 100644
--- a/ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_websocket_target_factory.ts
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {RECORDING_V2_FLAG} from '../../../core/feature_flags';
import {
OnTargetChangeCallback,
RecordingTargetV2,
@@ -22,7 +21,6 @@
buildAbdWebsocketCommand,
WEBSOCKET_CLOSED_ABNORMALLY_CODE,
} from '../recording_utils';
-import {targetFactoryRegistry} from '../target_factory_registry';
import {AndroidWebsocketTarget} from '../targets/android_websocket_target';
export const ANDROID_WEBSOCKET_TARGET_FACTORY = 'AndroidWebsocketTargetFactory';
@@ -268,8 +266,3 @@
this.onTargetChange = onTargetChange;
}
}
-
-// We only want to instantiate this class if Recording V2 is enabled.
-if (RECORDING_V2_FLAG.get()) {
- targetFactoryRegistry.register(new AndroidWebsocketTargetFactory());
-}
diff --git a/ui/src/common/recordingV2/target_factories/android_websocket_target_factory_unittest.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_websocket_target_factory_unittest.ts
similarity index 100%
rename from ui/src/common/recordingV2/target_factories/android_websocket_target_factory_unittest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_websocket_target_factory_unittest.ts
diff --git a/ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_webusb_target_factory.ts
similarity index 89%
rename from ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_webusb_target_factory.ts
index d27ab07..a969c31 100644
--- a/ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/android_webusb_target_factory.ts
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {getErrorMessage} from '../../../base/errors';
-import {assertExists} from '../../../base/logging';
-import {RECORDING_V2_FLAG} from '../../../core/feature_flags';
+import {getErrorMessage} from '../../../../base/errors';
+import {assertExists} from '../../../../base/logging';
import {AdbKeyManager} from '../auth/adb_key_manager';
import {RecordingError} from '../recording_error_handling';
import {
@@ -23,7 +22,6 @@
TargetFactory,
} from '../recording_interfaces_v2';
import {ADB_DEVICE_FILTER, findInterfaceAndEndpoint} from '../recording_utils';
-import {targetFactoryRegistry} from '../target_factory_registry';
import {AndroidWebusbTarget} from '../targets/android_webusb_target';
export const ANDROID_WEBUSB_TARGET_FACTORY = 'AndroidWebusbTargetFactory';
@@ -155,11 +153,3 @@
return deviceValidity;
}
}
-
-// We only want to instantiate this class if:
-// 1. The browser implements the USB functionality.
-// 2. Recording V2 is enabled.
-// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
-if (navigator.usb && RECORDING_V2_FLAG.get()) {
- targetFactoryRegistry.register(new AndroidWebusbTargetFactory(navigator.usb));
-}
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/chrome_target_factory.ts
similarity index 100%
rename from ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/chrome_target_factory.ts
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/chrome_target_factory_unittest.ts
similarity index 100%
rename from ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/chrome_target_factory_unittest.ts
diff --git a/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/host_os_target_factory.ts
similarity index 100%
rename from ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/host_os_target_factory.ts
diff --git a/ui/src/common/recordingV2/target_factories/index.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/index.ts
similarity index 100%
rename from ui/src/common/recordingV2/target_factories/index.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/index.ts
diff --git a/ui/src/common/recordingV2/target_factories/virtual_target_factory.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/virtual_target_factory.ts
similarity index 100%
rename from ui/src/common/recordingV2/target_factories/virtual_target_factory.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factories/virtual_target_factory.ts
diff --git a/ui/src/common/recordingV2/target_factory_registry.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factory_registry.ts
similarity index 96%
rename from ui/src/common/recordingV2/target_factory_registry.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factory_registry.ts
index e8de655..b34070d 100644
--- a/ui/src/common/recordingV2/target_factory_registry.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/target_factory_registry.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Registry} from '../../base/registry';
+import {Registry} from '../../../base/registry';
import {RecordingTargetV2, TargetFactory} from './recording_interfaces_v2';
export class TargetFactoryRegistry extends Registry<TargetFactory> {
diff --git a/ui/src/common/recordingV2/targets/android_target.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_target.ts
similarity index 96%
rename from ui/src/common/recordingV2/targets/android_target.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_target.ts
index 926846d..0bac1e4 100644
--- a/ui/src/common/recordingV2/targets/android_target.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_target.ts
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {fetchWithTimeout} from '../../../base/http_utils';
-import {exists} from '../../../base/utils';
-import {VERSION} from '../../../gen/perfetto_version';
+import {fetchWithTimeout} from '../../../../base/http_utils';
+import {exists} from '../../../../base/utils';
+import {VERSION} from '../../../../gen/perfetto_version';
import {AdbConnectionImpl} from '../adb_connection_impl';
import {
DataSource,
diff --git a/ui/src/common/recordingV2/targets/android_virtual_target.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_virtual_target.ts
similarity index 100%
rename from ui/src/common/recordingV2/targets/android_virtual_target.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_virtual_target.ts
diff --git a/ui/src/common/recordingV2/targets/android_websocket_target.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_websocket_target.ts
similarity index 100%
rename from ui/src/common/recordingV2/targets/android_websocket_target.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_websocket_target.ts
diff --git a/ui/src/common/recordingV2/targets/android_webusb_target.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_webusb_target.ts
similarity index 96%
rename from ui/src/common/recordingV2/targets/android_webusb_target.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_webusb_target.ts
index e70a19a..dc6e64d 100644
--- a/ui/src/common/recordingV2/targets/android_webusb_target.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/android_webusb_target.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists} from '../../../base/logging';
+import {assertExists} from '../../../../base/logging';
import {AdbConnectionOverWebusb} from '../adb_connection_over_webusb';
import {AdbKeyManager} from '../auth/adb_key_manager';
import {OnTargetChangeCallback, TargetInfo} from '../recording_interfaces_v2';
diff --git a/ui/src/common/recordingV2/targets/chrome_target.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/chrome_target.ts
similarity index 100%
rename from ui/src/common/recordingV2/targets/chrome_target.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/chrome_target.ts
diff --git a/ui/src/common/recordingV2/targets/host_os_target.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/host_os_target.ts
similarity index 100%
rename from ui/src/common/recordingV2/targets/host_os_target.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/targets/host_os_target.ts
diff --git a/ui/src/common/recordingV2/traced_tracing_session.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/traced_tracing_session.ts
similarity index 98%
rename from ui/src/common/recordingV2/traced_tracing_session.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/traced_tracing_session.ts
index c0ba444..8687432 100644
--- a/ui/src/common/recordingV2/traced_tracing_session.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/traced_tracing_session.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import protobuf from 'protobufjs/minimal';
-import {defer, Deferred} from '../../base/deferred';
-import {assertExists, assertFalse, assertTrue} from '../../base/logging';
+import {defer, Deferred} from '../../../base/deferred';
+import {assertExists, assertFalse, assertTrue} from '../../../base/logging';
import {
DisableTracingRequest,
DisableTracingResponse,
@@ -33,7 +33,7 @@
ReadBuffersRequest,
ReadBuffersResponse,
TraceConfig,
-} from '../../protos';
+} from '../../../protos';
import {RecordingError} from './recording_error_handling';
import {
ByteStream,
@@ -50,7 +50,7 @@
PARSING_UNRECOGNIZED_PORT,
RECORDING_IN_PROGRESS,
} from './recording_utils';
-import {exists} from '../../base/utils';
+import {exists} from '../../../base/utils';
// See wire_protocol.proto for more details.
const WIRE_PROTOCOL_HEADER_SIZE = 4;
diff --git a/ui/src/common/recordingV2/websocket_menu_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/websocket_menu_controller.ts
similarity index 97%
rename from ui/src/common/recordingV2/websocket_menu_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/websocket_menu_controller.ts
index 8b800a7..2da8f5b 100644
--- a/ui/src/common/recordingV2/websocket_menu_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/websocket_menu_controller.ts
@@ -16,7 +16,7 @@
ADB_ENDPOINT,
DEFAULT_WEBSOCKET_URL,
TRACED_ENDPOINT,
-} from '../../frontend/recording/recording_ui_utils';
+} from '../recording_ui_utils';
import {TargetFactory} from './recording_interfaces_v2';
import {
ANDROID_WEBSOCKET_TARGET_FACTORY,
diff --git a/ui/src/controller/recording_manager.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_manager.ts
similarity index 87%
rename from ui/src/controller/recording_manager.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_manager.ts
index 5d96017..be29691 100644
--- a/ui/src/controller/recording_manager.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_manager.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {createEmptyState} from '../common/empty_state';
+import {createEmptyState} from './empty_state';
import {
AdbRecordingTarget,
LoadedConfig,
@@ -20,39 +20,41 @@
RecordingTarget,
getDefaultRecordingTargets,
isAdbTarget,
-} from '../common/state';
-import {RECORDING_V2_FLAG} from '../core/feature_flags';
-import {raf} from '../core/raf_scheduler';
+} from './state';
import {AdbOverWebUsb} from './adb';
import {isGetCategoriesResponse} from './chrome_proxy_record_controller';
import {RecordConfig, createEmptyRecordConfig} from './record_config_types';
import {RecordController} from './record_controller';
+import {scheduleFullRedraw} from '../../widgets/raf';
+import {App} from '../../public/app';
+import {targetFactoryRegistry} from './recordingV2/target_factory_registry';
+import {AndroidWebsocketTargetFactory} from './recordingV2/target_factories/android_websocket_target_factory';
+import {AndroidWebusbTargetFactory} from './recordingV2/target_factories/android_webusb_target_factory';
+import {exists} from '../../base/utils';
const EXTENSION_ID = 'lfmkphfpdbjijhpomgecfikhfohaoine';
// TODO(primiano): this class and RecordController should be merged. I'm keeping
// them separate for now to reduce scope of refactorings.
export class RecordingManager {
+ readonly app: App;
private _state: RecordingState = createEmptyState();
private recCtl: RecordController;
- // TODO(primiano): this singleton is temporary. RecordingManager shoudl be
- // injected in all the recording pages and the instance should be created and
- // owned by the recording plugin. But for now we don't have a plugin.
- private static _instance: RecordingManager | undefined = undefined;
- static get instance() {
- if (this._instance === undefined) {
- this._instance = new RecordingManager();
- }
- return this._instance;
- }
-
- constructor() {
+ constructor(app: App, useRecordingV2: boolean) {
+ this.app = app;
const extensionLocalChannel = new MessageChannel();
- this.recCtl = new RecordController(this, extensionLocalChannel.port1);
+ this.recCtl = new RecordController(app, this, extensionLocalChannel.port1);
this.setupExtentionPort(extensionLocalChannel);
- if (!RECORDING_V2_FLAG.get()) {
+ if (useRecordingV2) {
+ targetFactoryRegistry.register(new AndroidWebsocketTargetFactory());
+ if (exists(navigator.usb)) {
+ targetFactoryRegistry.register(
+ new AndroidWebusbTargetFactory(navigator.usb),
+ );
+ }
+ } else {
this.updateAvailableAdbDevices();
try {
navigator.usb.addEventListener('connect', () =>
@@ -156,7 +158,7 @@
(message: object, _port: chrome.runtime.Port) => {
if (isGetCategoriesResponse(message)) {
this._state.chromeCategories = message.categories;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
return;
}
extensionLocalChannel.port2.postMessage(message);
@@ -191,7 +193,7 @@
this.setAvailableAdbDevices(availableAdbDevices);
this.selectAndroidDeviceIfAvailable(availableAdbDevices, recordingTarget);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
return availableAdbDevices;
}
diff --git a/ui/src/frontend/recording/recording_multiple_choice.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_multiple_choice.ts
similarity index 93%
rename from ui/src/frontend/recording/recording_multiple_choice.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_multiple_choice.ts
index 27a83fc..0e34f5c 100644
--- a/ui/src/frontend/recording/recording_multiple_choice.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_multiple_choice.ts
@@ -16,9 +16,9 @@
import {
RecordingTargetV2,
TargetFactory,
-} from '../../common/recordingV2/recording_interfaces_v2';
-import {RecordingPageController} from '../../common/recordingV2/recording_page_controller';
-import {RECORDING_MODAL_DIALOG_KEY} from '../../common/recordingV2/recording_utils';
+} from './recordingV2/recording_interfaces_v2';
+import {RecordingPageController} from './recordingV2/recording_page_controller';
+import {RECORDING_MODAL_DIALOG_KEY} from './recordingV2/recording_utils';
import {closeModal} from '../../widgets/modal';
interface RecordingMultipleChoiceAttrs {
diff --git a/ui/src/frontend/recording/recording_sections.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_sections.ts
similarity index 86%
rename from ui/src/frontend/recording/recording_sections.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_sections.ts
index 03ebff8..c83b9e0 100644
--- a/ui/src/frontend/recording/recording_sections.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_sections.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
-import {RecordingState} from '../../common/state';
+import {DataSource} from './recordingV2/recording_interfaces_v2';
+import {RecordingState} from './state';
export interface RecordingSectionAttrs {
recState: RecordingState;
diff --git a/ui/src/frontend/recording/recording_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_settings.ts
similarity index 96%
rename from ui/src/frontend/recording/recording_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_settings.ts
index c61b49d..e3058be 100644
--- a/ui/src/frontend/recording/recording_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_settings.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import m from 'mithril';
-import {RecordMode} from '../../common/state';
-import {Slider} from '../record_widgets';
+import {RecordMode} from './state';
+import {Slider} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
import {assetSrc} from '../../base/assets';
diff --git a/ui/src/frontend/recording/recording_ui_utils.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_ui_utils.ts
similarity index 100%
rename from ui/src/frontend/recording/recording_ui_utils.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_ui_utils.ts
diff --git a/ui/src/frontend/recording/reset_interface_modal.ts b/ui/src/plugins/dev.perfetto.RecordTrace/reset_interface_modal.ts
similarity index 100%
rename from ui/src/frontend/recording/reset_interface_modal.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/reset_interface_modal.ts
diff --git a/ui/src/frontend/recording/reset_target_modal.ts b/ui/src/plugins/dev.perfetto.RecordTrace/reset_target_modal.ts
similarity index 91%
rename from ui/src/frontend/recording/reset_target_modal.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/reset_target_modal.ts
index 4d3feb3..4d3d048 100644
--- a/ui/src/frontend/recording/reset_target_modal.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/reset_target_modal.ts
@@ -13,19 +13,19 @@
// limitations under the License.
import m from 'mithril';
-import {RecordingPageController} from '../../common/recordingV2/recording_page_controller';
+import {RecordingPageController} from './recordingV2/recording_page_controller';
import {
EXTENSION_URL,
RECORDING_MODAL_DIALOG_KEY,
-} from '../../common/recordingV2/recording_utils';
+} from './recordingV2/recording_utils';
import {
CHROME_TARGET_FACTORY,
ChromeTargetFactory,
-} from '../../common/recordingV2/target_factories/chrome_target_factory';
-import {targetFactoryRegistry} from '../../common/recordingV2/target_factory_registry';
-import {WebsocketMenuController} from '../../common/recordingV2/websocket_menu_controller';
+} from './recordingV2/target_factories/chrome_target_factory';
+import {targetFactoryRegistry} from './recordingV2/target_factory_registry';
+import {WebsocketMenuController} from './recordingV2/websocket_menu_controller';
import {closeModal, showModal} from '../../widgets/modal';
-import {CodeSnippet} from '../record_widgets';
+import {CodeSnippet} from './record_widgets';
import {RecordingMultipleChoice} from './recording_multiple_choice';
const RUN_WEBSOCKET_CMD =
diff --git a/ui/src/common/state.ts b/ui/src/plugins/dev.perfetto.RecordTrace/state.ts
similarity index 98%
rename from ui/src/common/state.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/state.ts
index 9c361f6..b94074b 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/state.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {RecordConfig} from '../controller/record_config_types';
+import {RecordConfig} from './record_config_types';
export const MAX_TIME = 180;
diff --git a/ui/src/core/trace_config_utils.ts b/ui/src/plugins/dev.perfetto.RecordTrace/trace_config_utils.ts
similarity index 96%
rename from ui/src/core/trace_config_utils.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/trace_config_utils.ts
index e05f711..c7697dd 100644
--- a/ui/src/core/trace_config_utils.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/trace_config_utils.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {EnableTracingRequest, TraceConfig} from '../protos';
+import {EnableTracingRequest, TraceConfig} from '../../protos';
// In this file are contained a few functions to simplify the proto parsing.
diff --git a/ui/src/plugins/dev.perfetto.Sched/index.ts b/ui/src/plugins/dev.perfetto.Sched/index.ts
index a72c0f3..68d3d0b 100644
--- a/ui/src/plugins/dev.perfetto.Sched/index.ts
+++ b/ui/src/plugins/dev.perfetto.Sched/index.ts
@@ -17,7 +17,10 @@
import {Trace} from '../../public/trace';
import {PerfettoPlugin} from '../../public/plugin';
import {ActiveCPUCountTrack, CPUType} from './active_cpu_count';
-import {RunnableThreadCountTrack} from './runnable_thread_count';
+import {
+ RunnableThreadCountTrack,
+ UninterruptibleSleepThreadCountTrack,
+} from './thread_count';
import {getSchedTable} from './table';
import {extensions} from '../../public/lib/extensions';
@@ -40,6 +43,26 @@
addPinnedTrack(ctx, runnableThreadCountUri, 'Runnable thread count'),
});
+ const uninterruptibleSleepThreadCountUri = `/uninterruptible_sleep_thread_count`;
+ ctx.tracks.registerTrack({
+ uri: uninterruptibleSleepThreadCountUri,
+ title: 'Uninterruptible Sleep thread count',
+ track: new UninterruptibleSleepThreadCountTrack({
+ trace: ctx,
+ uri: uninterruptibleSleepThreadCountUri,
+ }),
+ });
+ ctx.commands.registerCommand({
+ id: 'dev.perfetto.Sched.AddUninterruptibleSleepThreadCountTrackCommand',
+ name: 'Add track: uninterruptible sleep thread count',
+ callback: () =>
+ addPinnedTrack(
+ ctx,
+ uninterruptibleSleepThreadCountUri,
+ 'Uninterruptible Sleep thread count',
+ ),
+ });
+
const uri = uriForActiveCPUCountTrack();
const title = 'Active CPU count';
ctx.tracks.registerTrack({
diff --git a/ui/src/plugins/dev.perfetto.Sched/runnable_thread_count.ts b/ui/src/plugins/dev.perfetto.Sched/thread_count.ts
similarity index 77%
rename from ui/src/plugins/dev.perfetto.Sched/runnable_thread_count.ts
rename to ui/src/plugins/dev.perfetto.Sched/thread_count.ts
index 9b5e9c5..88fe892 100644
--- a/ui/src/plugins/dev.perfetto.Sched/runnable_thread_count.ts
+++ b/ui/src/plugins/dev.perfetto.Sched/thread_count.ts
@@ -18,7 +18,7 @@
} from '../../frontend/base_counter_track';
import {NewTrackArgs} from '../../frontend/track';
-export class RunnableThreadCountTrack extends BaseCounterTrack {
+abstract class ThreadCountTrack extends BaseCounterTrack {
constructor(args: NewTrackArgs) {
super(args);
}
@@ -35,7 +35,9 @@
`INCLUDE PERFETTO MODULE sched.thread_level_parallelism`,
);
}
+}
+export class RunnableThreadCountTrack extends ThreadCountTrack {
getSqlSource() {
return `
select
@@ -45,3 +47,14 @@
`;
}
}
+
+export class UninterruptibleSleepThreadCountTrack extends ThreadCountTrack {
+ getSqlSource() {
+ return `
+ select
+ ts,
+ uninterruptible_sleep_thread_count as value
+ from sched_uninterruptible_sleep_thread_count
+ `;
+ }
+}
diff --git a/ui/src/plugins/dev.perfetto.ThreadState/table.ts b/ui/src/plugins/dev.perfetto.ThreadState/table.ts
index 9c86c5c..c93fb40 100644
--- a/ui/src/plugins/dev.perfetto.ThreadState/table.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/table.ts
@@ -27,7 +27,7 @@
return {
name: 'thread_state',
columns: [
- new ThreadStateIdColumn('id', {notNull: true}),
+ new ThreadStateIdColumn('threadStateSqlId', {notNull: true}),
new TimestampColumn('ts'),
new DurationColumn('dur'),
new StandardColumn('state'),
@@ -46,11 +46,11 @@
},
{title: 'upid (process)', notNull: true},
),
- new StandardColumn('io_wait', {aggregationType: 'nominal'}),
- new StandardColumn('blocked_function'),
- new ThreadColumn('waker_utid', {title: 'Waker thread'}),
- new ThreadStateIdColumn('waker_id'),
- new StandardColumn('irq_context', {aggregationType: 'nominal'}),
+ new StandardColumn('ioWait', {aggregationType: 'nominal'}),
+ new StandardColumn('blockedFunction'),
+ new ThreadColumn('wakerUtid', {title: 'Waker thread'}),
+ new ThreadStateIdColumn('wakerId'),
+ new StandardColumn('irqContext', {aggregationType: 'nominal'}),
new StandardColumn('ucpu', {
aggregationType: 'nominal',
startsHidden: true,
diff --git a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
index b790f1d..0a2287b7 100644
--- a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
@@ -52,7 +52,7 @@
}
export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
- private state?: ThreadState;
+ private threadState?: ThreadState;
private relatedStates?: RelatedThreadStates;
constructor(
@@ -62,9 +62,9 @@
async load() {
const id = this.id;
- this.state = await getThreadState(this.trace.engine, id);
+ this.threadState = await getThreadState(this.trace.engine, id);
- if (!this.state) {
+ if (!this.threadState) {
return;
}
@@ -72,8 +72,8 @@
relatedStates.prev = (
await getThreadStateFromConstraints(this.trace.engine, {
filters: [
- `ts + dur = ${this.state.ts}`,
- `utid = ${this.state.thread?.utid}`,
+ `ts + dur = ${this.threadState.ts}`,
+ `utid = ${this.threadState.thread?.utid}`,
],
limit: 1,
})
@@ -81,22 +81,30 @@
relatedStates.next = (
await getThreadStateFromConstraints(this.trace.engine, {
filters: [
- `ts = ${this.state.ts + this.state.dur}`,
- `utid = ${this.state.thread?.utid}`,
+ `ts = ${this.threadState.ts + this.threadState.dur}`,
+ `utid = ${this.threadState.thread?.utid}`,
],
limit: 1,
})
)[0];
- if (this.state.wakerId !== undefined) {
+ if (this.threadState.wakerId !== undefined) {
relatedStates.waker = await getThreadState(
this.trace.engine,
- this.state.wakerId,
+ this.threadState.wakerId,
+ );
+ } else if (
+ this.threadState.state == 'Running' &&
+ relatedStates.prev.wakerId != undefined
+ ) {
+ relatedStates.waker = await getThreadState(
+ this.trace.engine,
+ relatedStates.prev.wakerId,
);
}
// note: this might be valid even if there is no |waker| slice, in the case
// of an interrupt wakeup while in the idle process (which is omitted from
// the thread_state table).
- relatedStates.wakerInterruptCtx = this.state.wakerInterruptCtx;
+ relatedStates.wakerInterruptCtx = this.threadState.wakerInterruptCtx;
relatedStates.wakee = await getThreadStateFromConstraints(
this.trace.engine,
@@ -121,7 +129,7 @@
m(
Section,
{title: 'Details'},
- this.state && this.renderTree(this.state),
+ this.threadState && this.renderTree(this.threadState),
),
m(
Section,
@@ -133,33 +141,37 @@
}
private renderLoadingText() {
- if (!this.state) {
+ if (!this.threadState) {
return 'Loading';
}
return this.id;
}
- private renderTree(state: ThreadState) {
- const thread = state.thread;
- const process = state.thread?.process;
+ private renderTree(threadState: ThreadState) {
+ const thread = threadState.thread;
+ const process = threadState.thread?.process;
return m(
Tree,
m(TreeNode, {
left: 'Start time',
- right: m(Timestamp, {ts: state.ts}),
+ right: m(Timestamp, {ts: threadState.ts}),
}),
m(TreeNode, {
left: 'Duration',
- right: m(DurationWidget, {dur: state.dur}),
+ right: m(DurationWidget, {dur: threadState.dur}),
}),
m(TreeNode, {
left: 'State',
- right: this.renderState(state.state, state.cpu, state.schedSqlId),
+ right: this.renderState(
+ threadState.state,
+ threadState.cpu,
+ threadState.schedSqlId,
+ ),
}),
- state.blockedFunction &&
+ threadState.blockedFunction &&
m(TreeNode, {
left: 'Blocked function',
- right: state.blockedFunction,
+ right: threadState.blockedFunction,
}),
process &&
m(TreeNode, {
@@ -167,9 +179,14 @@
right: getProcessName(process),
}),
thread && m(TreeNode, {left: 'Thread', right: getThreadName(thread)}),
+ threadState.priority !== undefined &&
+ m(TreeNode, {
+ left: 'Priority',
+ right: threadState.priority,
+ }),
m(TreeNode, {
left: 'SQL ID',
- right: m(SqlRef, {table: 'thread_state', id: state.threadStateSqlId}),
+ right: m(SqlRef, {table: 'thread_state', id: threadState.id}),
}),
);
}
@@ -197,18 +214,18 @@
}
private renderRelatedThreadStates(): m.Children {
- if (this.state === undefined || this.relatedStates === undefined) {
+ if (this.threadState === undefined || this.relatedStates === undefined) {
return 'Loading';
}
- const startTs = this.state.ts;
+ const startTs = this.threadState.ts;
const renderRef = (state: ThreadState, name?: string) =>
m(ThreadStateRef, {
- id: state.threadStateSqlId,
+ id: state.id,
name,
});
- const nameForNextOrPrev = (state: ThreadState) =>
- `${state.state} for ${renderDuration(state.dur)}`;
+ const nameForNextOrPrev = (threadState: ThreadState) =>
+ `${threadState.state} for ${renderDuration(threadState.dur)}`;
const renderWaker = (related: RelatedThreadStates) => {
// Could be absent if:
@@ -294,7 +311,7 @@
onclick: () => {
this.trace.commands.runCommand(
CRITICAL_PATH_LITE_CMD,
- this.state?.thread?.utid,
+ this.threadState?.thread?.utid,
);
},
}),
@@ -305,7 +322,7 @@
onclick: () => {
this.trace.commands.runCommand(
CRITICAL_PATH_CMD,
- this.state?.thread?.utid,
+ this.threadState?.thread?.utid,
);
},
}),
@@ -313,6 +330,6 @@
}
isLoading() {
- return this.state === undefined || this.relatedStates === undefined;
+ return this.threadState === undefined || this.relatedStates === undefined;
}
}
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
index 0b5a2e3..7bbcace 100644
--- a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
+++ b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
@@ -58,6 +58,7 @@
import {Chip, ChipBar} from '../../widgets/chip';
import {TrackWidget} from '../../widgets/track_widget';
import {scheduleFullRedraw} from '../../widgets/raf';
+import {CopyableLink} from '../../widgets/copyable_link';
const DATA_ENGLISH_LETTER_FREQUENCY = {
table: [
@@ -782,6 +783,17 @@
},
}),
m(WidgetShowcase, {
+ label: 'CopyableLink',
+ renderWidget: ({noicon}) =>
+ m(CopyableLink, {
+ noicon: arg(noicon, true),
+ url: 'https://perfetto.dev/docs/',
+ }),
+ initialOpts: {
+ noicon: false,
+ },
+ }),
+ m(WidgetShowcase, {
label: 'Table',
renderWidget: () => m(TableShowcase),
initialOpts: {},
diff --git a/ui/src/public/app.ts b/ui/src/public/app.ts
index 57bba08..50def57 100644
--- a/ui/src/public/app.ts
+++ b/ui/src/public/app.ts
@@ -20,6 +20,7 @@
import {PluginManager} from './plugin';
import {Trace} from './trace';
import {PageManager} from './page';
+import {FeatureFlagManager} from './feature_flag';
/**
* The API endpoint to interact programmaticaly with the UI before a trace has
@@ -37,6 +38,7 @@
readonly analytics: Analytics;
readonly plugins: PluginManager;
readonly pages: PageManager;
+ readonly featureFlags: FeatureFlagManager;
/**
* The parsed querystring passed when starting the app, before any navigation
@@ -58,4 +60,12 @@
* Navigate to a new page.
*/
navigate(newHash: string): void;
+
+ openTraceFromFile(file: File): void;
+ openTraceFromUrl(url: string): void;
+ openTraceFromBuffer(args: {
+ buffer: ArrayBuffer;
+ title: string;
+ fileName: string;
+ }): void;
}
diff --git a/ui/src/public/feature_flag.ts b/ui/src/public/feature_flag.ts
new file mode 100644
index 0000000..c82d38a
--- /dev/null
+++ b/ui/src/public/feature_flag.ts
@@ -0,0 +1,64 @@
+// 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.
+
+export interface FeatureFlagManager {
+ register(settings: FlagSettings): Flag;
+}
+
+export interface FlagSettings {
+ id: string;
+ defaultValue: boolean;
+ description: string;
+ name?: string;
+ devOnly?: boolean;
+}
+
+export interface Flag {
+ // A unique identifier for this flag ("magicSorting")
+ readonly id: string;
+
+ // The name of the flag the user sees ("New track sorting algorithm")
+ readonly name: string;
+
+ // A longer description which is displayed to the user.
+ // "Sort tracks using an embedded tfLite model based on your expression
+ // while waiting for the trace to load."
+ readonly description: string;
+
+ // Whether the flag defaults to true or false.
+ // If !flag.isOverridden() then flag.get() === flag.defaultValue
+ readonly defaultValue: boolean;
+
+ // Get the current value of the flag.
+ get(): boolean;
+
+ // Override the flag and persist the new value.
+ set(value: boolean): void;
+
+ // If the flag has been overridden.
+ // Note: A flag can be overridden to its default value.
+ isOverridden(): boolean;
+
+ // Reset the flag to its default setting.
+ reset(): void;
+
+ // Get the current state of the flag.
+ overriddenState(): OverrideState;
+}
+
+export enum OverrideState {
+ DEFAULT = 'DEFAULT',
+ TRUE = 'OVERRIDE_TRUE',
+ FALSE = 'OVERRIDE_FALSE',
+}
diff --git a/ui/src/public/trace.ts b/ui/src/public/trace.ts
index 6e60c79..95db546 100644
--- a/ui/src/public/trace.ts
+++ b/ui/src/public/trace.ts
@@ -96,3 +96,5 @@
export interface TraceAttrs {
trace: Trace;
}
+
+export const TRACE_SUFFIX = '.perfetto-trace';
diff --git a/ui/src/trace_processor/sql_utils/thread_state.ts b/ui/src/trace_processor/sql_utils/thread_state.ts
index 92834fe..cc10a5f 100644
--- a/ui/src/trace_processor/sql_utils/thread_state.ts
+++ b/ui/src/trace_processor/sql_utils/thread_state.ts
@@ -85,7 +85,7 @@
// Single thread state slice, corresponding to a row of |thread_slice| table.
export interface ThreadState {
// Id into |thread_state| table.
- threadStateSqlId: ThreadStateSqlId;
+ id: ThreadStateSqlId;
// Id of the corresponding entry in the |sched| table.
schedSqlId?: SchedSqlId;
// Timestamp of the beginning of this thread state in nanoseconds.
@@ -108,6 +108,8 @@
// unset even for runnable states, if the trace was recorded without
// interrupt information.
wakerInterruptCtx?: boolean;
+ // Kernel priority of this thread state.
+ priority?: number;
}
// Gets a list of thread state objects from Trace Processor with given
@@ -117,61 +119,63 @@
constraints: SQLConstraints,
): Promise<ThreadState[]> {
const query = await engine.query(`
- SELECT
- thread_state.id as threadStateSqlId,
- (select sched.id
- from sched
- where sched.ts=thread_state.ts and sched.utid=thread_state.utid
- limit 1
- ) as schedSqlId,
- ts,
- thread_state.dur as dur,
- thread_state.cpu as cpu,
- state,
- thread_state.blocked_function as blockedFunction,
- io_wait as ioWait,
- thread_state.utid as utid,
- waker_utid as wakerUtid,
- waker_id as wakerId,
- irq_context as wakerInterruptCtx
- FROM thread_state
+ WITH raw AS (
+ SELECT
+ ts.id,
+ sched.id AS sched_id,
+ ts.ts,
+ ts.dur,
+ ts.cpu,
+ ts.state,
+ ts.blocked_function,
+ ts.io_wait,
+ ts.utid,
+ ts.waker_utid,
+ ts.waker_id,
+ ts.irq_context,
+ sched.priority
+ FROM thread_state ts
+ LEFT JOIN sched USING (utid, ts)
+ )
+ SELECT * FROM raw
+
${constraintsToQuerySuffix(constraints)}`);
const it = query.iter({
- threadStateSqlId: NUM,
- schedSqlId: NUM_NULL,
+ id: NUM,
+ sched_id: NUM_NULL,
ts: LONG,
dur: LONG,
cpu: NUM_NULL,
state: STR_NULL,
- blockedFunction: STR_NULL,
- ioWait: NUM_NULL,
+ blocked_function: STR_NULL,
+ io_wait: NUM_NULL,
utid: NUM,
- wakerUtid: NUM_NULL,
- wakerId: NUM_NULL,
- wakerInterruptCtx: NUM_NULL,
+ waker_utid: NUM_NULL,
+ waker_id: NUM_NULL,
+ irq_context: NUM_NULL,
+ priority: NUM_NULL,
});
const result: ThreadState[] = [];
for (; it.valid(); it.next()) {
- const ioWait = it.ioWait === null ? undefined : it.ioWait > 0;
+ const ioWait = it.io_wait === null ? undefined : it.io_wait > 0;
- // TODO(altimin): Consider fetcing thread / process info using a single
+ // TODO(altimin): Consider fetching thread / process info using a single
// query instead of one per row.
result.push({
- threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId,
- schedSqlId: fromNumNull(it.schedSqlId) as SchedSqlId | undefined,
+ id: it.id as ThreadStateSqlId,
+ schedSqlId: fromNumNull(it.sched_id) as SchedSqlId | undefined,
ts: Time.fromRaw(it.ts),
dur: it.dur,
cpu: fromNumNull(it.cpu),
state: translateState(it.state ?? undefined, ioWait),
- blockedFunction: it.blockedFunction ?? undefined,
+ blockedFunction: it.blocked_function ?? undefined,
thread: await getThreadInfo(engine, asUtid(it.utid)),
- wakerUtid: asUtid(it.wakerUtid ?? undefined),
- wakerId: asThreadStateSqlId(it.wakerId ?? undefined),
- wakerInterruptCtx: fromNumNull(it.wakerInterruptCtx) as
- | boolean
- | undefined,
+ wakerUtid: asUtid(it.waker_id ?? undefined),
+ wakerId: asThreadStateSqlId(it.waker_id ?? undefined),
+ wakerInterruptCtx: fromNumNull(it.irq_context) as boolean | undefined,
+ priority: fromNumNull(it.priority),
});
}
return result;
diff --git a/ui/src/widgets/copyable_link.ts b/ui/src/widgets/copyable_link.ts
new file mode 100644
index 0000000..9244107
--- /dev/null
+++ b/ui/src/widgets/copyable_link.ts
@@ -0,0 +1,46 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {copyToClipboard} from '../base/clipboard';
+import {Anchor} from './anchor';
+
+interface CopyableLinkAttrs {
+ url: string;
+ text?: string; // Will use url if omitted.
+ noicon?: boolean;
+}
+
+export class CopyableLink implements m.ClassComponent<CopyableLinkAttrs> {
+ view({attrs}: m.CVnode<CopyableLinkAttrs>) {
+ const url = attrs.url;
+ return m(
+ 'div',
+ m(
+ Anchor,
+ {
+ href: url,
+ title: 'Click to copy the URL into the clipboard',
+ target: '_blank',
+ icon: attrs.noicon ? undefined : 'content_copy',
+ onclick: (e: Event) => {
+ e.preventDefault();
+ copyToClipboard(url);
+ },
+ },
+ attrs.text ?? url,
+ ),
+ );
+ }
+}
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,
});
}